continuous-improvement 3.9.2 → 3.11.0

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 (150) hide show
  1. package/.claude-plugin/marketplace.json +2 -2
  2. package/CHANGELOG.md +61 -0
  3. package/QUICKSTART.md +13 -8
  4. package/README.md +84 -153
  5. package/bin/backfill.mjs +0 -0
  6. package/bin/check-doc-runtime-claims.mjs +0 -0
  7. package/bin/check-docs-substrings.mjs +73 -0
  8. package/bin/check-everything-mirror.mjs +0 -0
  9. package/bin/check-routing-targets.mjs +0 -0
  10. package/bin/check-scripts-citation-drift.mjs +210 -0
  11. package/bin/check-skill-count.mjs +110 -0
  12. package/bin/check-skill-law-tag.mjs +0 -0
  13. package/bin/check-skill-mirror.mjs +0 -0
  14. package/bin/check-skill-tiers.mjs +0 -0
  15. package/bin/check-test-imports-only.mjs +126 -0
  16. package/bin/check-third-party-shape.mjs +0 -0
  17. package/bin/companion-preference-status.mjs +231 -0
  18. package/bin/gateguard-clear.mjs +53 -0
  19. package/bin/generate-plugin-manifests.mjs +7 -1
  20. package/bin/harvest-friction.mjs +0 -0
  21. package/bin/hook-stats.mjs +0 -0
  22. package/bin/install.mjs +182 -26
  23. package/bin/lint-transcript.mjs +0 -0
  24. package/bin/mcp-server.mjs +215 -2
  25. package/bin/observe.mjs +0 -0
  26. package/bin/pre-commit-block-strays.sh +0 -0
  27. package/bin/refresh-third-party.mjs +315 -313
  28. package/bin/unified-cli.mjs +0 -0
  29. package/commands/audit.md +34 -0
  30. package/commands/companion-preference.md +58 -0
  31. package/commands/discipline.md +14 -0
  32. package/commands/distill.md +39 -0
  33. package/commands/goal-check.md +53 -0
  34. package/commands/grill-me.md +40 -0
  35. package/commands/grill-with-docs.md +38 -0
  36. package/commands/handoff.md +42 -0
  37. package/commands/proceed-with-the-recommendation.md +20 -0
  38. package/commands/recall.md +49 -0
  39. package/commands/reconcile.md +47 -0
  40. package/commands/superpowers.md +2 -2
  41. package/commands/verify-install.md +55 -0
  42. package/hooks/companion-preference.mjs +190 -0
  43. package/hooks/gateguard.mjs +60 -25
  44. package/hooks/goal-drift-stop.mjs +183 -0
  45. package/hooks/observe.sh +15 -1
  46. package/hooks/route-prompt.mjs +180 -0
  47. package/hooks/route-table.json +35 -0
  48. package/hooks/three-section-close.mjs +134 -132
  49. package/lib/cli-anything.mjs +0 -0
  50. package/lib/compound-engineering.mjs +0 -0
  51. package/lib/gateguard-state.mjs +54 -2
  52. package/lib/goal-drift-gate.mjs +50 -0
  53. package/lib/goal-state.mjs +285 -0
  54. package/lib/observe-event.mjs +0 -0
  55. package/lib/plugin-metadata.mjs +126 -12
  56. package/lib/pm-marketplace.mjs +0 -0
  57. package/lib/pm-skills.mjs +0 -0
  58. package/lib/recall-index.mjs +175 -0
  59. package/lib/resolve-home-dir.mjs +0 -0
  60. package/lib/skill-distill.mjs +222 -0
  61. package/lib/skill-tiers.mjs +0 -0
  62. package/lib/unified-plugin.mjs +0 -0
  63. package/llms.txt +1 -1
  64. package/package.json +11 -7
  65. package/plugins/beginner.json +8 -3
  66. package/plugins/continuous-improvement/.claude-plugin/marketplace.json +2 -2
  67. package/plugins/continuous-improvement/.claude-plugin/plugin.json +2 -2
  68. package/plugins/continuous-improvement/agents/README.md +2 -2
  69. package/plugins/continuous-improvement/bin/backfill.mjs +0 -0
  70. package/plugins/continuous-improvement/bin/gateguard-clear.mjs +53 -0
  71. package/plugins/continuous-improvement/bin/mcp-server.mjs +215 -2
  72. package/plugins/continuous-improvement/bin/observe.mjs +0 -0
  73. package/plugins/continuous-improvement/commands/audit.md +34 -0
  74. package/plugins/continuous-improvement/commands/companion-preference.md +58 -0
  75. package/plugins/continuous-improvement/commands/discipline.md +14 -0
  76. package/plugins/continuous-improvement/commands/distill.md +39 -0
  77. package/plugins/continuous-improvement/commands/goal-check.md +53 -0
  78. package/plugins/continuous-improvement/commands/grill-me.md +40 -0
  79. package/plugins/continuous-improvement/commands/grill-with-docs.md +38 -0
  80. package/plugins/continuous-improvement/commands/handoff.md +42 -0
  81. package/plugins/continuous-improvement/commands/proceed-with-the-recommendation.md +20 -0
  82. package/plugins/continuous-improvement/commands/recall.md +49 -0
  83. package/plugins/continuous-improvement/commands/reconcile.md +47 -0
  84. package/plugins/continuous-improvement/commands/superpowers.md +2 -2
  85. package/plugins/continuous-improvement/commands/verify-install.md +55 -0
  86. package/plugins/continuous-improvement/hooks/companion-preference.mjs +190 -0
  87. package/plugins/continuous-improvement/hooks/gateguard.mjs +60 -25
  88. package/plugins/continuous-improvement/hooks/goal-drift-stop.mjs +183 -0
  89. package/plugins/continuous-improvement/hooks/hooks.json +18 -2
  90. package/plugins/continuous-improvement/hooks/observe.sh +15 -1
  91. package/plugins/continuous-improvement/hooks/route-prompt.mjs +180 -0
  92. package/plugins/continuous-improvement/hooks/route-table.json +35 -0
  93. package/plugins/continuous-improvement/hooks/three-section-close.mjs +134 -132
  94. package/plugins/continuous-improvement/lib/gateguard-state.mjs +137 -0
  95. package/plugins/continuous-improvement/lib/goal-drift-gate.mjs +50 -0
  96. package/plugins/continuous-improvement/lib/goal-state.mjs +285 -0
  97. package/plugins/continuous-improvement/lib/observe-event.mjs +0 -0
  98. package/plugins/continuous-improvement/lib/plugin-metadata.mjs +126 -12
  99. package/plugins/continuous-improvement/lib/recall-index.mjs +175 -0
  100. package/plugins/continuous-improvement/lib/resolve-home-dir.mjs +0 -0
  101. package/plugins/continuous-improvement/lib/skill-distill.mjs +222 -0
  102. package/plugins/continuous-improvement/skills/README.md +8 -0
  103. package/plugins/continuous-improvement/skills/audit/SKILL.md +73 -0
  104. package/plugins/continuous-improvement/skills/deploy-receipt/SKILL.md +13 -17
  105. package/plugins/continuous-improvement/skills/gateguard/SKILL.md +36 -17
  106. package/plugins/continuous-improvement/skills/goal-monitor/SKILL.md +81 -0
  107. package/plugins/continuous-improvement/skills/grill-me/SKILL.md +66 -0
  108. package/plugins/continuous-improvement/skills/grill-with-docs/SKILL.md +252 -0
  109. package/plugins/continuous-improvement/skills/handoff/SKILL.md +59 -0
  110. package/plugins/continuous-improvement/skills/para-memory-files/SKILL.md +1 -1
  111. package/plugins/continuous-improvement/skills/proceed-with-the-recommendation/SKILL.md +82 -6
  112. package/plugins/continuous-improvement/skills/ralph/SKILL.md +1 -0
  113. package/plugins/continuous-improvement/skills/recall/SKILL.md +50 -0
  114. package/plugins/continuous-improvement/skills/reconcile/SKILL.md +80 -0
  115. package/plugins/continuous-improvement/skills/recovery-classification/SKILL.md +1 -0
  116. package/plugins/continuous-improvement/skills/safety-guard/SKILL.md +1 -0
  117. package/plugins/continuous-improvement/skills/skill-distillation/SKILL.md +57 -0
  118. package/plugins/continuous-improvement/skills/state-reconciliation/SKILL.md +1 -0
  119. package/plugins/continuous-improvement/skills/strategic-compact/SKILL.md +1 -0
  120. package/plugins/continuous-improvement/skills/superpowers/SKILL.md +76 -2
  121. package/plugins/continuous-improvement/skills/token-budget-advisor/SKILL.md +1 -1
  122. package/plugins/continuous-improvement/skills/verification-loop/SKILL.md +28 -20
  123. package/plugins/continuous-improvement/skills/wild-risa-balance/SKILL.md +1 -0
  124. package/plugins/continuous-improvement/skills/workspace-surface-audit/SKILL.md +1 -1
  125. package/plugins/continuous-improvement/skills/worktree-safety/SKILL.md +3 -2
  126. package/plugins/expert.json +27 -2
  127. package/skills/README.md +3 -0
  128. package/skills/audit.md +73 -0
  129. package/skills/deploy-receipt.md +13 -17
  130. package/skills/gateguard.md +36 -17
  131. package/skills/goal-monitor.md +81 -0
  132. package/skills/grill-me.md +66 -0
  133. package/skills/grill-with-docs.md +252 -0
  134. package/skills/handoff.md +59 -0
  135. package/skills/para-memory-files.md +1 -1
  136. package/skills/proceed-with-the-recommendation.md +82 -6
  137. package/skills/ralph.md +1 -0
  138. package/skills/recall.md +50 -0
  139. package/skills/reconcile.md +80 -0
  140. package/skills/recovery-classification.md +1 -0
  141. package/skills/safety-guard.md +1 -0
  142. package/skills/skill-distillation.md +57 -0
  143. package/skills/state-reconciliation.md +1 -0
  144. package/skills/strategic-compact.md +1 -0
  145. package/skills/superpowers.md +76 -2
  146. package/skills/token-budget-advisor.md +1 -1
  147. package/skills/verification-loop.md +28 -20
  148. package/skills/wild-risa-balance.md +1 -0
  149. package/skills/workspace-surface-audit.md +1 -1
  150. package/skills/worktree-safety.md +3 -2
