baldart 4.26.0 → 4.26.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.
package/CHANGELOG.md CHANGED
@@ -5,6 +5,20 @@ All notable changes to BALDART will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [4.26.1] - 2026-06-11
9
+
10
+ **`new2`: specialization integrity — a full audit of every agent spawn; no role mixing anywhere.** User principle: code is written ONLY by `coder`, UI only by `ui-expert`, each agent does one thing. The audit of all 16 spawn sites found two genuine violations and three under-specified plumbing roles. **PATCH** (role-integrity fixes on the EXPERIMENTAL `new2` surface; no config key, no change to `/new`).
11
+
12
+ ### Fixed
13
+
14
+ - **`new2.js` E2 — the ops pre-flight agent no longer repairs the baseline.** "baseline FAILS → fix once" had a general-purpose git agent editing source code. Now: the pre-flight returns `baseline:'fail'` + an actionable log (explicit role boundary: it never edits source/doc files), and the WORKFLOW spawns the **coder** specialist for one bounded repair attempt — verified by deterministically re-running the baseline gates (no claim to trust), `E2-baseline: FIXED-BY-CODER` ledger row; still failing → batch-fatal as before. Zero extra spawns in the happy path.
15
+ - **`new2.js` — the Codex driver never reviews.** Its runtime fallback was "perform the review yourself with the code-reviewer lens" — a general-purpose agent doing code review. Now the driver returns `note:'codex-unavailable'` with empty findings, and the workflow spawns the REAL `code-reviewer` (same `stdReview` call as the matrix), ledgered as `review-codex: FALLBACK`.
16
+ - **`new2-resolve.js` — judge map completed per domain.** `doc` fixes are now judged by **doc-reviewer** (code-reviewer judging prose was cross-domain) and `test` fixes by **qa-sentinel**; `security`/`migration` → security-reviewer and `perf` → api-perf-cost-auditor unchanged; `ui`/`code` stay with code-reviewer (the judge verifies a CODE change — its charter, incl. DS rule 8 for UI). Fixer and judge of the same type remain two independent adversarial instances.
17
+
18
+ ### Changed
19
+
20
+ - **`new2.js` — explicit ROLE BOUNDARY lines on every plumbing agent** (pre-flight, commit, merge, production-readiness): they never edit source/doc files; commit/merge touch ONLY card YAML status fields + registry rows; a content-level merge conflict is leave+report, never hand-resolved; production-readiness executes stack-matched commands but reports (never edits) code/config changes. The full writer map is now: code/perf/migration/test fixes → `coder`; UI → `ui-expert`; security fixes → `security-reviewer`; docs → `doc-reviewer`; backlog YAML → `prd-card-writer`; bookkeeping (status/registry) → mechanical commit/merge agents.
21
+
8
22
  ## [4.26.0] - 2026-06-11
9
23
 
10
24
  **`new2`: Phase 1 decomposed into specialist agents — the owner implements, it no longer explores.** The old per-card pipeline gave the owner agent one mega-prompt ("you ARE claim + architect + plan-auditor + owner"), so the owner absorbed the whole codebase exploration into its own context and reached the actual coding with a degraded window. Phase 1 now runs as dedicated specialists with file handoff (`/tmp`), per the "ognuno fa una cosa" principle. Verified premise: nested subagent spawning does NOT exist in Claude Code (official docs: "Subagents cannot spawn other subagents" + 2 empirical probes), so the decomposition lives at the WORKFLOW level — the JS is the orchestrator, exactly what dynamic workflows are for. **MINOR** (pipeline capability on the EXPERIMENTAL `new2` surface only; no config key, no change to `/new`).
