continuous-improvement 3.20.0 → 3.20.4
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.
- package/.claude-plugin/marketplace.json +1 -1
- package/QUICKSTART.md +1 -1
- package/README.md +2 -2
- package/bin/check-landing-version.mjs +63 -0
- package/bin/check-scripts-citation-drift.mjs +61 -13
- package/bin/generate-plugin-manifests.mjs +2 -0
- package/bin/install.mjs +19 -11
- package/commands/verify-install.md +1 -1
- package/hooks/gateguard.mjs +22 -3
- package/hooks/query-cost-nudge.mjs +1 -0
- package/hooks/typecheck-stop.mjs +2 -1
- package/package.json +5 -3
- package/plugins/beginner.json +1 -1
- package/plugins/continuous-improvement/.claude-plugin/marketplace.json +1 -1
- package/plugins/continuous-improvement/.claude-plugin/plugin.json +1 -1
- package/plugins/continuous-improvement/README.md +1 -0
- package/plugins/continuous-improvement/commands/verify-install.md +1 -1
- package/plugins/continuous-improvement/hooks/gateguard.mjs +22 -3
- package/plugins/continuous-improvement/hooks/query-cost-nudge.mjs +1 -0
- package/plugins/continuous-improvement/hooks/typecheck-stop.mjs +2 -1
- package/plugins/continuous-improvement/scripts/README.md +33 -0
- package/plugins/continuous-improvement/scripts/detect-deploy-target.sh +66 -0
- package/plugins/continuous-improvement/scripts/get-deployed-sha.sh +113 -0
- package/plugins/continuous-improvement/scripts/git-state-snapshot.sh +48 -0
- package/plugins/continuous-improvement/scripts/resolve-verify-ladder.mjs +241 -0
- package/plugins/continuous-improvement/scripts/route-recommendation.mjs +178 -0
- package/plugins/continuous-improvement/scripts/route-recommendation.routes.json +213 -0
- package/plugins/continuous-improvement/scripts/run-synthetic.mjs +298 -0
- package/plugins/continuous-improvement/scripts/scan-past-mistakes.mjs +285 -0
- package/plugins/continuous-improvement/skills/deploy-receipt/SKILL.md +2 -2
- package/plugins/continuous-improvement/skills/gateguard/SKILL.md +2 -2
- package/plugins/continuous-improvement/skills/proceed-with-the-recommendation/SKILL.md +2 -2
- package/plugins/continuous-improvement/skills/reconcile/SKILL.md +1 -1
- package/plugins/continuous-improvement/skills/verification-loop/SKILL.md +5 -5
- package/plugins/continuous-improvement/skills/workspace-surface-audit/SKILL.md +1 -1
- package/plugins/continuous-improvement/skills/worktree-safety/SKILL.md +1 -1
- package/plugins/expert.json +1 -1
- package/scripts/README.md +33 -0
- package/scripts/detect-deploy-target.sh +66 -0
- package/scripts/get-deployed-sha.sh +113 -0
- package/scripts/git-state-snapshot.sh +48 -0
- package/scripts/resolve-verify-ladder.mjs +241 -0
- package/scripts/route-recommendation.mjs +178 -0
- package/scripts/route-recommendation.routes.json +213 -0
- package/scripts/run-synthetic.mjs +298 -0
- package/scripts/scan-past-mistakes.mjs +285 -0
- package/skills/deploy-receipt.md +2 -2
- package/skills/gateguard.md +2 -2
- package/skills/proceed-with-the-recommendation.md +2 -2
- package/skills/reconcile.md +1 -1
- package/skills/verification-loop.md +5 -5
- package/skills/workspace-surface-audit.md +1 -1
- package/skills/worktree-safety.md +1 -1
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# scripts/git-state-snapshot.sh
|
|
3
|
+
#
|
|
4
|
+
# Emit a single-line JSON envelope describing the current git working-tree
|
|
5
|
+
# state. Composability primitive used by skills that need to baseline or check
|
|
6
|
+
# git state without each restating the same 3-command triple.
|
|
7
|
+
#
|
|
8
|
+
# Fields:
|
|
9
|
+
# - head: short SHA returned by `git rev-parse --short HEAD`
|
|
10
|
+
# - upstream: short SHA of `@{u}` if the branch tracks an upstream, else "none"
|
|
11
|
+
# - dirty: integer count of lines from `git status --porcelain` (0 == clean)
|
|
12
|
+
# - root: absolute path from `git rev-parse --show-toplevel`
|
|
13
|
+
# - branch: `git symbolic-ref --short HEAD`, else "detached"
|
|
14
|
+
#
|
|
15
|
+
# Outside a git repository the script prints `{"error":"not-a-git-repo"}` and
|
|
16
|
+
# exits 1. All other failures are treated as a non-git-repo condition rather
|
|
17
|
+
# than emitting a partial envelope.
|
|
18
|
+
#
|
|
19
|
+
# Cited by:
|
|
20
|
+
# - skills/gateguard.md (Parallel-Actor Gate baseline + divergence)
|
|
21
|
+
# - skills/worktree-safety.md (Root + branch alignment)
|
|
22
|
+
# - skills/workspace-surface-audit.md (Environment Grain — parallel-actor row)
|
|
23
|
+
|
|
24
|
+
set -u
|
|
25
|
+
|
|
26
|
+
head=$(git rev-parse --short HEAD 2>/dev/null) || {
|
|
27
|
+
printf '{"error":"not-a-git-repo"}\n'
|
|
28
|
+
exit 1
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
if upstream=$(git rev-parse --short '@{u}' 2>/dev/null); then
|
|
32
|
+
upstream_field=$(printf '"%s"' "$upstream")
|
|
33
|
+
else
|
|
34
|
+
upstream_field='"none"'
|
|
35
|
+
fi
|
|
36
|
+
|
|
37
|
+
dirty=$(git status --porcelain 2>/dev/null | wc -l | tr -d ' ')
|
|
38
|
+
|
|
39
|
+
root=$(git rev-parse --show-toplevel 2>/dev/null || printf 'unknown')
|
|
40
|
+
|
|
41
|
+
if branch=$(git symbolic-ref --short HEAD 2>/dev/null); then
|
|
42
|
+
branch_field=$(printf '"%s"' "$branch")
|
|
43
|
+
else
|
|
44
|
+
branch_field='"detached"'
|
|
45
|
+
fi
|
|
46
|
+
|
|
47
|
+
printf '{"head":"%s","upstream":%s,"dirty":%s,"root":"%s","branch":%s}\n' \
|
|
48
|
+
"$head" "$upstream_field" "$dirty" "$root" "$branch_field"
|
|
@@ -0,0 +1,241 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// scripts/resolve-verify-ladder.mjs
|
|
3
|
+
//
|
|
4
|
+
// Resolve the per-project verification ladder for `verification-loop` Phase 0.
|
|
5
|
+
// Encodes the four-step resolution priority — manifest > package.json sniff >
|
|
6
|
+
// per-language toolchain > ask-operator — so the skill body cites this script
|
|
7
|
+
// instead of restating the 80+ lines of priority prose inline.
|
|
8
|
+
//
|
|
9
|
+
// Usage:
|
|
10
|
+
// node scripts/resolve-verify-ladder.mjs # pretty fenced block, cwd
|
|
11
|
+
// node scripts/resolve-verify-ladder.mjs <repo-root> # pretty, explicit root
|
|
12
|
+
// node scripts/resolve-verify-ladder.mjs --json # JSON for machines
|
|
13
|
+
// node scripts/resolve-verify-ladder.mjs --json <repo-root>
|
|
14
|
+
//
|
|
15
|
+
// Output (default — pretty fenced block):
|
|
16
|
+
//
|
|
17
|
+
// verify-ladder (resolved):
|
|
18
|
+
// build: npm run build (sniff:package.json:scripts.build)
|
|
19
|
+
// typecheck: npx tsc --noEmit (manifest)
|
|
20
|
+
// ...
|
|
21
|
+
//
|
|
22
|
+
// Output (--json — single JSON object on stdout):
|
|
23
|
+
//
|
|
24
|
+
// { "build": { "command": "npm run build", "source": "sniff:..." }, ... }
|
|
25
|
+
//
|
|
26
|
+
// Phases: build, typecheck, lint, test, security, deploy_receipt, synthetic_checks.
|
|
27
|
+
// Sources: "manifest", "manifest:null" (explicitly skipped), "sniff:package.json:<key>",
|
|
28
|
+
// "sniff:Cargo.toml" | "sniff:go.mod" | "sniff:pyproject.toml" | "sniff:Gemfile",
|
|
29
|
+
// "ask-operator" (nothing matched).
|
|
30
|
+
//
|
|
31
|
+
// Cited by:
|
|
32
|
+
// - skills/verification-loop.md Phase 0
|
|
33
|
+
|
|
34
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
35
|
+
import { join } from "node:path";
|
|
36
|
+
import { argv, cwd, exit, stdout } from "node:process";
|
|
37
|
+
|
|
38
|
+
const PHASES = [
|
|
39
|
+
"build",
|
|
40
|
+
"typecheck",
|
|
41
|
+
"lint",
|
|
42
|
+
"test",
|
|
43
|
+
"security",
|
|
44
|
+
"deploy_receipt",
|
|
45
|
+
"synthetic_checks",
|
|
46
|
+
];
|
|
47
|
+
|
|
48
|
+
function parseArgs() {
|
|
49
|
+
const args = argv.slice(2);
|
|
50
|
+
let json = false;
|
|
51
|
+
let root = cwd();
|
|
52
|
+
for (const a of args) {
|
|
53
|
+
if (a === "--json") {
|
|
54
|
+
json = true;
|
|
55
|
+
} else if (a === "-h" || a === "--help") {
|
|
56
|
+
stdout.write(
|
|
57
|
+
"usage: resolve-verify-ladder.mjs [--json] [<repo-root>]\n",
|
|
58
|
+
);
|
|
59
|
+
exit(0);
|
|
60
|
+
} else {
|
|
61
|
+
root = a;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
return { json, root };
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function safeReadJson(path) {
|
|
68
|
+
if (!existsSync(path)) return null;
|
|
69
|
+
try {
|
|
70
|
+
return JSON.parse(readFileSync(path, "utf8"));
|
|
71
|
+
} catch {
|
|
72
|
+
return null;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function readManifest(root) {
|
|
77
|
+
return safeReadJson(join(root, ".claude", "verify-ladder.json"));
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function readPackageJson(root) {
|
|
81
|
+
return safeReadJson(join(root, "package.json"));
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function sniffPackageJsonScript(pkg, phase) {
|
|
85
|
+
if (!pkg || !pkg.scripts) return null;
|
|
86
|
+
const scripts = pkg.scripts;
|
|
87
|
+
// Priority: verify:<phase> > <phase> > <phase>:* (wildcard tail).
|
|
88
|
+
// Phase aliases:
|
|
89
|
+
// typecheck also matches `tsc` (common in repos that just use `tsc`).
|
|
90
|
+
// security also matches `audit` (common in npm projects).
|
|
91
|
+
const candidates = [`verify:${phase}`, phase];
|
|
92
|
+
if (phase === "typecheck") candidates.push("tsc");
|
|
93
|
+
if (phase === "security") candidates.push("audit");
|
|
94
|
+
|
|
95
|
+
for (const name of candidates) {
|
|
96
|
+
if (typeof scripts[name] === "string") {
|
|
97
|
+
const cmd = name === "test" ? "npm test" : `npm run ${name}`;
|
|
98
|
+
return {
|
|
99
|
+
command: cmd,
|
|
100
|
+
source: `sniff:package.json:scripts.${name}`,
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// Last resort: <phase>:* wildcard tail.
|
|
106
|
+
const wildcardKey = Object.keys(scripts).find((k) => k.startsWith(`${phase}:`));
|
|
107
|
+
if (wildcardKey) {
|
|
108
|
+
return {
|
|
109
|
+
command: `npm run ${wildcardKey}`,
|
|
110
|
+
source: `sniff:package.json:scripts.${wildcardKey}`,
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
return null;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
const PER_LANGUAGE = [
|
|
118
|
+
{
|
|
119
|
+
marker: "Cargo.toml",
|
|
120
|
+
commands: {
|
|
121
|
+
build: "cargo build",
|
|
122
|
+
typecheck: "cargo check --all-targets",
|
|
123
|
+
lint: "cargo clippy --all-targets -- -D warnings",
|
|
124
|
+
test: "cargo test",
|
|
125
|
+
security: "cargo audit",
|
|
126
|
+
},
|
|
127
|
+
},
|
|
128
|
+
{
|
|
129
|
+
marker: "go.mod",
|
|
130
|
+
commands: {
|
|
131
|
+
build: "go build ./...",
|
|
132
|
+
typecheck: "go vet ./...",
|
|
133
|
+
lint: "go vet ./...",
|
|
134
|
+
test: "go test ./...",
|
|
135
|
+
},
|
|
136
|
+
},
|
|
137
|
+
{
|
|
138
|
+
marker: "pyproject.toml",
|
|
139
|
+
commands: {
|
|
140
|
+
typecheck: "pyright",
|
|
141
|
+
lint: "ruff check .",
|
|
142
|
+
test: "pytest",
|
|
143
|
+
},
|
|
144
|
+
},
|
|
145
|
+
{
|
|
146
|
+
marker: "Gemfile",
|
|
147
|
+
commands: {
|
|
148
|
+
lint: "bundle exec rubocop",
|
|
149
|
+
test: "bundle exec rspec",
|
|
150
|
+
},
|
|
151
|
+
},
|
|
152
|
+
];
|
|
153
|
+
|
|
154
|
+
function sniffPerLanguage(root, phase) {
|
|
155
|
+
for (const lang of PER_LANGUAGE) {
|
|
156
|
+
if (!existsSync(join(root, lang.marker))) continue;
|
|
157
|
+
if (lang.commands[phase]) {
|
|
158
|
+
return { command: lang.commands[phase], source: `sniff:${lang.marker}` };
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
return null;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
function resolve(root) {
|
|
165
|
+
const manifest = readManifest(root);
|
|
166
|
+
const pkg = readPackageJson(root);
|
|
167
|
+
const ladder = {};
|
|
168
|
+
|
|
169
|
+
for (const phase of PHASES) {
|
|
170
|
+
// 1. Manifest layer. Underscore-prefixed keys in the manifest are
|
|
171
|
+
// documentation/examples — verify-ladder.example.json uses keys like
|
|
172
|
+
// "_doc", "_node_example" — so PHASES never starts with "_" and we
|
|
173
|
+
// can rely on hasOwn for an exact phase match.
|
|
174
|
+
if (
|
|
175
|
+
manifest &&
|
|
176
|
+
Object.prototype.hasOwnProperty.call(manifest, phase) &&
|
|
177
|
+
!phase.startsWith("_")
|
|
178
|
+
) {
|
|
179
|
+
const value = manifest[phase];
|
|
180
|
+
if (value === null) {
|
|
181
|
+
ladder[phase] = { command: null, source: "manifest:null" };
|
|
182
|
+
} else if (typeof value === "string") {
|
|
183
|
+
ladder[phase] = { command: value, source: "manifest" };
|
|
184
|
+
} else {
|
|
185
|
+
ladder[phase] = { command: null, source: "manifest:invalid" };
|
|
186
|
+
}
|
|
187
|
+
continue;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
// 2. package.json scripts sniff.
|
|
191
|
+
const pkgHit = sniffPackageJsonScript(pkg, phase);
|
|
192
|
+
if (pkgHit) {
|
|
193
|
+
ladder[phase] = pkgHit;
|
|
194
|
+
continue;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
// 3. Per-language toolchain sniff.
|
|
198
|
+
const langHit = sniffPerLanguage(root, phase);
|
|
199
|
+
if (langHit) {
|
|
200
|
+
ladder[phase] = langHit;
|
|
201
|
+
continue;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
// 4. Nothing matched — operator must declare or set to null.
|
|
205
|
+
ladder[phase] = { command: null, source: "ask-operator" };
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
return ladder;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
function pretty(ladder) {
|
|
212
|
+
const widest = Math.max(...Object.keys(ladder).map((k) => k.length));
|
|
213
|
+
const lines = ["verify-ladder (resolved):"];
|
|
214
|
+
for (const [phase, entry] of Object.entries(ladder)) {
|
|
215
|
+
const label = `${phase}:`.padEnd(widest + 2);
|
|
216
|
+
let line;
|
|
217
|
+
if (entry.command === null && entry.source === "ask-operator") {
|
|
218
|
+
line = ` ${label} (ask operator — no marker found)`;
|
|
219
|
+
} else if (entry.command === null && entry.source === "manifest:null") {
|
|
220
|
+
line = ` ${label} (skipped — manifest set this field to null)`;
|
|
221
|
+
} else if (entry.command === null) {
|
|
222
|
+
line = ` ${label} (skipped — ${entry.source})`;
|
|
223
|
+
} else {
|
|
224
|
+
line = ` ${label} ${entry.command} (${entry.source})`;
|
|
225
|
+
}
|
|
226
|
+
lines.push(line);
|
|
227
|
+
}
|
|
228
|
+
return lines.join("\n") + "\n";
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
function main() {
|
|
232
|
+
const { json, root } = parseArgs();
|
|
233
|
+
const ladder = resolve(root);
|
|
234
|
+
if (json) {
|
|
235
|
+
stdout.write(JSON.stringify(ladder, null, 2) + "\n");
|
|
236
|
+
} else {
|
|
237
|
+
stdout.write(pretty(ladder));
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
main();
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// scripts/route-recommendation.mjs
|
|
3
|
+
//
|
|
4
|
+
// Route a single recommendation item to its preferred-skill chain + inline
|
|
5
|
+
// fallback, using the data-driven routing table in
|
|
6
|
+
// `scripts/route-recommendation.routes.json`.
|
|
7
|
+
//
|
|
8
|
+
// Replaces the 26-row Phase 3 routing table that `proceed-with-the-recommendation`
|
|
9
|
+
// would otherwise re-derive every list walk. The skill body cites this script
|
|
10
|
+
// once instead of restating the table inline; the routes.json file is the
|
|
11
|
+
// programmatic source of truth.
|
|
12
|
+
//
|
|
13
|
+
// Usage:
|
|
14
|
+
// node scripts/route-recommendation.mjs "<recommendation item>"
|
|
15
|
+
// node scripts/route-recommendation.mjs --json "<recommendation item>"
|
|
16
|
+
// node scripts/route-recommendation.mjs --list # all rows
|
|
17
|
+
// node scripts/route-recommendation.mjs --list --json # all rows as JSON
|
|
18
|
+
//
|
|
19
|
+
// Output (default):
|
|
20
|
+
// Match: <row name>
|
|
21
|
+
// Preferred: <skill1> → <skill2>
|
|
22
|
+
// Fallback: <inline-fallback text>
|
|
23
|
+
// Marker: (Reference behavior — does not require <plugin>.)
|
|
24
|
+
//
|
|
25
|
+
// Output (--json):
|
|
26
|
+
// { "input": "...", "match": { name, preferred, fallback, marker }, "candidates": [...] }
|
|
27
|
+
//
|
|
28
|
+
// Cited by:
|
|
29
|
+
// - skills/proceed-with-the-recommendation.md Phase 3 routing table
|
|
30
|
+
|
|
31
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
32
|
+
import { dirname, join } from "node:path";
|
|
33
|
+
import { argv, exit, stderr, stdout } from "node:process";
|
|
34
|
+
import { fileURLToPath } from "node:url";
|
|
35
|
+
|
|
36
|
+
const SCRIPT_DIR = dirname(fileURLToPath(import.meta.url));
|
|
37
|
+
const ROUTES_PATH = join(SCRIPT_DIR, "route-recommendation.routes.json");
|
|
38
|
+
|
|
39
|
+
function loadRoutes() {
|
|
40
|
+
if (!existsSync(ROUTES_PATH)) {
|
|
41
|
+
stderr.write(`routes file not found: ${ROUTES_PATH}\n`);
|
|
42
|
+
exit(3);
|
|
43
|
+
}
|
|
44
|
+
const raw = readFileSync(ROUTES_PATH, "utf8");
|
|
45
|
+
const parsed = JSON.parse(raw);
|
|
46
|
+
if (!parsed || !Array.isArray(parsed.rows)) {
|
|
47
|
+
stderr.write(`routes file is malformed (expected { rows: [...] })\n`);
|
|
48
|
+
exit(3);
|
|
49
|
+
}
|
|
50
|
+
return parsed.rows;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function parseArgs() {
|
|
54
|
+
const args = argv.slice(2);
|
|
55
|
+
const out = { json: false, list: false, input: null };
|
|
56
|
+
const positional = [];
|
|
57
|
+
for (let i = 0; i < args.length; i++) {
|
|
58
|
+
const a = args[i];
|
|
59
|
+
if (a === "--json") out.json = true;
|
|
60
|
+
else if (a === "--list") out.list = true;
|
|
61
|
+
else if (a === "-h" || a === "--help") {
|
|
62
|
+
stdout.write(
|
|
63
|
+
"usage: route-recommendation.mjs [--json] <recommendation item>\n" +
|
|
64
|
+
" route-recommendation.mjs --list [--json]\n",
|
|
65
|
+
);
|
|
66
|
+
exit(0);
|
|
67
|
+
} else {
|
|
68
|
+
positional.push(a);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
if (!out.list) {
|
|
72
|
+
if (positional.length === 0) {
|
|
73
|
+
stderr.write("usage: route-recommendation.mjs [--json] <recommendation item>\n");
|
|
74
|
+
stderr.write("input required: pass the recommendation text as the positional arg\n");
|
|
75
|
+
exit(2);
|
|
76
|
+
}
|
|
77
|
+
out.input = positional.join(" ").trim();
|
|
78
|
+
}
|
|
79
|
+
return out;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function compileRow(row) {
|
|
83
|
+
const patterns = Array.isArray(row.patterns) ? row.patterns : [];
|
|
84
|
+
const compiled = [];
|
|
85
|
+
for (const p of patterns) {
|
|
86
|
+
try {
|
|
87
|
+
compiled.push(new RegExp(p, "i"));
|
|
88
|
+
} catch {
|
|
89
|
+
// skip malformed pattern — surface via warning, don't crash
|
|
90
|
+
stderr.write(`warning: bad regex in routes.json (${row.name}): ${p}\n`);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
return { ...row, _compiled: compiled };
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function matchRow(row, input) {
|
|
97
|
+
for (const re of row._compiled) {
|
|
98
|
+
if (re.test(input)) return true;
|
|
99
|
+
}
|
|
100
|
+
return false;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function asciiArrow(preferred) {
|
|
104
|
+
return preferred.join(" -> ");
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function pretty(matchResult) {
|
|
108
|
+
if (!matchResult.match) {
|
|
109
|
+
return [
|
|
110
|
+
`No routing match for input: "${matchResult.input}"`,
|
|
111
|
+
`ask-operator: this recommendation doesn't fit any known routing row.`,
|
|
112
|
+
`Either pick a row from --list manually, or surface to the operator.`,
|
|
113
|
+
"",
|
|
114
|
+
].join("\n");
|
|
115
|
+
}
|
|
116
|
+
const m = matchResult.match;
|
|
117
|
+
const lines = [
|
|
118
|
+
`Match: ${m.name}`,
|
|
119
|
+
`Preferred: ${asciiArrow(m.preferred)}`,
|
|
120
|
+
`Fallback: ${m.fallback}`,
|
|
121
|
+
];
|
|
122
|
+
if (m.marker) {
|
|
123
|
+
lines.push(`Marker: (Reference behavior — does not require ${m.marker}.)`);
|
|
124
|
+
}
|
|
125
|
+
if (matchResult.candidates.length > 1) {
|
|
126
|
+
lines.push("");
|
|
127
|
+
lines.push(`Other candidates (top match wins):`);
|
|
128
|
+
for (const c of matchResult.candidates.slice(1, 4)) {
|
|
129
|
+
lines.push(` - ${c.name}`);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
lines.push("");
|
|
133
|
+
return lines.join("\n");
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
function main() {
|
|
137
|
+
const args = parseArgs();
|
|
138
|
+
const rawRows = loadRoutes();
|
|
139
|
+
const rows = rawRows.map(compileRow);
|
|
140
|
+
|
|
141
|
+
if (args.list) {
|
|
142
|
+
if (args.json) {
|
|
143
|
+
const stripped = rows.map(({ _compiled, ...row }) => row);
|
|
144
|
+
stdout.write(JSON.stringify({ rows: stripped }, null, 2) + "\n");
|
|
145
|
+
return;
|
|
146
|
+
}
|
|
147
|
+
for (const row of rows) {
|
|
148
|
+
stdout.write(`- ${row.name}\n`);
|
|
149
|
+
stdout.write(` preferred: ${asciiArrow(row.preferred)}\n`);
|
|
150
|
+
}
|
|
151
|
+
return;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
const candidates = rows.filter((r) => matchRow(r, args.input));
|
|
155
|
+
const result = {
|
|
156
|
+
input: args.input,
|
|
157
|
+
match: candidates.length > 0 ? stripCompiled(candidates[0]) : null,
|
|
158
|
+
candidates: candidates.map(stripCompiled),
|
|
159
|
+
};
|
|
160
|
+
|
|
161
|
+
if (args.json) {
|
|
162
|
+
stdout.write(JSON.stringify(result, null, 2) + "\n");
|
|
163
|
+
} else {
|
|
164
|
+
stdout.write(pretty(result));
|
|
165
|
+
}
|
|
166
|
+
// Surface the ask-operator hint on stderr regardless of mode, so callers
|
|
167
|
+
// that read stderr can detect no-match without re-parsing stdout.
|
|
168
|
+
if (!result.match) {
|
|
169
|
+
stderr.write("ask-operator: no routing row matched the input\n");
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
function stripCompiled(row) {
|
|
174
|
+
const { _compiled, ...rest } = row;
|
|
175
|
+
return rest;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
main();
|
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
{
|
|
2
|
+
"_doc": [
|
|
3
|
+
"Routing table for proceed-with-the-recommendation Phase 3.",
|
|
4
|
+
"scripts/route-recommendation.mjs reads this file; the skill body cites the script.",
|
|
5
|
+
"Each row maps a recommendation TYPE to a preferred-skill chain + inline fallback.",
|
|
6
|
+
"Patterns are case-insensitive regexes. First-match-wins; the script preserves row order on ties."
|
|
7
|
+
],
|
|
8
|
+
"rows": [
|
|
9
|
+
{
|
|
10
|
+
"name": "Long-running / PRD-style autonomous execution",
|
|
11
|
+
"patterns": ["\\bprd\\b", "\\bautonomous\\b", "long.running", "iterati(ve|on) loop", "ralph"],
|
|
12
|
+
"preferred": ["ralph"],
|
|
13
|
+
"fallback": "Break into sub-recommendations, run proceed-with-the-recommendation recursively per sub-item.",
|
|
14
|
+
"marker": null
|
|
15
|
+
},
|
|
16
|
+
{
|
|
17
|
+
"name": "Implement feature / add capability",
|
|
18
|
+
"patterns": ["implement.*feature", "add.*(capability|feature|endpoint)", "build.*feature", "new feature"],
|
|
19
|
+
"preferred": ["superpowers:brainstorming", "superpowers:writing-plans"],
|
|
20
|
+
"fallback": "Restate goal → list 3 design options → pick one → outline files to touch → build.",
|
|
21
|
+
"marker": "superpowers"
|
|
22
|
+
},
|
|
23
|
+
{
|
|
24
|
+
"name": "Fix bug / investigate failure",
|
|
25
|
+
"patterns": ["fix.*bug", "investigat(e|ion).*(failure|issue|bug)", "debug", "broken", "root.cause"],
|
|
26
|
+
"preferred": ["superpowers:systematic-debugging"],
|
|
27
|
+
"fallback": "Hypothesis → add logs/tests → reproduce → smallest fix → verify with the failing repro.",
|
|
28
|
+
"marker": "superpowers:systematic-debugging"
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
"name": "Write tests / add coverage",
|
|
32
|
+
"patterns": ["write tests?", "add (test|coverage)", "tdd", "test-driven", "unit test"],
|
|
33
|
+
"preferred": ["superpowers:test-driven-development", "tdd-workflow"],
|
|
34
|
+
"fallback": "RED (failing test) → GREEN (minimal code) → REFACTOR; one test, one behavior.",
|
|
35
|
+
"marker": null
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
"name": "Refactor / dead code cleanup",
|
|
39
|
+
"patterns": ["refactor", "dead code", "cleanup", "remove (duplicat|unused)", "simplify"],
|
|
40
|
+
"preferred": ["simplify"],
|
|
41
|
+
"fallback": "Find dupes/unused exports, delete in place, re-run type check and smallest test.",
|
|
42
|
+
"marker": "simplify"
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
"name": "Security review / auth audit",
|
|
46
|
+
"patterns": ["security review", "auth audit", "vulnerab", "owasp", "secret(s|.scan)"],
|
|
47
|
+
"preferred": ["security-review"],
|
|
48
|
+
"fallback": "Scan for hardcoded secrets, unsanitized input, missing authz, SQL string concat, open CORS.",
|
|
49
|
+
"marker": "security-review"
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
"name": "Code review before merge",
|
|
53
|
+
"patterns": ["code review", "review (the )?(diff|pr|change)", "pre-merge review"],
|
|
54
|
+
"preferred": ["superpowers:requesting-code-review", "code-review"],
|
|
55
|
+
"fallback": "Read diff top-to-bottom, flag CRITICAL / HIGH / MEDIUM.",
|
|
56
|
+
"marker": "code-review"
|
|
57
|
+
},
|
|
58
|
+
{
|
|
59
|
+
"name": "Verify before shipping",
|
|
60
|
+
"patterns": ["verify (before|that)", "verification", "pre.ship", "before (the )?(merge|ship|push)"],
|
|
61
|
+
"preferred": ["superpowers:verification-before-completion"],
|
|
62
|
+
"fallback": "Smallest check that proves correctness: typecheck + one test + one curl.",
|
|
63
|
+
"marker": "superpowers:verification-before-completion"
|
|
64
|
+
},
|
|
65
|
+
{
|
|
66
|
+
"name": "Multiple independent tasks",
|
|
67
|
+
"patterns": ["multiple (independent )?tasks", "parallel(ize)? (tasks|agents)", "fan out"],
|
|
68
|
+
"preferred": ["superpowers:dispatching-parallel-agents"],
|
|
69
|
+
"fallback": "Launch N parallel `Agent` tool calls in one message; reconcile results after.",
|
|
70
|
+
"marker": "superpowers:dispatching-parallel-agents"
|
|
71
|
+
},
|
|
72
|
+
{
|
|
73
|
+
"name": "Merge / close branch",
|
|
74
|
+
"patterns": ["merge (the )?branch", "close.*branch", "finish.*branch", "open (the )?pr", "ship (the )?pr"],
|
|
75
|
+
"preferred": ["superpowers:finishing-a-development-branch"],
|
|
76
|
+
"fallback": "Verify clean tree, rebase on main, green CI, open PR with summary + test plan.",
|
|
77
|
+
"marker": "superpowers:finishing-a-development-branch"
|
|
78
|
+
},
|
|
79
|
+
{
|
|
80
|
+
"name": "Post-merge deploy receipt (auto-deploy projects)",
|
|
81
|
+
"patterns": ["deploy receipt", "post.merge.*deploy", "deployed sha", "auto.deploy", "merge.*deployed"],
|
|
82
|
+
"preferred": ["deploy-receipt"],
|
|
83
|
+
"fallback": "Confirm deployed SHA == merge SHA via provider CLI, GitHub Deployments API, or version-endpoint curl, plus a 200 from a documented healthcheck. INCOMPLETE receipt blocks the merge from being reported as done.",
|
|
84
|
+
"marker": null
|
|
85
|
+
},
|
|
86
|
+
{
|
|
87
|
+
"name": "Schedule a follow-up",
|
|
88
|
+
"patterns": ["schedule (a )?(follow.up|task|run)", "remind me", "later today", "tomorrow"],
|
|
89
|
+
"preferred": ["schedule"],
|
|
90
|
+
"fallback": "Tell user the exact action + cadence; if no scheduler, write a dated TODO/memory entry.",
|
|
91
|
+
"marker": "schedule"
|
|
92
|
+
},
|
|
93
|
+
{
|
|
94
|
+
"name": "Recurring poll / interval task",
|
|
95
|
+
"patterns": ["recurring", "every (\\d+|few) (minute|hour|day)", "poll (every|until)", "loop (every|until)"],
|
|
96
|
+
"preferred": ["loop"],
|
|
97
|
+
"fallback": "Tell user the cadence + how to re-run manually.",
|
|
98
|
+
"marker": "loop"
|
|
99
|
+
},
|
|
100
|
+
{
|
|
101
|
+
"name": "Library / API docs lookup",
|
|
102
|
+
"patterns": ["lookup (the )?(docs|api)", "documentation (for|on)", "api docs", "library docs"],
|
|
103
|
+
"preferred": ["documentation-lookup"],
|
|
104
|
+
"fallback": "Use `WebFetch` against the official docs URL, cite what changed.",
|
|
105
|
+
"marker": "documentation-lookup"
|
|
106
|
+
},
|
|
107
|
+
{
|
|
108
|
+
"name": "Frontend / UI design work",
|
|
109
|
+
"patterns": ["frontend (work|design)", "ui design", "(landing|marketing) page", "redesign.*ui"],
|
|
110
|
+
"preferred": ["frontend-design:frontend-design"],
|
|
111
|
+
"fallback": "Build smallest vertical slice first, verify in browser before styling.",
|
|
112
|
+
"marker": "frontend-design"
|
|
113
|
+
},
|
|
114
|
+
{
|
|
115
|
+
"name": "Settings / hooks / permission change",
|
|
116
|
+
"patterns": ["settings\\.json", "hooks?\\.json", "permission(s)?( change)?", "update.*(setting|config)"],
|
|
117
|
+
"preferred": ["update-config"],
|
|
118
|
+
"fallback": "Edit `~/.claude/settings.json` with a minimal patch; restart session.",
|
|
119
|
+
"marker": "update-config"
|
|
120
|
+
},
|
|
121
|
+
{
|
|
122
|
+
"name": "Commit and push",
|
|
123
|
+
"patterns": ["commit and push", "commit (the )?(change|fix)", "push (the )?(commit|branch)", "git push"],
|
|
124
|
+
"preferred": ["commit-commands:commit", "commit-commands:commit-push-pr"],
|
|
125
|
+
"fallback": "`git add <specific files>` → commit with `type(scope): outcome` → push when asked.",
|
|
126
|
+
"marker": "commit-commands"
|
|
127
|
+
},
|
|
128
|
+
{
|
|
129
|
+
"name": "Continuous-improvement analysis / instinct update",
|
|
130
|
+
"patterns": ["continuous.improvement", "7 laws", "seven laws", "instinct (update|capture)", "reflect.*session"],
|
|
131
|
+
"preferred": ["continuous-improvement"],
|
|
132
|
+
"fallback": "Run the 7-Laws Reflection block manually; append to `observations.jsonl`.",
|
|
133
|
+
"marker": null
|
|
134
|
+
},
|
|
135
|
+
{
|
|
136
|
+
"name": "Spec-first contract before code",
|
|
137
|
+
"patterns": ["spec-first", "spec.driven", "write (a )?spec (before|for)", "contract first"],
|
|
138
|
+
"preferred": ["agent-skills:spec-driven-development"],
|
|
139
|
+
"fallback": "Restate the contract as a testable spec; downstream tests/code regenerate from it.",
|
|
140
|
+
"marker": "agent-skills"
|
|
141
|
+
},
|
|
142
|
+
{
|
|
143
|
+
"name": "Source-first reading before writing",
|
|
144
|
+
"patterns": ["source.first", "read (the )?(source|implementation) first", "read before writ"],
|
|
145
|
+
"preferred": ["agent-skills:source-driven-development"],
|
|
146
|
+
"fallback": "Read the existing implementation top-to-bottom first; never write blind.",
|
|
147
|
+
"marker": "agent-skills"
|
|
148
|
+
},
|
|
149
|
+
{
|
|
150
|
+
"name": "Curate context window before answering",
|
|
151
|
+
"patterns": ["context (window|budget)", "trim (the )?context", "context engineering"],
|
|
152
|
+
"preferred": ["agent-skills:context-engineering", "context-budget"],
|
|
153
|
+
"fallback": "Trim irrelevant context; keep only the load-bearing files in scope.",
|
|
154
|
+
"marker": "agent-skills"
|
|
155
|
+
},
|
|
156
|
+
{
|
|
157
|
+
"name": "Slice atomic increment with safe deploy",
|
|
158
|
+
"patterns": ["atomic (increment|change)", "smallest reviewable", "incremental implementation", "feature flag"],
|
|
159
|
+
"preferred": ["agent-skills:incremental-implementation"],
|
|
160
|
+
"fallback": "Smallest reviewable change; ship behind a flag if the full scope is too big.",
|
|
161
|
+
"marker": "agent-skills"
|
|
162
|
+
},
|
|
163
|
+
{
|
|
164
|
+
"name": "Refine vague request into ranked options",
|
|
165
|
+
"patterns": ["refine (the )?(idea|request)", "rank (the )?option", "brainstorm", "explore (alternatives|options)"],
|
|
166
|
+
"preferred": ["agent-skills:idea-refine", "superpowers:brainstorming"],
|
|
167
|
+
"fallback": "Compress N ideas to 1 with explicit scoring criteria.",
|
|
168
|
+
"marker": "agent-skills"
|
|
169
|
+
},
|
|
170
|
+
{
|
|
171
|
+
"name": "Fan out N agents on isolated worktrees with shared contract",
|
|
172
|
+
"patterns": ["swarm", "worktree.*shared contract", "fan out.*worktree", "provider migration"],
|
|
173
|
+
"preferred": ["superpowers:dispatching-parallel-agents", "ruflo-swarm:swarm-init"],
|
|
174
|
+
"fallback": "Use the swarm contract: fixed roles + base ref + shared contract test; reconcile results after.",
|
|
175
|
+
"marker": "ruflo-swarm"
|
|
176
|
+
},
|
|
177
|
+
{
|
|
178
|
+
"name": "Stream live observation of long agent runs",
|
|
179
|
+
"patterns": ["stream.*observation", "live observation", "monitor.*long run", "monitor-stream"],
|
|
180
|
+
"preferred": ["ruflo-swarm:monitor-stream"],
|
|
181
|
+
"fallback": "Push-based event log; poll fallback if the MCP server is offline.",
|
|
182
|
+
"marker": "ruflo-swarm"
|
|
183
|
+
},
|
|
184
|
+
{
|
|
185
|
+
"name": "Visual regression / browser-level diff",
|
|
186
|
+
"patterns": ["visual (regression|diff)", "screenshot diff", "browser.*regression", "visual-verdict"],
|
|
187
|
+
"preferred": ["oh-my-claudecode:visual-verdict"],
|
|
188
|
+
"fallback": "Playwright screenshot diff against staging baseline.",
|
|
189
|
+
"marker": "oh-my-claudecode"
|
|
190
|
+
},
|
|
191
|
+
{
|
|
192
|
+
"name": "Multi-session retrospective across a sprint",
|
|
193
|
+
"patterns": ["retrospective", "sprint review", "multi.session review", "what worked.*what failed"],
|
|
194
|
+
"preferred": ["oh-my-claudecode:retrospective", "learn-eval"],
|
|
195
|
+
"fallback": "What worked / what failed / what to do differently / 3 ranked next moves.",
|
|
196
|
+
"marker": "oh-my-claudecode"
|
|
197
|
+
},
|
|
198
|
+
{
|
|
199
|
+
"name": "Long autonomous run with quality gates",
|
|
200
|
+
"patterns": ["long autonomous", "ultrawork", "quality gates? (between|per) iteration"],
|
|
201
|
+
"preferred": ["oh-my-claudecode:ultrawork", "ralph"],
|
|
202
|
+
"fallback": "PRD-shaped autonomous loop with verify-between-iterations.",
|
|
203
|
+
"marker": "oh-my-claudecode"
|
|
204
|
+
},
|
|
205
|
+
{
|
|
206
|
+
"name": "Product-management work",
|
|
207
|
+
"patterns": ["\\bprd\\b.*(write|draft)", "okrs?", "personas?", "jtbd", "lean canvas", "market siz", "competitive analysis", "user stor(y|ies)", "acceptance criteria", "launch checklist"],
|
|
208
|
+
"preferred": ["phuryn/pm-skills (out-of-band install)"],
|
|
209
|
+
"fallback": "Keep the work shape (problem → user → goal → metric → scope; Given/When/Then per story; objective + 3-5 measurable KRs; we-believe / we'll-know hypothesis; TAM/SAM/SOM bottom-up) without depending on a specific routing target.",
|
|
210
|
+
"marker": "phuryn/pm-skills"
|
|
211
|
+
}
|
|
212
|
+
]
|
|
213
|
+
}
|