@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.
- package/CHANGELOG.md +31 -0
- package/README.md +91 -142
- package/bin/scan-data-collector.js +28 -0
- package/docs/architecture.md +231 -21
- package/docs/infrastructure.md +461 -245
- package/docs/requirements.md +226 -0
- package/docs/workflows.md +353 -4
- package/package.json +1 -1
- package/templates/CLAUDE-global.md +1 -1
- package/templates/workflows/gsd-t-debug.workflow.js +34 -5
- package/templates/workflows/gsd-t-execute.workflow.js +54 -29
- package/templates/workflows/gsd-t-integrate.workflow.js +37 -7
- package/templates/workflows/gsd-t-phase.workflow.js +36 -7
- package/templates/workflows/gsd-t-quick.workflow.js +59 -7
- package/templates/workflows/gsd-t-scan.workflow.js +49 -14
- package/templates/workflows/gsd-t-verify.workflow.js +67 -47
- package/templates/workflows/gsd-t-wave.workflow.js +7 -4
|
@@ -16,11 +16,41 @@ export const meta = {
|
|
|
16
16
|
],
|
|
17
17
|
};
|
|
18
18
|
|
|
19
|
-
|
|
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 =
|
|
22
|
-
const milestone =
|
|
23
|
-
const 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 =
|
|
71
|
+
const pre = await runPreflight(projectDir);
|
|
42
72
|
if (!pre.ok) return { status: "failed", reason: "preflight-failed", preflight: pre.envelope };
|
|
43
|
-
const brief =
|
|
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 =
|
|
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
|
-
|
|
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 =
|
|
49
|
-
const milestone =
|
|
50
|
-
const userInput =
|
|
51
|
-
const phaseName =
|
|
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 =
|
|
88
|
+
const pre = await runPreflight(projectDir);
|
|
60
89
|
if (!pre.ok) return { status: "failed", reason: "preflight-failed", preflight: pre.envelope };
|
|
61
|
-
const brief =
|
|
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
|
-
|
|
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
|
|
22
|
-
|
|
23
|
-
|
|
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 =
|
|
93
|
+
const pre = await runPreflight(projectDir);
|
|
42
94
|
if (!pre.ok) return { status: "failed", reason: "preflight-failed", preflight: pre.envelope };
|
|
43
|
-
const brief =
|
|
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 =
|
|
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
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
}
|
|
756
|
-
|
|
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
|
-
|
|
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 =
|
|
36
|
-
const milestone =
|
|
37
|
-
const skipUltra =
|
|
38
|
-
const skipUltraReason =
|
|
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 =
|
|
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 =
|
|
204
|
+
const brief = await generateBrief(projectDir, { kind: "verify", milestone, id: `verify-${(milestone || "m").toLowerCase()}` });
|
|
162
205
|
|
|
163
206
|
phase("Verify-Gate");
|
|
164
|
-
const vg =
|
|
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
|
|
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 =
|
|
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
|
-
|
|
225
|
-
|
|
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
|
-
//
|
|
237
|
-
//
|
|
238
|
-
|
|
239
|
-
const
|
|
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
|
-
|
|
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 =
|
|
20
|
-
const milestone =
|
|
21
|
-
const 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");
|