@vpxa/aikit 0.1.358 → 0.1.360

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (51) hide show
  1. package/package.json +1 -1
  2. package/packages/analyzers/dist/index.d.ts +1 -1
  3. package/packages/cli/dist/index.js +19 -19
  4. package/packages/cli/dist/init-C_WzeMTD.js +7 -0
  5. package/packages/cli/dist/{templates-Bu7dZtk_.js → templates-DUUaYk_d.js} +13 -12
  6. package/packages/core/dist/index.d.ts +43 -2
  7. package/packages/core/dist/index.js +1 -1
  8. package/packages/present/dist/annotation-layer.js +10 -10
  9. package/packages/server/dist/bin.js +1 -1
  10. package/packages/server/dist/index.js +1 -1
  11. package/packages/server/dist/observations-CfglxBMB.js +6 -0
  12. package/packages/server/dist/observations-CrQU_HVm.js +5 -0
  13. package/packages/server/dist/prelude-CNOjd5qt.js +2 -0
  14. package/packages/server/dist/prelude-Dki25eYn.js +1 -0
  15. package/packages/server/dist/sampling-B8NwOv_z.js +1 -0
  16. package/packages/server/dist/sampling-DOOgTH1B.js +2 -0
  17. package/packages/server/dist/{server-D5eu57oU.js → server-C2n9kQMF.js} +178 -183
  18. package/packages/server/dist/{server-D7gZ4K_H.js → server-CizTllz1.js} +178 -183
  19. package/packages/server/dist/{server-http-AarbD3gm.js → server-http-BaMUtb5Y.js} +1 -1
  20. package/packages/server/dist/{server-http-wnYmWFkC.js → server-http-Dd2uCMBi.js} +1 -1
  21. package/packages/server/dist/{server-stdio-BWoMqXU8.js → server-stdio-B5rk-PAI.js} +1 -1
  22. package/packages/server/dist/{server-stdio-CBGO6XiO.js → server-stdio-CtR4QNT3.js} +1 -1
  23. package/packages/tools/dist/index.js +69 -69
  24. package/scaffold/AUTOMATION-BOUNDARY-ANALYSIS.md +78 -0
  25. package/scaffold/dist/adapters/_shared.mjs +7 -6
  26. package/scaffold/dist/adapters/claude-code.mjs +7 -7
  27. package/scaffold/dist/adapters/claude-desktop.mjs +7 -0
  28. package/scaffold/dist/adapters/gemini.mjs +6 -6
  29. package/scaffold/dist/adapters/hermes.mjs +5 -5
  30. package/scaffold/dist/adapters/hooks.mjs +1 -1
  31. package/scaffold/dist/adapters/opencode.mjs +7 -7
  32. package/scaffold/dist/definitions/exec-hooks.mjs +1 -1
  33. package/scaffold/dist/definitions/platform-capabilities.mjs +1 -0
  34. package/scaffold/dist/definitions/protocols.mjs +7 -6
  35. package/scaffold/dist/definitions/skills/aikit.mjs +4 -4
  36. package/scaffold/general/hooks/scripts/_runtime.mjs +87 -15
  37. package/scaffold/general/hooks/scripts/freshness-signal.mjs +196 -0
  38. package/scaffold/general/hooks/scripts/privacy-guard.mjs +19 -6
  39. package/scaffold/general/hooks/scripts/scope-guard.mjs +160 -0
  40. package/scaffold/general/hooks/scripts/scout-guard.mjs +18 -14
  41. package/packages/cli/dist/init-CWLiK7bC.js +0 -7
  42. package/packages/server/dist/prelude-DetbWo8R.js +0 -1
  43. package/packages/server/dist/prelude-hfAEi93R.js +0 -2
  44. package/packages/server/dist/sampling-CWjnUevo.js +0 -2
  45. package/packages/server/dist/sampling-CnE3owSt.js +0 -1
  46. package/scaffold/general/hooks/scripts/post-edit-check.mjs +0 -36
  47. package/scaffold/general/hooks/scripts/pre-compact-save.mjs +0 -13
  48. package/scaffold/general/hooks/scripts/session-init.mjs +0 -85
  49. package/scaffold/general/hooks/scripts/session-learn.mjs +0 -53
  50. package/scaffold/general/hooks/scripts/session-observer.mjs +0 -77
  51. package/scaffold/general/hooks/scripts/subagent-context.mjs +0 -59
@@ -0,0 +1,78 @@
1
+ # Automation Boundary Analysis (Reconciled)
2
+
3
+ > **Status:** Corrected per architectural review (2026-06-28).
4
+ > **Previous version:** Classified 7 actions as AUTOMATABLE with 80% token savings claims.
5
+ > **Corrections:**
6
+ > - `status({includePrelude: true})` is LLM-JUDGMENT, not automatable
7
+ > - `search({query: "SESSION CHECKPOINT"})` is LLM-JUDGMENT — fixed query silently misses
8
+ > - `check({})` / `test_run({})` are MIXED/SCOPED only — full-project runs too slow
9
+ > - Token savings claims removed — not proven
10
+ >
11
+ > See `docs/architecture/eventbus-hooks-developer-guide.md` for the full implementation guide.
12
+
13
+ ---
14
+
15
+ ## Corrected Classification
16
+
17
+ | Action | Correct Category | Reason |
18
+ |--------|-----------------|--------|
19
+ | `status({})` at session start | **Automatable signal** | Safe only when bounded (no includePrelude) and not used as dynamic instruction replacement |
20
+ | `status({includePrelude: true})` | **LLM judgment** | Produces LLM-facing context; may be stale if hidden behind a hook |
21
+ | `knowledge({action: "list"})` at session start | **Automatable signal with budget** | Safe only with strict result cap and no full-list injection |
22
+ | `search({query: "SESSION CHECKPOINT"})` | **LLM judgment** | Fixed query can silently miss relevant checkpoints |
23
+ | `search({query: "<task>"})` before code change | **LLM judgment** | Query requires task understanding |
24
+ | `scope_map({task})` before code change | **LLM judgment** | Task description requires LLM synthesis |
25
+ | `check({})` / `test_run({})` before commit | **Mixed, scoped only** | Full-project runs too slow; must pass `files` from `git diff` or user intent |
26
+ | `blast_radius({changed_files})` before commit | **Mixed** | Call can be automated; output interpretation is LLM judgment |
27
+ | Handoff/checkpoint content creation | **LLM judgment** | Requires summarizing decisions and next steps |
28
+ | Writing a prepared handoff file | **Automatable** | File IO is deterministic after content exists |
29
+ | Skill loading at session start | **LLM judgment with signal** | Hook can recommend; LLM must choose and follow skill protocol |
30
+ | Context pressure check at session end | **Automatable signal** | Numeric comparison is deterministic |
31
+
32
+ ---
33
+
34
+ ## Per-Automation Specification
35
+
36
+ Each automated action must have:
37
+
38
+ | Field | Required |
39
+ |-------|----------|
40
+ | Input source | ✔ — What triggers the automation |
41
+ | Output budget | ✔ — Max tokens/characters produced |
42
+ | Timeout | ✔ — Max execution time |
43
+ | Failure mode | ✔ — Log, warn, or degrade (never silently fail) |
44
+ | User-visible trace | ✔ — Agent sees automation output |
45
+
46
+ ---
47
+
48
+ ## Per-LLM-Judgment Specification
49
+
50
+ Each LLM-judgment action must have:
51
+
52
+ | Field | Required |
53
+ |-------|----------|
54
+ | Why it cannot be automated | ✔ — Clear boundary reason |
55
+ | What instructions must remain | ✔ — Exact instruction text |
56
+ | How EventBus can assist (not replace) | ✔ — Signal, not decision |
57
+
58
+ ---
59
+
60
+ ## What NOT to Automate
61
+
62
+ These actions are explicitly rejected for automation:
63
+
64
+ - Search query composition (requires task understanding)
65
+ - Scope map task description (requires intent synthesis)
66
+ - Handoff/checkpoint content creation (requires LLM synthesis)
67
+ - Broad `check({})` / `test_run({})` without scope (too slow, may not match task)
68
+ - Any action where the automation would hide its own failures from the LLM
69
+
70
+ ---
71
+
72
+ ## Guiding Principles
73
+
74
+ 1. **Automate bounds, not decisions** — signals and file IO are safe; composition and synthesis are not
75
+ 2. **Every automation must be bounded** — max cost, max output, failure behavior, user-visible trace
76
+ 3. **No hidden behavior** — the LLM must know automation ran and see its output
77
+ 4. **LLM remains responsible for** — query composition, scope description, checkpoint/handoff synthesis, blast-radius interpretation, and validation choice when scope is unclear
78
+ 5. **Token savings require proof** — do not claim reduced instruction text until the removed instruction is proven safe to omit on that platform
@@ -5,10 +5,11 @@ import{renderLayeredKnowledgeOperatingPolicyV1 as e}from"../definitions/protocol
5
5
  `).trim();return l&&c.push(``,l),c.push(``,`---`,``),c.join(`
6
6
  `)}function a({title:e,content:t}){return`# ${e}\n\n${t}`}function o(e){return Object.entries(e).map(([e,t])=>[`### ${t.title||t.description||e}`,``,t.content.trim(),``,`---`,``].join(`
7
7
  `)).join(`
