@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.
@@ -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
- log(`M90 arch-trigger quick: fired=${triggerEnv.fired} reason=${triggerEnv.reason || "?"} provenByAdversaryOnly=${triggerEnv.provenByAdversaryOnly || false}`);
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 orthogonal triad`);
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
- `Return JSON per VERDICT_SCHEMA with blockingFindings listing concrete things that must fix.`,
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
  };