@tekyzinc/gsd-t 4.0.27 → 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.
@@ -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,
@@ -607,6 +607,19 @@ log(`register written: ${JSON.stringify(synthesis.counts)} (${synthesis.tdRange}
607
607
  // READ the just-written register itself, since the orchestrator can't read it.
608
608
  phase("Document");
609
609
  const sliceSummary = slices.map((s) => `- ${s.key} (${s.dimension}): ${JSON.stringify(s.paths)}`).join("\n");
610
+ // Compact serialization of the verified findings handed to each document agent. Project
611
+ // only the fields the docs need (full objects can carry verbose verify metadata); the
612
+ // baseCtx caps it at 120KB below. (Bugfix: findingsJson was referenced but never defined,
613
+ // which crashed the entire workflow at the Document phase AFTER all finders/verify/synthesis
614
+ // ran — ReferenceError: findingsJson is not defined.)
615
+ const findingsJson = JSON.stringify(
616
+ finalFindings.map((f) => ({
617
+ title: f.title, severity: f.severity, area: f.area,
618
+ files: f.files, detail: f.detail, impact: f.impact,
619
+ recommendation: f.recommendation, slice: f.slice,
620
+ })),
621
+ null, 1
622
+ );
610
623
  const baseCtx = [
611
624
  `Project: \`${projectDir}\`. Probe totals: ${JSON.stringify(probe.totals)}.`,
612
625
  `Slices the scan covered:`,
@@ -740,20 +753,37 @@ for (const sev of ["CRITICAL", "HIGH", "MEDIUM", "LOW"]) {
740
753
  }
741
754
  if (buf.trim()) peChunks.push(buf);
742
755
  }
743
- let peWrote = 0;
744
- for (let ci = 0; ci < peChunks.length; ci++) {
745
- const first = ci === 0;
746
- const res = await gatedAgent(
747
- [
748
- first ? `Create the file \`${peTarget}\` (overwrite) with EXACTLY the content between markers, using the Write tool. Verbatim.`
749
- : `APPEND EXACTLY the content between markers to the END of \`${peTarget}\` (do not overwrite; append) via a Bash heredoc \`cat >> ${peTarget} <<'GSDTEOF'\` … \`GSDTEOF\`. Verbatim.`,
750
- `Reply ONLY "OK".`, "", "<<<C>>>", peChunks[ci], "<<<END>>>",
751
- ].join("\n"),
752
- { label: `plain-english write ${ci + 1}/${peChunks.length}`, phase: "Plain-English", model: "haiku" }
753
- ).catch((e) => ({ _e: String(e && e.message) }));
754
- if (typeof res === "string" && /ok/i.test(res)) peWrote++;
755
- }
756
- log(`plain-english: ${Object.values(peGroups).reduce((a, b) => a + b.length, 0)}/${peItems.length} entries, grouped by severity, ${peWrote}/${peChunks.length} chunks written${peFailed ? `; ${peFailed} gen batch(es) failed` : ""}`);
756
+ // M80 fix: a SINGLE owning agent writes ALL chunks sequentially and SELF-VERIFIES the
757
+ // final `### TD-` entry count, retrying short writes. The prior design fanned each chunk
758
+ // to a separate haiku agent via heredoc-append — agents replied "OK" without faithfully
759
+ // appending 30KB blobs of markdown (special chars `$` ` # collide with the heredoc /
760
+ // get paraphrased), so the middle chunks silently dropped: run wf_b2a6a9e0-9de wrote
761
+ // only 65/181 entries (Critical+High + a 2-item tail). With one owner + a count check,
762
+ // an incomplete write is detected and fixed in-agent instead of shipping truncated.
763
+ const peExpectedEntries = Object.values(peGroups).reduce((a, b) => a + b.length, 0);
764
+ const peWriteRes = await gatedAgent(
765
+ [
766
+ `You write ONE file: \`${peTarget}\`. It has ${peChunks.length} ordered chunks (below, each between <<<C n>>> and <<<END n>>>).`,
767
+ `Procedure follow EXACTLY:`,
768
+ `1. Write chunk 1 to \`${peTarget}\` VERBATIM using the Write tool (creates/overwrites).`,
769
+ `2. For chunks 2..${peChunks.length}, APPEND each VERBATIM to the END of the file. Use the Write tool with the FULL accumulated content (read the file, concatenate the next chunk, Write the whole thing) do NOT use a heredoc (special chars corrupt it).`,
770
+ `3. After all chunks: run \`grep -c '^### TD-' ${peTarget}\`. It MUST equal ${peExpectedEntries}.`,
771
+ `4. If the count is LESS than ${peExpectedEntries}, you dropped content — redo the append for the missing chunks until the count is exactly ${peExpectedEntries}.`,
772
+ `Reply with ONLY the final integer count from step 3 (e.g. "${peExpectedEntries}"). Nothing else. Reproduce every chunk verbatim — do not summarize, reword, or skip entries.`,
773
+ ``,
774
+ ...peChunks.map((c, i) => `<<<C ${i + 1}>>>\n${c}\n<<<END ${i + 1}>>>`),
775
+ ].join("\n"),
776
+ { label: `plain-english write (${peChunks.length} chunks, ${peExpectedEntries} entries)`, phase: "Plain-English", model: "sonnet" }
777
+ ).catch((e) => ({ _e: String(e && e.message) }));
778
+ // Independent verification by a second cheap agent (the writer self-reports; trust but verify).
779
+ const peVerify = await gatedAgent(
780
+ `Run \`grep -c '^### TD-' ${peTarget}\` and reply with ONLY the integer it prints.`,
781
+ { label: `plain-english verify-count`, phase: "Plain-English", model: "haiku" }
782
+ ).catch(() => null);
783
+ const peActual = (typeof peVerify === "string" && /\d+/.test(peVerify)) ? parseInt(peVerify.match(/\d+/)[0], 10) : null;
784
+ const peComplete = peActual === peExpectedEntries;
785
+ if (!peComplete) log(`⚠ plain-english INCOMPLETE: wrote ${peActual}/${peExpectedEntries} entries (writer said ${typeof peWriteRes === "string" ? peWriteRes.trim().slice(0, 20) : JSON.stringify(peWriteRes).slice(0, 40)})`);
786
+ log(`plain-english: ${peExpectedEntries}/${peItems.length} entries, grouped by severity, ${peChunks.length} chunks, on-disk count ${peActual ?? "?"} (${peComplete ? "COMPLETE" : "INCOMPLETE"})${peFailed ? `; ${peFailed} gen batch(es) failed` : ""}`);
757
787
 
758
788
  // Commit the docs + dimension files + plain-english via a small agent (Bash git).
759
789
  const commitAgent = await agent(
@@ -787,6 +817,11 @@ return {
787
817
  docsWritten: docsOk.length,
788
818
  docsFailed: docsFailed.map((d) => d.doc),
789
819
  docsCommitted: commitAgent && commitAgent.status,
820
+ // M80: plain-english completeness is surfaced, not silent. plainEnglishComplete=false
821
+ // means the companion doc is truncated (writer dropped entries) — the caller should flag it.
822
+ plainEnglishEntries: peActual,
823
+ plainEnglishExpected: peExpectedEntries,
824
+ plainEnglishComplete: peComplete,
790
825
  htmlReport: null, // render stage removed (M71)
791
826
  probeTotals: probe.totals,
792
827
  };
@@ -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");