shapeup-sdlc 1.7.0 → 3.0.1

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 (98) hide show
  1. package/.claude/settings.local.example.json +5 -5
  2. package/.claude-plugin/plugin.json +1 -1
  3. package/AGENTS.md +17 -3
  4. package/README.md +124 -103
  5. package/SECURITY.md +37 -30
  6. package/bin/init.mjs +77 -47
  7. package/bin/lib/grant.mjs +145 -0
  8. package/commands/build.md +7 -6
  9. package/commands/ship.md +29 -16
  10. package/commands/wire.md +1 -1
  11. package/hooks/dispatch-receipt.mjs +195 -0
  12. package/hooks/gate-intake.mjs +3 -3
  13. package/hooks/gate-zerowork.mjs +25 -24
  14. package/hooks/hooks.json +9 -48
  15. package/hooks/lib/decision.mjs +19 -8
  16. package/hooks/safety-spine.mjs +3 -3
  17. package/hooks/sandbox-guard.mjs +84 -40
  18. package/{skills/tech-lead/scripts/compile-order.mjs → kernel/compile.mjs} +283 -41
  19. package/{skills/tech-lead/scripts/gate-answers.mjs → kernel/gate.mjs} +58 -14
  20. package/kernel/harness.mjs +134 -0
  21. package/{skills/tech-lead/scripts/fit-check.mjs → kernel/init/fit.mjs} +15 -12
  22. package/{skills/tech-lead/scripts/init-run.mjs → kernel/init/run.mjs} +152 -31
  23. package/{skills/tech-lead/scripts → kernel}/lib/argv.mjs +52 -31
  24. package/{skills/tech-lead/scripts/lib/contract-md.mjs → kernel/lib/contract.mjs} +251 -7
  25. package/kernel/lib/paths.mjs +491 -0
  26. package/kernel/probe/concurrency.mjs +510 -0
  27. package/{skills/tech-lead/scripts/aegis-digest.mjs → kernel/probe/digest.mjs} +9 -10
  28. package/kernel/probe/eval.mjs +77 -0
  29. package/kernel/probe/leg.mjs +125 -0
  30. package/{skills/tech-lead/scripts/resume-state.mjs → kernel/probe/resume.mjs} +204 -63
  31. package/{skills/tech-lead/scripts → kernel/probe}/stats.mjs +14 -9
  32. package/kernel/probe/t0.mjs +66 -0
  33. package/{skills/ba-pitch-analyzer/scripts/board-derive.mjs → kernel/reduce/board.mjs} +99 -23
  34. package/kernel/reduce/graph.mjs +437 -0
  35. package/{skills/tech-lead/scripts/hill-derive.mjs → kernel/reduce/hill.mjs} +28 -9
  36. package/kernel/reduce/ingest.mjs +633 -0
  37. package/{hooks/slop-cleaner.mjs → kernel/reduce/leftovers.mjs} +40 -57
  38. package/{skills/tech-lead/scripts/ship-report.mjs → kernel/reduce/ship.mjs} +61 -13
  39. package/{skills/tech-lead/scripts/run-snapshot.mjs → kernel/reduce/snapshot.mjs} +21 -15
  40. package/{skills/spec-evaluator/scripts/verdict-ledger.mjs → kernel/reduce/verdict.mjs} +13 -7
  41. package/kernel/report/export.mjs +321 -0
  42. package/kernel/report/facts.mjs +209 -0
  43. package/{skills/tech-lead/scripts/budget-check.mjs → kernel/verify/budget.mjs} +15 -12
  44. package/kernel/verify/dispatch.mjs +114 -0
  45. package/{skills/tech-lead/scripts/validate-envelope.mjs → kernel/verify/envelope.mjs} +20 -15
  46. package/{skills/tech-lead/scripts/lib → kernel/verify}/ratchet-tree.mjs +36 -14
  47. package/kernel/verify/skills.mjs +125 -0
  48. package/kernel/verify/spec.mjs +559 -0
  49. package/{skills/tech-lead/scripts/t0-verify.mjs → kernel/verify/t0.mjs} +134 -24
  50. package/{skills/tech-lead/scripts/trace-lint.mjs → kernel/verify/trace.mjs} +26 -11
  51. package/oracles/http-oracle.mjs +1 -1
  52. package/oracles/process-oracle.mjs +1 -1
  53. package/oracles/snapshot-oracle.mjs +1 -1
  54. package/oracles/test-oracle.mjs +1 -1
  55. package/package.json +11 -9
  56. package/skills/ba-pitch-analyzer/SKILL.md +5 -5
  57. package/skills/ba-pitch-analyzer/assets/templates/_index.tmpl.md +1 -1
  58. package/skills/ba-pitch-analyzer/assets/templates/api-feasibility.tmpl.md +12 -7
  59. package/skills/ba-pitch-analyzer/assets/templates/contracts/third-party-api.contract.tmpl.md +6 -3
  60. package/skills/ba-pitch-analyzer/assets/templates/cross-context/migration-plan.tmpl.md +5 -4
  61. package/skills/ba-pitch-analyzer/assets/templates/cross-context/team-handoff.tmpl.md +23 -13
  62. package/skills/ba-pitch-analyzer/assets/templates/scope-summary.tmpl.md +17 -12
  63. package/skills/ba-pitch-analyzer/assets/templates/synthesis.tmpl.md +17 -38
  64. package/skills/ba-pitch-analyzer/assets/templates/task.tmpl.md +3 -0
  65. package/skills/ba-pitch-analyzer/references/contract-patterns.md +8 -4
  66. package/skills/ba-pitch-analyzer/references/doc-schemas.md +2 -0
  67. package/skills/ba-pitch-analyzer/references/task-generation.md +2 -2
  68. package/skills/qa-edge-hunter/SKILL.md +7 -3
  69. package/skills/scope-architect/SKILL.md +40 -6
  70. package/skills/solution-architect/SKILL.md +19 -4
  71. package/skills/spec-evaluator/SKILL.md +12 -2
  72. package/skills/task-executor/SKILL.md +7 -6
  73. package/skills/tech-lead/SKILL.md +50 -38
  74. package/skills/tech-lead/references/gates.md +71 -20
  75. package/skills/tech-lead/references/protocol.md +832 -0
  76. package/skills/tech-lead/schemas/domain.schema.json +341 -66
  77. package/skills/tech-lead/schemas/work-order.schema.json +11 -2
  78. package/skills/tech-lead/schemas/work-result.schema.json +1 -1
  79. package/skills/tech-lead/workflows/shapeup-run.js +1327 -750
  80. package/hooks/anti-rationalization.mjs +0 -238
  81. package/hooks/compact-snapshot.mjs +0 -47
  82. package/hooks/gate-deadline.mjs +0 -147
  83. package/hooks/gate-l2.mjs +0 -161
  84. package/hooks/session-rehydrate.mjs +0 -108
  85. package/skills/ba-pitch-analyzer/scripts/spec-lint.mjs +0 -239
  86. package/skills/spec-evaluator/README.md +0 -93
  87. package/skills/tech-lead/README.md +0 -71
  88. package/skills/tech-lead/references/delegation.md +0 -261
  89. package/skills/tech-lead/references/hard-rules.md +0 -34
  90. package/skills/tech-lead/references/invocation.md +0 -45
  91. package/skills/tech-lead/references/ledger-schema.md +0 -213
  92. package/skills/tech-lead/references/round-protocol.md +0 -234
  93. package/skills/tech-lead/references/state-model.md +0 -66
  94. package/skills/tech-lead/scripts/ingest-result.mjs +0 -258
  95. package/skills/tech-lead/scripts/lib/is-main.mjs +0 -81
  96. package/skills/tech-lead/scripts/lib/paths.mjs +0 -280
  97. package/skills/tech-lead/scripts/run-workflow.mjs +0 -381
  98. package/skills/translator/README.md +0 -66
