basalt-ui 1.40.3 → 1.41.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/AGENTS.md +4 -2
- package/CHANGELOG.md +13 -0
- package/MIGRATING.md +36 -12
- package/README.md +1 -1
- package/agent/rules/basalt-batteries.md +1 -1
- package/agent/skills/basalt-app/SKILL.md +1 -1
- package/agent/skills/basalt-design/SKILL.md +1 -1
- package/agent/templates/DESIGN.md.tpl +1 -1
- package/configs/check.yml +1 -1
- package/configs/oxlint-plugin.js +5 -5
- package/configs/oxlint-plugin.test.ts +1 -1
- package/dist/agent/foreign.d.ts +3 -2
- package/dist/agent/foreign.d.ts.map +1 -1
- package/dist/cli/doctor.d.ts +5 -2
- package/dist/cli/doctor.d.ts.map +1 -1
- package/dist/cli/doctor.js +29 -0
- package/dist/cli/index.d.ts +61 -4
- package/dist/cli/index.d.ts.map +1 -1
- package/dist/cli/index.js +112 -6
- package/dist/cli/init.d.ts.map +1 -1
- package/dist/cli/init.js +10 -0
- package/dist/cli/sync.d.ts.map +1 -1
- package/dist/cli/sync.js +18 -1
- package/dist/content/copy-action.d.ts +1 -1
- package/dist/content/glyphs.d.ts +1 -1
- package/dist/data/virtual-list.d.ts +1 -1
- package/llms.txt +2 -2
- package/package.json +1 -1
- package/src/agent/foreign.ts +3 -2
- package/src/charts/index.ts +2 -2
- package/src/cli/doctor.ts +47 -4
- package/src/cli/index.ts +180 -11
- package/src/cli/init.ts +14 -0
- package/src/cli/sync.ts +24 -1
- package/src/content/copy-action.tsx +1 -1
- package/src/content/glyphs.tsx +1 -1
- package/src/data/virtual-list.tsx +1 -1
- package/src/guard/index.ts +1 -1
- /package/agent/templates/{CLAUDE-block.md.tpl → AGENTS-block.md.tpl} +0 -0
package/dist/cli/index.js
CHANGED
|
@@ -7,6 +7,7 @@ import {
|
|
|
7
7
|
readFileSync,
|
|
8
8
|
rmdirSync,
|
|
9
9
|
statSync,
|
|
10
|
+
realpathSync,
|
|
10
11
|
unlinkSync,
|
|
11
12
|
writeFileSync
|
|
12
13
|
} from "node:fs";
|
|
@@ -233,6 +234,8 @@ function waiverHintFor(relPath, profile) {
|
|
|
233
234
|
const BLOCK_BEGIN_PREFIX = "<!-- basalt:begin";
|
|
234
235
|
const BLOCK_END = "<!-- basalt:end -->";
|
|
235
236
|
const MANIFEST_PATH = ".basalt/manifest.json";
|
|
237
|
+
const AGENTS_MD = "AGENTS.md";
|
|
238
|
+
const LEGACY_BLOCK_HOST = "CLAUDE.md";
|
|
236
239
|
function sha256(text) {
|
|
237
240
|
return createHash("sha256").update(text, "utf8").digest("hex");
|
|
238
241
|
}
|
|
@@ -308,6 +311,91 @@ function applyBlock(host, block) {
|
|
|
308
311
|
${block.trim()}
|
|
309
312
|
`;
|
|
310
313
|
}
|
|
314
|
+
function sameFile(a, b) {
|
|
315
|
+
try {
|
|
316
|
+
return realpathSync(a) === realpathSync(b);
|
|
317
|
+
} catch (error) {
|
|
318
|
+
if (error.code === "ENOENT") return false;
|
|
319
|
+
throw error;
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
function hasManagedBlock(host) {
|
|
323
|
+
return host !== null && host.includes(BLOCK_BEGIN_PREFIX);
|
|
324
|
+
}
|
|
325
|
+
function scanBlock(host) {
|
|
326
|
+
const begin = hasManagedBlock(host);
|
|
327
|
+
if (!begin && !host.includes(BLOCK_END)) return { region: null, problem: null };
|
|
328
|
+
const bad = (problem) => ({ region: null, problem });
|
|
329
|
+
if (!begin) return bad(`\`${BLOCK_END}\` with no \`${BLOCK_BEGIN_PREFIX}\``);
|
|
330
|
+
let region;
|
|
331
|
+
try {
|
|
332
|
+
region = findBlockRegion(host);
|
|
333
|
+
} catch (error) {
|
|
334
|
+
return bad(error.message);
|
|
335
|
+
}
|
|
336
|
+
if (region === null) return bad(`\`${BLOCK_BEGIN_PREFIX}\` has no matching \`${BLOCK_END}\``);
|
|
337
|
+
if (host.indexOf(BLOCK_END) < region.start || host.indexOf(BLOCK_END, region.end) !== -1) {
|
|
338
|
+
return bad(`an extra or misplaced \`${BLOCK_END}\` outside the basalt block`);
|
|
339
|
+
}
|
|
340
|
+
return { region, problem: null };
|
|
341
|
+
}
|
|
342
|
+
function isAgentsImportLine(line) {
|
|
343
|
+
return line.trim() === `@${AGENTS_MD}`;
|
|
344
|
+
}
|
|
345
|
+
function importsAgentsMd(text) {
|
|
346
|
+
return text.split(/\r?\n/).some(isAgentsImportLine);
|
|
347
|
+
}
|
|
348
|
+
function migrateBlockToAgentsMd(cwd, manifest, opts = {}) {
|
|
349
|
+
const legacyHash = manifest.files[LEGACY_BLOCK_HOST];
|
|
350
|
+
if (legacyHash !== void 0) {
|
|
351
|
+
manifest.files[AGENTS_MD] ??= legacyHash;
|
|
352
|
+
delete manifest.files[LEGACY_BLOCK_HOST];
|
|
353
|
+
}
|
|
354
|
+
const none = { pending: false, deletedHost: false, problem: null };
|
|
355
|
+
const legacyAbs = resolve(cwd, LEGACY_BLOCK_HOST);
|
|
356
|
+
const agentsAbs = resolve(cwd, AGENTS_MD);
|
|
357
|
+
const legacy = readIfExists(legacyAbs);
|
|
358
|
+
if (legacy === null) return none;
|
|
359
|
+
try {
|
|
360
|
+
if (sameFile(legacyAbs, agentsAbs)) return none;
|
|
361
|
+
} catch (error) {
|
|
362
|
+
const why = error.code ?? error.message;
|
|
363
|
+
return { ...none, problem: `cannot tell whether ${LEGACY_BLOCK_HOST} is ${AGENTS_MD} (${why})` };
|
|
364
|
+
}
|
|
365
|
+
const legacyScan = scanBlock(legacy);
|
|
366
|
+
const { region } = legacyScan;
|
|
367
|
+
if (legacyScan.problem !== null) {
|
|
368
|
+
return { ...none, problem: `${LEGACY_BLOCK_HOST}: ${legacyScan.problem}` };
|
|
369
|
+
}
|
|
370
|
+
if (region === null) return none;
|
|
371
|
+
const agents = readIfExists(agentsAbs) ?? "";
|
|
372
|
+
const agentsScan = scanBlock(agents);
|
|
373
|
+
if (agentsScan.problem !== null)
|
|
374
|
+
return { ...none, problem: `${AGENTS_MD}: ${agentsScan.problem}` };
|
|
375
|
+
const legacyBlock = legacy.slice(region.start, region.end);
|
|
376
|
+
if (agentsScan.region !== null && normalizeForLedger(agents.slice(agentsScan.region.start, agentsScan.region.end)) !== normalizeForLedger(legacyBlock)) {
|
|
377
|
+
return {
|
|
378
|
+
...none,
|
|
379
|
+
problem: `${LEGACY_BLOCK_HOST} and ${AGENTS_MD} each carry a basalt block and they differ \u2014 delete the stale block from ${LEGACY_BLOCK_HOST} (or the one in ${AGENTS_MD}, if ${LEGACY_BLOCK_HOST}'s is the one you want kept), then re-run.`
|
|
380
|
+
};
|
|
381
|
+
}
|
|
382
|
+
const before = legacy.slice(0, region.start).trimEnd();
|
|
383
|
+
const after = legacy.slice(region.end).trimStart();
|
|
384
|
+
const remainder = before === "" || after === "" ? `${before}${after}` : `${before}
|
|
385
|
+
|
|
386
|
+
${after}`;
|
|
387
|
+
const deletedHost = remainder.split(/\r?\n/).every((l) => l.trim() === "" || isAgentsImportLine(l));
|
|
388
|
+
if (opts.dryRun === true) return { pending: true, deletedHost, problem: null };
|
|
389
|
+
if (agentsScan.region === null) writeFileEnsuringDir(agentsAbs, applyBlock(agents, legacyBlock));
|
|
390
|
+
if (deletedHost) unlinkSync(legacyAbs);
|
|
391
|
+
else writeFileSync(legacyAbs, `${remainder.trimEnd()}
|
|
392
|
+
`);
|
|
393
|
+
return { pending: true, deletedHost, problem: null };
|
|
394
|
+
}
|
|
395
|
+
function blockMigrationProblem(cwd) {
|
|
396
|
+
const { problem } = migrateBlockToAgentsMd(cwd, { version: 1, files: {} }, { dryRun: true });
|
|
397
|
+
return problem === null ? null : `${problem.replace(/\.$/, "")}. Nothing else was reconciled \u2014 resolve this by hand, then re-run.`;
|
|
398
|
+
}
|
|
311
399
|
function hasDependency(cwd, pkgName) {
|
|
312
400
|
try {
|
|
313
401
|
const pkg = JSON.parse(readFileSync(resolve(cwd, "package.json"), "utf8"));
|
|
@@ -397,15 +485,15 @@ function managedFiles(peers, placement = ROOT_PLACEMENT) {
|
|
|
397
485
|
source: `agent/skills/${name}/SKILL.md`,
|
|
398
486
|
render: (ctx) => readSource(ctx.pkgRoot, `agent/skills/${name}/SKILL.md`)
|
|
399
487
|
}));
|
|
400
|
-
const
|
|
401
|
-
dest:
|
|
488
|
+
const agentsBlock = {
|
|
489
|
+
dest: AGENTS_MD,
|
|
402
490
|
mode: "managed",
|
|
403
491
|
markers: true,
|
|
404
|
-
source: "agent/templates/
|
|
492
|
+
source: "agent/templates/AGENTS-block.md.tpl",
|
|
405
493
|
// With markers, render() returns the fully-rendered region INCLUDING its begin/end markers.
|
|
406
|
-
// The writer splices it into the consumer's host
|
|
494
|
+
// The writer splices it into the consumer's host AGENTS.md at apply time.
|
|
407
495
|
render: (ctx) => {
|
|
408
|
-
const tpl = readSource(ctx.pkgRoot, "agent/templates/
|
|
496
|
+
const tpl = readSource(ctx.pkgRoot, "agent/templates/AGENTS-block.md.tpl");
|
|
409
497
|
if (tpl === null) return null;
|
|
410
498
|
return fillTemplate(tpl, ctx.vars).trim();
|
|
411
499
|
}
|
|
@@ -498,7 +586,7 @@ pre-commit:
|
|
|
498
586
|
if (peers.hasQuery && placement.relocatedQueryClient === null) scaffolds.push(queryClient);
|
|
499
587
|
if (peers.hasRouter && peers.hasQuery) scaffolds.push(rootRoute);
|
|
500
588
|
const rootTooling = placement.isPackageRepoRoot ? [lefthook, ci] : [];
|
|
501
|
-
return [...rules, ...skills,
|
|
589
|
+
return [...rules, ...skills, agentsBlock, design, oxfmt, ...rootTooling, oxlintrc, ...scaffolds];
|
|
502
590
|
}
|
|
503
591
|
function placementNotices(cwd, placement) {
|
|
504
592
|
const out = [];
|
|
@@ -558,6 +646,14 @@ Retired but locally edited, so left in place \u2014 your agent still loads them.
|
|
|
558
646
|
` + pruned.drifted.map((dest) => ` \xB7 ${dest}`).join("\n")
|
|
559
647
|
);
|
|
560
648
|
}
|
|
649
|
+
function reportBlockMigration(migration, command) {
|
|
650
|
+
if (!migration.pending) return;
|
|
651
|
+
const tail = migration.deletedHost ? `${LEGACY_BLOCK_HOST} held nothing else and was deleted.` : `the rest of ${LEGACY_BLOCK_HOST} is yours; while it exists Claude Code loads it INSTEAD of ${AGENTS_MD} unless it imports @${AGENTS_MD} \u2014 move your content over and delete it.`;
|
|
652
|
+
console.log(
|
|
653
|
+
`
|
|
654
|
+
basalt-ui ${command}: moved the basalt block from ${LEGACY_BLOCK_HOST} to ${AGENTS_MD} \u2014 ${tail}`
|
|
655
|
+
);
|
|
656
|
+
}
|
|
561
657
|
function pruneRetiredManagedFiles(cwd, manifest, files, opts = {}) {
|
|
562
658
|
const result = { removed: [], drifted: [] };
|
|
563
659
|
for (const dest of retiredManagedDests(manifest, files)) {
|
|
@@ -944,6 +1040,8 @@ function run(argv, cwd = process.cwd()) {
|
|
|
944
1040
|
}
|
|
945
1041
|
}
|
|
946
1042
|
export {
|
|
1043
|
+
AGENTS_MD,
|
|
1044
|
+
LEGACY_BLOCK_HOST,
|
|
947
1045
|
MANIFEST_PATH,
|
|
948
1046
|
OXLINT_FILES,
|
|
949
1047
|
OXLINT_NO_BIN,
|
|
@@ -954,6 +1052,7 @@ export {
|
|
|
954
1052
|
SKIP,
|
|
955
1053
|
basaltBinCommand,
|
|
956
1054
|
basaltPresetEntry,
|
|
1055
|
+
blockMigrationProblem,
|
|
957
1056
|
checkTheme,
|
|
958
1057
|
classify,
|
|
959
1058
|
conflictingProfileFlags,
|
|
@@ -966,11 +1065,15 @@ export {
|
|
|
966
1065
|
fontsCss,
|
|
967
1066
|
guardHook,
|
|
968
1067
|
hasBasaltKey,
|
|
1068
|
+
hasManagedBlock,
|
|
1069
|
+
importsAgentsMd,
|
|
969
1070
|
init,
|
|
970
1071
|
inspectLefthookGate,
|
|
1072
|
+
isAgentsImportLine,
|
|
971
1073
|
isStringArray,
|
|
972
1074
|
managedFiles,
|
|
973
1075
|
mergeOxlintExtends,
|
|
1076
|
+
migrateBlockToAgentsMd,
|
|
974
1077
|
namesShipped,
|
|
975
1078
|
normalizeColorFunctions,
|
|
976
1079
|
normalizeForLedger,
|
|
@@ -989,6 +1092,7 @@ export {
|
|
|
989
1092
|
reconcileRoots,
|
|
990
1093
|
relativePosix,
|
|
991
1094
|
renderContext,
|
|
1095
|
+
reportBlockMigration,
|
|
992
1096
|
reportPrune,
|
|
993
1097
|
resolveGuardConfig,
|
|
994
1098
|
resolvePeerFlags,
|
|
@@ -997,6 +1101,8 @@ export {
|
|
|
997
1101
|
retiredManagedDests,
|
|
998
1102
|
rootsNotices,
|
|
999
1103
|
run,
|
|
1104
|
+
sameFile,
|
|
1105
|
+
scanBlock,
|
|
1000
1106
|
scannableFiles,
|
|
1001
1107
|
sha256,
|
|
1002
1108
|
shippedAssetPath,
|
package/dist/cli/init.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"init.d.ts","sourceRoot":"","sources":["../../src/cli/init.ts"],"names":[],"mappings":"AASA,OAAO,KAAK,EAAmB,aAAa,EAAE,MAAM,SAAS,CAAA;
|
|
1
|
+
{"version":3,"file":"init.d.ts","sourceRoot":"","sources":["../../src/cli/init.ts"],"names":[],"mappings":"AASA,OAAO,KAAK,EAAmB,aAAa,EAAE,MAAM,SAAS,CAAA;AA8C7D,yGAAyG;AACzG,wBAAgB,IAAI,CAAC,GAAG,GAAE,MAAsB,EAAE,aAAa,GAAE,aAAkB,GAAG,MAAM,CAuL3F"}
|
package/dist/cli/init.js
CHANGED
|
@@ -3,9 +3,11 @@ import { resolve } from "node:path";
|
|
|
3
3
|
import { deriveSpacing } from "../tokens/index.js";
|
|
4
4
|
import {
|
|
5
5
|
MANIFEST_PATH,
|
|
6
|
+
blockMigrationProblem,
|
|
6
7
|
findBasaltInstall,
|
|
7
8
|
managedFiles,
|
|
8
9
|
mergeOxlintExtends,
|
|
10
|
+
migrateBlockToAgentsMd,
|
|
9
11
|
normalizeForLedger,
|
|
10
12
|
patchPackageJson,
|
|
11
13
|
placementNotices,
|
|
@@ -13,6 +15,7 @@ import {
|
|
|
13
15
|
readManifest,
|
|
14
16
|
reconcileRoots,
|
|
15
17
|
renderContext,
|
|
18
|
+
reportBlockMigration,
|
|
16
19
|
reportPrune,
|
|
17
20
|
resolvePeerFlags,
|
|
18
21
|
resolvePlacement,
|
|
@@ -31,6 +34,11 @@ const KEPT_FILE_COST = {
|
|
|
31
34
|
"DESIGN.md": "the design brief Claude reads is yours, not the seeded one."
|
|
32
35
|
};
|
|
33
36
|
function init(cwd = process.cwd(), scaffoldFlags = {}) {
|
|
37
|
+
const blockProblem = blockMigrationProblem(cwd);
|
|
38
|
+
if (blockProblem !== null) {
|
|
39
|
+
console.error(`\u2716 basalt-ui init: ${blockProblem}`);
|
|
40
|
+
return 1;
|
|
41
|
+
}
|
|
34
42
|
const install = findBasaltInstall(cwd);
|
|
35
43
|
if (install.dir === null) {
|
|
36
44
|
console.error(
|
|
@@ -50,6 +58,7 @@ function init(cwd = process.cwd(), scaffoldFlags = {}) {
|
|
|
50
58
|
const peers = resolvePeerFlags(cwd, scaffoldFlags);
|
|
51
59
|
const placement = resolvePlacement(cwd);
|
|
52
60
|
const files = managedFiles(peers, placement);
|
|
61
|
+
const migration = migrateBlockToAgentsMd(cwd, manifest);
|
|
53
62
|
const writtenFiles = [];
|
|
54
63
|
const keptFiles = [];
|
|
55
64
|
const missingSources = [];
|
|
@@ -79,6 +88,7 @@ function init(cwd = process.cwd(), scaffoldFlags = {}) {
|
|
|
79
88
|
`basalt-ui init: ${writtenFiles.length} written, ${keptFiles.length} kept, manifest at ${MANIFEST_PATH}`
|
|
80
89
|
);
|
|
81
90
|
reportPrune(pruned);
|
|
91
|
+
reportBlockMigration(migration, "init");
|
|
82
92
|
if (missingSources.length > 0) {
|
|
83
93
|
console.log(
|
|
84
94
|
`basalt-ui init: ${missingSources.length} shipped asset(s) not present, skipped: ${missingSources.join(", ")}`
|
package/dist/cli/sync.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sync.d.ts","sourceRoot":"","sources":["../../src/cli/sync.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"sync.d.ts","sourceRoot":"","sources":["../../src/cli/sync.ts"],"names":[],"mappings":"AAmDA,gGAAgG;AAChG,wBAAgB,uBAAuB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,EAAE,CAGjE;AAED,MAAM,MAAM,WAAW,GAAG;IACxB,KAAK,CAAC,EAAE,OAAO,CAAA;IACf,KAAK,CAAC,EAAE,OAAO,CAAA;IACf,mGAAmG;IACnG,KAAK,CAAC,EAAE,SAAS,MAAM,EAAE,CAAA;CAC1B,CAAA;AAED;;;;;;;;;GASG;AACH;;;;;;;GAOG;AACH,wBAAgB,mBAAmB,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,CAKrF;AAED,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAQ5D;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,IAAI,CAAC,IAAI,GAAE,WAAgB,EAAE,aAAa,GAAE,MAAsB,GAAG,MAAM,CAsN1F"}
|
package/dist/cli/sync.js
CHANGED
|
@@ -10,8 +10,12 @@ import {
|
|
|
10
10
|
diffSummary,
|
|
11
11
|
findBasaltInstall,
|
|
12
12
|
inspectLefthookGate,
|
|
13
|
+
AGENTS_MD,
|
|
14
|
+
blockMigrationProblem,
|
|
13
15
|
isStringArray,
|
|
16
|
+
LEGACY_BLOCK_HOST,
|
|
14
17
|
managedFiles,
|
|
18
|
+
migrateBlockToAgentsMd,
|
|
15
19
|
normalizeForLedger,
|
|
16
20
|
noteRelocation,
|
|
17
21
|
OXLINT_FILES,
|
|
@@ -25,6 +29,7 @@ import {
|
|
|
25
29
|
reconcileRoots,
|
|
26
30
|
relativePosix,
|
|
27
31
|
renderContext,
|
|
32
|
+
reportBlockMigration,
|
|
28
33
|
reportPrune,
|
|
29
34
|
resolvePeerFlags,
|
|
30
35
|
resolvePlacement,
|
|
@@ -72,12 +77,18 @@ function sync(opts = {}, invocationCwd = process.cwd()) {
|
|
|
72
77
|
);
|
|
73
78
|
return 1;
|
|
74
79
|
}
|
|
80
|
+
const blockProblem = blockMigrationProblem(cwd);
|
|
81
|
+
if (blockProblem !== null) {
|
|
82
|
+
console.error(`\u2716 basalt-ui sync: ${blockProblem}`);
|
|
83
|
+
return 1;
|
|
84
|
+
}
|
|
75
85
|
const rootsState = reconcileRoots(cwd, { write: opts.check !== true });
|
|
76
86
|
const ctx = renderContext(cwd);
|
|
77
87
|
const manifest = readManifest(cwd);
|
|
78
88
|
const peers = resolvePeerFlags(cwd, {});
|
|
79
89
|
const placement = resolvePlacement(cwd);
|
|
80
90
|
const files = managedFiles(peers, placement);
|
|
91
|
+
const migration = migrateBlockToAgentsMd(cwd, manifest, { dryRun: opts.check === true });
|
|
81
92
|
let updated = 0;
|
|
82
93
|
let created = 0;
|
|
83
94
|
let recreated = 0;
|
|
@@ -155,10 +166,15 @@ function sync(opts = {}, invocationCwd = process.cwd()) {
|
|
|
155
166
|
`basalt-ui sync --check: ${retired.length} retired rule/skill file(s) still present: ${retired.join(", ")} \u2014 run \`basalt-ui sync\`.`
|
|
156
167
|
);
|
|
157
168
|
}
|
|
169
|
+
if (migration.pending) {
|
|
170
|
+
console.error(
|
|
171
|
+
`basalt-ui sync --check: the basalt block is still in ${LEGACY_BLOCK_HOST} \u2014 it now lives in ${AGENTS_MD}. Run \`basalt-ui sync\` to move it.`
|
|
172
|
+
);
|
|
173
|
+
}
|
|
158
174
|
if (staleForCheck > 0) {
|
|
159
175
|
console.error(`basalt-ui sync --check: ${staleForCheck} managed file(s) out of date.`);
|
|
160
|
-
return 1;
|
|
161
176
|
}
|
|
177
|
+
if (staleForCheck > 0 || migration.pending) return 1;
|
|
162
178
|
const n = placementSkips.length;
|
|
163
179
|
const skips = n === 0 ? "" : ` (${n} permanent placement skip(s) \u2014 --verbose for detail)`;
|
|
164
180
|
console.log(`\u2713 basalt-ui sync --check: all managed files current.${skips}`);
|
|
@@ -172,6 +188,7 @@ function sync(opts = {}, invocationCwd = process.cwd()) {
|
|
|
172
188
|
`basalt-ui sync: ${updated} updated, ${created} created, ${recreated} recreated, ${skippedDrift} skipped (drift), ${pruned.removed.length} retired.`
|
|
173
189
|
);
|
|
174
190
|
reportPrune(pruned);
|
|
191
|
+
reportBlockMigration(migration, "sync");
|
|
175
192
|
if (driftLines.length > 0) {
|
|
176
193
|
console.log("Locally-edited files were skipped (run with --force to overwrite):");
|
|
177
194
|
for (const l of driftLines) console.log(l);
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
*
|
|
7
7
|
* The check + tooltip are the entire success signal — no notification (docs/CONTENT-SPEC.md §7).
|
|
8
8
|
* `color="teal"` resolves through the bridged Mantine family, so it stays on the token layer in
|
|
9
|
-
* both schemes (see the package
|
|
9
|
+
* both schemes (see the package MAINTAINING.md on `cssVariablesResolver`).
|
|
10
10
|
*
|
|
11
11
|
* Not part of the public surface — reach it through `CodeBlock` / `HeadingAnchor`.
|
|
12
12
|
*/
|
package/dist/content/glyphs.d.ts
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* hand-drawn ones that drift apart.
|
|
5
5
|
*
|
|
6
6
|
* Inline SVG rather than an icon dep: the framework takes icons as `ReactNode` from the consumer
|
|
7
|
-
* (see the package
|
|
7
|
+
* (see the package MAINTAINING.md) and ships none, but these are internal chrome of a shipped component,
|
|
8
8
|
* not a consumer-facing slot. `currentColor` + `1em`-agnostic sizing keeps them steerable from CSS
|
|
9
9
|
* — the heading anchor sizes its glyph as an optical ratio of whatever step the heading lands on
|
|
10
10
|
* (docs/DESIGN-SPEC.md §3), while the code-block chrome pins a fixed 14px.
|
|
@@ -103,7 +103,7 @@ export type BasaltVirtualListHandle = {
|
|
|
103
103
|
/**
|
|
104
104
|
* The underlying `@tanstack/react-virtual` instance — the full escape hatch. Returns the RAW
|
|
105
105
|
* `@tanstack/react-virtual` `Virtualizer`, so a future major of that library reaching this shape
|
|
106
|
-
* ships here as a plain `feat:` under the no-majors doctrine (
|
|
106
|
+
* ships here as a plain `feat:` under the no-majors doctrine (root `AGENTS.md` "Commit type
|
|
107
107
|
* discipline"), not a silent break.
|
|
108
108
|
*/
|
|
109
109
|
getVirtualizer: () => Virtualizer<HTMLDivElement, Element>;
|
package/llms.txt
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# basalt-ui v1.
|
|
1
|
+
# basalt-ui v1.41.0
|
|
2
2
|
# Opinionated framework for Mantine-based React apps — theme, app shell, visx charts, toolchain presets
|
|
3
3
|
#
|
|
4
4
|
# basalt-ui is an opinionated framework for Mantine-based React apps.
|
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
# ./tokens.css subpath. Guide: docs/FRAMEWORK-FREE.md in the repo.
|
|
20
20
|
|
|
21
21
|
Package: basalt-ui
|
|
22
|
-
Version: 1.
|
|
22
|
+
Version: 1.41.0
|
|
23
23
|
|
|
24
24
|
## basalt-ui
|
|
25
25
|
Import: import { ... } from 'basalt-ui'
|
package/package.json
CHANGED
package/src/agent/foreign.ts
CHANGED
|
@@ -97,8 +97,9 @@ export type PartRenderContext<TPart = ForeignPart> = {
|
|
|
97
97
|
export type PartRenderer<TPart = ForeignPart> = (ctx: PartRenderContext<TPart>) => ReactNode
|
|
98
98
|
|
|
99
99
|
/**
|
|
100
|
-
* Canonical const-generic factory (`packages/basalt-ui/
|
|
101
|
-
*
|
|
100
|
+
* Canonical const-generic factory (`packages/basalt-ui/MAINTAINING.md` § "Token system — the
|
|
101
|
+
* derived palette", "Series color is consumer data") — identity passthrough, exact-keyed return,
|
|
102
|
+
* no builder, no config bag. Mirrors
|
|
102
103
|
* `defineSeries` (`tokens/index.ts:648-650`) and `defineNotifications`
|
|
103
104
|
* (`notifications/define-notifications.ts:112-115`).
|
|
104
105
|
*
|
package/src/charts/index.ts
CHANGED
|
@@ -166,8 +166,8 @@ export {
|
|
|
166
166
|
} from './sparklines'
|
|
167
167
|
|
|
168
168
|
// ── Re-exported visx primitives ──────────────────────────────────────────
|
|
169
|
-
// Bespoke charts (genuinely unique compositions per
|
|
170
|
-
// visx primitives. Re-exporting them keeps the dependency declared in one
|
|
169
|
+
// Bespoke charts (genuinely unique compositions, per `agent/rules/basalt-charts.md` "Adding a
|
|
170
|
+
// chart") need raw visx primitives. Re-exporting them keeps the dependency declared in one
|
|
171
171
|
// place and preserves the rule that consumers only import from `basalt-ui/charts`.
|
|
172
172
|
export { Group } from '@visx/group'
|
|
173
173
|
export { GridRows, GridColumns } from '@visx/grid'
|
package/src/cli/doctor.ts
CHANGED
|
@@ -8,6 +8,8 @@ import { relative, resolve } from 'node:path'
|
|
|
8
8
|
|
|
9
9
|
import type { BasaltConfig } from './config'
|
|
10
10
|
import {
|
|
11
|
+
AGENTS_MD,
|
|
12
|
+
LEGACY_BLOCK_HOST,
|
|
11
13
|
MANIFEST_PATH,
|
|
12
14
|
basaltBinCommand,
|
|
13
15
|
basaltPresetEntry,
|
|
@@ -15,6 +17,7 @@ import {
|
|
|
15
17
|
declaredProfile,
|
|
16
18
|
findBasaltInstall,
|
|
17
19
|
findRepoRoot,
|
|
20
|
+
importsAgentsMd,
|
|
18
21
|
inspectLefthookGate,
|
|
19
22
|
OXLINT_FILES,
|
|
20
23
|
OXLINT_NO_BIN,
|
|
@@ -24,12 +27,23 @@ import {
|
|
|
24
27
|
readIfExists,
|
|
25
28
|
readManifest,
|
|
26
29
|
resolveProjectDir,
|
|
30
|
+
sameFile,
|
|
27
31
|
scannableFiles,
|
|
32
|
+
scanBlock,
|
|
28
33
|
shippedAssetPath,
|
|
29
34
|
} from './index'
|
|
30
35
|
import { resolveRoots } from './config'
|
|
31
36
|
import { findManifestAbove, parentInstallAdvice } from './sync'
|
|
32
37
|
|
|
38
|
+
/** `sameFile`, but doctor never crashes: a path that cannot be inspected is "not provably the same". */
|
|
39
|
+
function isSameFile(a: string, b: string): boolean {
|
|
40
|
+
try {
|
|
41
|
+
return sameFile(a, b)
|
|
42
|
+
} catch {
|
|
43
|
+
return false
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
33
47
|
export type DoctorResult = {
|
|
34
48
|
/** Number of hard failures (exit non-zero). */
|
|
35
49
|
hardFailures: number
|
|
@@ -108,7 +122,7 @@ function resolveAiMajorSkewReason(cfg: BasaltConfig): {
|
|
|
108
122
|
* Check a consumer repo's basalt integration and print a pass/warn report.
|
|
109
123
|
*
|
|
110
124
|
* Narrowed to what a single-directory-scoped run can actually verify — no workspace-glob walking,
|
|
111
|
-
* no ascend/descend project discovery (see {@link resolveProjectDir}).
|
|
125
|
+
* no ascend/descend project discovery (see {@link resolveProjectDir}). Seven checks, numbered in
|
|
112
126
|
* the order they run and print:
|
|
113
127
|
*
|
|
114
128
|
* Hard failures (exit non-zero):
|
|
@@ -122,7 +136,7 @@ function resolveAiMajorSkewReason(cfg: BasaltConfig): {
|
|
|
122
136
|
* preset AND that path resolves (warns when no `oxlint` bin does). `init` keeps an existing config, so the framework's whole
|
|
123
137
|
* lint half can be silently off — one repo carried six real violations invisibly across five
|
|
124
138
|
* minors this way.
|
|
125
|
-
*
|
|
139
|
+
* 7. `ai-major-parity`: within THIS package.json, `dependencies`/`devDependencies`/
|
|
126
140
|
* `peerDependencies` agree on the `ai` package's major — the shape `basalt/ai-sdk-major` (per
|
|
127
141
|
* linted FILE) cannot see. A skew is exempt-able via `basalt.aiMajorSkewReason` (a non-empty
|
|
128
142
|
* reason, not a bare `true`); a declared reason when the majors agree warns it is stale.
|
|
@@ -134,6 +148,9 @@ function resolveAiMajorSkewReason(cfg: BasaltConfig): {
|
|
|
134
148
|
* gate elsewhere); a config that resolves but wires no guard command warns; a config lefthook
|
|
135
149
|
* dump could not read at all (binary missing, or the dump failed) warns and says so, rather
|
|
136
150
|
* than inventing a verdict.
|
|
151
|
+
* 6. `claude-md`: a project-root `CLAUDE.md` that does not import `@AGENTS.md` suppresses
|
|
152
|
+
* `AGENTS.md` (and with it the managed block); one that still carries a basalt block — or a
|
|
153
|
+
* malformed one — says so. A `CLAUDE.md` symlinked to `AGENTS.md` is one file and passes.
|
|
137
154
|
*
|
|
138
155
|
* Returns the exit code: 0 = all good, 1 = one or more hard failures.
|
|
139
156
|
*/
|
|
@@ -193,7 +210,7 @@ export function doctor(invocationCwd: string = process.cwd(), flags: string[] =
|
|
|
193
210
|
|
|
194
211
|
const install = findBasaltInstall(cwd)
|
|
195
212
|
|
|
196
|
-
// ── Hard check 2: install matches the manifest, and doctor says WHERE (the
|
|
213
|
+
// ── Hard check 2: install matches the manifest, and doctor says WHERE (the AGENTS-block's claim)
|
|
197
214
|
const manifestVersion = profile === 'tokens-only' ? undefined : readManifest(cwd).basaltVersion
|
|
198
215
|
const at = install.dir === null ? '' : `${relative(cwd, install.dir)} (${install.version})`
|
|
199
216
|
if (install.dir === null) {
|
|
@@ -300,7 +317,33 @@ export function doctor(invocationCwd: string = process.cwd(), flags: string[] =
|
|
|
300
317
|
)
|
|
301
318
|
}
|
|
302
319
|
|
|
303
|
-
// ──
|
|
320
|
+
// ── Warn check 6: a project-root CLAUDE.md that shadows AGENTS.md ───────────
|
|
321
|
+
// The managed block lives in AGENTS.md. Claude Code loads a repo-root AGENTS.md directly, but ANY
|
|
322
|
+
// CLAUDE.md in or above the cwd suppresses it unless that file imports `@AGENTS.md` — so a
|
|
323
|
+
// leftover CLAUDE.md means the block (and the rest of AGENTS.md) is not loaded, whether or not it
|
|
324
|
+
// still carries a block. A symlink to AGENTS.md is one file and is fine. Warn only: `sync`
|
|
325
|
+
// migrates the block, `sync --check` is the gate, and what else CLAUDE.md holds is the consumer's.
|
|
326
|
+
const legacyHost = resolve(cwd, LEGACY_BLOCK_HOST)
|
|
327
|
+
const legacy = profile === 'tokens-only' ? null : readIfExists(legacyHost)
|
|
328
|
+
if (legacy !== null && !isSameFile(legacyHost, resolve(cwd, AGENTS_MD))) {
|
|
329
|
+
const scan = scanBlock(legacy)
|
|
330
|
+
const issues: string[] = []
|
|
331
|
+
if (scan.problem !== null) {
|
|
332
|
+
issues.push(`has a malformed basalt block (${scan.problem}) — fix it by hand, then \`sync\``)
|
|
333
|
+
} else if (scan.region !== null) {
|
|
334
|
+
issues.push(`still carries a stale basalt block — run \`basalt-ui sync\` to move it`)
|
|
335
|
+
}
|
|
336
|
+
if (!importsAgentsMd(legacy)) {
|
|
337
|
+
issues.push(
|
|
338
|
+
`does not import \`@${AGENTS_MD}\`, so Claude Code loads it INSTEAD of ${AGENTS_MD} and the ` +
|
|
339
|
+
`basalt block never reaches the agent — move its content into ${AGENTS_MD} and delete it, ` +
|
|
340
|
+
`or add \`@${AGENTS_MD}\``,
|
|
341
|
+
)
|
|
342
|
+
}
|
|
343
|
+
if (issues.length > 0) warn(`${LEGACY_BLOCK_HOST} ${issues.join('; and ')}.`)
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
// ── Hard check 7: ai package major version parity within THIS package.json ─
|
|
304
347
|
// basalt/ai-sdk-major (the lint rule) is per-file against the NEAREST package.json, so a lint
|
|
305
348
|
// run scoped to one workspace package only ever sees that package's own `ai` major and is
|
|
306
349
|
// perfectly happy. This check reads the SAME package.json across its dependencies/
|