shapeup-sdlc 1.6.2

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 (135) hide show
  1. package/.claude/settings.local.example.json +20 -0
  2. package/.claude-plugin/marketplace.json +16 -0
  3. package/.claude-plugin/plugin.json +18 -0
  4. package/.env.shapeup.example +14 -0
  5. package/AGENTS.md +133 -0
  6. package/LICENSE +21 -0
  7. package/README.md +362 -0
  8. package/SECURITY.md +72 -0
  9. package/bin/init.mjs +329 -0
  10. package/commands/build.md +14 -0
  11. package/commands/eval.md +15 -0
  12. package/commands/hammer.md +12 -0
  13. package/commands/orient.md +11 -0
  14. package/commands/qa.md +12 -0
  15. package/commands/retro.md +13 -0
  16. package/commands/scopes.md +14 -0
  17. package/commands/shape.md +12 -0
  18. package/commands/ship.md +53 -0
  19. package/commands/wire.md +11 -0
  20. package/hooks/anti-rationalization.mjs +244 -0
  21. package/hooks/compact-snapshot.mjs +47 -0
  22. package/hooks/gate-deadline.mjs +151 -0
  23. package/hooks/gate-intake.mjs +110 -0
  24. package/hooks/gate-l2.mjs +161 -0
  25. package/hooks/gate-zerowork.mjs +264 -0
  26. package/hooks/hooks.json +118 -0
  27. package/hooks/lib/decision.mjs +183 -0
  28. package/hooks/safety-spine.mjs +296 -0
  29. package/hooks/sandbox-guard.mjs +172 -0
  30. package/hooks/session-rehydrate.mjs +109 -0
  31. package/hooks/slop-cleaner.mjs +176 -0
  32. package/oracles/_shared.mjs +46 -0
  33. package/oracles/http-oracle.mjs +155 -0
  34. package/oracles/index.mjs +36 -0
  35. package/oracles/process-oracle.mjs +146 -0
  36. package/oracles/snapshot-oracle.mjs +119 -0
  37. package/oracles/test-oracle.mjs +138 -0
  38. package/package.json +49 -0
  39. package/skills/advisor-protocol/SKILL.md +171 -0
  40. package/skills/ba-pitch-analyzer/SKILL.md +175 -0
  41. package/skills/ba-pitch-analyzer/assets/templates/_index.tmpl.md +84 -0
  42. package/skills/ba-pitch-analyzer/assets/templates/api-feasibility.tmpl.md +65 -0
  43. package/skills/ba-pitch-analyzer/assets/templates/assess-report.tmpl.md +127 -0
  44. package/skills/ba-pitch-analyzer/assets/templates/contracts/be-service.contract.tmpl.md +62 -0
  45. package/skills/ba-pitch-analyzer/assets/templates/contracts/offline-storage.contract.tmpl.md +92 -0
  46. package/skills/ba-pitch-analyzer/assets/templates/contracts/third-party-api.contract.tmpl.md +66 -0
  47. package/skills/ba-pitch-analyzer/assets/templates/cross-context/context-map.tmpl.md +64 -0
  48. package/skills/ba-pitch-analyzer/assets/templates/cross-context/event-choreography.tmpl.md +77 -0
  49. package/skills/ba-pitch-analyzer/assets/templates/cross-context/migration-plan.tmpl.md +104 -0
  50. package/skills/ba-pitch-analyzer/assets/templates/cross-context/team-handoff.tmpl.md +79 -0
  51. package/skills/ba-pitch-analyzer/assets/templates/domain-model.tmpl.md +79 -0
  52. package/skills/ba-pitch-analyzer/assets/templates/feedback.tmpl.md +87 -0
  53. package/skills/ba-pitch-analyzer/assets/templates/integration.tmpl.md +64 -0
  54. package/skills/ba-pitch-analyzer/assets/templates/run-state.tmpl.md +77 -0
  55. package/skills/ba-pitch-analyzer/assets/templates/scope-summary.tmpl.md +105 -0
  56. package/skills/ba-pitch-analyzer/assets/templates/synthesis.tmpl.md +215 -0
  57. package/skills/ba-pitch-analyzer/assets/templates/task-board.tmpl.md +35 -0
  58. package/skills/ba-pitch-analyzer/assets/templates/task-spike.tmpl.md +86 -0
  59. package/skills/ba-pitch-analyzer/assets/templates/task.tmpl.md +84 -0
  60. package/skills/ba-pitch-analyzer/assets/templates/usecase.tmpl.md +105 -0
  61. package/skills/ba-pitch-analyzer/assets/templates/ux-behavior.tmpl.md +65 -0
  62. package/skills/ba-pitch-analyzer/references/contract-patterns.md +152 -0
  63. package/skills/ba-pitch-analyzer/references/ddd-patterns.md +173 -0
  64. package/skills/ba-pitch-analyzer/references/doc-schemas.md +275 -0
  65. package/skills/ba-pitch-analyzer/references/integration-analysis.md +152 -0
  66. package/skills/ba-pitch-analyzer/references/task-generation.md +623 -0
  67. package/skills/ba-pitch-analyzer/references/test-surface.md +102 -0
  68. package/skills/ba-pitch-analyzer/references/ux-behavior-patterns.md +269 -0
  69. package/skills/ba-pitch-analyzer/scripts/board-derive.mjs +241 -0
  70. package/skills/ba-pitch-analyzer/scripts/spec-lint.mjs +239 -0
  71. package/skills/coach/SKILL.md +202 -0
  72. package/skills/orient/SKILL.md +262 -0
  73. package/skills/qa-edge-hunter/SKILL.md +369 -0
  74. package/skills/scope-architect/SKILL.md +111 -0
  75. package/skills/scope-hammer/SKILL.md +186 -0
  76. package/skills/shapeup/SKILL.md +403 -0
  77. package/skills/shapeup/resources/breadboard-reflection.md +88 -0
  78. package/skills/shapeup/resources/breadboarding.md +334 -0
  79. package/skills/shapeup/resources/context-compaction.md +240 -0
  80. package/skills/shapeup/resources/framing-doc.md +92 -0
  81. package/skills/shapeup/resources/kickoff-doc.md +95 -0
  82. package/skills/shapeup/resources/shaping.md +194 -0
  83. package/skills/shapeup/resources/spike.md +86 -0
  84. package/skills/solution-architect/SKILL.md +129 -0
  85. package/skills/spec-evaluator/README.md +93 -0
  86. package/skills/spec-evaluator/SKILL.md +213 -0
  87. package/skills/spec-evaluator/references/anti-leniency.md +50 -0
  88. package/skills/spec-evaluator/references/dimension-contract.md +126 -0
  89. package/skills/spec-evaluator/references/dimensions/_registry.md +40 -0
  90. package/skills/spec-evaluator/references/dimensions/completeness.md +102 -0
  91. package/skills/spec-evaluator/references/dimensions/integration.md +129 -0
  92. package/skills/spec-evaluator/references/dimensions/performance.md +48 -0
  93. package/skills/spec-evaluator/references/dimensions/security.md +60 -0
  94. package/skills/spec-evaluator/references/dimensions/spec-conformance.md +134 -0
  95. package/skills/spec-evaluator/references/dimensions/tdd-surface.md +110 -0
  96. package/skills/spec-evaluator/references/dimensions/test-surface-conformance.md +100 -0
  97. package/skills/spec-evaluator/references/probing.md +177 -0
  98. package/skills/spec-evaluator/references/report-schema.md +101 -0
  99. package/skills/spec-evaluator/references/verdict-ledger.md +92 -0
  100. package/skills/spec-evaluator/scripts/verdict-ledger.mjs +166 -0
  101. package/skills/task-executor/SKILL.md +194 -0
  102. package/skills/tech-lead/README.md +71 -0
  103. package/skills/tech-lead/SKILL.md +459 -0
  104. package/skills/tech-lead/references/delegation.md +254 -0
  105. package/skills/tech-lead/references/gates.md +379 -0
  106. package/skills/tech-lead/references/invocation.md +45 -0
  107. package/skills/tech-lead/references/ledger-schema.md +214 -0
  108. package/skills/tech-lead/references/round-protocol.md +184 -0
  109. package/skills/tech-lead/references/state-model.md +66 -0
  110. package/skills/tech-lead/references/tiny-lane.md +52 -0
  111. package/skills/tech-lead/schemas/domain.schema.json +2294 -0
  112. package/skills/tech-lead/schemas/gate-answers.schema.json +92 -0
  113. package/skills/tech-lead/schemas/work-order.schema.json +21 -0
  114. package/skills/tech-lead/schemas/work-result.schema.json +40 -0
  115. package/skills/tech-lead/scripts/aegis-digest.mjs +124 -0
  116. package/skills/tech-lead/scripts/budget-check.mjs +156 -0
  117. package/skills/tech-lead/scripts/compile-order.mjs +518 -0
  118. package/skills/tech-lead/scripts/fit-check.mjs +196 -0
  119. package/skills/tech-lead/scripts/gate-answers.mjs +338 -0
  120. package/skills/tech-lead/scripts/ingest-result.mjs +270 -0
  121. package/skills/tech-lead/scripts/init-run.mjs +326 -0
  122. package/skills/tech-lead/scripts/lib/argv.mjs +224 -0
  123. package/skills/tech-lead/scripts/lib/contract-md.mjs +481 -0
  124. package/skills/tech-lead/scripts/lib/is-main.mjs +82 -0
  125. package/skills/tech-lead/scripts/lib/paths.mjs +278 -0
  126. package/skills/tech-lead/scripts/lib/ratchet-tree.mjs +112 -0
  127. package/skills/tech-lead/scripts/run-snapshot.mjs +269 -0
  128. package/skills/tech-lead/scripts/ship-report.mjs +275 -0
  129. package/skills/tech-lead/scripts/stats.mjs +423 -0
  130. package/skills/tech-lead/scripts/t0-verify.mjs +470 -0
  131. package/skills/tech-lead/scripts/trace-lint.mjs +367 -0
  132. package/skills/tech-lead/scripts/validate-envelope.mjs +283 -0
  133. package/skills/translator/README.md +66 -0
  134. package/skills/translator/SKILL.md +258 -0
  135. package/skills/translator/references/preservation-rules.md +102 -0