@@ -1,11 +1,10 @@
1
1
  #!/usr/bin/env node
2
2
  // Sandbox guard — PreToolUse hook (the PA3 countermeasure: writes outside the order's substrate).
3
3
  //
4
- // Blocks Edit/Write/MultiEdit calls that the ACTIVE ORDER's own `substrate` block does not
5
- // permit. Turns "a worker only writes what its order authorised" from prose into a precondition
6
- // the model cannot talk past.
4
+ // Blocks Edit/Write/MultiEdit calls that no LIVE ORDER's `substrate` block permits. Turns "a worker
5
+ // only writes what the run authorised" from prose into a precondition the model cannot talk past.
7
6
  //
8
- // IT ENFORCES THE ORDER, NOT THE SCOPE CONTRACT, and that is the whole design. `compile-order.mjs`
7
+ // IT ENFORCES THE ORDER, NOT THE SCOPE CONTRACT, and that is the whole design. ``harness compile``
9
8
  // already stamps a write contract onto every order from `substrateFor(operation)` — allowed,
10
9
  // shared, append_only, frozen. Resolving the scope contract instead covered exactly one operation,
11
10
  // the build, because only build orders carry a scope; every other dispatch (`analyze`, `wire`,
@@ -14,12 +13,21 @@
14
13
  // and the contract the hook enforces the same object, for every operation, with no per-operation
