baldart 4.89.1 → 4.92.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +87 -0
- package/VERSION +1 -1
- package/framework/.claude/agents/api-perf-cost-auditor.md +9 -0
- package/framework/.claude/agents/code-reviewer.md +9 -0
- package/framework/.claude/agents/codebase-architect.md +6 -0
- package/framework/.claude/agents/coder.md +1 -1
- package/framework/.claude/agents/markup-fidelity-verifier.md +24 -1
- package/framework/.claude/agents/qa-sentinel.md +9 -0
- package/framework/.claude/agents/security-reviewer.md +9 -0
- package/framework/.claude/agents/ui-expert.md +49 -0
- package/framework/.claude/agents/visual-fidelity-verifier.md +16 -12
- package/framework/.claude/skills/e2e-review/CHANGELOG.md +49 -0
- package/framework/.claude/skills/e2e-review/SKILL.md +178 -24
- package/framework/.claude/skills/new/CHANGELOG.md +29 -0
- package/framework/.claude/skills/new/SKILL.md +44 -2
- package/framework/.claude/skills/new/references/completeness.md +1 -0
- package/framework/.claude/skills/new/references/implement.md +19 -3
- package/framework/.claude/skills/new/references/review-cycle.md +23 -1
- package/framework/.claude/skills/new/references/team-mode.md +4 -0
- package/framework/.claude/skills/new/scripts/verify-item.sh +54 -0
- package/framework/.claude/skills/new2/CHANGELOG.md +30 -0
- package/framework/.claude/skills/new2/SKILL.md +35 -8
- package/framework/.claude/skills/ui-implement/CHANGELOG.md +15 -0
- package/framework/.claude/skills/ui-implement/SKILL.md +31 -6
- package/framework/.claude/skills/ui-implement/references/integration.md +12 -3
- package/framework/.claude/skills/ui-implement/scripts/check-bindings.mjs +103 -0
- package/framework/.claude/workflows/new-card-review.js +19 -5
- package/framework/.claude/workflows/new-final-review.js +24 -9
- package/framework/.claude/workflows/new2.js +50 -4
- package/framework/agents/code-search-protocol.md +24 -0
- package/framework/agents/design-system-protocol.md +27 -0
- package/framework/docs/WORKFLOWS.md +9 -4
- package/framework/scripts/structural-compare.mjs +178 -0
- package/package.json +1 -1
- package/src/commands/update.js +51 -5
|
@@ -15,12 +15,17 @@ workflows are unavailable behaves exactly as before.
|
|
|
15
15
|
| :--- | :--- | :--- |
|
|
16
16
|
| `new-final-review` | `/new` Final Review (Step F.1.5) | Runs the read-only cross-batch review fan-out — architecture baseline + Codex ‖ doc-reviewer ‖ api-perf-cost-auditor ‖ qa-sentinel — then adversarially verifies low-confidence findings and returns them classified. Applies no fixes (the skill owns fix application + user gates). **v4.17.1+:** Codex availability is resolved by a **deterministic pre-flight glob + background poll** (no false negatives from a synchronous-run timeout); a **single-card batch** slims the breadth finders **per-finder, coverage-gated** (caller-supplied `slimDoc`/`slimApi`): it drops a duplicate Claude reviewer only when that reviewer already ran per-card on the same card — `new2` drops both doc + api (it runs both per-card, relevance-gated); classic `/new` drops only `doc-reviewer` (when Phase 3 ran it) and **keeps `api-perf-cost-auditor`** (deferred-to-final by design — the final is its only run). Codex + qa gates always run. Backward-compatible: callers without the flags get the full reviewer set. |
|
|
17
17
|
| `new-card-review` (v4.34.0) | `/new` per-card review cluster — sequential (`review-cycle.md` § Phase 2.5x) **and** team-mode (`team-mode.md` D.1.6) | Hosts the **per-wave** review-cluster OUTSIDE the orchestrator context — the biggest prefix-growth source on long epics. Takes **1..N co-located cards** (1 = sequential per-card; N = a team-mode group, so it runs **once per wave, not per card**) and fans out the finders per card: Simplify + cross-model Codex (agent-launched binary, `code-reviewer` fallback) + qa-sentinel (group max tier) + security-reviewer (high-risk only), each specialist FP-checking its own findings. Then **ONE `coder` applies all VERIFIED code/perf/security/simplify findings in a single pass** (files disjoint by ownership) and re-verifies lint/tsc/build. Returns a compact `{perCard:{fixesApplied,residual}, gateTable, summary}` — the minority `residual` (doc, needs-manual, scope, unconverged) the skill resolves with the right specialist / user gate. **Doc-review and E2E stay in the skill** (doc is write-mode + must see final code; E2E is human-gated + nests a skill); **api-perf is deferred to the Final FULL gate**. Maps to `review-cycle.md` Phase 2.55+3.5+3.7. |
|
|
18
|
-
| `new2` (v4.17.2) | `/new2` skill (the whole batch) | **
|
|
18
|
+
| `new2` (v4.17.2, promoted v4.90.0) | `/new2` skill (the whole batch) **+ `/new -auto` via its delegation gate** | **Workflow-hosted batch engine of `/new`.** Hosts the ENTIRE batch in the background runtime so subagent output never enters the main context. A **dependency-gated DAG scheduler** runs a card only when all in-batch deps are *committed* (and blocks transitive dependents of a failed dep instead of routing them to resolve); each card uses its **owner_agent** + a **specialized review fan-out** (not general-purpose); the worktree is kept **atomic per card** (rollback-to-HEAD on failure); transient API errors are retried and a sustained **outage degrades cleanly** (`degraded` return + durable resume via the skill); a **run ledger** dedups resolves and records accepted deferrals (no re-routing loop); the **merge is integrity-gated** (never force-DONE, never `git add` unreviewed code, never merge an incomplete/degraded batch); the commit step runs on **Haiku** while **follow-up cards are written by `prd-card-writer`**; telemetry carries real **cost** (`total_tokens` via `budget.spent()`, `agent_count`, skill-stamped `wall_clock_s`) + `degraded`. **v4.17.2:** the pre-flight **G3 cross-card Codex check is deterministic** (glob-first + background poll, skipped on single-card batches; `codex_resolved` in telemetry); a non-transient card crash is terminal-with-residual (no orphaned self-healing). Agents Read `/new`'s reference modules for semantics. |
|
|
19
19
|
| `new2-resolve` (v4.17.2) | `new2` (self-healing) | Resolution pass for any gate that would otherwise need a human (`ac-unmet · blocker · qa-fail · e2e-blocked · merge-blocker · scope-expansion`). A **terminal short-circuit** skips the costly multi-attempt when the problem is impossible-by-definition (`out-of-ownership` verified in JS; other terminal reasons ratified by a judge); a **MANDATORY adversarial judge** cross-checks every `verified` claim — the judge independently greps the files and the workflow verifies **at least one** falls inside MAY-EDIT (`.some()`, so listing adjacent changed files is not mistaken for fabrication); accepts a **batched `findings` list** (one resolve per fix-area). **The domain is normalized** (freeform `documentation`→`doc`, …) before routing the **fixer** (doc→doc-reviewer, ui→ui-expert, security→security-reviewer, else coder) and judge, and a **doc finding gets doc-tree MAY-EDIT** (not the card's code scope); the 3-angle Tier-2 fan-out is reserved for code domains (single retry for doc/test). Follow-ups are written by **`prd-card-writer`**, offline-safe (deferred to the skill if no agent can write). |
|
|
20
20
|
|
|
21
|
-
> **`new2` is
|
|
22
|
-
>
|
|
23
|
-
>
|
|
21
|
+
> **`new2` is the engine, `/new` is the SSOT.** Since v4.90.0 `/new -auto`
|
|
22
|
+
> delegates the whole batch here by default (AUTONOMOUS OFF-CONTEXT DELEGATION
|
|
23
|
+
> GATE — measured driver: 53–59% of an inline `-auto` run's billed tokens is the
|
|
24
|
+
> orchestrator's own turn-tax; escape hatch `-inline`; `-auto-ship`, declared
|
|
25
|
+
> migrations, interactive runs and every Codex run stay on the classic inline
|
|
26
|
+
> path). `/new` remains the semantic SSOT and the cross-tool default — `new2`'s
|
|
27
|
+
> agents Read `/new`'s reference modules; the gate map in `new2.js` encodes only
|
|
28
|
+
> orchestration shape + deterministic policy. `new2` is Claude-only and its
|
|
24
29
|
> skill HALTs with a pointer to `/new` when the `Workflow` tool is unavailable.
|
|
25
30
|
|
|
26
31
|
## How distribution works
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// structural-compare.mjs — deterministic STRUCTURAL pre-gate for the mockup→code
|
|
3
|
+
// fidelity chain (T1.B, framework v4.90.0). Zero-dep, heuristic, CONSERVATIVE.
|
|
4
|
+
//
|
|
5
|
+
// Compares a code-form mockup (an .html file, or a design_src dir of .jsx/.tsx/.css)
|
|
6
|
+
// against the implementation files on a small set of CLEAR structural signals:
|
|
7
|
+
// - landmark census (header/nav/aside/main/footer present in mockup, absent in impl)
|
|
8
|
+
// - max grid column count (multi-column mockup built single-column — the FEAT-0061 class)
|
|
9
|
+
// - responsive signals (@media breakpoints / Tailwind responsive prefixes present in
|
|
10
|
+
// the mockup, entirely absent in the impl)
|
|
11
|
+
//
|
|
12
|
+
// SEMANTICS — signals, not verdicts. The implementation may COMPOSE primitives (a
|
|
13
|
+
// `<SplitView>` that owns the grid, a `<TopBar>` that renders the <header>), and this
|
|
14
|
+
// script only sees the card-touched files — so a "divergence" here is a deterministic
|
|
15
|
+
// SIGNAL with a per-finding `confidence`, never a standalone verdict:
|
|
16
|
+
// - in-build (ui-expert self-check): the builder judges each signal — it knows which
|
|
17
|
+
// primitives it composed — and fixes what is real BEFORE returning.
|
|
18
|
+
// - in Phase 2.7 (/e2e-review): the signals are passed to `markup-fidelity-verifier`
|
|
19
|
+
// as `deterministic_signals` focus hints; the verifier confirms or dismisses each
|
|
20
|
+
// (it can read the composed primitives). The verifier spawn is NEVER skipped on a
|
|
21
|
+
// "match" — a regex match is necessary, not sufficient.
|
|
22
|
+
//
|
|
23
|
+
// Contract:
|
|
24
|
+
// node structural-compare.mjs --mockup <file-or-dir> --impl <f1> <f2> ...
|
|
25
|
+
// → one JSON line: { status: "match" | "divergence" | "skipped",
|
|
26
|
+
// mockup_signature, impl_signature,
|
|
27
|
+
// findings:[{category,severity,description,expected,actual}] }
|
|
28
|
+
// Exit: 0 = match/skipped (nothing provable), 3 = divergence (findings are evidence).
|
|
29
|
+
// Internal errors fail OPEN ({status:"skipped"}, exit 0) — pre-gate, not tribunal.
|
|
30
|
+
|
|
31
|
+
import fs from 'node:fs';
|
|
32
|
+
import path from 'node:path';
|
|
33
|
+
|
|
34
|
+
function emit(obj, code) {
|
|
35
|
+
process.stdout.write(JSON.stringify(obj) + '\n');
|
|
36
|
+
process.exit(code);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function parseArgs(argv) {
|
|
40
|
+
const a = { mockup: null, impl: [] };
|
|
41
|
+
for (let i = 2; i < argv.length; i++) {
|
|
42
|
+
if (argv[i] === '--mockup') a.mockup = argv[++i];
|
|
43
|
+
else if (argv[i] === '--impl') {
|
|
44
|
+
while (i + 1 < argv.length && !argv[i + 1].startsWith('--')) a.impl.push(argv[++i]);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
return a;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function readSources(target) {
|
|
51
|
+
const texts = [];
|
|
52
|
+
const push = (p) => { try { texts.push(fs.readFileSync(p, 'utf8')); } catch { /* fail-open per file */ } };
|
|
53
|
+
try {
|
|
54
|
+
const st = fs.statSync(target);
|
|
55
|
+
if (st.isDirectory()) {
|
|
56
|
+
for (const f of fs.readdirSync(target)) {
|
|
57
|
+
if (/\.(html?|jsx?|tsx?|css|scss|svelte|vue)$/i.test(f)) push(path.join(target, f));
|
|
58
|
+
}
|
|
59
|
+
} else push(target);
|
|
60
|
+
} catch { /* absent target → empty */ }
|
|
61
|
+
return texts;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// Count top-level tracks in a grid-template-columns value: "repeat(2, minmax(0,1fr))" → 2,
|
|
65
|
+
// "1fr 320px" → 2. Parens-aware split; `repeat(N, ...)` contributes N (integer N only).
|
|
66
|
+
function countTracks(value) {
|
|
67
|
+
const v = value.trim();
|
|
68
|
+
if (!v || /^(subgrid|none|inherit|initial|unset)/.test(v)) return 0;
|
|
69
|
+
const parts = [];
|
|
70
|
+
let depth = 0, cur = '';
|
|
71
|
+
for (const ch of v) {
|
|
72
|
+
if (ch === '(') { depth++; cur += ch; }
|
|
73
|
+
else if (ch === ')') { depth--; cur += ch; }
|
|
74
|
+
else if (/\s/.test(ch) && depth === 0) { if (cur) { parts.push(cur); cur = ''; } }
|
|
75
|
+
else cur += ch;
|
|
76
|
+
}
|
|
77
|
+
if (cur) parts.push(cur);
|
|
78
|
+
let n = 0;
|
|
79
|
+
for (const p of parts) {
|
|
80
|
+
const rep = p.match(/^repeat\(\s*(\d+)\s*,/);
|
|
81
|
+
if (rep) n += parseInt(rep[1], 10);
|
|
82
|
+
else if (/^\[.*\]$/.test(p)) continue; // named grid line, not a track
|
|
83
|
+
else n += 1;
|
|
84
|
+
}
|
|
85
|
+
return n;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function signature(texts) {
|
|
89
|
+
const sig = {
|
|
90
|
+
landmarks: { header: 0, nav: 0, aside: 0, main: 0, footer: 0 },
|
|
91
|
+
grid_cols_max: 0,
|
|
92
|
+
grid_cols_responsive_max: 0, // max cols behind a responsive gate (md:/lg:/@media)
|
|
93
|
+
has_media_queries: false,
|
|
94
|
+
has_responsive_prefixes: false,
|
|
95
|
+
files: texts.length,
|
|
96
|
+
};
|
|
97
|
+
for (const t of texts) {
|
|
98
|
+
for (const lm of Object.keys(sig.landmarks)) {
|
|
99
|
+
const m = t.match(new RegExp(`<${lm}[\\s/>]`, 'gi'));
|
|
100
|
+
if (m) sig.landmarks[lm] += m.length;
|
|
101
|
+
}
|
|
102
|
+
// CSS: grid-template-columns (also inside <style> blocks / .css files / inline style=)
|
|
103
|
+
for (const m of t.matchAll(/grid-template-columns\s*:\s*([^;"'}]+)/gi)) {
|
|
104
|
+
sig.grid_cols_max = Math.max(sig.grid_cols_max, countTracks(m[1]));
|
|
105
|
+
}
|
|
106
|
+
// @media-scoped multi-col: approximate — a grid-template-columns inside a file that
|
|
107
|
+
// also has @media counts as responsive-capable (regex cannot scope blocks reliably).
|
|
108
|
+
if (/@media[^{]*\(\s*(min|max)-width/i.test(t)) {
|
|
109
|
+
sig.has_media_queries = true;
|
|
110
|
+
for (const m of t.matchAll(/grid-template-columns\s*:\s*([^;"'}]+)/gi)) {
|
|
111
|
+
sig.grid_cols_responsive_max = Math.max(sig.grid_cols_responsive_max, countTracks(m[1]));
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
// Tailwind: grid-cols-N (+ responsive variants), incl. arbitrary grid-cols-[a_b]
|
|
115
|
+
for (const m of t.matchAll(/(?:^|[\s"'`])(?:(sm|md|lg|xl|2xl):)?grid-cols-(\d+|\[[^\]]+\])/g)) {
|
|
116
|
+
const cols = m[2].startsWith('[') ? m[2].split('_').length : parseInt(m[2], 10);
|
|
117
|
+
sig.grid_cols_max = Math.max(sig.grid_cols_max, cols);
|
|
118
|
+
if (m[1]) {
|
|
119
|
+
sig.has_responsive_prefixes = true;
|
|
120
|
+
sig.grid_cols_responsive_max = Math.max(sig.grid_cols_responsive_max, cols);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
if (/(?:^|[\s"'`])(?:sm|md|lg|xl|2xl):[\w[-]/m.test(t)) sig.has_responsive_prefixes = true;
|
|
124
|
+
}
|
|
125
|
+
return sig;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
function main() {
|
|
129
|
+
const args = parseArgs(process.argv);
|
|
130
|
+
if (!args.mockup || args.impl.length === 0) emit({ status: 'skipped', reason: 'usage: --mockup <file-or-dir> --impl <files...>' }, 0);
|
|
131
|
+
|
|
132
|
+
const mockTexts = readSources(args.mockup);
|
|
133
|
+
if (mockTexts.length === 0) emit({ status: 'skipped', reason: 'mockup unreadable: ' + args.mockup }, 0);
|
|
134
|
+
const implTexts = args.impl.flatMap((f) => readSources(f));
|
|
135
|
+
if (implTexts.length === 0) emit({ status: 'skipped', reason: 'no readable implementation files' }, 0);
|
|
136
|
+
|
|
137
|
+
const mock = signature(mockTexts);
|
|
138
|
+
const impl = signature(implTexts);
|
|
139
|
+
const findings = [];
|
|
140
|
+
|
|
141
|
+
// Confidence encodes the composition caveat: the impl files are the card-touched set,
|
|
142
|
+
// and grids/landmarks may live INSIDE a reused primitive this scan cannot see.
|
|
143
|
+
// 1. Multi-column mockup built single-column (the FEAT-0061 layout-break class).
|
|
144
|
+
if (mock.grid_cols_max >= 2 && impl.grid_cols_max <= 1) {
|
|
145
|
+
findings.push({
|
|
146
|
+
category: 'layout-break', severity: 'critical', confidence: 'medium',
|
|
147
|
+
description: `mockup declares a ${mock.grid_cols_max}-column grid; implementation files declare no multi-column grid (unless a composed primitive owns it — verify)`,
|
|
148
|
+
expected: `grid with ${mock.grid_cols_max} columns (mockup)`, actual: `max ${impl.grid_cols_max} column(s) found in touched files`,
|
|
149
|
+
});
|
|
150
|
+
}
|
|
151
|
+
// 2. Mockup gates multi-col behind a breakpoint; impl has NO responsive machinery at all.
|
|
152
|
+
if ((mock.has_media_queries || mock.has_responsive_prefixes) && !impl.has_media_queries && !impl.has_responsive_prefixes) {
|
|
153
|
+
findings.push({
|
|
154
|
+
category: 'responsiveness-break', severity: 'major', confidence: 'medium',
|
|
155
|
+
description: 'mockup is breakpoint-aware (@media / responsive prefixes); implementation files have neither (unless a composed primitive owns the breakpoints — verify)',
|
|
156
|
+
expected: 'breakpoint-gated layout as in the mockup', actual: 'no @media, no responsive class prefixes in touched files',
|
|
157
|
+
});
|
|
158
|
+
}
|
|
159
|
+
// 3. Landmark regions present in the mockup, entirely absent in the implementation.
|
|
160
|
+
// LOW confidence by construction: component-based impls render landmarks inside
|
|
161
|
+
// reused shell primitives (<TopBar> → <header>) invisible to this scan.
|
|
162
|
+
for (const lm of Object.keys(mock.landmarks)) {
|
|
163
|
+
if (mock.landmarks[lm] > 0 && impl.landmarks[lm] === 0) {
|
|
164
|
+
findings.push({
|
|
165
|
+
category: 'component-missing', severity: 'minor', confidence: 'low',
|
|
166
|
+
description: `mockup contains <${lm}> landmark region(s); none in the touched files (may be rendered by a composed shell primitive — verify)`,
|
|
167
|
+
expected: `<${lm}> region (mockup has ${mock.landmarks[lm]})`, actual: 'absent from touched files',
|
|
168
|
+
});
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
emit(
|
|
173
|
+
{ status: findings.length ? 'divergence' : 'match', mockup_signature: mock, impl_signature: impl, findings },
|
|
174
|
+
findings.length ? 3 : 0
|
|
175
|
+
);
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
try { main(); } catch (e) { emit({ status: 'skipped', reason: 'internal error: ' + (e && e.message) }, 0); }
|
package/package.json
CHANGED
package/src/commands/update.js
CHANGED
|
@@ -120,22 +120,67 @@ function scaffoldOverlaysForCommits(commits) {
|
|
|
120
120
|
|
|
121
121
|
// Path prefixes BALDART itself writes during install/update. Anything outside
|
|
122
122
|
// these patterns is treated as user-owned and never auto-staged.
|
|
123
|
+
//
|
|
124
|
+
// INVARIANT (do not let this list drift again): this allowlist MUST cover the
|
|
125
|
+
// FULL set of payloads BALDART writes to a consumer's tree — every runtime, not
|
|
126
|
+
// just Claude. A path BALDART generates but that is absent here is classified
|
|
127
|
+
// `userOwned`, never auto-committed, and resurfaces as an untracked/modified
|
|
128
|
+
// file in every other clone of the repo (the exact "commit never happens →
|
|
129
|
+
// other terminals see churn" bug). When you add ANY new written payload — a new
|
|
130
|
+
// `.claude/*` payload kind, a Codex artifact under `.codex/` or `.agents/`, a
|
|
131
|
+
// root file, an i18n/toolchain config — add its pattern here in the SAME change
|
|
132
|
+
// (parity twin of the schema-change propagation rule). See CHANGELOG v4.89.2.
|
|
123
133
|
const BALDART_MANAGED_PATTERNS = [
|
|
124
134
|
/^\.framework(\/|$)/,
|
|
125
135
|
/^AGENTS\.md$/,
|
|
126
136
|
/^CLAUDE\.md$/,
|
|
127
137
|
/^agents(\.backup)?(\/|$)/,
|
|
128
|
-
|
|
138
|
+
// Claude payload kinds (incl. output-styles, v4.50.0).
|
|
139
|
+
/^\.claude\/(agents|commands|skills|workflows|hooks|routines|output-styles|settings\.json)(\/|$)/,
|
|
140
|
+
// Codex-native payloads: skill symlinks (v3.7.0), transpiled agents (v4.80.0),
|
|
141
|
+
// native hooks (v4.88.0). Only exist when `tools.enabled ∋ codex`, so listing
|
|
142
|
+
// them unconditionally is safe — absent means never dirty.
|
|
143
|
+
/^\.agents\/skills(\/|$)/,
|
|
144
|
+
/^\.codex(\/|$)/,
|
|
129
145
|
/^\.baldart(\/|$)/,
|
|
130
146
|
/^baldart\.config\.yml$/,
|
|
131
147
|
/^docs\/references\/(ui-guidelines\.template\.md|brand-guidelines\.md)$/,
|
|
132
148
|
/^templates(\/|$)/,
|
|
133
149
|
/^\.github\/workflows\/baldart-/,
|
|
134
|
-
/^scripts\/routines(\/|$)
|
|
150
|
+
/^scripts\/routines(\/|$)/,
|
|
151
|
+
// i18n gate configs written by src/utils/i18n-gate.js (flat + legacy).
|
|
152
|
+
/^eslint\.i18n\.config\.mjs$/,
|
|
153
|
+
/^\.eslintrc\.i18n\.json$/
|
|
135
154
|
];
|
|
136
155
|
|
|
137
|
-
|
|
138
|
-
|
|
156
|
+
// The i18n context registry path is consumer-configurable (paths.i18n_registry),
|
|
157
|
+
// so its containing directory can't be a static literal — derive it from config
|
|
158
|
+
// so the registry file BALDART writes is auto-committed like every other managed
|
|
159
|
+
// payload. Falls back to the template default (docs/i18n/) when unset/unreadable.
|
|
160
|
+
function i18nRegistryPatterns(cwd = process.cwd()) {
|
|
161
|
+
let rel = 'docs/i18n/registry.yml';
|
|
162
|
+
try {
|
|
163
|
+
const fs = require('fs');
|
|
164
|
+
const path = require('path');
|
|
165
|
+
const yaml = require('js-yaml');
|
|
166
|
+
const cfgPath = path.join(cwd, 'baldart.config.yml');
|
|
167
|
+
if (fs.existsSync(cfgPath)) {
|
|
168
|
+
const cfg = yaml.load(fs.readFileSync(cfgPath, 'utf8'));
|
|
169
|
+
const v = cfg && cfg.paths && cfg.paths.i18n_registry;
|
|
170
|
+
if (typeof v === 'string' && v.trim()) rel = v.trim();
|
|
171
|
+
}
|
|
172
|
+
} catch (_) { /* fall back to the template default */ }
|
|
173
|
+
const norm = rel.replace(/\\/g, '/');
|
|
174
|
+
const out = [new RegExp('^' + norm.replace(/[.*+?^${}()|[\]\\]/g, '\\$&') + '$')];
|
|
175
|
+
const dir = norm.replace(/\/[^/]*$/, '');
|
|
176
|
+
if (dir && dir !== norm) {
|
|
177
|
+
out.push(new RegExp('^' + dir.replace(/[.*+?^${}()|[\]\\]/g, '\\$&') + '(\\/|$)'));
|
|
178
|
+
}
|
|
179
|
+
return out;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
function isBaldartManagedPath(p, extra = []) {
|
|
183
|
+
return BALDART_MANAGED_PATTERNS.some((rx) => rx.test(p)) || extra.some((rx) => rx.test(p));
|
|
139
184
|
}
|
|
140
185
|
|
|
141
186
|
// Re-generate the root primitives (AGENTS.md + CLAUDE.md) from the versioned
|
|
@@ -206,10 +251,11 @@ async function postUpdateAutoCommit(git, newVersion, options) {
|
|
|
206
251
|
const seen = new Set();
|
|
207
252
|
const managed = [];
|
|
208
253
|
const userOwned = [];
|
|
254
|
+
const extraPatterns = i18nRegistryPatterns();
|
|
209
255
|
for (const p of allDirty) {
|
|
210
256
|
if (!p || seen.has(p)) continue;
|
|
211
257
|
seen.add(p);
|
|
212
|
-
(isBaldartManagedPath(p) ? managed : userOwned).push(p);
|
|
258
|
+
(isBaldartManagedPath(p, extraPatterns) ? managed : userOwned).push(p);
|
|
213
259
|
}
|
|
214
260
|
if (managed.length === 0) {
|
|
215
261
|
return;
|