baldart 5.0.1 → 5.2.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 +157 -0
- package/README.md +6 -6
- package/VERSION +1 -1
- package/framework/.claude/agents/CHANGELOG.md +90 -0
- package/framework/.claude/agents/REGISTRY.md +5 -5
- package/framework/.claude/agents/coder.md +3 -3
- package/framework/.claude/agents/remotion-animator-orchestrator.md +4 -3
- package/framework/.claude/agents/senior-researcher.md +166 -151
- package/framework/.claude/agents/ui-expert.md +4 -10
- package/framework/.claude/agents/ui-quality-critic.md +27 -11
- package/framework/.claude/skills/design-system-init/CHANGELOG.md +5 -0
- package/framework/.claude/skills/design-system-init/SKILL.md +2 -2
- package/framework/.claude/skills/e2e-review/CHANGELOG.md +5 -0
- package/framework/.claude/skills/e2e-review/SKILL.md +2 -2
- package/framework/.claude/skills/frontend-design/CHANGELOG.md +20 -0
- package/framework/.claude/skills/frontend-design/SKILL.md +39 -216
- package/framework/.claude/skills/gamification-design/CHANGELOG.md +5 -0
- package/framework/.claude/skills/gamification-design/SKILL.md +2 -2
- package/framework/.claude/skills/prd/CHANGELOG.md +16 -0
- package/framework/.claude/skills/prd/SKILL.md +1 -1
- package/framework/.claude/skills/prd/references/discovery-phase.md +5 -1
- package/framework/.claude/skills/prd/references/research-phase.md +32 -12
- package/framework/.claude/skills/research/CHANGELOG.md +9 -0
- package/framework/.claude/skills/research/SKILL.md +94 -0
- package/framework/.claude/skills/research/assets/report-compare.template.md +50 -0
- package/framework/.claude/skills/research/assets/report-decision.template.md +42 -0
- package/framework/.claude/skills/research/assets/report-deep.template.md +61 -0
- package/framework/.claude/skills/research/assets/report-regulatory.template.md +44 -0
- package/framework/.claude/skills/research/references/playbook.md +112 -0
- package/framework/.claude/skills/research/scripts/rebuild-research-index.mjs +77 -0
- package/framework/.claude/skills/ui-design/CHANGELOG.md +60 -1
- package/framework/.claude/skills/ui-design/SKILL.md +127 -75
- package/framework/.claude/skills/ui-design/references/anti-slop.md +106 -0
- package/framework/.claude/skills/ui-design/references/craft-standards.md +259 -0
- package/framework/.claude/skills/ui-design/references/design-brief.md +92 -0
- package/framework/.claude/skills/ui-design/references/design-directions.md +188 -0
- package/framework/.claude/skills/ui-design/references/evaluation.md +125 -165
- package/framework/.claude/skills/ui-design/references/generation.md +125 -92
- package/framework/.claude/skills/ui-design/references/inventory.md +9 -2
- package/framework/.claude/skills/ui-design/scripts/craft-check.mjs +248 -0
- package/framework/agents/component-manifest-schema.md +1 -1
- package/framework/agents/design-system-protocol.md +6 -6
- package/framework/agents/index.md +3 -0
- package/framework/agents/research-protocol.md +228 -0
- package/framework/agents/skills-mapping.md +42 -23
- package/framework/docs/PROJECT-CONFIGURATION.md +28 -5
- package/framework/templates/baldart.config.template.yml +6 -0
- package/framework/templates/research-index.template.md +13 -0
- package/framework/templates/research-sources.CHANGELOG.md +14 -0
- package/framework/templates/research-sources.template.md +31 -0
- package/package.json +1 -1
- package/src/commands/configure.js +27 -1
- package/src/commands/doctor.js +82 -0
- package/src/commands/update.js +48 -1
- package/src/utils/__tests__/classify-divergence.test.js +42 -0
- package/src/utils/git.js +45 -9
- package/src/utils/research-library.js +92 -0
- package/src/utils/symlinks.js +3 -0
|
@@ -191,38 +191,38 @@ competing methodology — it detects the task type and passes the matching
|
|
|
191
191
|
|
|
192
192
|
## UI/UX Skills
|
|
193
193
|
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
-
|
|
199
|
-
-
|
|
200
|
-
-
|
|
201
|
-
|
|
202
|
-
**Triggers**:
|
|
203
|
-
|
|
204
|
-
- "Create [page]"
|
|
205
|
-
- "Build [component]"
|
|
206
|
-
- "Design [interface]"
|
|
194
|
+
> **UI routing rule (since v5.2.0).** ALL design-decision work routes to
|
|
195
|
+
> `ui-design` (the local design studio). `frontend-design` is a RETIRED
|
|
196
|
+
> generator kept as a router: its broad triggers still land there, but its
|
|
197
|
+
> body immediately reroutes (design → `/ui-design`, approved mockup → code →
|
|
198
|
+
> `/ui-implement`, direct in-code UI → `ui-expert` agent per `AGENTS.md`
|
|
199
|
+
> § delegation). Never follow pre-v5.2.0 `frontend-design` design guidance.
|
|
200
|
+
> `ui-ux-pro-max` is superseded by `ui-design/references/`
|
|
201
|
+
> (design-directions / craft-standards / anti-slop) — never route to it.
|
|
207
202
|
|
|
208
203
|
### ui-design
|
|
209
204
|
|
|
210
205
|
**When to use**:
|
|
211
206
|
|
|
212
|
-
- Designing
|
|
213
|
-
|
|
214
|
-
-
|
|
207
|
+
- Designing, prototyping or restyling ANY page/screen/component locally —
|
|
208
|
+
the internal twin of the Claude Design handoff
|
|
209
|
+
- The `/prd` skill reaches its UI design phase (Step 3)
|
|
210
|
+
- Redesigning existing pages / exploring design options or aesthetic
|
|
211
|
+
directions for review
|
|
215
212
|
|
|
216
213
|
**Triggers**:
|
|
217
214
|
|
|
218
215
|
- "/ui-design"
|
|
219
|
-
- "Design UI" / "mockup" / "opzioni di design"
|
|
220
|
-
|
|
216
|
+
- "Design UI" / "mockup" / "prototipo" / "opzioni di design" / "restyle" /
|
|
217
|
+
"rifai la UI" / "migliora il design" / "progetta interfaccia"
|
|
218
|
+
- Design-heavy features, visual refinement, greenfield landing/dashboard work
|
|
219
|
+
|
|
220
|
+
### frontend-design (router — retired generator)
|
|
221
221
|
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
222
|
+
**When to use**: never follow it as a workflow. If a trigger lands here
|
|
223
|
+
("create/build/style [page/component/UI]"), apply its routing table:
|
|
224
|
+
design work → `ui-design` · approved mockup → `ui-implement` · in-code UI
|
|
225
|
+
change → `ui-expert` agent · motion → `motion-design`.
|
|
226
226
|
|
|
227
227
|
### ui-implement
|
|
228
228
|
|
|
@@ -422,6 +422,23 @@ competing methodology — it detects the task type and passes the matching
|
|
|
422
422
|
- "/capture"
|
|
423
423
|
- "Salva sintesi" / "distilla conversazione" / "questa conversazione in wiki"
|
|
424
424
|
|
|
425
|
+
### research
|
|
426
|
+
|
|
427
|
+
**When to use**:
|
|
428
|
+
|
|
429
|
+
- Interactive, routed research over the research library (`paths.research_dir`)
|
|
430
|
+
- Scopes the question, routes profile (`decision`/`deep`/`compare`/`regulatory`) +
|
|
431
|
+
source policy by nature, checks the library for reusable prior research
|
|
432
|
+
BEFORE searching, launches `senior-researcher`, files + indexes the report
|
|
433
|
+
- Closes the source-matrix growth loop (`SOURCE_MATRIX_CANDIDATE`)
|
|
434
|
+
- NOT for `/prd`'s embedded research step (prd spawns the agent directly)
|
|
435
|
+
|
|
436
|
+
**Triggers**:
|
|
437
|
+
|
|
438
|
+
- "/research"
|
|
439
|
+
- "Fai una ricerca" / "ricerca best practice" / "confronta X e Y"
|
|
440
|
+
- "È già stato ricercato?" / "cosa dice la letteratura su"
|
|
441
|
+
|
|
425
442
|
### graph-align
|
|
426
443
|
|
|
427
444
|
**When to use**:
|
|
@@ -657,7 +674,9 @@ Is it a bug fix?
|
|
|
657
674
|
|
|
|
658
675
|
v
|
|
659
676
|
Is it UI/visual work?
|
|
660
|
-
|-- YES -->
|
|
677
|
+
|-- YES --> design decision (new/redesign/restyle/options) → /ui-design;
|
|
678
|
+
| approved mockup → code → /ui-implement;
|
|
679
|
+
| direct in-code UI change → ui-expert agent (AGENTS.md § delegation);
|
|
661
680
|
| motion → /motion-design
|
|
662
681
|
|
|
|
663
682
|
v
|
|
@@ -94,8 +94,8 @@ Empty string `""` means the concept doesn't exist in your project. Skills gated
|
|
|
94
94
|
|
|
95
95
|
| Key | Typical value | Used by |
|
|
96
96
|
|---|---|---|
|
|
97
|
-
| `design_system` | `docs/design-system` | ui-design,
|
|
98
|
-
| `ui_guidelines` | `docs/references/ui-guidelines.md` | ui-design,
|
|
97
|
+
| `design_system` | `docs/design-system` | ui-design, ui-implement, prd, bug, kie-ai, simplify, playwright-skill, webapp-testing, gamification-design |
|
|
98
|
+
| `ui_guidelines` | `docs/references/ui-guidelines.md` | ui-design, copywriting, gamification-design |
|
|
99
99
|
| `api_index` | `docs/references/api/index.md` | doc-writing-for-rag |
|
|
100
100
|
| `api_schemas` | `docs/references/api/schemas.md` | doc-writing-for-rag |
|
|
101
101
|
| `api_errors` | `docs/references/errors.md` | doc-writing-for-rag |
|
|
@@ -105,6 +105,7 @@ Empty string `""` means the concept doesn't exist in your project. Skills gated
|
|
|
105
105
|
| `backlog_dir` | `backlog` | new, prd, context-primer |
|
|
106
106
|
| `adrs_dir` | `docs/decisions` | prd, prd-add |
|
|
107
107
|
| `prd_dir` | `docs/prd` | prd, prd-add, ui-design |
|
|
108
|
+
| `research_dir` | `docs/research` | senior-researcher, research, prd (Step 2.5) — the research library (v5.1.0) |
|
|
108
109
|
| `docs_dir` | `docs` | code-reviewer, senior-researcher, worktree-manager (rg-search fallback root) |
|
|
109
110
|
| `references_dir` | `docs/references` | new, prd, context-primer, doc-writing-for-rag, simplify |
|
|
110
111
|
| `wiki_dir` | `docs/wiki` | capture, context-primer |
|
|
@@ -129,12 +130,34 @@ e2e_review:
|
|
|
129
130
|
| `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. |
|
|
130
131
|
| `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). |
|
|
131
132
|
|
|
133
|
+
### 4.2.2 `research_dir` — the research library (v5.1.0+)
|
|
134
|
+
|
|
135
|
+
Consumer-owned home of every research report produced by `senior-researcher`
|
|
136
|
+
(directly, via the `/research` skill, or by `/prd` Step 2.5). Layout, report
|
|
137
|
+
frontmatter, reuse pre-flight and the source matrix are defined in
|
|
138
|
+
`framework/agents/research-protocol.md`.
|
|
139
|
+
|
|
140
|
+
Lifecycle:
|
|
141
|
+
|
|
142
|
+
- **Creation** — `baldart configure` (right after writing the key; default
|
|
143
|
+
`docs/research` is proposed on FIRST encounter only) and `baldart doctor`
|
|
144
|
+
(`create-research-dir`, confirmable). Seeds `INDEX.md` (the library map) and
|
|
145
|
+
`SOURCES.md` (the live source matrix) from framework templates.
|
|
146
|
+
- **Update** — `baldart update` only backfills missing seeds into an EXISTING
|
|
147
|
+
directory; it never recreates a deleted library.
|
|
148
|
+
- **Opt-out** — set the key to `""`: respected everywhere, never re-proposed,
|
|
149
|
+
never resurrected.
|
|
150
|
+
- **Source matrix versioning** — `SOURCES.md` carries `matrix_version`
|
|
151
|
+
frontmatter; when the framework template ships a newer matrix, `doctor`
|
|
152
|
+
raises an ADVISORY (never overwrites — your `## Local additions` and manual
|
|
153
|
+
merges are yours).
|
|
154
|
+
|
|
132
155
|
### 4.3 `identity` — brand and audience facts
|
|
133
156
|
|
|
134
157
|
| Key | Type | Notes |
|
|
135
158
|
|---|---|---|
|
|
136
159
|
| `brand_name` | string | Public product name |
|
|
137
|
-
| `design_philosophy` | string | Free-form, one line. Examples: `"Neo-Brutalism"`, `"Glassmorphism"`, `"Minimalist"`, `"Material 3"`, `""` (no opinion). Drives aesthetic decisions in `ui-design`, `
|
|
160
|
+
| `design_philosophy` | string | Free-form, one line. Examples: `"Neo-Brutalism"`, `"Glassmorphism"`, `"Minimalist"`, `"Material 3"`, `""` (no opinion). Drives aesthetic decisions in `ui-design`, `kie-ai`, `motion-design`. |
|
|
138
161
|
| `language` | BCP-47 tag | `"en"`, `"it"`, etc. Drives copy register in `copywriting`, error-message language in `ui-design`. |
|
|
139
162
|
| `audience_segments` | array of strings | Domain split, e.g. `["merchant","customer"]` or `["b2b","b2c"]`. Empty `[]` = no segmentation. Drives prompts in `copywriting`, evaluation in `ui-design`. |
|
|
140
163
|
|
|
@@ -234,7 +257,7 @@ Every flag MUST be present as `true` or `false` once `baldart configure` has run
|
|
|
234
257
|
|
|
235
258
|
| Flag | Meaning |
|
|
236
259
|
|---|---|
|
|
237
|
-
| `has_design_system` | The project has docs rooted at `paths.design_system`. When `true`, `ui-design`, `
|
|
260
|
+
| `has_design_system` | The project has docs rooted at `paths.design_system`. When `true`, `ui-design`, `ui-implement`, `bug`, `simplify`, `playwright-skill` etc. treat the INDEX as BLOCKING. |
|
|
238
261
|
| `multi_tenant_theming` | The project supports per-tenant theme overrides. Activates themed-surface text/bg pairing rules in `ui-design`, `playwright-skill`, `webapp-testing`. |
|
|
239
262
|
| `has_api_docs` | Canonical API docs at `paths.api_index`. Gates `doc-writing-for-rag` schemas/errors workflow. |
|
|
240
263
|
| `has_backlog` | Card-driven workflow (`paths.backlog_dir` + AGENTS.md card protocol). `new` skill refuses to run without it. |
|
|
@@ -314,7 +337,7 @@ These skills had heavy hard-coding before v3.0.0; if your project has opinions i
|
|
|
314
337
|
|---|---|
|
|
315
338
|
| `ui-design` | Design philosophy mandates, theming pairing rules, charting wrappers, browser open command, evaluation thresholds |
|
|
316
339
|
| `copywriting` | Brand voice pillars, audience register matrix, forbidden vocabulary, language-only rules |
|
|
317
|
-
| `frontend-design` |
|
|
340
|
+
| `frontend-design` | (retired to a router since v5.2.0 — port aesthetic mandates / illustration-motion contract to the `ui-design` overlay) |
|
|
318
341
|
| `prd` / `prd-add` | Project terminology, mandatory review gates, design-system reads for UI PRDs |
|
|
319
342
|
| `kie-ai` | Illustration character library, video-prompt motion vocabulary, forbidden aesthetics |
|
|
320
343
|
| `bug` | Project debug entry points (env summary helper, cache debug switch, error-code module) |
|
|
@@ -46,6 +46,12 @@ paths:
|
|
|
46
46
|
backlog_dir: "" # e.g. backlog
|
|
47
47
|
adrs_dir: "" # e.g. docs/decisions
|
|
48
48
|
prd_dir: "" # e.g. docs/prd
|
|
49
|
+
# Research library (since v5.1.0). Consumer-owned home of every research
|
|
50
|
+
# report (senior-researcher / /research skill / /prd Step 2.5), indexed for
|
|
51
|
+
# reuse. Seeded with INDEX.md + SOURCES.md by `configure`/`doctor`; `update`
|
|
52
|
+
# only backfills seeds into an existing dir. Empty = deliberate opt-out
|
|
53
|
+
# (never re-proposed, never resurrected). Protocol: agents/research-protocol.md.
|
|
54
|
+
research_dir: "" # e.g. docs/research
|
|
49
55
|
docs_dir: "" # e.g. docs (umbrella docs root; rg-search fallback used by code-reviewer / senior-researcher / worktree-manager)
|
|
50
56
|
references_dir: "" # e.g. docs/references
|
|
51
57
|
wiki_dir: "" # e.g. docs/wiki (LLM-wiki overlay)
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
# Research Library — INDEX
|
|
2
|
+
|
|
3
|
+
> **This file is THE map of the research library.** Agents resolve reuse
|
|
4
|
+
> lookups against this index only — a report without a row here is invisible.
|
|
5
|
+
> Rows are **append-only**; superseded reports move to `_archive/` and their
|
|
6
|
+
> row is REMOVED (archive, never delete). The frontmatter of each report is
|
|
7
|
+
> the source of truth; this index is a regenerable view
|
|
8
|
+
> (`rebuild-research-index.mjs` in the `/research` skill reconciles it).
|
|
9
|
+
>
|
|
10
|
+
> Protocol: `agents/research-protocol.md SECTION=library`.
|
|
11
|
+
|
|
12
|
+
| id | title | category | profile | tags | date | valid_until | path |
|
|
13
|
+
|----|-------|----------|---------|------|------|-------------|------|
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
# Changelog — research source matrix
|
|
2
|
+
|
|
3
|
+
History of `research-sources.template.md` (`matrix_version` frontmatter).
|
|
4
|
+
Framework-internal (never installed into consumers — consumers hold their own
|
|
5
|
+
seeded `SOURCES.md` copy and merge updates via the doctor advisory). Bump
|
|
6
|
+
`matrix_version` in the same edit: MINOR for added sources/natures, PATCH for
|
|
7
|
+
demotions and note refinements.
|
|
8
|
+
|
|
9
|
+
## 1.0.0 — 2026-07-03 (framework v5.1.0)
|
|
10
|
+
|
|
11
|
+
- Initial default matrix: 5 natures (scientific, dev, regulatory, ux, market)
|
|
12
|
+
× primary/secondary/avoid/notes, distilled from the v5.1.0 research-layer
|
|
13
|
+
design pass (OSS recon: GPT Researcher, dzhng/deep-research, STORM,
|
|
14
|
+
open_deep_research; house evidence-strength labels).
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
---
|
|
2
|
+
matrix_version: 1.0.0
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
# Research Source Matrix
|
|
6
|
+
|
|
7
|
+
> Routes each research **nature** to the right sources. Read by
|
|
8
|
+
> `senior-researcher` on every run (resolution order and growth loop:
|
|
9
|
+
> `agents/research-protocol.md SECTION=sources`). This copy is
|
|
10
|
+
> **consumer-owned**: BALDART seeds it once and never overwrites it. Framework
|
|
11
|
+
> updates to the default matrix arrive as a doctor ADVISORY (compare
|
|
12
|
+
> `matrix_version` against the framework template) — merging is a human
|
|
13
|
+
> decision. Project-specific sources go under `## Local additions`.
|
|
14
|
+
|
|
15
|
+
## Default matrix (framework, v1.0.0)
|
|
16
|
+
|
|
17
|
+
| nature | primary | secondary | avoid | notes |
|
|
18
|
+
|---|---|---|---|---|
|
|
19
|
+
| **scientific** (algorithms, protocols, ML, formal methods) | Peer-reviewed venues (ACM DL, IEEE Xplore, USENIX, NeurIPS/ICML/ICLR), arXiv (flag as preprint), standards bodies (W3C, IETF, NIST) | Survey papers as entry points; Google Scholar citation chains; reputable lab/engineering blogs (paper-backed) | Popular-science rewrites; Medium summaries of papers; vendor "research" without artifacts | Prefer replicated results; extract method + dataset + metrics, not just conclusions |
|
|
20
|
+
| **dev** (frameworks, libraries, integration patterns, tooling) | Official documentation + release notes; the project's own GitHub repo (issues, discussions, RFCs); official engineering blogs | High-signal engineering blogs; conference talks; Stack Overflow accepted answers (check dates); HN threads for failure modes | SEO content farms ("Top 10 X in 2026"); AI-generated tutorial mills; outdated tutorials (> 18 months for fast-moving stacks) | Version-pin every claim (behavior changes across majors); GitHub issues reveal real gotchas docs hide |
|
|
21
|
+
| **regulatory** (compliance, privacy, accessibility, sector rules) | Primary legal texts (EUR-Lex, official gazettes); regulator guidance (EDPB, garanti, agencies); official standards (WCAG/W3C, PCI SSC, ISO abstracts) | Big-firm legal analyses (dated + jurisdiction-tagged); IAPP; specialized compliance blogs | Vendor compliance-marketing pages; forum legal opinions; anything without a jurisdiction + date | ALWAYS name the jurisdiction; obligations cite the primary text, practices may cite secondary |
|
|
22
|
+
| **ux** (interaction patterns, accessibility-in-practice, design systems) | Nielsen Norman Group; established design systems (Material, HIG, Carbon, Polaris) as pattern evidence; WCAG techniques; peer-reviewed HCI (CHI) | Baymard (e-commerce); reputable case studies with data; a11y practitioner blogs (Deque, TPGi) | Dribbble/Behance as evidence of usability (aesthetics ≠ usability); engagement-bait "UX laws" listicles | Pattern adoption across 2+ major design systems = [MODERATE] evidence of validity |
|
|
23
|
+
| **market** (vendor evaluation, pricing, adoption, ecosystem health) | Official pricing pages + changelogs + SLAs + status pages (dated snapshots); public financial/usage disclosures | Independent benchmarks (methodology visible); StackShare-class adoption signals; comparative engineering posts by USERS of the tool | Vendor-vs-competitor comparison pages; sponsored reviews; analyst quadrants without methodology access | Pricing claims MUST carry the retrieval date; ecosystem health = commit cadence + issue response, not stars |
|
|
24
|
+
|
|
25
|
+
## Local additions
|
|
26
|
+
|
|
27
|
+
<!-- Project-specific sources grow here (via the /research skill's matrix loop
|
|
28
|
+
or by hand). Same columns. BALDART never touches this section. -->
|
|
29
|
+
|
|
30
|
+
| nature | primary | secondary | avoid | notes |
|
|
31
|
+
|---|---|---|---|---|
|
package/package.json
CHANGED
|
@@ -479,6 +479,7 @@ function detect(cwd = process.cwd()) {
|
|
|
479
479
|
backlog_dir: exists('backlog') ? 'backlog' : '',
|
|
480
480
|
adrs_dir: countMatches('docs/decisions', /^ADR-.*\.md$/) > 0 ? 'docs/decisions' : '',
|
|
481
481
|
prd_dir: exists('docs/prd') ? 'docs/prd' : '',
|
|
482
|
+
research_dir: exists('docs/research') ? 'docs/research' : '',
|
|
482
483
|
docs_dir: exists('docs') ? 'docs' : '',
|
|
483
484
|
references_dir: exists('docs/references') ? 'docs/references' : '',
|
|
484
485
|
wiki_dir: exists('docs/wiki') ? 'docs/wiki' : '',
|
|
@@ -789,7 +790,7 @@ async function interactivePrompts(merged, detected) {
|
|
|
789
790
|
' /design-system-init (or invoke the skill)\n' +
|
|
790
791
|
'to scaffold INDEX.md + tokens-reference.md + per-component specs at\n' +
|
|
791
792
|
'docs/design-system/. The registry-first protocol (ui-expert, ui-design,\n' +
|
|
792
|
-
'
|
|
793
|
+
'ui-implement, /design-review) needs these files to enforce coherence.'
|
|
793
794
|
);
|
|
794
795
|
} else if (merged.features.has_design_system === false) {
|
|
795
796
|
UI.info(
|
|
@@ -814,6 +815,7 @@ async function interactivePrompts(merged, detected) {
|
|
|
814
815
|
['backlog_dir', 'Backlog directory', () => merged.features.has_backlog],
|
|
815
816
|
['adrs_dir', 'ADR directory', () => merged.features.has_adrs],
|
|
816
817
|
['prd_dir', 'PRD directory', () => merged.features.has_prd_workflow],
|
|
818
|
+
['research_dir', 'Research library directory (empty = disable)', () => true],
|
|
817
819
|
['docs_dir', 'Docs root directory (rg-search umbrella)', () => true],
|
|
818
820
|
['references_dir', 'References docs root', () => true],
|
|
819
821
|
['wiki_dir', 'Wiki overlay directory', () => merged.features.has_wiki_overlay],
|
|
@@ -1425,6 +1427,16 @@ async function configure(opts = {}) {
|
|
|
1425
1427
|
// mergePreserving(target, source) keeps target where set, fills from source.
|
|
1426
1428
|
let merged = mergePreserving(existing || {}, detected);
|
|
1427
1429
|
merged = mergePreserving(merged, template);
|
|
1430
|
+
|
|
1431
|
+
// paths.research_dir (v5.1.0): default `docs/research` on FIRST encounter only
|
|
1432
|
+
// (key never declared in the consumer's config). An explicitly emptied key is
|
|
1433
|
+
// a deliberate opt-out and is respected everywhere — never re-proposed, never
|
|
1434
|
+
// resurrected (research-protocol.md § degrade-safe).
|
|
1435
|
+
const researchKeyDeclared = !!(existing && existing.paths && 'research_dir' in existing.paths);
|
|
1436
|
+
if (!researchKeyDeclared && merged.paths && !merged.paths.research_dir) {
|
|
1437
|
+
merged.paths.research_dir = 'docs/research';
|
|
1438
|
+
if (detected.paths) detected.paths.research_dir = 'docs/research';
|
|
1439
|
+
}
|
|
1428
1440
|
merged.version = SCHEMA_VERSION;
|
|
1429
1441
|
|
|
1430
1442
|
const dsSignals = detected.stack.design_system_signals || {};
|
|
@@ -1502,6 +1514,20 @@ async function configure(opts = {}) {
|
|
|
1502
1514
|
UI.warning(`Agent re-merge after configure failed (${err.message}) — run \`npx baldart doctor\` to heal.`);
|
|
1503
1515
|
}
|
|
1504
1516
|
|
|
1517
|
+
// v5.1.0 — research library: create + seed ${paths.research_dir} right after
|
|
1518
|
+
// the key is written. Creation ownership lives HERE (and in doctor repairs);
|
|
1519
|
+
// `update` only backfills seeds into an existing dir. Fail-safe: configure
|
|
1520
|
+
// must never die on a scaffolding hiccup — doctor heals later.
|
|
1521
|
+
try {
|
|
1522
|
+
const { ensureResearchLibrary } = require('../utils/research-library');
|
|
1523
|
+
const createdResearch = ensureResearchLibrary(cwd, merged, { createDir: true });
|
|
1524
|
+
if (createdResearch.length) {
|
|
1525
|
+
UI.success(`Research library initialized at ${merged.paths.research_dir}/`);
|
|
1526
|
+
}
|
|
1527
|
+
} catch (err) {
|
|
1528
|
+
UI.warning(`Research library setup failed (${err.message}) — run \`npx baldart doctor\` to heal.`);
|
|
1529
|
+
}
|
|
1530
|
+
|
|
1505
1531
|
// Ensure overlays dir exists (user-owned)
|
|
1506
1532
|
const overlaysAbs = path.join(cwd, OVERLAYS_DIR);
|
|
1507
1533
|
if (!fs.existsSync(overlaysAbs)) {
|
package/src/commands/doctor.js
CHANGED
|
@@ -656,6 +656,39 @@ async function detectState(cwd, opts = {}) {
|
|
|
656
656
|
}
|
|
657
657
|
} catch (_) { /* never block doctor on design-system probe */ }
|
|
658
658
|
|
|
659
|
+
// ---- Research library (since v5.1.0) --------------------------------
|
|
660
|
+
// Gated on paths.research_dir being SET (empty = deliberate opt-out —
|
|
661
|
+
// never re-proposed, per research-protocol.md § degrade-safe). Three
|
|
662
|
+
// probes: dir missing (repair), seeds missing (backfill), source-matrix
|
|
663
|
+
// version behind the framework template (ADVISORY only — merging the
|
|
664
|
+
// matrix is a human decision, doctor never overwrites SOURCES.md).
|
|
665
|
+
state.researchDirMissing = false;
|
|
666
|
+
state.researchSeedMissing = [];
|
|
667
|
+
state.researchSourcesStale = null;
|
|
668
|
+
try {
|
|
669
|
+
const researchDir = (config && !config.__malformed && config.paths && config.paths.research_dir) || '';
|
|
670
|
+
if (typeof researchDir === 'string' && researchDir.trim()) {
|
|
671
|
+
const dirAbs = path.join(cwd, researchDir.trim());
|
|
672
|
+
if (!fs.existsSync(dirAbs)) {
|
|
673
|
+
state.researchDirMissing = true;
|
|
674
|
+
} else {
|
|
675
|
+
for (const seed of ['INDEX.md', 'SOURCES.md']) {
|
|
676
|
+
if (!fs.existsSync(path.join(dirAbs, seed))) state.researchSeedMissing.push(seed);
|
|
677
|
+
}
|
|
678
|
+
const { readMatrixVersion } = require('../utils/research-library');
|
|
679
|
+
const live = readMatrixVersion(path.join(dirAbs, 'SOURCES.md'));
|
|
680
|
+
const tpl = readMatrixVersion(path.join(cwd, '.framework', 'framework', 'templates', 'research-sources.template.md'));
|
|
681
|
+
if (live && tpl && live !== tpl) {
|
|
682
|
+
const toNum = (v) => v.split('.').map(Number);
|
|
683
|
+
const [lM, lm, lp] = toNum(live); const [tM, tm, tp] = toNum(tpl);
|
|
684
|
+
if (tM > lM || (tM === lM && (tm > lm || (tm === lm && tp > lp)))) {
|
|
685
|
+
state.researchSourcesStale = { live, template: tpl };
|
|
686
|
+
}
|
|
687
|
+
}
|
|
688
|
+
}
|
|
689
|
+
}
|
|
690
|
+
} catch (_) { /* never block doctor on research probe */ }
|
|
691
|
+
|
|
659
692
|
// ---- Auto-deploy allowlist presence (since v4.59.0) ----------------
|
|
660
693
|
// Purely informational: git.auto_deploy bounds what `/new -auto-ship`
|
|
661
694
|
// may execute as an OUTWARD action. An empty allowlist is the SAFE
|
|
@@ -1343,6 +1376,55 @@ function planActions(state) {
|
|
|
1343
1376
|
});
|
|
1344
1377
|
}
|
|
1345
1378
|
|
|
1379
|
+
// Research library (since v5.1.0). Creation/repair lives HERE (and in
|
|
1380
|
+
// configure) — update only backfills seeds into an existing dir. autoOk:
|
|
1381
|
+
// unlike the i18n registry ("no safe auto-create without project context"),
|
|
1382
|
+
// the research seeds are fully GENERIC (an empty index + the framework
|
|
1383
|
+
// default source matrix) — zero project context needed, so unattended
|
|
1384
|
+
// creation is safe. Precedent for consumer-space writes: tokens-build.
|
|
1385
|
+
if (state.researchDirMissing) {
|
|
1386
|
+
actions.push({
|
|
1387
|
+
key: 'create-research-dir',
|
|
1388
|
+
label: 'Create the research library (paths.research_dir is set but the directory is missing)',
|
|
1389
|
+
why: 'baldart.config.yml declares a research library but the directory does not exist on disk. Creating it (with the INDEX.md + SOURCES.md seeds and the category subdirs) restores the reuse pre-flight for senior-researcher / /research / /prd Step 2.5. If you deleted it on purpose, empty paths.research_dir instead — an empty key is a respected opt-out.',
|
|
1390
|
+
autoOk: true, // generic seeds, idempotent, no project context needed
|
|
1391
|
+
run: async () => {
|
|
1392
|
+
const cfg = loadConfig(state.cwd);
|
|
1393
|
+
const { ensureResearchLibrary } = require('../utils/research-library');
|
|
1394
|
+
const created = ensureResearchLibrary(state.cwd, cfg, { createDir: true });
|
|
1395
|
+
if (created.length) UI.success(`Research library initialized (${created.join(', ')}).`);
|
|
1396
|
+
else UI.info('Nothing to create — library already present.');
|
|
1397
|
+
},
|
|
1398
|
+
});
|
|
1399
|
+
}
|
|
1400
|
+
if (state.researchSeedMissing && state.researchSeedMissing.length > 0) {
|
|
1401
|
+
actions.push({
|
|
1402
|
+
key: 'backfill-research-seeds',
|
|
1403
|
+
label: `Backfill research library seed file(s): ${state.researchSeedMissing.join(', ')}`,
|
|
1404
|
+
why: `The research library directory exists but ${state.researchSeedMissing.join(' and ')} are missing (pre-v5.1 install or manual cleanup). The seeds are generic framework templates (empty index / default source matrix); existing files are never overwritten.`,
|
|
1405
|
+
autoOk: true, // copy-if-absent only
|
|
1406
|
+
run: async () => {
|
|
1407
|
+
const cfg = loadConfig(state.cwd);
|
|
1408
|
+
const { ensureResearchLibrary } = require('../utils/research-library');
|
|
1409
|
+
const created = ensureResearchLibrary(state.cwd, cfg, { createDir: false });
|
|
1410
|
+
if (created.length) UI.success(`Seeded: ${created.join(', ')}.`);
|
|
1411
|
+
else UI.info('Nothing to backfill.');
|
|
1412
|
+
},
|
|
1413
|
+
});
|
|
1414
|
+
}
|
|
1415
|
+
if (state.researchSourcesStale) {
|
|
1416
|
+
actions.push({
|
|
1417
|
+
key: 'research-sources-advisory',
|
|
1418
|
+
label: `Research source matrix behind framework default (${state.researchSourcesStale.live} < ${state.researchSourcesStale.template})`,
|
|
1419
|
+
why: `Your SOURCES.md carries matrix_version ${state.researchSourcesStale.live} while the framework template ships ${state.researchSourcesStale.template}. ADVISORY only — SOURCES.md is consumer-owned and doctor never overwrites it. Compare with .framework/framework/templates/research-sources.template.md (its CHANGELOG sibling lists what changed) and merge what you want, keeping your "## Local additions".`,
|
|
1420
|
+
autoOk: true, // advisory print-only; run() does NOT mutate anything
|
|
1421
|
+
run: () => {
|
|
1422
|
+
UI.info(`Source matrix: local ${state.researchSourcesStale.live} vs framework ${state.researchSourcesStale.template}.`);
|
|
1423
|
+
UI.info('Diff against .framework/framework/templates/research-sources.template.md and merge manually (your `## Local additions` section is yours).');
|
|
1424
|
+
},
|
|
1425
|
+
});
|
|
1426
|
+
}
|
|
1427
|
+
|
|
1346
1428
|
// Auto-deploy allowlist (since v4.59.0). Advisory print-only — surfaced ONLY
|
|
1347
1429
|
// when the allowlist is non-empty (the safe default of empty/unset opens
|
|
1348
1430
|
// nothing, so it warrants no nag). Reminds the user that `/new -auto-ship`
|
package/src/commands/update.js
CHANGED
|
@@ -183,6 +183,28 @@ function isBaldartManagedPath(p, extra = []) {
|
|
|
183
183
|
return BALDART_MANAGED_PATTERNS.some((rx) => rx.test(p)) || extra.some((rx) => rx.test(p));
|
|
184
184
|
}
|
|
185
185
|
|
|
186
|
+
// Research-library seed paths (v5.1.0). The library CONTENT is consumer-owned
|
|
187
|
+
// (like docs/prd/) and never enters the managed patterns; only the two seed
|
|
188
|
+
// files BALDART itself creates are auto-commit candidates — and only while
|
|
189
|
+
// UNTRACKED (see postUpdateAutoCommit), so a consumer-edited tracked
|
|
190
|
+
// SOURCES.md/INDEX.md is never swept into a reconcile commit.
|
|
191
|
+
function researchSeedPaths(cwd = process.cwd()) {
|
|
192
|
+
try {
|
|
193
|
+
const fs = require('fs');
|
|
194
|
+
const path = require('path');
|
|
195
|
+
const yaml = require('js-yaml');
|
|
196
|
+
const cfgPath = path.join(cwd, 'baldart.config.yml');
|
|
197
|
+
if (!fs.existsSync(cfgPath)) return [];
|
|
198
|
+
const cfg = yaml.load(fs.readFileSync(cfgPath, 'utf8'));
|
|
199
|
+
const dir = cfg && cfg.paths && cfg.paths.research_dir;
|
|
200
|
+
if (typeof dir !== 'string' || !dir.trim()) return [];
|
|
201
|
+
const norm = dir.trim().replace(/\\/g, '/').replace(/\/+$/, '');
|
|
202
|
+
return [`${norm}/INDEX.md`, `${norm}/SOURCES.md`];
|
|
203
|
+
} catch (_) {
|
|
204
|
+
return [];
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
|
|
186
208
|
// Re-generate the root primitives (AGENTS.md + CLAUDE.md) from the versioned
|
|
187
209
|
// skeletons + baldart.config.yml + overlays. Idempotent + byte-stable, so it is
|
|
188
210
|
// safe to call on both the aligned-install and post-pull paths. Never throws —
|
|
@@ -252,10 +274,14 @@ async function postUpdateAutoCommit(git, newVersion, options) {
|
|
|
252
274
|
const managed = [];
|
|
253
275
|
const userOwned = [];
|
|
254
276
|
const extraPatterns = i18nRegistryPatterns();
|
|
277
|
+
// Research seeds: managed ONLY while untracked (created by BALDART, never yet
|
|
278
|
+
// the consumer's). Tracked-and-modified seeds are user content — untouched.
|
|
279
|
+
const untracked = new Set([...status.not_added, ...status.created]);
|
|
280
|
+
const researchSeedNew = researchSeedPaths().filter((p) => untracked.has(p));
|
|
255
281
|
for (const p of allDirty) {
|
|
256
282
|
if (!p || seen.has(p)) continue;
|
|
257
283
|
seen.add(p);
|
|
258
|
-
(isBaldartManagedPath(p, extraPatterns) ? managed : userOwned).push(p);
|
|
284
|
+
(isBaldartManagedPath(p, extraPatterns) || researchSeedNew.includes(p) ? managed : userOwned).push(p);
|
|
259
285
|
}
|
|
260
286
|
if (managed.length === 0) {
|
|
261
287
|
return;
|
|
@@ -986,6 +1012,17 @@ async function update(options = {}, unknownArgs = []) {
|
|
|
986
1012
|
// --json is programmatic → force autonomous so a handwritten file is never mutated.
|
|
987
1013
|
reconcileRootPrimitives(process.cwd(), alignedTools, options && options.json === true ? true : undefined);
|
|
988
1014
|
|
|
1015
|
+
// Research-library seed backfill (v5.1.0) — seeds only, into an EXISTING
|
|
1016
|
+
// dir. Never creates the dir here (creation ownership: configure/doctor;
|
|
1017
|
+
// update must not resurrect a deliberately deleted library). Silent + idempotent.
|
|
1018
|
+
try {
|
|
1019
|
+
const fsl = require('fs');
|
|
1020
|
+
const pathl = require('path');
|
|
1021
|
+
const yaml = require('js-yaml');
|
|
1022
|
+
const cfg = yaml.load(fsl.readFileSync(pathl.join(process.cwd(), 'baldart.config.yml'), 'utf8'));
|
|
1023
|
+
require('../utils/research-library').ensureResearchLibrary(process.cwd(), cfg, { createDir: false });
|
|
1024
|
+
} catch (_) { /* doctor heals */ }
|
|
1025
|
+
|
|
989
1026
|
// Backfill hooks on the aligned/self-heal path too. Same gap class as the
|
|
990
1027
|
// payload reconcile above: a consumer who framework-updated with an OLDER
|
|
991
1028
|
// CLI (before a given hook shipped — e.g. the S2 Codex hooks) then aligns
|
|
@@ -1351,6 +1388,16 @@ async function update(options = {}, unknownArgs = []) {
|
|
|
1351
1388
|
// a primitive/slot/overlay change lands. Idempotent + byte-stable.
|
|
1352
1389
|
reconcileRootPrimitives(process.cwd(), enabledTools, options && options.json === true ? true : undefined);
|
|
1353
1390
|
|
|
1391
|
+
// Research-library seed backfill (v5.1.0) — seeds only, into an EXISTING dir
|
|
1392
|
+
// (creation ownership: configure/doctor; never resurrect a deleted library).
|
|
1393
|
+
try {
|
|
1394
|
+
const fsl = require('fs');
|
|
1395
|
+
const pathl = require('path');
|
|
1396
|
+
const yaml = require('js-yaml');
|
|
1397
|
+
const cfg = yaml.load(fsl.readFileSync(pathl.join(process.cwd(), 'baldart.config.yml'), 'utf8'));
|
|
1398
|
+
require('../utils/research-library').ensureResearchLibrary(process.cwd(), cfg, { createDir: false });
|
|
1399
|
+
} catch (_) { /* doctor heals */ }
|
|
1400
|
+
|
|
1354
1401
|
// Routines wizard (since v2.1.0) — surfaces routines added in the new framework version
|
|
1355
1402
|
try {
|
|
1356
1403
|
const routinesCmd = require('./routines');
|
|
@@ -290,5 +290,47 @@ console.log('\n─── overlayCoversTouched (fs-backed) ───');
|
|
|
290
290
|
fsMod.rmSync(tmp, { recursive: true, force: true });
|
|
291
291
|
}
|
|
292
292
|
|
|
293
|
+
// absorbablePayloadSubset(touched) — pure. The subset whose byte-content must
|
|
294
|
+
// match upstream for a commit to be "absorbed". Consumer files (outside
|
|
295
|
+
// `.framework/`) and subtree bookkeeping (VERSION/CHANGELOG/README) are
|
|
296
|
+
// EXCLUDED. Fixtures reproduce the exact mayo v5.0.1→v5.1.0 divergence that
|
|
297
|
+
// falsely blocked the update pre-v5.1.1 (mixed FEAT commits + add-then-revert
|
|
298
|
+
// pairs touching VERSION/CHANGELOG).
|
|
299
|
+
console.log('\n─── absorbablePayloadSubset (pure) ───');
|
|
300
|
+
{
|
|
301
|
+
const cases = [
|
|
302
|
+
{ name: 'mixed FEAT commit: framework doc + app files → only the doc survives',
|
|
303
|
+
touched: ['.framework/framework/agents/design-system-protocol.md',
|
|
304
|
+
'backlog/FEAT-0054-03-enforcement.yml',
|
|
305
|
+
'docs/references/ssot-registry.md'],
|
|
306
|
+
expected: ['.framework/framework/agents/design-system-protocol.md'] },
|
|
307
|
+
{ name: 'mixed FEAT commit: framework doc + CLAUDE.md + eslint config → only the doc',
|
|
308
|
+
touched: ['.framework/framework/agents/design-system-protocol.md',
|
|
309
|
+
'CLAUDE.md', 'eslint.responsive.config.mjs'],
|
|
310
|
+
expected: ['.framework/framework/agents/design-system-protocol.md'] },
|
|
311
|
+
{ name: 'bookkeeping-only touch (VERSION/CHANGELOG/README) → empty subset',
|
|
312
|
+
touched: ['.framework/VERSION', '.framework/CHANGELOG.md', '.framework/README.md'],
|
|
313
|
+
expected: [] },
|
|
314
|
+
{ name: 'revert commit: bookkeeping + payload scripts → only the scripts survive',
|
|
315
|
+
touched: ['.framework/CHANGELOG.md', '.framework/README.md', '.framework/VERSION',
|
|
316
|
+
'.framework/framework/.claude/skills/design-system-init/scripts/extract-ts.mjs',
|
|
317
|
+
'.framework/framework/agents/component-manifest-schema.md'],
|
|
318
|
+
expected: ['.framework/framework/.claude/skills/design-system-init/scripts/extract-ts.mjs',
|
|
319
|
+
'.framework/framework/agents/component-manifest-schema.md'] },
|
|
320
|
+
{ name: 'consumer-only files → empty subset',
|
|
321
|
+
touched: ['src/app/page.tsx', 'README.md'], expected: [] },
|
|
322
|
+
{ name: 'empty touched → empty subset',
|
|
323
|
+
touched: [], expected: [] },
|
|
324
|
+
{ name: 'null touched → empty subset (no throw)',
|
|
325
|
+
touched: null, expected: [] },
|
|
326
|
+
];
|
|
327
|
+
for (const c of cases) {
|
|
328
|
+
const actual = GitUtils.absorbablePayloadSubset(c.touched);
|
|
329
|
+
const ok = JSON.stringify(actual) === JSON.stringify(c.expected);
|
|
330
|
+
if (ok) { pass++; console.log(` ✓ ${c.name}`); }
|
|
331
|
+
else { fail++; console.log(` ✗ ${c.name} → ${JSON.stringify(actual)} (expected ${JSON.stringify(c.expected)})`); }
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
|
|
293
335
|
console.log(`\nResult: ${pass} pass, ${fail} fail`);
|
|
294
336
|
process.exit(fail > 0 ? 1 : 0);
|
package/src/utils/git.js
CHANGED
|
@@ -209,10 +209,13 @@ class GitUtils {
|
|
|
209
209
|
// - subtree-merge: `git subtree pull --squash` merge commits
|
|
210
210
|
// - subtree-squash: the squash payload commits themselves
|
|
211
211
|
// - chore-wrapper: CLI-generated [CHORE]/chore(baldart): commits
|
|
212
|
-
// - absorbed: a would-be custom commit whose touched .framework/
|
|
213
|
-
// are now byte-identical to upstream (FETCH_HEAD) — the
|
|
214
|
-
// has been absorbed into upstream (or reset away), so a
|
|
215
|
-
// re-sync loses nothing.
|
|
212
|
+
// - absorbed: a would-be custom commit whose touched .framework/ PAYLOAD
|
|
213
|
+
// files are now byte-identical to upstream (FETCH_HEAD) — the
|
|
214
|
+
// edit has been absorbed into upstream (or reset away), so a
|
|
215
|
+
// re-sync loses nothing. Consumer files (outside .framework/)
|
|
216
|
+
// and subtree bookkeeping (VERSION/CHANGELOG/README) are not
|
|
217
|
+
// counted (see absorbablePayloadSubset). Pure history, not
|
|
218
|
+
// live drift → noise.
|
|
216
219
|
// - custom-overlay-able: user edited a framework agent/skill/command
|
|
217
220
|
// → should migrate to .baldart/overlays/
|
|
218
221
|
// - custom-other: anything else (src/, CHANGELOG, ad-hoc fixes)
|
|
@@ -297,18 +300,51 @@ class GitUtils {
|
|
|
297
300
|
});
|
|
298
301
|
}
|
|
299
302
|
|
|
300
|
-
//
|
|
303
|
+
// The subset of a commit's touched paths whose byte-content must match
|
|
304
|
+
// upstream for the commit to count as "absorbed". Two classes of path are
|
|
305
|
+
// EXCLUDED because they can never conflict in a `.framework/` subtree sync:
|
|
306
|
+
// 1. Anything outside `.framework/` — the consumer's OWN files. A mixed
|
|
307
|
+
// commit that edits both a framework doc and app code (e.g. a FEAT that
|
|
308
|
+
// also tweaks a protocol module) is common; the subtree merge never
|
|
309
|
+
// touches the app files, so they are irrelevant to whether the commit's
|
|
310
|
+
// framework portion is absorbed. (Pre-v5.1.1 the check bailed to
|
|
311
|
+
// not-absorbed on the FIRST such path → mixed commits falsely blocked
|
|
312
|
+
// the update even when their `.framework/` payload matched upstream.)
|
|
313
|
+
// 2. Subtree bookkeeping (`.framework/{VERSION,CHANGELOG.md,README.md}`) —
|
|
314
|
+
// framework-owned metadata the merge reconciles wholesale, which ALWAYS
|
|
315
|
+
// differs across versions (VERSION bumps, CHANGELOG grows). Counting it
|
|
316
|
+
// meant any commit touching it (e.g. an add-then-revert pair) could
|
|
317
|
+
// never be absorbed. It is never consumer content, so dropping it is
|
|
318
|
+
// safe: a hand-edit to it SHOULD be overwritten by upstream anyway.
|
|
319
|
+
// Pure (no fs / no git) so it stays unit-testable.
|
|
320
|
+
static absorbablePayloadSubset(touched) {
|
|
321
|
+
const prefix = `${FRAMEWORK_DIR}/`;
|
|
322
|
+
const META = new Set([
|
|
323
|
+
`${prefix}VERSION`,
|
|
324
|
+
`${prefix}CHANGELOG.md`,
|
|
325
|
+
`${prefix}README.md`,
|
|
326
|
+
]);
|
|
327
|
+
return (touched || []).filter((p) => p.startsWith(prefix) && !META.has(p));
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
// True when EVERY `.framework/` PAYLOAD file touched by the commit is already
|
|
301
331
|
// byte-identical to the upstream (FETCH_HEAD) blob — i.e. the commit's edit
|
|
302
332
|
// has been fully absorbed into upstream (or reset away) and a re-sync would
|
|
303
333
|
// lose nothing. Compares blob SHAs (cheap, exact). The subtree prefix
|
|
304
334
|
// `.framework/` is stripped to reach the upstream path (FETCH_HEAD has no
|
|
305
|
-
// `.framework/` prefix).
|
|
306
|
-
//
|
|
335
|
+
// `.framework/` prefix). The comparison runs over `absorbablePayloadSubset`
|
|
336
|
+
// ONLY (consumer files + subtree bookkeeping excluded — see there). A commit
|
|
337
|
+
// whose payload subset is EMPTY (it touched `.framework/` only via bookkeeping
|
|
338
|
+
// and/or only touched consumer files) carries no subtree drift → absorbed.
|
|
339
|
+
// Conservative on the payload itself: a file missing at HEAD (deleted) or
|
|
340
|
+
// missing upstream (net-new), or whose SHA differs → not absorbed (real
|
|
341
|
+
// drift, keep flagging).
|
|
307
342
|
async isAbsorbedAgainstUpstream(touched) {
|
|
308
343
|
if (!touched || !touched.length) return false;
|
|
309
344
|
const prefix = `${FRAMEWORK_DIR}/`;
|
|
310
|
-
|
|
311
|
-
|
|
345
|
+
const payload = GitUtils.absorbablePayloadSubset(touched);
|
|
346
|
+
if (!payload.length) return true;
|
|
347
|
+
for (const p of payload) {
|
|
312
348
|
const upstreamPath = p.slice(prefix.length);
|
|
313
349
|
let headSha;
|
|
314
350
|
let upSha;
|