baldart 4.89.2 → 4.92.1

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.
Files changed (34) hide show
  1. package/CHANGELOG.md +87 -0
  2. package/VERSION +1 -1
  3. package/framework/.claude/agents/api-perf-cost-auditor.md +9 -0
  4. package/framework/.claude/agents/code-reviewer.md +9 -0
  5. package/framework/.claude/agents/codebase-architect.md +6 -0
  6. package/framework/.claude/agents/coder.md +1 -1
  7. package/framework/.claude/agents/markup-fidelity-verifier.md +24 -1
  8. package/framework/.claude/agents/qa-sentinel.md +9 -0
  9. package/framework/.claude/agents/security-reviewer.md +9 -0
  10. package/framework/.claude/agents/ui-expert.md +49 -0
  11. package/framework/.claude/agents/visual-fidelity-verifier.md +16 -12
  12. package/framework/.claude/skills/e2e-review/CHANGELOG.md +59 -0
  13. package/framework/.claude/skills/e2e-review/SKILL.md +187 -26
  14. package/framework/.claude/skills/new/CHANGELOG.md +29 -0
  15. package/framework/.claude/skills/new/SKILL.md +44 -2
  16. package/framework/.claude/skills/new/references/completeness.md +1 -0
  17. package/framework/.claude/skills/new/references/implement.md +19 -3
  18. package/framework/.claude/skills/new/references/review-cycle.md +23 -1
  19. package/framework/.claude/skills/new/references/team-mode.md +4 -0
  20. package/framework/.claude/skills/new/scripts/verify-item.sh +54 -0
  21. package/framework/.claude/skills/new2/CHANGELOG.md +46 -0
  22. package/framework/.claude/skills/new2/SKILL.md +58 -10
  23. package/framework/.claude/skills/ui-implement/CHANGELOG.md +20 -0
  24. package/framework/.claude/skills/ui-implement/SKILL.md +37 -7
  25. package/framework/.claude/skills/ui-implement/references/integration.md +12 -3
  26. package/framework/.claude/skills/ui-implement/scripts/check-bindings.mjs +103 -0
  27. package/framework/.claude/workflows/new-card-review.js +19 -5
  28. package/framework/.claude/workflows/new-final-review.js +24 -9
  29. package/framework/.claude/workflows/new2.js +84 -5
  30. package/framework/agents/code-search-protocol.md +24 -0
  31. package/framework/agents/design-system-protocol.md +27 -0
  32. package/framework/docs/WORKFLOWS.md +9 -4
  33. package/framework/scripts/structural-compare.mjs +183 -0
  34. package/package.json +1 -1
@@ -203,6 +203,12 @@ const PREFLIGHT_SCHEMA = {
203
203
  // F-016 — ACs whose only implementation file is outside the card MAY-EDIT,
204
204
  // pre-classified deferred-by-policy (never routed to resolve).
205
205
  policyDeferredACs: { type: 'array', items: { type: 'object', additionalProperties: true } },
206
+ // T1.A (v4.90.0) — the mockup's code-form sources, so the workflow can run the
207
+ // route-free structural fidelity lane (markup-fidelity-verifier) without
208
+ // re-reading the card. Empty string when absent.
209
+ designSrcDir: { type: 'string', description: "abs path of links.design_src / mockups/_src dir ('' when absent)" },
210
+ designHtml: { type: 'string', description: "abs path of an .html links.design mockup ('' when absent)" },
211
+ hasBindings: { type: 'boolean', description: 'card carries a non-empty component_bindings map' },
206
212
  },
207
213
  },
208
214
  },
