baldart 4.72.0 → 4.74.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 +34 -0
- package/VERSION +1 -1
- package/framework/.claude/agents/prd-card-writer.md +16 -2
- package/framework/.claude/skills/new/references/implement.md +30 -3
- package/framework/.claude/skills/prd/assets/card-template.yml +7 -1
- package/framework/.claude/skills/prd/references/discovery-phase.md +24 -1
- package/framework/.claude/skills/prd/references/ui-design-phase.md +11 -3
- package/framework/agents/card-schema.md +9 -0
- package/framework/templates/baldart.config.template.yml +19 -0
- package/package.json +1 -1
- package/src/commands/configure.js +17 -0
- package/src/commands/update.js +41 -5
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,40 @@ 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.74.0] - 2026-06-25
|
|
9
|
+
|
|
10
|
+
**Update in autonomy: a standing `git.on_divergence` policy so `npx baldart update` stops asking every release.** A consumer that keeps local commits on `.framework/` which the updater cannot auto-classify as overlays — the canonical case being a project-specific edit to a `framework/agents/*.md` **protocol module** (e.g. a design-system review rule naming the project's own files), which unlike `.claude/{agents,skills,commands}/` has **no overlay channel** — was stopped on EVERY update with "Custom commits touch non-overlayable paths … cannot be auto-resolved", forcing a manual `--on-divergence pull` each time. The non-interactive escape hatch existed only as a per-run CLI flag; there was no way to persist the choice. Now `git.on_divergence` in `baldart.config.yml` records a standing policy the interactive `update` honours, so a consumer opts in once and never passes the flag again. The `--on-divergence` CLI flag still wins for a single run, and the default (unset/null) preserves the current "stop once and ask" behaviour exactly.
|
|
11
|
+
|
|
12
|
+
**MINOR** — adds capability via a new config key. The **schema-change propagation rule applies** and is satisfied end-to-end: template (`git.on_divergence`) + configure prompt + update detector (already diffs all `git.*` keys, so the new key auto-surfaces on existing configs) + the gating code in `update` + CHANGELOG. Backward compatible: an existing config without the key resolves to null → no policy → unchanged behaviour; a config typo fails loud up-front (validated against the same `DIVERGENCE_STRATEGIES` set as the flag).
|
|
13
|
+
|
|
14
|
+
### Added
|
|
15
|
+
|
|
16
|
+
- **`git.on_divergence` standing divergence policy** (`framework/templates/baldart.config.template.yml` § `git:`). Values: `null`/unset (DEFAULT — stop once and ask), `pull` (keep the local commits and merge upstream over them; non-destructive, the autonomy choice), `scaffold-overlays` (auto-scaffold the overlay-able subset, then stop). Read by `update` via a new `readDivergencePolicy()` helper; surfaced interactively (`Applying persisted divergence policy …`) so an auto-merge is never a surprise. The `configure` git section now prompts for it (`src/commands/configure.js`), writing the key explicitly (as null when unset) so the schema-drift detector does not re-offer it every release.
|
|
17
|
+
|
|
18
|
+
### Changed
|
|
19
|
+
|
|
20
|
+
- **`update` resolves the divergence strategy as flag-or-config** (`src/commands/update.js`). `--on-divergence` (explicit per-run override) wins; absent a flag, the resolver falls back to `git.on_divergence`. Up-front validation now reports the correct source (`--on-divergence` vs ``git.on_divergence`` in baldart.config.yml) on a typo. The stale-CLI self-relaunch needs no change — the newer child re-reads the same config and resolves the policy itself.
|
|
21
|
+
|
|
22
|
+
### Fixed
|
|
23
|
+
|
|
24
|
+
- **`links.design` now resolves to a path that EXISTS — making the v4.73.0 image-load fire in the mockup-only case.** v4.73.0 made the `/new` implementer `Read` the mockup image when `links.design` is a `.png`, but the card field was still hardcoded to `design.html` (`framework/.claude/agents/prd-card-writer.md`, `framework/.claude/skills/prd/assets/card-template.yml`). In **mockup-only** runs — exactly the "I hand `/prd` Claude Design / Figma mockups" flow — no `design.html` is generated (`ui-design-phase.md` sets `design.html_path: n/a (mockup-only)`), so the card pointed at a **non-existent** file: the implementer got a dead path AND the image branch never fired (extension was `.html`). `prd-card-writer` now resolves `links.design` to an on-disk path — design.html when generated, ELSE the screen's canonical mockup PNG from `mockup_analysis.screens[].mockup_ref` / `prerender_png` under `mockups/` (chat-only `chat://image-N` images are never persisted, so such a card is flagged visually-unreferenced rather than pointed at a path that won't resolve). The `/new` briefing (`framework/.claude/skills/new/references/implement.md` § "Design Reference") also falls back to the sibling `mockups/` PNG when a legacy card's `.html` path is absent. No behaviour change for generated-design runs.
|
|
25
|
+
|
|
26
|
+
## [4.73.0] - 2026-06-25
|
|
27
|
+
|
|
28
|
+
**Mockup→code fidelity: let the implementer actually SEE the mockup, and carry the intent a flat list loses.** A recurring complaint: a detailed mockup (Claude Design / Figma) is handed to `/prd`, then `/new` ships something that is not 1:1 with the design. The obvious diagnosis — "the PRD's visual mapping is too weak, add a mockup-understanding agent" — was **refuted** by an adversarial pass: the visual data largely already exists (`mockup_analysis` captures affordances + traceability + states + DS violations; `component_bindings` propagates region→component to the implementer), and a parallel machine-readable `design-spec` artifact would be a second SSOT that drifts from the mockup and would feed the verifier a textual proxy instead of the pixels (a bias `visual-fidelity-verifier` explicitly forbids). The **actual driver**: the `/new` UI briefing passes the mockup as a *path* and says "Read the design.html file" — but for a binary PNG/Figma mockup nothing ever instructs the (multimodal) `ui-expert` to **load the image into its context**. The implementer was building from text, never looking at the design.
|
|
29
|
+
|
|
30
|
+
**MINOR** — adds capability; **no new `baldart.config.yml` key** (rides on the existing `features.has_design_system`), so the schema-change propagation rule does NOT apply. The new `component_bindings[].props` is an **optional** sub-field — legacy cards omit it and the validator (which keys on the `C`-profile top-level field) is unaffected. On a non-multimodal tool (Codex) the image-load instruction degrades to a harmless no-op and the structured fields carry the intent.
|
|
31
|
+
|
|
32
|
+
### Changed
|
|
33
|
+
|
|
34
|
+
- **The `/new` UI briefing loads the mockup IMAGE multimodally** (`framework/.claude/skills/new/references/implement.md` § "Design Reference"). The instruction now branches on the file type of `links.design`: an IMAGE (`.png`/`.jpg`/`.jpeg`/`.webp`/`.pdf`, or a `mockups/*.png` canonical path) is **`Read` directly** so it renders into the agent's context (pixels = fidelity target); an `.html` keeps the existing read-as-text behaviour. The briefing also now surfaces `component_bindings[].props`/`variant` (exact variant intent) and the card's responsive ACs (breakpoint behaviour a single-viewport image cannot show) as the structured complement to the image.
|
|
35
|
+
- **`mockup_analysis` schema enriched in-place with the four dimensions a flat affordance list + a single static image lose** (`framework/.claude/skills/prd/references/discovery-phase.md` § "Mockup analysis schema"). Each `screens[]` entry gains four OPTIONAL fields: `layout` (compact nesting sketch — the hierarchy), `component_props` (key props/variant legible per component — the reuse-vs-`reuse-variant` signal), `states` (richer `states_visible` mapping each state→the AC that mandates it; an untraced state is a Discovery gap by the same rule as an untraced affordance), and `responsive` (breakpoint behaviour). "Omit rather than guess" discipline extended to all four.
|
|
36
|
+
- **Component Reconciliation consumes the enrichment and persists it** (`framework/.claude/skills/prd/references/ui-design-phase.md` § "Component Reconciliation"). `REUSE+VARIANT` detection now compares `mockup_analysis.component_props` against the component's spec HEAD (props the spec doesn't cover ⇒ variant), and the persist step carries resolved props into `component_bindings[].props` and lifts `responsive` notes into the UI card's `acceptance_criteria` as testable ACs.
|
|
37
|
+
|
|
38
|
+
### Added
|
|
39
|
+
|
|
40
|
+
- **`component_bindings[].props` card sub-field** (`framework/agents/card-schema.md` § `component_bindings`) — the optional resolved props/variant each region must use, the exact-variant intent a static image leaves ambiguous; `ui-expert` implements it verbatim. Sub-field of the existing `C`-profile `component_bindings`, so no validator/matrix change.
|
|
41
|
+
|
|
8
42
|
## [4.72.0] - 2026-06-25
|
|
9
43
|
|
|
10
44
|
**Session-id provenance on PRDs and cards — trace an implementation bug back to the chat history that produced it.** When `/new` ships a card that turns out wrong, there was no link from the card back to *which Claude Code session* planned it (`/prd`) or implemented it (`/new`/`/new2`) — so you couldn't reopen the transcript to see the steps the session took (or skipped). Now every card carries an optional `provenance` block recording the session UUID for each lifecycle stage, and the PRD frontmatter records its authoring session. Resolve the chat history at `~/.claude/projects/*/<session-id>.jsonl` (the UUID is globally unique — no project slug needed). This turns "this feature is broken, why?" into a concrete pointer at the planning/implementation conversation, for improving the framework from real runs.
|
package/VERSION
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
4.
|
|
1
|
+
4.74.0
|
|
@@ -468,7 +468,19 @@ Every card MUST include ALL fields from the template:
|
|
|
468
468
|
- `review_profile` (`skip`|`light`|`balanced`|`deep` — computed via Rule C, or honored if pre-set)
|
|
469
469
|
- `files_likely_touched` (exact paths with NEW/MODIFY)
|
|
470
470
|
- `links.prd` (path to PRD)
|
|
471
|
-
- `links.design` (
|
|
471
|
+
- `links.design` (REQUIRED for any card with UI scope) — the card's screen design
|
|
472
|
+
reference, resolved to a path **that exists on disk in the worktree**:
|
|
473
|
+
- **design.html when one was generated** for this screen (Step 3 produced it).
|
|
474
|
+
- **ELSE the screen's canonical mockup PNG** under `${paths.prd_dir}/<slug>/mockups/`
|
|
475
|
+
— read it from the screen's `mockup_analysis.screens[].mockup_ref` (or
|
|
476
|
+
`prerender_png`). A **mockup-only** run (user-provided Claude Design / Figma
|
|
477
|
+
mockups, no design generated) sets `design.html_path: n/a (mockup-only)` — in
|
|
478
|
+
that case design.html does NOT exist, so pointing at it gives the `/new`
|
|
479
|
+
implementer a dead path and no visual reference. Use the PNG.
|
|
480
|
+
- A **chat-only image** (`mockup_ref: chat://image-N`, never persisted to disk —
|
|
481
|
+
see `discovery-phase.md` 1.6.4 step 3) cannot be referenced: it is unreachable
|
|
482
|
+
from `/new`. Flag the card as visually-unreferenced rather than writing a path
|
|
483
|
+
that won't resolve in the worktree.
|
|
472
484
|
- `canonical_docs` — REQUIRED for all cards generated from a PRD:
|
|
473
485
|
- `ssot_registry_entry`: scan `${paths.references_dir}/ssot-registry.md` and copy the exact row name (column 1, bold) matching this card's macro feature. If the feature is new and has no entry yet, use the PRD slug.
|
|
474
486
|
- `data_model_refs`: list of `${paths.references_dir}/collections/*.md` paths for collections touched by this card — REQUIRED whenever the card has `data_fields`; derive from the collection names in `data_fields`.
|
|
@@ -605,7 +617,9 @@ Rules:
|
|
|
605
617
|
|
|
606
618
|
Rules:
|
|
607
619
|
- Every UI element MUST map to at least one card
|
|
608
|
-
- Every card with UI scope MUST include `links.design
|
|
620
|
+
- Every card with UI scope MUST include `links.design`, resolved to an on-disk
|
|
621
|
+
path per the `links.design` rule above (design.html when generated, else the
|
|
622
|
+
screen's canonical mockup PNG — never a non-existent design.html)
|
|
609
623
|
- Every UI requirement MUST reference the specific element number
|
|
610
624
|
|
|
611
625
|
## Parallel Group Computation (MANDATORY)
|
|
@@ -111,9 +111,36 @@
|
|
|
111
111
|
|
|
112
112
|
### Design Reference (UI cards only — include if card has links.design)
|
|
113
113
|
Design file: [path from card's links.design field]
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
114
|
+
|
|
115
|
+
**Load the mockup into your context — branch on the file type:**
|
|
116
|
+
- If `links.design` resolves to an IMAGE (`.png` / `.jpg` / `.jpeg` /
|
|
117
|
+
`.webp` / `.pdf`, or the card points at a `mockups/*.png` canonical path):
|
|
118
|
+
you are multimodal — **`Read` the mockup image file path directly**. `Read`
|
|
119
|
+
renders the image visually into your context (the native multimodal
|
|
120
|
+
mechanism). The mockup's PIXELS are the fidelity target: study layout,
|
|
121
|
+
spacing, hierarchy, density, color, and the relative position of every
|
|
122
|
+
region. Do NOT implement from the filename or a textual guess — you MUST
|
|
123
|
+
actually load and look at the image. (On a non-multimodal tool the `Read`
|
|
124
|
+
degrades to no-op text; fall back to the structured fields below.)
|
|
125
|
+
- If `links.design` is an `.html` file: **Read the design.html file** and use
|
|
126
|
+
it as the visual reference. **If that file does not exist** (a legacy
|
|
127
|
+
mockup-only card that points at a `design.html` which was never generated),
|
|
128
|
+
fall back: look under the PRD's `mockups/` dir (sibling of the `links.design`
|
|
129
|
+
path) for the screen's PNG/JPG and `Read` that image instead — do not proceed
|
|
130
|
+
with no visual reference.
|
|
131
|
+
|
|
132
|
+
Use it as the **visual reference** for your implementation (layout, spacing,
|
|
133
|
+
hierarchy, copy). The design was approved by the user — your implementation
|
|
134
|
+
MUST match its appearance.
|
|
135
|
+
|
|
136
|
+
**Structured design intent (when present on the card).** The image shows the
|
|
137
|
+
static look at one viewport; these fields cover what a single image cannot:
|
|
138
|
+
- `component_bindings[].props` / `variant` — the exact props/variant each
|
|
139
|
+
region must use (e.g. `SectionHeader {depth: 0}`, `DataTable {density:
|
|
140
|
+
comfortable}`). Implement them verbatim; do not eyeball a variant.
|
|
141
|
+
- the card's responsive acceptance criteria — breakpoint behaviour the
|
|
142
|
+
single-viewport mockup does not show (e.g. "≤640px: table collapses to
|
|
143
|
+
card-list"). These are testable ACs, not optional polish.
|
|
117
144
|
|
|
118
145
|
**The mockup is a visual reference, NOT a license to re-create components.**
|
|
119
146
|
When the card carries `component_bindings` (the `/prd` Component Reconciliation
|
|
@@ -130,7 +130,13 @@ files_likely_touched:
|
|
|
130
130
|
|
|
131
131
|
links:
|
|
132
132
|
prd: "${paths.prd_dir}/{{slug}}/PRD.md"
|
|
133
|
-
|
|
133
|
+
# REQUIRED for UI cards (omit for API-only). Point at the card's screen design
|
|
134
|
+
# reference THAT EXISTS ON DISK: the generated design.html when one was produced,
|
|
135
|
+
# ELSE the screen's canonical mockup PNG under mockups/ (mockup-only runs have NO
|
|
136
|
+
# design.html). Never point at a design.html that was not generated — the /new
|
|
137
|
+
# implementer loads this path; a chat-only image (chat://image-N, never persisted)
|
|
138
|
+
# cannot be referenced.
|
|
139
|
+
design: "${paths.prd_dir}/{{slug}}/design.html" # OR ${paths.prd_dir}/{{slug}}/mockups/<screen>.png
|
|
134
140
|
|
|
135
141
|
# --- Canonical Docs (REQUIRED for cards generated from a PRD) ---
|
|
136
142
|
# Traces which SSOT documents informed this card's requirements.
|
|
@@ -1122,11 +1122,24 @@ mockup_analysis:
|
|
|
1122
1122
|
purpose: <one line — what this screen lets the user do>
|
|
1123
1123
|
components:
|
|
1124
1124
|
- <component name from ${paths.design_system}/INDEX.md if matched, else free-form>
|
|
1125
|
+
layout: # OPTIONAL — compact nesting sketch (the hierarchy a flat list loses)
|
|
1126
|
+
- region: <e.g. "page-header">
|
|
1127
|
+
component: <component or free-form for this region>
|
|
1128
|
+
contains: [<child region/component>, …] # omit for leaf regions
|
|
1129
|
+
component_props: # OPTIONAL — key props/variants legible from the mockup, per component
|
|
1130
|
+
- component: <e.g. "SectionHeader">
|
|
1131
|
+
props: {<prop>: <value>} # e.g. {depth: 0} · {density: comfortable, selectable: true}
|
|
1125
1132
|
affordances: # interactive + iconographic artifacts visible in the mockup
|
|
1126
1133
|
- label: <e.g. "Export CSV button", "bell icon", "Filters tab">
|
|
1127
1134
|
traces_to: <US-id / AC-id / discovery dimension this artifact serves — or "" if none yet>
|
|
1128
1135
|
classification: backed | decorative | orphan
|
|
1129
1136
|
states_visible: [empty, loading, error, success, default]
|
|
1137
|
+
states: # OPTIONAL — richer form: map each visible state to the AC that mandates it
|
|
1138
|
+
- state: <loading | empty | error | success | default>
|
|
1139
|
+
traces_to: <AC-id — or "" if no AC mandates it yet (a Discovery gap, see below)>
|
|
1140
|
+
representation: <one line, e.g. "skeleton rows ×5", "EmptyState, copy 'Nessun ordine'">
|
|
1141
|
+
responsive: # OPTIONAL — breakpoint behaviour a single-viewport mockup cannot show
|
|
1142
|
+
- <e.g. "≤640px: DataTable collapses to card-list; header shows back button">
|
|
1130
1143
|
copy_excerpts:
|
|
1131
1144
|
- <verbatim text visible in the mockup, max ~10 strings per screen>
|
|
1132
1145
|
gaps:
|
|
@@ -1148,11 +1161,21 @@ mockup_analysis:
|
|
|
1148
1161
|
- Omit `copy_excerpts` for screens that have no readable text (icon-only).
|
|
1149
1162
|
- Omit `states_visible` for screens that show only the default state — do not
|
|
1150
1163
|
invent states the mockup doesn't show.
|
|
1164
|
+
- `layout`, `component_props`, `states`, and `responsive` are OPTIONAL
|
|
1165
|
+
enrichments. Fill them when the mockup makes them legible — they carry exactly
|
|
1166
|
+
the intent a flat affordance list and a single static image lose (nesting
|
|
1167
|
+
hierarchy, the exact component variant, state→AC mapping, breakpoint
|
|
1168
|
+
behaviour). Omit a field rather than guess; never invent a breakpoint,
|
|
1169
|
+
prop, or nesting the mockup doesn't actually show (same rule as `states_visible`).
|
|
1170
|
+
These feed the Component Reconciliation (reuse vs `reuse-variant`) and the
|
|
1171
|
+
`/new` implementer briefing — they are how visual intent survives to code.
|
|
1151
1172
|
|
|
1152
1173
|
**Gap discipline:** every entry in `gaps[]` MUST become either a Discovery question
|
|
1153
1174
|
or an explicit non-question (recorded in Discovery Log as "skipped — outside
|
|
1154
1175
|
mockup scope"). Gaps that remain unresolved by the end of Discovery block PRD
|
|
1155
|
-
writing in Step 4.
|
|
1176
|
+
writing in Step 4. A `states[]` entry with `traces_to: ""` is a gap by the same
|
|
1177
|
+
rule — a visible state no AC mandates must be resolved in Discovery (gain an AC,
|
|
1178
|
+
or be confirmed cosmetic), exactly like an untraced affordance.
|
|
1156
1179
|
|
|
1157
1180
|
**Affordance discipline (Functional Traceability):** classify every interactive
|
|
1158
1181
|
or iconographic artifact the mockup shows. The mockup is a fidelity target, not
|
|
@@ -158,7 +158,10 @@ Before invoking `ui-design`:
|
|
|
158
158
|
- **REUSE** — an existing component (or a closed-family canonical member) covers
|
|
159
159
|
it. Record the exact component. *No question asked — this is the silent path.*
|
|
160
160
|
- **REUSE+VARIANT** — an existing component covers it but the mockup needs a
|
|
161
|
-
prop/variant not yet in its spec.
|
|
161
|
+
prop/variant not yet in its spec. The `mockup_analysis.component_props` for
|
|
162
|
+
this component is the signal: compare the legible props against the
|
|
163
|
+
component's spec HEAD — props the spec doesn't cover ⇒ this is a VARIANT,
|
|
164
|
+
not a plain REUSE.
|
|
162
165
|
- **NEW** — genuinely no match.
|
|
163
166
|
5. **Ask ONLY on ambiguity** (`REUSE+VARIANT` and `NEW` — never on plain REUSE).
|
|
164
167
|
One consolidated `AskUserQuestion` per ambiguous element, in the
|
|
@@ -179,7 +182,12 @@ Before invoking `ui-design`:
|
|
|
179
182
|
- Into each affected card → the `component_bindings` block (see
|
|
180
183
|
`agents/card-schema.md`): `region → component (reuse | new) [+ variant]`. This
|
|
181
184
|
is the authority `ui-expert` implements against (the mockup becomes a visual
|
|
182
|
-
reference, not a license to re-create).
|
|
185
|
+
reference, not a license to re-create). **Carry the resolved props** from
|
|
186
|
+
`mockup_analysis.component_props` into `component_bindings[].props` so the
|
|
187
|
+
exact-variant intent reaches the implementer (a single static image leaves it
|
|
188
|
+
ambiguous). Likewise, lift any `mockup_analysis.screens[].responsive` notes
|
|
189
|
+
into the UI card's `acceptance_criteria` as testable responsive ACs — the
|
|
190
|
+
mockup cannot show breakpoint behaviour at one viewport.
|
|
183
191
|
- Into the manifest → when the user confirms a new standard / closure, the
|
|
184
192
|
component's `canonical_for` / `use_when` / `selection_closed` are written (via
|
|
185
193
|
`/design-system-init` enrich or `doc-reviewer`), so `baldart ds-gate` enforces
|
|
@@ -222,7 +230,7 @@ Pass the resolved inventory to `ui-design`:
|
|
|
222
230
|
```
|
|
223
231
|
### Component Reconciliation (resolved)
|
|
224
232
|
REUSE — bind region → existing component:
|
|
225
|
-
- [region] → [Component] — [path] — [variant if any]
|
|
233
|
+
- [region] → [Component] — [path] — [variant if any] — [props if any]
|
|
226
234
|
|
|
227
235
|
NEW (governed) — confirmed with user:
|
|
228
236
|
- [region] → [NewComponent] — role: [family@context] — standard: [yes/no] — closed: [yes/no]
|
|
@@ -148,6 +148,7 @@ component_bindings:
|
|
|
148
148
|
action: reuse # reuse | reuse-variant | new
|
|
149
149
|
variant: "" # when action: reuse-variant
|
|
150
150
|
role: "page-header@depth-0" # when it maps a Selection-Policy role
|
|
151
|
+
props: {depth: 0} # optional — exact props the region must use (from mockup_props)
|
|
151
152
|
- region: "row actions"
|
|
152
153
|
component: IconButton
|
|
153
154
|
action: reuse
|
|
@@ -159,6 +160,14 @@ that finds a `component_bindings` entry MUST honor it over any contrary reading
|
|
|
159
160
|
the mockup; a UI card that went through `/prd` with `features.has_design_system:
|
|
160
161
|
true` but lacks this field is under-specified (the reconciliation did not run).
|
|
161
162
|
|
|
163
|
+
`props` is **optional** — the resolved key props/variant the region must use,
|
|
164
|
+
lifted from the mockup analysis `component_props` during reconciliation (e.g.
|
|
165
|
+
`{depth: 0}`, `{density: comfortable}`). It carries the exact-variant intent a
|
|
166
|
+
single static image leaves ambiguous; `ui-expert` implements it verbatim. Omit it
|
|
167
|
+
when the mockup exposes no distinguishing props (the component's defaults apply).
|
|
168
|
+
Breakpoint behaviour the mockup cannot show at a single viewport lives in the
|
|
169
|
+
card's `acceptance_criteria` as testable responsive ACs, not here.
|
|
170
|
+
|
|
162
171
|
### `provenance` (session-id traceability — optional, skill-written)
|
|
163
172
|
|
|
164
173
|
A small block that records **which Claude Code session produced the card**, so a card
|
|
@@ -443,6 +443,25 @@ git:
|
|
|
443
443
|
# the production-readiness gate, e.g. ["supabase-db-push", "vercel-prod"].
|
|
444
444
|
auto_deploy: []
|
|
445
445
|
|
|
446
|
+
# Standing divergence policy (since v4.74.0). When THIS consumer has local
|
|
447
|
+
# commits on `.framework/` that `npx baldart update` cannot auto-classify as
|
|
448
|
+
# overlays — e.g. a project-specific edit to a `framework/agents/*.md` protocol
|
|
449
|
+
# module, which (unlike `.claude/{agents,skills,commands}/`) has NO overlay
|
|
450
|
+
# channel — the interactive update STOPS once and asks. Set a standing policy
|
|
451
|
+
# here to let `update` self-resolve without passing `--on-divergence` every
|
|
452
|
+
# release (the "update in autonomy" knob):
|
|
453
|
+
#
|
|
454
|
+
# null / unset — DEFAULT. Stop once and ask (current behaviour).
|
|
455
|
+
# pull — keep the local commits and merge upstream over them.
|
|
456
|
+
# Non-destructive: a merge commit is created; resolve any
|
|
457
|
+
# conflicts that surface. This is the autonomy choice.
|
|
458
|
+
# scaffold-overlays — auto-scaffold overlay skeletons for the overlay-able
|
|
459
|
+
# subset, then stop (only helps `.claude/{agents,skills,
|
|
460
|
+
# commands}/` edits — useless for protocol-module drift).
|
|
461
|
+
#
|
|
462
|
+
# The `--on-divergence` CLI flag always overrides this key for a single run.
|
|
463
|
+
on_divergence: null
|
|
464
|
+
|
|
446
465
|
# ─── TOOLS ───────────────────────────────────────────────────────────────
|
|
447
466
|
# Which AI CLI tools should the framework target on this machine?
|
|
448
467
|
# Each enabled tool gets its own per-item skill symlinks pointing at the
|
package/package.json
CHANGED
|
@@ -694,6 +694,23 @@ async function interactivePrompts(merged, detected) {
|
|
|
694
694
|
? autoDeployAnswer.split(',').map((s) => s.trim()).filter(Boolean)
|
|
695
695
|
: [];
|
|
696
696
|
|
|
697
|
+
// ---- Standing divergence policy (since v4.74.0) -----------------------
|
|
698
|
+
// Lets `npx baldart update` self-resolve `.framework/` divergence without a
|
|
699
|
+
// manual `--on-divergence` flag every release. Default (blank) = stop & ask.
|
|
700
|
+
const currentDiv = (typeof merged.git.on_divergence === 'string' && merged.git.on_divergence.trim())
|
|
701
|
+
? merged.git.on_divergence.trim() : '';
|
|
702
|
+
const chosenDiv = await UI.select(
|
|
703
|
+
`Standing divergence policy for \`npx baldart update\` (current: ${currentDiv || 'ask each time'})`,
|
|
704
|
+
[
|
|
705
|
+
{ name: 'ask each time — stop & ask when .framework/ has unclassifiable local commits (default)', value: '' },
|
|
706
|
+
{ name: 'pull — auto-merge upstream over the local commits (non-destructive; the autonomy choice)', value: 'pull' },
|
|
707
|
+
{ name: 'scaffold-overlays — auto-scaffold overlays for the overlay-able subset, then stop', value: 'scaffold-overlays' },
|
|
708
|
+
]
|
|
709
|
+
);
|
|
710
|
+
// Write the key even when unset (as null) so the schema-drift detector does
|
|
711
|
+
// not re-offer it on every future update.
|
|
712
|
+
merged.git.on_divergence = chosenDiv || null;
|
|
713
|
+
|
|
697
714
|
UI.section('Features (explicit yes/no — option A: always ask)');
|
|
698
715
|
for (const flag of [
|
|
699
716
|
['has_design_system', 'Project has a documented design system?'],
|
package/src/commands/update.js
CHANGED
|
@@ -29,6 +29,27 @@ function readEnabledTools(cwd = process.cwd()) {
|
|
|
29
29
|
// agent-overlay feedback that prompted this).
|
|
30
30
|
const DIVERGENCE_STRATEGIES = ['abort', 'pull', 'scaffold-overlays'];
|
|
31
31
|
|
|
32
|
+
// Persisted divergence policy (since v4.74.0). When a consumer has local commits
|
|
33
|
+
// on `.framework/` that the interactive flow would otherwise STOP on (e.g. a
|
|
34
|
+
// project-specific edit to a `framework/agents/*.md` protocol module, which has
|
|
35
|
+
// no overlay channel), `git.on_divergence` in baldart.config.yml lets `update`
|
|
36
|
+
// self-resolve without a manual `--on-divergence` flag every release. The CLI
|
|
37
|
+
// flag still wins (explicit per-run override). Returns the raw string (validated
|
|
38
|
+
// by the caller, so a config typo fails loud) or null when no policy is set.
|
|
39
|
+
function readDivergencePolicy(cwd = process.cwd()) {
|
|
40
|
+
try {
|
|
41
|
+
const fs = require('fs');
|
|
42
|
+
const path = require('path');
|
|
43
|
+
const yaml = require('js-yaml');
|
|
44
|
+
const cfgPath = path.join(cwd, 'baldart.config.yml');
|
|
45
|
+
if (!fs.existsSync(cfgPath)) return null;
|
|
46
|
+
const cfg = yaml.load(fs.readFileSync(cfgPath, 'utf8'));
|
|
47
|
+
const v = cfg && cfg.git && cfg.git.on_divergence;
|
|
48
|
+
if (typeof v === 'string' && v.trim()) return v.trim();
|
|
49
|
+
} catch (_) { /* fall through — no policy */ }
|
|
50
|
+
return null;
|
|
51
|
+
}
|
|
52
|
+
|
|
32
53
|
// Machine-readable result channel (v3.32.0+). When --json is active, STDOUT
|
|
33
54
|
// must carry exactly ONE JSON object and nothing else — UI.setJsonMode() has
|
|
34
55
|
// already redirected every human line to STDERR. Every terminal point in the
|
|
@@ -749,15 +770,25 @@ async function update(options = {}, unknownArgs = []) {
|
|
|
749
770
|
}
|
|
750
771
|
}
|
|
751
772
|
|
|
752
|
-
//
|
|
753
|
-
//
|
|
754
|
-
|
|
773
|
+
// Divergence strategy resolution (flag since v3.29.0; persisted policy since
|
|
774
|
+
// v4.74.0). The `--on-divergence` CLI flag is the explicit per-run override;
|
|
775
|
+
// absent a flag, fall back to the standing `git.on_divergence` policy in
|
|
776
|
+
// baldart.config.yml so a consumer that opted in never has to pass the flag.
|
|
777
|
+
// Validated up-front so a typo (in EITHER source) fails fast instead of
|
|
778
|
+
// mid-flow after the network fetch.
|
|
779
|
+
const divFlag = options.onDivergence;
|
|
780
|
+
const divFromConfig = divFlag === undefined ? readDivergencePolicy() : null;
|
|
781
|
+
const divStrategy = divFlag !== undefined ? divFlag : (divFromConfig || undefined);
|
|
782
|
+
const divStrategySource = divFlag !== undefined ? 'flag' : (divFromConfig ? 'config' : null);
|
|
755
783
|
if (divStrategy !== undefined && !DIVERGENCE_STRATEGIES.includes(divStrategy)) {
|
|
784
|
+
const where = divStrategySource === 'config'
|
|
785
|
+
? '`git.on_divergence` in baldart.config.yml'
|
|
786
|
+
: '--on-divergence';
|
|
756
787
|
if (JSON_MODE) {
|
|
757
788
|
emitUpdateJson({ ok: false, action: 'usage-error',
|
|
758
|
-
reason: `Invalid
|
|
789
|
+
reason: `Invalid ${where} "${divStrategy}". Use one of: ${DIVERGENCE_STRATEGIES.join(', ')}.` }, 1);
|
|
759
790
|
}
|
|
760
|
-
UI.error(`Invalid
|
|
791
|
+
UI.error(`Invalid ${where} "${divStrategy}". Use one of: ${DIVERGENCE_STRATEGIES.join(', ')}.`);
|
|
761
792
|
process.exit(1);
|
|
762
793
|
}
|
|
763
794
|
|
|
@@ -875,6 +906,11 @@ async function update(options = {}, unknownArgs = []) {
|
|
|
875
906
|
// a framework file → suggest /overlay. real-custom = prompt 3-way.
|
|
876
907
|
if (status.hasDivergence) {
|
|
877
908
|
UI.newline();
|
|
909
|
+
// Surface the persisted policy so an interactive user isn't surprised that
|
|
910
|
+
// `update` auto-resolved (e.g. auto-merged) instead of stopping to ask.
|
|
911
|
+
if (divStrategySource === 'config') {
|
|
912
|
+
UI.info(`Applying persisted divergence policy git.on_divergence: "${divStrategy}" (from baldart.config.yml). Pass --on-divergence to override for one run.`);
|
|
913
|
+
}
|
|
878
914
|
if (status.divergenceClass === 'all-noise') {
|
|
879
915
|
UI.info(`Detected ${status.divergenceCommits.length} subtree-merge / chore commit(s) (git plumbing noise — no real content drift). Auto-resolving…`);
|
|
880
916
|
} else if (status.divergenceClass === 'overlay-able') {
|