@@ -0,0 +1,183 @@
1
+ #!/usr/bin/env node
2
+ // goal-drift-stop.mts — Stop hook that scores the turn's activity against the
3
+ // stated goal (task_plan.md ## Goal) and, when the session has DRIFTed on a
4
+ // substantive wrap-up, warns or (opt-in) blocks the close. This is the runtime
5
+ // arm of goal-monitor: it auto-fires the drift check at the Stop phase boundary
6
+ // instead of waiting for someone to run /goal-check.
7
+ //
8
+ // Mode via CLAUDE_GOAL_DRIFT_GATE: "warn" (default) | "block" | "off".
9
+ // - warn : print a one-line drift notice to stderr; never blocks.
10
+ // - block : emit {"decision":"block","reason":...} to re-prompt.
11
+ // - off : no-op.
12
+ // The drift status is persisted to
13
+ // ~/.claude/instincts/<project-hash>/goal-drift-state.json every turn.
14
+ //
15
+ // Project-hash resolution is byte-identical to bin/observe.mts so this hook
16
+ // reads the SAME observations.jsonl the observer writes. Fail-open by
17
+ // construction: any error / missing goal / unreadable observations exits 0 and
18
+ // never blocks. No network. 5s hook budget.
19
+ import { execFileSync } from "node:child_process";
20
+ import { createHash } from "node:crypto";
21
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
22
+ import { join } from "node:path";
23
+ import { evaluateGoalDrift } from "../lib/goal-drift-gate.mjs";
24
+ import { resolveHomeDir } from "../lib/resolve-home-dir.mjs";
25
+ function readStdinSync() {
26
+ try {
27
+ return readFileSync(0, "utf8");
28
+ }
29
+ catch {
30
+ return "";
31
+ }
32
+ }
33
+ function safeJsonParse(text) {
34
+ try {
35
+ return JSON.parse(text);
36
+ }
37
+ catch {
38
+ return null;
39
+ }
40
+ }
41
+ function resolveMode() {
42
+ const raw = (process.env.CLAUDE_GOAL_DRIFT_GATE ?? "warn").trim().toLowerCase();
43
+ return raw === "block" || raw === "off" ? raw : "warn";
44
+ }
45
+ // Mirrors bin/observe.mts:resolveProjectRoot — the same basis the observer uses
46
+ // to bucket observations.jsonl, so the hash here resolves to the same file.
47
+ function resolveProjectRoot() {
48
+ const fromEnv = process.env.CLAUDE_PROJECT_DIR;
49
+ if (fromEnv)
50
+ return fromEnv;
51
+ try {
52
+ const root = execFileSync("git", ["rev-parse", "--show-toplevel"], {
53
+ encoding: "utf8",
54
+ stdio: ["ignore", "pipe", "ignore"],
55
+ }).trim();
56
+ if (root)
57
+ return root;
58
+ }
59
+ catch {
60
+ // not a git repo
61
+ }
62
+ return "global";
63
+ }
64
+ function projectHash(root) {
65
+ return createHash("sha256").update(root).digest("hex").slice(0, 12);
66
+ }
67
+ function readGoalMarkdown(projectRoot, instinctsProjectDir) {
68
+ const candidates = [join(projectRoot, "task_plan.md"), join(instinctsProjectDir, "goal.md")];
69
+ for (const file of candidates) {
70
+ try {
71
+ if (existsSync(file))
72
+ return readFileSync(file, "utf8");
73
+ }
74
+ catch {
75
+ // try the next candidate
76
+ }
77
+ }
78
+ return "";
79
+ }
80
+ function readObservations(instinctsProjectDir) {
81
+ const file = join(instinctsProjectDir, "observations.jsonl");
82
+ try {
83
+ if (!existsSync(file))
84
+ return [];
85
+ const lines = readFileSync(file, "utf8")
86
+ .split("\n")
87
+ .filter((line) => line.trim().length > 0);
88
+ const out = [];
89
+ for (const line of lines.slice(-100)) {
90
+ const row = safeJsonParse(line);
91
+ if (!row)
92
+ continue;
93
+ out.push({
94
+ ts: typeof row.ts === "string" ? row.ts : "",
95
+ tool: typeof row.tool === "string" ? row.tool : "",
96
+ input_summary: typeof row.input_summary === "string" ? row.input_summary : "",
97
+ output_summary: typeof row.output_summary === "string" ? row.output_summary : "",
98
+ });
99
+ }
100
+ return out;
101
+ }
102
+ catch {
103
+ return [];
104
+ }
105
+ }
106
+ function lastAssistantTextLength(transcriptPath) {
107
+ try {
108
+ const lines = readFileSync(transcriptPath, "utf8").split(/\r?\n/);
109
+ for (let i = lines.length - 1; i >= 0; i -= 1) {
110
+ const line = lines[i].trim();
111
+ if (!line)
112
+ continue;
113
+ const obj = safeJsonParse(line);
114
+ if (!obj || obj.type !== "assistant")
115
+ continue;
116
+ const blocks = obj.message?.content;
117
+ if (!Array.isArray(blocks))
118
+ continue;
119
+ const text = blocks
120
+ .filter((b) => b &&
121
+ typeof b === "object" &&
122
+ b.type === "text" &&
123
+ typeof b.text === "string")
124
+ .map((b) => b.text)
125
+ .join("\n");
126
+ if (text.length === 0)
127
+ continue;
128
+ return text.length;
129
+ }
130
+ }
131
+ catch {
132
+ // fall through to 0
133
+ }
134
+ return 0;
135
+ }
136
+ function persistState(instinctsProjectDir, state) {
137
+ try {
138
+ mkdirSync(instinctsProjectDir, { recursive: true });
139
+ writeFileSync(join(instinctsProjectDir, "goal-drift-state.json"), JSON.stringify({ ts: new Date().toISOString(), ...state }) + "\n", "utf8");
140
+ }
141
+ catch {
142
+ // state persistence is best-effort; never throw
143
+ }
144
+ }
145
+ function main() {
146
+ const mode = resolveMode();
147
+ if (mode === "off")
148
+ return;
149
+ const stdin = readStdinSync();
150
+ if (!stdin)
151
+ return;
152
+ const payload = safeJsonParse(stdin);
153
+ if (!payload)
154
+ return;
155
+ const home = resolveHomeDir();
156
+ if (!home)
157
+ return;
158
+ const projectRoot = resolveProjectRoot();
159
+ const instinctsProjectDir = join(home, ".claude", "instincts", projectHash(projectRoot));
160
+ const goalMarkdown = readGoalMarkdown(projectRoot, instinctsProjectDir);
161
+ const observations = readObservations(instinctsProjectDir);
162
+ const textLength = payload.transcript_path && existsSync(payload.transcript_path)
163
+ ? lastAssistantTextLength(payload.transcript_path)
164
+ : 0;
165
+ const decision = evaluateGoalDrift({ goalMarkdown, observations, textLength, mode });
166
+ persistState(instinctsProjectDir, {
167
+ status: decision.status,
168
+ score: decision.score,
169
+ action: decision.action,
170
+ });
171
+ if (decision.action === "block") {
172
+ process.stdout.write(JSON.stringify({ decision: "block", reason: decision.reason }) + "\n");
173
+ }
174
+ else if (decision.action === "warn") {
175
+ process.stderr.write(`[continuous-improvement] goal-drift: ${decision.reason}\n`);
176
+ }
177
+ }
178
+ try {
179
+ main();
180
+ }
181
+ catch {
182
+ // fail open — never block the session due to a hook bug
183
+ }
@@ -1,5 +1,5 @@
1
1
  {
2
- "description": "Gateguard fact-forcing PreToolUse, observation, session lifecycle, and 3-section-close discipline hooks for continuous-improvement.",
2
+ "description": "Gateguard fact-forcing PreToolUse, companion-preference enforcement, observation, session lifecycle, 3-section-close discipline, goal-drift Stop gate, and UserPromptSubmit lazy-routing hooks for continuous-improvement.",
3
3
  "hooks": {
4
4
  "PreToolUse": [
5
5
  {
@@ -11,7 +11,7 @@
11
11
  },
12
12
  {
13
13
  "type": "command",
14
- "command": "bash \"${CLAUDE_PLUGIN_ROOT}/hooks/observe.sh\"",
14
+ "command": "node \"${CLAUDE_PLUGIN_ROOT}/hooks/companion-preference.mjs\"",
15
15
  "timeout": 5
16
16
  }
17
17
  ]
@@ -28,6 +28,17 @@
28
28
  ]
29
29
  }
