shapeup-sdlc 1.6.2 → 1.7.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 (85) hide show
  1. package/.claude-plugin/plugin.json +1 -1
  2. package/AGENTS.md +36 -103
  3. package/README.md +60 -67
  4. package/SECURITY.md +31 -16
  5. package/bin/init.mjs +24 -69
  6. package/commands/build.md +19 -0
  7. package/commands/eval.md +5 -4
  8. package/commands/scopes.md +5 -4
  9. package/commands/shape.md +1 -1
  10. package/commands/ship.md +37 -4
  11. package/hooks/anti-rationalization.mjs +7 -13
  12. package/hooks/compact-snapshot.mjs +5 -5
  13. package/hooks/gate-deadline.mjs +12 -16
  14. package/hooks/gate-intake.mjs +13 -14
  15. package/hooks/gate-l2.mjs +3 -3
  16. package/hooks/gate-zerowork.mjs +97 -16
  17. package/hooks/lib/decision.mjs +21 -13
  18. package/hooks/safety-spine.mjs +1 -1
  19. package/hooks/sandbox-guard.mjs +83 -47
  20. package/hooks/session-rehydrate.mjs +8 -9
  21. package/hooks/slop-cleaner.mjs +1 -1
  22. package/oracles/_shared.mjs +1 -1
  23. package/oracles/http-oracle.mjs +1 -1
  24. package/oracles/index.mjs +1 -1
  25. package/oracles/process-oracle.mjs +1 -1
  26. package/oracles/snapshot-oracle.mjs +1 -1
  27. package/oracles/test-oracle.mjs +1 -1
  28. package/package.json +3 -7
  29. package/skills/ba-pitch-analyzer/SKILL.md +20 -15
  30. package/skills/ba-pitch-analyzer/references/doc-schemas.md +1 -1
  31. package/skills/ba-pitch-analyzer/references/task-generation.md +6 -6
  32. package/skills/ba-pitch-analyzer/references/test-surface.md +1 -1
  33. package/skills/ba-pitch-analyzer/scripts/board-derive.mjs +5 -5
  34. package/skills/ba-pitch-analyzer/scripts/spec-lint.mjs +2 -2
  35. package/skills/coach/SKILL.md +3 -3
  36. package/skills/orient/SKILL.md +2 -1
  37. package/skills/qa-edge-hunter/SKILL.md +9 -39
  38. package/skills/scope-architect/SKILL.md +17 -12
  39. package/skills/scope-hammer/SKILL.md +5 -5
  40. package/skills/shapeup/SKILL.md +3 -3
  41. package/skills/shapeup/resources/context-compaction.md +4 -3
  42. package/skills/solution-architect/SKILL.md +18 -11
  43. package/skills/spec-evaluator/SKILL.md +12 -5
  44. package/skills/spec-evaluator/references/dimensions/_registry.md +2 -2
  45. package/skills/spec-evaluator/references/dimensions/completeness.md +1 -1
  46. package/skills/spec-evaluator/references/dimensions/integration.md +98 -67
  47. package/skills/spec-evaluator/references/dimensions/spec-conformance.md +4 -4
  48. package/skills/spec-evaluator/references/dimensions/test-surface-conformance.md +1 -1
  49. package/skills/spec-evaluator/references/probing.md +42 -23
  50. package/skills/spec-evaluator/references/report-schema.md +2 -2
  51. package/skills/spec-evaluator/scripts/verdict-ledger.mjs +1 -1
  52. package/skills/task-executor/SKILL.md +25 -12
  53. package/skills/tech-lead/SKILL.md +115 -438
  54. package/skills/tech-lead/references/delegation.md +38 -31
  55. package/skills/tech-lead/references/gates.md +75 -33
  56. package/skills/tech-lead/references/hard-rules.md +34 -0
  57. package/skills/tech-lead/references/invocation.md +2 -2
  58. package/skills/tech-lead/references/ledger-schema.md +17 -18
  59. package/skills/tech-lead/references/round-protocol.md +77 -27
  60. package/skills/tech-lead/references/state-model.md +1 -1
  61. package/skills/tech-lead/schemas/domain.schema.json +320 -148
  62. package/skills/tech-lead/schemas/gate-answers.schema.json +2 -2
  63. package/skills/tech-lead/schemas/work-result.schema.json +55 -17
  64. package/skills/tech-lead/scripts/aegis-digest.mjs +1 -1
  65. package/skills/tech-lead/scripts/budget-check.mjs +7 -13
  66. package/skills/tech-lead/scripts/compile-order.mjs +69 -22
  67. package/skills/tech-lead/scripts/fit-check.mjs +10 -10
  68. package/skills/tech-lead/scripts/gate-answers.mjs +11 -12
  69. package/skills/tech-lead/scripts/hill-derive.mjs +133 -0
  70. package/skills/tech-lead/scripts/ingest-result.mjs +7 -19
  71. package/skills/tech-lead/scripts/init-run.mjs +60 -18
  72. package/skills/tech-lead/scripts/lib/argv.mjs +1 -1
  73. package/skills/tech-lead/scripts/lib/contract-md.mjs +10 -10
  74. package/skills/tech-lead/scripts/lib/is-main.mjs +11 -12
  75. package/skills/tech-lead/scripts/lib/paths.mjs +15 -13
  76. package/skills/tech-lead/scripts/resume-state.mjs +387 -0
  77. package/skills/tech-lead/scripts/run-snapshot.mjs +1 -6
  78. package/skills/tech-lead/scripts/run-workflow.mjs +381 -0
  79. package/skills/tech-lead/scripts/ship-report.mjs +33 -2
  80. package/skills/tech-lead/scripts/stats.mjs +9 -10
  81. package/skills/tech-lead/scripts/t0-verify.mjs +4 -4
  82. package/skills/tech-lead/scripts/trace-lint.mjs +2 -2
  83. package/skills/tech-lead/workflows/shapeup-run.js +911 -0
  84. package/skills/translator/SKILL.md +2 -2
  85. package/skills/advisor-protocol/SKILL.md +0 -171
