@tekyzinc/gsd-t 4.7.11 → 4.9.11
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 +46 -0
- package/README.md +1 -1
- package/bin/gsd-t-architectural-trigger.cjs +80 -5
- package/bin/gsd-t-divergence-grammar.cjs +213 -0
- package/bin/gsd-t-guard-map-derive.cjs +210 -0
- package/bin/gsd-t-guard-map.cjs +279 -0
- package/bin/gsd-t-jargon-lint.cjs +363 -0
- package/bin/gsd-t-milestone-state.cjs +236 -0
- package/bin/gsd-t-rule-consume.cjs +141 -0
- package/bin/gsd-t-shrink-metric.cjs +255 -0
- package/bin/gsd-t-traceability-gate.cjs +321 -24
- package/bin/gsd-t.js +61 -0
- package/commands/gsd-t-doc-ripple.md +1 -1
- package/commands/gsd-t-milestone.md +39 -1
- package/commands/gsd-t-quick.md +13 -6
- package/package.json +1 -1
- package/scripts/gsd-t-brevity-guard.js +340 -0
- package/templates/CLAUDE-global.md +18 -0
- package/templates/PseudoCode-spec.md +194 -0
- package/templates/prompts/blind-adversary-subagent.md +4 -0
- package/templates/prompts/keep-or-supersede-subagent.md +61 -0
- package/templates/prompts/pre-mortem-subagent.md +4 -0
- package/templates/prompts/qa-subagent.md +14 -0
- package/templates/prompts/red-team-subagent.md +16 -0
- package/templates/workflows/gsd-t-execute.workflow.js +7 -1
- package/templates/workflows/gsd-t-phase.workflow.js +22 -2
- package/templates/workflows/gsd-t-quick.workflow.js +16 -2
- package/templates/workflows/gsd-t-verify.workflow.js +183 -2
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
# QA Subagent Prompt — Per-Task Validation
|
|
2
2
|
|
|
3
|
+
<!-- reader-contract -->
|
|
4
|
+
**Report concisely:** verdict/answer first, no preamble. Gloss every code/jargon term (e.g. `M93-D2` = milestone 93, domain 2) in plain words on first use. Bullets over paragraphs. Expand only if asked.
|
|
5
|
+
<!-- /reader-contract -->
|
|
6
|
+
|
|
3
7
|
You are the QA agent. Your sole job is test generation, execution, and gap reporting. You write ZERO feature code. You never modify implementation files — only test files and reports.
|
|
4
8
|
|
|
5
9
|
<!-- M61 D7-T3: Workflow-stage invocation -->
|
|
@@ -8,6 +12,16 @@ You are the QA agent. Your sole job is test generation, execution, and gap repor
|
|
|
8
12
|
<!-- M55-D5: brief-first rule -->
|
|
9
13
|
**Brief first.** If you're about to grep, read, or run a test, check the brief first at `$BRIEF_PATH` (a ≤2,500-token JSON snapshot of CLAUDE.md + contracts + scope + constraints, generated by `bin/gsd-t-context-brief.cjs`). The brief replaces the 30–60k context re-read every parallel worker would otherwise perform — it's the dominant ITPM-relief lever in M55. If `$BRIEF_PATH` is unset or the file is missing, fall back to the legacy read-everything pattern, but log a note so the orchestrator can see the gap.
|
|
10
14
|
|
|
15
|
+
<!-- guard-map-ingest -->
|
|
16
|
+
**Guard-map rule ingest (A5 — contract-compliance frame).** When a guard-map is
|
|
17
|
+
present, the surfaced RULE-ID set reaches you via
|
|
18
|
+
`gsd-t rule-consume --map <guard-map.json> --json` → the `qa` array. INGEST that
|
|
19
|
+
set: treat **every RULE-ID as a required compliance assertion**. For each id, the
|
|
20
|
+
build/tests MUST demonstrate the rule's invariant holds — an unverified or
|
|
21
|
+
unasserted rule is a contract-compliance FAILURE, not a pass. Do NOT drop, skip,
|
|
22
|
+
or sample rules: every id in the `qa` frame is a mandatory check.
|
|
23
|
+
<!-- /guard-map-ingest -->
|
|
24
|
+
|
|
11
25
|
## What to Do
|
|
12
26
|
|
|
13
27
|
1. **Detect every configured test suite** in this project — vitest/jest/mocha config, `playwright.config.*`, `cypress.config.*`. Run EVERY suite that exists.
|
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
# Red Team Subagent Prompt — Adversarial QA (per-domain)
|
|
2
2
|
|
|
3
|
+
<!-- reader-contract -->
|
|
4
|
+
**Report concisely:** verdict/answer first, no preamble. Gloss every code/jargon term (e.g. `M93-D2` = milestone 93, domain 2) in plain words on first use. Bullets over paragraphs. Expand only if asked.
|
|
5
|
+
<!-- /reader-contract -->
|
|
6
|
+
|
|
3
7
|
You are a Red Team QA adversary. Your job is to BREAK the code that was just written for this domain. You operate with inverted incentives — your value is measured by REAL bugs found, not tests passed.
|
|
4
8
|
|
|
5
9
|
<!-- M61 D7-T3: Workflow-stage invocation -->
|
|
@@ -8,6 +12,18 @@ You are a Red Team QA adversary. Your job is to BREAK the code that was just wri
|
|
|
8
12
|
<!-- M55-D5: brief-first rule -->
|
|
9
13
|
**Brief first.** If you're about to grep, read, or run a test, check the brief first at `$BRIEF_PATH` (a ≤2,500-token JSON snapshot of CLAUDE.md + contracts + scope + constraints + recent commits, generated by `bin/gsd-t-context-brief.cjs --kind red-team`). The brief identifies high-risk surfaces, recent diffs, and contract status — your starting attack surface. The brief replaces the 30–60k context re-read every parallel worker would otherwise perform. If `$BRIEF_PATH` is unset or the file is missing, fall back to the legacy read-everything pattern, but log a note so the orchestrator can see the gap.
|
|
10
14
|
|
|
15
|
+
<!-- guard-map-ingest -->
|
|
16
|
+
**Guard-map rule ingest (A5 — attack-surface frame).** When a guard-map is
|
|
17
|
+
present, the surfaced RULE-ID set reaches you via
|
|
18
|
+
`gsd-t rule-consume --map <guard-map.json> --json` → the `redTeam` array. INGEST
|
|
19
|
+
that set: treat **every RULE-ID as an invariant to ATTACK**. For each id, mount a
|
|
20
|
+
concrete attack that tries to make the rule's invariant FALSE (boundary inputs,
|
|
21
|
+
race/double-submit, out-of-order state, error paths). A rule you cannot break
|
|
22
|
+
after an exhaustive attempt is a defended invariant; a rule you DROP from your
|
|
23
|
+
attack plan is a seam breach. Do NOT sample — every id in the `redTeam` frame
|
|
24
|
+
must be attacked.
|
|
25
|
+
<!-- /guard-map-ingest -->
|
|
26
|
+
|
|
11
27
|
## Hard Rules
|
|
12
28
|
|
|
13
29
|
- **Bugs found = value.** A short attack list is failure.
|
|
@@ -548,9 +548,15 @@ for (const dr of domainResults.filter(Boolean)) {
|
|
|
548
548
|
reason: triggerEnv.reason || null,
|
|
549
549
|
provenByAdversaryOnly: triggerEnv.provenByAdversaryOnly || false,
|
|
550
550
|
stopDirective: triggerEnv.stopDirective || false,
|
|
551
|
+
// M92 — surface the cheaper-first response rung + its directive (look→smallest→spike→defer)
|
|
552
|
+
// so the "look at what exists / smallest change" guidance reaches the worker.
|
|
553
|
+
mode: triggerEnv.mode || null,
|
|
554
|
+
responseDirective: triggerEnv.lookDirective || triggerEnv.smallestDirective || triggerEnv.deferDirective || null,
|
|
551
555
|
});
|
|
552
556
|
}
|
|
553
|
-
log(`M90 arch-trigger: ${domainArchTriggerResults.length} domain(s) fired extend-existing-code signal`
|
|
557
|
+
log(`M90 arch-trigger: ${domainArchTriggerResults.length} domain(s) fired extend-existing-code signal` +
|
|
558
|
+
(domainArchTriggerResults.some((r) => r.mode === "look")
|
|
559
|
+
? ` — M92 default response: LOOK (grep/read existing before scoping)` : ""));
|
|
554
560
|
|
|
555
561
|
phase("Integrate");
|
|
556
562
|
const integratePrompt = [
|
|
@@ -555,9 +555,19 @@ const _PROBE_SCHEMA = {
|
|
|
555
555
|
approaches: { type: "array", items: { type: "string" } },
|
|
556
556
|
},
|
|
557
557
|
};
|
|
558
|
-
async function runSolutionSpaceProbe(projectDir, phaseName, { milestone, briefPath, userInput, phaseNameOpt } = {}) {
|
|
558
|
+
async function runSolutionSpaceProbe(projectDir, phaseName, { milestone, briefPath, userInput, phaseNameOpt, altitude } = {}) {
|
|
559
|
+
// M87 altitude shift: when behavior is SPEC'D (the milestone is authored at the
|
|
560
|
+
// intention-first high-level-approach altitude), the milestone solution-space
|
|
561
|
+
// probe shifts UP — it competes over high-level APPROACHES (what/why/when, actors,
|
|
562
|
+
// one-breath thesis), NOT lower-altitude implementation detail. This changes WHAT
|
|
563
|
+
// the producers compete on, never WHO competes (producers stay opus, judge differs —
|
|
564
|
+
// M82 blindness invariant preserved) nor the probe's model (stays fable).
|
|
565
|
+
const atApproachAltitude = altitude === "high-level-approach";
|
|
559
566
|
const prompt = [
|
|
560
567
|
`You are the Solution-Space Probe for the ${phaseName} phase${milestone ? ` of ${milestone}` : ""}. Decide ONE thing: should this phase generate MULTIPLE competing candidates (then a judge picks the best), or is a single draft sufficient?`,
|
|
568
|
+
atApproachAltitude
|
|
569
|
+
? `ALTITUDE: HIGH-LEVEL APPROACH (M87 — behavior is spec'd intention-first). Compete over distinct high-level APPROACHES — what/why/when, the actors, the one-breath thesis — NOT over field-level implementation detail. The competition is about WHICH approach best serves the intention, decided before the detailed PseudoCode-[Title].md is authored.`
|
|
570
|
+
: "",
|
|
561
571
|
`**Brief:** ${briefPath || "(none — read the relevant .gsd-t docs/contracts/requirements directly)"}`,
|
|
562
572
|
userInput ? `\nUser input:\n${userInput}\n` : "",
|
|
563
573
|
`Compete WHEN there are ≥2 genuinely DIFFERENT, viable approaches whose trade-offs matter — different architectures, decomposition strategies, data models, sequencing, or design directions that a reasonable expert could disagree about. List them in "approaches".`,
|
|
@@ -690,9 +700,13 @@ if (_competitionEligible) {
|
|
|
690
700
|
// Automatic decision — the workflow probes and decides. Opus probe (or the
|
|
691
701
|
// partition-specific probe); biased toward competing.
|
|
692
702
|
phase("Probe");
|
|
703
|
+
// M87: the milestone phase is authored intention-first — behavior is spec'd at the
|
|
704
|
+
// high-level-approach altitude, so its solution-space probe shifts UP to compete over
|
|
705
|
+
// high-level approaches (not implementation detail). Other phases keep their altitude.
|
|
706
|
+
const _probeAltitude = phaseName === "milestone" ? "high-level-approach" : undefined;
|
|
693
707
|
const probe = phaseName === "partition"
|
|
694
708
|
? await runPartitionProbe(projectDir, { milestone, briefPath: brief.briefPath, userInput, phaseNameOpt: "Probe" })
|
|
695
|
-
: await runSolutionSpaceProbe(projectDir, phaseName, { milestone, briefPath: brief.briefPath, userInput, phaseNameOpt: "Probe" });
|
|
709
|
+
: await runSolutionSpaceProbe(projectDir, phaseName, { milestone, briefPath: brief.briefPath, userInput, phaseNameOpt: "Probe", altitude: _probeAltitude });
|
|
696
710
|
competitionOn = !!probe.compete;
|
|
697
711
|
competitionN = competitionOn ? AUTO_COMPETITION_N : 1;
|
|
698
712
|
log(`competition: AUTO → ${competitionOn ? `COMPETE (${AUTO_COMPETITION_N} producers)` : "single draft"} — ${probe.reason}${probe.approaches && probe.approaches.length ? ` [approaches: ${probe.approaches.join("; ")}]` : ""}`);
|
|
@@ -720,6 +734,12 @@ ${STATED_CLAIMS_INSTRUCTION}`,
|
|
|
720
734
|
${STATED_CLAIMS_INSTRUCTION}`,
|
|
721
735
|
milestone: `Define a new milestone — origin, goal, success criteria, falsifiable acceptance. Append to .gsd-t/progress.md. Defer partition/plan.
|
|
722
736
|
|
|
737
|
+
TWO-ALTITUDE INTENTION-FIRST FLOW (M87, default-ON — contract pseudocode-source-of-truth-contract.md §1). Author the milestone at two altitudes, IN ORDER:
|
|
738
|
+
ALTITUDE 1 — HIGH-LEVEL APPROACH (signed off FIRST): emit the high-level approach pseudocode — what/why/when (the user's intention, never agent reasoning), the actors, and a one-breath summary ("one call in one breath"). PRESENT this approach to the user for SIGN-OFF. The detailed doc is authored ONLY AFTER the approach is approved — the sign-off is the checkpoint between the two altitudes. (This is a PROSE flow: describe the checkpoint; do NOT assert a machine-checkable DEFINED-state predicate — that is M88.)
|
|
739
|
+
ALTITUDE 2 — DETAILED doc: only after the approach is signed off, author .gsd-t/pseudocode/PseudoCode-[Title].md at exemplar granularity (the §1 section set: Intention, Mechanism, one-breath table, Guard map, Divergence flags, Appendix). [Title] = the SUBJECT (e.g. PseudoCode-PayPal.md), never a milestone id; a milestone may produce several.
|
|
740
|
+
DEFAULT-ON; skip is a LOGGED decision in progress.md naming WHY — NEVER a silent default-off (feedback_no_silent_degradation).
|
|
741
|
+
KEEP-OR-SUPERSEDE: before encoding any model inherited from shipped code, run the keep-or-supersede protocol (templates/prompts/keep-or-supersede-subagent.md) — per inherited model ASK keep or supersede; each supersede WRITES a ⚠ Divergence flag (§4 grammar) into the doc. Keep = no flag.
|
|
742
|
+
|
|
723
743
|
${STATED_CLAIMS_INSTRUCTION}`,
|
|
724
744
|
prd: `Generate a product requirements doc at docs/prd.md. Functional + non-functional requirements traceable to acceptance criteria.`,
|
|
725
745
|
"design-decompose": `Decompose a design reference (Figma URL / images) into hierarchical contracts: elements -> widgets -> pages, each at .gsd-t/contracts/design/.`,
|
|
@@ -182,8 +182,17 @@ const result = await agent(
|
|
|
182
182
|
`Quick task: ${task}`,
|
|
183
183
|
`**Brief:** ${brief.briefPath || "(no brief)"}`,
|
|
184
184
|
``,
|
|
185
|
+
`**CRUX-FIRST — START HERE (smallest change is the default, ceremony is opt-in):**`,
|
|
186
|
+
`1. CRUX: state the crux of this ask in ONE line — the single thing that must become true.`,
|
|
187
|
+
`2. WHAT EXISTS: grep/read what ALREADY exists for this before choosing any scope.`,
|
|
188
|
+
` Do not build outward until you know what is already here to edit.`,
|
|
189
|
+
`3. SMALLEST CHANGE: propose the SMALLEST change that hits the crux — edit INWARD at the`,
|
|
190
|
+
` source (the one place the behavior is defined), not OUTWARD at the N consumers.`,
|
|
191
|
+
` The recommendation is "do it directly / one-file change," not a partition or plan.`,
|
|
192
|
+
`4. ESCALATE ONLY IF NEEDED: reach for ceremony (plan→execute, partition, competition) ONLY`,
|
|
193
|
+
` when the crux genuinely needs cross-domain coordination or real uncertainty — and say WHY.`,
|
|
194
|
+
``,
|
|
185
195
|
`Constraints from CLAUDE.md:`,
|
|
186
|
-
`- SIMPLICITY ABOVE ALL — minimal change`,
|
|
187
196
|
`- Check downstream effects before changing existing code`,
|
|
188
197
|
`- Run affected tests before reporting done`,
|
|
189
198
|
`- Update relevant docs in the same commit`,
|
|
@@ -372,13 +381,18 @@ if (Array.isArray(result.filesEdited) && result.filesEdited.length > 0) {
|
|
|
372
381
|
"Research"
|
|
373
382
|
);
|
|
374
383
|
const triggerEnv = triggerResult.envelope || {};
|
|
375
|
-
|
|
384
|
+
const m92Directive = triggerEnv.lookDirective || triggerEnv.smallestDirective || triggerEnv.deferDirective || null;
|
|
385
|
+
log(`M90 arch-trigger quick: fired=${triggerEnv.fired} reason=${triggerEnv.reason || "?"} provenByAdversaryOnly=${triggerEnv.provenByAdversaryOnly || false}` +
|
|
386
|
+
(triggerEnv.mode ? ` — M92 response: ${triggerEnv.mode}${triggerEnv.mode === "look" ? " (grep/read existing before scoping)" : ""}` : ""));
|
|
376
387
|
quickArchTriggerResult = {
|
|
377
388
|
existingFiles,
|
|
378
389
|
fired: triggerEnv.fired || false,
|
|
379
390
|
reason: triggerEnv.reason || null,
|
|
380
391
|
provenByAdversaryOnly: triggerEnv.provenByAdversaryOnly || false,
|
|
381
392
|
stopDirective: triggerEnv.stopDirective || false,
|
|
393
|
+
// M92 — cheaper-first response rung + its directive surfaced to the quick worker.
|
|
394
|
+
mode: triggerEnv.mode || null,
|
|
395
|
+
responseDirective: m92Directive,
|
|
382
396
|
};
|
|
383
397
|
}
|
|
384
398
|
}
|
|
@@ -39,6 +39,7 @@ export const meta = {
|
|
|
39
39
|
{ title: "Auto-Research Gate", detail: "M89 §7 ENFORCE: scan for status=uncited markers (A4 — no silent guess)" },
|
|
40
40
|
{ title: "CI-Parity", detail: "M57 build-coverage + ci-parity (FAIL-blocking)" },
|
|
41
41
|
{ title: "Test-Data Purge", detail: "M58 test-data --purge (FAIL-blocking)" },
|
|
42
|
+
{ title: "Guard-Map Gate", detail: "M87 [RULE] guard-map gate (FAIL-blocking, §7 discovery)" },
|
|
42
43
|
{ title: "Orthogonal Triad", detail: "code-review ultra ∥ Red Team ∥ QA" },
|
|
43
44
|
{ title: "Synthesis", detail: "merge without collapsing categories" },
|
|
44
45
|
],
|
|
@@ -77,6 +78,13 @@ async function runCli(projectDir, subcmd, argv, localBin, label, parseJson = tru
|
|
|
77
78
|
}
|
|
78
79
|
async function runPreflight(projectDir, label = "preflight", phaseName) { return runCli(projectDir, "preflight", ["--json"], "cli-preflight.cjs", label, true, phaseName); }
|
|
79
80
|
async function runVerifyGate(projectDir, label = "verify-gate", phaseName) { return runCli(projectDir, "verify-gate", ["--json"], "gsd-t-verify-gate.cjs", label, true, phaseName); }
|
|
81
|
+
// M92 D2 (keystone) — the deterministic shrink-metric. Computes the milestone diff's
|
|
82
|
+
// net size from `git diff --numstat <range>` via the shared runCli helper (M71-clean:
|
|
83
|
+
// the git call runs in the agent's Bash, NOT via require/fs/child_process here). model:
|
|
84
|
+
// "haiku" (mechanical), like the other deterministic-gate CLI calls.
|
|
85
|
+
async function runShrinkMetric(projectDir, range, label = "m92:shrink-metric", phaseName) {
|
|
86
|
+
return runCli(projectDir, "shrink-metric", ["--range", range, "--project-dir", projectDir, "--json"], "gsd-t-shrink-metric.cjs", label, true, phaseName);
|
|
87
|
+
}
|
|
80
88
|
async function generateBrief(projectDir, { kind = "verify", milestone, domain, id, label = "brief", phaseName } = {}) {
|
|
81
89
|
const argv = ["--kind", kind, "--spawn-id", id, "--out", `${projectDir}/.gsd-t/briefs/${id}.json`];
|
|
82
90
|
if (milestone) argv.push("--milestone", milestone);
|
|
@@ -205,6 +213,20 @@ const VERDICT_SCHEMA = {
|
|
|
205
213
|
overallVerdict: { type: "string", enum: ["VERIFIED", "VERIFIED-WITH-WARNINGS", "VERIFY-FAILED"] },
|
|
206
214
|
summary: { type: "string" },
|
|
207
215
|
blockingFindings: { type: "array", items: { type: "string" } },
|
|
216
|
+
// M92 D2 (keystone) — ADDITIVE, ORTHOGONAL "did it get leaner" readout. The
|
|
217
|
+
// 3-enum stays the pure correctness gate (AND-of-gates); `shrink` is a SEPARATE
|
|
218
|
+
// success dimension surfaced ALONGSIDE it (never folded into pass/fail). MEASURED
|
|
219
|
+
// by bin/gsd-t-shrink-metric.cjs from `git diff --numstat`, never LLM-attested.
|
|
220
|
+
// Optional: omitted when the diff base is unavailable (logged skip-with-reason).
|
|
221
|
+
shrink: {
|
|
222
|
+
type: "object",
|
|
223
|
+
required: ["netLoc", "leaner"],
|
|
224
|
+
additionalProperties: true,
|
|
225
|
+
properties: {
|
|
226
|
+
netLoc: { type: "number" },
|
|
227
|
+
leaner: { type: "boolean" },
|
|
228
|
+
},
|
|
229
|
+
},
|
|
208
230
|
},
|
|
209
231
|
};
|
|
210
232
|
|
|
@@ -517,7 +539,105 @@ if (!td.ok) {
|
|
|
517
539
|
log(`M58 test-data purge FAIL exitCode=${td.exitCode} — halting (FAIL-blocking)`);
|
|
518
540
|
return { status: "test-data-purge-failed", overallVerdict: "VERIFY-FAILED", testDataPurge: td.envelope };
|
|
519
541
|
}
|
|
520
|
-
log(`M58 test-data purge green — proceeding to
|
|
542
|
+
log(`M58 test-data purge green — proceeding to guard-map gate`);
|
|
543
|
+
|
|
544
|
+
// ─── M87 D1 Guard-Map Gate (FAIL-blocking — A1 / SC1) ─────────────────────
|
|
545
|
+
// Turns each PseudoCode-[Title].md prose `[RULE]` guard map into a
|
|
546
|
+
// machine-checkable gate: a divergent (UNBACKED or CONTRADICTED) rule HALTS
|
|
547
|
+
// verify BEFORE the triad, at contract-breach severity, naming the RULE-ID —
|
|
548
|
+
// zero LLM judgment in the pass/fail decision (deterministic, like verify-gate /
|
|
549
|
+
// CI-parity / test-data).
|
|
550
|
+
//
|
|
551
|
+
// Discovery per contract §7 (deterministic, path-as-path):
|
|
552
|
+
// 1. glob `.gsd-t/pseudocode/PseudoCode-*.md` (multi-doc — a milestone may
|
|
553
|
+
// produce several subjects).
|
|
554
|
+
// 2. each doc's map = same basename with `.md` → `.map.json`, co-located.
|
|
555
|
+
// 3. pairing outcomes (each OBSERVABLE, never a silent pass):
|
|
556
|
+
// - doc + co-located map → FIRE the gate on that pair.
|
|
557
|
+
// - doc with no map → logged skip-with-reason `no-build-map`.
|
|
558
|
+
// - zero docs → logged skip-with-reason `no-pseudocode-docs`.
|
|
559
|
+
// 4. the fire path passes `--doc <doc> --map <map>` to gsd-t-guard-map.cjs; any
|
|
560
|
+
// FAIL-blocking non-zero HALTS verify before the triad.
|
|
561
|
+
// M81: no fs — a haiku discovery agent globs + pairs and returns the set; each
|
|
562
|
+
// fire-able pair is gated via the runCli helper (model: haiku, like the other gates).
|
|
563
|
+
// Contract: pseudocode-source-of-truth-contract.md §2 + §7.
|
|
564
|
+
phase("Guard-Map Gate");
|
|
565
|
+
const GUARD_MAP_DISCOVERY_SCHEMA = {
|
|
566
|
+
type: "object",
|
|
567
|
+
required: ["docsFound", "firePairs", "skips"],
|
|
568
|
+
additionalProperties: true,
|
|
569
|
+
properties: {
|
|
570
|
+
docsFound: { type: "integer" },
|
|
571
|
+
firePairs: {
|
|
572
|
+
type: "array",
|
|
573
|
+
items: {
|
|
574
|
+
type: "object",
|
|
575
|
+
required: ["doc", "map"],
|
|
576
|
+
properties: { doc: { type: "string" }, map: { type: "string" } },
|
|
577
|
+
},
|
|
578
|
+
},
|
|
579
|
+
skips: {
|
|
580
|
+
type: "array",
|
|
581
|
+
items: {
|
|
582
|
+
type: "object",
|
|
583
|
+
required: ["reason"],
|
|
584
|
+
properties: { reason: { type: "string" }, doc: { type: "string" } },
|
|
585
|
+
},
|
|
586
|
+
},
|
|
587
|
+
notes: { type: "string" },
|
|
588
|
+
},
|
|
589
|
+
};
|
|
590
|
+
const guardMapDiscovery = await agent(
|
|
591
|
+
[
|
|
592
|
+
`You are the M87 guard-map discovery scanner for the project at "${projectDir}". Discover the PseudoCode doc + build-map pairs per contract §7 (deterministic, path-as-path) and return JSON. Do NOT gate anything — only enumerate.`,
|
|
593
|
+
``,
|
|
594
|
+
`Steps:`,
|
|
595
|
+
`1. Glob the docs: \`ls ${projectDir}/.gsd-t/pseudocode/PseudoCode-*.md 2>/dev/null || true\`. These are the candidate docs (a milestone may produce SEVERAL — handle the multi-doc set).`,
|
|
596
|
+
`2. docsFound = the number of PseudoCode-*.md docs found.`,
|
|
597
|
+
`3. For EACH doc, the build-map is the SAME basename with \`.md\` replaced by \`.map.json\`, co-located in \`.gsd-t/pseudocode/\` (e.g. PseudoCode-Foo.md → PseudoCode-Foo.map.json). Check it exists: \`test -f <map> && echo EXISTS || echo ABSENT\`.`,
|
|
598
|
+
` - doc + co-located map EXISTS → add { "doc": "<abs-or-project-rel doc path>", "map": "<map path>" } to firePairs.`,
|
|
599
|
+
` - doc with map ABSENT → add { "reason": "no-build-map", "doc": "<doc path>" } to skips (a logged skip WITH A REASON, never a silent pass).`,
|
|
600
|
+
`4. If docsFound === 0 → firePairs is empty and skips = [ { "reason": "no-pseudocode-docs" } ] (the gate legitimately has nothing to gate, but the skip is SURFACED, not silent).`,
|
|
601
|
+
``,
|
|
602
|
+
`Return JSON per the schema: { "docsFound": N, "firePairs": [ { "doc", "map" } ], "skips": [ { "reason", "doc"? } ], "notes": "..." }. Discovery is path-as-path (glob + basename derivation), never substring.`,
|
|
603
|
+
].join("\n"),
|
|
604
|
+
{ label: "guard-map-discovery", phase: "Guard-Map Gate", schema: GUARD_MAP_DISCOVERY_SCHEMA, model: "haiku" }
|
|
605
|
+
).catch((e) => ({ docsFound: 0, firePairs: [], skips: [{ reason: "discovery-error" }], notes: `guard-map discovery agent error: ${e && e.message}` }));
|
|
606
|
+
|
|
607
|
+
const guardMapResults = [];
|
|
608
|
+
if (guardMapDiscovery.docsFound === 0 || (guardMapDiscovery.firePairs || []).length === 0) {
|
|
609
|
+
// No fire-able pair — surface the DISTINCT skip reason(s), never a silent pass.
|
|
610
|
+
const reasons = (guardMapDiscovery.skips || []).map((s) => s.reason).join(", ") || "no-pseudocode-docs";
|
|
611
|
+
log(`M87 guard-map gate: SKIP (no fire-able doc+map pair) — reason(s): ${reasons}`);
|
|
612
|
+
} else {
|
|
613
|
+
for (const pair of guardMapDiscovery.firePairs) {
|
|
614
|
+
const gmr = await runCli(
|
|
615
|
+
projectDir,
|
|
616
|
+
"guard-map",
|
|
617
|
+
["--doc", pair.doc, "--map", pair.map, "--json"],
|
|
618
|
+
"gsd-t-guard-map.cjs",
|
|
619
|
+
`m87:guard-map:${pair.doc.split("/").pop()}`,
|
|
620
|
+
true,
|
|
621
|
+
"Guard-Map Gate"
|
|
622
|
+
);
|
|
623
|
+
guardMapResults.push({ pair, envelope: gmr.envelope, ok: gmr.ok, exitCode: gmr.exitCode });
|
|
624
|
+
if (!gmr.ok) {
|
|
625
|
+
const env = gmr.envelope || {};
|
|
626
|
+
const named = (env.violations || []).map((v) => v.id).join(", ") || env.reason || "(rule id unavailable)";
|
|
627
|
+
log(`M87 guard-map gate FAIL exitCode=${gmr.exitCode} on ${pair.doc} — divergent RULE(s): ${named} — halting before triad`);
|
|
628
|
+
return {
|
|
629
|
+
status: "guard-map-gate-failed",
|
|
630
|
+
overallVerdict: "VERIFY-FAILED",
|
|
631
|
+
reason: `M87 guard-map divergence on ${pair.doc}: ${named} (unbacked or contradicted [RULE]) — back/cite the rule, then re-verify`,
|
|
632
|
+
guardMap: { discovery: guardMapDiscovery, results: guardMapResults },
|
|
633
|
+
verifyGate: vg.envelope,
|
|
634
|
+
autoResearchGate: arGate,
|
|
635
|
+
};
|
|
636
|
+
}
|
|
637
|
+
log(`M87 guard-map gate: PASS on ${pair.doc} — all ${(gmr.envelope && gmr.envelope.ruleCount) || "?"} [RULE]s backed, none contradicted`);
|
|
638
|
+
}
|
|
639
|
+
log(`M87 guard-map gate green (${guardMapResults.length} doc+map pair(s) fired) — proceeding to orthogonal triad`);
|
|
640
|
+
}
|
|
521
641
|
|
|
522
642
|
phase("Orthogonal Triad");
|
|
523
643
|
|
|
@@ -594,6 +714,55 @@ const stages = [
|
|
|
594
714
|
|
|
595
715
|
const triadResults = await parallel(stages);
|
|
596
716
|
|
|
717
|
+
// ─── M92 D2 Shrink-Metric (keystone — ADDITIVE leanness dimension) ─────────
|
|
718
|
+
// Before synthesis, MEASURE the milestone diff's net size so the verdict can SAY
|
|
719
|
+
// "we made it smaller" — a first-class, ORTHOGONAL success dimension surfaced
|
|
720
|
+
// ALONGSIDE the overallVerdict enum (never folded into pass/fail). MEASURED, never
|
|
721
|
+
// LLM-attested ([[feedback_measure_dont_claim]]): the metric is computed by
|
|
722
|
+
// bin/gsd-t-shrink-metric.cjs from `git diff --numstat <base>..HEAD`.
|
|
723
|
+
//
|
|
724
|
+
// The diff base = the milestone branch-point (merge-base of HEAD against the default
|
|
725
|
+
// branch). M71-clean: BOTH the merge-base resolution AND the numstat run go through an
|
|
726
|
+
// agent's Bash, never require/fs/child_process in this orchestrator. If the base can't
|
|
727
|
+
// be resolved (detached HEAD, shallow clone, no default branch), the metric is SKIPPED
|
|
728
|
+
// with a logged reason — NEVER fabricated ([[feedback_no_silent_degradation]]).
|
|
729
|
+
const SHRINK_BASE_SCHEMA = {
|
|
730
|
+
type: "object",
|
|
731
|
+
required: ["resolved"],
|
|
732
|
+
additionalProperties: true,
|
|
733
|
+
properties: { resolved: { type: "boolean" }, base: { type: "string" }, reason: { type: "string" } },
|
|
734
|
+
};
|
|
735
|
+
const shrinkBase = await agent(
|
|
736
|
+
[
|
|
737
|
+
`Resolve the milestone diff BASE for the git repo at "${projectDir}" (the branch-point of the current work). Run ONLY git, report JSON. Steps:`,
|
|
738
|
+
`1. Find the default branch tip. Try in order, using the first that resolves to a commit (cwd "${projectDir}"):`,
|
|
739
|
+
` a. \`git -C "${projectDir}" rev-parse --verify --quiet origin/HEAD\` (then its symbolic target), else`,
|
|
740
|
+
` b. \`git -C "${projectDir}" rev-parse --verify --quiet main\`, else`,
|
|
741
|
+
` c. \`git -C "${projectDir}" rev-parse --verify --quiet master\`.`,
|
|
742
|
+
`2. Compute the merge-base of HEAD and that default tip: \`git -C "${projectDir}" merge-base HEAD <defaultTip>\`.`,
|
|
743
|
+
`3. If a merge-base commit is produced AND it differs from HEAD's own sha (there IS a diff to measure), return { "resolved": true, "base": "<merge-base-sha>" }.`,
|
|
744
|
+
`4. If NO default branch resolves, OR merge-base fails, OR merge-base === HEAD (nothing to measure), return { "resolved": false, "reason": "<short reason, e.g. no default branch / detached / merge-base===HEAD>" }.`,
|
|
745
|
+
`Do NOT do any other work. ONLY run git and report.`,
|
|
746
|
+
].join("\n"),
|
|
747
|
+
{ label: "m92:shrink-base", phase: "Synthesis", schema: SHRINK_BASE_SCHEMA, model: "haiku" }
|
|
748
|
+
).catch((e) => ({ resolved: false, reason: `shrink-base agent error: ${e && e.message}` }));
|
|
749
|
+
|
|
750
|
+
let shrink = null;
|
|
751
|
+
let shrinkSkipReason = null;
|
|
752
|
+
if (shrinkBase && shrinkBase.resolved && shrinkBase.base) {
|
|
753
|
+
const sm = await runShrinkMetric(projectDir, `${shrinkBase.base}..HEAD`, "m92:shrink-metric", "Synthesis");
|
|
754
|
+
if (sm.ok && sm.envelope && typeof sm.envelope.netLoc === "number" && typeof sm.envelope.leaner === "boolean") {
|
|
755
|
+
shrink = { netLoc: sm.envelope.netLoc, leaner: sm.envelope.leaner, insertions: sm.envelope.insertions, deletions: sm.envelope.deletions, filesAdded: sm.envelope.filesAdded, filesRemoved: sm.envelope.filesRemoved, filesModified: sm.envelope.filesModified };
|
|
756
|
+
log(`M92 shrink-metric: netLoc=${shrink.netLoc} leaner=${shrink.leaner} (+${shrink.insertions}/-${shrink.deletions}) — surfacing as the orthogonal leanness dimension`);
|
|
757
|
+
} else {
|
|
758
|
+
shrinkSkipReason = `shrink-metric CLI did not return a usable metric (exitCode=${sm.exitCode}) — SKIPPED, not fabricated`;
|
|
759
|
+
log(`M92 shrink-metric: SKIP — ${shrinkSkipReason}`);
|
|
760
|
+
}
|
|
761
|
+
} else {
|
|
762
|
+
shrinkSkipReason = `diff base unavailable (${(shrinkBase && shrinkBase.reason) || "unknown"}) — shrink-metric SKIPPED, never fabricated`;
|
|
763
|
+
log(`M92 shrink-metric: SKIP — ${shrinkSkipReason}`);
|
|
764
|
+
}
|
|
765
|
+
|
|
597
766
|
phase("Synthesis");
|
|
598
767
|
const synthesisPrompt = [
|
|
599
768
|
`You are the synthesis agent. Three orthogonal validators have run.`,
|
|
@@ -611,7 +780,15 @@ const synthesisPrompt = [
|
|
|
611
780
|
`- VERIFIED-WITH-WARNINGS if: Red Team GRUDGING-PASS, QA suite green, contracts compliant, AND any of: code-review ultra has "important" findings, OR skipUltra=true (reason: ${skipUltraReason || "(none — would have failed above)"}), OR QA shallowTests.length === 1 (single non-core).`,
|
|
612
781
|
`- VERIFY-FAILED otherwise (Red Team FAIL, QA fail>0, contract violations>0, shallowTests ≥ 2 or in core paths).`,
|
|
613
782
|
``,
|
|
614
|
-
|
|
783
|
+
`**M92 SHRINK DIMENSION (ORTHOGONAL — do NOT fold into pass/fail):** a deterministic shrink-metric measured the milestone diff:`,
|
|
784
|
+
shrink
|
|
785
|
+
? ` shrink = ${JSON.stringify({ netLoc: shrink.netLoc, leaner: shrink.leaner })} (full: +${shrink.insertions}/-${shrink.deletions}, files +${shrink.filesAdded}/-${shrink.filesRemoved}/~${shrink.filesModified})`
|
|
786
|
+
: ` shrink = (SKIPPED — ${shrinkSkipReason}; report it as not-measured, NEVER fabricate a value)`,
|
|
787
|
+
shrink
|
|
788
|
+
? ` Copy this measured shrink object VERBATIM into the "shrink" field of your output ({ "netLoc": ${shrink.netLoc}, "leaner": ${shrink.leaner} }). It is MEASURED — do not recompute or alter it. If leaner=true, explicitly ACKNOWLEDGE in the summary that the milestone made the codebase smaller (net-negative LOC) as a SUCCESS dimension, ORTHOGONAL to the correctness verdict — a VERIFIED-or-not correctness result and a leaner:true result are independent and BOTH reported.`
|
|
789
|
+
: ` OMIT the "shrink" field (the metric was skipped — say so in the summary; do NOT invent a netLoc/leaner).`,
|
|
790
|
+
``,
|
|
791
|
+
`Return JSON per VERDICT_SCHEMA. Include blockingFindings listing concrete things that must fix${shrink ? ", and the measured shrink object" : ""}.`,
|
|
615
792
|
].join("\n");
|
|
616
793
|
|
|
617
794
|
const verdict = await agent(synthesisPrompt, {
|
|
@@ -629,6 +806,10 @@ return {
|
|
|
629
806
|
buildCoverage: bc.envelope,
|
|
630
807
|
ciParity: cip.envelope,
|
|
631
808
|
testDataPurge: td.envelope,
|
|
809
|
+
guardMap: { discovery: guardMapDiscovery, results: guardMapResults },
|
|
632
810
|
triad: triadResults,
|
|
811
|
+
// M92 D2 (keystone) — the measured leanness dimension, surfaced ALONGSIDE the
|
|
812
|
+
// overallVerdict enum (orthogonal; null + a reason when the diff base was unavailable).
|
|
813
|
+
shrink: shrink || { skipped: true, reason: shrinkSkipReason },
|
|
633
814
|
verdict,
|
|
634
815
|
};
|