@@ -0,0 +1,239 @@
1
+ #!/usr/bin/env node
2
+ // Spec lint (pure-skill architecture v1.0, plan §8.2).
3
+ //
4
+ // The mechanical half of the old ba-pitch-analyzer Phase 7a self-audit + Phase 7c parse steps
5
+ // + Phase 6b PA1/PA2 lints — checkbox walking and glob checks a model should never grade on
6
+ // its own output (a worker grading itself was always a judge-purity smell):
7
+ //
8
+ // PA1 a scope substrate aligned 1:1 with a single top-level directory (directory-thinking)
9
+ // PA2 a scope substrate resolving to more than the size cap (~15 files)
10
+ // DISJOINT a path matched by two scopes' allowed_file_substrate without BOTH declaring it
11
+ // in shared_substrate
12
+ // STRUCTURE spec tree completeness (usecases/ ≥1 UC, domain-model, UC ## Steps),
13
+ // unresolved wikilinks, task frontmatter completeness, unlocks edge-symmetry,
14
+ // depends_on referencing unknown tasks
15
+ // TIER-DIRECTION a committed (SHARED) spec doc wikilinking the LOCAL board ([[tasks/...]]).
16
+ // Persisted links flow LOCAL→SHARED only: task ids are machine-local (boards
17
+ // regenerate and renumber) and .shapeup/ is gitignored — a committed task
18
+ // link dangles on every fresh clone. Cite the UC or scope_id instead.
19
+ // UC-ANCHOR a task whose use_case_refs is empty or names a UC with no usecases/UC-*.md —
20
+ // the LOCAL→SHARED anchor must be complete (single-anchor rule; SPIKE/CHORE/
21
+ // DOCS/MIGRATION tasks anchor elsewhere and are exempt)
22
+ //
23
+ // Zero dependencies (glob matcher inlined from hooks/sandbox-guard.mjs). Judgment stays in the skill
24
+ // (gap severity, lens choice); this script only reports facts.
25
+ //
26
+ // Usage: node skills/ba-pitch-analyzer/scripts/spec-lint.mjs --slug <slug> [--cwd <dir>]
27
+ // Prints a JSON report. Exit 0 = no red findings, 1 = at least one red.
28
+
29
+ import { readFileSync, existsSync, readdirSync, statSync } from "node:fs";
30
+ import { resolve, join, relative } from "node:path";
31
+ import { parseBoard, deriveUnlocks } from "./board-derive.mjs";
32
+ import { isMain } from "../../tech-lead/scripts/lib/is-main.mjs";
33
+ import { runArgs } from "../../tech-lead/scripts/lib/argv.mjs";
34
+ import { LOCAL } from "../../tech-lead/scripts/lib/paths.mjs";
35
+ import { specDir, scopesDir, tasksDir } from "../../tech-lead/scripts/lib/paths.mjs";
36
+ import { readAllContracts, unreadableReason, SCOPE_CONTRACT } from "../../tech-lead/scripts/lib/contract-md.mjs";
37
+
38
+ // Inlined from hooks/sandbox-guard.mjs so this skill ships self-contained (a skill's scripts
39
+ // must not reach outside its own folder — channels that copy only skills/ would dangle).
40
+ /**
41
+ * Compile a substrate glob into an anchored RegExp (inlined from sandbox-guard so the skill ships
42
+ * self-contained). Supports single-star, double-star, and double-star-slash segment wildcards.
43
+ * @param {string} glob - The glob pattern.
44
+ * @returns {RegExp} A full-string (`^…$`) matcher for repo-relative paths.
45
+ */
46
+ export function globToRegExp(glob) {
47
+ let re = "";
48
+ for (let i = 0; i < glob.length; i++) {
49
+ const c = glob[i];
50
+ if (c === "*") {
51
+ if (glob[i + 1] === "*") {
52
+ re += glob[i + 2] === "/" ? "(?:[^/]+/)*" : ".*";
53
+ i += glob[i + 2] === "/" ? 2 : 1;
54
+ } else re += "[^/]*";
55
+ } else if ("\\^$.|?+()[]{}".includes(c)) re += "\\" + c;
56
+ else re += c;
57
+ }
58
+ return new RegExp(`^${re}$`);
59
+ }
60
+
61
+ const SIZE_CAP = 15;
62
+
63
+ /**
64
+ * Recursively list repo-relative file paths under a root, skipping .git/node_modules/.shapeup.
65
+ * @param {string} root - The base the results are made relative to.
66
+ * @param {string} [dir=root] - Current directory being walked (callers omit it).
67
+ * @param {string[]} [acc=[]] - Accumulator (callers omit it).
68
+ * @returns {string[]} Repo-relative paths of every file found.
69
+ */
70
+ function walkFiles(root, dir = root, acc = []) {
71
+ for (const e of readdirSync(dir, { withFileTypes: true })) {
72
+ if (e.name === ".git" || e.name === "node_modules" || e.name === LOCAL) continue;
73
+ const p = join(dir, e.name);
74
+ if (e.isDirectory()) walkFiles(root, p, acc);
75
+ else acc.push(relative(root, p));
76
+ }
77
+ return acc;
78
+ }
79
+
80
+ /**
81
+ * Lint scope contracts for PA1 (directory-thinking), PA2 (size cap), and DISJOINT substrate overlap.
82
+ * @param {Array<{scope_id:string, topology_type?:string, allowed_file_substrate?:string[],
83
+ * shared_substrate?:string[]}>} scopes - The scope contracts.
84
+ * @param {string[]} repoFiles - Repo-relative file list the substrate globs resolve against.
85
+ * @returns {Array<{rule:string, level:("red"|"warn"), scope:string, detail:string}>} Findings; [] when clean.
86
+ */
87
+ export function lintScopes(scopes, repoFiles) {
88
+ const findings = [];
89
+ /**
90
+ * Resolve a list of substrate globs to the repo files they match.
91
+ * @param {(string[]|undefined)} globs - Glob patterns (undefined → none).
92
+ * @returns {string[]} The repo-relative files matched by any glob (deduplication is the caller's).
93
+ */
94
+ const resolveGlobs = (globs) => {
95
+ const res = (globs || []).map((g) => globToRegExp(g));
96
+ return repoFiles.filter((f) => res.some((r) => r.test(f)));
97
+ };
98
+ for (const s of scopes) {
99
+ const allowed = s.allowed_file_substrate || [];
100
+ // PA1 — directory-thinking: every glob confined to ONE layer directory (e.g. all of it
101
+ // under apps/web/). A flow slice crosses layers (apps/web/cart + apps/api/cart passes).
102
+ const layers = new Set(allowed.map((g) => g.split("/").slice(0, 2).join("/")));
103
+ if (allowed.length && layers.size === 1 && s.topology_type !== "CHOWDER") {
104
+ findings.push({ rule: "PA1", level: "red", scope: s.scope_id, detail: `substrate aligns 1:1 with '${[...layers][0]}/' — slice by flow, not by directory` });
105
+ }
106
+ // PA2 — resolved file count over the cap (chowder absorbs true strays).
107
+ const files = resolveGlobs(allowed);
108
+ if (files.length > SIZE_CAP && s.topology_type !== "CHOWDER") {
109
+ findings.push({ rule: "PA2", level: "warn", scope: s.scope_id, detail: `substrate resolves to ${files.length} files (cap ~${SIZE_CAP}) — consider splitting` });
110
+ }
111
+ }
112
+ // DISJOINT — pairwise overlap not covered by BOTH scopes' shared_substrate.
113
+ for (let i = 0; i < scopes.length; i++) {
114
+ for (let j = i + 1; j < scopes.length; j++) {
115
+ const a = scopes[i], b = scopes[j];
116
+ const filesA = new Set(resolveGlobs(a.allowed_file_substrate));
117
+ const overlap = resolveGlobs(b.allowed_file_substrate).filter((f) => filesA.has(f));
118
+ const sharedA = (a.shared_substrate || []).map(globToRegExp);
119
+ const sharedB = (b.shared_substrate || []).map(globToRegExp);
120
+ for (const f of overlap) {
121
+ const declared = sharedA.some((r) => r.test(f)) && sharedB.some((r) => r.test(f));
122
+ if (!declared) findings.push({ rule: "DISJOINT", level: "red", scope: `${a.scope_id}+${b.scope_id}`, detail: `${f} is in both substrates but not in both shared_substrate lists — PA3 waiting to happen` });
123
+ }
124
+ }
125
+ }
126
+ return findings;
127
+ }
128
+
129
+ /**
130
+ * Lint spec-tree completeness, wikilink resolution, tier-direction, task frontmatter/graph
131
+ * integrity (edge symmetry, dependency existence), and UC-anchor completeness.
132
+ * @param {{specDir:string, tasks:Array<object>}} input - The SHARED spec dir and the parsed board.
133
+ * @returns {Array<{rule:string, level:("red"|"warn"), detail:string}>} Findings; [] when clean.
134
+ */
135
+ export function lintStructure({ specDir, tasks }) {
136
+ const findings = [];
137
+ const ucDir = join(specDir, "usecases");
138
+ if (!existsSync(join(specDir, "domain-model.md"))) findings.push({ rule: "STRUCTURE", level: "red", detail: "domain-model.md missing" });
139
+ const ucs = existsSync(ucDir) ? readdirSync(ucDir).filter((f) => /^UC-.*\.md$/.test(f)) : [];
140
+ if (!ucs.length) findings.push({ rule: "STRUCTURE", level: "red", detail: "usecases/ has no UC-*.md — nothing to build or grade against" });
141
+ for (const f of ucs) {
142
+ const body = readFileSync(join(ucDir, f), "utf8");
143
+ if (!/^##\s+Steps/m.test(body)) findings.push({ rule: "STRUCTURE", level: "warn", detail: `${f} has no ## Steps section` });
144
+ }
145
+ // Wikilinks in spec docs must resolve within the spec dir — and never cross the tier
146
+ // boundary: a SHARED doc linking the LOCAL board is the wrong direction by construction.
147
+ const specFiles = existsSync(specDir) ? walkFiles(specDir) : [];
148
+ const names = new Set(specFiles.map((f) => f.replace(/\.md$/, "")));
149
+ for (const f of specFiles.filter((x) => x.endsWith(".md"))) {
150
+ const body = readFileSync(join(specDir, f), "utf8");
151
+ for (const m of body.matchAll(/\[\[([^\]#|]+)/g)) {
152
+ const target = m[1].trim().replace(/\.md$/, "");
153
+ if (target.startsWith("tasks/")) {
154
+ findings.push({ rule: "TIER-DIRECTION", level: "red", detail: `${f} → [[${m[1].trim()}]] links the LOCAL board from a committed doc — links flow LOCAL→SHARED only; cite the UC or scope_id instead (task ids renumber per machine)` });
155
+ continue;
156
+ }
157
+ if (!names.has(target) && ![...names].some((n) => n.endsWith(`/${target}`) || n === target)) {
158
+ findings.push({ rule: "WIKILINK", level: "warn", detail: `${f} → [[${m[1].trim()}]] unresolved in spec dir` });
159
+ }
160
+ }
161
+ }
162
+ // Task frontmatter + graph integrity (edge symmetry = the mechanized KB-BA-001 check).
163
+ const ids = new Set(tasks.map((t) => t.id));
164
+ const derived = deriveUnlocks(tasks);
165
+ for (const t of tasks) {
166
+ for (const k of ["id", "status"]) if (!t[k] || t[k] === "unknown") findings.push({ rule: "TASK", level: "red", detail: `${t.file} missing frontmatter ${k}` });
167
+ for (const d of t.depends_on) if (!ids.has(d)) findings.push({ rule: "TASK", level: "red", detail: `${t.id} depends_on ${d} which does not exist` });
168
+ if (JSON.stringify([...t.unlocks].sort()) !== JSON.stringify(derived[t.id] || [])) {
169
+ findings.push({ rule: "EDGE-SYMMETRY", level: "red", detail: `${t.id} unlocks ${JSON.stringify(t.unlocks)} ≠ derived inverse ${JSON.stringify(derived[t.id])} — run board-derive.mjs --write` });
170
+ }
171
+ }
172
+ // UC-ANCHOR — the LOCAL→SHARED anchor must be complete: every implementation task names
173
+ // ≥1 UC (single-anchor rule, task-generation.md) and each named UC exists on disk.
174
+ // SPIKE/CHORE/DOCS/MIGRATION tasks anchor elsewhere (api_ref / linked_docs) — exempt.
175
+ const ucIds = new Set(ucs.map((f) => f.replace(/\.md$/, "")));
176
+ const anchorExempt = new Set(["spike", "chore", "docs", "migration"]);
177
+ for (const t of tasks) {
178
+ if (anchorExempt.has((t.type || "").toLowerCase())) continue;
179
+ const refs = t.use_case_refs || [];
180
+ if (!refs.length) {
181
+ findings.push({ rule: "UC-ANCHOR", level: "red", detail: `${t.id} has empty use_case_refs — every task must anchor into the committed spec (LOCAL→SHARED, single-anchor rule)` });
182
+ continue;
183
+ }
184
+ for (const r of refs) {
185
+ const ucId = r.replace(/^\[\[|\]\]$/g, "").replace(/^usecases\//, "");
186
+ if (!ucIds.has(ucId)) findings.push({ rule: "UC-ANCHOR", level: "red", detail: `${t.id} use_case_refs "${r}" does not resolve to usecases/${ucId}.md` });
187
+ }
188
+ }
189
+ return findings;
190
+ }
191
+
192
+ /**
193
+ * Run the full spec lint (scopes + structure) for a slug.
194
+ * @param {{cwd:string, slug:string}} opts - Working root and feature slug.
195
+ * @returns {{slug:string, scopes:number, tasks:number, red:number, warn:number,
196
+ * findings:Array<object>}} Counts and the combined findings from {@link lintScopes} and
197
+ * {@link lintStructure}.
198
+ */
199
+ export function lint({ cwd, slug }) {
200
+ const specRoot = specDir(cwd, slug);
201
+ const contracts = readAllContracts(scopesDir(cwd, slug), SCOPE_CONTRACT);
202
+ const scopes = contracts.map((c) => c.contract);
203
+ const tasks = parseBoard(tasksDir(cwd, slug));
204
+ const repoFiles = walkFiles(cwd);
205
+ const findings = [
206
+ // HD-001. A contract whose table this parser cannot see reads as a contract that declared no
207
+ // table, and every rule below then passes for the part it could not read. Loud, not empty.
208
+ ...contracts
209
+ .map(({ contract, path }) => ({ reason: unreadableReason(contract), scope: contract.scope_id || path }))
210
+ .filter((x) => x.reason)
211
+ .map((x) => ({ rule: "CONTRACT-UNREADABLE", level: "red", scope: x.scope, detail: `${x.reason} — the rules below could not check what they could not read` })),
212
+ ...lintScopes(scopes, repoFiles),
213
+ ...lintStructure({ specDir: specRoot, tasks }),
214
+ ];
215
+ return {
216
+ slug,
217
+ scopes: scopes.length,
218
+ tasks: tasks.length,
219
+ red: findings.filter((f) => f.level === "red").length,
220
+ warn: findings.filter((f) => f.level === "warn").length,
221
+ findings,
222
+ };
223
+ }
224
+
225
+ /** The typed argv contract (see `skills/tech-lead/scripts/lib/argv.mjs`). */
226
+ export const ARGV_SPEC = {
227
+ usage: "spec-lint.mjs --slug <slug> [--cwd <dir>]",
228
+ _: { arity: 0, max: 0, name: "(no positional operands)" },
229
+ slug: { type: "str", required: true },
230
+ cwd: { type: "path" },
231
+ };
232
+
233
+ const isMainModule = isMain(import.meta.url);
234
+ if (isMainModule) {
235
+ const args = runArgs(ARGV_SPEC);
236
+ const report = lint({ cwd: resolve(args.cwd || process.cwd()), slug: args.slug });
237
+ console.log(JSON.stringify(report, null, 2));
238
+ process.exit(report.red > 0 ? 1 : 0);
239
+ }
@@ -0,0 +1,202 @@
1
+ ---
2
+ name: coach
3
+ description: "Use this skill to turn raw Product Owner / Tech Lead feedback at the Ship Sign-off (L4 Gate) into structured, team-shared guidelines that future harness runs read back. Triggers on: \"coach this feedback\", \"record this for next sprint\", \"update the knowledge base\", \"RLHF the harness\", and Vietnamese \"ghi lại cho sprint sau\", \"cập nhật knowledge base\". tech-lead invokes it automatically at GATE L4 when the PO gives substantive feedback instead of a bare 'y'. NOT for grading work (spec-evaluator), fixing bugs (task-executor), or filing discovered tasks (the ledger)."
4
+ ---
5
+
6
+ # Coach Skill — RLHF for the harness
7
+
8
+ The `/coach` skill closes the learning loop. After a feature ships, the PO/TL drops raw,
9
+ unstructured feedback at the L4 Gate ("the executor keeps over-engineering DTOs", "the BA
10
+ under-scopes mobile"). The coach distills that into durable **guidelines** and files them where
11
+ the relevant worker will read them on its **next** run — so the lesson is learned once and applied
12
+ by the whole team forever, not re-explained every sprint.
13
+
14
+ Two properties make this useful and were missing before:
15
+
16
+ 1. **Team-shared, not local.** Guidelines are written under `shapeup/knowledge-base/`,
17
+ which is **committed** (the `.shapeup/` run-trace root is gitignored — guidelines written
18
+ there would never reach a teammate). A `git pull` is all a team member needs to inherit the
19
+ harness's accumulated judgment.
20
+ 2. **Read back, not write-only.** Each guideline is filed under the **one skill that will act on
21
+ it**, in that skill's own file, so the consumer loads only its own rules. `task-executor`,
22
+ `ba-pitch-analyzer`, and `qa-edge-hunter` each read their file at the top of their run.
23
+
24
+ ```
25
+ PO feedback at L4 ─► /coach ─► [parse into candidate rules] ─► ⏸ GATE COACH-1 (categorize, ask — never assume)
26
+
27
+ shapeup/knowledge-base/<skill>.md ◄───┤ (one file per coachable skill, committed)
28
+
29
+ next run: task-executor / ba-pitch-analyzer / qa-edge-hunter reads its own file
30
+
31
+ shapeup/knowledge-base/harness-defects.md ◄───────┘ (mechanism at fault →
32
+ drafted raw idea for the Betting Table — read by no worker, committed)
33
+ ```
34
+
35
+ ---
36
+
37
+ ## Coachable skills (the only valid categories)
38
+
39
+ A guideline is only useful if a worker reads it back. These three workers have a read-side hook;
40
+ they are the **complete** set of categories the gate may offer:
41
+
42
+ | Category | File | The worker reads it at | Good for |
43
+ |----------|------|------------------------|----------|
44
+ | `task-executor` | `shapeup/knowledge-base/task-executor.md` | Phase 1 (Context Load) | implementation discipline, code style, surgical-change habits, recurring over/under-engineering |
45
+ | `ba-pitch-analyzer` | `shapeup/knowledge-base/ba-pitch-analyzer.md` | Phase 1 (Ingest & Scan) | scoping, task decomposition, DDD/spec habits, missed test-surface patterns |
46
+ | `qa-edge-hunter` | `shapeup/knowledge-base/qa-edge-hunter.md` | Phase Q1 (Charter Map) | recurring edge classes, lenses that keep finding bugs, areas worth probing |
47
+
48
+ **Not coachable.** `spec-evaluator` is deliberately excluded — the harness has a **single-judge**
49
+ rule and the knowledge base is guidance, never an invariant; routing rules into the evaluator would
50
+ turn advice into a second grader. `orient`, `shapeup`, `tech-lead`, and `translator` have no
51
+ read-side hook, so a rule filed there would never be read. If feedback truly targets one of these,
52
+ say so plainly — do **not** force-fit it into a coachable category.
53
+
54
+ **Harness defect ≠ worker steering.** When the feedback's root cause is the *mechanism itself* —
55
+ a hook that fail-opens, a gate that reads the wrong file, two skill contracts that contradict
56
+ each other — no amount of steering a worker fixes it, and filing it as a KB rule misdiagnoses a
57
+ defect as a habit (island-escape's KB-BA-002 filed an orchestration/hook defect as BA guidance,
58
+ on a premise the skill contracts contradict). That is what the `harness-defect` category below is
59
+ for: the coach records it in the committed defect register as a drafted **raw idea** for the
60
+ Betting Table — the debt-free path ("remaining findings + new feedback → new raw idea") — and it
61
+ never lands in any worker's KB.
62
+
63
+ ---
64
+
65
+ ## Envelope contract — the domain layer
66
+
67
+ Orchestrated, this skill is dispatched like every worker: a **WorkOrder** in (`--order <path>`,
68
+ operation `coach`), a **WorkResult** out. Standalone, the raw feedback is passed directly; it
69
+ maps onto the one payload field registered for this worker in the central domain registry
70
+ (`skills/tech-lead/schemas/domain.schema.json`, `x-payload-by-worker`):
71
+
72
+ | Payload field | Standalone form | Meaning |
73
+ |---|---|---|
74
+ | `payload.feedback` | positional text | The PO's raw L4 feedback to distill and categorize at GATE COACH-1 |
75
+
76
+ The WorkResult may carry only `files_touched`, `artifacts`, `assumptions`, `deviations`
77
+ (`x-result-by-worker`): the knowledge-base files written under
78
+ `shapeup/knowledge-base/` return as `files_touched`/`artifacts` — the coach itself is
79
+ not coachable and never returns discoveries, verdicts, or task results.
80
+
81
+ ---
82
+
83
+ ## Instructions
84
+
85
+ ### Step 1 — Parse the raw feedback into discrete candidate rules
86
+ Feedback is usually a blob covering several points. Split it into atomic, generalized candidate
87
+ rules — one actionable lesson each. Generalize the specific incident into a habit ("DTO had 9
88
+ fields nobody used" → "Prefer the minimum DTO that satisfies the AC; don't add speculative
89
+ fields"). Keep the originating why — a rule without its reason gets ignored or misapplied.
90
+
91
+ ### Step 2 — ⏸ GATE COACH-1: Categorize (ASK, never assume)
92
+ This is the load-bearing gate. **Do not infer which skill a rule belongs to** — a
93
+ miscategorized rule lands in a file the wrong worker reads (or no worker reads). Present every
94
+ candidate rule and ask the PO to assign each one. Emit this block, then stop and wait:
95
+
96
+ ```
97
+ ⏸ GATE COACH-1 — Categorize feedback
98
+ For each candidate rule, which skill should act on it?
99
+ Valid: [task-executor] [ba-pitch-analyzer] [qa-edge-hunter]
100
+ [harness-defect — mechanism at fault, file as raw idea] [skip — not coachable]
101
+
102
+ R1. "<generalized rule>" (why: <reason>) → ?
103
+ R2. "<generalized rule>" (why: <reason>) → ?
104
+ ...
105
+
106
+ Reply with an assignment per rule, e.g. "R1→task-executor, R2→harness-defect, R3→skip".
107
+ A rule may map to more than one skill if it genuinely applies to both (e.g. "R1→task-executor, ba-pitch-analyzer").
108
+ ```
109
+
110
+ Rules to honor at this gate:
111
+ - **No silent defaulting.** If the PO's reply is ambiguous or leaves a rule unassigned, ask once
112
+ more for that rule specifically. Never pick a category on the PO's behalf.
113
+ - **`skip` is a first-class answer.** Feedback aimed at a non-coachable skill, or one-off context
114
+ with no general lesson, is recorded as skipped in your summary and **not** written anywhere.
115
+ - **Respect the single-judge rule.** If the PO tries to assign a rule to `spec-evaluator`,
116
+ surface that it isn't coachable (guidance ≠ invariant) and offer the nearest real target
117
+ (usually `ba-pitch-analyzer`, which owns the spec/test-surface) or `skip`.
118
+ - **Recommend `harness-defect` when the mechanism is at fault.** If a candidate rule's "why"
119
+ blames a gate, hook, script, or a contradiction between skill contracts (rather than a
120
+ worker's judgment), say so and recommend `harness-defect` — but the PO still decides. The
121
+ telltale: the rule asks a worker to compensate for something the harness was supposed to
122
+ enforce ("cross-check X because the bookkeeping step gets skipped").
123
+
124
+ ### Step 3 — Merge each assigned rule into its skill's knowledge-base file
125
+ For each `<skill>` that received at least one rule:
126
+ 1. Ensure `shapeup/knowledge-base/<skill>.md` exists (create from the template below if
127
+ not — the directory is committed, so the file ships to the team on the next commit).
128
+ 2. Read the existing file. Merge the new rule(s):
129
+ - **Consolidate** overlapping rules into one stronger statement.
130
+ - **Deduplicate** — if the lesson is already captured, reinforce/sharpen it rather than adding a
131
+ near-duplicate. Bump nothing silently; note the merge in your summary.
132
+ - **Generalize** a specific incident into a reusable guideline.
133
+ 3. Assign each new rule a stable id `KB-<SKILL-INITIALS>-NNN` (e.g. `KB-TE-001`, `KB-BA-004`,
134
+ `KB-QA-002`) and stamp it with the originating feature slug + date so a future reader can trace
135
+ it back.
136
+ 4. Rewrite the file. Keep it tight — the consumer loads it every run, so prune stale or
137
+ contradicted rules rather than letting it grow unboundedly. A rule whose premise the current
138
+ skill contracts contradict is a `harness-defect` in disguise — move it to the register
139
+ (Step 3b) and note the reclassification, don't keep re-teaching a misdiagnosis.
140
+
141
+ ### Step 3b — File `harness-defect` rules to the defect register (raw ideas, not steering)
142
+
143
+ For each rule the PO assigned `harness-defect`, append an entry to
144
+ `shapeup/knowledge-base/harness-defects.md` (create from the template below if
145
+ missing). This file is **committed but read by no worker** — it is the PO's backlog of drafted
146
+ raw ideas for the Betting Table, not guidance. Each entry gets a stable id `HD-NNN`, the
147
+ observed symptom, the suspected mechanism at fault, and a one-paragraph raw-idea draft the PO
148
+ can carry straight into Shaping. A report-only mention would evaporate (the write-only failure
149
+ this skill exists to prevent); a worker-KB entry would steer the wrong actor — the register is
150
+ the one spot that is both durable and inert.
151
+
152
+ ```markdown
153
+ # Harness Defect Register
154
+
155
+ > Filed by `/coach` from Ship-Gate (L4) feedback the PO categorized as `harness-defect` at
156
+ > GATE COACH-1. **Read by no worker** — these are drafted raw ideas for the Betting Table
157
+ > (the debt-free path), not guidelines. Remove an entry when its fix ships or its pitch is bet.
158
+
159
+ ## Defects
160
+ - **HD-001** — <symptom observed at ship>. Suspected mechanism: <gate/hook/skill contract>.
161
+ Raw idea: <one-paragraph pitch seed>. · from `<feature-slug>` (<date>)
162
+ ```
163
+
164
+ ### Step 4 — Report back
165
+ Summarize: which rules went to which file (with ids), which were consolidated into existing rules,
166
+ which were filed as harness defects (HD ids — remind the PO these await a Betting Table decision,
167
+ nothing acts on them automatically), and which were skipped (and why). Remind the PO that these are **guidelines** the named workers read
168
+ on their next run — they steer `task-executor`, `ba-pitch-analyzer`, and `qa-edge-hunter`, but they
169
+ are **not invariants** and the `spec-evaluator` verdict is unaffected (single-judge rule). Note that
170
+ the files are committed, so a teammate inherits them on `git pull`.
171
+
172
+ ---
173
+
174
+ ## Knowledge-base file template
175
+
176
+ When creating `shapeup/knowledge-base/<skill>.md` for the first time:
177
+
178
+ ```markdown
179
+ # Knowledge Base — <skill>
180
+
181
+ > Team-shared guidelines distilled from PO/TL feedback at the Ship Gate (L4) by `/coach`.
182
+ > Read by `<skill>` at the top of its run. **Guidelines, not invariants** — they steer the
183
+ > worker; they never override a spec or change the spec-evaluator verdict (single-judge rule).
184
+ > Committed on purpose: a teammate inherits these on `git pull`.
185
+
186
+ ## Guidelines
187
+ - **KB-<XX>-001** — <generalized rule>. _(why: <reason>)_ · from `<feature-slug>` (<date>)
188
+ - **KB-<XX>-002** — <generalized rule>. _(why: <reason>)_ · from `<feature-slug>` (<date>)
189
+ ```
190
+
191
+ ---
192
+
193
+ ## Hard Rules
194
+ | Rule | Rationale |
195
+ |------|-----------|
196
+ | Never assume a category — GATE COACH-1 asks the PO for every rule | A miscategorized rule reaches the wrong reader or none; the PO's intent is authoritative |
197
+ | Only `task-executor`, `ba-pitch-analyzer`, `qa-edge-hunter` are valid worker categories | They are the only workers with a read-side hook; a rule elsewhere is never read |
198
+ | A mechanism-at-fault rule goes to the defect register (`harness-defect`), never a worker KB | Steering a worker to compensate for a broken gate/hook misdiagnoses a defect as a habit and hides it from the Betting Table |
199
+ | `spec-evaluator` is never a category | Single-judge rule: the KB is guidance, not an invariant — routing rules into the judge creates a second grader |
200
+ | Write only under `shapeup/knowledge-base/` (committed) | The `.shapeup/` run-trace is gitignored; guidelines there never reach the team |
201
+ | Guidelines, not invariants | The consumer weighs them; they don't gate, score, or override the spec |
202
+ | Keep each file tight — prune as you merge | Consumers load it every run; unbounded growth becomes token cost and noise |