8
- `).trimEnd()}function s(e){return[[`Session Start`,e.sessionStart],[`Before Code Change`,e.beforeCodeChange],[`Before Commit`,e.beforeCommit],[`Session End`,e.sessionEnd]].map(([e,t])=>{let n=(t?.actions||[]).map(e=>`- \`${e}\``);return[`### ${e}`,``,...n,``].join(`
9
- `)}).join(`
10
- `).trimEnd()}function c(e,t){if(!e)return null;let n=typeof e==`function`?e(t):e[t];return n?typeof n==`string`?n:n.primary||n.model||null:null}function l(e,t){let n=!!t,r=[];for(let[i,a]of Object.entries(e))if(a.variants)for(let[e,o]of Object.entries(a.variants)){let s=`${i}-${e}`,l=[`| **${s}** | ${o.description} |`];n&&l.push(` ${c(t,s)||`—`} |`),l.push(` ${a.category||`—`} |`),r.push(l.join(``))}else{let e=[`| **${i}** | ${a.description} |`];n&&e.push(` ${c(t,i)||`—`} |`),e.push(` ${a.category||`—`} |`),r.push(e.join(``))}return n?`| Agent | Purpose | Model | Category |\n|-------|---------|-------|----------|\n${r.join(`
8
+ `).trimEnd()}const s={sessionStart:`SessionStart`,beforeCodeChange:`PreToolUse`,beforeCommit:`PreCommit`,sessionEnd:`SessionEnd`},c=[`sessionStart`,`beforeCodeChange`,`beforeCommit`,`sessionEnd`];function l(e){return e?.overall===`none`?[``,`> **Note**: This platform does not support native hook execution (no SessionStart, PreToolUse,`,`> PostToolUse, or PreCommit events). All hook behaviors documented below are **manual** -- you`,`> must perform them yourself. If you need server-side guardrails (privacy guards, scope`,`> enforcement, auto-knowledge signals), consider running the MCP server's **EventBus**, which`,`> provides in-process lifecycle subscribers without requiring IDE-level hook support.`,"> Check `aikit://hooks/config` for available EventBus subscribers.",``].join(`
9
+ `):``}function u(e,t,n,r,i){if(!e||i===`omit`)return``;i!==void 0&&i!==`pinned`&&i!=="default"&&console.warn(`buildHookSubsection: unrecognized treatment "${i}" for "${t}". Valid: ${p.join(`, `)}. Falling back to level-based rendering.`);let a=(e.actions||[]).map(e=>`- \`${e}\``),o=[`### ${t}`];return n===`native`?o.push(``,`_`+(r.native||`Auto-handled by native hooks.`)+`_ The following run automatically:`,``,...a,``):n===`eventbus`?o.push(``,`_`+(r.eventbus||`Monitored by EventBus.`)+`_ Active subscribers run automatically.`,`For awareness, these are the current subscribers:`,``,...a,``,"Check `aikit://hooks/config` for the full subscriber list.",``):n===`middleware`?o.push(``,`_`+(r.middleware||`Automated by middleware.`)+`_ The platform middleware handles this:`,``,...a,``):o.push(``,`${r.none||`Manually run the following:`}`,``,...a,``),o.join(`
10
+ `)}const d={sessionStart:{native:`Auto-handled by hooks. Status/knowledge/search run on every session start.`,eventbus:`Handled by EventBus. Session-start subscribers fire on connection.`,middleware:`Automated by ToolPipeline middleware. Status/knowledge/search run on session init.`,none:`On every session start, manually run the following to resume context:`},beforeCodeChange:{native:`Auto-handled by hooks. Search+scope checks run before every code change.`,eventbus:`Monitored by EventBus. Tool-before subscribers may provide context.`,none:`Before modifying any code, manually run these checks:`},beforeCommit:{native:`Hook auto-runs. Check+test+blast-radius run before every commit.`,eventbus:`No native pre-commit hooks on this platform. Validate manually:`,middleware:`No middleware-based pre-commit support. Validate manually:`,none:`Before committing changes, manually run these validations:`},sessionEnd:{native:`Auto-handled by hooks. Session-end hook persists checkpoints automatically.`,eventbus:`Tracked by EventBus. Stop subscribers fire on shutdown.`,middleware:`Automated by middleware. Handoff runs on session end.`,none:`Before ending a session (or at high context pressure), manually create a handoff:`}},f={sessionStart:`Session Start`,beforeCodeChange:`Before Code Change`,beforeCommit:`Before Commit`,sessionEnd:`Session End`},p=[`pinned`,`default`,`omit`];function m(e,t,n){if(!e)return``;let r=c.map(r=>{let i=e[r];if(!i)return``;let a=s[r],o=t&&a&&t[a]||`none`,c=d[r]||{},l=n?.[r];return u(i,f[r],o,c,l)}),i=l(t);return[...r,i].filter(Boolean).join(`
11
+ `).trimEnd()}function h(e,t){if(!e)return null;let n=typeof e==`function`?e(t):e[t];return n?typeof n==`string`?n:n.primary||n.model||null:null}function g(e,t){let n=!!t,r=[];for(let[i,a]of Object.entries(e))if(a.variants)for(let[e,o]of Object.entries(a.variants)){let s=`${i}-${e}`,c=[`| **${s}** | ${o.description} |`];n&&c.push(` ${h(t,s)||`—`} |`),c.push(` ${a.category||`—`} |`),r.push(c.join(``))}else{let e=[`| **${i}** | ${a.description} |`];n&&e.push(` ${h(t,i)||`—`} |`),e.push(` ${a.category||`—`} |`),r.push(e.join(``))}return n?`| Agent | Purpose | Model | Category |\n|-------|---------|-------|----------|\n${r.join(`
11
12
  `)}`:`| Agent | Purpose | Category |\n|-------|---------|----------|\n${r.join(`
12
- `)}`}function u(e){let t=[];for(let[n,r]of Object.entries(e)){if(n===`Orchestrator`)continue;let e=r.compactRole||r.description;if(r.variants){let i=Object.keys(r.variants);t.push(`| **${n}-${i.join(`/`)}** | ${e} |`)}else t.push(`| **${n}** | ${e} |`)}return`| Agent | Role |\n|-------|------|\n${t.join(`
13
- `)}`}function d({serverName:e,mcpEntry:t,configKey:n,extraServerFields:r}){let i={...t};return r&&Object.assign(i,r),JSON.stringify({[n]:{[e]:i}},null,2)}function f({flowTool:t=`flow`,isLifecycleOwner:n=!0}={}){let r=[`## Flows`,``,`This project uses aikit's pluggable flow system. Check flow status with the \`${t}\` MCP tool.`,``];return n?r.push(e({audience:`agent`}),``,`Use \`${t}({ action: 'list' })\` to see available flows and \`${t}({ action: 'start', name, topic })\` to begin one.`,``,`If a flow is active, follow the current step's skill instructions. Advance with \`${t}({ action: 'step', advance: 'next' })\`.`):r.push(`This project uses aikit's pluggable flow system. Use \`${t}({ action: 'status' })\` to check if a flow is active.`,`If dispatched as part of a flow, your work contributes to the current step. Do NOT advance or manage the flow — the Orchestrator handles flow lifecycle.`),r.join(`
14
- `)}function p({flowTool:e=`flow`}={}){return f({flowTool:e,isLifecycleOwner:!1})}export{r as buildAgentPrompt,l as buildAgentTable,u as buildCompactAgentTable,f as buildFlowSection,s as buildHooksSection,i as buildInlineAgentSection,d as buildMcpConfigJson,a as buildPromptFile,o as buildPromptSections,n as buildSkillsSection,p as buildSubagentFlowSection,t as lowerFirst};
13
+ `)}`}function _(e){let t=[];for(let[n,r]of Object.entries(e)){if(n===`Orchestrator`)continue;let e=r.compactRole||r.description;if(r.variants){let i=Object.keys(r.variants);t.push(`| **${n}-${i.join(`/`)}** | ${e} |`)}else t.push(`| **${n}** | ${e} |`)}return`| Agent | Role |\n|-------|------|\n${t.join(`
14
+ `)}`}function v({serverName:e,mcpEntry:t,configKey:n,extraServerFields:r}){let i={...t};return r&&Object.assign(i,r),JSON.stringify({[n]:{[e]:i}},null,2)}function y({flowTool:t=`flow`,isLifecycleOwner:n=!0}={}){let r=[`## Flows`,``,`This project uses aikit's pluggable flow system. Check flow status with the \`${t}\` MCP tool.`,``];return n?r.push(e({audience:`agent`}),``,`Use \`${t}({ action: 'list' })\` to see available flows and \`${t}({ action: 'start', name, topic })\` to begin one.`,``,`If a flow is active, follow the current step's skill instructions. Advance with \`${t}({ action: 'step', advance: 'next' })\`.`):r.push(`This project uses aikit's pluggable flow system. Use \`${t}({ action: 'status' })\` to check if a flow is active.`,`If dispatched as part of a flow, your work contributes to the current step. Do NOT advance or manage the flow — the Orchestrator handles flow lifecycle.`),r.join(`
15
+ `)}function b({flowTool:e=`flow`}={}){return y({flowTool:e,isLifecycleOwner:!1})}export{r as buildAgentPrompt,g as buildAgentTable,_ as buildCompactAgentTable,l as buildEventBusNote,y as buildFlowSection,m as buildHooksSection,i as buildInlineAgentSection,v as buildMcpConfigJson,a as buildPromptFile,o as buildPromptSections,n as buildSkillsSection,b as buildSubagentFlowSection,t as lowerFirst};
@@ -1,8 +1,8 @@
1
- import{AGENTS as e}from"../definitions/agents.mjs";import{AGENT_BODIES as t}from"../definitions/bodies.mjs";import{HOOKS as n}from"../definitions/hooks.mjs";import{MCP_SERVER_ENTRY as r,SERVER_NAME as i}from"../definitions/mcp.mjs";import{getPlatformPrimaryModelMap as a}from"../definitions/models.mjs";import{PROMPTS as o}from"../definitions/prompts.mjs";import{AIKIT_INSTALL_HINT_SECTION as s,PLATFORM_OUTPUT_RULES_SECTION as c,PROTOCOLS as l}from"../definitions/protocols.mjs";import{AIKIT_TOOLS as u,CLAUDE_CODE_TOOL_MAP as d,IDE_CAPABILITIES as f}from"../definitions/tools.mjs";import{buildAgentTable as p,buildCompactAgentTable as m,buildFlowSection as h,buildHooksSection as g,buildMcpConfigJson as _,buildSkillsSection as v,lowerFirst as y}from"./_shared.mjs";const b=h({flowTool:`flow`,isLifecycleOwner:!0}),x=h({flowTool:`flow`,isLifecycleOwner:!0}),S=h({flowTool:`flow`,isLifecycleOwner:!1});function C(e){return e.replace(/([a-z0-9])([A-Z])/g,`$1-$2`).replace(/[^a-zA-Z0-9]+/g,`-`).replace(/^-+|-+$/g,``).toLowerCase()}function w(e){return`"${String(e).replace(/\\/g,`\\\\`).replace(/"/g,`\\"`)}"`}function T(e){let t=(f[e]||[]).flatMap(e=>d[e]||[]),n=u.map(e=>`mcp__aikit__${e}`);return[...new Set([...t,...n])].sort((e,t)=>e.localeCompare(t))}function E(e,t){let n=[];return n.push(`# aikit ${e}`),n.push(``),t.description&&(n.push(`> ${t.description}`),n.push(``)),n.push(t.content.trim()),n.push(``),n.join(`
2
- `)}function D(e,n){let r=t[e];return typeof r==`function`?r(n):r||``}function O(e){let t=[];e.extraBody&&t.push(e.extraBody),e.sharedBase&&l[e.sharedBase]&&t.push(l[e.sharedBase]);for(let n of e.sharedProtocols||[])l[n]&&t.push(l[n]);return t}function k(){let t=[];for(let[n,r]of Object.entries(e)){if(r.variants){for(let[e,i]of Object.entries(r.variants))t.push({name:`${n}-${e}`,description:i.description||r.description});continue}t.push({name:n,description:r.description})}return t}function A(e){return k().map(({name:t,description:n})=>`- \`${e}/${t}.md\` — ${n}`).join(`
3
- `)}function j(t=p(e,a(`claude-code`))){return[`# aikit — Claude Code Agents`,``,c,``,s,``,`## MCP Server`,``,"Server name: `aikit`","Configured in `.mcp.json` (project) and `~/.claude.json` (user-global — `claude mcp add --scope user` format).","Default agent: `Orchestrator` in `~/.claude/settings.json` — every conversation starts as Orchestrator automatically.",``,`## Agents`,``,t,``,`## Agent Files`,``,A(`.claude/agents`),``,`## Commands`,``,"Slash commands live under `.claude/commands/`.",``,`## Session Protocol`,``,g(n),``].join(`
4
- `)}function M(){return[`# aikit — Claude Code Instructions`,``,"Detailed workspace instructions live in `AGENTS.md`.","Full sub-agent instructions live in `.claude/agents/`.","Slash commands live in `.claude/commands/`.","MCP server config lives in `.mcp.json` (project) and `~/.claude.json` (user-global — written by `claude mcp add --scope user`).","Default agent set to `Orchestrator` in `~/.claude/settings.json` — every conversation starts as Orchestrator, no `/agent` command needed.",``,"Start with `AGENTS.md`, then open the specific sub-agent file you want to use.",``,c,``,s,``].join(`
5
- `)}function N(e,t,n,r,i){let a=T(t.toolRole),o=O(t),s=v(t.skills),c=e===`Orchestrator`?[b,x]:[S],l=[n,...o,t.bodyAddendum,s,...c].filter(Boolean).join(`
1
+ import{AGENTS as e}from"../definitions/agents.mjs";import{AGENT_BODIES as t}from"../definitions/bodies.mjs";import{HOOKS as n}from"../definitions/hooks.mjs";import{MCP_SERVER_ENTRY as r,SERVER_NAME as i}from"../definitions/mcp.mjs";import{getPlatformPrimaryModelMap as a}from"../definitions/models.mjs";import{PLATFORM_HOOK_CAPABILITIES as o}from"../definitions/platform-capabilities.mjs";import{PROMPTS as s}from"../definitions/prompts.mjs";import{AIKIT_INSTALL_HINT_SECTION as c,PLATFORM_OUTPUT_RULES_SECTION as l,PROTOCOLS as u}from"../definitions/protocols.mjs";import{AIKIT_TOOLS as d,CLAUDE_CODE_TOOL_MAP as f,IDE_CAPABILITIES as p}from"../definitions/tools.mjs";import{buildAgentTable as m,buildCompactAgentTable as h,buildFlowSection as g,buildHooksSection as _,buildMcpConfigJson as v,buildSkillsSection as y,lowerFirst as b}from"./_shared.mjs";const x=g({flowTool:`flow`,isLifecycleOwner:!0}),S=g({flowTool:`flow`,isLifecycleOwner:!0}),C=g({flowTool:`flow`,isLifecycleOwner:!1});function w(e){return e.replace(/([a-z0-9])([A-Z])/g,`$1-$2`).replace(/[^a-zA-Z0-9]+/g,`-`).replace(/^-+|-+$/g,``).toLowerCase()}function T(e){return`"${String(e).replace(/\\/g,`\\\\`).replace(/"/g,`\\"`)}"`}function E(e){let t=(p[e]||[]).flatMap(e=>f[e]||[]),n=d.map(e=>`mcp__aikit__${e}`);return[...new Set([...t,...n])].sort((e,t)=>e.localeCompare(t))}function D(e,t){let n=[];return n.push(`# aikit ${e}`),n.push(``),t.description&&(n.push(`> ${t.description}`),n.push(``)),n.push(t.content.trim()),n.push(``),n.join(`
2
+ `)}function O(e,n){let r=t[e];return typeof r==`function`?r(n):r||``}function k(e){let t=[];e.extraBody&&t.push(e.extraBody),e.sharedBase&&u[e.sharedBase]&&t.push(u[e.sharedBase]);for(let n of e.sharedProtocols||[])u[n]&&t.push(u[n]);return t}function A(){let t=[];for(let[n,r]of Object.entries(e)){if(r.variants){for(let[e,i]of Object.entries(r.variants))t.push({name:`${n}-${e}`,description:i.description||r.description});continue}t.push({name:n,description:r.description})}return t}function j(e){return A().map(({name:t,description:n})=>`- \`${e}/${t}.md\` — ${n}`).join(`
3
+ `)}function M(t=m(e,a(`claude-code`))){return[`# aikit — Claude Code Agents`,``,l,``,c,``,`## MCP Server`,``,"Server name: `aikit`","Configured in `.mcp.json` (project) and `~/.claude.json` (user-global — `claude mcp add --scope user` format).","Default agent: `Orchestrator` in `~/.claude/settings.json` — every conversation starts as Orchestrator automatically.",``,`## Agents`,``,t,``,`## Agent Files`,``,j(`.claude/agents`),``,`## Commands`,``,"Slash commands live under `.claude/commands/`.",``,`## Session Protocol`,``,_(n,o[`claude-code`]),``].join(`
4
+ `)}function N(){return[`# aikit — Claude Code Instructions`,``,"Detailed workspace instructions live in `AGENTS.md`.","Full sub-agent instructions live in `.claude/agents/`.","Slash commands live in `.claude/commands/`.","MCP server config lives in `.mcp.json` (project) and `~/.claude.json` (user-global — written by `claude mcp add --scope user`).","Default agent set to `Orchestrator` in `~/.claude/settings.json` — every conversation starts as Orchestrator, no `/agent` command needed.",``,"Start with `AGENTS.md`, then open the specific sub-agent file you want to use.",``,l,``,c,``].join(`
5
+ `)}function P(e,t,n,r,i){let a=E(t.toolRole),o=k(t),s=y(t.skills),c=e===`Orchestrator`?[x,S]:[C],l=[n,...o,t.bodyAddendum,s,...c].filter(Boolean).join(`
6
6
 
7
- `).trim(),u=t.identity?`You are the **${e}**${t.identity}`:`You are the **${e}**, ${y(t.description)}.`,d=i?.[e],f=a.length>0?`tools: [${a.join(`, `)}]`:null,p=t.skills?.length>0?`skills: [${t.skills.map(([e])=>e).join(`, `)}]`:null,m=[`---`,`name: ${C(e)}`,`description: ${w(t.description)}`,...d?[`model: ${w(d)}`]:[],...f?[f]:[],`mcpServers:`,` - aikit`,...p?[p]:[],`---`,``,`# ${e} - ${t.title||e}`,``,u];return l&&m.push(``,l),m.push(``),m.join(`
8
- `)}function P(){let t=[],n=a(`claude-code`),s=p(e,n),c=m(e);t.push({path:`.mcp.json`,content:`${_({serverName:i,mcpEntry:r,configKey:`mcpServers`})}\n`}),t.push({path:`CLAUDE.md`,content:M()}),t.push({path:`AGENTS.md`,content:j(s)});for(let[e,n]of Object.entries(o))t.push({path:`.claude/commands/aikit-${e}.md`,content:E(e,n)});for(let[r,i]of Object.entries(e)){let e=D(r,c);if(i.variants){for(let[a,o]of Object.entries(i.variants)){let c=`${r}-${a}`;t.push({path:`.claude/agents/${c}.md`,content:N(c,{...i,description:o.description||i.description,identity:o.identity,bodyAddendum:o.bodyAddendum},e,s,n)})}continue}t.push({path:`.claude/agents/${r}.md`,content:N(r,i,e,s,n)})}return t}export{b as CLAUDE_FLOWS_SECTION,x as CLAUDE_ORCHESTRATOR_FLOW_ROUTING_SECTION,P as generateClaudeCode};
7
+ `).trim(),u=t.identity?`You are the **${e}**${t.identity}`:`You are the **${e}**, ${b(t.description)}.`,d=i?.[e],f=a.length>0?`tools: [${a.join(`, `)}]`:null,p=t.skills?.length>0?`skills: [${t.skills.map(([e])=>e).join(`, `)}]`:null,m=[`---`,`name: ${w(e)}`,`description: ${T(t.description)}`,...d?[`model: ${T(d)}`]:[],...f?[f]:[],`mcpServers:`,` - aikit`,...p?[p]:[],`---`,``,`# ${e} - ${t.title||e}`,``,u];return l&&m.push(``,l),m.push(``),m.join(`
8
+ `)}function F(){let t=[],n=a(`claude-code`),o=m(e,n),c=h(e);t.push({path:`.mcp.json`,content:`${v({serverName:i,mcpEntry:r,configKey:`mcpServers`})}\n`}),t.push({path:`CLAUDE.md`,content:N()}),t.push({path:`AGENTS.md`,content:M(o)});for(let[e,n]of Object.entries(s))t.push({path:`.claude/commands/aikit-${e}.md`,content:D(e,n)});for(let[r,i]of Object.entries(e)){let e=O(r,c);if(i.variants){for(let[a,s]of Object.entries(i.variants)){let c=`${r}-${a}`;t.push({path:`.claude/agents/${c}.md`,content:P(c,{...i,description:s.description||i.description,identity:s.identity,bodyAddendum:s.bodyAddendum},e,o,n)})}continue}t.push({path:`.claude/agents/${r}.md`,content:P(r,i,e,o,n)})}return t}export{x as CLAUDE_FLOWS_SECTION,S as CLAUDE_ORCHESTRATOR_FLOW_ROUTING_SECTION,F as generateClaudeCode};
@@ -0,0 +1,7 @@
1
+ import{AGENTS as e}from"../definitions/agents.mjs";import{AGENT_BODIES as t}from"../definitions/bodies.mjs";import{HOOKS as n}from"../definitions/hooks.mjs";import{MCP_SERVER_ENTRY as r}from"../definitions/mcp.mjs";import{getPlatformPrimaryModelMap as i}from"../definitions/models.mjs";import{PLATFORM_HOOK_CAPABILITIES as a}from"../definitions/platform-capabilities.mjs";import{PROMPTS as o}from"../definitions/prompts.mjs";import{AIKIT_INSTALL_HINT_SECTION as s,PLATFORM_OUTPUT_RULES_SECTION as c,PROTOCOLS as l}from"../definitions/protocols.mjs";import{AIKIT_TOOLS as u,CLAUDE_CODE_TOOL_MAP as d,IDE_CAPABILITIES as f}from"../definitions/tools.mjs";import{buildAgentTable as p,buildCompactAgentTable as m,buildFlowSection as h,buildHooksSection as g,buildSkillsSection as _,lowerFirst as v}from"./_shared.mjs";const y=h({flowTool:`flow`,isLifecycleOwner:!0}),b=h({flowTool:`flow`,isLifecycleOwner:!1});function x(e){return e.replace(/([a-z0-9])([A-Z])/g,`$1-$2`).replace(/[^a-zA-Z0-9]+/g,`-`).replace(/^-+|-+$/g,``).toLowerCase()}function S(e){return`"${String(e).replace(/\\/g,`\\\\`).replace(/"/g,`\\"`)}"`}function C(e){let t=(f[e]||[]).flatMap(e=>d[e]||[]),n=u.map(e=>`mcp__aikit__${e}`);return[...new Set([...t,...n])].sort((e,t)=>e.localeCompare(t))}function w(e,t){let n=[];return n.push(`# aikit ${e}`),n.push(``),t.description&&(n.push(`> ${t.description}`),n.push(``)),n.push(t.content.trim()),n.push(``),n.join(`
2
+ `)}function T(e,n){let r=t[e];return typeof r==`function`?r(n):r||``}function E(e){let t=[];e.extraBody&&t.push(e.extraBody),e.sharedBase&&l[e.sharedBase]&&t.push(l[e.sharedBase]);for(let n of e.sharedProtocols||[])l[n]&&t.push(l[n]);return t}function D(){return[`# aikit — Claude Desktop Instructions`,``,"Detailed workspace instructions live in `AGENTS.md`.","MCP server config lives in `claude_desktop_config.json`.",``,c,``,s,``,g(n,a[`claude-desktop`]),``].join(`
3
+ `)}function O(e){return[`# aikit — Claude Desktop Agents`,``,c,``,s,``,`## MCP Server`,``,"Server name: `aikit`","Configured in `claude_desktop_config.json`.",``,`## Agents`,``,e,``,g(n,a[`claude-desktop`]),``].join(`
4
+ `)}function k(e,t,n,r,i){let a=C(t.toolRole),o=E(t),s=_(t.skills),c=e===`Orchestrator`?[y,b]:[b],l=[n,...o,t.bodyAddendum,s,...c].filter(Boolean).join(`
5
+
6
+ `).trim(),u=t.identity?`You are the **${e}**${t.identity}`:`You are the **${e}**, ${v(t.description)}.`,d=i?.[e],f=a.length>0?`tools: [${a.join(`, `)}]`:null;t.skills?.length>0&&`${t.skills.map(([e])=>e).join(`, `)}`;let p=[`---`,`name: ${x(e)}`,`description: ${S(t.description)}`,...d?[`model: ${S(d)}`]:[],...f?[f]:[],`---`,``,`# ${e} - ${t.title||e}`,``,u];return l&&p.push(``,l),p.push(``),p.join(`
7
+ `)}function A(){let t=[],n=i(`claude-desktop`),a=p(e,n),s=m(e);t.push({path:`claude_desktop_config.json`,content:`${JSON.stringify({mcpServers:{aikit:{command:r.command,args:r.args}}},null,2)}\n`}),t.push({path:`CLAUDE.md`,content:D()}),t.push({path:`AGENTS.md`,content:O(a)});for(let[e,n]of Object.entries(o))t.push({path:`.claude/commands/aikit-${e}.md`,content:w(e,n)});for(let[r,i]of Object.entries(e)){let e=T(r,s);if(i.variants){for(let[o,s]of Object.entries(i.variants)){let c=`${r}-${o}`;t.push({path:`.claude/agents/${c}.md`,content:k(c,{...i,description:s.description||i.description,identity:s.identity,bodyAddendum:s.bodyAddendum},e,a,n)})}continue}t.push({path:`.claude/agents/${r}.md`,content:k(r,i,e,a,n)})}return t}export{A as generateClaudeDesktop};
@@ -1,7 +1,7 @@
1
- import{AGENTS as e}from"../definitions/agents.mjs";import{AGENT_BODIES as t}from"../definitions/bodies.mjs";import{HOOKS as n}from"../definitions/hooks.mjs";import{MCP_SERVER_ENTRY as r,SERVER_NAME as i}from"../definitions/mcp.mjs";import{PROMPTS as a}from"../definitions/prompts.mjs";import{PROTOCOLS as o}from"../definitions/protocols.mjs";import{buildAgentTable as s,buildCompactAgentTable as c,buildFlowSection as l,buildHooksSection as u,buildMcpConfigJson as d,buildPromptSections as f,buildSkillsSection as p,lowerFirst as m}from"./_shared.mjs";const h=l({flowTool:`flow`,isLifecycleOwner:!0}),g=l({flowTool:`flow`,isLifecycleOwner:!1});function _(e,n){let r=t[e];return typeof r==`function`?r(n):r||``}function v(e){let t=[];e.extraBody&&t.push(e.extraBody),e.sharedBase&&o[e.sharedBase]&&t.push(o[e.sharedBase]);for(let n of e.sharedProtocols||[])o[n]&&t.push(o[n]);return t}function y(){let t=[];for(let[n,r]of Object.entries(e)){if(r.variants){for(let[e,i]of Object.entries(r.variants))t.push({name:`${n}-${e}`,description:i.description||r.description});continue}t.push({name:n,description:r.description})}return t}function b(e){return y().map(({name:t,description:n})=>`- \`${e}/${t}.md\` — ${n}`).join(`
2
- `)}function x(e,t,n){let r=v(t),i=p(t.skills),a=e===`Orchestrator`?h:g,o=[n,...r,t.bodyAddendum,i,a].filter(Boolean).join(`
1
+ import{AGENTS as e}from"../definitions/agents.mjs";import{AGENT_BODIES as t}from"../definitions/bodies.mjs";import{HOOKS as n}from"../definitions/hooks.mjs";import{MCP_SERVER_ENTRY as r,SERVER_NAME as i}from"../definitions/mcp.mjs";import{PLATFORM_HOOK_CAPABILITIES as a}from"../definitions/platform-capabilities.mjs";import{PROMPTS as o}from"../definitions/prompts.mjs";import{PROTOCOLS as s}from"../definitions/protocols.mjs";import{buildAgentTable as c,buildCompactAgentTable as l,buildFlowSection as u,buildHooksSection as d,buildMcpConfigJson as f,buildPromptSections as p,buildSkillsSection as m,lowerFirst as h}from"./_shared.mjs";const g=u({flowTool:`flow`,isLifecycleOwner:!0}),_=u({flowTool:`flow`,isLifecycleOwner:!1});function v(e,n){let r=t[e];return typeof r==`function`?r(n):r||``}function y(e){let t=[];e.extraBody&&t.push(e.extraBody),e.sharedBase&&s[e.sharedBase]&&t.push(s[e.sharedBase]);for(let n of e.sharedProtocols||[])s[n]&&t.push(s[n]);return t}function b(){let t=[];for(let[n,r]of Object.entries(e)){if(r.variants){for(let[e,i]of Object.entries(r.variants))t.push({name:`${n}-${e}`,description:i.description||r.description});continue}t.push({name:n,description:r.description})}return t}function x(e){return b().map(({name:t,description:n})=>`- \`${e}/${t}.md\` — ${n}`).join(`
2
+ `)}function S(e,t,n){let r=y(t),i=m(t.skills),a=e===`Orchestrator`?g:_,o=[n,...r,t.bodyAddendum,i,a].filter(Boolean).join(`
3
3
 
4
- `).trim();return[`# ${e} - ${t.title||e}`,``,`> ${t.description}`,``,t.identity?`You are the **${e}**${t.identity}`:`You are the **${e}**, ${m(t.description)}.`,...o?[``,o]:[],``].join(`
5
- `)}function S(){return[`# aikit — Gemini CLI Agents`,``,"This workspace keeps the root `GEMINI.md` intentionally small. Full agent instructions are split into per-agent files under `.gemini/agents/`.",``,`## MCP Server`,``,"Server name: `aikit`","Configure in `.gemini/settings.json` (already done if scaffold was deployed).",``,`## Agents`,``,s(e),``,`## Agent Files`,``,b(`.gemini/agents`),``,`## Prompts`,``,f(a),``,`## Session Protocol`,``,u(n),``].join(`
6
- `)}function C(){return[`# aikit — Gemini CLI Instructions`,``,"Detailed workspace instructions live in `AGENTS.md`.","Per-agent instructions are emitted under `.gemini/agents/`.","MCP server config lives in `.gemini/settings.json`.",``,"Start with `AGENTS.md`, then open the specific agent file you want to use.",``].join(`
7
- `)}function w(){let t=[{path:`.gemini/settings.json`,content:`${d({serverName:i,mcpEntry:r,configKey:`mcpServers`})}\n`},{path:`GEMINI.md`,content:C()},{path:`AGENTS.md`,content:S()}],n=c(e);for(let[r,i]of Object.entries(e)){let e=_(r,n);if(i.variants){for(let[n,a]of Object.entries(i.variants)){let o=`${r}-${n}`;t.push({path:`.gemini/agents/${o}.md`,content:x(o,{...i,description:a.description||i.description,identity:a.identity,bodyAddendum:a.bodyAddendum},e)})}continue}t.push({path:`.gemini/agents/${r}.md`,content:x(r,i,e)})}return t}export{w as generateGemini};
4
+ `).trim();return[`# ${e} - ${t.title||e}`,``,`> ${t.description}`,``,t.identity?`You are the **${e}**${t.identity}`:`You are the **${e}**, ${h(t.description)}.`,...o?[``,o]:[],``].join(`
5
+ `)}function C(){return[`# aikit — Gemini CLI Agents`,``,"This workspace keeps the root `GEMINI.md` intentionally small. Full agent instructions are split into per-agent files under `.gemini/agents/`.",``,`## MCP Server`,``,"Server name: `aikit`","Configure in `.gemini/settings.json` (already done if scaffold was deployed).",``,`## Agents`,``,c(e),``,`## Agent Files`,``,x(`.gemini/agents`),``,`## Prompts`,``,p(o),``,`## Session Protocol`,``,d(n,a.gemini),``].join(`
6
+ `)}function w(){return[`# aikit — Gemini CLI Instructions`,``,"Detailed workspace instructions live in `AGENTS.md`.","Per-agent instructions are emitted under `.gemini/agents/`.","MCP server config lives in `.gemini/settings.json`.",``,"Start with `AGENTS.md`, then open the specific agent file you want to use.",``].join(`
7
+ `)}function T(){let t=[{path:`.gemini/settings.json`,content:`${f({serverName:i,mcpEntry:r,configKey:`mcpServers`})}\n`},{path:`GEMINI.md`,content:w()},{path:`AGENTS.md`,content:C()}],n=l(e);for(let[r,i]of Object.entries(e)){let e=v(r,n);if(i.variants){for(let[n,a]of Object.entries(i.variants)){let o=`${r}-${n}`;t.push({path:`.gemini/agents/${o}.md`,content:S(o,{...i,description:a.description||i.description,identity:a.identity,bodyAddendum:a.bodyAddendum},e)})}continue}t.push({path:`.gemini/agents/${r}.md`,content:S(r,i,e)})}return t}export{T as generateGemini};
@@ -1,6 +1,6 @@
1
- import{AGENTS as e}from"../definitions/agents.mjs";import{AGENT_BODIES as t}from"../definitions/bodies.mjs";import{HOOKS as n}from"../definitions/hooks.mjs";import{PROMPTS as r}from"../definitions/prompts.mjs";import{PROTOCOLS as i}from"../definitions/protocols.mjs";import{buildAgentTable as a,buildCompactAgentTable as o,buildFlowSection as s,buildHooksSection as c,buildPromptSections as l,buildSkillsSection as u,lowerFirst as d}from"./_shared.mjs";const f=s({flowTool:`flow`,isLifecycleOwner:!0}),p=s({flowTool:`flow`,isLifecycleOwner:!1});function m(e,n){let r=t[e];return typeof r==`function`?r(n):r||``}function h(e){let t=[];e.extraBody&&t.push(e.extraBody),e.sharedBase&&i[e.sharedBase]&&t.push(i[e.sharedBase]);for(let n of e.sharedProtocols||[])i[n]&&t.push(i[n]);return t}function g(){let t=a(e),i=o(e),s=l(r),u=[];for(let[t,n]of Object.entries(e)){let e=m(t,i);if(n.variants){for(let[r,i]of Object.entries(n.variants)){let a=`${t}-${r}`;u.push(_(a,{...n,description:i.description||n.description,identity:i.identity,bodyAddendum:i.bodyAddendum},e))}continue}u.push(_(t,n,e))}return[`# AI Kit — Hermes Agent Instructions`,``,"This project uses **AI Kit** (`@vpxa/aikit`), a local-first MCP server that provides search,",`code analysis, persistent memory, context compression, and guided development flows.`,``,`The AI Kit MCP server is registered in your Hermes config and its 64 tools are available`,"as `mcp_aikit_*` tool calls in every conversation.",``,`## MCP Server`,``,"Server name: `aikit`","Configured via `hermes mcp add` (user-level). Tools are auto-discovered at session start.",``,`## Skills-First Rule`,``,`**Before starting ANY task, check available skills.** If a skill matches your task domain`,`— load it first. Skills contain domain-specific patterns, templates, and constraints.`,``,`## Available Tools (64)`,``,`AI Kit provides tools across the following categories:`,``,`| Category | Tools |`,`|----------|-------|`,"| **Search & Discovery** | `search`, `find`, `symbol`, `trace`, `lookup`, `scope_map`, `file_summary`, `dead_symbols` |","| **Code Analysis** | `analyze`, `blast_radius`, `audit`, `graph`, `health`, `check` |","| **Context Compression** | `file_summary`, `digest` |","| **Memory & Knowledge** | `knowledge`, `remember`, `read`, `update`, `forget`, `list`, `stash` |","| **FORGE Quality Gates** | `forge_classify`, `forge_ground`, `evidence_map`, `compliance_score` |","| **Development Workflows** | `flow`, `onboard`, `reindex`, `status`, `guide`, `config` |","| **Testing & Build** | `test_run`, `parse_output`, `check`, `eval` |","| **Code Manipulation** | `rename`, `codemod`, `data_transform`, `diff_parse`, `git_context`, `replay` |","| **Web & HTTP** | `web_search`, `web_fetch`, `http` |","| **Present & Visualize** | `present`, `signal`, `session_digest` |","| **Sandbox & Process** | `process`, `watch`, `workset`, `lane`, `queue`, `restore`, `delegate` |","| **Utilities** | `encode`, `regex_test`, `measure`, `changelog`, `schema_validate`, `env`, `time` |","| **Browser** | `browser` |",``,`## Important: Prefer AI Kit Tools Over Generic Shell Commands`,``,`When working in this project, use AI Kit MCP tools instead of generic alternatives:`,``,`| Instead of | Use AI Kit Tool | Why |`,`|-----------|----------------|-----|`,"| `grep` / `rg` | `mcp_aikit_search` or `mcp_aikit_find` | Hybrid search (vector + keyword + BM25) across all indexed content |","| `read_file` for understanding | `mcp_aikit_file_summary` | T1=structure, T2=structure+content — 10x fewer tokens than raw file reads |","| `cd` + `ls` | `mcp_aikit_analyze` | Structural analysis with tree views |","| Running tests in terminal | `mcp_aikit_test_run` | Structured test output — no shell needed |","| `tsc` / `biome` in terminal | `mcp_aikit_check` | Typecheck + lint combined |","| Web search | `mcp_aikit_web_search` | Fans out to multiple keyless providers |",``,`## Agents`,``,t,``,`## Agent Instructions`,``,u.join(`
2
- `),``,`## Prompts`,``,s,``,f,``,`## Session Protocol`,``,c(n),``].join(`
3
- `)}function _(e,t,n){let r=h(t),i=u(t.skills),a=e===`Orchestrator`?f:p,o=[n,...r,t.bodyAddendum,i,a].filter(Boolean).join(`
1
+ import{AGENTS as e}from"../definitions/agents.mjs";import{AGENT_BODIES as t}from"../definitions/bodies.mjs";import{HOOKS as n}from"../definitions/hooks.mjs";import{PLATFORM_HOOK_CAPABILITIES as r}from"../definitions/platform-capabilities.mjs";import{PROMPTS as i}from"../definitions/prompts.mjs";import{PROTOCOLS as a}from"../definitions/protocols.mjs";import{buildAgentTable as o,buildCompactAgentTable as s,buildFlowSection as c,buildHooksSection as l,buildPromptSections as u,buildSkillsSection as d,lowerFirst as f}from"./_shared.mjs";const p=c({flowTool:`flow`,isLifecycleOwner:!0}),m=c({flowTool:`flow`,isLifecycleOwner:!1});function h(e,n){let r=t[e];return typeof r==`function`?r(n):r||``}function g(e){let t=[];e.extraBody&&t.push(e.extraBody),e.sharedBase&&a[e.sharedBase]&&t.push(a[e.sharedBase]);for(let n of e.sharedProtocols||[])a[n]&&t.push(a[n]);return t}function _(){let t=o(e),a=s(e),c=u(i),d=[];for(let[t,n]of Object.entries(e)){let e=h(t,a);if(n.variants){for(let[r,i]of Object.entries(n.variants)){let a=`${t}-${r}`;d.push(v(a,{...n,description:i.description||n.description,identity:i.identity,bodyAddendum:i.bodyAddendum},e))}continue}d.push(v(t,n,e))}return[`# AI Kit — Hermes Agent Instructions`,``,"This project uses **AI Kit** (`@vpxa/aikit`), a local-first MCP server that provides search,",`code analysis, persistent memory, context compression, and guided development flows.`,``,`The AI Kit MCP server is registered in your Hermes config and its 64 tools are available`,"as `mcp_aikit_*` tool calls in every conversation.",``,`## MCP Server`,``,"Server name: `aikit`","Configured via `hermes mcp add` (user-level). Tools are auto-discovered at session start.",``,`## Skills-First Rule`,``,`**Before starting ANY task, check available skills.** If a skill matches your task domain`,`— load it first. Skills contain domain-specific patterns, templates, and constraints.`,``,`## Available Tools (64)`,``,`AI Kit provides tools across the following categories:`,``,`| Category | Tools |`,`|----------|-------|`,"| **Search & Discovery** | `search`, `find`, `symbol`, `trace`, `lookup`, `scope_map`, `file_summary`, `dead_symbols` |","| **Code Analysis** | `analyze`, `blast_radius`, `audit`, `graph`, `health`, `check` |","| **Context Compression** | `file_summary`, `digest` |","| **Memory & Knowledge** | `knowledge`, `remember`, `read`, `update`, `forget`, `list`, `stash` |","| **FORGE Quality Gates** | `forge_classify`, `forge_ground`, `evidence_map`, `compliance_score` |","| **Development Workflows** | `flow`, `onboard`, `reindex`, `status`, `guide`, `config` |","| **Testing & Build** | `test_run`, `parse_output`, `check`, `eval` |","| **Code Manipulation** | `rename`, `codemod`, `data_transform`, `diff_parse`, `git_context`, `replay` |","| **Web & HTTP** | `web_search`, `web_fetch`, `http` |","| **Present & Visualize** | `present`, `signal`, `session_digest` |","| **Sandbox & Process** | `process`, `watch`, `workset`, `lane`, `queue`, `restore`, `delegate` |","| **Utilities** | `encode`, `regex_test`, `measure`, `changelog`, `schema_validate`, `env`, `time` |","| **Browser** | `browser` |",``,`## Important: Prefer AI Kit Tools Over Generic Shell Commands`,``,`When working in this project, use AI Kit MCP tools instead of generic alternatives:`,``,`| Instead of | Use AI Kit Tool | Why |`,`|-----------|----------------|-----|`,"| `grep` / `rg` | `mcp_aikit_search` or `mcp_aikit_find` | Hybrid search (vector + keyword + BM25) across all indexed content |","| `read_file` for understanding | `mcp_aikit_file_summary` | T1=structure, T2=structure+content — 10x fewer tokens than raw file reads |","| `cd` + `ls` | `mcp_aikit_analyze` | Structural analysis with tree views |","| Running tests in terminal | `mcp_aikit_test_run` | Structured test output — no shell needed |","| `tsc` / `biome` in terminal | `mcp_aikit_check` | Typecheck + lint combined |","| Web search | `mcp_aikit_web_search` | Fans out to multiple keyless providers |",``,`## Agents`,``,t,``,`## Agent Instructions`,``,d.join(`
2
+ `),``,`## Prompts`,``,c,``,p,``,`## Session Protocol`,``,l(n,r.hermes),``].join(`
3
+ `)}function v(e,t,n){let r=g(t),i=d(t.skills),a=e===`Orchestrator`?p:m,o=[n,...r,t.bodyAddendum,i,a].filter(Boolean).join(`
4
4
 
5
- `).trim();return[`## ${e} - ${t.title||e}`,``,`> ${t.description}`,``,t.identity?`You are the **${e}**${t.identity}`:`You are the **${e}**, ${d(t.description)}.`,...o?[``,o]:[],``].join(`
6
- `)}function v(){return[{path:`AGENTS.md`,content:g()}]}export{v as generateHermes};
5
+ `).trim();return[`## ${e} - ${t.title||e}`,``,`> ${t.description}`,``,t.identity?`You are the **${e}**${t.identity}`:`You are the **${e}**, ${f(t.description)}.`,...o?[``,o]:[],``].join(`
6
+ `)}function y(){return[{path:`AGENTS.md`,content:_()}]}export{y as generateHermes};
@@ -1 +1 @@
1
- import{EXEC_HOOKS as e,HOOK_EVENTS as t,HOOK_TOOL_MATCHERS as n,SUPPORTED_PLATFORMS as r}from"../definitions/exec-hooks.mjs";const i={PreCompact:3e3,PostToolUse:3e3},a={copilot:`hooks.json`,copilotCli:`hooks.json`,hermes:`hooks.json`},o=r;function s(e){if(!o.includes(e))throw Error(`Unknown platform: "${e}". Supported: ${o.join(`, `)}. Add it to SUPPORTED_PLATFORMS in exec-hooks.mjs first, then add entries to HOOK_EVENTS and HOOK_TOOL_MATCHERS. Add FILE_NAMES in generateHooks if file-producing.`);for(let[n,r]of Object.entries(t))if(!(e in r))throw Error(`Platform "${e}" is missing from HOOK_EVENTS.${n}. Add a "${e}" key to HOOK_EVENTS.${n} in exec-hooks.mjs.`);for(let[t,r]of Object.entries(n))if(!(e in r))throw Error(`Platform "${e}" is missing from HOOK_TOOL_MATCHERS.${t}. Add a "${e}" key to HOOK_TOOL_MATCHERS.${t} in exec-hooks.mjs.`)}function c(e,t){return(e.matcher||[]).flatMap(e=>{let r=n[e];if(!r)throw Error(`Unknown hook matcher: ${e}`);return r[t]||[]})}function l(e,t,n,r){let a=`${n}/${e.script}`;return c(e,r),r===`copilot`?{event:t,steps:[{type:`command`,command:`node`,args:[a],timeout:i[e.event]||5e3}]}:r===`claude`?{type:`command`,command:`node ${a}`}:{command:`node`,args:[a]}}function u(n,r){s(n);let i={};for(let a of Object.values(e)){let e=t[a.event]?.[n];if(!e){console.warn(`[aikit] Unsupported hook event ${a.event} for ${n} — skipping`);continue}i[e]||=[],i[e].push(l(a,e,r,n))}return{hooks:i}}function d(n,r){if(s(n),n===`copilot`){let i=Object.values(e).flatMap(e=>{let i=t[e.event]?.[n];return i?l(e,i,r,n):(console.warn(`[aikit] Unsupported hook event ${e.event} for ${n} — skipping`),[])});return[{path:a[n],content:JSON.stringify({hooks:i},null,2)}]}if(n===`claude`)return[];if(!(n in a))throw Error(`Platform "${n}" is missing from FILE_NAMES. Add "${n}: '<filename>'" to FILE_NAMES in adapters/hooks.mjs.`);let i={};for(let a of Object.values(e)){let e=t[a.event]?.[n];if(!e){console.warn(`[aikit] Unsupported hook event ${a.event} for ${n} — skipping`);continue}i[e]||=[],i[e].push(l(a,e,r,n))}return[{path:a[n],content:JSON.stringify({hooks:i},null,2)}]}function f(){return[`_runtime.mjs`,...Object.values(e).map(e=>e.script)]}export{u as buildHooksBlock,d as generateHooks,f as getHookScriptFiles};
1
+ import{EXEC_HOOKS as e,HOOK_EVENTS as t,HOOK_TOOL_MATCHERS as n,SUPPORTED_PLATFORMS as r}from"../definitions/exec-hooks.mjs";const i={PreCompact:3e3,PostToolUse:3e3},a={copilot:`hooks.json`,copilotCli:`hooks.json`,hermes:`hooks.json`},o=r;function s(e){if(!o.includes(e))throw Error(`Unknown platform: "${e}". Supported: ${o.join(`, `)}. Add it to SUPPORTED_PLATFORMS in exec-hooks.mjs first, then add entries to HOOK_EVENTS and HOOK_TOOL_MATCHERS. Add FILE_NAMES in generateHooks if file-producing.`);for(let[n,r]of Object.entries(t))if(!(e in r))throw Error(`Platform "${e}" is missing from HOOK_EVENTS.${n}. Add a "${e}" key to HOOK_EVENTS.${n} in exec-hooks.mjs.`);for(let[t,r]of Object.entries(n))if(!(e in r))throw Error(`Platform "${e}" is missing from HOOK_TOOL_MATCHERS.${t}. Add a "${e}" key to HOOK_TOOL_MATCHERS.${t} in exec-hooks.mjs.`)}function c(e,t){return(e.matcher||[]).flatMap(e=>{let r=n[e];if(!r)throw Error(`Unknown hook matcher: ${e}`);return r[t]||[]})}function l(e,t,n,r){let a=`${n}/${e.script}`;if(r===`claude`){let t=c(e,r);return{matcher:t.length>0?t.join(`|`):``,hooks:[{type:`command`,command:`node ${JSON.stringify(a)}`}]}}return r===`copilot`?{event:t,steps:[{type:`command`,command:`node`,args:[a],timeout:i[e.event]||5e3}]}:{command:`node`,args:[a]}}function u(n,r){s(n);let i={};for(let a of Object.values(e)){let e=t[a.event]?.[n];if(!e){console.warn(`[aikit] Unsupported hook event ${a.event} for ${n} — skipping`);continue}i[e]||=[],i[e].push(l(a,e,r,n))}return{hooks:i}}function d(n,r){if(s(n),n===`claude`)return[];if(n===`copilot`){let i=Object.values(e).flatMap(e=>{let i=t[e.event]?.[n];return i?l(e,i,r,n):(console.warn(`[aikit] Unsupported hook event ${e.event} for ${n} — skipping`),[])});return[{path:a[n],content:JSON.stringify({hooks:i},null,2)}]}if(!(n in a))throw Error(`Platform "${n}" is missing from FILE_NAMES. Add "${n}: '<filename>'" to FILE_NAMES in adapters/hooks.mjs.`);let i={};for(let a of Object.values(e)){let e=t[a.event]?.[n];if(!e){console.warn(`[aikit] Unsupported hook event ${a.event} for ${n} — skipping`);continue}i[e]||=[],i[e].push(l(a,e,r,n))}return[{path:a[n],content:JSON.stringify({hooks:i},null,2)}]}function f(){return[`_runtime.mjs`,...Object.values(e).map(e=>e.script)]}function p(){let t={PostToolUse:`tool:after`,SessionStart:`session:start`,Stop:`server:stop`},n={fileRead:[`read_file`,`readFile`,`Read`,`grep_search`,`semantic_search`],fileWrite:[`write_file`,`patch`,`editFiles`,`Edit`,`Write`,`MultiEdit`],fileSearch:[`search_files`,`grep_search`,`semantic_search`,`find`,`Bash`,`mcp_aikit_search`,`mcp_aikit_find`]};return Object.values(e).map(e=>{let r=t[e.event]||e.event.toLowerCase().replace(/([a-z])([A-Z])/g,`$1:$2`).replace(/^./,e=>e.toLowerCase()),i=(e.matcher||[]).flatMap(e=>n[e]||[]);return{id:`scaffold-${e.id}`,eventType:r,policy:`async`,name:e.id,toolFilter:i.length>0?i:void 0,description:e.description,llmBenefit:e.llmBenefit,category:e.category,failurePolicy:e.failurePolicy,executionModel:e.executionModel}})}export{u as buildHooksBlock,p as buildServerHookConfig,d as generateHooks,f as getHookScriptFiles};
@@ -1,8 +1,8 @@
1
- import{AGENTS as e}from"../definitions/agents.mjs";import{AGENT_BODIES as t}from"../definitions/bodies.mjs";import{HOOKS as n}from"../definitions/hooks.mjs";import{MCP_SERVER_ENTRY as r,SERVER_NAME as i}from"../definitions/mcp.mjs";import{PROMPTS as a}from"../definitions/prompts.mjs";import{PROTOCOLS as o}from"../definitions/protocols.mjs";import{buildAgentTable as s,buildCompactAgentTable as c,buildFlowSection as l,buildHooksSection as u,buildPromptSections as d,buildSkillsSection as f,lowerFirst as p}from"./_shared.mjs";const m=l({flowTool:`flow`,isLifecycleOwner:!0}),h=l({flowTool:`flow`,isLifecycleOwner:!1});function g(e,n){let r=t[e];return typeof r==`function`?r(n):r||``}function _(e){let t=[];e.extraBody&&t.push(e.extraBody),e.sharedBase&&o[e.sharedBase]&&t.push(o[e.sharedBase]);for(let n of e.sharedProtocols||[])o[n]&&t.push(o[n]);return t}function v(){let t=[];for(let[n,r]of Object.entries(e)){if(r.variants){for(let[e,i]of Object.entries(r.variants))t.push({name:`${n}-${e}`,description:i.description||r.description});continue}t.push({name:n,description:r.description})}return t}function y(e){return v().map(({name:t,description:n})=>`- \`${e}/${t}.md\` — ${n}`).join(`
2
- `)}const b={orchestrator:{edit:`allow`,bash:`allow`},planner:{edit:`allow`,bash:`allow`},codeAgent:{edit:`allow`,bash:`allow`},refactor:{edit:`allow`,bash:`allow`},debugger:{edit:`allow`,bash:`allow`},security:{edit:`allow`,bash:`allow`},documenter:{edit:`allow`,bash:`deny`},explorer:{edit:`deny`,bash:`deny`},researcher:{edit:`deny`,bash:`deny`},reviewer:{edit:`deny`,bash:`deny`}};function x(e){let t=e.category===`orchestration`&&e.title===`The Master Conductor`?`primary`:`subagent`,n=b[e.toolRole]||{edit:`deny`,bash:`deny`},r=[`---`,`description: ${e.description}`,`mode: ${t}`,`permission:`];for(let[e,t]of Object.entries(n))r.push(` ${e}: ${t}`);return r.push(`---`),r.join(`
3
- `)}function S(e,t,n){let r=_(t),i=f(t.skills),a=e===`Orchestrator`?m:h,o=[n,...r,t.bodyAddendum,i,a].filter(Boolean).join(`
1
+ import{AGENTS as e}from"../definitions/agents.mjs";import{AGENT_BODIES as t}from"../definitions/bodies.mjs";import{HOOKS as n}from"../definitions/hooks.mjs";import{MCP_SERVER_ENTRY as r,SERVER_NAME as i}from"../definitions/mcp.mjs";import{PLATFORM_HOOK_CAPABILITIES as a}from"../definitions/platform-capabilities.mjs";import{PROMPTS as o}from"../definitions/prompts.mjs";import{PROTOCOLS as s}from"../definitions/protocols.mjs";import{buildAgentTable as c,buildCompactAgentTable as l,buildFlowSection as u,buildHooksSection as d,buildPromptSections as f,buildSkillsSection as p,lowerFirst as m}from"./_shared.mjs";const h=u({flowTool:`flow`,isLifecycleOwner:!0}),g=u({flowTool:`flow`,isLifecycleOwner:!1});function _(e,n){let r=t[e];return typeof r==`function`?r(n):r||``}function v(e){let t=[];e.extraBody&&t.push(e.extraBody),e.sharedBase&&s[e.sharedBase]&&t.push(s[e.sharedBase]);for(let n of e.sharedProtocols||[])s[n]&&t.push(s[n]);return t}function y(){let t=[];for(let[n,r]of Object.entries(e)){if(r.variants){for(let[e,i]of Object.entries(r.variants))t.push({name:`${n}-${e}`,description:i.description||r.description});continue}t.push({name:n,description:r.description})}return t}function b(e){return y().map(({name:t,description:n})=>`- \`${e}/${t}.md\` — ${n}`).join(`
2
+ `)}const x={orchestrator:{edit:`allow`,bash:`allow`},planner:{edit:`allow`,bash:`allow`},codeAgent:{edit:`allow`,bash:`allow`},refactor:{edit:`allow`,bash:`allow`},debugger:{edit:`allow`,bash:`allow`},security:{edit:`allow`,bash:`allow`},documenter:{edit:`allow`,bash:`deny`},explorer:{edit:`deny`,bash:`deny`},researcher:{edit:`deny`,bash:`deny`},reviewer:{edit:`deny`,bash:`deny`}};function S(e){let t=e.category===`orchestration`&&e.title===`The Master Conductor`?`primary`:`subagent`,n=x[e.toolRole]||{edit:`deny`,bash:`deny`},r=[`---`,`description: ${e.description}`,`mode: ${t}`,`permission:`];for(let[e,t]of Object.entries(n))r.push(` ${e}: ${t}`);return r.push(`---`),r.join(`
3
+ `)}function C(e,t,n){let r=v(t),i=p(t.skills),a=e===`Orchestrator`?h:g,o=[n,...r,t.bodyAddendum,i,a].filter(Boolean).join(`
4
4
 
5
- `).trim(),s=[`# ${e} - ${t.title||e}`,``,`> ${t.description}`,``,t.identity?`You are the **${e}**${t.identity}`:`You are the **${e}**, ${p(t.description)}.`,...o?[``,o]:[],``].join(`
6
- `);return`${x(t)}\n\n${s}`}function C(){return[`# aikit — OpenCode Agents`,``,"This workspace keeps the root `OPENCODE.md` intentionally small. Full agent instructions are split into per-agent files under `.opencode/agents/`.",``,`## MCP Server`,``,"Server name: `aikit`","Configure in `.opencode/opencode.json` (already done if scaffold was deployed).",``,`## Agents`,``,s(e),``,`## Agent Files`,``,y(`.opencode/agents`),``,`## Prompts`,``,d(a),``,`## Session Protocol`,``,u(n),``].join(`
7
- `)}function w(){return[`# aikit — OpenCode Instructions`,``,"Detailed workspace instructions live in `OPENCODE.md`.","Per-agent instructions are emitted under `.opencode/agents/`.","MCP server config lives in `.opencode/opencode.json`.",``,"Start with `OPENCODE.md`, then open the specific agent file you want to use.",``].join(`
8
- `)}function T(e,t){let{type:n,...r}=t;return JSON.stringify({mcp:{[e]:{type:`local`,command:[r.command,...r.args]}}},null,2)}function E(){let t=[{path:`.opencode/opencode.json`,content:`${T(i,r)}\n`},{path:`OPENCODE.md`,content:w()},{path:`AGENTS.md`,content:C()}],n=c(e);for(let[r,i]of Object.entries(e)){let e=g(r,n);if(i.variants){for(let[n,a]of Object.entries(i.variants)){let o=`${r}-${n}`;t.push({path:`.opencode/agents/${o}.md`,content:S(o,{...i,description:a.description||i.description,identity:a.identity,bodyAddendum:a.bodyAddendum},e)})}continue}t.push({path:`.opencode/agents/${r}.md`,content:S(r,i,e)})}return t}export{E as generateOpencode};
5
+ `).trim(),s=[`# ${e} - ${t.title||e}`,``,`> ${t.description}`,``,t.identity?`You are the **${e}**${t.identity}`:`You are the **${e}**, ${m(t.description)}.`,...o?[``,o]:[],``].join(`
6
+ `);return`${S(t)}\n\n${s}`}function w(){return[`# aikit — OpenCode Agents`,``,"This workspace keeps the root `OPENCODE.md` intentionally small. Full agent instructions are split into per-agent files under `.opencode/agents/`.",``,`## MCP Server`,``,"Server name: `aikit`","Configure in `.opencode/opencode.json` (already done if scaffold was deployed).",``,`## Agents`,``,c(e),``,`## Agent Files`,``,b(`.opencode/agents`),``,`## Prompts`,``,f(o),``,`## Session Protocol`,``,d(n,a.opencode),``].join(`
7
+ `)}function T(){return[`# aikit — OpenCode Instructions`,``,"Detailed workspace instructions live in `OPENCODE.md`.","Per-agent instructions are emitted under `.opencode/agents/`.","MCP server config lives in `.opencode/opencode.json`.",``,"Start with `OPENCODE.md`, then open the specific agent file you want to use.",``].join(`
8
+ `)}function E(e,t){let{type:n,...r}=t;return JSON.stringify({mcp:{[e]:{type:`local`,command:[r.command,...r.args]}}},null,2)}function D(){let t=[{path:`.opencode/opencode.json`,content:`${E(i,r)}\n`},{path:`OPENCODE.md`,content:T()},{path:`AGENTS.md`,content:w()}],n=l(e);for(let[r,i]of Object.entries(e)){let e=_(r,n);if(i.variants){for(let[n,a]of Object.entries(i.variants)){let o=`${r}-${n}`;t.push({path:`.opencode/agents/${o}.md`,content:C(o,{...i,description:a.description||i.description,identity:a.identity,bodyAddendum:a.bodyAddendum},e)})}continue}t.push({path:`.opencode/agents/${r}.md`,content:C(r,i,e)})}return t}export{D as generateOpencode};
@@ -1 +1 @@
1
- const e=[`copilot`,`claude`,`copilotCli`,`hermes`],t={SessionStart:{copilot:`SessionStart`,claude:`SessionStart`,copilotCli:`sessionStart`,hermes:`SessionStart`},PreToolUse:{copilot:`PreToolUse`,claude:`PreToolUse`,copilotCli:`preToolUse`,hermes:`PreToolUse`},PostToolUse:{copilot:`PostToolUse`,claude:`PostToolUse`,copilotCli:`postToolUse`,hermes:`PostToolUse`},SubagentStart:{copilot:`SubagentStart`,claude:`SubagentStart`,copilotCli:`subagentStart`,hermes:`SubagentStart`},PreCompact:{copilot:`PreCompact`,claude:`PreCompact`,copilotCli:`preCompact`,hermes:`PreCompact`},Stop:{copilot:`Stop`,claude:`Stop`,copilotCli:`stop`,hermes:`Stop`}},n={fileRead:{copilot:[`read_file`,`readFile`],claude:[`Read`],copilotCli:[`read_file`],hermes:[`read_file`]},fileWrite:{copilot:[`editFiles`,`replace_string_in_file`,`create_file`],claude:[`Write`,`Edit`],copilotCli:[`editFiles`,`replace_string_in_file`],hermes:[`write_file`,`patch`]},fileSearch:{copilot:[`grep_search`,`semantic_search`,`find`],claude:[`Bash`],copilotCli:[`grep_search`],hermes:[`search_files`]}},r={"privacy-guard":{id:`privacy-guard`,event:`PreToolUse`,tier:`safety`,description:`Blocks reads of secret-bearing env files, key material, SSH paths, and credential artifacts.`,script:`privacy-guard.mjs`,matcher:[`fileRead`],scope:`user`,patterns:[`.env`,`*.pem`,`*.key`,`id_rsa*`,`.ssh/*`,`*credentials*`,`*.secret`]},"scout-guard":{id:`scout-guard`,event:`PreToolUse`,tier:`safety`,description:`Blocks reads and searches inside generated, dependency, and git object directories.`,script:`scout-guard.mjs`,matcher:[`fileRead`,`fileSearch`],scope:`user`,patterns:[`node_modules/`,`dist/`,`.git/objects/`,`vendor/`,`build/`]},"subagent-context":{id:`subagent-context`,event:`SubagentStart`,tier:`efficiency`,description:`Injects compact project context into each subagent spawn to reduce repeated discovery.`,script:`subagent-context.mjs`,scope:`user`},"pre-compact-save":{id:`pre-compact-save`,event:`PreCompact`,tier:`efficiency`,description:`Persists critical state before context compaction truncates recent session history.`,script:`pre-compact-save.mjs`,scope:`user`},"post-edit-check":{id:`post-edit-check`,event:`PostToolUse`,tier:`nudge`,description:`After repeated file edits, injects a validation reminder so checks are not skipped.`,script:`post-edit-check.mjs`,matcher:[`fileWrite`],scope:`user`},"session-init":{id:`session-init`,event:`SessionStart`,tier:`nudge`,description:`Detects project stack and injects workspace metadata and environment context at startup.`,script:`session-init.mjs`,scope:`user`},"session-observer":{id:`session-observer`,event:`PostToolUse`,tier:`efficiency`,description:`Captures tool usage patterns for autonomous lesson extraction. Writes to session buffer.`,script:`session-observer.mjs`,scope:`user`},"session-learn":{id:`session-learn`,event:`Stop`,tier:`efficiency`,description:`Nudges final pattern analysis on buffered observations at session end.`,script:`session-learn.mjs`,scope:`user`}};function i(r){let i=[{name:`HOOK_EVENTS`,entries:Object.values(t)},{name:`HOOK_TOOL_MATCHERS`,entries:Object.values(n)}];for(let t of e){for(let e of i)for(let n of e.entries)if(!(t in n))throw Error(`Registration drift: platform "${t}" is missing from ${e.name} (entry has keys: ${Object.keys(n).join(`, `)}). Add a "${t}" key to all entries in ${e.name} in exec-hooks.mjs.`);if(r){for(let[e,n]of Object.entries(r))if(!(t in n))throw Error(`Registration drift: platform "${t}" is missing from ${e} (has keys: ${Object.keys(n).join(`, `)}). Add "${t}: '...'" to ${e}.`)}}}export{r as EXEC_HOOKS,t as HOOK_EVENTS,n as HOOK_TOOL_MATCHERS,e as SUPPORTED_PLATFORMS,i as validateAllPlatformRegistries};
1
+ const e=[`copilot`,`claude`,`copilotCli`,`hermes`],t={SessionStart:{copilot:`SessionStart`,claude:`SessionStart`,copilotCli:`sessionStart`,hermes:`SessionStart`},PreToolUse:{copilot:`PreToolUse`,claude:`PreToolUse`,copilotCli:`preToolUse`,hermes:`PreToolUse`},PostToolUse:{copilot:`PostToolUse`,claude:`PostToolUse`,copilotCli:`postToolUse`,hermes:`PostToolUse`},SubagentStart:{copilot:`SubagentStart`,claude:`SubagentStart`,copilotCli:`subagentStart`,hermes:`SubagentStart`},PreCompact:{copilot:`PreCompact`,claude:`PreCompact`,copilotCli:`preCompact`,hermes:`PreCompact`},Stop:{copilot:`Stop`,claude:`Stop`,copilotCli:`stop`,hermes:`Stop`}},n={fileRead:{copilot:[`read_file`,`readFile`],claude:[`Read`],copilotCli:[`read_file`],hermes:[`read_file`]},fileWrite:{copilot:[`editFiles`,`replace_string_in_file`,`create_file`],claude:[`Edit`,`Write`,`MultiEdit`],copilotCli:[`editFiles`,`replace_string_in_file`],hermes:[`write_file`,`patch`]},fileSearch:{copilot:[`grep_search`,`semantic_search`,`find`],claude:[`Bash`],copilotCli:[`grep_search`],hermes:[`search_files`]}},r={"privacy-guard":{id:`privacy-guard`,event:`PreToolUse`,category:`guard`,description:`Deny reads of secret-bearing files: .env, .pem, .key, .ssh/*, credentials, tokens, .netrc.`,llmBenefit:`Prevents accidental leakage of API keys, tokens, and credentials into conversation context.`,script:`privacy-guard.mjs`,matcher:[`fileRead`],outputBudgetLines:0,executionModel:`child-process`,scope:`user`,failurePolicy:`deny-closed`,supportedPlatforms:[`copilot`,`claude`,`copilotCli`,`hermes`],unsupportedBehavior:`Reject via agent instruction — no native hook runtime available`,evidenceRequirements:`Hook script tested against known secret patterns`,rollbackBehavior:`Remove from settings.json hooks block and delete script file`},"scout-guard":{id:`scout-guard`,event:`PreToolUse`,category:`guard`,description:`Deny reads inside generated/dependency directories: node_modules/, dist/, .git/objects/, vendor/, build/.`,llmBenefit:`Keeps context clean by preventing reads of irrelevant generated or vendored files.`,script:`scout-guard.mjs`,matcher:[`fileRead`,`fileSearch`],outputBudgetLines:0,executionModel:`child-process`,scope:`user`,failurePolicy:`deny-closed`,supportedPlatforms:[`copilot`,`claude`,`copilotCli`,`hermes`],unsupportedBehavior:`Reject via agent instruction — no native hook runtime available`,evidenceRequirements:`Hook script tested against known generated directory patterns`,rollbackBehavior:`Remove from settings.json hooks block and delete script file`},"freshness-signal":{id:`freshness-signal`,event:`SessionStart`,category:`signal`,description:`Check workspace state freshness at session start: onboard staleness, active flow, scaffold drift.`,llmBenefit:`Avoids working with stale onboard data or drifting scaffold state.`,script:`freshness-signal.mjs`,outputBudgetLines:5,executionModel:`child-process`,scope:`user`,silentWhenHealthy:!0,failurePolicy:`allow-open`,matcher:[],supportedPlatforms:[`copilot`,`claude`,`copilotCli`,`hermes`],unsupportedBehavior:`Skip no native hook runtime on this platform`,evidenceRequirements:`None (signal-only, no enforcement)`,rollbackBehavior:`Remove from settings.json hooks block`},"scope-guard":{id:`scope-guard`,event:`PostToolUse`,category:`signal`,description:`After file writes, detect edit drift: files changed outside flow scope, too many files, scaffold source/dist mismatch.`,llmBenefit:`Prevents accidental scope creep, over-editing, and scaffold drift during a session.`,script:`scope-guard.mjs`,matcher:[`fileWrite`],outputBudgetLines:5,executionModel:`child-process`,scope:`user`,silentWhenHealthy:!0,failurePolicy:`allow-open`,supportedPlatforms:[`copilot`,`claude`,`copilotCli`,`hermes`],unsupportedBehavior:`Skip no native hook runtime on this platform`,evidenceRequirements:`None (signal-only, no enforcement)`,rollbackBehavior:`Remove from settings.json hooks block`}},i=[`id`,`event`,`category`,`script`,`description`],a=[`matcher`,`llmBenefit`,`executionModel`,`scope`,`failurePolicy`,`outputBudgetLines`,`supportedPlatforms`,`unsupportedBehavior`],o=[`evidenceRequirements`,`rollbackBehavior`],s=[`silentWhenHealthy`];function c(){let e=new Set(Object.keys(t)),n=[`child-process`,`eventbus-subscriber`],c=[`deny-closed`,`allow-open`];for(let[t,l]of Object.entries(r)){for(let e of i){if(!(e in l)||l[e]===void 0||l[e]===null)throw Error(`EXEC_HOOKS["${t}"] is missing or null Tier 1 field "${e}". Add it to the "${t}" entry in exec-hooks.mjs.`);if(typeof l[e]==`string`&&l[e].trim()===``)throw Error(`EXEC_HOOKS["${t}"] has empty string for Tier 1 field "${e}". Provide a non-empty value.`)}for(let e of a)(!(e in l)||l[e]===void 0||l[e]===null)&&console.warn(`EXEC_HOOKS["${t}"] is missing Tier 2 field "${e}". Add it when the field values are actionable.`);if(l.category===`guard`)for(let e of o)(!(e in l)||l[e]===void 0||l[e]===null)&&console.warn(`EXEC_HOOKS["${t}"] (guard) is missing guard field "${e}". Add it when guard lifecycle tooling exists.`);if(l.category===`signal`)for(let e of s)(!(e in l)||l[e]===void 0||l[e]===null)&&console.warn(`EXEC_HOOKS["${t}"] (signal) is missing signal field "${e}". Add it when signal lifecycle tooling exists.`);if(l.id!==t)throw Error(`EXEC_HOOKS["${t}"] has id="${l.id}" which doesn't match its object key. Set id to "${t}".`);if(!e.has(l.event))throw Error(`EXEC_HOOKS["${t}"] has unrecognized event "${l.event}". Valid: ${[...e].join(`, `)}.`);if(![`guard`,`signal`].includes(l.category))throw Error(`EXEC_HOOKS["${t}"] has invalid category "${l.category}". Must be "guard" or "signal".`);[`user`,`workspace`].includes(l.scope)||console.warn(`EXEC_HOOKS["${t}"] has invalid scope "${l.scope}". Must be "user" or "workspace".`),l.executionModel&&!n.includes(l.executionModel)&&console.warn(`EXEC_HOOKS["${t}"] has unrecognized executionModel "${l.executionModel}". Valid: ${n.join(`, `)}.`),l.failurePolicy&&!c.includes(l.failurePolicy)&&console.warn(`EXEC_HOOKS["${t}"] has unrecognized failurePolicy "${l.failurePolicy}". Valid: ${c.join(`, `)}.`)}}function l(r){let i=[{name:`HOOK_EVENTS`,entries:Object.values(t)},{name:`HOOK_TOOL_MATCHERS`,entries:Object.values(n)}];for(let t of e){for(let e of i)for(let n of e.entries)if(!(t in n))throw Error(`Registration drift: platform "${t}" is missing from ${e.name} (entry has keys: ${Object.keys(n).join(`, `)}). Add a "${t}" key to all entries in ${e.name} in exec-hooks.mjs.`);if(r){for(let[e,n]of Object.entries(r))if(!(t in n))throw Error(`Registration drift: platform "${t}" is missing from ${e} (has keys: ${Object.keys(n).join(`, `)}). Add "${t}: '...'" to ${e}.`)}}}function u(e){let t=[];if(!e||typeof e!=`object`)return t.push(`Generated hook must be a non-null object`),t;for(let n of[`id`,`event`,`category`,`description`])(!e[n]||typeof e[n]!=`string`)&&t.push(`Generated hook missing or invalid "${n}" (must be a non-empty string)`);e.category&&![`guard`,`signal`].includes(e.category)&&t.push(`Generated hook has invalid category "${e.category}". Must be "guard" or "signal".`);let n=[`candidate`,`evaluated`,`promoted`,`rejected`,`disabled`];return e.promotionState&&!n.includes(e.promotionState)&&t.push(`Generated hook has invalid promotionState "${e.promotionState}". Valid: ${n.join(`, `)}.`),e.confidence!==void 0&&(typeof e.confidence!=`number`||e.confidence<0||e.confidence>1)&&t.push(`Generated hook confidence must be a number between 0 and 1, got "${e.confidence}"`),e.originToolNames!==void 0&&!Array.isArray(e.originToolNames)&&t.push(`Generated hook originToolNames must be an array of strings`),e.promotionState===`promoted`&&!e.createdAt&&t.push(`Generated hook promoted but missing createdAt timestamp`),t}export{r as EXEC_HOOKS,t as HOOK_EVENTS,n as HOOK_TOOL_MATCHERS,e as SUPPORTED_PLATFORMS,l as validateAllPlatformRegistries,u as validateGeneratedHookDef,c as validateHookDefinitions};
@@ -0,0 +1 @@
1
+ const e=[`SessionStart`,`PreToolUse`,`PostToolUse`,`PreCommit`,`SessionEnd`],t=[`native`,`eventbus`,`middleware`,`none`],n=[`full`,`partial`,`eventbus`,`middleware`,`none`],r={"claude-code":{SessionStart:`native`,PreToolUse:`native`,PostToolUse:`native`,PreCommit:`native`,SessionEnd:`native`,overall:`full`,label:`Claude Code`},"claude-desktop":{SessionStart:`eventbus`,PreToolUse:`none`,PostToolUse:`eventbus`,PreCommit:`none`,SessionEnd:`eventbus`,overall:`eventbus`,label:`Claude Desktop`},cursor:{SessionStart:`none`,PreToolUse:`none`,PostToolUse:`none`,PreCommit:`none`,SessionEnd:`none`,overall:`none`,label:`Cursor`},windsurf:{SessionStart:`none`,PreToolUse:`none`,PostToolUse:`none`,PreCommit:`none`,SessionEnd:`none`,overall:`none`,label:`Windsurf`},copilot:{SessionStart:`none`,PreToolUse:`native`,PostToolUse:`native`,PreCommit:`none`,SessionEnd:`none`,overall:`partial`,label:`GitHub Copilot`},hermes:{SessionStart:`middleware`,PreToolUse:`middleware`,PostToolUse:`middleware`,PreCommit:`none`,SessionEnd:`middleware`,overall:`middleware`,label:`Hermes Agent (legacy)`},"hermes-agent":{SessionStart:`middleware`,PreToolUse:`middleware`,PostToolUse:`middleware`,PreCommit:`none`,SessionEnd:`middleware`,overall:`middleware`,label:`Hermes Agent`},codex:{SessionStart:`none`,PreToolUse:`none`,PostToolUse:`none`,PreCommit:`none`,SessionEnd:`none`,overall:`none`,label:`Codex CLI`},gemini:{SessionStart:`none`,PreToolUse:`none`,PostToolUse:`none`,PreCommit:`none`,SessionEnd:`none`,overall:`none`,label:`Gemini CLI`},opencode:{SessionStart:`none`,PreToolUse:`none`,PostToolUse:`none`,PreCommit:`none`,SessionEnd:`none`,overall:`none`,label:`OpenCode`},zed:{SessionStart:`none`,PreToolUse:`none`,PostToolUse:`none`,PreCommit:`none`,SessionEnd:`none`,overall:`none`,label:`Zed`},intellij:{SessionStart:`none`,PreToolUse:`none`,PostToolUse:`none`,PreCommit:`none`,SessionEnd:`none`,overall:`none`,label:`IntelliJ`}};function i(){let i=new Set(e),a=[`overall`,`label`];for(let[o,s]of Object.entries(r)){for(let e of i)if(!(e in s))throw Error(`Capability matrix: platform "${o}" is missing event "${e}". Add it to PLATFORM_HOOK_CAPABILITIES.${o}.`);for(let e of a)if(!(e in s))throw Error(`Capability matrix: platform "${o}" is missing "${e}". Add it to PLATFORM_HOOK_CAPABILITIES.${o}.`);if(typeof s.label!=`string`||s.label.length===0)throw Error(`Capability matrix: platform "${o}" has empty or missing "label". Set a non-empty label.`);if(!n.includes(s.overall))throw Error(`Capability matrix: platform "${o}" has invalid overall "${s.overall}". Valid: ${n.join(`, `)}.`);if(e.every(e=>s[e]===`none`)&&s.overall!==`none`)throw Error(`Capability matrix: platform "${o}" has all events 'none' but overall="${s.overall}". Set overall to 'none'.`);for(let e of i)if(!t.includes(s[e]))throw Error(`Capability matrix: platform "${o}" event "${e}" has invalid level "${s[e]}". Valid: ${t.join(`, `)}.`)}}function a(e){let t=Object.keys(r),n=new Set(e);for(let e of t)n.has(e)||console.warn(`Capability matrix: platform "${e}" has no corresponding adapter in generate.mjs ADAPTERS. Hook generation for this platform will be skipped.`)}export{e as HOOK_EVENTS,r as PLATFORM_HOOK_CAPABILITIES,i as validateCapabilityMatrix,a as validateMatrixAdapterAlignment};
@@ -24,23 +24,24 @@ ${e===`<PROFILE>`?`**Profile:** Check your role → implementer | documenter | r
24
24
 
25
25
  AI Kit delivers knowledge through four layers:
26
26
 
27
- - **L0 — Generated Briefing Cards**: Fast, task-ready workspace setup. \`workspace-core\` available as local resource \`aikit://l0/workspace-core\` (full card) and via \`status({ includePrelude: true })\` (compact summary), both populated after \`onboard({ path, mode: 'generate' })\` runs for the workspace. If workspaceCore null → run onboard first. If a flow is active, \`workflow:<flow>\` loaded via flow context (replaces task card). Total budget: ${n.sessionStart.totalTokenBudget} tokens.
27
+ - **L0 — Generated Briefing Cards**: Fast, task-ready workspace setup. \`workspace-core\` available as local resource \`aikit://l0/workspace-core\` (full card) and via \`status({ includePrelude: true })\` (compact summary), both populated after \`onboard({ path, mode: 'generate' })\` runs for the workspace. Treat this card as already-loaded workspace context: consume it before broad \`search\`, \`scope_map\`, \`digest\`, or repeated \`file_summary\`. If workspaceCore null → run onboard first. If a flow is active, \`workflow:<flow>\` loaded via flow context (replaces task card). Total budget: ${n.sessionStart.totalTokenBudget} tokens.
28
28
  - **L1 — Flow Working Context**: Active run state, current step, and role-filtered dispatch context. Withdraw via \`knowledge({ action: 'withdraw', scope: 'flow', profile: '<role>', budget: 6000 })\`.
29
29
  - **L2 — Canonical Curated Knowledge**: Durable workspace archive managed by CuratedKnowledgeManager. Retrieve on demand via \`search()\` or \`knowledge({ action: 'read' })\`.
30
30
  - **L3 — Evidence Archive**: Auto-knowledge observations and tool-output evidence. Inspect only for provenance, conflict review, or promotion assessment. Never auto-injected.
31
31
 
32
32
  ### Hot Path
33
- 1. Session start → fetch \`aikit://l0/workspace-core\` resource (full card) or \`status({ includePrelude: true })\` (compact summary). Prerequisite: \`onboard({ path, mode: 'generate' })\` must have run for the workspace. If workspaceCore null → run onboard first, then status again. If flow active, \`workflow:<flow>\` loaded via flow context. If no flow, may add one task card via \`file_summary\`.
34
- 2. Active flow use L1 snapshot + dispatch refs before broad retrieval.
35
- 3. Retrieve L2 on demand.
36
- 4. Inspect L3 only for evidence, provenance, or promotion.
33
+ 1. Session start → fetch \`aikit://l0/workspace-core\` resource (full card) or \`status({ includePrelude: true })\` (compact summary). Prerequisite: \`onboard({ path, mode: 'generate' })\` must have run for the workspace. If workspaceCore null → run onboard first, then status again.
34
+ 2. Before broad repo scans, extract and reuse the L0 facts: workspace shape, package map, tool surfaces, key entry points, constraints, and known pitfalls. If L0 answers enough, skip \`scope_map\`/wide \`search\` and go straight to task-specific retrieval.
35
+ 3. If flow active, \`workflow:<flow>\` loaded via flow context; use L1 snapshot + dispatch refs before broad retrieval. If no flow, may add one task card via \`file_summary\`.
36
+ 4. Retrieve L2 on demand.
37
+ 5. Inspect L3 only for evidence, provenance, or promotion.
37
38
 
38
39
  ### No-Flow Fallback
39
40
  - Keep L1 absent; workspace-core available via status after onboard; optionally one task card.
40
41
  - L2 on demand; turn-local scratch, no L1 synthesis.
41
42
 
42
43
  ### Degraded Fallback
43
- - Missing L0 card deterministic regeneration → status prelude + context manifest.
44
+ - Missing L0 card after regeneration → use \`status({ includePrelude: true })\` as compact fallback, then only task-specific L2 retrieval; do not replace L0 with a workspace-wide scan.
44
45
  - Unavailable L1 → read persisted FlowRunMeta without mutation.
45
46
  - Unavailable indexed L2 → read canonical curated markdown directly.
46
47
  - Mark missing evidence unresolved; block promotion.
@@ -19,10 +19,10 @@ Use AI Kit as compression, memory, validation, and coordination layer. Each call
19
19
  Start:
20
20
  1. status({ includePrelude: true }).
21
21
  2. If prelude.workspaceCore is null → run \`onboard({ path, mode: 'generate' })\`, then \`status({ includePrelude: true })\` again.
22
- 3. **L0 session start**: Fetch \`aikit://l0/workspace-core\` resource (full card) or \`status({ includePrelude: true })\` (compact summary). If a flow is active, workflow card loaded via flow context. ≤ 2,100 tokens.
22
+ 3. **L0 session start**: Fetch \`aikit://l0/workspace-core\` resource (full card) or \`status({ includePrelude: true })\` (compact summary). Extract workspace shape, package map, tool surfaces, key entry points, constraints, and known pitfalls before broad retrieval. If L0 answers enough, skip \`scope_map\`/wide \`search\`. If a flow is active, workflow card loaded via flow context. ≤ 2,100 tokens.
23
23
  4. search({ query: "SESSION CHECKPOINT", origin: "curated" }).
24
24
  5. flow({ action: 'status' }) when active flow may exist — use L1 snapshot before broad retrieval.
25
- 6. scope_map({ task }) for non-trivial work.
25
+ 6. scope_map({ task }) only when L0 + checkpoint do not identify a narrow workset.
26
26
 
27
27
  During:
28
28
  - Prefer search/file_summary/digest/symbol/trace/graph over raw reads.
@@ -40,7 +40,7 @@ End:
40
40
 
41
41
  | Layer | Need | Tool |
42
42
  |-------|------|------|
43
- | **L0** | Session briefing (after onboard) | Resource \`aikit://l0/workspace-core\` (full card) or \`status({ includePrelude: true })\` (compact summary). Run \`onboard\` first if card missing. |
43
+ | **L0** | Session briefing (after onboard) | Resource \`aikit://l0/workspace-core\` (full card) or \`status({ includePrelude: true })\` (compact summary). Run \`onboard\` first if card missing. Consume before broad scans. |
44
44
  | **L0** | Architecture overview | \`architecture\` briefing card |
45
45
  | **L0** | Known issues | \`known-issues\` briefing card |
46
46
  | **L0** | Flow-specific context | \`workflow:<flow>\` briefing card |
@@ -85,7 +85,7 @@ Review decayed/stale entries periodically with knowledge({ action: 'flagged' }).
85
85
  Flows guide multi-step work. Read active step before acting. FORGE handles tiering/evidence/gates; load references only when main skill is too thin.
86
86
 
87
87
  ### Layered Knowledge Protocol
88
- - **L0** → Generated briefing cards: load \`workspace-core\` at session start via \`aikit://l0/workspace-core\` resource (full card, live-updated) or \`status({ includePrelude: true })\` (compact summary). Requires \`onboard()\` first — if card null, run \`onboard\` before session start. Add \`workflow:<flow>\` if flow active, or one task card if no flow. Use \`file_summary({ tier: 'T1' })\` for structure, or \`file_summary({ tier: 'T2', query })\` for content. Token-bounded.
88
+ - **L0** → Generated briefing cards: load \`workspace-core\` at session start via \`aikit://l0/workspace-core\` resource (full card, live-updated) or \`status({ includePrelude: true })\` (compact summary). Requires \`onboard()\` first — if card null, run \`onboard\` before session start. Consume L0 as the default workspace map before \`search\`, \`scope_map\`, \`digest\`, or repeated \`file_summary\`; add \`workflow:<flow>\` if flow active, or one task card if no flow. Use \`file_summary({ tier: 'T1' })\` for structure, or \`file_summary({ tier: 'T2', query })\` for content only after L0 narrows the workset. Token-bounded.
89
89
  - **L1** → Flow working context. Withdraw before dispatch; reuse before broad retrieval.
90
90
  - **L2** → Canonical curated knowledge. Retrieve on demand; never preload broadly.
91
91
  - **L3** → Evidence/auto-knowledge. Inspect only for provenance, conflict, or promotion assessment.
@@ -1,6 +1,16 @@
1
1
  import path from 'node:path';
2
2
  import process from 'node:process';
3
3
 
4
+ /** Cross-platform event name aliases → canonical PascalCase. */
5
+ const EVENT_ALIASES = {
6
+ sessionstart: 'SessionStart',
7
+ pretooluse: 'PreToolUse',
8
+ posttooluse: 'PostToolUse',
9
+ subagentstart: 'SubagentStart',
10
+ precompact: 'PreCompact',
11
+ stop: 'Stop',
12
+ };
13
+
4
14
  const FILE_KEYS = new Set([
5
15
  'dirPath',
6
16
  'file',
@@ -33,16 +43,56 @@ const normalizePath = (value) => {
33
43
  };
34
44
 
35
45
  /**
36
- * Creates a hook entrypoint that normalizes stdin and writes a platform-neutral response.
46
+ * Normalize event name from any platform casing to canonical PascalCase.
47
+ * "postToolUse", "post_tool_use", "PostToolUse" all → "PostToolUse".
48
+ */
49
+ function normalizeEvent(rawEvent) {
50
+ const key = String(rawEvent ?? '')
51
+ .toLowerCase()
52
+ .replace(/[_-]/g, '');
53
+ return EVENT_ALIASES[key] ?? String(rawEvent ?? 'SessionStart');
54
+ }
55
+
56
+ /**
57
+ * Truncate a string to at most `maxLines` lines. Appends "(+N more)" if truncated.
58
+ * Returns empty string when input is empty or all-whitespace.
59
+ */
60
+ function truncateOutput(text, maxLines) {
61
+ if (!text?.trim()) return '';
62
+ const lines = text.split('\n');
63
+ if (lines.length <= maxLines) return text;
64
+ return `${lines.slice(0, maxLines).join('\n')}\n(+${lines.length - maxLines} more)`;
65
+ }
66
+
67
+ /**
68
+ * Create a hook entrypoint that normalizes stdin and writes a platform-neutral response.
69
+ *
37
70
  * @param {(context: ReturnType<typeof normalizeContext>) => Promise<object> | object} handler
71
+ * @param {object} [options]
72
+ * @param {string} [options.event] - If set, return allow for non-matching events (replaces per-script boilerplate).
73
+ * @param {'guard'|'signal'} [options.category] - 'guard' hooks deny on error; 'signal' hooks fail silent/allow.
74
+ * @param {number} [options.outputBudgetLines] - Max lines in additionalContext (0 = suppress all output).
38
75
  */
39
- export async function createHook(handler) {
76
+ export async function createHook(handler, options = {}) {
40
77
  try {
41
78
  const raw = await readStdin();
42
- const result = (await handler(normalizeContext(raw))) ?? { decision: 'allow' };
43
- respond(result);
79
+ const context = normalizeContext(raw);
80
+
81
+ // Event filter — silently allow non-matching events
82
+ if (options.event && context.event !== options.event) {
83
+ respond({ decision: 'allow' });
84
+ return;
85
+ }
86
+
87
+ const result = (await handler(context)) ?? { decision: 'allow' };
88
+ const output = normalizeOutput(result, options);
89
+ respond(output);
44
90
  } catch (error) {
45
- respond({ decision: 'deny', reason: error instanceof Error ? error.message : String(error) });
91
+ const isGuard = options.category === 'guard';
92
+ respond({
93
+ decision: isGuard ? 'deny' : 'allow',
94
+ reason: isGuard ? `Hook error: ${error.message || error}` : undefined,
95
+ });
46
96
  }
47
97
  }
48
98
 
@@ -86,7 +136,7 @@ export function normalizeContext(raw = {}) {
86
136
  return {
87
137
  raw,
88
138
  platform,
89
- event: raw.event ?? raw.hookEvent ?? raw.hook_event ?? 'SessionStart',
139
+ event: normalizeEvent(raw.event ?? raw.hookEvent ?? raw.hook_event ?? 'SessionStart'),
90
140
  toolName: raw.toolName ?? raw.tool_name ?? raw.tool ?? '',
91
141
  toolInput,
92
142
  filePaths: extractFilePaths(toolInput),
@@ -97,21 +147,43 @@ export function normalizeContext(raw = {}) {
97
147
  };
98
148
  }
99
149
 
150
+ /**
151
+ * Normalize hook output: apply budget, suppress empty context, handle deny vs signal.
152
+ * @param {{ decision?: 'allow' | 'deny', reason?: string, additionalContext?: string, continue?: boolean }} result
153
+ * @param {{ outputBudgetLines?: number }} [options]
154
+ */
155
+ export function normalizeOutput(result = {}, options = {}) {
156
+ const decision = result.decision ?? 'allow';
157
+ if (decision === 'deny') {
158
+ return { decision: 'deny', reason: result.reason ?? 'Hook denied request.' };
159
+ }
160
+ const budget = options.outputBudgetLines ?? 0;
161
+ const rawContext = result.additionalContext || '';
162
+ const context = budget > 0 ? truncateOutput(rawContext, budget) : rawContext;
163
+ const payload = {};
164
+ if (context) payload.additionalContext = context;
165
+ if (result.continue === false) payload.continue = false;
166
+ return payload;
167
+ }
168
+
100
169
  /**
101
170
  * Writes a normalized hook response to stdout and sets the exit code.
102
171
  * @param {{ decision?: 'allow' | 'deny', reason?: string, additionalContext?: string, continue?: boolean }} result
103
172
  */
104
- export function respond(result = { decision: 'allow' }) {
173
+ export function respond(result = {}) {
105
174
  const decision = result.decision ?? 'allow';
106
- const payload =
107
- decision === 'deny'
108
- ? { decision: 'deny', reason: result.reason ?? 'Hook denied request.' }
109
- : {
110
- ...(result.additionalContext ? { additionalContext: result.additionalContext } : {}),
111
- ...(result.continue === false ? { continue: false } : {}),
112
- };
175
+ if (decision === 'deny') {
176
+ process.stdout.write(
177
+ `${JSON.stringify({ decision: 'deny', reason: result.reason ?? 'Hook denied request.' })}\n`,
178
+ );
179
+ process.exitCode = 2;
180
+ return;
181
+ }
182
+ const payload = {};
183
+ if (result.additionalContext) payload.additionalContext = result.additionalContext;
184
+ if (result.continue === false) payload.continue = false;
113
185
  if (Object.keys(payload).length > 0) process.stdout.write(`${JSON.stringify(payload)}\n`);
114
- process.exitCode = decision === 'deny' ? 2 : 0;
186
+ process.exitCode = 0;
115
187
  }
116
188
 
117
189
  /**