baldart 3.17.0 → 3.18.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 +93 -0
- package/README.md +21 -12
- package/VERSION +1 -1
- package/bin/baldart.js +34 -3
- package/framework/.claude/agents/REGISTRY.md +1 -0
- package/framework/.claude/agents/visual-fidelity-verifier.md +319 -0
- package/framework/.claude/commands/design-review.md +2 -0
- package/framework/.claude/skills/e2e-review/SKILL.md +552 -0
- package/framework/.claude/skills/new/SKILL.md +99 -85
- package/framework/.claude/skills/playwright-skill/SKILL.md +29 -0
- package/framework/.claude/skills/prd/assets/card-template.yml +7 -0
- package/framework/.claude/skills/webapp-testing/SKILL.md +23 -0
- package/framework/AGENTS.md +1 -0
- package/framework/docs/PROJECT-CONFIGURATION.md +20 -1
- package/framework/templates/baldart.config.template.yml +38 -0
- package/package.json +1 -1
- package/src/commands/configure.js +58 -0
- package/src/commands/status.js +36 -4
- package/src/commands/update.js +33 -14
|
@@ -811,93 +811,107 @@ After completeness is verified, clean up the implementation before it reaches re
|
|
|
811
811
|
If any valid finding revealed a reusable pattern or common mistake, append 1-line to `## Lessons Learned`:
|
|
812
812
|
`SIMPLIFY: <pattern> — <file>`
|
|
813
813
|
|
|
814
|
-
### Phase 2.6 —
|
|
815
|
-
|
|
816
|
-
This phase
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
## Test Credentials
|
|
852
|
-
Persona: [from test_plan.test_credentials.persona]
|
|
853
|
-
Auth method: [from test_plan.test_credentials.auth_method]
|
|
854
|
-
Credentials: [from test_plan.test_credentials.credentials]
|
|
855
|
-
Store: [from test_plan.test_credentials.store]
|
|
856
|
-
Notes: [from test_plan.test_credentials.notes]
|
|
857
|
-
|
|
858
|
-
## Auth Handling
|
|
859
|
-
- If persona is CUSTOMER with OTP auth: navigate to login, enter phone number,
|
|
860
|
-
then use `await test.step('OTP input (manual)', async () => { await page.pause() })`
|
|
861
|
-
to pause for manual OTP entry by the user. After pause resumes, continue the flow.
|
|
862
|
-
- If persona is MERCHANT with password auth: use page.fill() for username/password
|
|
863
|
-
fields, then submit the login form. No manual intervention needed.
|
|
864
|
-
|
|
865
|
-
## Test Data Prerequisites
|
|
866
|
-
[paste test_data_prerequisites — data that must exist in the persistence layer before the test runs]
|
|
867
|
-
|
|
868
|
-
## Guidelines
|
|
869
|
-
- Use page.getByRole(), page.getByText(), page.getByTestId() for selectors
|
|
870
|
-
- Use expect(locator) assertions with auto-waiting
|
|
871
|
-
- Group related steps with test.describe()
|
|
872
|
-
- Implement ALL scenarios from the PRD test plan (not just happy path)
|
|
873
|
-
- Use baseURL from config (just page.goto('/relative-path'))
|
|
874
|
-
- Do NOT use chromium.launch() or manual browser management
|
|
875
|
-
- For OTP flows: use page.pause() and document it clearly in test output
|
|
814
|
+
### Phase 2.6 — End-to-End Review (BLOCKING, since v3.18.0)
|
|
815
|
+
|
|
816
|
+
This phase delegates to the `/e2e-review` skill — a deterministic, BLOCKING
|
|
817
|
+
orchestrator that combines functional E2E (Playwright spec written by `coder`,
|
|
818
|
+
executed via `playwright-skill`) with visual fidelity diff
|
|
819
|
+
(`visual-fidelity-verifier` multimodal agent), aggregates findings under a
|
|
820
|
+
strict severity gate, runs a bounded self-heal loop, and either passes /
|
|
821
|
+
blocks / accepts an explicit user override.
|
|
822
|
+
|
|
823
|
+
The skill replaces the previous advisory pair (legacy Phase 2.6 E2E testing
|
|
824
|
+
and Phase 2.7 visual design review). It runs by default on every UI card,
|
|
825
|
+
auto-skips on backend-only cards, and BLOCKS the commit on gating findings.
|
|
826
|
+
|
|
827
|
+
#### Gate (skip the whole phase when any of the below is true)
|
|
828
|
+
|
|
829
|
+
| Condition | Action |
|
|
830
|
+
|-----------|--------|
|
|
831
|
+
| `features.has_e2e_review: false` in `baldart.config.yml` | **SKIP** — log `"e2e-review: SKIP (feature disabled — run `npx baldart configure` to enable)"`. Preserves backwards compatibility for pre-3.18 consumers. |
|
|
832
|
+
| Card has no `links.design` AND `git diff --name-only` shows no file under `${paths.app_dir}` AND no file under `${paths.components_primitives}` AND no `*.tsx` / `*.css` / `*.scss` / `*.svelte` / `*.vue` anywhere | **SKIP** — log `"e2e-review: SKIP (backend-only card)"`. |
|
|
833
|
+
| Card type is `docs`, `chore`, or `config` | **SKIP** — same log. |
|
|
834
|
+
|
|
835
|
+
#### Invocation contract
|
|
836
|
+
|
|
837
|
+
When the gate above passes, the orchestrator invokes `/e2e-review` in
|
|
838
|
+
**programmatic mode**:
|
|
839
|
+
|
|
840
|
+
1. **Update tracker**: phase = "2.6-e2e-review".
|
|
841
|
+
2. Spawn the skill with the payload:
|
|
842
|
+
|
|
843
|
+
```json
|
|
844
|
+
{
|
|
845
|
+
"card_id": "<CARD-ID>",
|
|
846
|
+
"worktree_path": "<absolute worktree path>",
|
|
847
|
+
"mode": "programmatic",
|
|
848
|
+
"tolerance": "<features.e2e_review.fidelity_tolerance>",
|
|
849
|
+
"max_self_heal_iterations": "<features.e2e_review.max_self_heal_iterations>"
|
|
850
|
+
}
|
|
876
851
|
```
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
852
|
+
|
|
853
|
+
The skill is responsible for: reading the card YAML + `test_plan`, deriving
|
|
854
|
+
routes from the diff, walking the 4-level mockup cascade (Figma MCP →
|
|
855
|
+
local image → compliance-only → skip), generating the Playwright spec via
|
|
856
|
+
`coder`, executing it headless, invoking `visual-fidelity-verifier` per
|
|
857
|
+
route, and returning a single JSON object.
|
|
858
|
+
|
|
859
|
+
3. Parse the returned JSON. The contract is:
|
|
860
|
+
|
|
861
|
+
```json
|
|
862
|
+
{
|
|
863
|
+
"status": "passed" | "blocked" | "overridden" | "skipped" | "error",
|
|
864
|
+
"iterations": 0,
|
|
865
|
+
"findings": [ ... ],
|
|
866
|
+
"ds_drift_codes": [ "DS_TOKENS_DRIFT" | "DS_COMPONENT_STALE" | "DS_INDEX_DRIFT" ],
|
|
867
|
+
"gaps": [ ... ],
|
|
868
|
+
"transcript_paths": [ ... ]
|
|
869
|
+
}
|
|
880
870
|
```
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
871
|
+
|
|
872
|
+
#### Gating logic (BLOCKING)
|
|
873
|
+
|
|
874
|
+
| `status` from `/e2e-review` | Orchestrator action |
|
|
875
|
+
|----|----|
|
|
876
|
+
| `"passed"` | Log result in tracker. Proceed to Phase 3 (doc review + code review). |
|
|
877
|
+
| `"skipped"` | Log skip reason. Proceed to Phase 3. |
|
|
878
|
+
| `"overridden"` | Log override + reason in `## Issues & Flags` as `[E2E-OVERRIDE] <reason>`. Proceed to Phase 3. |
|
|
879
|
+
| `"blocked"` | **STOP the card**. Log findings in tracker + `## Issues & Flags` as `[E2E-BLOCKED] <count> gating findings (<categories>)`. Ask the user whether to (a) override with reason, (b) escalate (open follow-up card), or (c) abandon this card and continue the batch. Do NOT proceed to Phase 3 silently. |
|
|
880
|
+
| `"error"` | Log error in `## Issues & Flags`. Ask the user whether to retry, skip, or abandon. Do NOT proceed silently. |
|
|
881
|
+
|
|
882
|
+
#### Re-run trigger (after Phase 3.7 `/codexreview`)
|
|
883
|
+
|
|
884
|
+
If the mandatory `/codexreview` gate in Phase 3.7 modifies any file under
|
|
885
|
+
`${paths.app_dir}`, `${paths.components_primitives}`, or any `*.css` /
|
|
886
|
+
`*.scss`, the orchestrator MUST re-invoke `/e2e-review` BEFORE the Phase 4
|
|
887
|
+
commit. This protects against code-review changes that silently reintroduce
|
|
888
|
+
visual / functional drift. The re-run reuses the same
|
|
889
|
+
`.baldart/e2e-review/<CARD-ID>/` state directory and starts from Phase 3
|
|
890
|
+
of the skill (skipping plan extraction and mockup cascade).
|
|
891
|
+
|
|
892
|
+
#### Tracker output
|
|
893
|
+
|
|
894
|
+
Append to `## Current Card` and (on completion) carry into `## Completed Cards`:
|
|
895
|
+
|
|
896
|
+
```
|
|
897
|
+
e2e-review: <status> | iterations: <N> | findings: <X critical, Y major, Z minor> | drift: [<codes>] | tolerance: <strict|balanced|lenient>
|
|
898
|
+
```
|
|
899
|
+
|
|
900
|
+
#### Out of scope for this phase
|
|
901
|
+
|
|
902
|
+
- Mockup re-design (the skill never modifies the mockup — only the
|
|
903
|
+
implementation).
|
|
904
|
+
- Code review or doc review (those remain Phase 3 / 3.7 responsibilities).
|
|
905
|
+
- Unit-test execution (that is `qa-sentinel`).
|
|
906
|
+
- Visual polish beyond gating findings (advisory Minor findings under
|
|
907
|
+
balanced/lenient tolerance are logged but not actioned).
|
|
908
|
+
|
|
909
|
+
#### Legacy cards
|
|
910
|
+
|
|
911
|
+
Cards written before v3.18.0 (without a `test_plan` block) still work — the
|
|
912
|
+
skill's Phase 1 falls back to deriving Gherkin scenarios from
|
|
913
|
+
`acceptance_criteria` directly. The mockup cascade gracefully degrades when
|
|
914
|
+
`links.design` is missing.
|
|
901
915
|
|
|
902
916
|
---
|
|
903
917
|
|
|
@@ -17,6 +17,35 @@ All browser testing uses the **Playwright Test CLI** via `playwright.config.ts`
|
|
|
17
17
|
**Overlay:** loads `.baldart/overlays/playwright-skill.md` if present — project-specific test URLs, auth helpers, theming pairing assertions.
|
|
18
18
|
**On missing/empty keys:** ask the user; do not assume defaults. See `framework/agents/project-context.md` § 3.
|
|
19
19
|
|
|
20
|
+
## MCP dependencies
|
|
21
|
+
|
|
22
|
+
See [framework/docs/MCP-INTEGRATION.md](../../../docs/MCP-INTEGRATION.md) for the BALDART MCP integration contract and the rationale behind this section.
|
|
23
|
+
|
|
24
|
+
**None.** This skill uses the **Playwright Test CLI** (`@playwright/test`) directly via `npm run test:e2e` — `.spec.ts` files under `${paths.e2e_tests_dir}`. It does NOT use Playwright MCP. Consumers don't need any MCP configured to use this skill. For ad-hoc interactive browser debugging during bug investigation (not E2E test authoring) see [bug/SKILL.md](../bug/SKILL.md), which DOES require `mcp__playwright__browser_*`.
|
|
25
|
+
|
|
26
|
+
## E2E Review Integration (since v3.18.0)
|
|
27
|
+
|
|
28
|
+
This skill is the tooling layer consumed by `/e2e-review` Phase 3 (functional
|
|
29
|
+
E2E). When the orchestrator invokes this skill, the contract is:
|
|
30
|
+
|
|
31
|
+
- The `coder` agent generates the `.spec.ts` under `${paths.e2e_tests_dir}/`
|
|
32
|
+
from a Gherkin verification plan persisted at
|
|
33
|
+
`.baldart/e2e-review/<CARD-ID>/plan.json` (do not invent scenarios outside
|
|
34
|
+
the plan).
|
|
35
|
+
- The spec MUST screenshot every route in scope and save the file at
|
|
36
|
+
`.baldart/e2e-review/<CARD-ID>/screenshots/<route-slug>.png` — that path is
|
|
37
|
+
consumed verbatim by the `visual-fidelity-verifier` agent in Phase 4.
|
|
38
|
+
- Execution is **headless** in programmatic mode (invocation from `/new`);
|
|
39
|
+
headed mode is reserved for OTP flows during manual `/e2e-review` runs.
|
|
40
|
+
- Failed scenarios become functional findings in the aggregate report; the
|
|
41
|
+
severity is determined by the orchestrator (auth/payments/data-mutation =
|
|
42
|
+
Critical; display-only = Major; flaky-passed-on-retry = Minor).
|
|
43
|
+
|
|
44
|
+
No logic of this skill changes for E2E-review consumption — the consumer
|
|
45
|
+
simply requires the conventions above. See
|
|
46
|
+
[`framework/.claude/skills/e2e-review/SKILL.md`](../e2e-review/SKILL.md) Phase 3
|
|
47
|
+
for the full contract.
|
|
48
|
+
|
|
20
49
|
## Quick Reference
|
|
21
50
|
|
|
22
51
|
| Action | Command |
|
|
@@ -131,6 +131,13 @@ canonical_docs:
|
|
|
131
131
|
additional_refs:
|
|
132
132
|
- "{{${paths.adrs_dir}/ADR-XXXXX.md}}"
|
|
133
133
|
|
|
134
|
+
# test_plan is consumed BOTH by `/new` Phase 2.6 (legacy fallback path) AND by
|
|
135
|
+
# `/e2e-review` (since v3.18.0). The /e2e-review skill maps each test_scenarios[]
|
|
136
|
+
# entry into a Gherkin scenario in Phase 1 (Verification Plan Extraction).
|
|
137
|
+
# When the card has `links.design`, the same scenarios additionally drive the
|
|
138
|
+
# per-route visual fidelity check in Phase 4. Keep scenario.description short
|
|
139
|
+
# and action-oriented (one user-facing intent per scenario) so the Gherkin
|
|
140
|
+
# translation reads cleanly. See framework/.claude/skills/e2e-review/SKILL.md.
|
|
134
141
|
test_plan:
|
|
135
142
|
e2e_required: {{true|false}}
|
|
136
143
|
e2e_rationale: "{{reason from evaluation tree}}"
|
|
@@ -15,6 +15,29 @@ license: Complete terms in LICENSE.txt
|
|
|
15
15
|
|
|
16
16
|
---
|
|
17
17
|
|
|
18
|
+
## E2E Review Integration (since v3.18.0)
|
|
19
|
+
|
|
20
|
+
This skill is the tooling layer consumed by `/e2e-review` in **compliance-only
|
|
21
|
+
mode** (Phase 2 cascade level c) and as a fallback for runtime inspection.
|
|
22
|
+
When invoked from the orchestrator:
|
|
23
|
+
|
|
24
|
+
- Use the Python Playwright helpers to fetch the rendered DOM's computed
|
|
25
|
+
styles (CSS color / spacing / radius / shadow values) for the routes in
|
|
26
|
+
scope when no mockup image is available.
|
|
27
|
+
- The compliance check reads `${paths.design_system}/tokens-reference.md` and
|
|
28
|
+
flags any computed value that does not match a token (the
|
|
29
|
+
`visual-fidelity-verifier` agent owns the actual cross-reference; this
|
|
30
|
+
skill only surfaces the runtime values).
|
|
31
|
+
- The server lifecycle helper (`scripts/with_server.py`) MAY be used to start
|
|
32
|
+
/ stop the dev server when the orchestrator runs outside a worktree that
|
|
33
|
+
already has one running.
|
|
34
|
+
|
|
35
|
+
No logic of this skill changes for E2E-review consumption — see
|
|
36
|
+
[`framework/.claude/skills/e2e-review/SKILL.md`](../e2e-review/SKILL.md)
|
|
37
|
+
Phase 2 (compliance-only branch) for the full contract.
|
|
38
|
+
|
|
39
|
+
---
|
|
40
|
+
|
|
18
41
|
To test local web applications, write native Python Playwright scripts.
|
|
19
42
|
|
|
20
43
|
**Helper Scripts Available**:
|
package/framework/AGENTS.md
CHANGED
|
@@ -16,6 +16,7 @@ This file is generic. Paths referenced as `${paths.X}` resolve from `baldart.con
|
|
|
16
16
|
| PRD workflow, PRD sessions | `features.has_prd_workflow: true` |
|
|
17
17
|
| LLM wiki overlay sync | `features.has_wiki_overlay: true` |
|
|
18
18
|
| Design-system blocking reads | `features.has_design_system: true` |
|
|
19
|
+
| E2E review BLOCKING gate (Phase 2.6 of `/new` invokes `/e2e-review`) | `features.has_e2e_review: true` |
|
|
19
20
|
|
|
20
21
|
When a MUST rule's feature is `false` or its target file does not exist, the rule is **silent** — do not invent a workflow that isn't there. Project-specific terminology (audience segments, domain entities, brand voice) comes from `identity.audience_segments`, `agents/coding-standards.md`, and skill overlays under `.baldart/overlays/`. See `agents/project-context.md` for the full protocol.
|
|
21
22
|
|
|
@@ -107,7 +107,26 @@ Empty string `""` means the concept doesn't exist in your project. Skills gated
|
|
|
107
107
|
| `prd_dir` | `docs/prd` | prd, prd-add, ui-design |
|
|
108
108
|
| `references_dir` | `docs/references` | new, prd, context-primer, doc-writing-for-rag, simplify |
|
|
109
109
|
| `wiki_dir` | `docs/wiki` | capture, context-primer |
|
|
110
|
-
| `e2e_tests_dir` | `tests/e2e` | playwright-skill |
|
|
110
|
+
| `e2e_tests_dir` | `tests/e2e` | playwright-skill, e2e-review (v3.18.0) |
|
|
111
|
+
|
|
112
|
+
### 4.2.1 `e2e_review` — tuning for the BLOCKING end-to-end review gate (v3.18.0+)
|
|
113
|
+
|
|
114
|
+
Only consulted when `features.has_e2e_review: true`. The block governs how the `/e2e-review` skill (auto-invoked by `/new` Phase 2.6 on UI cards) decides what blocks, how aggressively it retries, and what counts as an acceptable user override.
|
|
115
|
+
|
|
116
|
+
```yaml
|
|
117
|
+
e2e_review:
|
|
118
|
+
fidelity_tolerance: strict # strict | balanced | lenient
|
|
119
|
+
max_self_heal_iterations: 2
|
|
120
|
+
pixel_diff_threshold: 0.02
|
|
121
|
+
require_override_reason: true
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
| Key | Type | Default | Effect |
|
|
125
|
+
|---|---|---|---|
|
|
126
|
+
| `fidelity_tolerance` | enum (`strict`/`balanced`/`lenient`) | `strict` | `strict` blocks on Critical + Major + Minor findings (Minor with `confidence: low` auto-demote to advisory to absorb font-rendering / anti-aliasing variance); `balanced` blocks on Critical + Major, Minor advisory; `lenient` blocks on Critical only. |
|
|
127
|
+
| `max_self_heal_iterations` | int | `2` | After gating findings, the orchestrator re-spawns `coder` with the findings as fix instructions and re-runs functional + visual phases. Matches the existing `/new` Phase 2.5 retry policy. After exhaustion, the override path engages. |
|
|
128
|
+
| `pixel_diff_threshold` | float `0.0`–`1.0` | `0.02` | When the implementation screenshot vs mockup pixel-diff is below this fraction, the orchestrator skips the Vision call for that route and treats it as a pass. Primary latency / cost saver — most routes pass pixel-diff cleanly. Set higher to skip Vision more aggressively; set to `0.0` to always invoke Vision. |
|
|
129
|
+
| `require_override_reason` | bool | `true` | When self-heal exhausts iterations and the user chooses to override the gate, a written reason is mandatory. The reason is recorded both in the batch tracker's `## Issues & Flags` (as `[E2E-OVERRIDE] <reason>`) and in `.baldart/e2e-review/<CARD-ID>/report.json`. Set `false` to allow silent override (not recommended). |
|
|
111
130
|
|
|
112
131
|
### 4.3 `identity` — brand and audience facts
|
|
113
132
|
|
|
@@ -145,6 +145,44 @@ features:
|
|
|
145
145
|
# `baldart configure` installs the relevant language servers when enabled.
|
|
146
146
|
has_lsp_layer: false
|
|
147
147
|
|
|
148
|
+
# End-to-end review BLOCKING gate (since v3.18.0). When true, /new Phase 2.6
|
|
149
|
+
# invokes the /e2e-review skill, which combines functional E2E (Playwright
|
|
150
|
+
# spec written by `coder`, executed via playwright-skill) with visual
|
|
151
|
+
# fidelity diff (`visual-fidelity-verifier` multimodal agent). Findings are
|
|
152
|
+
# aggregated under a severity gate (strict by default) with a bounded
|
|
153
|
+
# self-heal loop. Replaces the legacy advisory Phase 2.6 + 2.7. Requires
|
|
154
|
+
# `@playwright/test` installed and `paths.e2e_tests_dir` set. Auto-skips on
|
|
155
|
+
# backend-only cards and on cards without `links.design` when no UI files
|
|
156
|
+
# changed. See framework/.claude/skills/e2e-review/SKILL.md.
|
|
157
|
+
has_e2e_review: false
|
|
158
|
+
|
|
159
|
+
# ─── E2E REVIEW ──────────────────────────────────────────────────────────
|
|
160
|
+
# Tuning for the /e2e-review skill (only consulted when has_e2e_review: true).
|
|
161
|
+
e2e_review:
|
|
162
|
+
# Severity floor that BLOCKS the commit.
|
|
163
|
+
# strict — Critical + Major + Minor block (default, recommended for
|
|
164
|
+
# design-system-strict projects); Minor findings with
|
|
165
|
+
# `confidence: low` auto-demote to advisory (anti font-rendering
|
|
166
|
+
# false positive).
|
|
167
|
+
# balanced — Critical + Major block; Minor advisory.
|
|
168
|
+
# lenient — Critical blocks; Major + Minor advisory.
|
|
169
|
+
fidelity_tolerance: strict
|
|
170
|
+
|
|
171
|
+
# Max self-heal iterations before falling through to the override path. The
|
|
172
|
+
# orchestrator re-spawns `coder` with the gating findings and re-runs the
|
|
173
|
+
# functional + visual phases. 2 matches the existing Phase 2.5 retry policy.
|
|
174
|
+
max_self_heal_iterations: 2
|
|
175
|
+
|
|
176
|
+
# Pre-filter pixel-diff threshold (0.0–1.0). When the implementation vs
|
|
177
|
+
# mockup pixel diff is below this value, the orchestrator skips the Vision
|
|
178
|
+
# call for that route and treats it as a pass. Primary latency / cost saver.
|
|
179
|
+
pixel_diff_threshold: 0.02
|
|
180
|
+
|
|
181
|
+
# When true, the override path (after self-heal exhaustion) requires the
|
|
182
|
+
# user to provide a written reason that is recorded in the tracker's
|
|
183
|
+
# `## Issues & Flags` and in `.baldart/e2e-review/<CARD-ID>/report.json`.
|
|
184
|
+
require_override_reason: true
|
|
185
|
+
|
|
148
186
|
# ─── LSP ─────────────────────────────────────────────────────────────────
|
|
149
187
|
# State of the LSP layer for this project. Populated by `baldart configure`
|
|
150
188
|
# (and by `npx baldart lsp install` / the `/lsp-bootstrap` skill). Leave
|
package/package.json
CHANGED
|
@@ -560,6 +560,7 @@ async function interactivePrompts(merged, detected) {
|
|
|
560
560
|
['has_prd_workflow', 'Project uses PRD workflow (docs/prd/)?'],
|
|
561
561
|
['has_wiki_overlay', 'Project has LLM-wiki overlay (docs/wiki/)?'],
|
|
562
562
|
['has_lsp_layer', 'Enable LSP symbol-search layer? (recommended for large codebases)'],
|
|
563
|
+
['has_e2e_review', 'Enable BLOCKING end-to-end review (Phase 2.6 of /new invokes /e2e-review — functional + visual fidelity gate)?'],
|
|
563
564
|
]) {
|
|
564
565
|
const [key, question] = flag;
|
|
565
566
|
const currentVal = merged.features[key];
|
|
@@ -619,6 +620,62 @@ async function interactivePrompts(merged, detected) {
|
|
|
619
620
|
merged.paths[key] = await promptForKey(`${label}`, proposed);
|
|
620
621
|
}
|
|
621
622
|
|
|
623
|
+
// ---- E2E review tuning (since v3.18.0) --------------------------------
|
|
624
|
+
// Only runs when the user opted in above. Populates the e2e_review.*
|
|
625
|
+
// sub-section with the severity floor, self-heal cap, pixel pre-filter
|
|
626
|
+
// threshold, and override-reason requirement. Idempotent: re-running
|
|
627
|
+
// configure on an already-installed project re-asks each value with the
|
|
628
|
+
// current YAML value as default.
|
|
629
|
+
merged.e2e_review = merged.e2e_review || {};
|
|
630
|
+
if (merged.features.has_e2e_review === true) {
|
|
631
|
+
UI.section('E2E review tuning (/e2e-review skill — Phase 2.6 of /new)');
|
|
632
|
+
const tolDefault = merged.e2e_review.fidelity_tolerance || 'strict';
|
|
633
|
+
const tolerance = await UI.select(
|
|
634
|
+
`Severity floor that BLOCKS the commit (current: ${tolDefault})`,
|
|
635
|
+
[
|
|
636
|
+
{ name: 'strict — Critical + Major + Minor block (recommended for design-system-strict projects)', value: 'strict' },
|
|
637
|
+
{ name: 'balanced — Critical + Major block; Minor advisory', value: 'balanced' },
|
|
638
|
+
{ name: 'lenient — Critical blocks; Major + Minor advisory', value: 'lenient' },
|
|
639
|
+
]
|
|
640
|
+
);
|
|
641
|
+
merged.e2e_review.fidelity_tolerance = tolerance;
|
|
642
|
+
|
|
643
|
+
const maxIterDefault = merged.e2e_review.max_self_heal_iterations ?? 2;
|
|
644
|
+
const maxIter = await promptForKey(
|
|
645
|
+
'Max self-heal iterations before override path',
|
|
646
|
+
String(maxIterDefault)
|
|
647
|
+
);
|
|
648
|
+
merged.e2e_review.max_self_heal_iterations = Number.isFinite(Number(maxIter)) ? Number(maxIter) : 2;
|
|
649
|
+
|
|
650
|
+
const diffDefault = merged.e2e_review.pixel_diff_threshold ?? 0.02;
|
|
651
|
+
const diffThr = await promptForKey(
|
|
652
|
+
'Pixel-diff pre-filter threshold (0.0 = always invoke Vision, 1.0 = never)',
|
|
653
|
+
String(diffDefault)
|
|
654
|
+
);
|
|
655
|
+
merged.e2e_review.pixel_diff_threshold = Number.isFinite(Number(diffThr)) ? Number(diffThr) : 0.02;
|
|
656
|
+
|
|
657
|
+
const reqReasonDefault = merged.e2e_review.require_override_reason !== undefined
|
|
658
|
+
? merged.e2e_review.require_override_reason
|
|
659
|
+
: true;
|
|
660
|
+
merged.e2e_review.require_override_reason = await promptForKey(
|
|
661
|
+
'Require a written reason when the user overrides the gate after self-heal exhaustion?',
|
|
662
|
+
reqReasonDefault,
|
|
663
|
+
'confirm'
|
|
664
|
+
);
|
|
665
|
+
|
|
666
|
+
if (!merged.paths.e2e_tests_dir) {
|
|
667
|
+
UI.info(
|
|
668
|
+
'E2E review requires `paths.e2e_tests_dir` set and `@playwright/test` installed.\n' +
|
|
669
|
+
'The path prompt above asked for the directory — confirm it exists or run `npm i -D @playwright/test` later.'
|
|
670
|
+
);
|
|
671
|
+
}
|
|
672
|
+
} else {
|
|
673
|
+
UI.info(
|
|
674
|
+
'has_e2e_review=false — /new Phase 2.6 silently skips the BLOCKING gate.\n' +
|
|
675
|
+
'Re-run `baldart configure` to enable when you want functional + visual fidelity enforced per card.'
|
|
676
|
+
);
|
|
677
|
+
}
|
|
678
|
+
|
|
622
679
|
// ---- LSP layer (since v3.10.0) ----------------------------------------
|
|
623
680
|
// Only runs when the user opted in above. Idempotent: re-running configure
|
|
624
681
|
// on an already-installed project re-detects, asks which servers to keep,
|
|
@@ -836,6 +893,7 @@ async function configure(opts = {}) {
|
|
|
836
893
|
`Backlog: ${detected.features.has_backlog ? 'yes' : 'no'}`,
|
|
837
894
|
`ADRs: ${detected.features.has_adrs ? 'yes' : 'no'}`,
|
|
838
895
|
`API docs: ${detected.features.has_api_docs ? 'yes' : 'no'}`,
|
|
896
|
+
`E2E review: ${detected.features.has_e2e_review === true ? 'yes' : detected.features.has_e2e_review === false ? 'no' : '— (will ask)'}`,
|
|
839
897
|
`Charting libs: ${detected.stack.charting.canonical.join(', ') || '—'}`,
|
|
840
898
|
`Animation libs: ${detected.stack.animation.canonical.join(', ') || '—'}`,
|
|
841
899
|
`E2E framework: ${detected.stack.testing.e2e || '—'}`,
|
package/src/commands/status.js
CHANGED
|
@@ -4,6 +4,19 @@ const UI = require('../utils/ui');
|
|
|
4
4
|
const fs = require('fs');
|
|
5
5
|
const path = require('path');
|
|
6
6
|
|
|
7
|
+
// Lightweight semver compare. Returns positive if a > b, negative if a < b,
|
|
8
|
+
// zero if equal. Treats malformed input as 0 — degrade open rather than throw.
|
|
9
|
+
function cmpSemver(a, b) {
|
|
10
|
+
if (!a || !b) return 0;
|
|
11
|
+
const pa = String(a).split('.').map((n) => parseInt(n, 10) || 0);
|
|
12
|
+
const pb = String(b).split('.').map((n) => parseInt(n, 10) || 0);
|
|
13
|
+
for (let i = 0; i < Math.max(pa.length, pb.length); i++) {
|
|
14
|
+
const d = (pa[i] || 0) - (pb[i] || 0);
|
|
15
|
+
if (d !== 0) return d;
|
|
16
|
+
}
|
|
17
|
+
return 0;
|
|
18
|
+
}
|
|
19
|
+
|
|
7
20
|
async function status() {
|
|
8
21
|
const git = new GitUtils();
|
|
9
22
|
const symlinks = new SymlinkUtils();
|
|
@@ -94,24 +107,43 @@ async function status() {
|
|
|
94
107
|
}
|
|
95
108
|
}
|
|
96
109
|
|
|
97
|
-
// Check for pending updates
|
|
110
|
+
// Check for pending updates (since v3.17.1 — was a no-op stub before).
|
|
98
111
|
UI.newline();
|
|
99
112
|
UI.section('Update Status');
|
|
100
113
|
|
|
114
|
+
let updateAvailable = false;
|
|
115
|
+
let remoteVersion = null;
|
|
101
116
|
try {
|
|
102
117
|
await git.fetch('antbald/BALDART');
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
118
|
+
remoteVersion = await git.getRemoteVersion('antbald/BALDART', 'main');
|
|
119
|
+
if (!remoteVersion || remoteVersion === 'unknown') {
|
|
120
|
+
UI.warning('Could not read remote VERSION — check `baldart update` manually.');
|
|
121
|
+
} else if (cmpSemver(remoteVersion, version) > 0) {
|
|
122
|
+
updateAvailable = true;
|
|
123
|
+
UI.warning(`Update available: ${version} → ${remoteVersion}. Run: baldart update`);
|
|
124
|
+
} else if (cmpSemver(remoteVersion, version) < 0) {
|
|
125
|
+
UI.info(`Local v${version} is ahead of remote v${remoteVersion} (likely an unreleased commit).`);
|
|
126
|
+
} else {
|
|
127
|
+
UI.success(`Up to date (v${version})`);
|
|
128
|
+
}
|
|
106
129
|
} catch (error) {
|
|
107
130
|
UI.warning('Cannot check for updates (offline?)');
|
|
108
131
|
}
|
|
109
132
|
|
|
110
133
|
// Summary
|
|
111
134
|
UI.newline();
|
|
135
|
+
let updateLine;
|
|
136
|
+
if (updateAvailable && remoteVersion) {
|
|
137
|
+
updateLine = `Update: ${version} → ${remoteVersion} available`;
|
|
138
|
+
} else if (remoteVersion && remoteVersion !== 'unknown') {
|
|
139
|
+
updateLine = `Update: up to date`;
|
|
140
|
+
} else {
|
|
141
|
+
updateLine = `Update: unknown (could not reach remote)`;
|
|
142
|
+
}
|
|
112
143
|
UI.box('SUMMARY', [
|
|
113
144
|
`Version: ${version}`,
|
|
114
145
|
`Installation: ${symlinkValid ? 'Valid' : 'Issues detected'}`,
|
|
146
|
+
updateLine,
|
|
115
147
|
'',
|
|
116
148
|
'Commands available:',
|
|
117
149
|
' • baldart update - Update framework',
|
package/src/commands/update.js
CHANGED
|
@@ -81,7 +81,10 @@ async function postUpdateAutoCommit(git, newVersion, options) {
|
|
|
81
81
|
if (userOwned.length > 0) {
|
|
82
82
|
UI.warning(`${userOwned.length} other dirty path(s) will be left untouched (not BALDART-managed).`);
|
|
83
83
|
}
|
|
84
|
-
const
|
|
84
|
+
const autoYes = options && options.yes === true;
|
|
85
|
+
const ok = autoYes
|
|
86
|
+
? true
|
|
87
|
+
: await UI.confirm(`Commit them as \`chore(baldart): post-update reconcile to v${newVersion}\`?`, true);
|
|
85
88
|
if (!ok) {
|
|
86
89
|
UI.info('Auto-commit declined. Your worktree keeps the changes for manual review.');
|
|
87
90
|
return;
|
|
@@ -110,6 +113,13 @@ async function update(options = {}) {
|
|
|
110
113
|
const symlinks = new SymlinkUtils();
|
|
111
114
|
const repo = 'antbald/BALDART'; // Default repo
|
|
112
115
|
|
|
116
|
+
// Non-interactive flags (since v3.17.1). `--yes` implies `--auto-stash`.
|
|
117
|
+
// When `yes` is set, every UI.confirm/UI.select downstream short-circuits
|
|
118
|
+
// to its default — the same value an unattended user would have produced.
|
|
119
|
+
const autoYes = options.yes === true;
|
|
120
|
+
const autoStash = autoYes || options.autoStash === true;
|
|
121
|
+
const confirm = async (msg, def = true) => (autoYes ? def : UI.confirm(msg, def));
|
|
122
|
+
|
|
113
123
|
try {
|
|
114
124
|
// Step 1: Verify installation
|
|
115
125
|
UI.header('STEP 1/5: Verify Installation');
|
|
@@ -194,14 +204,14 @@ async function update(options = {}) {
|
|
|
194
204
|
const diff = await git.diffWithRemote();
|
|
195
205
|
if (diff) {
|
|
196
206
|
UI.info('Changes detected. Review recommended.');
|
|
197
|
-
const showDiff = await
|
|
207
|
+
const showDiff = await confirm('Show detailed diff?', false);
|
|
198
208
|
|
|
199
209
|
if (showDiff) {
|
|
200
210
|
console.log(diff);
|
|
201
211
|
}
|
|
202
212
|
}
|
|
203
213
|
|
|
204
|
-
const proceedUpdate = await
|
|
214
|
+
const proceedUpdate = await confirm('Proceed with update?', true);
|
|
205
215
|
if (!proceedUpdate) {
|
|
206
216
|
UI.info('Update cancelled');
|
|
207
217
|
process.exit(0);
|
|
@@ -225,10 +235,16 @@ async function update(options = {}) {
|
|
|
225
235
|
UI.list(unique.slice(0, 8), 'yellow');
|
|
226
236
|
if (unique.length > 8) UI.info(`…and ${unique.length - 8} more.`);
|
|
227
237
|
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
238
|
+
let action;
|
|
239
|
+
if (autoStash) {
|
|
240
|
+
UI.info('Auto-stashing dirty paths (--yes / --auto-stash).');
|
|
241
|
+
action = 'stash';
|
|
242
|
+
} else {
|
|
243
|
+
action = await UI.select('How would you like to proceed?', [
|
|
244
|
+
{ name: 'Auto-stash now and re-apply after the update (recommended)', value: 'stash' },
|
|
245
|
+
{ name: 'Abort — I want to commit or stash manually first', value: 'abort' }
|
|
246
|
+
]);
|
|
247
|
+
}
|
|
232
248
|
if (action === 'abort') {
|
|
233
249
|
UI.info('Aborted. Commit or `git stash -u`, then re-run `npx baldart update`.');
|
|
234
250
|
process.exit(0);
|
|
@@ -361,9 +377,12 @@ async function update(options = {}) {
|
|
|
361
377
|
'Convert legacy .claude/skills/ bulk symlink (v2.0.x) into the new per-item layout.',
|
|
362
378
|
`Merge framework skills into per-tool skill dirs (${enabledTools.join(', ')}) without touching your personal skills.`
|
|
363
379
|
]);
|
|
364
|
-
const recreate = await
|
|
380
|
+
const recreate = await confirm('Reconcile symlinks now?', true);
|
|
365
381
|
if (recreate) {
|
|
366
|
-
|
|
382
|
+
// In auto-yes mode pass 'force' instead of 'prompt' so per-conflict
|
|
383
|
+
// prompts inside SymlinkUtils don't reintroduce blocking I/O.
|
|
384
|
+
const mode = autoYes ? 'force' : 'prompt';
|
|
385
|
+
await symlinks.createAllSymlinks({ mode, tools: enabledTools });
|
|
367
386
|
}
|
|
368
387
|
} else {
|
|
369
388
|
// Re-run the per-item merges for every enabled tool so newly-shipped
|
|
@@ -403,10 +422,10 @@ async function update(options = {}) {
|
|
|
403
422
|
if (!fs.existsSync(configPath)) {
|
|
404
423
|
UI.newline();
|
|
405
424
|
UI.warning('No baldart.config.yml in this project — skills installed by this framework version require it.');
|
|
406
|
-
const runConfigure = await
|
|
425
|
+
const runConfigure = await confirm('Run `baldart configure` now?', true);
|
|
407
426
|
if (runConfigure) {
|
|
408
427
|
const configureCmd = require('./configure');
|
|
409
|
-
await configureCmd();
|
|
428
|
+
await configureCmd(autoYes ? { nonInteractive: true } : undefined);
|
|
410
429
|
}
|
|
411
430
|
} else {
|
|
412
431
|
// Schema migration: backfill `tools.enabled` for configs created
|
|
@@ -425,7 +444,7 @@ async function update(options = {}) {
|
|
|
425
444
|
UI.warning('Pre-v3.7.0 config detected — `tools.enabled` is missing.');
|
|
426
445
|
UI.info(`From v3.7.0, BALDART can install framework skills for multiple AI tools (Claude Code, OpenAI Codex CLI, …) from the same source.`);
|
|
427
446
|
UI.info(`Autodetected on this machine: ${suggested.join(', ')}`);
|
|
428
|
-
const ok = await
|
|
447
|
+
const ok = await confirm(`Backfill \`tools.enabled: [${suggested.join(', ')}]\` into baldart.config.yml?`, true);
|
|
429
448
|
if (ok) {
|
|
430
449
|
cur.tools = cur.tools || {};
|
|
431
450
|
cur.tools.enabled = suggested;
|
|
@@ -470,10 +489,10 @@ async function update(options = {}) {
|
|
|
470
489
|
UI.warning(`New config keys in this version: ${allMissing.join(', ')}.`);
|
|
471
490
|
// Auto-offer configure so the user actually answers, instead of
|
|
472
491
|
// silently falling back to template defaults on first use.
|
|
473
|
-
const runConfigure = await
|
|
492
|
+
const runConfigure = await confirm('Run `baldart configure` now to populate them? (existing values are preserved)', true);
|
|
474
493
|
if (runConfigure) {
|
|
475
494
|
const configureCmd = require('./configure');
|
|
476
|
-
await configureCmd();
|
|
495
|
+
await configureCmd(autoYes ? { nonInteractive: true } : undefined);
|
|
477
496
|
} else {
|
|
478
497
|
UI.info('Skipped. Skills will prompt for the missing keys on first use.');
|
|
479
498
|
}
|