@tekyzinc/gsd-t 4.0.28 → 4.0.29

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/CHANGELOG.md CHANGED
@@ -2,6 +2,21 @@
2
2
 
3
3
  All notable changes to GSD-T are documented here. Updated with each release.
4
4
 
5
+ ## [4.0.29] - 2026-06-05 (M81 Workflows Runtime-Native - patch)
6
+
7
+ ### Fixed - TD-113: 6 of 7 workflows (+ quick) crashed in the Workflow sandbox and had never run
8
+
9
+ The GSD-T self-scan (Scan #12) and a live NiceNote session both confirmed it: every `*.workflow.js` except `gsd-t-scan` opened with `require("./_lib.js")`, which the Anthropic Workflow sandbox forbids (it provides only `agent/parallel/pipeline/log/phase/budget/args` — no `require`/`fs`/`path`/`child_process`/`process`). Each threw `ReferenceError` on first eval, so the entire orchestration layer — `execute`, `verify`, `wave`, `integrate`, `debug`, `phase`, `quick` — silently fell back to hand-driven runs and never actually executed as workflows.
10
+
11
+ Ported all 7 to the runtime-native pattern proven on scan in M71/M80: inline `async` helpers that delegate each CLI call (preflight, verify-gate, brief, build-coverage, ci-parity, test-data, parallel/disjointness) to an `agent()`'s Bash — preferring project-local `bin/<tool>.cjs`, falling back to the global `gsd-t` PATH binary — and parse the JSON envelope. `args` is now `JSON.parse`d (it arrives stringified). File reads moved into the agents that have `Read` (worker reads its own scope.md/tasks.md; triad agents read their own protocol from `templates/prompts/`). `verify`'s raw `spawnSync`/`require` CI-parity block and `Date.now()` run-id were replaced; the M57/M58 FAIL-blocking semantics are unchanged.
12
+
13
+ - `templates/workflows/gsd-t-{execute,verify,wave,integrate,debug,phase,quick}.workflow.js`: runtime-native port.
14
+ - `test/m71-workflow-runtime-native-lint.test.js`: lint now covers all 8 workflows (was scan-only).
15
+ - `test/m81-workflows-runtime-native.test.js`: structural invariants (no `_lib` require, args-string parse, no `spawnSync`/`Date.now`/`Math.random` in orchestrator, FAIL-blocking gates preserved).
16
+ - `CLAUDE.md`, `~/.claude/CLAUDE.md` + `templates/CLAUDE-global.md`: documented the runtime-native invariant; retired `_lib.js` as a workflow dependency.
17
+
18
+ Proven in the REAL sandbox: `quick` ran end-to-end (verify-gate PASS), `verify` evaluated through its CLI delegations returning a real verify-gate envelope, `execute` evaluated cleanly to its arg-guard — all with zero ReferenceError. Suite 1341/1341 pass.
19
+
5
20
  ## [4.0.28] - 2026-06-04 (M80 Scan Document-Phase Fixes - patch)
6
21
 
7
22
  ### Fixed - scan workflow crashed at the document phase, then shipped a truncated plain-English doc
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tekyzinc/gsd-t",
3
- "version": "4.0.28",
3
+ "version": "4.0.29",
4
4
  "description": "GSD-T: Contract-Driven Development for Claude Code — 54 slash commands with headless-by-default workflow spawning, unattended supervisor relay with event stream, graph-powered code analysis, real-time agent dashboard, task telemetry, doc-ripple enforcement, backlog management, impact analysis, test sync, milestone archival, and PRD generation",
5
5
  "author": "Tekyz, Inc.",
6
6
  "license": "MIT",
@@ -331,7 +331,7 @@ Canonical scripts:
331
331
  - `gsd-t-phase.workflow.js` — generic upper-stage runner (partition / plan / discuss / impact / milestone / prd / design-decompose / doc-ripple)
332
332
  - `gsd-t-scan.workflow.js` — preflight → volume-probe → pipeline(per-slice deep finder → single verify) → synthesis → document → render (M66: fans out by codebase VOLUME, not a fixed 5-teammate dimension count; M67: deep document phase deterministically produces the full living-doc set + dimension files, per-doc fan-out)
333
333
 
334
- Shared helpers: `templates/workflows/_lib.js` — `runPreflight`, `generateBrief`, `proveFileDisjointness`, `runVerifyGate`, `loadProtocol`, `readDomainTasks`, `readScope`. Each prefers project-local `bin/<tool>.cjs` and falls back to global `gsd-t` PATH binary (preserves M55-D5 project-local-bin invariant).
334
+ **Runtime-native invariant (M81 — v4.0.29+):** the Workflow sandbox provides ONLY `agent/parallel/pipeline/log/phase/budget/args` — NO `require`/`fs`/`path`/`child_process`/`process`, and `args` arrives as a JSON STRING. Each workflow is self-contained: it `JSON.parse`s `args` and delegates every CLI call (preflight, verify-gate, brief, build-coverage, ci-parity, test-data, disjointness) to inline `async` helpers that run the command via an `agent()`'s Bash (preferring project-local `bin/<tool>.cjs`, else the global `gsd-t` PATH binary) and parse the JSON envelope — preserving the M55-D5 project-local-bin invariant. The old `require("./_lib.js")` pattern threw `ReferenceError` on first eval and silently broke every workflow except scan (TD-113, fixed M81); `_lib.js` is retired as a workflow dependency.
335
335
 
336
336
  ## Preflight Gate (KEPT from M55)
337
337
 
@@ -16,10 +16,39 @@ export const meta = {
16
16
  ],
17
17
  };
18
18
 