@@ -277,7 +283,7 @@ try {
277
283
  preflight = await agentSafe(
278
284
  `You are the deterministic PRE-FLIGHT for an autonomous /new batch (variant new2). Follow ${REF}/setup.md (Phase 0 + Pre-flight) and ${REF}/implement.md (Phase 1 depends-on gate) for the SEMANTICS, but replace EVERY AskUserQuestion with the deterministic policy below. You run all git/bash yourself (the workflow cannot).\n\n` +
279
285
  `${projectBrief}\n\nCards in batch (Read each YAML):\n${cardPaths.join('\n')}\nCard IDs: ${cardIds.join(' ')}\n\n` +
280
- `Create/maintain the recovery tracker at /tmp/batch-tracker-${firstCard}.md (per setup.md § Context Tracking).\n\n` +
286
+ `Create/maintain the recovery tracker at "$(git -C "${MAIN}" rev-parse --git-common-dir)/baldart/run/batch-tracker-${firstCard}.md" (mkdir -p its dir first; per setup.md § Context Tracking — DURABLE location, v4.91.0: /tmp is volatile and a paused/overnight run lost it — B4).\n\n` +
281
287
  `ROLE BOUNDARY (specialization integrity): you are the OPS/GIT agent. You NEVER edit source or doc files — any needed content change belongs to the coder specialist; report it instead.\n\n` +
282
288
  `DETERMINISTIC GATE POLICIES (NO user prompts):\n` +
283
289
  `• G1 dirty-tree (main repo ${MAIN}): partition framework-managed noise exactly as setup.md step 3 ($METRICS=${METRICS}, .baldart/generated|state.json|skill-conflicts.json — NOT overlays/). Genuine user work → auto-stash 'baldart-new2-${firstCard}' (main checkout) and record the label. Never commit/abort/prompt.\n` +
@@ -286,7 +292,7 @@ try {
286
292
  g3Bullet +
287
293
  `• G4 card-field validation (setup.md 1b/1c, card-schema.md consumer contract): card missing acceptance_criteria/files_likely_touched/scope → EXCLUDE (excluded[] + reason). \`requirements\` is CONDITIONAL, not an auto-exclude: if it is missing BUT acceptance_criteria + scope are both present and non-empty, FAITHFULLY DERIVE it (a concrete restatement/decomposition of the existing AC + scope — faithful, never inventing new scope) and WRITE it back to the card YAML on disk in ${MAIN}/${paths.backlog_dir || 'backlog'} (F-040: main repo, not the worktree copy; the card artifact is NOT a source/doc file, so this is allowed despite the role boundary), logging \`[BACKFILL] <id>: requirements=<N items, AC+scope-derived>\`. Only EXCLUDE for missing requirements when acceptance_criteria OR scope is ALSO empty (a genuinely thin card). Never HALT for one bad card.\n` +
288
294
  `• G5 depends-on: a card whose depends_on names a non-DONE card NOT in this batch → EXCLUDE it AND every in-batch card that transitively depends on it.\n` +
289
- `• cardGraph (REQUIRED, F-021): for every runnable card return { id, dependsOn:[IN-BATCH deps only], ownerAgent (the card's owner_agent; G25 unknown→'coder'), hasMockup (BOOLEAN — true IFF the card has a non-empty links.design OR links.design_src; drives the mockup-first build override — implement.md §6a), reviewProfile (the card's review_profile; default 'balanced'), policyDeferredACs, alreadyCommitted, alreadyCommittedSha, isEpic (implement.md §6b epic guard: id ends '-00' OR filename ends '-epic.yml' OR group.is_epic:true OR review_profile 'skip' with no requirements), filesLikelyTouched (verbatim from the YAML) }.\n` +
295
+ `• cardGraph (REQUIRED, F-021): for every runnable card return { id, dependsOn:[IN-BATCH deps only], ownerAgent (the card's owner_agent; G25 unknown→'coder'), hasMockup (BOOLEAN — true IFF the card has a non-empty links.design OR links.design_src; drives the mockup-first build override — implement.md §6a), designSrcDir (ABS path of links.design_src / the PRD mockups/_src dir; '' when absent), designHtml (ABS path when links.design is an .html file; '' otherwise), hasBindings (BOOLEAN — non-empty component_bindings), reviewProfile (the card's review_profile; default 'balanced'), policyDeferredACs, alreadyCommitted, alreadyCommittedSha, isEpic (implement.md §6b epic guard: id ends '-00' OR filename ends '-epic.yml' OR group.is_epic:true OR review_profile 'skip' with no requirements), filesLikelyTouched (verbatim from the YAML) }.\n` +
290
296
  `• B1/F-026 idempotency (per card, AFTER the worktree exists): set alreadyCommitted:true (+ alreadyCommittedSha) IFF ALL hold: (a) a commit referencing the card id exists in ${TRUNK}..HEAD of the worktree; (b) the card's validation_commands re-run GREEN right now; (c) NO open follow-up card for it exists in ${paths.backlog_dir || 'backlog'}. On a FRESH worktree ${TRUNK}..HEAD is empty → all false, zero extra work.\n` +
291
297
  `• F-016 AC↔ownership consistency: for each acceptance_criterion, derive the file(s) it requires editing. If those files are NOT a subset of the card's MAY-EDIT/files_likely_touched → add the AC to policyDeferredACs:[{n,text,owningCard|owningFile,reason}] (it will become ONE follow-up, never a resolve). Do the same for any AC whose remedy is an owner-gated infra action (remote db push / deploy / secret / DNS).\n` +
292
298
  (migrationApplied
@@ -594,16 +600,21 @@ async function runCard(cardId, cardPath) {
594
600
  const phase1Brief = architectOk
595
601
  ? `Phase 1 already ran as specialist agents. READ the architecture baseline at ${baselinePath} BEFORE writing any code — it is your codebase context; do NOT redo the exploration.${auditCorrections.length ? `\nPlan-audit corrections (apply them as amendments to this briefing — do NOT modify the backlog YAML):\n${auditCorrections.map((c, i) => ` ${i + 1}. ${c}`).join('\n')}` : ''}`
596
602
  : `Phase 1 fallback: the architect specialist was unavailable — do the Phase 1 claim+architect exploration yourself per ${REF}/implement.md and persist the baseline to ${baselinePath} before coding.`
603
+ // T1.A (v4.90.0) — mockup cards: binding-compliance is verified deterministically by the
604
+ // check-bindings.mjs script (ui-implement Step 5b is the SSOT; this is the new2 parallel).
605
+ const bindingBit = node.hasMockup && node.hasBindings
606
+ ? `\nBINDING GATE (deterministic — ui-implement Step 5b parallel): after building, run \`node "$(ls .claude/skills/ui-implement/scripts/check-bindings.mjs .framework/framework/.claude/skills/ui-implement/scripts/check-bindings.mjs 2>/dev/null | head -1)" --bindings-json '<the card component_bindings as JSON>' --files <your touched implementation files>\`. Exit 3 = a reuse/reuse-variant binding is NOT used in the code: the map is AUTHORITATIVE — fix it before returning (one pass). Include the script's one-line JSON output as bindingCheck in your return.\n`
607
+ : ''
597
608
  let impl
598
609
  try {
599
610
  impl = await agentSafe(
600
- `Implement card ${cardId} per ${REF}/implement.md Phase 2 — you ARE the owner_agent '${ownerAgent}' — and ${REF}/completeness.md (Phase 2.5 + 2.5b AC-closure ledger). Run all gates/bash yourself.\n\n${phase1Brief}\n\n${cardBrief}\n\n` +
611
+ `Implement card ${cardId} per ${REF}/implement.md Phase 2 — you ARE the owner_agent '${ownerAgent}' — and ${REF}/completeness.md (Phase 2.5 + 2.5b AC-closure ledger). Run all gates/bash yourself.\n${bindingBit}\n${phase1Brief}\n\n${cardBrief}\n\n` +
601
612
  `POLICIES: G26 Phase-2 lint/tsc/test/build failing after the module's retry cap → buildBlocked:true + blockedGate. Build the AC Closure Ledger (one row per AC: implemented|unmet|policy-deferred). DO NOT silently defer; report unmet rows (excluding policy-deferred). Persist the diff to /tmp/diff-${cardId}.txt.\n\n` +
602
613
  `E4 OWNERSHIP RECONCILE (implement.md §11b — do this BEFORE returning): the card's MAY-EDIT includes files_likely_touched ∪ paths NAMED EXPLICITLY in this card's acceptance_criteria/definition_of_done (e.g. an ADR the DoD says to update, the data-model / ER doc for a schema change). Editing THOSE is in-scope. For any OTHER dirty file outside MAY-EDIT (another card's file, or unrelated): \`git checkout -- <file>\` to revert it (NEVER leave it orphaned), list it in revertedOutOfOwnership. Set fileDiffViolation:true ONLY if such an edit genuinely could not be reverted (then say why in note) — it is no longer a silent label.\n\n` +
603
614
  `Return: { epic, buildBlocked, blockedGate, unmetACs:[{n,text}], scopeFiles, mayEditPaths, revertedOutOfOwnership:[paths], fileDiffViolation, note }`,
604
615
  { label: `implement:${cardId}`, phase: 'Implement', agentType: ownerAgent,
605
616
  schema: { type: 'object', required: ['epic', 'buildBlocked', 'unmetACs', 'scopeFiles'], additionalProperties: true,
606
- properties: { epic: { type: 'boolean' }, buildBlocked: { type: 'boolean' }, blockedGate: { type: 'string' }, unmetACs: { type: 'array', items: { type: 'object', additionalProperties: true } }, scopeFiles: { type: 'array', items: { type: 'string' } }, mayEditPaths: { type: 'array', items: { type: 'string' } }, revertedOutOfOwnership: { type: 'array', items: { type: 'string' } }, fileDiffViolation: { type: 'boolean' }, note: { type: 'string' } } } }
617
+ properties: { epic: { type: 'boolean' }, buildBlocked: { type: 'boolean' }, blockedGate: { type: 'string' }, unmetACs: { type: 'array', items: { type: 'object', additionalProperties: true } }, scopeFiles: { type: 'array', items: { type: 'string' } }, mayEditPaths: { type: 'array', items: { type: 'string' } }, revertedOutOfOwnership: { type: 'array', items: { type: 'string' } }, fileDiffViolation: { type: 'boolean' }, note: { type: 'string' }, bindingCheck: { type: 'object', additionalProperties: true, description: 'check-bindings.mjs one-line JSON (mockup+bindings cards only)' } } } }
607
618
  )
608
619
  } catch (e) {
609
620
  if (e && e.transientExhausted) { noteDegraded('outage'); return { card: cardId, status: 'pending', gates } }
@@ -642,6 +653,74 @@ async function runCard(cardId, cardPath) {
642
653
  if (r.status !== 'resolved') { deferredOpen = true; deferredClasses.add(r.deferralClass || 'unresolved') }
643
654
  }
644
655
 
656
+ // --- R1-F2 (v4.92.1): the binding gate result is CONSUMED, not write-only. The agent
657
+ // was already told to run check-bindings.mjs and fix before returning; here the workflow
658
+ // verifies the report: violations survive the agent's own pass → resolve (ui domain);
659
+ // report absent on a bindings card → fail-open but LEDGERED (audit trail, never silent).
660
+ if (node.hasMockup && node.hasBindings) {
661
+ const bc = impl && impl.bindingCheck
662
+ const bcViol = (bc && Array.isArray(bc.violations)) ? bc.violations : []
663
+ if (!bc) g('binding-gate', 'NOT-REPORTED', 'agent returned no bindingCheck — fail-open (script may be absent on older installs), recorded for audit')
664
+ else if (bcViol.length) {
665
+ g('binding-gate', 'VIOLATIONS', bcViol.map((v) => v.component).join(', '))
666
+ for (const v of bcViol) {
667
+ const r = await resolve('blocker', cardId, `binding-not-honored: region "${v.region || ''}" is bound action:${v.action || 'reuse'} → ${v.component}, but the component appears in NO touched implementation file — the binding map is authoritative (card-schema.md § component_bindings)`, { mayEditPaths: mayEdit, scopeFiles, domain: 'ui' })
668
+ if (r.status !== 'resolved') { deferredOpen = true; deferredClasses.add(r.deferralClass || 'unresolved') }
669
+ }
670
+ } else g('binding-gate', 'OK', `${(bc && bc.honored) || 0}/${(bc && bc.checked) || 0} bindings honored`)
671
+ }
672
+
673
+ // --- T1.A (v4.90.0): route-free structural fidelity lane — the new2 parallel of
674
+ // /e2e-review Phase 2.7. new2 has NO browser lanes (visual/quality verify runs only on
675
+ // the /new path), so the code-form structural check is its ONE per-card fidelity lane.
676
+ // Running it is a Lane-Ledger obligation (design-system-protocol § 6. Verification-Lane
677
+ // Ledger): a mockup card with a code-form source may NOT complete with zero fidelity
678
+ // verification (the FEAT-0064 under-run). Verifier unavailable → verification-coverage-gap
679
+ // residual + the card cannot be auto-DONE — an under-run blocks, never passes silently.
680
+ if (node.hasMockup && (node.designSrcDir || node.designHtml)) {
681
+ const structSrc = node.designSrcDir
682
+ ? { level: 'design_src', design_src_dir: node.designSrcDir }
683
+ : { level: 'html', mockup_path: node.designHtml }
684
+ const spawnMfv = () => agentSafe(
685
+ `Compare the implementation's code structure against the mockup's code structure and return JSON per your output schema (route-free — no browser, no dev server; cd into ${sharedCtx.worktreePath}).\n\nInput payload:\n${JSON.stringify({ card_id: cardId, route: '(card-level)', mockup_source: structSrc, implementation_files: scopeFiles, tolerance: 'strict' })}\n\nReturn STRICT JSON only: { status, findings:[{category,severity,description,expected,actual,fix_hint}] }`,
686
+ { label: `fidelity:${cardId}:structural`, phase: 'Implement', agentType: 'markup-fidelity-verifier',
687
+ schema: { type: 'object', required: ['findings'], additionalProperties: true, properties: { status: { type: 'string' }, findings: { type: 'array', items: { type: 'object', additionalProperties: true } } } } }
688
+ )
689
+ let mfv = null
690
+ try { mfv = await spawnMfv() } catch (e) { if (e && e.transientExhausted) noteDegraded('outage') }
691
+ // R1-F1 (v4.92.1): a verifier that RETURNS but self-reports `status:"skipped"`
692
+ // (no_structural_ground_truth — unreadable mockup / no layout-bearing impl) is an
693
+ // under-run exactly like an unavailable verifier, NEVER a PASS.
694
+ const mfvSkipped = mfv && /skip/i.test(String(mfv.status || ''))
695
+ if (!mfv || mfvSkipped) {
696
+ g('fidelity-structural', 'UNDERRUN', `planned lane did not verify (${!mfv ? 'verifier unavailable' : 'verifier skipped: ' + String((mfv && mfv.reason) || 'no_structural_ground_truth')}) — verification-coverage-gap, card cannot auto-DONE`)
697
+ deferredOpen = true; deferredClasses.add('unresolved')
698
+ residuals.push({ card: cardId, kind: 'verification-coverage-gap', evidence: `structural fidelity lane planned but did not verify (${!mfv ? 'markup-fidelity-verifier unavailable' : 'verifier skipped'})`, materialized: false })
699
+ } else {
700
+ const gatingOf = (x) => ((x && x.findings) || []).filter((f) => /critical|major/i.test(String(f.severity || '')))
701
+ const gating = gatingOf(mfv)
702
+ g('fidelity-structural', gating.length ? 'BLOCKED' : 'PASS', gating.length ? `${gating.length} gating structural finding(s)` : `ran — ${(mfv.findings || []).length} finding(s), none gating`)
703
+ let anyResolved = false
704
+ for (const f of gating) {
705
+ const r = await resolve('blocker', cardId, `structural fidelity (${f.category || 'layout'}): ${f.description || ''} — expected: ${f.expected || '(see mockup)'}`, { mayEditPaths: mayEdit, scopeFiles, domain: 'ui' })
706
+ if (r.status !== 'resolved') { deferredOpen = true; deferredClasses.add(r.deferralClass || 'unresolved') }
707
+ else anyResolved = true
708
+ }
709
+ // R1-F3 (v4.92.1): a resolved structural fix is RE-VERIFIED by the deterministic
710
+ // verifier once (mirror of /e2e-review Phase 5b step 2 — a fixer's self-report is
711
+ // not the verifier's confirmation). Single re-check, no loop: still-gating after
712
+ // the re-run → deferral (the follow-up carries it), never a second fix pass here.
713
+ if (anyResolved) {
714
+ let mfvRe = null
715
+ try { mfvRe = await spawnMfv() } catch (e) { if (e && e.transientExhausted) noteDegraded('outage') }
716
+ const reGating = mfvRe && !/skip/i.test(String(mfvRe.status || '')) ? gatingOf(mfvRe) : null
717
+ if (reGating === null) { g('fidelity-structural-reverify', 'UNDERRUN', 're-verify did not run — treated as unresolved'); deferredOpen = true; deferredClasses.add('unresolved') }
718
+ else if (reGating.length) { g('fidelity-structural-reverify', 'STILL-BLOCKED', `${reGating.length} gating finding(s) survive the fix — deferred`); deferredOpen = true; deferredClasses.add('unresolved'); residuals.push({ card: cardId, kind: 'blocker', domain: 'ui', evidence: `structural fidelity still failing after fix: ${reGating.map((f) => f.category).join(', ')}`, materialized: false }) }
719
+ else g('fidelity-structural-reverify', 'PASS', 'fix confirmed by the verifier')
720
+ }
721
+ }
722
+ }
723
+
645
724
  // --- Review fan-out (F-024/F-025): specialized agents, trimmed by review_profile. ---
646
725
  // G5 — scopeFiles tokens alone miss a security card whose files don't carry them. Also
647
726
  // trigger security-reviewer on the card's RAW owner_agent and its brief (title/requirements).
@@ -1106,7 +1185,7 @@ if (!committed.length) {
1106
1185
  } else {
1107
1186
  try {
1108
1187
  mergeResult = await agentSafe(
1109
- `Auto-merge the batch worktree to ${TRUNK} per ${REF}/merge-cleanup.md: Phase 6 via the DETERMINISTIC script merge-worktree.sh, then Phase 6b status reconciliation + Phase 6c hygiene. Run git yourself.\n\n${projectBrief}\nWorktree: ${sharedCtx.worktreePath}\nBranch: ${sharedCtx.branch}\nmerge_strategy: ${mergeStrategy}\nCommitted cards: ${committed.map((r) => r.card).join(' ')}\nPhase-0 stash to restore (if any): see /tmp/batch-tracker-${firstCard}.md.\n\n` +
1188
+ `Auto-merge the batch worktree to ${TRUNK} per ${REF}/merge-cleanup.md: Phase 6 via the DETERMINISTIC script merge-worktree.sh, then Phase 6b status reconciliation + Phase 6c hygiene. Run git yourself.\n\n${projectBrief}\nWorktree: ${sharedCtx.worktreePath}\nBranch: ${sharedCtx.branch}\nmerge_strategy: ${mergeStrategy}\nCommitted cards: ${committed.map((r) => r.card).join(' ')}\nPhase-0 stash to restore (if any): see "$(git -C "${MAIN}" rev-parse --git-common-dir)/baldart/run/batch-tracker-${firstCard}.md".\n\n` +
1110
1189
  `PHASE 6 — run the SSOT script (the merge analogue of setup-worktree.sh; NOT a hand-rolled merge, NOT /mw inline):\n` +
1111
1190
  ` MERGE_SH="$(ls .framework/framework/.claude/skills/worktree-manager/scripts/merge-worktree.sh .claude/skills/worktree-manager/scripts/merge-worktree.sh 2>/dev/null | head -1)"\n` +
1112
1191
  ` bash "$MERGE_SH" --worktree ${sharedCtx.worktreePath} --manifest /tmp/new2-merge-status-${firstCard}.txt --skip-checks\n` +
@@ -91,6 +91,30 @@ Before picking a tier, classify the query:
91
91
  ~8–12M tokens with three coders independently re-discovering one NUL byte in a
92
92
  single multi-hundred-KB `data.ts`.)
93
93
 
94
+ ## Large-file read discipline (hot files)
95
+
96
+ A **hot file** is any source file **> 800 lines** that multiple agents touch in the
97
+ same run (a form client, a data layer, a locale dictionary). Forensics on real
98
+ `/new` batches showed hot-file FULL re-reads are the dominant repeated-read cost —
99
+ NOT the framework modules (read 1× each): one 2110-line client component (~29k
100
+ tokens per full view) was read **28 times** across architect → coder → fixer →
101
+ verifier spawns in a single run, because each fresh context re-read it whole.
102
+
103
+ Rules (every agent, every runtime):
104
+
105
+ 1. **Map once, range-read after.** The `codebase-architect` baseline carries a
106
+ `## Hot-File Map` for every in-scope file > 800 lines (per-symbol line ranges —
107
+ see `codebase-architect.md` § terse contract). When you need a hot file and a
108
+ map exists, `Read` the **target range only** (offset/limit around the symbol),
109
+ never the whole file.
110
+ 2. **No map → locate first, then range-read.** `rg -n "<symbol>"` (or LSP
111
+ go-to-definition when the flag is on) to find the line, then `Read` a bounded
112
+ window around it. A whole-file `Read` of a hot file is justified ONLY when the
113
+ edit genuinely spans dispersed regions of it — say so in one line when you do it.
114
+ 3. **Briefs pass paths, not bodies.** An orchestrator briefing an agent passes the
115
+ baseline **path** (e.g. `/tmp/arch-baseline-<CARD-ID>.md`), never its inlined
116
+ content — and never inlines a hot file into a prompt.
117
+
94
118
  ## Fallback rules
95
119
 
96
120
  - LSP tool not loaded / plugin missing → silently fall back to Grep with the
@@ -654,6 +654,33 @@ card's `owner_agent: coder` (correct for its logic/review ownership) sent the UI
654
654
  `coder`. `owner_agent` still governs the review/ownership semantics; it just does not pick
655
655
  the build agent when a mockup is present.
656
656
 
657
+ ### 6. Verification-Lane Ledger — planned vs executed (no silent lane under-run)
658
+
659
+ Checks 1–4 above each guard ONE lane. This check guards the **whole matrix**: the run
660
+ that silently executes *fewer lanes than it planned*. The driving lesson (FEAT-0064,
661
+ post-mortem of run `a73a52d0`): a mockup card went through "fidelity review" in which
662
+ **only** the structural verifier ever spawned — no `visual-fidelity-verifier`, no
663
+ `ui-quality-critic` — and nothing in the verdict recorded the under-run; the escaped
664
+ polish surfaced later as a hand-worked bug card. A lane that is planned and then not
665
+ executed is invisible to per-lane guards, so the ledger is reconciled globally:
666
+
667
+ - **At plan time** the verifying orchestrator (`/e2e-review` Phase 2; the degraded
668
+ direct-trio path in `/ui-implement`; the `new2.js` structural lane) derives, per
669
+ route/surface, the set of REQUIRED lanes — `structural` (when a code-form mockup
670
+ exists), `visual` per in-scope viewport, `quality` (when a quality oracle exists),
671
+ `functional` (when scenarios exist) — and persists it.
672
+ - **At verdict time** it reconciles planned vs executed. Every planned lane must be
673
+ `ran` or `skipped(<authorized reason>)` where the authorized reasons are ONLY the
674
+ ones this protocol enumerates (pixel-diff pre-filter pass, `render_unavailable` with
675
+ structural covered, `no_quality_oracle`, harness-render fidelity guard,
676
+ desktop-only route for the responsive pass). A planned lane that is simply
677
+ **`not_run`** synthesizes a **`verification-coverage-gap`** (Critical) — the matrix
678
+ twin of `coverage-gap`: not fixable by an agent, bypasses the self-heal loop, maps
679
+ to `blocked`. A verdict of `passed` with an unreconciled lane ledger is a protocol
680
+ violation, and no self-served "accepted (harness limit)" style verdict exists —
681
+ accepting an un-run lane requires the explicit override path (human reason) or the
682
+ caller's escalation, never the verifier's own discretion.
683
+
657
684
  ### Gating philosophy (calibration)
658
685
 
659
686
  Vision-model reliability research (*MLLM as a UI Judge*, 2025) shows multimodal judges are
@@ -15,12 +15,17 @@ workflows are unavailable behaves exactly as before.
15
15
  | :--- | :--- | :--- |
16
16
  | `new-final-review` | `/new` Final Review (Step F.1.5) | Runs the read-only cross-batch review fan-out — architecture baseline + Codex ‖ doc-reviewer ‖ api-perf-cost-auditor ‖ qa-sentinel — then adversarially verifies low-confidence findings and returns them classified. Applies no fixes (the skill owns fix application + user gates). **v4.17.1+:** Codex availability is resolved by a **deterministic pre-flight glob + background poll** (no false negatives from a synchronous-run timeout); a **single-card batch** slims the breadth finders **per-finder, coverage-gated** (caller-supplied `slimDoc`/`slimApi`): it drops a duplicate Claude reviewer only when that reviewer already ran per-card on the same card — `new2` drops both doc + api (it runs both per-card, relevance-gated); classic `/new` drops only `doc-reviewer` (when Phase 3 ran it) and **keeps `api-perf-cost-auditor`** (deferred-to-final by design — the final is its only run). Codex + qa gates always run. Backward-compatible: callers without the flags get the full reviewer set. |
17
17
  | `new-card-review` (v4.34.0) | `/new` per-card review cluster — sequential (`review-cycle.md` § Phase 2.5x) **and** team-mode (`team-mode.md` D.1.6) | Hosts the **per-wave** review-cluster OUTSIDE the orchestrator context — the biggest prefix-growth source on long epics. Takes **1..N co-located cards** (1 = sequential per-card; N = a team-mode group, so it runs **once per wave, not per card**) and fans out the finders per card: Simplify + cross-model Codex (agent-launched binary, `code-reviewer` fallback) + qa-sentinel (group max tier) + security-reviewer (high-risk only), each specialist FP-checking its own findings. Then **ONE `coder` applies all VERIFIED code/perf/security/simplify findings in a single pass** (files disjoint by ownership) and re-verifies lint/tsc/build. Returns a compact `{perCard:{fixesApplied,residual}, gateTable, summary}` — the minority `residual` (doc, needs-manual, scope, unconverged) the skill resolves with the right specialist / user gate. **Doc-review and E2E stay in the skill** (doc is write-mode + must see final code; E2E is human-gated + nests a skill); **api-perf is deferred to the Final FULL gate**. Maps to `review-cycle.md` Phase 2.55+3.5+3.7. |
18
- | `new2` (v4.17.2) | `/new2` skill (the whole batch) | **EXPERIMENTAL A/B variant of `/new`.** Hosts the ENTIRE batch in the background runtime so subagent output never enters the main context. A **dependency-gated DAG scheduler** runs a card only when all in-batch deps are *committed* (and blocks transitive dependents of a failed dep instead of routing them to resolve); each card uses its **owner_agent** + a **specialized review fan-out** (not general-purpose); the worktree is kept **atomic per card** (rollback-to-HEAD on failure); transient API errors are retried and a sustained **outage degrades cleanly** (`degraded` return + durable resume via the skill); a **run ledger** dedups resolves and records accepted deferrals (no re-routing loop); the **merge is integrity-gated** (never force-DONE, never `git add` unreviewed code, never merge an incomplete/degraded batch); the commit step runs on **Haiku** while **follow-up cards are written by `prd-card-writer`**; telemetry carries real **cost** (`total_tokens` via `budget.spent()`, `agent_count`, skill-stamped `wall_clock_s`) + `degraded`. **v4.17.2:** the pre-flight **G3 cross-card Codex check is deterministic** (glob-first + background poll, skipped on single-card batches; `codex_resolved` in telemetry); a non-transient card crash is terminal-with-residual (no orphaned self-healing). Agents Read `/new`'s reference modules for semantics. |
18
+ | `new2` (v4.17.2, promoted v4.90.0) | `/new2` skill (the whole batch) **+ `/new -auto` via its delegation gate** | **Workflow-hosted batch engine of `/new`.** Hosts the ENTIRE batch in the background runtime so subagent output never enters the main context. A **dependency-gated DAG scheduler** runs a card only when all in-batch deps are *committed* (and blocks transitive dependents of a failed dep instead of routing them to resolve); each card uses its **owner_agent** + a **specialized review fan-out** (not general-purpose); the worktree is kept **atomic per card** (rollback-to-HEAD on failure); transient API errors are retried and a sustained **outage degrades cleanly** (`degraded` return + durable resume via the skill); a **run ledger** dedups resolves and records accepted deferrals (no re-routing loop); the **merge is integrity-gated** (never force-DONE, never `git add` unreviewed code, never merge an incomplete/degraded batch); the commit step runs on **Haiku** while **follow-up cards are written by `prd-card-writer`**; telemetry carries real **cost** (`total_tokens` via `budget.spent()`, `agent_count`, skill-stamped `wall_clock_s`) + `degraded`. **v4.17.2:** the pre-flight **G3 cross-card Codex check is deterministic** (glob-first + background poll, skipped on single-card batches; `codex_resolved` in telemetry); a non-transient card crash is terminal-with-residual (no orphaned self-healing). Agents Read `/new`'s reference modules for semantics. |
19
19
  | `new2-resolve` (v4.17.2) | `new2` (self-healing) | Resolution pass for any gate that would otherwise need a human (`ac-unmet · blocker · qa-fail · e2e-blocked · merge-blocker · scope-expansion`). A **terminal short-circuit** skips the costly multi-attempt when the problem is impossible-by-definition (`out-of-ownership` verified in JS; other terminal reasons ratified by a judge); a **MANDATORY adversarial judge** cross-checks every `verified` claim — the judge independently greps the files and the workflow verifies **at least one** falls inside MAY-EDIT (`.some()`, so listing adjacent changed files is not mistaken for fabrication); accepts a **batched `findings` list** (one resolve per fix-area). **The domain is normalized** (freeform `documentation`→`doc`, …) before routing the **fixer** (doc→doc-reviewer, ui→ui-expert, security→security-reviewer, else coder) and judge, and a **doc finding gets doc-tree MAY-EDIT** (not the card's code scope); the 3-angle Tier-2 fan-out is reserved for code domains (single retry for doc/test). Follow-ups are written by **`prd-card-writer`**, offline-safe (deferred to the skill if no agent can write). |
20
20
 
21
- > **`new2` is an experiment, not a replacement.** It exists to A/B-test the
22
- > context-economy of workflow-hosting `/new`. `/new` remains the SSOT, the
23
- > cross-tool default, and the recovery-safe path. `new2` is Claude-only and its
21
+ > **`new2` is the engine, `/new` is the SSOT.** Since v4.90.0 `/new -auto`
22
+ > delegates the whole batch here by default (AUTONOMOUS OFF-CONTEXT DELEGATION
23
+ > GATE measured driver: 53–59% of an inline `-auto` run's billed tokens is the
24
+ > orchestrator's own turn-tax; escape hatch `-inline`; `-auto-ship`, declared
25
+ > migrations, interactive runs and every Codex run stay on the classic inline
26
+ > path). `/new` remains the semantic SSOT and the cross-tool default — `new2`'s
27
+ > agents Read `/new`'s reference modules; the gate map in `new2.js` encodes only
28
+ > orchestration shape + deterministic policy. `new2` is Claude-only and its
24
29
  > skill HALTs with a pointer to `/new` when the `Workflow` tool is unavailable.
25
30
 
26
31
  ## How distribution works
@@ -0,0 +1,183 @@
1
+ #!/usr/bin/env node
2
+ // structural-compare.mjs — deterministic STRUCTURAL pre-gate for the mockup→code
3
+ // fidelity chain (T1.B, framework v4.90.0). Zero-dep, heuristic, CONSERVATIVE.
4
+ //
5
+ // Compares a code-form mockup (an .html file, or a design_src dir of .jsx/.tsx/.css)
6
+ // against the implementation files on a small set of CLEAR structural signals:
7
+ // - landmark census (header/nav/aside/main/footer present in mockup, absent in impl)
8
+ // - max grid column count (multi-column mockup built single-column — the FEAT-0061 class)
9
+ // - responsive signals (@media breakpoints / Tailwind responsive prefixes present in
10
+ // the mockup, entirely absent in the impl)
11
+ //
12
+ // SEMANTICS — signals, not verdicts. The implementation may COMPOSE primitives (a
13
+ // `<SplitView>` that owns the grid, a `<TopBar>` that renders the <header>), and this
14
+ // script only sees the card-touched files — so a "divergence" here is a deterministic
15
+ // SIGNAL with a per-finding `confidence`, never a standalone verdict:
16
+ // - in-build (ui-expert self-check): the builder judges each signal — it knows which
17
+ // primitives it composed — and fixes what is real BEFORE returning.
18
+ // - in Phase 2.7 (/e2e-review): the signals are passed to `markup-fidelity-verifier`
19
+ // as `deterministic_signals` focus hints; the verifier confirms or dismisses each
20
+ // (it can read the composed primitives). The verifier spawn is NEVER skipped on a
21
+ // "match" — a regex match is necessary, not sufficient.
22
+ //
23
+ // Contract:
24
+ // node structural-compare.mjs --mockup <file-or-dir> --impl <f1> <f2> ...
25
+ // → one JSON line: { status: "match" | "divergence" | "skipped",
26
+ // mockup_signature, impl_signature,
27
+ // findings:[{category,severity,description,expected,actual}] }
28
+ // Exit: 0 = match/skipped (nothing provable), 3 = divergence (findings are evidence).
29
+ // Internal errors fail OPEN ({status:"skipped"}, exit 0) — pre-gate, not tribunal.
30
+
31
+ import fs from 'node:fs';
32
+ import path from 'node:path';
33
+
34
+ function emit(obj, code) {
35
+ process.stdout.write(JSON.stringify(obj) + '\n');
36
+ process.exit(code);
37
+ }
38
+
39
+ function parseArgs(argv) {
40
+ const a = { mockup: null, impl: [] };
41
+ for (let i = 2; i < argv.length; i++) {
42
+ if (argv[i] === '--mockup') a.mockup = argv[++i];
43
+ else if (argv[i] === '--impl') {
44
+ while (i + 1 < argv.length && !argv[i + 1].startsWith('--')) a.impl.push(argv[++i]);
45
+ }
46
+ }
47
+ return a;
48
+ }
49
+
50
+ function readSources(target) {
51
+ const texts = [];
52
+ const push = (p) => { try { texts.push(fs.readFileSync(p, 'utf8')); } catch { /* fail-open per file */ } };
53
+ try {
54
+ const st = fs.statSync(target);
55
+ if (st.isDirectory()) {
56
+ for (const f of fs.readdirSync(target)) {
57
+ if (/\.(html?|jsx?|tsx?|css|scss|svelte|vue)$/i.test(f)) push(path.join(target, f));
58
+ }
59
+ } else push(target);
60
+ } catch { /* absent target → empty */ }
61
+ return texts;
62
+ }
63
+
64
+ // Count top-level tracks in a grid-template-columns value: "repeat(2, minmax(0,1fr))" → 2,
65
+ // "1fr 320px" → 2. Parens-aware split; `repeat(N, ...)` contributes N (integer N only).
66
+ function countTracks(value) {
67
+ const v = value.trim();
68
+ if (!v || /^(subgrid|none|inherit|initial|unset)/.test(v)) return 0;
69
+ const parts = [];
70
+ let depth = 0, cur = '';
71
+ for (const ch of v) {
72
+ if (ch === '(') { depth++; cur += ch; }
73
+ else if (ch === ')') { depth--; cur += ch; }
74
+ else if (/\s/.test(ch) && depth === 0) { if (cur) { parts.push(cur); cur = ''; } }
75
+ else cur += ch;
76
+ }
77
+ if (cur) parts.push(cur);
78
+ let n = 0;
79
+ for (const p of parts) {
80
+ const rep = p.match(/^repeat\(\s*(\d+)\s*,/);
81
+ if (rep) n += parseInt(rep[1], 10);
82
+ else if (/^\[.*\]$/.test(p)) continue; // named grid line, not a track
83
+ else n += 1;
84
+ }
85
+ return n;
86
+ }
87
+
88
+ function signature(texts) {
89
+ const sig = {
90
+ landmarks: { header: 0, nav: 0, aside: 0, main: 0, footer: 0 },
91
+ grid_cols_max: 0,
92
+ grid_cols_responsive_max: 0, // max cols behind a responsive gate (md:/lg:/@media)
93
+ has_media_queries: false,
94
+ has_responsive_prefixes: false,
95
+ files: texts.length,
96
+ };
97
+ for (const t of texts) {
98
+ for (const lm of Object.keys(sig.landmarks)) {
99
+ const m = t.match(new RegExp(`<${lm}[\\s/>]`, 'gi'));
100
+ if (m) sig.landmarks[lm] += m.length;
101
+ }
102
+ // CSS: grid-template-columns (also inside <style> blocks / .css files / inline style=)
103
+ for (const m of t.matchAll(/grid-template-columns\s*:\s*([^;"'}]+)/gi)) {
104
+ sig.grid_cols_max = Math.max(sig.grid_cols_max, countTracks(m[1]));
105
+ }
106
+ // @media-scoped multi-col: approximate — a grid-template-columns inside a file that
107
+ // also has @media counts as responsive-capable (regex cannot scope blocks reliably).
108
+ if (/@media[^{]*\(\s*(min|max)-width/i.test(t)) {
109
+ sig.has_media_queries = true;
110
+ for (const m of t.matchAll(/grid-template-columns\s*:\s*([^;"'}]+)/gi)) {
111
+ sig.grid_cols_responsive_max = Math.max(sig.grid_cols_responsive_max, countTracks(m[1]));
112
+ }
113
+ }
114
+ // Tailwind: grid-cols-N (+ responsive variants), incl. arbitrary grid-cols-[a_b].
115
+ // Arbitrary values use `_` as the space stand-in and may contain functions —
116
+ // `grid-cols-[repeat(2,_minmax(0,1fr))]` is 2 tracks, not split('_').length=3:
117
+ // normalize `_`→space and reuse the parens-aware countTracks().
118
+ for (const m of t.matchAll(/(?:^|[\s"'`])(?:(sm|md|lg|xl|2xl):)?grid-cols-(\d+|\[[^\]]+\])/g)) {
119
+ const cols = m[2].startsWith('[')
120
+ ? countTracks(m[2].slice(1, -1).replace(/_/g, ' '))
121
+ : parseInt(m[2], 10);
122
+ sig.grid_cols_max = Math.max(sig.grid_cols_max, cols);
123
+ if (m[1]) {
124
+ sig.has_responsive_prefixes = true;
125
+ sig.grid_cols_responsive_max = Math.max(sig.grid_cols_responsive_max, cols);
126
+ }
127
+ }
128
+ if (/(?:^|[\s"'`])(?:sm|md|lg|xl|2xl):[\w[-]/m.test(t)) sig.has_responsive_prefixes = true;
129
+ }
130
+ return sig;
131
+ }
132
+
133
+ function main() {
134
+ const args = parseArgs(process.argv);
135
+ if (!args.mockup || args.impl.length === 0) emit({ status: 'skipped', reason: 'usage: --mockup <file-or-dir> --impl <files...>' }, 0);
136
+
137
+ const mockTexts = readSources(args.mockup);
138
+ if (mockTexts.length === 0) emit({ status: 'skipped', reason: 'mockup unreadable: ' + args.mockup }, 0);
139
+ const implTexts = args.impl.flatMap((f) => readSources(f));
140
+ if (implTexts.length === 0) emit({ status: 'skipped', reason: 'no readable implementation files' }, 0);
141
+
142
+ const mock = signature(mockTexts);
143
+ const impl = signature(implTexts);
144
+ const findings = [];
145
+
146
+ // Confidence encodes the composition caveat: the impl files are the card-touched set,
147
+ // and grids/landmarks may live INSIDE a reused primitive this scan cannot see.
148
+ // 1. Multi-column mockup built single-column (the FEAT-0061 layout-break class).
149
+ if (mock.grid_cols_max >= 2 && impl.grid_cols_max <= 1) {
150
+ findings.push({
151
+ category: 'layout-break', severity: 'critical', confidence: 'medium',
152
+ description: `mockup declares a ${mock.grid_cols_max}-column grid; implementation files declare no multi-column grid (unless a composed primitive owns it — verify)`,
153
+ expected: `grid with ${mock.grid_cols_max} columns (mockup)`, actual: `max ${impl.grid_cols_max} column(s) found in touched files`,
154
+ });
155
+ }
156
+ // 2. Mockup gates multi-col behind a breakpoint; impl has NO responsive machinery at all.
157
+ if ((mock.has_media_queries || mock.has_responsive_prefixes) && !impl.has_media_queries && !impl.has_responsive_prefixes) {
158
+ findings.push({
159
+ category: 'responsiveness-break', severity: 'major', confidence: 'medium',
160
+ description: 'mockup is breakpoint-aware (@media / responsive prefixes); implementation files have neither (unless a composed primitive owns the breakpoints — verify)',
161
+ expected: 'breakpoint-gated layout as in the mockup', actual: 'no @media, no responsive class prefixes in touched files',
162
+ });
163
+ }
164
+ // 3. Landmark regions present in the mockup, entirely absent in the implementation.
165
+ // LOW confidence by construction: component-based impls render landmarks inside
166
+ // reused shell primitives (<TopBar> → <header>) invisible to this scan.
167
+ for (const lm of Object.keys(mock.landmarks)) {
168
+ if (mock.landmarks[lm] > 0 && impl.landmarks[lm] === 0) {
169
+ findings.push({
170
+ category: 'component-missing', severity: 'minor', confidence: 'low',
171
+ description: `mockup contains <${lm}> landmark region(s); none in the touched files (may be rendered by a composed shell primitive — verify)`,
172
+ expected: `<${lm}> region (mockup has ${mock.landmarks[lm]})`, actual: 'absent from touched files',
173
+ });
174
+ }
175
+ }
176
+
177
+ emit(
178
+ { status: findings.length ? 'divergence' : 'match', mockup_signature: mock, impl_signature: impl, findings },
179
+ findings.length ? 3 : 0
180
+ );
181
+ }
182
+
183
+ try { main(); } catch (e) { emit({ status: 'skipped', reason: 'internal error: ' + (e && e.message) }, 0); }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "baldart",
3
- "version": "4.89.2",
3
+ "version": "4.92.1",
4
4
  "description": "Claude Agent Framework - Reusable framework for coordinating AI agents and humans in software projects",
5
5
  "bin": {
6
6
  "baldart": "./bin/baldart.js"