@@ -1,18 +1,17 @@
1
1
  #!/usr/bin/env node
2
2
  // GATE L0.1 — START THE RUN. The orchestrator's first tool call, before any prose.
3
3
  //
4
- // WHY THIS EXISTS (measured, not theorized).
4
+ // WHY THIS EXISTS (reproduced, not theorized).
5
5
  //
6
- // On the SDD harness benchmark (`sdd-harness-bench`, F2, Haiku 4.5, n=5, zero variance) the
7
- // orchestrator was dispatched with a valid spec and did this:
6
+ // Dispatched with a valid spec, the orchestrator did this:
8
7
  //
9
- // TOOL Skill(tech-lead, "--unattended --rounds 3\n\n# F2 — category budgets…")
8
+ // TOOL Skill(tech-lead, "--unattended --rounds 3\n\n# the requirement…")
10
9
  // TEXT "The tech-lead skill is orchestrating the full Shape Up harness. It will: 1. …"
11
10
  // FINAL (same text — session ends)
12
11
  //
13
- // It loaded a 450-line instruction file describing eleven gates and returned a description of
14
- // eleven gates. No code, no board, no gate artifacts — and prose that reads exactly like a
15
- // successful run. 29% acceptance, 10 escaped defects, five times out of five.
12
+ // It loaded an instruction file describing eleven gates and returned a description of eleven
13
+ // gates. No code, no board, no gate artifacts — and prose that reads exactly like a successful
14
+ // run, with every defect still in the deliverable.
16
15
  //
17
16
  // Two guards existed and neither could see it:
18
17
  // • `gate-intake.mjs` (L0.0) fires on an EMPTY intake. Intake was valid here. Correct no-op.
@@ -34,10 +33,10 @@
34
33
  // assumed someone had established. Previously that someone was the model, deciding to.
35
34
  // An invariant that depends on the model choosing to establish it is a prompt, not a gate.
36
35
  //
37
- // It also HASHES the intake into the receipt. The benchmark's first (wrong) diagnosis was that
38
- // requirement text was dropped on the hand-off. It was not, on re-run — but nothing on disk
39
- // could have settled that either way. Now it can: the intake that reached the orchestrator is
40
- // recorded verbatim next to its digest, so "the spec was dropped" is checkable, not arguable.
36
+ // It also HASHES the intake into the receipt. The first (wrong) diagnosis of the failure above was
37
+ // that requirement text had been dropped on the hand-off. It had not — but nothing on disk could
38
+ // settle that either way. Now it can: the intake that reached the orchestrator is recorded
39
+ // verbatim next to its digest, so "the spec was dropped" is checkable, not arguable.
41
40
  //