30
30
  ],
31
+ "UserPromptSubmit": [
32
+ {
33
+ "hooks": [
34
+ {
35
+ "type": "command",
36
+ "command": "node \"${CLAUDE_PLUGIN_ROOT}/hooks/route-prompt.mjs\"",
37
+ "timeout": 5
38
+ }
39
+ ]
40
+ }
41
+ ],
31
42
  "SessionStart": [
32
43
  {
33
44
  "hooks": [
@@ -57,6 +68,11 @@
57
68
  "type": "command",
58
69
  "command": "node \"${CLAUDE_PLUGIN_ROOT}/hooks/three-section-close.mjs\"",
59
70
  "timeout": 5
71
+ },
72
+ {
73
+ "type": "command",
74
+ "command": "node \"${CLAUDE_PLUGIN_ROOT}/hooks/goal-drift-stop.mjs\"",
75
+ "timeout": 5
60
76
  }
61
77
  ]
62
78
  }
@@ -101,6 +101,20 @@ fi
101
101
  # ---------------------------------------------------------------------------
102
102
  # Compute project hash and name
103
103
  # ---------------------------------------------------------------------------
104
+ # Canonicalize MSYS-mounted Windows paths (/d/Path) to native form (D:/Path)
105
+ # so the bash fallback hashes to the same project bucket as the Node observer.
106
+ # Without this, Git Bash on Windows produces /d/Ai/repo from `git rev-parse`
107
+ # while Node's child_process produces D:/Ai/repo for the same physical path,
108
+ # splitting telemetry between two SHA-256 buckets per project.
109
+ # Pattern matches /<single-letter>/... only — Linux/macOS paths like /usr,
110
+ # /home, /Users are unaffected.
111
+ case "$PROJECT_ROOT" in
112
+ /[a-zA-Z]/*)
113
+ drive_letter="$(printf '%s' "$PROJECT_ROOT" | cut -c2 | tr '[:lower:]' '[:upper:]')"
114
+ PROJECT_ROOT="${drive_letter}:$(printf '%s' "$PROJECT_ROOT" | cut -c3-)"
115
+ ;;
116
+ esac
117
+
104
118
  PROJECT_HASH="$(printf '%s' "$PROJECT_ROOT" | sha256sum | cut -c1-12)"
105
119
  PROJECT_NAME="$(basename "${PROJECT_ROOT%.git}")"
106
120
 
@@ -119,7 +133,7 @@ if [[ -f "$OBS_FILE" ]]; then
119
133
  LINE_COUNT="$(wc -l < "$OBS_FILE")"
120
134
  if (( LINE_COUNT >= 10000 )); then
121
135
  mv "$OBS_FILE" "${PROJECT_DIR}/observations.$(date -u +"%Y-%m-%d-%H%M%S").jsonl"
122
-
136
+
123
137
  # Clean up old archives - keep only 10 most recent
124
138
  ls -t "${PROJECT_DIR}"/observations.*.jsonl 2>/dev/null | tail -n +11 | xargs -r rm -f
125
139
  fi
@@ -0,0 +1,180 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Runtime UserPromptSubmit prompt-router hook.
4
+ *
5
+ * Reads the user prompt, matches it against patterns in
6
+ * hooks/route-table.json, and emits a single `<system-reminder>` recommending
7
+ * the matched skill. When no row matches, the hook emits nothing — the prompt
8
+ * passes through untouched.
9
+ *
10
+ * Stdin : JSON { prompt, session_id?, transcript_path?, cwd? }
11
+ * Stdout : JSON { hookSpecificOutput: { hookEventName: "UserPromptSubmit",
12
+ * additionalContext: string } }
13
+ * OR empty when there is no match.
14
+ * Exit : 0 always — routing decisions live in stdout, never in exit code.
15
+ *
16
+ * Fail-open: any unexpected error reading stdin / parsing the route table /
17
+ * compiling a pattern → emit nothing and exit 0. The hook never blocks a
18
+ * prompt on its own bugs.
19
+ *
20
+ * Telemetry: each match appends one JSONL line to
21
+ * ~/.claude/instincts/<project-hash>/route-prompt.jsonl with the row index,
22
+ * skill, and pattern. Telemetry failures never change the decision.
23
+ *
24
+ * Route table schema (hooks/route-table.json):
25
+ * {
26
+ * "rows": [
27
+ * { "pattern": "regex source", "flags": "i", "skill": "skill-name",
28
+ * "reason": "one-line description" }
29
+ * ]
30
+ * }
31
+ * Rows are evaluated in order; first match wins.
32
+ */
33
+ import { createHash } from "node:crypto";
34
+ import { appendFileSync, existsSync, mkdirSync, readFileSync } from "node:fs";
35
+ import { homedir } from "node:os";
36
+ import { dirname, join } from "node:path";
37
+ import { execFileSync } from "node:child_process";
38
+ import { fileURLToPath } from "node:url";
39
+ function readStdin() {
40
+ try {
41
+ return readFileSync(0, "utf8");
42
+ }
43
+ catch {
44
+ return "";
45
+ }
46
+ }
47
+ function resolveHome() {
48
+ return process.env.HOME ?? process.env.USERPROFILE ?? homedir();
49
+ }
50
+ function resolveProjectRoot() {
51
+ const fromEnv = process.env.CLAUDE_PROJECT_DIR;
52
+ if (fromEnv)
53
+ return fromEnv;
54
+ try {
55
+ const root = execFileSync("git", ["rev-parse", "--show-toplevel"], {
56
+ encoding: "utf8",
57
+ stdio: ["ignore", "pipe", "ignore"],
58
+ }).trim();
59
+ if (root)
60
+ return root;
61
+ }
62
+ catch {
63
+ // not in a git repo
64
+ }
65
+ return "global";
66
+ }
67
+ function telemetryPath(home) {
68
+ const hash = createHash("sha256")
69
+ .update(resolveProjectRoot())
70
+ .digest("hex")
71
+ .slice(0, 12);
72
+ return join(home, ".claude", "instincts", hash, "route-prompt.jsonl");
73
+ }
74
+ function writeTelemetry(home, event) {
75
+ try {
76
+ const path = telemetryPath(home);
77
+ const dir = dirname(path);
78
+ if (!existsSync(dir))
79
+ mkdirSync(dir, { recursive: true });
80
+ appendFileSync(path, `${JSON.stringify(event)}\n`);
81
+ }
82
+ catch {
83
+ // fail-open
84
+ }
85
+ }
86
+ function loadRouteTable() {
87
+ // Hook lives at <plugin-root>/hooks/route-prompt.mjs at runtime; the table
88
+ // sits next to it. import.meta.url gives the file:// URL of the compiled
89
+ // .mjs.
90
+ try {
91
+ const here = fileURLToPath(new URL(".", import.meta.url));
92
+ const tablePath = join(here, "route-table.json");
93
+ const raw = readFileSync(tablePath, "utf8");
94
+ const parsed = JSON.parse(raw);
95
+ if (!parsed || !Array.isArray(parsed.rows))
96
+ return null;
97
+ return parsed;
98
+ }
99
+ catch {
100
+ return null;
101
+ }
102
+ }
103
+ function findMatch(prompt, table) {
104
+ for (let i = 0; i < table.rows.length; i++) {
105
+ const row = table.rows[i];
106
+ if (!row || typeof row.pattern !== "string" || typeof row.skill !== "string") {
107
+ continue;
108
+ }
109
+ let rx;
110
+ try {
111
+ rx = new RegExp(row.pattern, row.flags ?? "");
112
+ }
113
+ catch {
114
+ continue;
115
+ }
116
+ if (rx.test(prompt)) {
117
+ return { row, index: i };
118
+ }
119
+ }
120
+ return null;
121
+ }
122
+ function buildContext(row) {
123
+ const reason = row.reason
124
+ ? `${row.reason}\nConsider invoking \`/${row.skill}\` now.`
125
+ : `Consider invoking \`/${row.skill}\` now.`;
126
+ return `<system-reminder>\nPrompt-router suggestion: ${reason}\n</system-reminder>`;
127
+ }
128
+ function emit(output) {
129
+ if (output !== null) {
130
+ process.stdout.write(`${JSON.stringify(output)}\n`);
131
+ }
132
+ process.exit(0);
133
+ }
134
+ function main() {
135
+ const raw = readStdin();
136
+ let payload;
137
+ try {
138
+ payload = JSON.parse(raw);
139
+ }
140
+ catch {
141
+ emit(null);
142
+ return;
143
+ }
144
+ const prompt = payload.prompt;
145
+ if (typeof prompt !== "string" || prompt.length === 0) {
146
+ emit(null);
147
+ return;
148
+ }
149
+ const table = loadRouteTable();
150
+ if (!table) {
151
+ emit(null);
152
+ return;
153
+ }
154
+ const match = findMatch(prompt, table);
155
+ const home = resolveHome();
156
+ if (!match) {
157
+ writeTelemetry(home, {
158
+ ts: new Date().toISOString(),
159
+ hook: "route-prompt",
160
+ matched: false,
161
+ });
162
+ emit(null);
163
+ return;
164
+ }
165
+ writeTelemetry(home, {
166
+ ts: new Date().toISOString(),
167
+ hook: "route-prompt",
168
+ matched: true,
169
+ row_index: match.index,
170
+ skill: match.row.skill,
171
+ pattern: match.row.pattern,
172
+ });
173
+ emit({
174
+ hookSpecificOutput: {
175
+ hookEventName: "UserPromptSubmit",
176
+ additionalContext: buildContext(match.row),
177
+ },
178
+ });
179
+ }
180
+ main();
@@ -0,0 +1,35 @@
1
+ {
2
+ "description": "Declarative prompt-router for the continuous-improvement UserPromptSubmit hook. Each row maps a regex (prompt-side trigger) to a skill the agent should consider invoking. First match wins; rows are evaluated in order. Edit this file to grow the routing table — no code change required.",
3
+ "rows": [
4
+ {
5
+ "pattern": "\\b(write|add|new)\\s+(a\\s+)?(failing\\s+)?test\\b|\\btdd\\b|\\bred[- ]green[- ]refactor\\b",
6
+ "flags": "i",
7
+ "skill": "tdd-workflow",
8
+ "reason": "Prompt mentions writing tests, TDD, or RED-GREEN-REFACTOR."
9
+ },
10
+ {
11
+ "pattern": "\\b(verify|verification|prove\\s+it\\s+works|smallest\\s+check|check\\s+it\\s+passes)\\b",
12
+ "flags": "i",
13
+ "skill": "verification-loop",
14
+ "reason": "Prompt asks for verification before declaring done."
15
+ },
16
+ {
17
+ "pattern": "\\b(plan|blueprint|design\\s+(the\\s+)?approach|step[- ]by[- ]step\\s+plan|roadmap)\\b",
18
+ "flags": "i",
19
+ "skill": "planning-with-files",
20
+ "reason": "Prompt asks for a plan, blueprint, or design before implementation."
21
+ },
22
+ {
23
+ "pattern": "\\b(security\\s+review|threat\\s+model|owasp|vulnerab|auth(entication|orization)\\s+check)\\b",
24
+ "flags": "i",
25
+ "skill": "security-review",
26
+ "reason": "Prompt touches security review, threat modeling, or auth surface."
27
+ },
28
+ {
29
+ "pattern": "\\b(code\\s+review|review\\s+(my|the|this)\\s+(code|pr|diff|patch)|critique\\s+(the|this)\\s+code)\\b",
30
+ "flags": "i",
31
+ "skill": "code-review",
32
+ "reason": "Prompt asks for a code review on existing changes."
33
+ }
34
+ ]
35
+ }