baldart 4.89.2 → 4.92.0
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 +73 -0
- package/VERSION +1 -1
- package/framework/.claude/agents/api-perf-cost-auditor.md +9 -0
- package/framework/.claude/agents/code-reviewer.md +9 -0
- package/framework/.claude/agents/codebase-architect.md +6 -0
- package/framework/.claude/agents/coder.md +1 -1
- package/framework/.claude/agents/markup-fidelity-verifier.md +24 -1
- package/framework/.claude/agents/qa-sentinel.md +9 -0
- package/framework/.claude/agents/security-reviewer.md +9 -0
- package/framework/.claude/agents/ui-expert.md +49 -0
- package/framework/.claude/agents/visual-fidelity-verifier.md +16 -12
- package/framework/.claude/skills/e2e-review/CHANGELOG.md +49 -0
- package/framework/.claude/skills/e2e-review/SKILL.md +178 -24
- package/framework/.claude/skills/new/CHANGELOG.md +29 -0
- package/framework/.claude/skills/new/SKILL.md +44 -2
- package/framework/.claude/skills/new/references/completeness.md +1 -0
- package/framework/.claude/skills/new/references/implement.md +19 -3
- package/framework/.claude/skills/new/references/review-cycle.md +23 -1
- package/framework/.claude/skills/new/references/team-mode.md +4 -0
- package/framework/.claude/skills/new/scripts/verify-item.sh +54 -0
- package/framework/.claude/skills/new2/CHANGELOG.md +30 -0
- package/framework/.claude/skills/new2/SKILL.md +35 -8
- package/framework/.claude/skills/ui-implement/CHANGELOG.md +15 -0
- package/framework/.claude/skills/ui-implement/SKILL.md +31 -6
- package/framework/.claude/skills/ui-implement/references/integration.md +12 -3
- package/framework/.claude/skills/ui-implement/scripts/check-bindings.mjs +103 -0
- package/framework/.claude/workflows/new-card-review.js +19 -5
- package/framework/.claude/workflows/new-final-review.js +24 -9
- package/framework/.claude/workflows/new2.js +50 -4
- package/framework/agents/code-search-protocol.md +24 -0
- package/framework/agents/design-system-protocol.md +27 -0
- package/framework/docs/WORKFLOWS.md +9 -4
- package/framework/scripts/structural-compare.mjs +178 -0
- package/package.json +1 -1
|
@@ -2,6 +2,36 @@
|
|
|
2
2
|
|
|
3
3
|
Formato: [Keep a Changelog](https://keepachangelog.com/) · [SemVer](https://semver.org/).
|
|
4
4
|
|
|
5
|
+
## 1.2.0 — 2026-07-02
|
|
6
|
+
|
|
7
|
+
- **Resume loop HARD-BOUNDED (Step 5.3)**: `resumeMaxAttempts = 3` con contatore
|
|
8
|
+
esplicito + stall detection (stesso set di card incomplete = stop immediato).
|
|
9
|
+
Un `degraded` persistente è un outage/blocker strutturale, non converge iterando
|
|
10
|
+
(~0–200k+ token a ciclo).
|
|
11
|
+
|
|
12
|
+
## 1.1.1 — 2026-07-02
|
|
13
|
+
|
|
14
|
+
- Fix B4 (location parallela in `new2.js`): il preflight creava il tracker in
|
|
15
|
+
`/tmp/batch-tracker-…` (volatile — la causa dei 2/3 run con "tracker LOST");
|
|
16
|
+
ora la location DURABILE `$(git -C $MAIN rev-parse --git-common-dir)/baldart/
|
|
17
|
+
run/…` allineata a new/SKILL.md § Context Tracking. Anche il riferimento del
|
|
18
|
+
merge allo stash Phase-0 aggiornato.
|
|
19
|
+
- Cap adattivo del fan-out nei workflow review (`new-card-review.js` /
|
|
20
|
+
`new-final-review.js`): 5 → 2 al primo transient rate-limit (era fisso 3).
|
|
21
|
+
|
|
22
|
+
## 1.1.0 — 2026-07-02
|
|
23
|
+
|
|
24
|
+
- **Promossa a motore di default di `/new -auto`** (delegation gate v4.90.0):
|
|
25
|
+
non più solo variante sperimentale A/B. Nuovo Step 1.5 (contratto della
|
|
26
|
+
delega: grammar residua, mai `-auto`/`-auto-ship`/`-inline`; batch delegati
|
|
27
|
+
sempre migration-free — trovare una migrazione dichiarata su run delegata =
|
|
28
|
+
HALT + handback a `/new -inline`, mai una domanda).
|
|
29
|
+
- `new2.js`: lane di fedeltà strutturale route-free per card con mockup
|
|
30
|
+
(markup-fidelity-verifier + obbligo Lane-Ledger: verifier assente →
|
|
31
|
+
`verification-coverage-gap`, card mai auto-DONE) + binding gate deterministico
|
|
32
|
+
nel brief di implement + campi cardGraph `designSrcDir`/`designHtml`/
|
|
33
|
+
`hasBindings` (T1.A).
|
|
34
|
+
|
|
5
35
|
## 1.0.0 — 2026-07-01
|
|
6
36
|
|
|
7
37
|
- Baseline: versioning per-skill introdotto al framework v4.82.0.
|
|
@@ -1,15 +1,18 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: new2
|
|
3
3
|
effort: high
|
|
4
|
-
version: 1.
|
|
4
|
+
version: 1.2.0
|
|
5
5
|
description: >
|
|
6
|
-
|
|
6
|
+
Workflow-hosted batch engine for /new. Implements one or
|
|
7
7
|
more backlog cards end-to-end by delegating the WHOLE batch to a background
|
|
8
8
|
dynamic workflow — so subagent output never enters the main orchestrator
|
|
9
9
|
context. The batch runs autonomously (zero AskUserQuestion during the run): every
|
|
10
10
|
/new gate is replaced by a deterministic policy + a self-healing resolution pass;
|
|
11
11
|
in interactive mode an optional post-batch escape hatch can hand the hard-case
|
|
12
|
-
follow-ups to /new for the real human gate.
|
|
12
|
+
follow-ups to /new for the real human gate. Since v4.90.0 this is ALSO the
|
|
13
|
+
default engine of `/new -auto` (its delegation gate invokes this skill when the
|
|
14
|
+
Workflow tool + new2.js are present — measured driver: 53–59% orchestrator
|
|
15
|
+
turn-tax on inline -auto runs). Claude-only (needs the Workflow tool).
|
|
13
16
|
Usage: /new2 CARD-IDS (same arg grammar as /new). Triggers on:
|
|
14
17
|
/new2, "implementa le card con workflow", "new2".
|
|
15
18
|
---
|
|
@@ -71,6 +74,19 @@ Also verify the workflow script is linked: if `.claude/workflows/new2.js` is abs
|
|
|
71
74
|
print the same guidance and HALT (the framework did not link it — Codex tree, or a
|
|
72
75
|
pre-v4.16 install).
|
|
73
76
|
|
|
77
|
+
### Step 1.5 — Called by `/new -auto` (delegation note, v4.90.0)
|
|
78
|
+
|
|
79
|
+
`/new`'s AUTONOMOUS OFF-CONTEXT DELEGATION GATE invokes this skill with the same
|
|
80
|
+
residual grammar (card IDs + `-full` + `-stats` + `effort=`) — never
|
|
81
|
+
`-auto`/`-auto-ship`/`-inline` (this skill IS autonomous by contract; the
|
|
82
|
+
`AUTO_SHIP` deploy boundary stays in `/new`'s classic path). `/new` only
|
|
83
|
+
delegates **migration-free** batches (it probes `migration_plan.required`
|
|
84
|
+
itself), so Step 3.5's one interactive question cannot fire on a delegated run —
|
|
85
|
+
if you nonetheless find a declared, unapplied migration in a delegated batch,
|
|
86
|
+
HALT and hand back to `/new` classic (`"batch con migrazione dichiarata — riesegui
|
|
87
|
+
/new <cards> -auto -inline"`) instead of asking. Everything else is identical to a
|
|
88
|
+
direct `/new2` invocation.
|
|
89
|
+
|
|
74
90
|
### Step 2 — Parse the invocation (same grammar as `/new`)
|
|
75
91
|
|
|
76
92
|
Parse `CARD-IDS` exactly as `/new` does — space-separated, hyphen-range
|
|
@@ -239,11 +255,22 @@ returns when the batch is done. It returns:
|
|
|
239
255
|
prints nothing, set the epic card `status: DONE` + `completed_date` + note
|
|
240
256
|
`"epic-closure gate — all children DONE (post-run, new2 skill)"`, folded into the SAME
|
|
241
257
|
reconciliation commit. If any child is still open, leave the epic untouched.
|
|
242
|
-
3. **Resume if degraded.** If `degraded` is true, re-invoke
|
|
243
|
-
`Workflow({ scriptPath, resumeFromRunId })` (same `args` + the
|
|
244
|
-
per-card **skip-completed** guard makes the resume idempotent —
|
|
245
|
-
cards are skipped, only the incomplete/blocked ones run.
|
|
246
|
-
|
|
258
|
+
3. **Resume if degraded — HARD-BOUNDED (v4.92.0).** If `degraded` is true, re-invoke
|
|
259
|
+
the workflow with `Workflow({ scriptPath, resumeFromRunId })` (same `args` + the
|
|
260
|
+
new `ts`). The per-card **skip-completed** guard makes the resume idempotent —
|
|
261
|
+
already-committed cards are skipped, only the incomplete/blocked ones run.
|
|
262
|
+
Bounds (BOTH enforced, count explicitly — do not rely on remembering):
|
|
263
|
+
- **`resumeMaxAttempts = 3`** total re-invocations per batch. Keep a literal
|
|
264
|
+
counter in your working notes (`resume 1/3`, `2/3`, …); at 3 still `degraded`
|
|
265
|
+
→ STOP resuming: surface `"batch stalled after 3 resumes — manual
|
|
266
|
+
intervention needed"` with the incomplete card list (their follow-ups are
|
|
267
|
+
already on disk from step 1 — nothing is lost by stopping).
|
|
268
|
+
- **Stall detection**: if a resume completes with the SAME incomplete card set
|
|
269
|
+
as the previous attempt (zero progress), stop immediately even before the
|
|
270
|
+
cap — a second identical pass will not converge either.
|
|
271
|
+
An unbounded resume loop re-pays the whole batch-loop cost per cycle (~0–200k+
|
|
272
|
+
tokens each) with no new information; a persistent `degraded` is an outage or a
|
|
273
|
+
structural blocker, not something iteration fixes.
|
|
247
274
|
3b. **Escape-hatch escalation for the hard cases (INTERACTIVE mode only — the `new2`
|
|
248
275
|
"relaxation").** `new2` is autonomous *during the batch* — but a genuinely-blocked
|
|
249
276
|
card (the workflow rolled it back / left it `IN_PROGRESS`, DoD not met) is exactly the
|
|
@@ -2,6 +2,21 @@
|
|
|
2
2
|
|
|
3
3
|
Formato: [Keep a Changelog](https://keepachangelog.com/) · [SemVer](https://semver.org/).
|
|
4
4
|
|
|
5
|
+
## 1.1.0 — 2026-07-02
|
|
6
|
+
|
|
7
|
+
- **Binding-compliance gate (Step 5b)**: nuovo script deterministico
|
|
8
|
+
`scripts/check-bindings.mjs` — ogni `component_bindings` entry `reuse`/
|
|
9
|
+
`reuse-variant` deve comparire (import o uso JSX) nei file toccati dalla build;
|
|
10
|
+
violazione = `binding-not-honored` (Critical, 1 retry a `ui-expert`, poi
|
|
11
|
+
`blocked`). Chiude il buco "la mappa è autoritativa ma nessuno verifica che la
|
|
12
|
+
build l'abbia onorata". Fail-open sugli errori interni dello script.
|
|
13
|
+
- **Build-with-eyes nel briefing (Step 4)**: il brief di `ui-expert` ora porta
|
|
14
|
+
l'obbligo del self-check in-build (`ui-expert.md` § "Build-with-eyes": check
|
|
15
|
+
strutturale deterministico via `structural-compare.mjs` + check visivo con
|
|
16
|
+
screenshot Playwright quando catturabile, max 2 iterazioni, degrado onesto su
|
|
17
|
+
runtime non-multimodale) — costruisci-bene a monte invece di
|
|
18
|
+
cattura-e-ripara a valle.
|
|
19
|
+
|
|
5
20
|
## 1.0.0 — 2026-07-01
|
|
6
21
|
|
|
7
22
|
- Skill introdotta (framework v4.82.0). Estrae da `/new` il playbook mockup→code
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: ui-implement
|
|
3
3
|
effort: medium
|
|
4
|
-
version: 1.
|
|
4
|
+
version: 1.1.0
|
|
5
5
|
description: >
|
|
6
6
|
Implement an ALREADY-APPROVED mockup into the codebase end-to-end and
|
|
7
7
|
self-verify its fidelity. Takes a mockup in any channel — a Claude Design
|
|
@@ -123,11 +123,15 @@ Each step is a delegation. Read the cited SSOT; do not paraphrase it.
|
|
|
123
123
|
4. **Implement — spawn `ui-expert`.** Build via the `ui-expert` agent with a
|
|
124
124
|
briefing that carries: the chosen build workflow (Step 1), the mockup path(s),
|
|
125
125
|
the registry-first cascade declaration requirement, the design-source
|
|
126
|
-
declaration requirement (when code-src exists), the `component_bindings`,
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
126
|
+
declaration requirement (when code-src exists), the `component_bindings`, the
|
|
127
|
+
**build-with-eyes self-check obligation** (`ui-expert.md` § "Build-with-eyes"
|
|
128
|
+
— bounded structural + visual self-check BEFORE returning; the completion
|
|
129
|
+
report must carry its one-line outcome), and the i18n / states-taxonomy /
|
|
130
|
+
token-cascade constraints. All of these are `ui-expert.md`'s own contracts —
|
|
131
|
+
the briefing points at them; it does not re-teach them. (Codex /
|
|
132
|
+
no-subagents → the skill performs the build inline following the same
|
|
133
|
+
`ui-expert.md` contracts; a non-multimodal runtime degrades the visual
|
|
134
|
+
self-check to a logged skip.)
|
|
131
135
|
|
|
132
136
|
5. **Ground the design-source declaration** *(only when the card has
|
|
133
137
|
`links.design_src` / `mockups/_src/` exists)*. After the agent's first
|
|
@@ -137,6 +141,27 @@ Each step is a delegation. Read the cited SSOT; do not paraphrase it.
|
|
|
137
141
|
re-spawn. Log `design-source: grounded` | `rejected (<reason>)`. (This is the
|
|
138
142
|
Step 7b grounding lifted out of `/new`.)
|
|
139
143
|
|
|
144
|
+
5b. **Binding-compliance gate (deterministic — only when the card carries
|
|
145
|
+
`component_bindings`).** The bindings are authoritative (Step 3), but until
|
|
146
|
+
now nothing verified the built code actually HONORED them. Run:
|
|
147
|
+
|
|
148
|
+
```bash
|
|
149
|
+
node .claude/skills/ui-implement/scripts/check-bindings.mjs \
|
|
150
|
+
--bindings-json '<the card's component_bindings serialized to JSON>' \
|
|
151
|
+
--files <the build's touched implementation files>
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
One JSON line on stdout; exit `3` = violations. Every `reuse`/`reuse-variant`
|
|
155
|
+
binding whose component appears in NO touched file (import or JSX usage) is a
|
|
156
|
+
**`binding-not-honored`** finding (Critical, UI domain): re-spawn `ui-expert`
|
|
157
|
+
ONCE with the violation list ("the binding map is authoritative — use
|
|
158
|
+
`<Component>` for region `<region>`, do not re-invent it"); still violated →
|
|
159
|
+
the card is `blocked` (this is a fidelity-contract breach, not a taste issue).
|
|
160
|
+
`action: new` entries are informational here — their governance belongs to the
|
|
161
|
+
Post-Intervention Coherence Check (Step 6). Script errors fail OPEN (`skipped`,
|
|
162
|
+
exit 0) — the gate is a safety net, never a tribunal. Log
|
|
163
|
+
`bindings: ok (N honored)` | `violations (<components>)` | `skipped`.
|
|
164
|
+
|
|
140
165
|
6. **Post-Intervention Coherence Check (BLOCKING).** Reconcile the design-system
|
|
141
166
|
sources in the SAME change — CITE `design-system-protocol.md`
|
|
142
167
|
§ "Post-Intervention Coherence Check": INDEX (`DS_INDEX_DRIFT`), component spec
|
|
@@ -34,6 +34,9 @@ only the input source and the return shape differ.
|
|
|
34
34
|
"findings": [ /* gating findings, canonical severity taxonomy */ ],
|
|
35
35
|
"ds_drift_codes": [ "DS_INDEX_DRIFT" | "DS_COMPONENT_STALE" | "DS_TOKENS_DRIFT" ],
|
|
36
36
|
"design_system_coherence": { /* INDEX / components / tokens / mirror reconciled */ },
|
|
37
|
+
"bindings": { /* Step 5b check-bindings.mjs one-line JSON (skill ≥1.1.0); absent when the card has no component_bindings */ },
|
|
38
|
+
"lane_coverage": "complete" | "incomplete",
|
|
39
|
+
"lanes": { /* forwarded from the internal /e2e-review report (skill ≥1.1.0, e2e-review ≥1.1.0) */ },
|
|
37
40
|
"report_path": ".baldart/e2e-review/<id>/report.json"
|
|
38
41
|
}
|
|
39
42
|
```
|
|
@@ -85,8 +88,14 @@ same opt-in contract as `/prd` → `/ds-handoff`.
|
|
|
85
88
|
## `new2` (workflow) note
|
|
86
89
|
|
|
87
90
|
`new2.js` hosts the batch in a background workflow and reads `implement.md` /
|
|
88
|
-
`review-cycle.md` for the per-card semantics.
|
|
89
|
-
|
|
90
|
-
|
|
91
|
+
`review-cycle.md` for the per-card semantics. `new2` keeps its **inline**
|
|
92
|
+
implement path (the fallback prose stays valid), with two T1.A parallels wired in
|
|
93
|
+
directly (v4.90.0): the mockup card's implement brief carries the
|
|
94
|
+
**binding-compliance gate** (same `check-bindings.mjs` script as Step 5b), and a
|
|
95
|
+
**route-free structural fidelity lane** (`markup-fidelity-verifier`) runs after
|
|
96
|
+
implement for every card with a code-form mockup — its Lane-Ledger obligation
|
|
97
|
+
(`design-system-protocol.md` § 6): verifier unavailable → `verification-coverage-gap`
|
|
98
|
+
residual, the card cannot auto-DONE. Browser lanes (visual/quality) remain
|
|
99
|
+
`/new`-path only. Full `new2` parity — delegating the UI card's implement+verify
|
|
91
100
|
off-context via `workflow()` — is a follow-up, to be taken after the context-economy
|
|
92
101
|
measurement (`SKILL.md` scope note + the plan's measure-first gate).
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// check-bindings.mjs — deterministic `binding-not-honored` gate (T1.A, framework v4.90.0).
|
|
3
|
+
//
|
|
4
|
+
// A card's `component_bindings` (card-schema.md § component_bindings) is AUTHORITATIVE
|
|
5
|
+
// over the mockup: each `action: reuse | reuse-variant` entry promises that the bound
|
|
6
|
+
// registry component is actually USED in the implementation. Until now nothing verified
|
|
7
|
+
// that promise after the build — a ui-expert that ignored a binding and re-invented the
|
|
8
|
+
// primitive sailed through (the verifier trio checks fidelity-to-mockup, not
|
|
9
|
+
// binding-compliance). This script closes that gap without a model call.
|
|
10
|
+
//
|
|
11
|
+
// Contract:
|
|
12
|
+
// node check-bindings.mjs --bindings-json '<json array>' --files <f1> <f2> ...
|
|
13
|
+
// node check-bindings.mjs --bindings-file <path.json> --files <f1> <f2> ...
|
|
14
|
+
//
|
|
15
|
+
// - `--bindings-json` is the card's `component_bindings` serialized to JSON by the
|
|
16
|
+
// caller (the orchestrator/agent already has the card in context; YAML parsing is
|
|
17
|
+
// deliberately NOT re-implemented here — the deterministic part is the code check).
|
|
18
|
+
// - `--files` are the implementation files the card touched (the build's scopeFiles).
|
|
19
|
+
// - Prints ONE JSON line to stdout:
|
|
20
|
+
// { status: "ok" | "violations" | "skipped",
|
|
21
|
+
// checked, honored, violations:[{component, region, action}],
|
|
22
|
+
// results:[{component, region, action, status, matched_in:[...]}] }
|
|
23
|
+
// - Exit codes: 0 = ok/skipped (nothing to gate), 3 = violations (gate fires).
|
|
24
|
+
// Internal errors NEVER crash the caller: fail-open → {status:"skipped"} exit 0
|
|
25
|
+
// (same philosophy as the framework-edit-gate: a safety net, not a tribunal).
|
|
26
|
+
//
|
|
27
|
+
// Matching rule (deterministic, conservative — prefer false-negative over false-block):
|
|
28
|
+
// an entry with action reuse/reuse-variant is HONORED when the component identifier
|
|
29
|
+
// appears in ≥1 provided file as an import specifier OR a JSX opening tag
|
|
30
|
+
// (`<Component` / `<NS.Component`). `action: new` entries are reported as
|
|
31
|
+
// informational (`new-component`) and never count as violations here — the
|
|
32
|
+
// Post-Intervention Coherence Check owns their governance.
|
|
33
|
+
|
|
34
|
+
import fs from 'node:fs';
|
|
35
|
+
|
|
36
|
+
function emit(obj, code) {
|
|
37
|
+
process.stdout.write(JSON.stringify(obj) + '\n');
|
|
38
|
+
process.exit(code);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function parseArgs(argv) {
|
|
42
|
+
const a = { bindingsJson: null, bindingsFile: null, files: [] };
|
|
43
|
+
for (let i = 2; i < argv.length; i++) {
|
|
44
|
+
if (argv[i] === '--bindings-json') a.bindingsJson = argv[++i];
|
|
45
|
+
else if (argv[i] === '--bindings-file') a.bindingsFile = argv[++i];
|
|
46
|
+
else if (argv[i] === '--files') {
|
|
47
|
+
while (i + 1 < argv.length && !argv[i + 1].startsWith('--')) a.files.push(argv[++i]);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
return a;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function main() {
|
|
54
|
+
const args = parseArgs(process.argv);
|
|
55
|
+
let raw = args.bindingsJson;
|
|
56
|
+
if (!raw && args.bindingsFile) {
|
|
57
|
+
if (!fs.existsSync(args.bindingsFile)) emit({ status: 'skipped', reason: 'bindings file not found: ' + args.bindingsFile }, 0);
|
|
58
|
+
raw = fs.readFileSync(args.bindingsFile, 'utf8');
|
|
59
|
+
}
|
|
60
|
+
if (!raw) emit({ status: 'skipped', reason: 'no --bindings-json / --bindings-file' }, 0);
|
|
61
|
+
|
|
62
|
+
let bindings;
|
|
63
|
+
try { bindings = JSON.parse(raw); } catch (e) { emit({ status: 'skipped', reason: 'bindings JSON unparsable: ' + e.message }, 0); }
|
|
64
|
+
if (!Array.isArray(bindings) || bindings.length === 0) emit({ status: 'ok', checked: 0, honored: 0, violations: [], results: [], note: 'no bindings to check' }, 0);
|
|
65
|
+
|
|
66
|
+
const sources = [];
|
|
67
|
+
for (const f of args.files) {
|
|
68
|
+
try {
|
|
69
|
+
if (fs.existsSync(f) && fs.statSync(f).isFile()) sources.push({ path: f, text: fs.readFileSync(f, 'utf8') });
|
|
70
|
+
} catch { /* unreadable file → treated as absent, fail-open per-file */ }
|
|
71
|
+
}
|
|
72
|
+
if (sources.length === 0) emit({ status: 'skipped', reason: 'no readable implementation files passed via --files' }, 0);
|
|
73
|
+
|
|
74
|
+
const results = [];
|
|
75
|
+
for (const b of bindings) {
|
|
76
|
+
const component = String(b.component || b.name || '').trim();
|
|
77
|
+
const action = String(b.action || 'reuse').trim();
|
|
78
|
+
const region = String(b.region || '').trim();
|
|
79
|
+
if (!component) { results.push({ component: '(missing)', region, action, status: 'skipped-malformed', matched_in: [] }); continue; }
|
|
80
|
+
if (action === 'new') { results.push({ component, region, action, status: 'new-component', matched_in: [] }); continue; }
|
|
81
|
+
|
|
82
|
+
// Import specifier or JSX opening tag, word-bounded. Conservative: any hit counts.
|
|
83
|
+
const esc = component.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
84
|
+
const importRe = new RegExp(`import[^;]*\\b${esc}\\b[^;]*from`, 'm');
|
|
85
|
+
const jsxRe = new RegExp(`<(?:[A-Za-z_$][\\w$]*\\.)?${esc}[\\s/>]`);
|
|
86
|
+
const bareRe = new RegExp(`\\b${esc}\\b`);
|
|
87
|
+
const matched = sources.filter((s) => importRe.test(s.text) || jsxRe.test(s.text) || (/\.(css|scss)$/.test(s.path) && bareRe.test(s.text)));
|
|
88
|
+
results.push({
|
|
89
|
+
component, region, action,
|
|
90
|
+
status: matched.length ? 'honored' : 'not-found',
|
|
91
|
+
matched_in: matched.map((s) => s.path),
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
const violations = results.filter((r) => r.status === 'not-found').map(({ component, region, action }) => ({ component, region, action }));
|
|
96
|
+
const honored = results.filter((r) => r.status === 'honored').length;
|
|
97
|
+
emit(
|
|
98
|
+
{ status: violations.length ? 'violations' : 'ok', checked: results.length, honored, violations, results },
|
|
99
|
+
violations.length ? 3 : 0
|
|
100
|
+
);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
try { main(); } catch (e) { emit({ status: 'skipped', reason: 'internal error: ' + (e && e.message) }, 0); }
|
|
@@ -46,18 +46,28 @@ const protocolRef = '.claude/skills/new/references/review-cycle.md'
|
|
|
46
46
|
// (kills agents mid-work). Cap concurrent agents at 3 — a rolling worker pool (NOT chunked barriers,
|
|
47
47
|
// which would block fast finders behind the slow background Codex poll). Mirrors the team-mode coder
|
|
48
48
|
// cap so review + implementation obey the same ceiling.
|
|
49
|
-
|
|
50
|
-
|
|
49
|
+
// ADAPTIVE since v4.91.0 (mirrors new-final-review.js): start at 5; on the FIRST rate-limit
|
|
50
|
+
// transient anywhere in the run drop to 2 (below the old fixed 3 — when the shared org limit
|
|
51
|
+
// is hot, narrower is what keeps reviewers alive; when it is not, the fixed cap was pure
|
|
52
|
+
// queue-wait tax). Surplus workers retire at their next pickup.
|
|
53
|
+
const CAP = { value: 5 }
|
|
54
|
+
const MAX_PARALLEL = CAP.value // kept for arg-compat at call sites; capNow() is authoritative
|
|
55
|
+
const capNow = () => CAP.value
|
|
56
|
+
function noteRateLimitPressure(src) {
|
|
57
|
+
if (CAP.value > 2) { CAP.value = 2; log(`parallelCapped: rate-limit transient (${src}) → cap 5→2 for the rest of the run`) }
|
|
58
|
+
}
|
|
59
|
+
async function parallelCapped(thunks, _cap) {
|
|
51
60
|
if (!Array.isArray(thunks) || !thunks.length) return []
|
|
52
61
|
const results = new Array(thunks.length)
|
|
53
62
|
let next = 0
|
|
54
|
-
const worker = async () => {
|
|
63
|
+
const worker = async (idx) => {
|
|
55
64
|
while (next < thunks.length) {
|
|
65
|
+
if (idx >= capNow()) return // adaptive shrink
|
|
56
66
|
const i = next++
|
|
57
67
|
try { results[i] = await thunks[i]() } catch (_) { results[i] = null }
|
|
58
68
|
}
|
|
59
69
|
}
|
|
60
|
-
await Promise.all(Array.from({ length: Math.min(
|
|
70
|
+
await Promise.all(Array.from({ length: Math.min(capNow(), thunks.length) }, (_, idx) => worker(idx)))
|
|
61
71
|
return results
|
|
62
72
|
}
|
|
63
73
|
|
|
@@ -79,7 +89,11 @@ async function agentSafe(prompt, opts, maxAttempts) {
|
|
|
79
89
|
try {
|
|
80
90
|
const o = i === 0 ? opts : Object.assign({}, opts, { label: `${(opts && opts.label) || 'agent'}~retry${i}` })
|
|
81
91
|
return await agent(prompt, o)
|
|
82
|
-
} catch (e) {
|
|
92
|
+
} catch (e) {
|
|
93
|
+
lastErr = e
|
|
94
|
+
if (!isTransient(e)) throw e // permanent error → surface
|
|
95
|
+
noteRateLimitPressure((opts && opts.label) || 'agent') // v4.91.0 — shrink the pool for the rest of the run
|
|
96
|
+
}
|
|
83
97
|
}
|
|
84
98
|
const err = new Error('transient-exhausted: ' + String((lastErr && lastErr.message) || lastErr))
|
|
85
99
|
err.transientExhausted = true
|
|
@@ -53,22 +53,33 @@ const editableFiles = Array.isArray(a.editableFiles) ? a.editableFiles.filter(Bo
|
|
|
53
53
|
const tcCmds = (cfg.toolchain && cfg.toolchain.commands) || {}
|
|
54
54
|
const tc = (key, fallback) => (tcCmds[key] && String(tcCmds[key]).trim()) || fallback
|
|
55
55
|
|
|
56
|
-
// Concurrency cap (v4.53.6
|
|
57
|
-
// coder cap). The org-level rate limit is SHARED across every terminal/session; a wide
|
|
58
|
-
// saturates it and the server throttles
|
|
59
|
-
//
|
|
60
|
-
|
|
61
|
-
|
|
56
|
+
// Concurrency cap (v4.53.6 guardrail, ADAPTIVE since v4.91.0 — mirrors new-card-review.js + the
|
|
57
|
+
// team-mode coder cap). The org-level rate limit is SHARED across every terminal/session; a wide
|
|
58
|
+
// agent fan-out saturates it and the server throttles (the v4.66.0 transient-kill class). The old
|
|
59
|
+
// fixed cap of 3 protected against that but taxed EVERY run's wall-clock, including the majority
|
|
60
|
+
// with no rate pressure (measured: review fan-out ≈ 50 min on a small batch, mostly queue wait).
|
|
61
|
+
// Adaptive policy: start at 5; on the FIRST rate-limit transient seen anywhere in the run, drop to
|
|
62
|
+
// 2 for the remainder (BELOW the old cap — when the org limit is hot, narrower is what saves the
|
|
63
|
+
// reviewers). Rolling worker pool (NOT chunked barriers — those would block fast finders behind
|
|
64
|
+
// the slow background Codex poll); workers above the current cap retire at their next pickup.
|
|
65
|
+
const CAP = { value: 5 }
|
|
66
|
+
const MAX_PARALLEL = CAP.value // kept for arg-compat at call sites; capNow() is authoritative
|
|
67
|
+
const capNow = () => CAP.value
|
|
68
|
+
function noteRateLimitPressure(src) {
|
|
69
|
+
if (CAP.value > 2) { CAP.value = 2; log(`parallelCapped: rate-limit transient (${src}) → cap 5→2 for the rest of the run`) }
|
|
70
|
+
}
|
|
71
|
+
async function parallelCapped(thunks, _cap) {
|
|
62
72
|
if (!Array.isArray(thunks) || !thunks.length) return []
|
|
63
73
|
const results = new Array(thunks.length)
|
|
64
74
|
let next = 0
|
|
65
|
-
const worker = async () => {
|
|
75
|
+
const worker = async (idx) => {
|
|
66
76
|
while (next < thunks.length) {
|
|
77
|
+
if (idx >= capNow()) return // adaptive shrink: retire surplus workers at pickup time
|
|
67
78
|
const i = next++
|
|
68
79
|
try { results[i] = await thunks[i]() } catch (_) { results[i] = null }
|
|
69
80
|
}
|
|
70
81
|
}
|
|
71
|
-
await Promise.all(Array.from({ length: Math.min(
|
|
82
|
+
await Promise.all(Array.from({ length: Math.min(capNow(), thunks.length) }, (_, idx) => worker(idx)))
|
|
72
83
|
return results
|
|
73
84
|
}
|
|
74
85
|
|
|
@@ -91,7 +102,11 @@ async function agentSafe(prompt, opts, maxAttempts) {
|
|
|
91
102
|
try {
|
|
92
103
|
const o = i === 0 ? opts : Object.assign({}, opts, { label: `${(opts && opts.label) || 'agent'}~retry${i}` })
|
|
93
104
|
return await agent(prompt, o)
|
|
94
|
-
} catch (e) {
|
|
105
|
+
} catch (e) {
|
|
106
|
+
lastErr = e
|
|
107
|
+
if (!isTransient(e)) throw e // permanent error → surface
|
|
108
|
+
noteRateLimitPressure((opts && opts.label) || 'agent') // v4.91.0 — shrink the pool for the rest of the run
|
|
109
|
+
}
|
|
95
110
|
}
|
|
96
111
|
const err = new Error('transient-exhausted: ' + String((lastErr && lastErr.message) || lastErr))
|
|
97
112
|
err.transientExhausted = true
|
|
@@ -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 /
|
|
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,10 +600,15 @@ 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 }`,
|
|
@@ -642,6 +653,41 @@ async function runCard(cardId, cardPath) {
|
|
|
642
653
|
if (r.status !== 'resolved') { deferredOpen = true; deferredClasses.add(r.deferralClass || 'unresolved') }
|
|
643
654
|
}
|
|
644
655
|
|
|
656
|
+
// --- T1.A (v4.90.0): route-free structural fidelity lane — the new2 parallel of
|
|
657
|
+
// /e2e-review Phase 2.7. new2 has NO browser lanes (visual/quality verify runs only on
|
|
658
|
+
// the /new path), so the code-form structural check is its ONE per-card fidelity lane.
|
|
659
|
+
// Running it is a Lane-Ledger obligation (design-system-protocol § 6. Verification-Lane
|
|
660
|
+
// Ledger): a mockup card with a code-form source may NOT complete with zero fidelity
|
|
661
|
+
// verification (the FEAT-0064 under-run). Verifier unavailable → verification-coverage-gap
|
|
662
|
+
// residual + the card cannot be auto-DONE — an under-run blocks, never passes silently.
|
|
663
|
+
if (node.hasMockup && (node.designSrcDir || node.designHtml)) {
|
|
664
|
+
const structSrc = node.designSrcDir
|
|
665
|
+
? { level: 'design_src', design_src_dir: node.designSrcDir }
|
|
666
|
+
: { level: 'html', mockup_path: node.designHtml }
|
|
667
|
+
let mfv = null
|
|
668
|
+
try {
|
|
669
|
+
mfv = await agentSafe(
|
|
670
|
+
`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: { findings:[{category,severity,description,expected,actual,fix_hint}] }`,
|
|
671
|
+
{ label: `fidelity:${cardId}:structural`, phase: 'Implement', agentType: 'markup-fidelity-verifier',
|
|
672
|
+
schema: { type: 'object', required: ['findings'], additionalProperties: true, properties: { findings: { type: 'array', items: { type: 'object', additionalProperties: true } } } } }
|
|
673
|
+
)
|
|
674
|
+
} catch (e) {
|
|
675
|
+
if (e && e.transientExhausted) noteDegraded('outage')
|
|
676
|
+
}
|
|
677
|
+
if (!mfv) {
|
|
678
|
+
g('fidelity-structural', 'UNDERRUN', 'planned lane never executed (verifier unavailable) — verification-coverage-gap, card cannot auto-DONE')
|
|
679
|
+
deferredOpen = true; deferredClasses.add('unresolved')
|
|
680
|
+
residuals.push({ card: cardId, kind: 'verification-coverage-gap', evidence: 'structural fidelity lane planned but not executed (markup-fidelity-verifier unavailable)', materialized: false })
|
|
681
|
+
} else {
|
|
682
|
+
const gating = (mfv.findings || []).filter((f) => /critical|major/i.test(String(f.severity || '')))
|
|
683
|
+
g('fidelity-structural', gating.length ? 'BLOCKED' : 'PASS', gating.length ? `${gating.length} gating structural finding(s)` : `ran — ${(mfv.findings || []).length} finding(s), none gating`)
|
|
684
|
+
for (const f of gating) {
|
|
685
|
+
const r = await resolve('blocker', cardId, `structural fidelity (${f.category || 'layout'}): ${f.description || ''} — expected: ${f.expected || '(see mockup)'}`, { mayEditPaths: mayEdit, scopeFiles, domain: 'ui' })
|
|
686
|
+
if (r.status !== 'resolved') { deferredOpen = true; deferredClasses.add(r.deferralClass || 'unresolved') }
|
|
687
|
+
}
|
|
688
|
+
}
|
|
689
|
+
}
|
|
690
|
+
|
|
645
691
|
// --- Review fan-out (F-024/F-025): specialized agents, trimmed by review_profile. ---
|
|
646
692
|
// G5 — scopeFiles tokens alone miss a security card whose files don't carry them. Also
|
|
647
693
|
// trigger security-reviewer on the card's RAW owner_agent and its brief (title/requirements).
|
|
@@ -1106,7 +1152,7 @@ if (!committed.length) {
|
|
|
1106
1152
|
} else {
|
|
1107
1153
|
try {
|
|
1108
1154
|
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 /
|
|
1155
|
+
`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
1156
|
`PHASE 6 — run the SSOT script (the merge analogue of setup-worktree.sh; NOT a hand-rolled merge, NOT /mw inline):\n` +
|
|
1111
1157
|
` 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
1158
|
` 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
|