42
41
  // USAGE
43
42
  // node init-run.mjs --slug <slug> --intake-file <path> [options] <- prefer this
@@ -46,13 +45,14 @@
46
45
  //
47
46
  // PREFER --intake-file. A multi-line requirement inlined into a shell argument is where this step
48
47
  // goes wrong: quoting breaks, a `#` after a newline trips path validation, and the run spends six
49
- // turns fighting its own command line instead of starting. Measured, on this project's benchmark.
48
+ // turns fighting its own command line instead of starting. Observed, repeatedly.
50
49
  //
51
50
  // --auto-level interactive | auto | unattended (default: interactive)
52
51
  // --lens lite | standard | cross-context (default: standard)
53
52
  // --max-rounds N outer circuit breaker (default: 3)
54
53
  // --attempts N inner per-scope T0 budget (default: 5)
55
54
  // --spec-folder SHARED spec deliverable path (default: shapeup/<slug>/spec/)
55
+ // --dimensions comma-separated eval dimensions (default: spec-conformance)
56
56
  // --gate-answers path | preset name (see gate-answers.mjs; recorded, not read)
57
57
  // --wall-clock-budget N deadline breaker, seconds (off by default; see budget-check.mjs)
58
58
  // --cwd project root (default: process.cwd())
@@ -74,6 +74,7 @@ import { createHash } from "node:crypto";
74
74
  import { decideLane, treeSize } from "./fit-check.mjs";
75
75
  import { isMain } from "./lib/is-main.mjs";
76
76
  import { runArgs } from "./lib/argv.mjs";
77
+ import { uncoerce } from "./lib/contract-md.mjs";
77
78
  import { deriveSnapshot } from "./run-snapshot.mjs";
78
79
  import { localRoot, activeScope, globLocal, globShared } from "./lib/paths.mjs";
79
80
 
@@ -82,6 +83,35 @@ export const RECEIPT_VERSION = 1;
82
83
  const AUTO_LEVELS = new Set(["interactive", "auto", "unattended"]);
83
84
  const LENSES = new Set(["lite", "standard", "cross-context"]);
84
85
 
86
+ /**
87
+ * The eval dimension set when the caller names none. Kept to the base correctness dimension so an
88
+ * unconfigured run behaves exactly as it did before this flag existed.
89
+ */
90
+ export const DEFAULT_DIMENSIONS = ["spec-conformance"];
91
+
92
+ /**
93
+ * Parse `--dimensions` into the set written to the ledger. Shape-validated only, NOT checked against
94
+ * the dimensions that ship: adding `references/dimensions/<id>.md` and naming it here is the
95
+ * documented injection path, so a closed list here would make the evaluator's own extension point
96
+ * unreachable. An id with no file behind it is skipped-with-a-warning at dimension resolution, which
97
+ * is where that check belongs and where it can actually see the files.
98
+ *
99
+ * @param {(string|null|undefined)} raw - The comma-separated flag value; absent → the default set.
100
+ * @returns {string[]} Trimmed, de-duplicated ids in the caller's order.
101
+ * @throws {Error} If the list is empty or an entry is not a kebab-case id.
102
+ */
103
+ export function parseDimensions(raw) {
104
+ if (raw === null || raw === undefined) return [...DEFAULT_DIMENSIONS];
105
+ const ids = String(raw).split(",").map((s) => s.trim()).filter(Boolean);
106
+ if (!ids.length) throw new Error("--dimensions: empty list — omit the flag to use the default [spec-conformance]");
107
+ for (const id of ids) {
108
+ if (!/^[a-z][a-z0-9]*(-[a-z0-9]+)*$/.test(id)) {
109
+ throw new Error(`--dimensions: "${id}" is not a dimension id (kebab-case, e.g. spec-conformance, tdd-surface, integration)`);
110
+ }
111
+ }
112
+ return [...new Set(ids)];
113
+ }
114
+
85
115
  /** Slugify a free-text feature name into a filesystem-safe run id. */