19
- const lib = require("./_lib.js");
19
+ // M81: runtime-native helpers (sandbox bans require/fs/child_process/process — the old
20
+ // require("./_lib.js") crashed this workflow on first eval, TD-113). Delegate CLI calls
21
+ // to an agent's Bash; args arrives as a JSON STRING in this runtime. See gsd-t-scan.workflow.js.
22
+ const _args = (typeof args === "string") ? (() => { try { return JSON.parse(args); } catch { return {}; } })() : (args || {});
23
+ const _CLI_ENVELOPE_SCHEMA = {
24
+ type: "object", required: ["ok", "exitCode"], additionalProperties: true,
25
+ properties: { ok: { type: "boolean" }, exitCode: { type: "integer" }, envelope: {}, stdout: { type: "string" }, stderr: { type: "string" }, via: { type: "string" } },
26
+ };
27
+ async function runCli(projectDir, subcmd, argv, localBin, label, parseJson = true, phaseName) {
28
+ const argStr = (argv || []).map((a) => `'${String(a).replace(/'/g, "'\\''")}'`).join(" ");
29
+ const prompt = [
30
+ `Run a GSD-T CLI command for the project at \`${projectDir}\` and report the result. Steps:`,
31
+ `1. If \`${projectDir}/bin/${localBin}\` exists, run: \`node ${projectDir}/bin/${localBin} ${argStr}\` (set via="local"). Otherwise run: \`gsd-t ${subcmd} ${argStr}\` (set via="global"). Use cwd \`${projectDir}\`.`,
32
+ `2. Capture exit code (ok = exitCode 0) and stdout/stderr.`,
33
+ parseJson ? `3. Parse stdout as JSON into \`envelope\` (null if not JSON). Return JSON per the schema.` : `3. Put stdout (trimmed, ≤4000 chars) in \`stdout\`. Return JSON per the schema.`,
34
+ `Do NOT do any other work. ONLY run this one command and report.`,
35
+ ].join("\n");
36
+ const opts = { label, schema: _CLI_ENVELOPE_SCHEMA, model: "haiku" };
37
+ if (phaseName) opts.phase = phaseName;
38
+ const r = await agent(prompt, opts).catch((e) => ({ ok: false, exitCode: -1, envelope: null, stderr: String(e && e.message), via: "error" }));
39
+ return r || { ok: false, exitCode: -1, envelope: null, via: "error" };
40
+ }
41
+ async function runPreflight(projectDir, label = "preflight", phaseName) { return runCli(projectDir, "preflight", ["--json"], "cli-preflight.cjs", label, true, phaseName); }
42
+ async function generateBrief(projectDir, { kind = "execute", milestone, domain, id, label = "brief", phaseName } = {}) {
43
+ const argv = ["--kind", kind, "--spawn-id", id, "--out", `${projectDir}/.gsd-t/briefs/${id}.json`];
44
+ if (milestone) argv.push("--milestone", milestone);
45
+ if (domain) argv.push("--domain", domain);
46
+ const r = await runCli(projectDir, "brief", argv, "gsd-t-context-brief.cjs", label, false, phaseName);
47
+ return { ok: r.ok, briefPath: `${projectDir}/.gsd-t/briefs/${id}.json`, via: r.via };
48
+ }
20
49
 
21
- const projectDir = (args && args.projectDir) || ".";
22
- const symptom = (args && args.symptom) || null;
50
+ const projectDir = _args.projectDir || ".";
51
+ const symptom = _args.symptom || null;
23
52
 
