Files
vercel__vercel-plugin/hooks/subagent-stop-sync.mjs
John Lindquist 8e24d0694a feat: add chainTo skill chaining, prompt signals, validation upgrades, and dedup reset on context clear
Add a chainTo frontmatter field to skills that triggers follow-up skill injection
when PostToolUse file contents match regex patterns. Add upgradeToSkill/upgradeWhy
fields to validation rules so validate errors can recommend loading a specific skill.
Register posttooluse-bash-chain.mjs in hooks.json. Add ChainToRule interface to
patterns.mts and skill-map-frontmatter.mts, with parseChainToRules() parser. Add
duplicate-key detection to the YAML parser. Reset dedup claim dir and session file
on clear/compact events in session-start-seen-skills so skills re-inject into fresh
context windows.

Add chainTo rules to: agent-browser-verify, agent-browser, ai-elements, ai-gateway,
ai-generation-persistence, ai-sdk, auth, bootstrap, chat-sdk, cms, cron-jobs,
deployments-cicd, email, env-vars, geist, investigation-mode, json-render,
marketplace, micro, ncc, next-forge, nextjs, observability, payments,
react-best-practices, routing-middleware, runtime-cache, satori, shadcn,
sign-in-with-vercel, swr, turbopack, turborepo, v0-dev, vercel-agent, vercel-api,
vercel-cli, vercel-firewall, vercel-flags, vercel-functions, vercel-queues,
vercel-sandbox, vercel-storage, verification, workflow. Add upgradeToSkill to
ai-elements and ai-sdk validate rules. Expand ai-sdk validate messages with
Run Skill() hints. Update nextjs, vercel-storage, runtime-cache, workflow, turborepo
skill bodies.

Add new skills: geistdocs (Geist design system docs), zzz-test-meta-name-mask
(test fixture). Add skills/_chain-audit.md chain coverage audit doc.

Delete .claude-plugin/marketplace.json, .claude-plugin/plugin.json (deprecated),
skills/edge-runtime/SKILL.md (consolidated into vercel-functions).

Add tests: posttooluse-chain.test.ts (4699 lines, chain injection e2e),
ai-sdk-companion.test.ts (181 lines). Expand build-skill-map.test.ts (+335 lines),
validate-rules.test.ts (+936 lines), session-start-seen-skills.test.ts (+74 lines),
skill-map-frontmatter.test.ts (+50 lines), verification-skill.test.ts (+20 lines).

Update build-manifest.ts to emit chainTo rules and upgradeToSkill fields. Rebuild
generated/skill-manifest.json, generated/skill-catalog.md, generated/build-from-skills.manifest.json.
Rebuild all compiled hooks/*.mjs. Update CLAUDE.md lexical prompt default to on.
Update vercel.md ecosystem graph, docs, and cli-reference.
2026-03-11 15:18:35 -06:00

85 lines
2.3 KiB
JavaScript
Executable File

#!/usr/bin/env node
// hooks/src/subagent-stop-sync.mts
import { appendFileSync } from "fs";
import { readFileSync } from "fs";
import { resolve } from "path";
import { tmpdir } from "os";
import { fileURLToPath } from "url";
import { listSessionKeys } from "./hook-env.mjs";
import { createLogger, logCaughtError } from "./logger.mjs";
var log = createLogger();
function parseInput() {
try {
const raw = readFileSync(0, "utf8");
if (!raw.trim()) return null;
return JSON.parse(raw);
} catch {
return null;
}
}
function ledgerPath(sessionId) {
return resolve(tmpdir(), `vercel-plugin-${sessionId}-subagent-ledger.jsonl`);
}
function appendLedger(entry) {
const path = ledgerPath(entry.session_id);
try {
appendFileSync(path, JSON.stringify(entry) + "\n", "utf-8");
} catch (error) {
logCaughtError(log, "subagent-stop-sync:append-ledger-failed", error, { path });
}
}
function main() {
const input = parseInput();
if (!input) {
process.exit(0);
}
const sessionId = input.session_id;
if (!sessionId) {
process.exit(0);
}
const agentId = input.agent_id ?? "unknown";
const agentType = input.agent_type ?? "unknown";
log.debug("subagent-stop-sync", { sessionId, agentId, agentType });
let ledgerEntryWritten = false;
try {
appendLedger({
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
session_id: sessionId,
agent_id: agentId,
agent_type: agentType,
agent_transcript_path: input.agent_transcript_path
});
ledgerEntryWritten = true;
} catch (error) {
logCaughtError(log, "subagent-stop-sync:ledger-write-failed", error, {
sessionId,
agentId
});
}
let skillsInjected = 0;
try {
const claimed = listSessionKeys(sessionId, "seen-skills", agentId !== "unknown" ? agentId : void 0);
skillsInjected = claimed.length;
} catch {
}
log.summary("subagent-stop-sync:complete", {
agent_id: agentId,
agent_type: agentType,
skills_injected: skillsInjected,
ledger_entry_written: ledgerEntryWritten
});
process.exit(0);
}
var ENTRYPOINT = fileURLToPath(import.meta.url);
var isEntrypoint = process.argv[1] ? resolve(process.argv[1]) === ENTRYPOINT : false;
if (isEntrypoint) {
main();
}
export {
appendLedger,
ledgerPath,
main,
parseInput
};