baldart 5.0.0 → 5.1.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 +119 -0
- package/README.md +3 -3
- package/VERSION +1 -1
- package/framework/.claude/agents/CHANGELOG.md +68 -0
- package/framework/.claude/agents/REGISTRY.md +3 -3
- package/framework/.claude/agents/coder.md +4 -5
- package/framework/.claude/agents/doc-reviewer.md +3 -2
- package/framework/.claude/agents/prd-card-writer.md +1 -1
- package/framework/.claude/agents/qa-sentinel.md +4 -4
- package/framework/.claude/agents/senior-researcher.md +166 -151
- package/framework/.claude/commands/codexreview.md +26 -4
- package/framework/.claude/skills/new/CHANGELOG.md +28 -0
- package/framework/.claude/skills/new/SKILL.md +1 -1
- package/framework/.claude/skills/new/references/implement.md +8 -4
- package/framework/.claude/skills/new/references/review-cycle.md +8 -5
- package/framework/.claude/skills/new/scripts/build-review-bundle.mjs +13 -5
- package/framework/.claude/skills/new/scripts/doc-invariants.mjs +6 -3
- package/framework/.claude/skills/new2/CHANGELOG.md +9 -0
- package/framework/.claude/skills/new2/SKILL.md +1 -1
- 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/workflows/new-card-review.js +16 -2
- package/framework/.claude/workflows/new2-resolve.js +1 -1
- package/framework/.claude/workflows/new2.js +1 -1
- package/framework/agents/coding-standards.md +2 -2
- package/framework/agents/doc-audit-protocol.md +9 -4
- package/framework/agents/index.md +2 -0
- package/framework/agents/research-protocol.md +228 -0
- package/framework/agents/skills-mapping.md +17 -0
- package/framework/docs/PROJECT-CONFIGURATION.md +23 -0
- package/framework/docs/UPGRADE-3.12-UI-COHERENCE.md +1 -1
- 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 +26 -0
- package/src/commands/doctor.js +82 -0
- package/src/commands/update.js +48 -1
- package/src/utils/research-library.js +92 -0
- package/src/utils/symlinks.js +3 -0
|
@@ -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' : '',
|
|
@@ -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');
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Research library scaffolding (v5.1.0).
|
|
3
|
+
*
|
|
4
|
+
* Creates/seeds the consumer-owned research library at `paths.research_dir`:
|
|
5
|
+
* category subdirectories + two seed files copied from framework-internal
|
|
6
|
+
* templates (INDEX.md — the library map; SOURCES.md — the live source matrix).
|
|
7
|
+
*
|
|
8
|
+
* Lifecycle ownership (deliberate, see research-protocol.md):
|
|
9
|
+
* - `configure` CREATES (right after writing the key) — createDir: true
|
|
10
|
+
* - `doctor` REPAIRS (confirmable actions) — createDir: true
|
|
11
|
+
* - `update` only BACKFILLS seeds into an EXISTING dir — createDir: false
|
|
12
|
+
* (never resurrects a deliberately deleted library)
|
|
13
|
+
*
|
|
14
|
+
* Idempotent and silent on already-existing artifacts (no per-run warnings).
|
|
15
|
+
* Returns the repo-relative paths it created THIS run, so `update` can scope
|
|
16
|
+
* its auto-commit to exactly these artifacts (v4.89.2 lesson) while the rest
|
|
17
|
+
* of the library stays consumer-owned and untouched.
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
const fs = require('fs');
|
|
21
|
+
const path = require('path');
|
|
22
|
+
|
|
23
|
+
const FRAMEWORK_TEMPLATES = path.join('.framework', 'framework', 'templates');
|
|
24
|
+
|
|
25
|
+
const CATEGORY_DIRS = [
|
|
26
|
+
'architecture',
|
|
27
|
+
'integrations',
|
|
28
|
+
'regulatory',
|
|
29
|
+
'ux-patterns',
|
|
30
|
+
'algorithms',
|
|
31
|
+
'tooling',
|
|
32
|
+
'_archive',
|
|
33
|
+
];
|
|
34
|
+
|
|
35
|
+
const SEEDS = [
|
|
36
|
+
{ template: 'research-index.template.md', dest: 'INDEX.md' },
|
|
37
|
+
{ template: 'research-sources.template.md', dest: 'SOURCES.md' },
|
|
38
|
+
];
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* @param {string} cwd - consumer repo root
|
|
42
|
+
* @param {object|null} config - parsed baldart.config.yml
|
|
43
|
+
* @param {{createDir?: boolean}} [opts]
|
|
44
|
+
* @returns {string[]} repo-relative paths created this run
|
|
45
|
+
*/
|
|
46
|
+
function ensureResearchLibrary(cwd, config, opts = {}) {
|
|
47
|
+
const createDir = opts.createDir !== false;
|
|
48
|
+
const created = [];
|
|
49
|
+
|
|
50
|
+
const key = config && config.paths && config.paths.research_dir;
|
|
51
|
+
if (!key || typeof key !== 'string') return created; // opt-out / unconfigured — no-op
|
|
52
|
+
|
|
53
|
+
const dirAbs = path.join(cwd, key);
|
|
54
|
+
if (!fs.existsSync(dirAbs)) {
|
|
55
|
+
if (!createDir) return created; // update path: never resurrect a deleted library
|
|
56
|
+
fs.mkdirSync(dirAbs, { recursive: true });
|
|
57
|
+
created.push(key);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
for (const sub of CATEGORY_DIRS) {
|
|
61
|
+
const subAbs = path.join(dirAbs, sub);
|
|
62
|
+
if (!fs.existsSync(subAbs)) fs.mkdirSync(subAbs, { recursive: true });
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
for (const seed of SEEDS) {
|
|
66
|
+
const destAbs = path.join(dirAbs, seed.dest);
|
|
67
|
+
if (fs.existsSync(destAbs)) continue; // consumer-owned — never overwrite, never warn
|
|
68
|
+
const srcAbs = path.join(cwd, FRAMEWORK_TEMPLATES, seed.template);
|
|
69
|
+
if (!fs.existsSync(srcAbs)) continue; // older framework payload — doctor backfills later
|
|
70
|
+
fs.copyFileSync(srcAbs, destAbs);
|
|
71
|
+
created.push(path.join(key, seed.dest));
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
return created;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Parse the `matrix_version:` frontmatter value from a source-matrix file.
|
|
79
|
+
* Returns null when the file is missing/unreadable or carries no version —
|
|
80
|
+
* callers treat null as "cannot compare" (never a failure).
|
|
81
|
+
*/
|
|
82
|
+
function readMatrixVersion(fileAbs) {
|
|
83
|
+
try {
|
|
84
|
+
const head = fs.readFileSync(fileAbs, 'utf8').slice(0, 500);
|
|
85
|
+
const m = head.match(/^matrix_version:\s*["']?(\d+\.\d+\.\d+)["']?\s*$/m);
|
|
86
|
+
return m ? m[1] : null;
|
|
87
|
+
} catch {
|
|
88
|
+
return null;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
module.exports = { ensureResearchLibrary, readMatrixVersion, CATEGORY_DIRS };
|
package/src/utils/symlinks.js
CHANGED
|
@@ -948,6 +948,9 @@ class SymlinkUtils {
|
|
|
948
948
|
'skill-project-context.snippet.md',
|
|
949
949
|
'component-spec.template.md', // canonical prose-body template — consumed by serialize-spec.mjs, not hand-edited
|
|
950
950
|
'overlays', // example overlays directory — consumers reference, don't copy wholesale
|
|
951
|
+
'research-index.template.md', // research library seed — copied to ${paths.research_dir}/INDEX.md by ensureResearchLibrary
|
|
952
|
+
'research-sources.template.md', // source matrix seed + default-matrix SSOT — copied to ${paths.research_dir}/SOURCES.md
|
|
953
|
+
'research-sources.CHANGELOG.md', // matrix version history — framework metadata, never installed
|
|
951
954
|
]);
|
|
952
955
|
|
|
953
956
|
this.ensureDirectory('templates');
|