15
14
  // code here.
16
15
  //
17
- // It finds the order through `.shapeup/active-order`, which `compile-order.mjs` publishes as it
18
- // writes the order (and the workflow script re-points explicitly before each dispatch). Both
19
- // authors matter: the compiler's write is what fences the lanes that never reach the workflow —
20
- // `--tiny`, the prose round loop, a standalone `/build`.
16
+ // IT READS EVERY LIVE ORDER, NOT A POINTER TO ONE. `.shapeup/active-order` still names the run and
17
+ // seeds the search `harness compile` publishes it as it writes each order, which is what fences
18
+ // the lanes that never reach the workflow (`--tiny`, the prose round loop, a standalone `/build`).
19
+ // But a single pointer cannot survive concurrency: with scopes building side by side the last
20
+ // compile wins the pointer, and a write from scope A is then judged against scope B's contract —
21
+ // a false block or a false permit depending on which way the race fell. So the candidate set is
22
+ // every order under `orders/` with no matching file under `results/` (compiled, not yet ingested),
23
+ // and a write is permitted when SOME live contract covers it.
21
24
  //
22
- // Design (deliberately conservative, mirrors gate-l2.mjs):
25
+ // That is the same question as "the writer's own contract" because scope substrates are disjoint by
26
+ // construction — `harness verify spec`'s DISJOINT rule fails a spec where two scopes claim the same
27
+ // path, and it runs at GATE L1b before any build starts. `frozen` is checked across all of them, so
28
+ // a path one scope froze stays frozen while another scope is in flight.
29
+ //
30
+ // Design (deliberately conservative, mirrors the GATE L2 block):
23
31
  // • Fail-OPEN whenever there is nothing to enforce: no active-order pointer (not running inside
24
32
  // a harness dispatch), pointer names an order that doesn't exist or is unparsable, the order
25
33
  // declares no boundaries at all, or the tool call carries no resolvable file path. A guard
@@ -34,18 +42,18 @@
34
42
  // execution logs, the P3.7 discovery ledger). Substrate globs whitelist product code and
35
43
  // never list the run-trace, so without the carve-out a scoped round leaves its own task files
36
44
  // stale. Deliberately narrow: only the active slug's root. The pointers at the `.shapeup/`
37
- // root — `active-order` (this guard's own) and `active-scope` — sit OUTSIDE the carve-out by
38
- // construction, so a worker cannot widen its own sandbox by rewriting the thing that defines
39
- // it.
45
+ // root — `active-order` (this guard's own) and `active-scope` (the run pointer) — sit OUTSIDE
46
+ // the carve-out by construction, so a worker cannot widen its own sandbox by rewriting the
47
+ // thing that defines it.
40
48
  // • Every denial is also appended to the metrics pathology log (telemetry, not just defense).
41
49
  //
42
50
  // Contract: PreToolUse stdin JSON { tool_name, tool_input:{file_path | edits[].file_path}, cwd }.
43
51
  // Deny via { hookSpecificOutput: { hookEventName, permissionDecision:"deny", permissionDecisionReason } }.
44
52
 