package/VERSION CHANGED
@@ -1 +1 @@
1
- 4.26.0
1
+ 4.26.1
@@ -99,7 +99,17 @@ const FOLLOWUP_SCHEMA = {
99
99
  // F-024 — domain-specialized fixer + judge (full map; reviewer-owns-its-domain — a doc
100
100
  // finding is fixed by doc-reviewer, a security finding by security-reviewer, never coder).
101
101
  const fixerAgent = ({ doc: 'doc-reviewer', ui: 'ui-expert', security: 'security-reviewer' })[domain] || 'coder'
102
- const judgeAgent = (domain === 'security' || domain === 'migration') ? 'security-reviewer' : domain === 'perf' ? 'api-perf-cost-auditor' : 'code-reviewer'
102
+ // Specialization integrity (v4.26.1) the judge is the VERIFICATION specialist of the
103
+ // finding's domain: doc fixes judged by doc-reviewer (code-reviewer judging prose was
104
+ // cross-domain), test fixes by qa-sentinel (THE test specialist). `ui` and `code` stay with
105
+ // code-reviewer: the judge verifies a CODE change, which is code-reviewer's charter
106
+ // (including DS-coherence rule 8 for UI). Fixer and judge of the same type are still two
107
+ // independent instances — the judge prompt is adversarial and greps the files itself.
108
+ const judgeAgent = (domain === 'security' || domain === 'migration') ? 'security-reviewer'
109
+ : domain === 'perf' ? 'api-perf-cost-auditor'
110
+ : domain === 'doc' ? 'doc-reviewer'
111
+ : domain === 'test' ? 'qa-sentinel'
112
+ : 'code-reviewer'
103
113
 
104
114
  const findingsBlock = findings.map((f, i) => ` ${i + 1}. [${f.kind || kind}/${f.domain || domain}] ${f.evidence}`).join('\n')
105
115
  const brief = [
@@ -219,9 +219,10 @@ try {
219
219
  `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` +
220
220
  `${projectBrief}\n\nCards in batch (Read each YAML):\n${cardPaths.join('\n')}\nCard IDs: ${cardIds.join(' ')}\n\n` +
221
221
  `Create/maintain the recovery tracker at /tmp/batch-tracker-${firstCard}.md (per setup.md § Context Tracking).\n\n` +
222
+ `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` +
222
223
  `DETERMINISTIC GATE POLICIES (NO user prompts):\n` +
223
224
  `• 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` +
224
- `• Worktree (setup.md step 4): create ONE code worktree off ${TRUNK}; install deps; assign a port; run the baseline (tsc+lint+build). Copy ONLY the artifacts needed (env/.env.local/.env.example/supabase/.temp) — do NOT bulk-copy untracked files from the main repo (avoids stray backlog cards in the worktree). Use the git-authoritative idempotency pre-check. E2: baseline FAILS → fix once; still failing baseline:'fail' + baselineLog (batch-fatal).\n` +
225
+ `• Worktree (setup.md step 4): create ONE code worktree off ${TRUNK}; install deps; assign a port; run the baseline (tsc+lint+build). Copy ONLY the artifacts needed (env/.env.local/.env.example/supabase/.temp) — do NOT bulk-copy untracked files from the main repo (avoids stray backlog cards in the worktree). Use the git-authoritative idempotency pre-check. E2: baseline FAILS → do NOT fix it yourself (role boundary — the coder specialist repairs it); return baseline:'fail' + a baselineLog precise enough for a coder to act (failing command, error excerpt, suspect files).\n` +
225
226
  codexResolveBullet +
226
227
  g3Bullet +
227
228
  `• G4 card-field validation (setup.md 1b/1c): card missing requirements/acceptance_criteria/files_likely_touched → EXCLUDE (excluded[] + reason). Never HALT for one bad card.\n` +
@@ -239,9 +240,28 @@ try {
239
240
  return finalReturn({ fatal: true, reason: 'pre-flight failed: ' + String(e && e.message) })
240
241
  }
241
242
 
242
- if (!preflight || preflight.ok === false || preflight.baseline === 'fail') {
243
- ledger(firstCard, 'E2-baseline', 'BATCH-FATAL', (preflight && preflight.baselineLog) || 'workspace unworkable')
244
- return finalReturn({ fatal: true, reason: 'baseline build irrecoverable — see baselineLog' })
243
+ if (!preflight || preflight.ok === false) {
244
+ ledger(firstCard, 'preflight', 'BATCH-FATAL', (preflight && preflight.workspaceNote) || 'workspace unworkable')
245
+ return finalReturn({ fatal: true, reason: 'workspace unworkable — see pre-flight' })
246
+ }
247
+ if (preflight.baseline === 'fail') {
248
+ // E2 (specialization integrity) — baseline repair is CODE work: it belongs to the coder
249
+ // specialist, not the ops pre-flight agent (which never edits source). ONE bounded attempt;
250
+ // the verification is the deterministic re-run of the baseline gates themselves (no claim
251
+ // to trust — and every card's G26 re-exercises them anyway). Still failing → batch-fatal.
252
+ let repair = null
253
+ try {
254
+ repair = await agentSafe(
255
+ `You are the coder. The batch worktree BASELINE is failing on trunk-derived code (this is NOT card work — no card has run yet). Worktree: ${preflight.worktreePath} (cd into it).\n\nFailure log:\n${preflight.baselineLog || '(missing — re-run tsc/lint/build to reproduce)'}\n\nApply the minimal correct fix so the baseline gates (tsc + lint + build) pass, RE-RUN them, and report honestly (fixed:true ONLY if they now pass). Return: { fixed, log }`,
256
+ { label: 'baseline-repair', phase: 'Pre-flight', agentType: 'coder',
257
+ schema: { type: 'object', required: ['fixed'], additionalProperties: true, properties: { fixed: { type: 'boolean' }, log: { type: 'string' } } } }
258
+ )
259
+ } catch (e) { if (e && e.transientExhausted) noteDegraded('outage'); repair = null }
260
+ if (repair && repair.fixed) ledger(firstCard, 'E2-baseline', 'FIXED-BY-CODER', String(repair.log || '').slice(0, 200))
261
+ else {
262
+ ledger(firstCard, 'E2-baseline', 'BATCH-FATAL', preflight.baselineLog || 'baseline irrecoverable')
263
+ return finalReturn({ fatal: true, reason: 'baseline build irrecoverable — see baselineLog' })
264
+ }
245
265
  }
246
266
 
247
267
  for (const ex of preflight.excluded || []) ledger(ex.card, 'preflight-exclude', 'EXCLUDED', ex.reason)
@@ -563,28 +583,41 @@ async function runCard(cardId, cardPath) {
563
583
  const reviewSchema = { type: 'object', required: ['blocks', 'scopeExpansion'], additionalProperties: true,
564
584
  properties: { blocks: { type: 'array', items: { type: 'object', additionalProperties: true } }, scopeExpansion: { type: 'array', items: { type: 'object', additionalProperties: true } }, note: { type: 'string' } } }
565
585
  let reviewResults = []
586
+ const onErr = (e) => { if (e && e.transientExhausted) noteDegraded('outage'); return null }
587
+ // The standard SPECIALIST reviewer spawn — also reused as the JS-level fallback when the
588
+ // Codex companion dies at runtime (specialization integrity: the driver never reviews).
589
+ const stdReview = (ra) => agentSafe(
590
+ `You are ${ra}. Review card ${cardId} per ${REF}/review-cycle.md + ${REF}/codex-gate.md (your domain only). Run your gates on the COMMITTED-or-working state.\n\n${cardBrief}\nDiff: /tmp/diff-${cardId}.txt\n\n` +
591
+ `Report ONLY blocking failures that survive your retry cap as blocks:[{gate,domain,evidence}] (each MUST have non-empty gate AND evidence — F-014). Report legitimate findings BEYOND this card's AC as scopeExpansion:[{evidence,domain,withinOwnership,newAC}].\n\n` +
592
+ `Return: { blocks:[...], scopeExpansion:[...], note }`,
593
+ { label: `review:${cardId}:${ra}`, phase: 'Implement', agentType: ra, schema: reviewSchema }
594
+ ).catch(onErr)
566
595
  try {
567
596
  reviewResults = (await parallel(reviewers.map((ra) => () => {
568
- const onErr = (e) => { if (e && e.transientExhausted) noteDegraded('outage'); return null }
569
597
  if (ra === 'codex') {
570
- // Codex-light finder: a general-purpose agent drives the resolved companion (Bash, --wait).
598
+ // Codex-light finder: a general-purpose agent DRIVES the resolved companion (Bash,
599
+ // --wait). Driver role only — it never reviews; runtime failure → note flag, and the
600
+ // workflow spawns the real code-reviewer below.
571
601
  return agentSafe(
572
- `You are the Codex review driver for card ${cardId} (review_profile=light — Codex is the SOLE finder since v4.18.0; you are NOT code-reviewer). Run the OpenAI Codex companion as a REVIEW-ONLY adversarial pass over this card's diff, then return its material findings in the schema below.\n\n${cardBrief}\nDiff: /tmp/diff-${cardId}.txt\nMAY-EDIT: ${JSON.stringify(mayEdit)}\n\n` +
602
+ `You are the Codex review DRIVER for card ${cardId} (review_profile=light — Codex is the SOLE finder since v4.18.0; you are a driver, NOT a reviewer). Run the OpenAI Codex companion as a REVIEW-ONLY adversarial pass over this card's diff, then return its material findings in the schema below.\n\n${cardBrief}\nDiff: /tmp/diff-${cardId}.txt\nMAY-EDIT: ${JSON.stringify(mayEdit)}\n\n` +
573
603
  `Run it in the FOREGROUND (it blocks; do NOT pass run_in_background):\n node "${sharedCtx.codexScriptPath}" task "Review-only — DO NOT make edits, no --write flag. Adversarial review of card ${cardId} using the diff at /tmp/diff-${cardId}.txt. Focus: auth/permission boundaries, data-loss paths, race conditions, rollback safety, schema drift, invariant violations. Report ONLY material findings with file+line evidence." --wait\n` +
574
- `Read the Codex output ONLY through a [codex]-trace-stripping filter. **Fallback**: if it exits non-zero / prints CODEX_NOT_FOUND / stays empty, set note='codex-unavailable' and perform the review yourself with the code-reviewer lens (your domain).\n\n` +
604
+ `Read the Codex output ONLY through a [codex]-trace-stripping filter. **Fallback**: if it exits non-zero / prints CODEX_NOT_FOUND / stays empty, return note:'codex-unavailable' with EMPTY blocks/scopeExpansion — do NOT review yourself (role boundary); the workflow spawns the real code-reviewer.\n\n` +
575
605
  `Map Codex BLOCKER/HIGH findings to blocks:[{gate:'codex-light',domain,evidence}] (each non-empty gate AND evidence — F-014). Map legitimate findings BEYOND this card's AC to scopeExpansion:[{evidence,domain,withinOwnership,newAC}].\n\n` +
576
606
  `Return: { blocks:[...], scopeExpansion:[...], note }`,
577
607
  { label: `review:${cardId}:codex`, phase: 'Implement', agentType: 'general-purpose', schema: reviewSchema }
578
608
  ).catch(onErr)
579
609
  }
580
- return agentSafe(
581
- `You are ${ra}. Review card ${cardId} per ${REF}/review-cycle.md + ${REF}/codex-gate.md (your domain only). Run your gates on the COMMITTED-or-working state.\n\n${cardBrief}\nDiff: /tmp/diff-${cardId}.txt\n\n` +
582
- `Report ONLY blocking failures that survive your retry cap as blocks:[{gate,domain,evidence}] (each MUST have non-empty gate AND evidence — F-014). Report legitimate findings BEYOND this card's AC as scopeExpansion:[{evidence,domain,withinOwnership,newAC}].\n\n` +
583
- `Return: { blocks:[...], scopeExpansion:[...], note }`,
584
- { label: `review:${cardId}:${ra}`, phase: 'Implement', agentType: ra, schema: reviewSchema }
585
- ).catch(onErr)
610
+ return stdReview(ra)
586
611
  }))).filter(Boolean)
587
612
  } catch (_) { /* parallel never rejects; nulls filtered */ }
613
+ // Specialization integrity — companion died at runtime: replace the empty driver result
614
+ // with a REAL code-reviewer pass (the same fallback the JS-level codexAvail gate uses).
615
+ if (reviewResults.some((r) => r && r.note === 'codex-unavailable')) {
616
+ reviewResults = reviewResults.filter((r) => !(r && r.note === 'codex-unavailable'))
617
+ g('review-codex', 'FALLBACK', 'companion failed at runtime → code-reviewer spawned (driver never reviews)')
618
+ const fb = await stdReview('code-reviewer')
619
+ if (fb) reviewResults.push(fb)
620
+ }
588
621
 
589
622
  // F-014 — only route well-formed blocks (non-empty gate+evidence).
590
623
  const blocks = reviewResults.flatMap((r) => (r.blocks || [])).filter((b) => b && b.gate && b.evidence)
@@ -642,7 +675,7 @@ async function runCard(cardId, cardPath) {
642
675
  let commitRes
643
676
  try {
644
677
  commitRes = await agentSafe(
645
- `Commit card ${cardId} in worktree ${sharedCtx.worktreePath}. MECHANICAL — do NOT re-read reference modules.\n` +
678
+ `Commit card ${cardId} in worktree ${sharedCtx.worktreePath}. MECHANICAL — do NOT re-read reference modules. ROLE BOUNDARY: you NEVER modify file contents except the card YAML status/note fields and the ssot-registry row — source/doc changes are not yours.\n` +
646
679
  `Steps: (1) \`git status --porcelain\`; (2) stage = MAY-EDIT (${JSON.stringify(mayEdit)}) ∩ dirty — NEVER \`git add -A\`, NEVER \`git stash\`; if dirty has files OUTSIDE MAY-EDIT, do NOT stage them and set reconcileNote; (3) commit message \`[${cardId}] <concise>\`; ${doneStep} (5) 'nothing to commit' = already committed (record HEAD).\n` +
647
680
  `On COMMIT_LOCK: clear stale lock + retry once. Still locked → committed:false.\n\n` +
648
681
  `Return: { committed, commit, filesChanged, reconcileNote }`,
@@ -863,6 +896,7 @@ if (!committed.length) {
863
896
  mergeResult = await agentSafe(
864
897
  `Auto-merge the batch worktree to ${TRUNK} per ${REF}/merge-cleanup.md (Phase 6 via /mw programmatic checksAlreadyPassed:true, 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` +
865
898
  `DETERMINISTIC POLICIES (NO prompts):\n` +
899
+ `• ROLE BOUNDARY: you are the OPS/GIT agent — you NEVER edit source or doc files. Reconciliation touches ONLY card YAML status fields + registry rows. A merge conflict on content is leave+report, never hand-resolved here.\n` +
866
900
  `• G24 → auto-merge via merge_strategy.\n` +
867
901
  `• F-030 HARD RULE: NEVER \`git add\`/commit code that did not pass the per-card gates. If the worktree is dirty with uncommitted code → DO NOT commit it; leave it, set uncommittedLeft:true, and report. NO "safety commit". Security/migration code is NEVER swept in.\n` +
868
902
  `• F-029 HARD RULE: Phase 6b reconciliation marks a card DONE ONLY if it has a real commit in ${TRUNK}..HEAD AND its gates are green. NEVER force a non-implemented card to DONE. Return forcedDone:[] (must be empty).\n` +
@@ -894,7 +928,7 @@ phase('Production')
894
928
  if (mergeResult && mergeResult.merged) {
895
929
  try {
896
930
  prodReadiness = await agentSafe(
897
- `Run the post-merge Production Readiness checklist per ${REF}/production-readiness.md (Phase 7) over the batch's changed files. Auto-EXECUTE only stack-matched index/access-rule/cron deploys; REPORT (do not execute) env vars, feature flags, DB migrations, secrets, DNS. NON-BLOCKING.\n\n${projectBrief}\nChanged files: ${dedupe(committed.flatMap((r) => r.filesChanged || [])).join(', ') || '(derive from git)'}\n\nReturn: { autoExecuted:[...], manualItems:[...], note }`,
931
+ `Run the post-merge Production Readiness checklist per ${REF}/production-readiness.md (Phase 7) over the batch's changed files. Auto-EXECUTE only stack-matched index/access-rule/cron deploys; REPORT (do not execute) env vars, feature flags, DB migrations, secrets, DNS. NON-BLOCKING. ROLE BOUNDARY: you EXECUTE commands, you never edit repository files — a needed code/config change is reported as a manual item.\n\n${projectBrief}\nChanged files: ${dedupe(committed.flatMap((r) => r.filesChanged || [])).join(', ') || '(derive from git)'}\n\nReturn: { autoExecuted:[...], manualItems:[...], note }`,
898
932
  { label: 'production-readiness', phase: 'Production', agentType: 'general-purpose',
899
933
  schema: { type: 'object', required: ['manualItems'], additionalProperties: true, properties: { autoExecuted: { type: 'array', items: { type: 'string' } }, manualItems: { type: 'array', items: { type: 'string' } }, note: { type: 'string' } } } }
900
934
  )
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "baldart",
3
- "version": "4.26.0",
3
+ "version": "4.26.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"