bearings 0.3.4 → 0.5.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/dist/{chunk-GEIRG7UT.js → chunk-5D5YO3E3.js} +145 -40
- package/dist/cli.js +60 -3
- package/dist/{update-XOUXGD2Z.js → update-QTWNGAGX.js} +212 -25
- package/package.json +1 -1
- package/templates/agents/commands/setup-repo.md +72 -21
- package/templates/agents/skills/checklist/SKILL.md +69 -0
- package/templates/agents/skills/checklist/previewer/index.html +38 -0
- package/templates/agents/skills/checklist/previewer/viewer.css +93 -0
- package/templates/agents/skills/checklist/previewer/viewer.js +340 -0
- package/templates/agents/skills/checklist/schema.reference.json +90 -0
- package/templates/agents/skills/checklist/scripts/build.mjs +98 -0
- package/templates/agents/skills/checklist/scripts/validate.mjs +175 -0
- package/templates/agents/skills/forge-a-skill/SKILL.md +162 -0
- package/templates/agents/skills/forge-a-skill/templates/guardrail.md +45 -0
- package/templates/agents/skills/forge-a-skill/templates/probe-set.json +45 -0
- package/templates/agents/skills/manual-testplan/SKILL.md +80 -0
|
@@ -13,6 +13,23 @@ var STARTER_SKILL_NAMES = [
|
|
|
13
13
|
"recording-decisions"
|
|
14
14
|
];
|
|
15
15
|
var SKILLS = STARTER_SKILL_NAMES;
|
|
16
|
+
var MANUAL_TESTPLAN_FILES = [
|
|
17
|
+
"SKILL.md"
|
|
18
|
+
];
|
|
19
|
+
var FORGE_A_SKILL_FILES = [
|
|
20
|
+
"SKILL.md",
|
|
21
|
+
"templates/guardrail.md",
|
|
22
|
+
"templates/probe-set.json"
|
|
23
|
+
];
|
|
24
|
+
var CHECKLIST_FILES = [
|
|
25
|
+
"SKILL.md",
|
|
26
|
+
"schema.reference.json",
|
|
27
|
+
"scripts/validate.mjs",
|
|
28
|
+
"scripts/build.mjs",
|
|
29
|
+
"previewer/index.html",
|
|
30
|
+
"previewer/viewer.css",
|
|
31
|
+
"previewer/viewer.js"
|
|
32
|
+
];
|
|
16
33
|
function starterSkillNameFromTarget(targetPath) {
|
|
17
34
|
return STARTER_SKILL_NAMES.find((skillName) => targetPath === `.agents/skills/${skillName}/SKILL.md`);
|
|
18
35
|
}
|
|
@@ -40,6 +57,21 @@ var SCAFFOLD = [
|
|
|
40
57
|
template: `agents/skills/${skill}/SKILL.md`,
|
|
41
58
|
target: `.agents/skills/${skill}/SKILL.md`,
|
|
42
59
|
owner: "bearings"
|
|
60
|
+
})),
|
|
61
|
+
...MANUAL_TESTPLAN_FILES.map((file) => ({
|
|
62
|
+
template: `agents/skills/manual-testplan/${file}`,
|
|
63
|
+
target: `.agents/skills/manual-testplan/${file}`,
|
|
64
|
+
owner: "bearings"
|
|
65
|
+
})),
|
|
66
|
+
...FORGE_A_SKILL_FILES.map((file) => ({
|
|
67
|
+
template: `agents/skills/forge-a-skill/${file}`,
|
|
68
|
+
target: `.agents/skills/forge-a-skill/${file}`,
|
|
69
|
+
owner: "bearings"
|
|
70
|
+
})),
|
|
71
|
+
...CHECKLIST_FILES.map((file) => ({
|
|
72
|
+
template: `agents/skills/checklist/${file}`,
|
|
73
|
+
target: `.agents/skills/checklist/${file}`,
|
|
74
|
+
owner: "bearings"
|
|
43
75
|
}))
|
|
44
76
|
];
|
|
45
77
|
|
|
@@ -198,6 +230,24 @@ function validateSetupPending(value) {
|
|
|
198
230
|
toVersion
|
|
199
231
|
};
|
|
200
232
|
}
|
|
233
|
+
function validateMigrationReconciliation(value, index) {
|
|
234
|
+
const context = `migrationReconciliations[${index}]`;
|
|
235
|
+
if (!isPlainObject(value)) fail(`${context} must be an object`);
|
|
236
|
+
const { kind, source, target, backup, sourceHash, incomingHash } = value;
|
|
237
|
+
if (kind !== "testplan-collision" && kind !== "invalid-testplan") {
|
|
238
|
+
fail(`${context}.kind must be "testplan-collision" or "invalid-testplan"`);
|
|
239
|
+
}
|
|
240
|
+
if (!isSafeManifestPath(source)) fail(`${context}.source is unsafe: ${String(source)}`);
|
|
241
|
+
if (!isSafeManifestPath(backup)) fail(`${context}.backup is unsafe: ${String(backup)}`);
|
|
242
|
+
if (!isHash(sourceHash)) fail(`${context}.sourceHash must match sha256 format`);
|
|
243
|
+
if (kind === "testplan-collision") {
|
|
244
|
+
if (!isSafeManifestPath(target)) fail(`${context}.target is unsafe: ${String(target)}`);
|
|
245
|
+
if (!isHash(incomingHash)) fail(`${context}.incomingHash must match sha256 format`);
|
|
246
|
+
return { kind, source, target, backup, sourceHash, incomingHash };
|
|
247
|
+
}
|
|
248
|
+
if (target !== void 0 && !isSafeManifestPath(target)) fail(`${context}.target is unsafe: ${String(target)}`);
|
|
249
|
+
return { kind, source, ...target !== void 0 ? { target } : {}, backup, sourceHash };
|
|
250
|
+
}
|
|
201
251
|
function parseManifest(raw) {
|
|
202
252
|
if (!isPlainObject(raw)) fail("manifest must be an object");
|
|
203
253
|
const { version, bearingsVersion, harnesses, exposure, files } = raw;
|
|
@@ -213,13 +263,19 @@ function parseManifest(raw) {
|
|
|
213
263
|
if (version === 2) {
|
|
214
264
|
const validatedFiles = files.map((file, index) => validateFileV2(file, index));
|
|
215
265
|
assertUniquePaths(validatedFiles.map((file) => file.path));
|
|
216
|
-
const { setupPending } = raw;
|
|
266
|
+
const { setupPending, migrationReconciliations } = raw;
|
|
267
|
+
if (migrationReconciliations !== void 0 && !Array.isArray(migrationReconciliations)) {
|
|
268
|
+
fail("migrationReconciliations must be an array");
|
|
269
|
+
}
|
|
217
270
|
return {
|
|
218
271
|
version: 2,
|
|
219
272
|
bearingsVersion,
|
|
220
273
|
harnesses,
|
|
221
274
|
exposure,
|
|
222
275
|
...setupPending !== void 0 ? { setupPending: validateSetupPending(setupPending) } : {},
|
|
276
|
+
...migrationReconciliations !== void 0 ? {
|
|
277
|
+
migrationReconciliations: migrationReconciliations.map(validateMigrationReconciliation)
|
|
278
|
+
} : {},
|
|
223
279
|
files: validatedFiles
|
|
224
280
|
};
|
|
225
281
|
}
|
|
@@ -336,6 +392,7 @@ function renderUpdatePlan(input) {
|
|
|
336
392
|
pushSection(lines, "Skipped", categories.skipped);
|
|
337
393
|
pushSection(lines, "Removed", categories.removed);
|
|
338
394
|
pushSection(lines, "Kept and untracked", categories.keptUntracked);
|
|
395
|
+
pushMigrationSections(lines, input.migrationActions ?? []);
|
|
339
396
|
if (input.adapterActions.length) {
|
|
340
397
|
lines.push("", `Adapter changes (${input.adapterActions.length}):`);
|
|
341
398
|
for (const a of input.adapterActions) lines.push(` ${a.kind} ${a.path}`);
|
|
@@ -360,6 +417,7 @@ function renderUpdateReport(input) {
|
|
|
360
417
|
pushSection(lines, "Skipped", categories.skipped);
|
|
361
418
|
pushSection(lines, "Removed", categories.removed);
|
|
362
419
|
pushSection(lines, "Kept and untracked", categories.keptUntracked);
|
|
420
|
+
pushMigrationSections(lines, input.migrationActions ?? []);
|
|
363
421
|
if (input.adapterActions.length) {
|
|
364
422
|
lines.push("", `Adapter changes (${input.adapterActions.length}):`);
|
|
365
423
|
for (const a of input.adapterActions) lines.push(` ${a.kind} ${a.path}`);
|
|
@@ -382,6 +440,12 @@ function renderUpdateReport(input) {
|
|
|
382
440
|
}
|
|
383
441
|
return lines.join("\n");
|
|
384
442
|
}
|
|
443
|
+
function pushMigrationSections(lines, actions) {
|
|
444
|
+
pushSection(lines, "Migrated legacy test plans", actions.filter((action) => action.kind === "migrate-testplan").map((action) => `${action.source} -> ${action.target}`));
|
|
445
|
+
pushSection(lines, "Migration collisions backed up -> /setup-repo", actions.flatMap((action) => action.kind === "migrate-testplan" && action.backup !== void 0 ? [`${action.target} -> ${action.backup}`] : []));
|
|
446
|
+
pushSection(lines, "Invalid legacy test plans preserved -> /setup-repo", actions.filter((action) => action.kind === "preserve-invalid-testplan").map((action) => `${action.source} -> ${action.backup}`));
|
|
447
|
+
pushSection(lines, "Removed empty legacy plan directories", actions.filter((action) => action.kind === "remove-empty-testplan-dir").map((action) => action.path));
|
|
448
|
+
}
|
|
385
449
|
function renderVerifyReport(v) {
|
|
386
450
|
const lines = [];
|
|
387
451
|
for (const f of v.failures) lines.push(`FAIL ${f.code} ${f.path} \u2014 ${f.message}`);
|
|
@@ -393,7 +457,7 @@ ${v.failures.length} failure(s).` : "\nbearings verify: OK");
|
|
|
393
457
|
|
|
394
458
|
// src/commands/init.ts
|
|
395
459
|
import { lstat as lstat2, readdir as readdir2 } from "fs/promises";
|
|
396
|
-
import { join as
|
|
460
|
+
import { join as join7 } from "path";
|
|
397
461
|
|
|
398
462
|
// src/scanner.ts
|
|
399
463
|
import { access as access2, symlink, rm } from "fs/promises";
|
|
@@ -424,40 +488,78 @@ async function scan(repoDir) {
|
|
|
424
488
|
}
|
|
425
489
|
|
|
426
490
|
// src/generator.ts
|
|
427
|
-
import { mkdir as
|
|
491
|
+
import { mkdir as mkdir3, readFile as readFile2, writeFile as writeFile2, access as access4 } from "fs/promises";
|
|
492
|
+
import { dirname as dirname3, join as join5 } from "path";
|
|
493
|
+
|
|
494
|
+
// src/backup-path.ts
|
|
495
|
+
import { access as access3, mkdir as mkdir2, open, rm as rm2 } from "fs/promises";
|
|
428
496
|
import { dirname as dirname2, join as join4 } from "path";
|
|
497
|
+
async function freeBackupPath(repoDir, target) {
|
|
498
|
+
let candidate = `${target}.bkp`;
|
|
499
|
+
for (let index = 1; await access3(join4(repoDir, candidate)).then(() => true, () => false); index++) {
|
|
500
|
+
candidate = `${target}.bkp.${index}`;
|
|
501
|
+
}
|
|
502
|
+
return candidate;
|
|
503
|
+
}
|
|
504
|
+
async function writeBackup(repoDir, target, content, options = {}) {
|
|
505
|
+
let candidate = options.path ?? `${target}.bkp`;
|
|
506
|
+
for (let index = 1; ; index++) {
|
|
507
|
+
await options.beforeCreate?.(candidate);
|
|
508
|
+
const absolute = join4(repoDir, candidate);
|
|
509
|
+
await mkdir2(dirname2(absolute), { recursive: true });
|
|
510
|
+
let handle;
|
|
511
|
+
try {
|
|
512
|
+
handle = await open(absolute, "wx");
|
|
513
|
+
} catch (error) {
|
|
514
|
+
if (!options.path && error.code === "EEXIST") {
|
|
515
|
+
candidate = `${target}.bkp.${index}`;
|
|
516
|
+
continue;
|
|
517
|
+
}
|
|
518
|
+
throw error;
|
|
519
|
+
}
|
|
520
|
+
try {
|
|
521
|
+
await handle.writeFile(content);
|
|
522
|
+
await handle.close();
|
|
523
|
+
return candidate;
|
|
524
|
+
} catch (error) {
|
|
525
|
+
await handle.close().catch(() => {
|
|
526
|
+
});
|
|
527
|
+
await rm2(absolute, { force: true }).catch(() => {
|
|
528
|
+
});
|
|
529
|
+
throw error;
|
|
530
|
+
}
|
|
531
|
+
}
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
// src/generator.ts
|
|
429
535
|
async function exists2(p) {
|
|
430
536
|
try {
|
|
431
|
-
await
|
|
537
|
+
await access4(p);
|
|
432
538
|
return true;
|
|
433
539
|
} catch {
|
|
434
540
|
return false;
|
|
435
541
|
}
|
|
436
542
|
}
|
|
437
|
-
async function
|
|
438
|
-
let candidate = `${target}.bkp`;
|
|
439
|
-
for (let i = 1; await exists2(join4(repoDir, candidate)); i++) candidate = `${target}.bkp.${i}`;
|
|
440
|
-
return candidate;
|
|
441
|
-
}
|
|
442
|
-
async function generate(repoDir, bearingsVersion) {
|
|
543
|
+
async function generate(repoDir, bearingsVersion, hooks = {}) {
|
|
443
544
|
const priorState = await inspectManifest(repoDir);
|
|
444
545
|
if (priorState.kind === "invalid") throw new Error(`Invalid manifest: ${priorState.message}`);
|
|
445
546
|
const prior = priorState.kind === "valid" ? priorState.manifest.version === 1 ? await migrateV1(repoDir, priorState.manifest) : priorState.manifest : null;
|
|
446
547
|
const result = { written: [], backedUp: [], skippedUnchanged: [], files: [] };
|
|
447
548
|
for (const entry of SCAFFOLD) {
|
|
448
|
-
const abs =
|
|
449
|
-
const templateContent = await readFile2(
|
|
549
|
+
const abs = join5(repoDir, entry.target);
|
|
550
|
+
const templateContent = await readFile2(join5(templatesDir(), entry.template), "utf8");
|
|
450
551
|
const priorEntry = prior?.files.find((f) => f.path === entry.target);
|
|
451
552
|
let backup;
|
|
452
553
|
let currentContent;
|
|
453
554
|
if (await exists2(abs)) {
|
|
454
|
-
|
|
555
|
+
const currentBytes = await readFile2(abs);
|
|
556
|
+
currentContent = currentBytes.toString("utf8");
|
|
455
557
|
if (priorEntry && sha256(currentContent) === priorEntry.hash) {
|
|
456
558
|
const skillName2 = starterSkillNameFromTarget(entry.target);
|
|
457
559
|
if (skillName2) {
|
|
458
|
-
const baseline =
|
|
560
|
+
const baseline = join5(repoDir, skillBaselinePath(skillName2));
|
|
459
561
|
if (!await exists2(baseline)) {
|
|
460
|
-
await
|
|
562
|
+
await mkdir3(dirname3(baseline), { recursive: true });
|
|
461
563
|
await writeFile2(baseline, currentContent);
|
|
462
564
|
}
|
|
463
565
|
}
|
|
@@ -465,18 +567,19 @@ async function generate(repoDir, bearingsVersion) {
|
|
|
465
567
|
result.files.push(priorEntry);
|
|
466
568
|
continue;
|
|
467
569
|
}
|
|
468
|
-
backup = await
|
|
469
|
-
|
|
570
|
+
backup = await writeBackup(repoDir, entry.target, currentBytes, {
|
|
571
|
+
beforeCreate: hooks.beforeBackupCreate
|
|
572
|
+
});
|
|
470
573
|
result.backedUp.push({ path: entry.target, backup });
|
|
471
574
|
}
|
|
472
|
-
await
|
|
575
|
+
await mkdir3(dirname3(abs), { recursive: true });
|
|
473
576
|
await writeFile2(abs, templateContent);
|
|
474
577
|
result.written.push(entry.target);
|
|
475
578
|
const incomingHash = sha256(templateContent);
|
|
476
579
|
const skillName = starterSkillNameFromTarget(entry.target);
|
|
477
580
|
if (skillName) {
|
|
478
|
-
const baseline =
|
|
479
|
-
await
|
|
581
|
+
const baseline = join5(repoDir, skillBaselinePath(skillName));
|
|
582
|
+
await mkdir3(dirname3(baseline), { recursive: true });
|
|
480
583
|
await writeFile2(baseline, templateContent);
|
|
481
584
|
}
|
|
482
585
|
const reconciliation = backup && currentContent !== void 0 ? {
|
|
@@ -500,15 +603,15 @@ async function generate(repoDir, bearingsVersion) {
|
|
|
500
603
|
}
|
|
501
604
|
|
|
502
605
|
// src/adapters.ts
|
|
503
|
-
import { mkdir as
|
|
504
|
-
import { join as
|
|
606
|
+
import { mkdir as mkdir4, readdir, symlink as symlink2, lstat, readlink, rm as rm3, cp, readFile as readFile3 } from "fs/promises";
|
|
607
|
+
import { join as join6 } from "path";
|
|
505
608
|
var ADAPTER_KINDS = ["skills", "commands"];
|
|
506
609
|
async function canonicalAdapterEntries(repoDir) {
|
|
507
610
|
const entries = /* @__PURE__ */ new Map();
|
|
508
611
|
for (const kind of ADAPTER_KINDS) {
|
|
509
|
-
const directory =
|
|
612
|
+
const directory = join6(repoDir, ".agents", kind);
|
|
510
613
|
for (const name of await readdir(directory).catch(() => [])) {
|
|
511
|
-
entries.set(`${kind}/${name}`,
|
|
614
|
+
entries.set(`${kind}/${name}`, join6(directory, name));
|
|
512
615
|
}
|
|
513
616
|
}
|
|
514
617
|
return entries;
|
|
@@ -528,7 +631,7 @@ async function entriesMatch(src, dst) {
|
|
|
528
631
|
dstEntries.sort();
|
|
529
632
|
for (let i = 0; i < srcEntries.length; i++) {
|
|
530
633
|
if (srcEntries[i] !== dstEntries[i]) return false;
|
|
531
|
-
if (!await entriesMatch(
|
|
634
|
+
if (!await entriesMatch(join6(src, srcEntries[i]), join6(dst, dstEntries[i]))) return false;
|
|
532
635
|
}
|
|
533
636
|
return true;
|
|
534
637
|
}
|
|
@@ -540,30 +643,30 @@ async function entriesMatch(src, dst) {
|
|
|
540
643
|
async function expose(repoDir, harness, mode) {
|
|
541
644
|
const created = [];
|
|
542
645
|
for (const kind of ADAPTER_KINDS) {
|
|
543
|
-
const srcDir =
|
|
646
|
+
const srcDir = join6(repoDir, ".agents", kind);
|
|
544
647
|
let entries;
|
|
545
648
|
try {
|
|
546
649
|
entries = await readdir(srcDir);
|
|
547
650
|
} catch {
|
|
548
651
|
continue;
|
|
549
652
|
}
|
|
550
|
-
const dstDir =
|
|
551
|
-
await
|
|
653
|
+
const dstDir = join6(repoDir, `.${harness}`, kind);
|
|
654
|
+
await mkdir4(dstDir, { recursive: true });
|
|
552
655
|
for (const name of entries) {
|
|
553
|
-
const dst =
|
|
554
|
-
const adapterPath =
|
|
555
|
-
const relTarget =
|
|
656
|
+
const dst = join6(dstDir, name);
|
|
657
|
+
const adapterPath = join6(`.${harness}`, kind, name);
|
|
658
|
+
const relTarget = join6("..", "..", ".agents", kind, name);
|
|
556
659
|
const stat = await lstat(dst).catch(() => null);
|
|
557
660
|
if (mode === "symlink") {
|
|
558
661
|
if (stat?.isSymbolicLink() && await readlink(dst) === relTarget) continue;
|
|
559
662
|
if (stat && !stat.isSymbolicLink()) throw new Error(`Refusing to replace existing adapter entry: ${adapterPath}`);
|
|
560
|
-
if (stat) await
|
|
663
|
+
if (stat) await rm3(dst, { recursive: true });
|
|
561
664
|
await symlink2(relTarget, dst);
|
|
562
665
|
} else {
|
|
563
|
-
const src =
|
|
666
|
+
const src = join6(srcDir, name);
|
|
564
667
|
if (stat && await entriesMatch(src, dst)) continue;
|
|
565
668
|
if (stat && !stat.isSymbolicLink()) throw new Error(`Refusing to replace existing adapter entry: ${adapterPath}`);
|
|
566
|
-
if (stat) await
|
|
669
|
+
if (stat) await rm3(dst, { recursive: true });
|
|
567
670
|
await cp(src, dst, { recursive: true });
|
|
568
671
|
}
|
|
569
672
|
created.push(adapterPath);
|
|
@@ -595,14 +698,14 @@ async function plannedAdapterSources(repoDir) {
|
|
|
595
698
|
const prefix = `.agents/${kind}/`;
|
|
596
699
|
if (entry.target.startsWith(prefix)) {
|
|
597
700
|
const name = entry.target.slice(prefix.length).split("/")[0];
|
|
598
|
-
sources[kind].set(name,
|
|
701
|
+
sources[kind].set(name, join7(templatesDir(), "agents", kind, name));
|
|
599
702
|
}
|
|
600
703
|
}
|
|
601
704
|
}
|
|
602
705
|
for (const kind of KINDS) {
|
|
603
|
-
const srcDir =
|
|
706
|
+
const srcDir = join7(repoDir, ".agents", kind);
|
|
604
707
|
for (const name of await readdir2(srcDir).catch(() => [])) {
|
|
605
|
-
if (!sources[kind].has(name)) sources[kind].set(name,
|
|
708
|
+
if (!sources[kind].has(name)) sources[kind].set(name, join7(srcDir, name));
|
|
606
709
|
}
|
|
607
710
|
}
|
|
608
711
|
return sources;
|
|
@@ -612,8 +715,8 @@ async function preflightAdapterCollisions(repoDir, harnesses, exposure) {
|
|
|
612
715
|
for (const h of harnesses) {
|
|
613
716
|
for (const kind of KINDS) {
|
|
614
717
|
for (const [name, source] of sources[kind]) {
|
|
615
|
-
const adapterPath =
|
|
616
|
-
const target =
|
|
718
|
+
const adapterPath = join7(`.${h}`, kind, name);
|
|
719
|
+
const target = join7(repoDir, adapterPath);
|
|
617
720
|
const stat = await lstat2(target).catch(() => null);
|
|
618
721
|
if (!stat || stat.isSymbolicLink()) continue;
|
|
619
722
|
if (exposure === "copy" && await entriesMatch(source, target)) continue;
|
|
@@ -680,7 +783,7 @@ async function runFreshInit(repoDir, flags, version) {
|
|
|
680
783
|
async function runInit(repoDir, flags, version) {
|
|
681
784
|
const state = await inspectManifest(repoDir);
|
|
682
785
|
if (state.kind === "absent") return runFreshInit(repoDir, flags, version);
|
|
683
|
-
const { runUpdate } = await import("./update-
|
|
786
|
+
const { runUpdate } = await import("./update-QTWNGAGX.js");
|
|
684
787
|
return runUpdate(repoDir, flags, version, state);
|
|
685
788
|
}
|
|
686
789
|
|
|
@@ -694,6 +797,8 @@ export {
|
|
|
694
797
|
sha256,
|
|
695
798
|
inspectManifest,
|
|
696
799
|
migrateV1,
|
|
800
|
+
freeBackupPath,
|
|
801
|
+
writeBackup,
|
|
697
802
|
ADAPTER_KINDS,
|
|
698
803
|
canonicalAdapterEntries,
|
|
699
804
|
entriesMatch,
|
package/dist/cli.js
CHANGED
|
@@ -8,13 +8,13 @@ import {
|
|
|
8
8
|
skillBaselinePath,
|
|
9
9
|
starterSkillNameFromTarget,
|
|
10
10
|
validateHarnesses
|
|
11
|
-
} from "./chunk-
|
|
11
|
+
} from "./chunk-5D5YO3E3.js";
|
|
12
12
|
|
|
13
13
|
// src/cli.ts
|
|
14
14
|
import { Command } from "commander";
|
|
15
15
|
import { readFileSync } from "fs";
|
|
16
16
|
import { fileURLToPath } from "url";
|
|
17
|
-
import { dirname as dirname2, join as
|
|
17
|
+
import { dirname as dirname2, join as join3 } from "path";
|
|
18
18
|
|
|
19
19
|
// src/verifier.ts
|
|
20
20
|
import { access, lstat, readFile, readdir, readlink, stat } from "fs/promises";
|
|
@@ -81,6 +81,17 @@ async function verify(repoDir) {
|
|
|
81
81
|
}
|
|
82
82
|
}
|
|
83
83
|
}
|
|
84
|
+
if ("migrationReconciliations" in m && m.migrationReconciliations) {
|
|
85
|
+
for (const reconciliation of m.migrationReconciliations) {
|
|
86
|
+
if (await exists(join(repoDir, reconciliation.backup))) {
|
|
87
|
+
warnings.push({
|
|
88
|
+
code: "pending-reconciliation",
|
|
89
|
+
path: reconciliation.backup,
|
|
90
|
+
message: reconciliation.kind === "invalid-testplan" ? "repair with the checklist skill during /setup-repo, then delete" : "review during /setup-repo, then delete"
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
}
|
|
84
95
|
for (const kind of KINDS) {
|
|
85
96
|
const srcDir = join(repoDir, ".agents", kind);
|
|
86
97
|
let entries = [];
|
|
@@ -150,9 +161,52 @@ async function runVerify(repoDir) {
|
|
|
150
161
|
return v.failures.length ? 1 : 0;
|
|
151
162
|
}
|
|
152
163
|
|
|
164
|
+
// src/commands/checklist.ts
|
|
165
|
+
import { spawn } from "child_process";
|
|
166
|
+
import { access as access2 } from "fs/promises";
|
|
167
|
+
import { join as join2 } from "path";
|
|
168
|
+
function openChecklist(path, platform = process.platform, spawnProcess = spawn) {
|
|
169
|
+
const command = platform === "darwin" ? "open" : platform === "win32" ? "cmd" : "xdg-open";
|
|
170
|
+
const args = platform === "win32" ? ["/c", "start", "", path] : [path];
|
|
171
|
+
return new Promise((resolve2, reject) => {
|
|
172
|
+
const child = spawnProcess(command, args, { detached: true, stdio: "ignore" });
|
|
173
|
+
child.once("error", reject);
|
|
174
|
+
child.once("spawn", () => resolve2());
|
|
175
|
+
child.unref();
|
|
176
|
+
});
|
|
177
|
+
}
|
|
178
|
+
async function runBuilder(builder, repoDir) {
|
|
179
|
+
return new Promise((resolve2, reject) => {
|
|
180
|
+
const child = spawn(process.execPath, [builder, repoDir], { cwd: repoDir, stdio: "inherit" });
|
|
181
|
+
child.once("error", reject);
|
|
182
|
+
child.once("exit", (code) => resolve2(code ?? 1));
|
|
183
|
+
});
|
|
184
|
+
}
|
|
185
|
+
async function runChecklist(repoDir, open = openChecklist, log = console.log) {
|
|
186
|
+
const bundle = join2(repoDir, ".agents/skills/checklist");
|
|
187
|
+
const builder = join2(bundle, "scripts/build.mjs");
|
|
188
|
+
const previewer = join2(bundle, "previewer/index.html");
|
|
189
|
+
log(previewer);
|
|
190
|
+
try {
|
|
191
|
+
await Promise.all([
|
|
192
|
+
builder,
|
|
193
|
+
join2(bundle, "scripts/validate.mjs"),
|
|
194
|
+
previewer,
|
|
195
|
+
join2(bundle, "previewer/viewer.js"),
|
|
196
|
+
join2(bundle, "previewer/viewer.css")
|
|
197
|
+
].map((path) => access2(path)));
|
|
198
|
+
} catch {
|
|
199
|
+
throw new Error("Checklist previewer is not installed. Run `bearings init` to update this repository.");
|
|
200
|
+
}
|
|
201
|
+
const buildCode = await runBuilder(builder, repoDir);
|
|
202
|
+
if (buildCode !== 0) return buildCode;
|
|
203
|
+
await open(previewer);
|
|
204
|
+
return 0;
|
|
205
|
+
}
|
|
206
|
+
|
|
153
207
|
// src/cli.ts
|
|
154
208
|
var pkg = JSON.parse(
|
|
155
|
-
readFileSync(
|
|
209
|
+
readFileSync(join3(dirname2(fileURLToPath(import.meta.url)), "../package.json"), "utf8")
|
|
156
210
|
);
|
|
157
211
|
var program = new Command("bearings").version(pkg.version);
|
|
158
212
|
program.command("init").description("Scaffold the agent-friendly setup in the current repository").option("--harness <name...>", "claude and/or opencode").option("--copy", "copy instead of symlink").option("-y, --yes", "accept defaults, no prompts").action(async (o) => {
|
|
@@ -166,6 +220,9 @@ program.command("init").description("Scaffold the agent-friendly setup in the cu
|
|
|
166
220
|
program.command("verify").description("Check the agent-friendly setup for drift and breakage").action(async () => {
|
|
167
221
|
process.exitCode = await runVerify(process.cwd());
|
|
168
222
|
});
|
|
223
|
+
program.command("checklist").description("Build and open the checklist previewer").action(async () => {
|
|
224
|
+
process.exitCode = await runChecklist(process.cwd());
|
|
225
|
+
});
|
|
169
226
|
program.parseAsync().catch((error) => {
|
|
170
227
|
console.error(error instanceof Error ? error.message : String(error));
|
|
171
228
|
process.exit(1);
|