45
- import { readFileSync, existsSync, appendFileSync, mkdirSync } from "node:fs";
53
+ import { readFileSync, existsSync, appendFileSync, mkdirSync, readdirSync } from "node:fs";
46
54
  import { resolve, join, relative, dirname, sep } from "node:path";
47
- import { isMain } from "../skills/tech-lead/scripts/lib/is-main.mjs";
48
- import { LOCAL, activeOrder, metricsShard } from "../skills/tech-lead/scripts/lib/paths.mjs";
55
+ import { isMain } from "../kernel/lib/argv.mjs";
56
+ import { LOCAL, activeOrder, ordersDir, resultsDir, metricsShard } from "../kernel/lib/paths.mjs";
49
57
  import { runHook, readStdin, settle } from "./lib/decision.mjs";
50
58
 
51
59
  // --- tiny glob matcher: supports *, **, ? — enough for substrate globs, zero dependencies ---
@@ -80,6 +88,31 @@ function readJSON(p) {
80
88
  try { return JSON.parse(readFileSync(p, "utf8")); } catch { return null; }
81
89
  }
82
90
 
91
+ /**
92
+ * Every order for this run that has been compiled and not yet ingested.
93
+ *
94
+ * "Not yet ingested" is read off the filesystem — an order with a same-named file under `results/`
95
+ * has finished — because that is the only signal that survives a killed session. The pointer's own
96
+ * order is always included, even when its result has landed, so the single-order lane behaves
97
+ * exactly as it did before concurrency existed.
98
+ *
99
+ * @param {string} cwd - Project root.
100
+ * @param {string} slug - The active run's slug.
101
+ * @param {string} pointerOrder - Absolute path of the order the pointer names.
102
+ * @returns {object[]} Parsed orders; unreadable files are skipped, never treated as permissive.
103
+ */
104
+ function liveOrders(cwd, slug, pointerOrder) {
105
+ const dir = ordersDir(cwd, slug);
106
+ const done = new Set(existsSync(resultsDir(cwd, slug)) ? readdirSync(resultsDir(cwd, slug)) : []);
107
+ const paths = new Set(existsSync(pointerOrder) ? [pointerOrder] : []);
108
+ if (existsSync(dir)) {
109
+ for (const f of readdirSync(dir)) {
110
+ if (f.endsWith(".json") && !done.has(f)) paths.add(join(dir, f));
111
+ }
112
+ }
113
+ return [...paths].map(readJSON).filter(Boolean);
114
+ }
115
+
83
116
  function extractPaths(toolInput) {
84
117
  const paths = [];
85
118
  if (toolInput?.file_path) paths.push(toolInput.file_path);
@@ -118,20 +151,30 @@ async function main() {
118
151
  const active = readJSON(activeOrderPath);
119
152
  if (!active?.slug || !active?.order_path) defer("active-order pointer is unreadable or incomplete", "bad-pointer");
120
153
 
121
- const orderPathAbs = resolve(cwd, active.order_path);
122
- if (!existsSync(orderPathAbs)) defer(`no order found at ${active.order_path}`, "no-order");
123
-
124
- const order = readJSON(orderPathAbs);
125
- if (!order?.substrate) defer("active order has no substrate block", "no-substrate");
126
-
127
- const contract = order.substrate;
128
- const allowed = [...(contract.allowed || []), ...(contract.shared || [])];
129
- const appendOnly = contract.append_only || [];
130
- const frozen = contract.frozen || [];
131
-
132
- if (allowed.length === 0 && appendOnly.length === 0 && frozen.length === 0) {
133
- defer(`order declares no write/append/frozen boundaries`, "no-whitelist");
134
- }
154
+ // EVERY LIVE ORDER, not just the pointer's. The pointer names one order, and with scopes building
155
+ // CONCURRENTLY the last compile wins it — so a write from scope A would be judged against scope
156
+ // B's contract, which is either a false block or a false permit depending on which way the race
157
+ // fell. Scope substrates are disjoint by construction (spec-lint's DISJOINT rule fails a spec
158
+ // where they are not), so "is this write inside SOME live contract" and "is it inside the
159
+ // writer's own contract" are the same question — and only the first can be asked without a
160
+ // shared mutable pointer.
161
+ //
162
+ // Live = compiled and not yet ingested. An order whose result is on disk has finished; leaving it
163
+ // in the candidate set would keep a finished scope's substrate open for the rest of the run.
164
+ const orders = liveOrders(cwd, active.slug, resolve(cwd, active.order_path));
165
+ if (orders.length === 0) defer(`no live order for ${active.slug}`, "no-order");
166
+
167
+ const withSubstrate = orders.filter((o) => o.substrate);
168
+ if (withSubstrate.length === 0) defer("no live order declares a substrate block", "no-substrate");
169
+
170
+ const contracts = withSubstrate.map((o) => ({
171
+ order_id: o.order_id,
172
+ allowed: [...(o.substrate.allowed || []), ...(o.substrate.shared || [])],
173
+ appendOnly: o.substrate.append_only || [],
174
+ frozen: o.substrate.frozen || [],
175
+ })).filter((c) => c.allowed.length || c.appendOnly.length || c.frozen.length);
176
+
177
+ if (contracts.length === 0) defer("no live order declares write/append/frozen boundaries", "no-whitelist");
135
178
 
136
179
  const targetPaths = extractPaths(p.tool_input);
137
180
  if (targetPaths.length === 0) defer("no writable path in the tool input", "no-target");
@@ -145,19 +188,20 @@ async function main() {
145
188
  const abs = resolve(cwd, raw);
146
189
  const rel = relative(cwd, abs);
147
190
  if (rel.startsWith(runTracePrefix)) continue;
148
-
149
- // frozen takes absolute precedence
150
- if (matchesAny(rel, frozen)) {
191
+
192
+ // Frozen takes absolute precedence, and it is checked across EVERY live contract: a path one
193
+ // scope froze stays frozen while another scope is in flight, which is the whole point of
194
+ // declaring it.
195
+ const freezer = contracts.find((c) => matchesAny(rel, c.frozen));
196
+ if (freezer) {
151
197
  violations.push(rel);
152
- blockReasons.push(`${rel} is frozen`);
198
+ blockReasons.push(`${rel} is frozen by ${freezer.order_id}`);
153
199
  continue;
154
200
  }
155
201
 
156
- if (matchesAny(rel, allowed)) {
157
- continue; // OK
158
- }
202
+ if (contracts.some((c) => matchesAny(rel, c.allowed))) continue; // inside a live contract
159
203
 
160
- if (matchesAny(rel, appendOnly)) {
204
+ if (contracts.some((c) => matchesAny(rel, c.appendOnly))) {
161
205
  if (p.tool_name === "Write") {
162
206
  violations.push(rel);
163
207
  blockReasons.push(`${rel} is append-only (Write overwrites, use Edit)`);
@@ -166,13 +210,13 @@ async function main() {
166
210
  }
167
211
 
168
212
  violations.push(rel);
169
- blockReasons.push(`${rel} is outside allowed scopes`);
213
+ blockReasons.push(`${rel} is outside every live order's allowed scopes`);
170
214
  }
171
215
 
172
216
  // Inside the substrate — the "inspected and permitted" row. Previously byte-identical to
173
217
  // "this hook never ran", which is how 26 enforcement points sat inert behind 610 green checks.
174
218
  if (violations.length === 0) {
175
- defer(`${targetPaths.length} path(s) inside order substrate — permitted`, "in-substrate");
219
+ defer(`${targetPaths.length} path(s) inside a live order's substrate (${contracts.length} live) — permitted`, "in-substrate");
176
220
  }
177
221
 
178
222
  logPathology(metricsPath, {
@@ -194,7 +238,7 @@ async function main() {
194
238
  hookEventName: "PreToolUse",
195
239
  permissionDecision: "deny",
196
240
  permissionDecisionReason:
197
- `Sandbox guard (PA3) — active order substrate blocked these writes:\n` +
241
+ `Sandbox guard (PA3) — no live order's substrate covers these writes:\n` +
198
242
  `${blockReasons.join("\n")}\n` +
199
243
  `If this write legitimately crosses scopes, the order's substrate needs to be expanded (e.g. via ba --remap).`,
200
244
  },