86
116
  export function slugify(text) {
87
117
  return String(text || "")
@@ -124,7 +154,10 @@ export function runFrontmatter({ slug, config, startedAt }) {
124
154
  `feature: ${slug}`,
125
155
  `spec_folder: ${config.spec_folder}`,
126
156
  `lens: ${config.lens}`,
127
- "eval_dimensions: [spec-conformance]",
157
+ // The ledger is the ONE place the dimension set lives: resume-state reads it back off this line
158
+ // and the workflow hands it to every evaluate order. Written through the same list dialect the
159
+ // parser reads (contract-md), never hand-joined.
160
+ `eval_dimensions: ${uncoerce(config.eval_dimensions ?? DEFAULT_DIMENSIONS)}`,
128
161
  `max_rounds: ${config.max_rounds}`,
129
162
  `attempt_budget: ${config.attempt_budget}`,
130
163
  `wall_clock_budget_s: ${config.wall_clock_budget_s ?? "~"}`,
@@ -164,7 +197,8 @@ export function runFrontmatter({ slug, config, startedAt }) {
164
197
  export const ARGV_SPEC = {
165
198
  usage: 'init-run.mjs (--intake-file <path> | --intake-text "<req>" | --intake-stdin) ' +
166
199
  "[--slug <slug>] [--auto-level interactive|auto|unattended] [--lens <lens>] " +
167
- "[--max-rounds N] [--attempts N] [--spec-folder <dir>] [--gate-answers <preset|path>] " +
200
+ "[--max-rounds N] [--attempts N] [--spec-folder <dir>] [--dimensions <a,b>] " +
201
+ "[--gate-answers <preset|path>] " +
168
202
  "[--lane full|tiny] [--tiny] [--wall-clock-budget <seconds>] [--cwd <dir>] [--force]",
169
203
  _: { arity: 0, max: 0, name: "(no positional operands)" },
170
204
  cwd: { type: "path" },
@@ -177,6 +211,7 @@ export const ARGV_SPEC = {
177
211
  "max-rounds": { type: "int", min: 1 },
178
212
  attempts: { type: "int", min: 1 },
179
213
  "spec-folder": { type: "path" },
214
+ dimensions: { type: "str" },
180
215
  "gate-answers": { type: "str" },
181
216
  lane: { type: "str" },
182
217
  tiny: { type: "flag" },
@@ -220,10 +255,17 @@ export function main() {
220
255
  if (!AUTO_LEVELS.has(auto_level)) fail(2, `--auto-level must be one of: ${[...AUTO_LEVELS].join(", ")}`);
221
256
  const lens = args.lens ?? "standard";
222
257
  if (!LENSES.has(lens)) fail(2, `--lens must be one of: ${[...LENSES].join(", ")}`);
258
+ // GATE L0.5 — the eval dimension set, resolved ONCE here and carried by the ledger. Before this
259
+ // flag the line was a constant, so a dimension the PO asked for at L0.5 had nowhere to be
260
+ // recorded and the run graded spec-conformance whatever the answer had been.
261
+ let eval_dimensions;
262
+ try { eval_dimensions = parseDimensions(args.dimensions ?? null); }
263
+ catch (e) { fail(2, e.message); }
223
264
 
224
265
  const config = {
225
266
  auto_level,
226
267
  lens,
268
+ eval_dimensions,
227
269
  max_rounds: args.maxRounds ?? 3,
228
270
  attempt_budget: args.attempts ?? 5,
229
271
  spec_folder: args.specFolder ?? `${globShared(slug, "spec")}/`,
@@ -242,7 +284,7 @@ export function main() {
242
284
  })(),
243
285
  // The third breaker (see scripts/budget-check.mjs). Null = off, which is the default and
244
286
  // keeps every existing run behaving exactly as before. Set it in any lane with a hard clock
245
- // — CI, a benchmark, an overnight run — so the harness trips its own breaker and ships what
287
+ // — CI, an overnight run — so the harness trips its own breaker and ships what
246
288
  // is green, instead of being killed from outside and shipping nothing.
247
289
  wall_clock_budget_s: args.wallClockBudget ?? null,
248
290
  };
@@ -258,9 +300,9 @@ export function main() {
258
300
  // step, the runtime named a mechanism that does not exist, on a script whose failure mode was
259
301
  // already invisible (see lib/is-main.mjs — under a symlinked install this whole body did not run).
260
302
  //
261
- // Measured consequence, on the SDD harness benchmark's F4 handoff: a fresh session in a workspace
262
- // with an open run spent 82–120 turns before its first write, largely on forensics against this
263
- // step, and closed 0/3 of the gap.
303
+ // Observed consequence on a handoff: a fresh session in a workspace with an open run burns most
304
+ // of its budget before its first write, largely on forensics against this step, and closes none
305
+ // of the gap.
264
306
  //
265
307
  // So the refusal now DOES the resume work instead of describing it. It emits the derived snapshot
266
308
  // — the same file-only derivation `hooks/session-rehydrate.mjs` injects — so the orchestrator gets
@@ -37,7 +37,7 @@
37
37
  // rewrite call sites. Unknown flags are rejected rather than silently swallowed as positionals:
38
38
  // a typo'd `--rounds 2` that lands in `_` is the same defect wearing a different hat.
39
39
  //
40
- // `tests/structural/13-argv-contract.mjs` executes every entry point with each declared int flag
40
+ // The structural suite executes every entry point with each declared int flag
41
41
  // both empty and non-numeric, and asserts exit 2 with a parseable reason and no artifact on disk.
42
42
 
43
43
  /** Thrown by {@link parseArgs} when argv does not satisfy the spec. `detail` is the wire shape. */
@@ -38,7 +38,7 @@ import { join } from "node:path";
38
38
  // Per-type layout: which array-of-objects field lives under which heading.
39
39
  //
40
40
  // `signatures` names the columns that identify a table as THAT field's, and it exists because of
41
- // HD-001: the heading match is exact, so a table written under `# Wiring map — <slug>` instead of
41
+ // The heading match is exact, so a table written under `# Wiring map — <slug>` instead of
42
42
  // `## Wiring` parsed as ABSENT, and every reader downstream treats absent as "none declared". The
43
43
  // observed consequence was `trace-lint` reporting `🟢 green · 0/0 engines reach src/cli/main.js`
44
44
  // for a committed map holding six correct rows — the gate whose whole purpose is that no engine
@@ -91,7 +91,7 @@ export const UNREADABLE = "$unreadable_tables";
91
91
  */
92
92
  export function coerce(raw) {
93
93
  let trimmed = String(raw ?? "").trim();
94
- // HD-005 — A MARKDOWN CODE SPAN IS FORMATTING, NOT PART OF THE VALUE.
94
+ // A MARKDOWN CODE SPAN IS FORMATTING, NOT PART OF THE VALUE.
95
95
  //
96
96
  // These contracts are MARKDOWN on disk, and a code span is the idiomatic way to write a path or
97
97
  // an identifier in one; this repo's own prose backticks every path it names. Read literally,
@@ -99,7 +99,7 @@ export function coerce(raw) {
99
99
  // anywhere — so `trace-lint` reported "engine file not on disk" and then "reachability is not
100
100
  // demonstrated" for a wiring map whose engines all resolve AND all reach the entry point. The
101
101
  // gate that exists so no engine ships orphaned failing CLOSED, on a correct map, is the same
102
- // silent-format family as HD-001..HD-004 arriving from the other direction.
102
+ // silent-format family as the parser defects above, arriving from the other direction.
103
103
  //
104
104
  // Stripped ONLY when the whole value is a single span: a cell like
105
105
  // "Registered as the `add` entry in `TABLE`" is prose that happens to contain spans, and its
@@ -125,7 +125,7 @@ export function coerce(raw) {
125
125
  */
126
126
  export function uncoerce(v) {
127
127
  if (v === null || v === undefined) return "~";
128
- // HD-002's other half. A member containing the delimiter must go back out QUOTED, or the round
128
+ // The other half of the comma defect. A member containing the delimiter must go back out QUOTED, or the round
129
129
  // trip that wrote it re-reads as several members — the same shredding, arriving from the writer's
130
130
  // side instead of the reader's.
131
131
  if (Array.isArray(v)) return `[${v.map((x) => (/[,"]/.test(String(x)) ? JSON.stringify(String(x)) : String(x))).join(", ")}]`;
@@ -135,7 +135,7 @@ export function uncoerce(v) {
135
135
  /**
136
136
  * Split a `[a, b]` list body on commas that are NOT inside quotes.
137
137
  *
138
- * HD-002. The old implementation was `body.split(",")`, and it shredded any member carrying a
138
+ * The old implementation was `body.split(",")`, and it shredded any member carrying a
139
139
  * comma — even a correctly quoted one. Measured: a `scope-architect` run probed the running CLI,
140
140
  * confirmed `tag` was unimplemented, and wrote the honest entry its own SKILL.md asks for —
141
141
  * ["TBD — `tag` is not in dispatch.js's TABLE (exits 1, confirmed against the running CLI). A
@@ -191,12 +191,12 @@ export function splitFrontmatter(md) {
191
191
  const key = line.slice(0, c).trim();
192
192
  const inline = line.slice(c + 1).trim();
193
193
 
194
- // HD-003. An indented run beneath a key is a YAML BLOCK SEQUENCE, and it used to be skipped
194
+ // An indented run beneath a key is a YAML BLOCK SEQUENCE, and it used to be skipped
195
195
  // entirely — so `e2e_verification_fixtures:` followed by two `- "node …"` lines parsed to
196
196
  // null, the members vanished, and no reader could tell "declared nothing" from "declared
197
197
  // something I discarded". Measured: a scope-architect run wrote three scopes of researched
198
198
  // fixtures in this form and every one evaporated. It is ACCEPTED now, because it is the form a
199
- // model reaches for by default and the one that carries prose members without HD-002's quoting
199
+ // model reaches for by default and the one that carries prose members without the quoting
200
200
  // problem — and anything indented that is NOT a block sequence is reported rather than dropped.
201
201
  const block = [];
202
202
  let stray = 0;
@@ -322,13 +322,13 @@ export function parseContract(md, spec = SCOPE_CONTRACT) {
322
322
  const { meta, body } = splitFrontmatter(md);
323
323
  const tables = parseTables(body);
324
324
  const out = { ...meta };
325
- // Frontmatter-level diagnostics (HD-003) and table-level ones (HD-001) share one channel, so a
325
+ // Frontmatter-level diagnostics and table-level ones share one channel, so a
326
326
  // reader asks `unreadableReason()` once and cannot check for one while missing the other.
327
327
  const unreadable = [...(meta[UNREADABLE] || [])];
328
328
  delete out[UNREADABLE];
329
329
  for (const [field, heading] of Object.entries(spec.tables || {})) {
330
330
  if (tables[heading]) { out[field] = tables[heading]; continue; }
331
- // HD-001. The field is absent — but is it absent because nobody declared it, or because the
331
+ // The field is absent — but is it absent because nobody declared it, or because the
332
332
  // author declared it somewhere this parser does not look? Those are opposite facts and the
333
333
  // old code returned the same thing for both. A table carrying this field's signature columns,
334
334
  // under a heading the spec does not claim, is the second case.
@@ -351,7 +351,7 @@ export function parseContract(md, spec = SCOPE_CONTRACT) {
351
351
  * The one-line reason a contract could not be read, or null when it read cleanly.
352
352
  *
353
353
  * Exported so every consumer asks the same question the same way. A reader that skips this is
354
- * back to treating "I could not see your table" as "you declared no table", which is HD-001.
354
+ * back to treating "I could not see your table" as "you declared no table" the whole defect class.
355
355
  * @param {Object|null} contract - A parsed contract (or a `readContract()` result's `.contract`).
356
356
  * @returns {string|null} A human-readable failure, or null if the contract parsed cleanly.
357
357
  */
@@ -1,6 +1,6 @@
1
1
  // isMain — "was this module executed directly, or imported?"
2
2
  //
3
- // WHY THIS FILE EXISTS (measured on the SDD harness benchmark, not theorized).
3
+ // WHY THIS FILE EXISTS (observed, not theorized).
4
4
  //
5
5
  // Eighteen of this plugin's scripts and hooks decided whether to do anything at all with:
6
6
  //
@@ -12,22 +12,21 @@
12
12
  //
13
13
  // 1. A SYMLINKED DIRECTORY ANYWHERE IN THE PATH. Node resolves `import.meta.url` through
14
14
  // symlinks; `process.argv[1]` is the string as typed. On macOS `/var` is a symlink to
15
- // `/private/var`, so EVERY path under the system temp directory mismatches — which is how
16
- // the benchmark installs this plugin (`/var/folders/…/package`). nvm, pnpm's content store,
15
+ // `/private/var`, so EVERY path under the system temp directory mismatches — which is where
16
+ // an npm-packed install lands (`/var/folders/…/package`). nvm, pnpm's content store,
17
17
  // Homebrew and any symlinked checkout do the same thing on every platform.
18
18
  //
19
19
  // 2. A SPACE OR OTHER URL-RESERVED CHARACTER IN THE PATH. `import.meta.url` is percent-encoded
20
20
  // (`My%20Plugins`); the template literal is not (`My Plugins`). So a plugin installed under
21
21
  // `~/Library/Application Support/…` or any directory with a space in its name mismatches too.
22
22
  //
23
- // WHAT THAT COST, measured. `init-run.mjs` is GATE L0.1 — the orchestrator's mandatory first tool
24
- // call, the script that writes the run receipt everything else is derived from. Under a `/var`
25
- // path it exited 0 with empty stdout and wrote no receipt. The orchestrator could not distinguish
26
- // "the run opened" from "nothing happened", and in the benchmark's F4 handoff rows it spent
27
- // 82–120 turns before its first write doing forensics on its own bootstrap retrying the script
28
- // six ways, hitting five separate permission refusals trying to capture an exit code, and finally
29
- // running `find /` to look for its own skill. Session B cost $4.57–$10.36 and recovered 0/3 of
30
- // the gap while the artifact it needed sat on disk the entire time.
23
+ // WHAT THAT COSTS. `init-run.mjs` is GATE L0.1 — the orchestrator's mandatory first tool call, the
24
+ // script that writes the run receipt everything else is derived from. Under a `/var` path it
25
+ // exited 0 with empty stdout and wrote no receipt. The orchestrator then cannot distinguish "the
26
+ // run opened" from "nothing happened", and burns its budget on forensics against its own
27
+ // bootstrap retrying the script several ways, hitting permission refusals trying to capture an
28
+ // exit code, and finally searching the filesystem for its own skill while the artifact it needs
29
+ // sits on disk the entire time.
31
30
  //
32
31
  // The same guard sits in seven hooks, including `gate-zerowork`, `safety-spine` and
33
32
  // `sandbox-guard`. This project's stated organising idea is that "every invariant that matters
@@ -36,7 +35,7 @@
36
35
  // an enforcement layer can have, because it is indistinguishable from working.
37
36
  //
38
37
  // THE FIX. Compare resolved URL to resolved URL, using `pathToFileURL` so encoding matches and
39
- // `realpathSync` so symlinks match. `tests/structural/11-is-main.mjs` asserts that no file
38
+ // `realpathSync` so symlinks match. The structural suite asserts that no file
40
39
  // reintroduces the fragile form, and actually executes each entry point through a symlinked
41
40
  // directory AND through a directory with a space in its name to prove the guard holds — because
42
41
  // this defect was invisible to every test that invoked scripts by their real path.
@@ -2,17 +2,18 @@
2
2
  //
3
3
  // WHY THIS FILE EXISTS (measured by grepping the shipped tree, not theorized).
4
4
  //
5
- // The two storage roots were hard-coded in ~90 files, in TWO syntaxes that no single search finds:
5
+ // The two storage roots were hard-coded across the tree in TWO syntaxes that no single search
6
+ // finds:
6
7
  //
7
- // "docs/shapeup-sdlc/<slug>/scopes/..." 568 string literals
8
- // join(cwd, "docs", "shapeup-sdlc", slug, ...) 48 segment-built sites
8
+ // "docs/shapeup-sdlc/<slug>/scopes/..." spelled string literals
9
+ // join(cwd, "docs", "shapeup-sdlc", slug, ...) segment-built sites
9
10
  //
10
11
  // A find/replace over the first set leaves the second silently pointing at the old root. That is
11
12
  // the failure mode this project keeps rediscovering: a change that appears complete, produces no
12
13
  // error, and is wrong — `lib/is-main.mjs` (a guard duplicated 18 times, inert under a symlink) and
13
14
  // `lib/argv.mjs` (`rNaN-a1.json` written with exit 0) are the same defect at different layers. The
14
15
  // remedy each time is to give the duplicated thing one home and add a test that no one may bypass
15
- // it (`tests/structural/45-paths.mjs`, mirroring #11a).
16
+ // it — asserted by the structural suite.
16
17
  //
17
18
  // It also removes a live ambiguity. `gate-answers.mjs` resolved three candidate paths and
18
19
  // `sandbox-guard.mjs` built a fourth independently; the same filename meant "my personal lane" at
@@ -21,7 +22,7 @@
21
22
  // TIER DISCIPLINE, restated here because this is where it becomes mechanical:
22
23
  // SHARED (committed) — prose a teammate reads: shaping, spec, contracts, requirements, report.
23
24
  // LOCAL (gitignored) — run state, envelopes, verification artifacts, machine policy.
24
- // See docs/design/adr/0001-consumer-file-organization.md.
25
+ // See ADR-0001 (consumer file organization).
25
26
  //
26
27
  // Zero dependencies. Pure — every function takes `cwd` and returns a path; nothing here touches
27
28
  // the filesystem, so importing this module can never have a side effect.
@@ -122,10 +123,11 @@ export const report = (cwd, slug) => join(sharedRoot(cwd, slug), "REPORT.md");
122
123
  export const knowledgeBaseDir = (cwd) => join(sharedDir(cwd), "knowledge-base");
123
124
  /** One worker's coaching file. */
124
125
  export const knowledgeBase = (cwd, skill) => join(knowledgeBaseDir(cwd), `${skill}.md`);
125
- /** Migration bookkeeping committed by necessity (ADR-0001 exception 3). */
126
- export const harnessVersion = (cwd) => join(sharedDir(cwd), ".harness-version");
127
- /** Applied-migration record committed by necessity (ADR-0001 exception 3). */
128
- export const harnessMigrations = (cwd) => join(sharedDir(cwd), ".harness-migrations");
126
+ // `.harness-version` and `.harness-migrations` were resolved here, committed by necessity as
127
+ // ADR-0001's third exception. Their sole writer was the upgrade path's data-migration runner, which
128
+ // has been removed; no code in the plugin has written either file since. The helpers went with
129
+ // them rather than remaining as resolvable paths to files nothing produces — a path helper is a
130
+ // claim that the file is part of the layout, and this one would have been false.
129
131
 
130
132
  // ---------------------------------------------------------------------------
131
133
  // LOCAL — run state
@@ -167,8 +169,6 @@ export const spikesDir = (cwd, slug) => join(localRoot(cwd, slug), "spikes");
167
169
  export const traceDir = (cwd, slug) => join(localRoot(cwd, slug), "trace");
168
170
  /** The discovered-task ledger every discovery flow appends to. */
169
171
  export const discoveryLedger = (cwd, slug) => join(localRoot(cwd, slug), "discovery", "ledger.md");
170
- /** Queued worker escalations awaiting adjudication. */
171
- export const escalatesDir = (cwd, slug) => join(localRoot(cwd, slug), "escalates");
172
172
  /**
173
173
  * Adjudicated decisions, read back by `compile-order` as binding precedent.
174
174
  *
@@ -191,6 +191,8 @@ export const workingDir = (cwd, slug) => join(localRoot(cwd, slug), "working");
191
191
 
192
192
  /** The pointer the sandbox guard reads to answer "which scope is checked out?". */
193
193
  export const activeScope = (cwd) => join(localDir(cwd), "active-scope");
194
+ /** The pointer the sandbox guard reads to answer "which order is executing?". */
195
+ export const activeOrder = (cwd) => join(localDir(cwd), "active-order");
194
196
  /** Hook receipts — one row per evaluation, so `allow` carries evidence. */
195
197
  export const decisions = (cwd) => join(localDir(cwd), "decisions.jsonl");
196
198
  /** Human-authored safety escape hatch. LOCAL so no PR can widen another machine's envelope. */
@@ -266,13 +268,13 @@ export const globLocal = (slug, ...parts) => [LOCAL, slug, ...parts].join("/");
266
268
  * A deliverable glob, POSIX-separated for the same reason as {@link globLocal}.
267
269
  * @param {string} slug - Feature slug.
268
270
  * @param {...string} parts - Glob segments under the feature's shared root.
269
- * @returns {string} e.g. `docs/shapeup-sdlc/checkout/scopes/*.json`.
271
+ * @returns {string} e.g. `shapeup/checkout/scopes/*.md`.
270
272
  */
271
273
  export const globShared = (slug, ...parts) => [SHARED, slug, ...parts].join("/");
272
274
 
273
275
  /**
274
276
  * The coaching file a coachable worker reads, as a repo-relative path for the WorkOrder payload.
275
277
  * @param {string} skill - Worker name (task-executor | ba-pitch-analyzer | qa-edge-hunter).
276
- * @returns {string} e.g. `docs/shapeup-sdlc/knowledge-base/task-executor.md`.
278
+ * @returns {string} e.g. `shapeup/knowledge-base/task-executor.md`.
277
279
  */
278
280
  export const relKnowledgeBase = (skill) => [SHARED, "knowledge-base", `${skill}.md`].join("/");