24
53
  const DEBUG_CYCLE_SCHEMA = {
25
54
  type: "object",
@@ -42,9 +71,9 @@ if (!symptom) {
42
71
  }
43
72
 
44
73
  phase("Preflight");
45
- const pre = lib.runPreflight({ projectDir });
74
+ const pre = await runPreflight(projectDir);
46
75
  if (!pre.ok) return { status: "failed", reason: "preflight-failed", preflight: pre.envelope };
47
- const brief = lib.generateBrief({ kind: "execute", projectDir });
76
+ const brief = await generateBrief(projectDir, { kind: "execute", id: "debug-brief" });
48
77
 
49
78
  let lastResult = null;
50
79
  for (let cycle = 1; cycle <= 2; cycle++) {
@@ -65,12 +65,47 @@ const INTEGRATE_RESULT_SCHEMA = {
65
65
 
66
66
  // ───── Script body ──────────────────────────────────────────────────────────
67
67
 
68
- const lib = require("./_lib.js");
69
- const path = require("path");
68
+ // M81: runtime-native helpers (sandbox bans require/fs/path/child_process/process — the
69
+ // old require("./_lib.js")+require("path") crashed this on first eval, TD-113). CLI calls
70
+ // delegate to an agent's Bash; file reads (scope.md/tasks.md) move INTO the worker agent
71
+ // (it has Read). args arrives as a JSON STRING in this runtime. See gsd-t-scan.workflow.js.
72
+ const _args = (typeof args === "string") ? (() => { try { return JSON.parse(args); } catch { return {}; } })() : (args || {});
73
+ const _CLI_ENVELOPE_SCHEMA = {
74
+ type: "object", required: ["ok", "exitCode"], additionalProperties: true,
75
+ properties: { ok: { type: "boolean" }, exitCode: { type: "integer" }, envelope: {}, stdout: { type: "string" }, stderr: { type: "string" }, via: { type: "string" } },
76
+ };
77
+ async function runCli(projectDir, subcmd, argv, localBin, label, parseJson = true, phaseName) {
78
+ const argStr = (argv || []).map((a) => `'${String(a).replace(/'/g, "'\\''")}'`).join(" ");
79
+ const prompt = [
80
+ `Run a GSD-T CLI command for the project at \`${projectDir}\` and report the result. Steps:`,
81
+ `1. If \`${projectDir}/bin/${localBin}\` exists, run: \`node ${projectDir}/bin/${localBin} ${argStr}\` (set via="local"). Otherwise run: \`gsd-t ${subcmd} ${argStr}\` (set via="global"). Use cwd \`${projectDir}\`.`,
82
+ `2. Capture exit code (ok = exitCode 0) and stdout/stderr.`,
83
+ parseJson ? `3. Parse stdout as JSON into \`envelope\` (null if not JSON). Return JSON per the schema.` : `3. Put stdout (trimmed, ≤4000 chars) in \`stdout\`. Return JSON per the schema.`,
84
+ `Do NOT do any other work. ONLY run this one command and report.`,
85
+ ].join("\n");
86
+ const opts = { label, schema: _CLI_ENVELOPE_SCHEMA, model: "haiku" };
87
+ if (phaseName) opts.phase = phaseName;
88
+ const r = await agent(prompt, opts).catch((e) => ({ ok: false, exitCode: -1, envelope: null, stderr: String(e && e.message), via: "error" }));
89
+ return r || { ok: false, exitCode: -1, envelope: null, via: "error" };
90
+ }
91
+ async function runPreflight(projectDir, label = "preflight", phaseName) { return runCli(projectDir, "preflight", ["--json"], "cli-preflight.cjs", label, true, phaseName); }
92
+ async function runVerifyGate(projectDir, label = "verify-gate", phaseName) { return runCli(projectDir, "verify-gate", ["--json"], "gsd-t-verify-gate.cjs", label, true, phaseName); }
93
+ async function proveFileDisjointness(projectDir, domains, label = "disjointness", phaseName) {
94
+ const argv = ["--dry-run"];
95
+ for (const d of (domains || [])) { argv.push("--domain", d); }
96
+ return runCli(projectDir, "parallel", argv, "gsd-t-parallel.cjs", label, false, phaseName);
97
+ }
98
+ async function generateBrief(projectDir, { kind = "execute", milestone, domain, id, label = "brief", phaseName } = {}) {
99
+ const argv = ["--kind", kind, "--spawn-id", id, "--out", `${projectDir}/.gsd-t/briefs/${id}.json`];
100
+ if (milestone) argv.push("--milestone", milestone);
101
+ if (domain) argv.push("--domain", domain);
102
+ const r = await runCli(projectDir, "brief", argv, "gsd-t-context-brief.cjs", label, false, phaseName);
103
+ return { ok: r.ok, briefPath: `${projectDir}/.gsd-t/briefs/${id}.json`, via: r.via };
104
+ }
70
105
 
71
- const projectDir = (args && args.projectDir) || ".";
72
- const milestone = (args && args.milestone) || null;
73
- const domains = (args && Array.isArray(args.domains) && args.domains) || [];
106
+ const projectDir = _args.projectDir || ".";
107
+ const milestone = _args.milestone || null;
108
+ const domains = (Array.isArray(_args.domains) && _args.domains) || [];
74
109
 
75
110
  if (!milestone) {
76
111
  log("execute: no milestone provided — args.milestone is required");
@@ -83,7 +118,7 @@ if (!domains.length) {
83
118
 
84
119
  phase("Preflight");
85
120
  log(`execute: milestone=${milestone}, domains=${domains.length}`);
86
- const pre = lib.runPreflight({ projectDir });
121
+ const pre = await runPreflight(projectDir);
87
122
  if (!pre.ok) {
88
123
  log(`preflight FAIL — exitCode=${pre.exitCode}: ${pre.stderr || "(no stderr)"}`);
89
124
  return { status: "failed", reason: "preflight-failed", preflight: pre.envelope };
@@ -93,7 +128,7 @@ log(`preflight OK`);
93
128
  phase("Disjointness");
94
129
  // 4.8-audit fix: scope disjointness to the requested domain set, not the whole project.
95
130
  // Without this, an unrelated DRAFT domain elsewhere in the project could flip the result.
96
- const disj = lib.proveFileDisjointness({ projectDir, domains });
131
+ const disj = await proveFileDisjointness(projectDir, domains);
97
132
  if (!disj.ok) {
98
133
  log(`disjointness FAIL — exitCode=${disj.exitCode}: ${disj.stderr || disj.stdout}`);
99
134
  return { status: "failed", reason: "non-disjoint" };
@@ -105,32 +140,22 @@ const domainResults = await parallel(
105
140
  domains.map((domain) => async () => {
106
141
  // 4.8-audit fix: per-domain brief (M55-D2 brief-per-spawn semantic) — each worker
107
142
  // gets a brief scoped to its own domain so grep-the-brief is most effective.
108
- const domBrief = lib.generateBrief({ kind: "execute", milestone, domain, projectDir });
109
- const briefRef = domBrief.ok
110
- ? domBrief.briefPath
111
- : "(brief generation failed — re-walk repo)";
112
-
113
- // 4.8-audit fix: do NOT truncate scope/tasks. The worker is being told to "execute
114
- // every task" — silently dropping tail content is a correctness regression. Briefs
115
- // are the compression layer; raw scope/tasks must pass whole.
116
- const scope = lib.readScope({ projectDir, domain }) || "(scope.md missing)";
117
- const tasks = lib.readDomainTasks({ projectDir, domain }) || "(tasks.md missing)";
143
+ // M81: generated via an awaited agent (sandbox-safe); the worker reads its own
144
+ // scope.md/tasks.md (it has Read) instead of the orchestrator pre-reading via fs.
145
+ const domBrief = await generateBrief(projectDir, { kind: "execute", milestone, domain, id: `execute-${(milestone || "m").toLowerCase()}-${domain}`, phaseName: "Domains", label: `brief:${domain}` });
146
+ const briefRef = domBrief.ok ? domBrief.briefPath : "(brief generation failed — re-walk repo)";
147
+ const scopePath = `${projectDir}/.gsd-t/domains/${domain}/scope.md`;
148
+ const tasksPath = `${projectDir}/.gsd-t/domains/${domain}/tasks.md`;
118
149
  const prompt = [
119
150
  `You are the worker agent for the GSD-T domain \`${domain}\` in milestone \`${milestone}\`.`,
120
151
  ``,
121
- `Your job: execute every task listed under "## Tasks" in this domain's tasks.md, respecting the file ownership in scope.md.`,
152
+ `FIRST, read these two files in full (do NOT skip or truncate them):`,
153
+ `- Scope (your owned files): \`${scopePath}\``,
154
+ `- Tasks: \`${tasksPath}\``,
122
155
  ``,
123
- `**Brief (REQUIRED READ):** ${briefRef} if present, grep this JSON first instead of re-reading CLAUDE.md and contracts.`,
124
- ``,
125
- `**Scope (your owned files):**`,
126
- "```",
127
- scope,
128
- "```",
156
+ `Your job: execute every task listed under "## Tasks" in tasks.md, respecting the file ownership in scope.md.`,
129
157
  ``,
130
- `**Tasks:**`,
131
- "```",
132
- tasks,
133
- "```",
158
+ `**Brief (REQUIRED READ):** ${briefRef} — if present, grep this JSON first instead of re-reading CLAUDE.md and contracts.`,
134
159
  ``,
135
160
  `Constraints:`,
136
161
  `- Touch only files in your scope's "Owned Files" list.`,
@@ -194,7 +219,7 @@ if (integrate.status === "failed") {
194
219
  }
195
220
 
196
221
  phase("Verify-Gate");
197
- const vg = lib.runVerifyGate({ projectDir });
222
+ const vg = await runVerifyGate(projectDir);
198
223
  log(`verify-gate exitCode=${vg.exitCode} ok=${vg.ok}`);
199
224
 
200
225
  return {
@@ -16,11 +16,41 @@ export const meta = {
16
16
  ],
17
17
  };
18
18
 
19
- const lib = require("./_lib.js");
19
+ // M81: runtime-native helpers (sandbox bans require/fs/child_process/process — the old
20
+ // require("./_lib.js") crashed this workflow on first eval, TD-113). Delegate CLI calls
21
+ // to an agent's Bash; args arrives as a JSON STRING in this runtime. See gsd-t-scan.workflow.js.
22
+ const _args = (typeof args === "string") ? (() => { try { return JSON.parse(args); } catch { return {}; } })() : (args || {});
23
+ const _CLI_ENVELOPE_SCHEMA = {
24
+ type: "object", required: ["ok", "exitCode"], additionalProperties: true,
25
+ properties: { ok: { type: "boolean" }, exitCode: { type: "integer" }, envelope: {}, stdout: { type: "string" }, stderr: { type: "string" }, via: { type: "string" } },
26
+ };
27
+ async function runCli(projectDir, subcmd, argv, localBin, label, parseJson = true, phaseName) {
28
+ const argStr = (argv || []).map((a) => `'${String(a).replace(/'/g, "'\\''")}'`).join(" ");
29
+ const prompt = [
30
+ `Run a GSD-T CLI command for the project at \`${projectDir}\` and report the result. Steps:`,
31
+ `1. If \`${projectDir}/bin/${localBin}\` exists, run: \`node ${projectDir}/bin/${localBin} ${argStr}\` (set via="local"). Otherwise run: \`gsd-t ${subcmd} ${argStr}\` (set via="global"). Use cwd \`${projectDir}\`.`,
32
+ `2. Capture exit code (ok = exitCode 0) and stdout/stderr.`,
33
+ parseJson ? `3. Parse stdout as JSON into \`envelope\` (null if not JSON). Return JSON per the schema.` : `3. Put stdout (trimmed, ≤4000 chars) in \`stdout\`. Return JSON per the schema.`,
34
+ `Do NOT do any other work. ONLY run this one command and report.`,
35
+ ].join("\n");
36
+ const opts = { label, schema: _CLI_ENVELOPE_SCHEMA, model: "haiku" };
37
+ if (phaseName) opts.phase = phaseName;
38
+ const r = await agent(prompt, opts).catch((e) => ({ ok: false, exitCode: -1, envelope: null, stderr: String(e && e.message), via: "error" }));
39
+ return r || { ok: false, exitCode: -1, envelope: null, via: "error" };
40
+ }
41
+ async function runPreflight(projectDir, label = "preflight", phaseName) { return runCli(projectDir, "preflight", ["--json"], "cli-preflight.cjs", label, true, phaseName); }
42
+ async function runVerifyGate(projectDir, label = "verify-gate", phaseName) { return runCli(projectDir, "verify-gate", ["--json"], "gsd-t-verify-gate.cjs", label, true, phaseName); }
43
+ async function generateBrief(projectDir, { kind = "execute", milestone, domain, id, label = "brief", phaseName } = {}) {
44
+ const argv = ["--kind", kind, "--spawn-id", id, "--out", `${projectDir}/.gsd-t/briefs/${id}.json`];
45
+ if (milestone) argv.push("--milestone", milestone);
46
+ if (domain) argv.push("--domain", domain);
47
+ const r = await runCli(projectDir, "brief", argv, "gsd-t-context-brief.cjs", label, false, phaseName);
48
+ return { ok: r.ok, briefPath: `${projectDir}/.gsd-t/briefs/${id}.json`, via: r.via };
49
+ }
20
50
 
21
- const projectDir = (args && args.projectDir) || ".";
22
- const milestone = (args && args.milestone) || null;
23
- const domains = (args && args.domains) || [];
51
+ const projectDir = _args.projectDir || ".";
52
+ const milestone = _args.milestone || null;
53
+ const domains = _args.domains || [];
24
54
 
25
55
  const INTEGRATE_SCHEMA = {
26
56
  type: "object",
@@ -38,9 +68,9 @@ if (!milestone || !domains.length) {
38
68
  }
39
69
 
40
70
  phase("Preflight");
41
- const pre = lib.runPreflight({ projectDir });
71
+ const pre = await runPreflight(projectDir);
42
72
  if (!pre.ok) return { status: "failed", reason: "preflight-failed", preflight: pre.envelope };
43
- const brief = lib.generateBrief({ kind: "execute", milestone, projectDir });
73
+ const brief = await generateBrief(projectDir, { kind: "execute", milestone, id: `integrate-${(milestone || "m").toLowerCase()}` });
44
74
 
45
75
  phase("Integrate");
46
76
  const integrate = await agent(
@@ -63,7 +93,7 @@ if (integrate.status === "failed") {
63
93
  }
64
94
 
65
95
  phase("Verify-Gate");
66
- const vg = lib.runVerifyGate({ projectDir });
96
+ const vg = await runVerifyGate(projectDir);
67
97
  return {
68
98
  status: vg.ok ? "complete" : "verify-failed",
69
99
  integrate,
@@ -26,7 +26,36 @@ export const meta = {
26
26
  ],
27
27
  };
28
28
 
29
- const lib = require("./_lib.js");
29
+ // M81: runtime-native helpers (sandbox bans require/fs/child_process/process — the old
30
+ // require("./_lib.js") crashed this workflow on first eval, TD-113). Delegate CLI calls
31
+ // to an agent's Bash; args arrives as a JSON STRING in this runtime. See gsd-t-scan.workflow.js.
32
+ const _args = (typeof args === "string") ? (() => { try { return JSON.parse(args); } catch { return {}; } })() : (args || {});
33
+ const _CLI_ENVELOPE_SCHEMA = {
34
+ type: "object", required: ["ok", "exitCode"], additionalProperties: true,
35
+ properties: { ok: { type: "boolean" }, exitCode: { type: "integer" }, envelope: {}, stdout: { type: "string" }, stderr: { type: "string" }, via: { type: "string" } },
36
+ };
37
+ async function runCli(projectDir, subcmd, argv, localBin, label, parseJson = true, phaseNameOpt) {
38
+ const argStr = (argv || []).map((a) => `'${String(a).replace(/'/g, "'\\''")}'`).join(" ");
39
+ const prompt = [
40
+ `Run a GSD-T CLI command for the project at \`${projectDir}\` and report the result. Steps:`,
41
+ `1. If \`${projectDir}/bin/${localBin}\` exists, run: \`node ${projectDir}/bin/${localBin} ${argStr}\` (set via="local"). Otherwise run: \`gsd-t ${subcmd} ${argStr}\` (set via="global"). Use cwd \`${projectDir}\`.`,
42
+ `2. Capture exit code (ok = exitCode 0) and stdout/stderr.`,
43
+ parseJson ? `3. Parse stdout as JSON into \`envelope\` (null if not JSON). Return JSON per the schema.` : `3. Put stdout (trimmed, ≤4000 chars) in \`stdout\`. Return JSON per the schema.`,
44
+ `Do NOT do any other work. ONLY run this one command and report.`,
45
+ ].join("\n");
46
+ const opts = { label, schema: _CLI_ENVELOPE_SCHEMA, model: "haiku" };
47
+ if (phaseNameOpt) opts.phase = phaseNameOpt;
48
+ const r = await agent(prompt, opts).catch((e) => ({ ok: false, exitCode: -1, envelope: null, stderr: String(e && e.message), via: "error" }));
49
+ return r || { ok: false, exitCode: -1, envelope: null, via: "error" };
50
+ }
51
+ async function runPreflight(projectDir, label = "preflight", phaseNameOpt) { return runCli(projectDir, "preflight", ["--json"], "cli-preflight.cjs", label, true, phaseNameOpt); }
52
+ async function generateBrief(projectDir, { kind = "execute", milestone, domain, id, label = "brief", phaseNameOpt } = {}) {
53
+ const argv = ["--kind", kind, "--spawn-id", id, "--out", `${projectDir}/.gsd-t/briefs/${id}.json`];
54
+ if (milestone) argv.push("--milestone", milestone);
55
+ if (domain) argv.push("--domain", domain);
56
+ const r = await runCli(projectDir, "brief", argv, "gsd-t-context-brief.cjs", label, false, phaseNameOpt);
57
+ return { ok: r.ok, briefPath: `${projectDir}/.gsd-t/briefs/${id}.json`, via: r.via };
58
+ }
30
59
 
31
60
  const VALID_PHASES = [
32
61
  "partition", "plan", "discuss", "impact",
@@ -45,10 +74,10 @@ const PHASE_RESULT_SCHEMA = {
45
74
  },
46
75
  };
47
76
 
48
- const projectDir = (args && args.projectDir) || ".";
49
- const milestone = (args && args.milestone) || null;
50
- const userInput = (args && args.userInput) || "";
51
- const phaseName = args && args.phase;
77
+ const projectDir = _args.projectDir || ".";
78
+ const milestone = _args.milestone || null;
79
+ const userInput = _args.userInput || "";
80
+ const phaseName = _args.phase;
52
81
 
53
82
  if (!phaseName || !VALID_PHASES.includes(phaseName)) {
54
83
  log(`phase: args.phase must be one of: ${VALID_PHASES.join(", ")}`);
@@ -56,9 +85,9 @@ if (!phaseName || !VALID_PHASES.includes(phaseName)) {
56
85
  }
57
86
 
58
87
  phase("Preflight");
59
- const pre = lib.runPreflight({ projectDir });
88
+ const pre = await runPreflight(projectDir);
60
89
  if (!pre.ok) return { status: "failed", reason: "preflight-failed", preflight: pre.envelope };
61
- const brief = lib.generateBrief({ kind: phaseName, milestone, projectDir });
90
+ const brief = await generateBrief(projectDir, { kind: phaseName, milestone, id: `${phaseName}-${(milestone || "m").toLowerCase()}` });
62
91
 
63
92
  phase("Phase");
64
93
  const promptByPhase = {
@@ -16,11 +16,63 @@ export const meta = {
16
16
  ],
17
17
  };
18
18
 
19
- const lib = require("./_lib.js");
19
+ // M81: runtime-native helpers. The Anthropic Workflow sandbox provides ONLY the
20
+ // globals agent/parallel/pipeline/log/phase/budget/args — NO require/fs/path/
21
+ // child_process/process. The old `require("./_lib.js")` threw ReferenceError on first
22
+ // eval, so EVERY workflow except scan silently crashed and never ran (TD-113, confirmed
23
+ // by the NiceNote session 2026-06-05). These inline helpers delegate the CLI calls to an
24
+ // agent() that runs them via Bash (preferring project-local bin/<tool>.cjs, falling back
25
+ // to the global `gsd-t` PATH binary), parsing the JSON envelope — same brains, sandbox-safe
26
+ // invocation. The args global also arrives as a JSON STRING in this runtime, so parse it.
27
+ const _args = (typeof args === "string") ? (() => { try { return JSON.parse(args); } catch { return {}; } })() : (args || {});
20
28
 
21
- const projectDir = (args && args.projectDir) || ".";
22
- const task = (args && args.task) || null;
23
- const model = (args && args.model) || "sonnet";
29
+ const _CLI_ENVELOPE_SCHEMA = {
30
+ type: "object", required: ["ok", "exitCode"], additionalProperties: true,
31
+ properties: {
32
+ ok: { type: "boolean" }, exitCode: { type: "integer" },
33
+ envelope: {}, stdout: { type: "string" }, stderr: { type: "string" }, via: { type: "string" },
34
+ },
35
+ };
36
+ // Run a `gsd-t <subcmd>` CLI (or project-local bin/<localBin>) via an agent's Bash and
37
+ // return { ok, exitCode, envelope, stderr, via }. parseJson=true parses stdout as the envelope.
38
+ async function runCli(projectDir, subcmd, argv, localBin, label, parseJson = true, phaseName) {
39
+ const argStr = (argv || []).map((a) => `'${String(a).replace(/'/g, "'\\''")}'`).join(" ");
40
+ const prompt = [
41
+ `Run a GSD-T CLI command for the project at \`${projectDir}\` and report the result. Steps:`,
42
+ `1. If \`${projectDir}/bin/${localBin}\` exists, run: \`node ${projectDir}/bin/${localBin} ${argStr}\` (set via="local").`,
43
+ ` Otherwise run: \`gsd-t ${subcmd} ${argStr}\` (set via="global").`,
44
+ ` Run it with cwd \`${projectDir}\` (use \`cd ${projectDir} && …\` or \`-C\`/\`--cwd\` as appropriate).`,
45
+ `2. Capture the exit code (ok = exitCode 0) and stdout/stderr.`,
46
+ parseJson
47
+ ? `3. Parse stdout as JSON into \`envelope\` (null if not JSON). Return JSON per the schema.`
48
+ : `3. Put stdout (trimmed, ≤4000 chars) in \`stdout\`. Return JSON per the schema.`,
49
+ `Do NOT do any other work. ONLY run this one command and report.`,
50
+ ].join("\n");
51
+ const opts = { label, schema: _CLI_ENVELOPE_SCHEMA, model: "haiku" };
52
+ if (phaseName) opts.phase = phaseName; // opts.phase MUST be a string, never the phase() fn
53
+ const r = await agent(prompt, opts)
54
+ .catch((e) => ({ ok: false, exitCode: -1, envelope: null, stderr: String(e && e.message), via: "error" }));
55
+ return r || { ok: false, exitCode: -1, envelope: null, via: "error" };
56
+ }
57
+ async function runPreflight(projectDir, label = "preflight", phaseName) {
58
+ return runCli(projectDir, "preflight", ["--json"], "cli-preflight.cjs", label, true, phaseName);
59
+ }
60
+ async function runVerifyGate(projectDir, label = "verify-gate", phaseName) {
61
+ return runCli(projectDir, "verify-gate", ["--json"], "gsd-t-verify-gate.cjs", label, true, phaseName);
62
+ }
63
+ // Brief generation: writes .gsd-t/briefs/<id>.json and returns its path. The id must be
64
+ // caller-supplied (no Date.now/Math.random in the sandbox) — pass a stable id per spawn.
65
+ async function generateBrief(projectDir, { kind = "execute", milestone, domain, id, label = "brief", phaseName } = {}) {
66
+ const argv = ["--kind", kind, "--spawn-id", id, "--out", `${projectDir}/.gsd-t/briefs/${id}.json`];
67
+ if (milestone) argv.push("--milestone", milestone);
68
+ if (domain) argv.push("--domain", domain);
69
+ const r = await runCli(projectDir, "brief", argv, "gsd-t-context-brief.cjs", label, false, phaseName);
70
+ return { ok: r.ok, briefPath: `${projectDir}/.gsd-t/briefs/${id}.json`, via: r.via };
71
+ }
72
+
73
+ const projectDir = _args.projectDir || ".";
74
+ const task = _args.task || null;
75
+ const model = _args.model || "sonnet";
24
76
 
25
77
  const QUICK_SCHEMA = {
26
78
  type: "object",
@@ -38,9 +90,9 @@ if (!task) {
38
90
  }
39
91
 
40
92
  phase("Preflight");
41
- const pre = lib.runPreflight({ projectDir });
93
+ const pre = await runPreflight(projectDir);
42
94
  if (!pre.ok) return { status: "failed", reason: "preflight-failed", preflight: pre.envelope };
43
- const brief = lib.generateBrief({ kind: "execute", projectDir });
95
+ const brief = await generateBrief(projectDir, { kind: "execute", id: "quick-brief" });
44
96
 
45
97
  phase("Execute");
46
98
  const result = await agent(
@@ -64,7 +116,7 @@ if (result.status === "failed" || result.status === "blocked") {
64
116
  }
65
117
 
66
118
  phase("Verify");
67
- const vg = lib.runVerifyGate({ projectDir });
119
+ const vg = await runVerifyGate(projectDir);
68
120
  return {
69
121
  status: vg.ok ? "complete" : "verify-failed",
70
122
  result,
@@ -30,12 +30,55 @@ export const meta = {
30
30
  ],
31
31
  };
32
32
 
33
- const lib = require("./_lib.js");
33
+ // M81: runtime-native helpers (sandbox bans require/fs/path/child_process/process — the
34
+ // old require("./_lib.js") + the inline require("child_process"/"fs"/"path") in the
35
+ // CI-parity block crashed this on first eval, TD-113). All CLI calls (preflight,
36
+ // verify-gate, build-coverage, ci-parity, test-data) delegate to an agent's Bash; the
37
+ // QA/Red-Team protocol bodies are read by an agent (Read) instead of fs. args arrives as
38
+ // a JSON STRING in this runtime. See gsd-t-scan.workflow.js.
39
+ const _args = (typeof args === "string") ? (() => { try { return JSON.parse(args); } catch { return {}; } })() : (args || {});
40
+ const _CLI_ENVELOPE_SCHEMA = {
41
+ type: "object", required: ["ok", "exitCode"], additionalProperties: true,
42
+ properties: { ok: { type: "boolean" }, exitCode: { type: "integer" }, envelope: {}, stdout: { type: "string" }, stderr: { type: "string" }, via: { type: "string" } },
43
+ };
44
+ async function runCli(projectDir, subcmd, argv, localBin, label, parseJson = true, phaseName) {
45
+ const argStr = (argv || []).map((a) => `'${String(a).replace(/'/g, "'\\''")}'`).join(" ");
46
+ const prompt = [
47
+ `Run a GSD-T CLI command for the project at \`${projectDir}\` and report the result. Steps:`,
48
+ `1. If \`${projectDir}/bin/${localBin}\` exists, run: \`node ${projectDir}/bin/${localBin} ${argStr}\` (set via="local"). Otherwise run: \`gsd-t ${subcmd} ${argStr}\` (set via="global"). Use cwd \`${projectDir}\`.`,
49
+ `2. Capture exit code (ok = exitCode 0) and stdout/stderr.`,
50
+ parseJson ? `3. Parse stdout as JSON into \`envelope\` (null if not JSON). Return JSON per the schema.` : `3. Put stdout (trimmed, ≤4000 chars) in \`stdout\`. Return JSON per the schema.`,
51
+ `Do NOT do any other work. ONLY run this one command and report.`,
52
+ ].join("\n");
53
+ const opts = { label, schema: _CLI_ENVELOPE_SCHEMA, model: "haiku" };
54
+ if (phaseName) opts.phase = phaseName;
55
+ const r = await agent(prompt, opts).catch((e) => ({ ok: false, exitCode: -1, envelope: null, stderr: String(e && e.message), via: "error" }));
56
+ return r || { ok: false, exitCode: -1, envelope: null, via: "error" };
57
+ }
58
+ async function runPreflight(projectDir, label = "preflight", phaseName) { return runCli(projectDir, "preflight", ["--json"], "cli-preflight.cjs", label, true, phaseName); }
59
+ async function runVerifyGate(projectDir, label = "verify-gate", phaseName) { return runCli(projectDir, "verify-gate", ["--json"], "gsd-t-verify-gate.cjs", label, true, phaseName); }
60
+ async function generateBrief(projectDir, { kind = "verify", milestone, domain, id, label = "brief", phaseName } = {}) {
61
+ const argv = ["--kind", kind, "--spawn-id", id, "--out", `${projectDir}/.gsd-t/briefs/${id}.json`];
62
+ if (milestone) argv.push("--milestone", milestone);
63
+ if (domain) argv.push("--domain", domain);
64
+ const r = await runCli(projectDir, "brief", argv, "gsd-t-context-brief.cjs", label, false, phaseName);
65
+ return { ok: r.ok, briefPath: `${projectDir}/.gsd-t/briefs/${id}.json`, via: r.via };
66
+ }
67
+ // The QA / Red-Team / design-verify protocol bodies live at templates/prompts/<name>-subagent.md
68
+ // inside the installed @tekyzinc/gsd-t package. The orchestrator can't read files (no fs); each
69
+ // triad agent reads its OWN protocol via Read at spawn time. loadProtocol returns a Read-instruction
70
+ // the agent prompt embeds, rather than the protocol text itself.
71
+ function loadProtocolInstruction(name) {
72
+ const rel = `templates/prompts/${name}-subagent.md`;
73
+ // Locate the protocol inside the installed @tekyzinc/gsd-t package using ONLY shell
74
+ // (no require/fs tokens — those trip the runtime-native lint even inside a string).
75
+ return `Read your protocol FIRST. Find it by running in Bash: \`cat "$(npm root -g)/@tekyzinc/gsd-t/${rel}"\` (or, if a project-local \`${rel}\` exists, read that instead). Follow that protocol exactly.`;
76
+ }
34
77
 
35
- const projectDir = (args && args.projectDir) || ".";
36
- const milestone = (args && args.milestone) || null;
37
- const skipUltra = (args && args.skipUltra) || false;
38
- const skipUltraReason = (args && args.skipUltraReason) || null;
78
+ const projectDir = _args.projectDir || ".";
79
+ const milestone = _args.milestone || null;
80
+ const skipUltra = _args.skipUltra || false;
81
+ const skipUltraReason = _args.skipUltraReason || null;
39
82
 
40
83
  // 4.8-audit fix: skipUltra requires a recorded reason per
41
84
  // orthogonal-validation-contract.md Rule #2. Refuse without one.
@@ -153,15 +196,15 @@ if (!milestone) {
153
196
  }
154
197
 
155
198
  phase("Preflight");
156
- const pre = lib.runPreflight({ projectDir });
199
+ const pre = await runPreflight(projectDir);
157
200
  if (!pre.ok) {
158
201
  log(`preflight FAIL — halting verify`);
159
202
  return { status: "failed", reason: "preflight-failed", preflight: pre.envelope };
160
203
  }
161
- const brief = lib.generateBrief({ kind: "verify", milestone, projectDir });
204
+ const brief = await generateBrief(projectDir, { kind: "verify", milestone, id: `verify-${(milestone || "m").toLowerCase()}` });
162
205
 
163
206
  phase("Verify-Gate");
164
- const vg = lib.runVerifyGate({ projectDir });
207
+ const vg = await runVerifyGate(projectDir);
165
208
  if (!vg.ok) {
166
209
  log(`verify-gate FAIL exitCode=${vg.exitCode} — halting before triad`);
167
210
  return {
@@ -179,36 +222,16 @@ log(`verify-gate green`);
179
222
  // from Dockerfile COPY — silent CI-divergence regression. M57 made this gate
180
223
  // mandatory; Workflow MUST preserve it or we re-introduce that exact failure.
181
224
  // Detected by user/worker in parallel session 2026-05-29 13:00.
225
+ // M81: these were raw spawnSync + require("fs"/"path"/"child_process") in the orchestrator
226
+ // — the exact sandbox-forbidden pattern (TD-113). Now awaited runCli agent calls. The
227
+ // FAIL-blocking semantics are UNCHANGED: a non-zero exit halts verify before the triad.
182
228
  phase("CI-Parity");
183
- const { spawnSync } = require("child_process");
184
- function _runJsonCli(subcmd, argv = []) {
185
- // Use _lib-style resolution — prefer project-local bin/<tool>.cjs
186
- const fsMod = require("fs");
187
- const pMod = require("path");
188
- const localMap = {
189
- "build-coverage": "gsd-t-build-coverage.cjs",
190
- "ci-parity": "gsd-t-ci-parity.cjs",
191
- "test-data": "gsd-t-test-data-ledger.cjs",
192
- };
193
- const local = pMod.join(projectDir, "bin", localMap[subcmd] || "");
194
- const cmd = fsMod.existsSync(local) ? process.execPath : "gsd-t";
195
- const args = fsMod.existsSync(local) ? [local, ...argv] : [subcmd, ...argv];
196
- const r = spawnSync(cmd, args, { cwd: projectDir, stdio: "pipe" });
197
- let envelope = null;
198
- try { envelope = r.stdout ? JSON.parse(r.stdout.toString()) : null; } catch (_) {}
199
- return {
200
- ok: r.status === 0,
201
- exitCode: r.status,
202
- envelope,
203
- stderr: r.stderr && r.stderr.toString(),
204
- };
205
- }
206
- const bc = _runJsonCli("build-coverage", ["--json"]);
229
+ const bc = await runCli(projectDir, "build-coverage", ["--json"], "gsd-t-build-coverage.cjs", "m57:build-coverage", true, "CI-Parity");
207
230
  if (!bc.ok) {
208
231
  log(`M57 build-coverage FAIL exitCode=${bc.exitCode} — halting (FAIL-blocking)`);
209
232
  return { status: "ci-parity-failed", overallVerdict: "VERIFY-FAILED", buildCoverage: bc.envelope };
210
233
  }
211
- const cip = _runJsonCli("ci-parity", ["--json"]);
234
+ const cip = await runCli(projectDir, "ci-parity", ["--json"], "gsd-t-ci-parity.cjs", "m57:ci-parity", true, "CI-Parity");
212
235
  if (!cip.ok) {
213
236
  log(`M57 ci-parity FAIL exitCode=${cip.exitCode} — halting (FAIL-blocking)`);
214
237
  return { status: "ci-parity-failed", overallVerdict: "VERIFY-FAILED", ciParity: cip.envelope };
@@ -221,8 +244,10 @@ log(`M57 CI-parity gate green`);
221
244
  // live in production data. M58 made post-E2E purge mandatory; M60 hardened
222
245
  // the adapters against empty-prefix bypass. Workflow MUST preserve.
223
246
  phase("Test-Data Purge");
224
- const verifyRunId = `verify-${milestone || "M??"}-${Date.now().toString(36)}`;
225
- const td = _runJsonCli("test-data", ["--purge", "--run", verifyRunId, "--json"]);
247
+ // M81: run-id is stable per verify run (no Date.now in the sandbox); the milestone scope
248
+ // is sufficient for purge targeting and is deterministic on resume.
249
+ const verifyRunId = `verify-${(milestone || "M__").toLowerCase()}`;
250
+ const td = await runCli(projectDir, "test-data", ["--purge", "--run", verifyRunId, "--json"], "gsd-t-test-data-ledger.cjs", "m58:test-data-purge", true, "Test-Data Purge");
226
251
  if (!td.ok) {
227
252
  log(`M58 test-data purge FAIL exitCode=${td.exitCode} — halting (FAIL-blocking)`);
228
253
  return { status: "test-data-purge-failed", overallVerdict: "VERIFY-FAILED", testDataPurge: td.envelope };
@@ -233,10 +258,11 @@ phase("Orthogonal Triad");
233
258
 
234
259
  const briefRef = brief.briefPath || "(brief generation failed — re-walk repo)";
235
260
 
236
- // Load the methodology protocols KEPT in templates/prompts/. The protocol body
237
- // IS the methodology Workflow just hosts the agent() call.
238
- const redTeamProtocol = lib.loadProtocol("red-team");
239
- const qaProtocol = lib.loadProtocol("qa");
261
+ // M81: the protocol body lives in templates/prompts/<name>-subagent.md inside the installed
262
+ // package; the orchestrator can't read files (no fs). Each triad agent reads its OWN
263
+ // protocol via Read at spawn time — loadProtocolInstruction returns the Read directive.
264
+ const redTeamProtocolInstruction = loadProtocolInstruction("red-team");
265
+ const qaProtocolInstruction = loadProtocolInstruction("qa");
240
266
 
241
267
  const stages = [
242
268
  // Stage A — /code-review ultra (cooperative correctness + cleanup)
@@ -273,10 +299,7 @@ const stages = [
273
299
  `**adversarial / security / boundaries**. You are NOT cooperative — your`,
274
300
  `success is measured in bugs FOUND, not tests passed. Try to break the code.`,
275
301
  ``,
276
- `Run the Red Team protocol:`,
277
- "----- BEGIN RED TEAM PROTOCOL -----",
278
- redTeamProtocol.slice(0, 8000),
279
- "----- END RED TEAM PROTOCOL -----",
302
+ `Run the Red Team protocol. ${redTeamProtocolInstruction}`,
280
303
  ``,
281
304
  `Verdict is FAIL if you found any CRITICAL or HIGH severity bug; GRUDGING-PASS`,
282
305
  `if you searched exhaustively and found nothing. Return JSON per the schema.`,
@@ -296,10 +319,7 @@ const stages = [
296
319
  `counts. Detect shallow tests (layout-only assertions that pass on an empty HTML page).`,
297
320
  `Verify contract compliance against .gsd-t/contracts/.`,
298
321
  ``,
299
- `Run the QA protocol:`,
300
- "----- BEGIN QA PROTOCOL -----",
301
- qaProtocol.slice(0, 8000),
302
- "----- END QA PROTOCOL -----",
322
+ `Run the QA protocol. ${qaProtocolInstruction}`,
303
323
  ``,
304
324
  `Return JSON per the schema.`,
305
325
  ].join("\n"),
@@ -14,11 +14,14 @@ export const meta = {
14
14
  ],
15
15
  };
16
16
 
17
- const lib = require("./_lib.js");
17
+ // M81: this workflow only composes sub-workflows (execute + verify) — it never used
18
+ // lib.*, but the `require("./_lib.js")` import alone crashed it on first eval in the
19
+ // sandbox (TD-113). Removed. args arrives as a JSON STRING in this runtime, so parse it.
20
+ const _args = (typeof args === "string") ? (() => { try { return JSON.parse(args); } catch { return {}; } })() : (args || {});
18
21
 
19
- const projectDir = (args && args.projectDir) || ".";
20
- const milestone = (args && args.milestone) || null;
21
- const domains = (args && args.domains) || [];
22
+ const projectDir = _args.projectDir || ".";
23
+ const milestone = _args.milestone || null;
24
+ const domains = _args.domains || [];
22
25
 
23
26
  if (!milestone || !domains.length) {
24
27
  log("wave: args.milestone and args.domains required");