bearings 0.4.0 → 0.5.1
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-KQPOIH7N.js → chunk-5D5YO3E3.js} +135 -42
- package/dist/cli.js +49 -13
- package/dist/{update-TBMTUJUT.js → update-QTWNGAGX.js} +212 -25
- package/package.json +1 -1
- package/templates/AGENTS.md +1 -6
- 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 +68 -38
- package/templates/agents/skills/manual-testplan/schema.reference.json +0 -67
- package/templates/agents/skills/manual-testplan/scripts/validate.mjs +0 -131
- package/templates/agents/skills/manual-testplan/scripts/view.mjs +0 -132
- package/templates/agents/skills/manual-testplan/viewer.html +0 -313
|
@@ -14,11 +14,21 @@ var STARTER_SKILL_NAMES = [
|
|
|
14
14
|
];
|
|
15
15
|
var SKILLS = STARTER_SKILL_NAMES;
|
|
16
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 = [
|
|
17
25
|
"SKILL.md",
|
|
18
26
|
"schema.reference.json",
|
|
19
27
|
"scripts/validate.mjs",
|
|
20
|
-
"scripts/
|
|
21
|
-
"
|
|
28
|
+
"scripts/build.mjs",
|
|
29
|
+
"previewer/index.html",
|
|
30
|
+
"previewer/viewer.css",
|
|
31
|
+
"previewer/viewer.js"
|
|
22
32
|
];
|
|
23
33
|
function starterSkillNameFromTarget(targetPath) {
|
|
24
34
|
return STARTER_SKILL_NAMES.find((skillName) => targetPath === `.agents/skills/${skillName}/SKILL.md`);
|
|
@@ -52,6 +62,16 @@ var SCAFFOLD = [
|
|
|
52
62
|
template: `agents/skills/manual-testplan/${file}`,
|
|
53
63
|
target: `.agents/skills/manual-testplan/${file}`,
|
|
54
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"
|
|
55
75
|
}))
|
|
56
76
|
];
|
|
57
77
|
|
|
@@ -210,6 +230,24 @@ function validateSetupPending(value) {
|
|
|
210
230
|
toVersion
|
|
211
231
|
};
|
|
212
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
|
+
}
|
|
213
251
|
function parseManifest(raw) {
|
|
214
252
|
if (!isPlainObject(raw)) fail("manifest must be an object");
|
|
215
253
|
const { version, bearingsVersion, harnesses, exposure, files } = raw;
|
|
@@ -225,13 +263,19 @@ function parseManifest(raw) {
|
|
|
225
263
|
if (version === 2) {
|
|
226
264
|
const validatedFiles = files.map((file, index) => validateFileV2(file, index));
|
|
227
265
|
assertUniquePaths(validatedFiles.map((file) => file.path));
|
|
228
|
-
const { setupPending } = raw;
|
|
266
|
+
const { setupPending, migrationReconciliations } = raw;
|
|
267
|
+
if (migrationReconciliations !== void 0 && !Array.isArray(migrationReconciliations)) {
|
|
268
|
+
fail("migrationReconciliations must be an array");
|
|
269
|
+
}
|
|
229
270
|
return {
|
|
230
271
|
version: 2,
|
|
231
272
|
bearingsVersion,
|
|
232
273
|
harnesses,
|
|
233
274
|
exposure,
|
|
234
275
|
...setupPending !== void 0 ? { setupPending: validateSetupPending(setupPending) } : {},
|
|
276
|
+
...migrationReconciliations !== void 0 ? {
|
|
277
|
+
migrationReconciliations: migrationReconciliations.map(validateMigrationReconciliation)
|
|
278
|
+
} : {},
|
|
235
279
|
files: validatedFiles
|
|
236
280
|
};
|
|
237
281
|
}
|
|
@@ -348,6 +392,7 @@ function renderUpdatePlan(input) {
|
|
|
348
392
|
pushSection(lines, "Skipped", categories.skipped);
|
|
349
393
|
pushSection(lines, "Removed", categories.removed);
|
|
350
394
|
pushSection(lines, "Kept and untracked", categories.keptUntracked);
|
|
395
|
+
pushMigrationSections(lines, input.migrationActions ?? []);
|
|
351
396
|
if (input.adapterActions.length) {
|
|
352
397
|
lines.push("", `Adapter changes (${input.adapterActions.length}):`);
|
|
353
398
|
for (const a of input.adapterActions) lines.push(` ${a.kind} ${a.path}`);
|
|
@@ -372,6 +417,7 @@ function renderUpdateReport(input) {
|
|
|
372
417
|
pushSection(lines, "Skipped", categories.skipped);
|
|
373
418
|
pushSection(lines, "Removed", categories.removed);
|
|
374
419
|
pushSection(lines, "Kept and untracked", categories.keptUntracked);
|
|
420
|
+
pushMigrationSections(lines, input.migrationActions ?? []);
|
|
375
421
|
if (input.adapterActions.length) {
|
|
376
422
|
lines.push("", `Adapter changes (${input.adapterActions.length}):`);
|
|
377
423
|
for (const a of input.adapterActions) lines.push(` ${a.kind} ${a.path}`);
|
|
@@ -394,6 +440,12 @@ function renderUpdateReport(input) {
|
|
|
394
440
|
}
|
|
395
441
|
return lines.join("\n");
|
|
396
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
|
+
}
|
|
397
449
|
function renderVerifyReport(v) {
|
|
398
450
|
const lines = [];
|
|
399
451
|
for (const f of v.failures) lines.push(`FAIL ${f.code} ${f.path} \u2014 ${f.message}`);
|
|
@@ -405,7 +457,7 @@ ${v.failures.length} failure(s).` : "\nbearings verify: OK");
|
|
|
405
457
|
|
|
406
458
|
// src/commands/init.ts
|
|
407
459
|
import { lstat as lstat2, readdir as readdir2 } from "fs/promises";
|
|
408
|
-
import { join as
|
|
460
|
+
import { join as join7 } from "path";
|
|
409
461
|
|
|
410
462
|
// src/scanner.ts
|
|
411
463
|
import { access as access2, symlink, rm } from "fs/promises";
|
|
@@ -436,40 +488,78 @@ async function scan(repoDir) {
|
|
|
436
488
|
}
|
|
437
489
|
|
|
438
490
|
// src/generator.ts
|
|
439
|
-
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";
|
|
440
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
|
|
441
535
|
async function exists2(p) {
|
|
442
536
|
try {
|
|
443
|
-
await
|
|
537
|
+
await access4(p);
|
|
444
538
|
return true;
|
|
445
539
|
} catch {
|
|
446
540
|
return false;
|
|
447
541
|
}
|
|
448
542
|
}
|
|
449
|
-
async function
|
|
450
|
-
let candidate = `${target}.bkp`;
|
|
451
|
-
for (let i = 1; await exists2(join4(repoDir, candidate)); i++) candidate = `${target}.bkp.${i}`;
|
|
452
|
-
return candidate;
|
|
453
|
-
}
|
|
454
|
-
async function generate(repoDir, bearingsVersion) {
|
|
543
|
+
async function generate(repoDir, bearingsVersion, hooks = {}) {
|
|
455
544
|
const priorState = await inspectManifest(repoDir);
|
|
456
545
|
if (priorState.kind === "invalid") throw new Error(`Invalid manifest: ${priorState.message}`);
|
|
457
546
|
const prior = priorState.kind === "valid" ? priorState.manifest.version === 1 ? await migrateV1(repoDir, priorState.manifest) : priorState.manifest : null;
|
|
458
547
|
const result = { written: [], backedUp: [], skippedUnchanged: [], files: [] };
|
|
459
548
|
for (const entry of SCAFFOLD) {
|
|
460
|
-
const abs =
|
|
461
|
-
const templateContent = await readFile2(
|
|
549
|
+
const abs = join5(repoDir, entry.target);
|
|
550
|
+
const templateContent = await readFile2(join5(templatesDir(), entry.template), "utf8");
|
|
462
551
|
const priorEntry = prior?.files.find((f) => f.path === entry.target);
|
|
463
552
|
let backup;
|
|
464
553
|
let currentContent;
|
|
465
554
|
if (await exists2(abs)) {
|
|
466
|
-
|
|
555
|
+
const currentBytes = await readFile2(abs);
|
|
556
|
+
currentContent = currentBytes.toString("utf8");
|
|
467
557
|
if (priorEntry && sha256(currentContent) === priorEntry.hash) {
|
|
468
558
|
const skillName2 = starterSkillNameFromTarget(entry.target);
|
|
469
559
|
if (skillName2) {
|
|
470
|
-
const baseline =
|
|
560
|
+
const baseline = join5(repoDir, skillBaselinePath(skillName2));
|
|
471
561
|
if (!await exists2(baseline)) {
|
|
472
|
-
await
|
|
562
|
+
await mkdir3(dirname3(baseline), { recursive: true });
|
|
473
563
|
await writeFile2(baseline, currentContent);
|
|
474
564
|
}
|
|
475
565
|
}
|
|
@@ -477,18 +567,19 @@ async function generate(repoDir, bearingsVersion) {
|
|
|
477
567
|
result.files.push(priorEntry);
|
|
478
568
|
continue;
|
|
479
569
|
}
|
|
480
|
-
backup = await
|
|
481
|
-
|
|
570
|
+
backup = await writeBackup(repoDir, entry.target, currentBytes, {
|
|
571
|
+
beforeCreate: hooks.beforeBackupCreate
|
|
572
|
+
});
|
|
482
573
|
result.backedUp.push({ path: entry.target, backup });
|
|
483
574
|
}
|
|
484
|
-
await
|
|
575
|
+
await mkdir3(dirname3(abs), { recursive: true });
|
|
485
576
|
await writeFile2(abs, templateContent);
|
|
486
577
|
result.written.push(entry.target);
|
|
487
578
|
const incomingHash = sha256(templateContent);
|
|
488
579
|
const skillName = starterSkillNameFromTarget(entry.target);
|
|
489
580
|
if (skillName) {
|
|
490
|
-
const baseline =
|
|
491
|
-
await
|
|
581
|
+
const baseline = join5(repoDir, skillBaselinePath(skillName));
|
|
582
|
+
await mkdir3(dirname3(baseline), { recursive: true });
|
|
492
583
|
await writeFile2(baseline, templateContent);
|
|
493
584
|
}
|
|
494
585
|
const reconciliation = backup && currentContent !== void 0 ? {
|
|
@@ -512,15 +603,15 @@ async function generate(repoDir, bearingsVersion) {
|
|
|
512
603
|
}
|
|
513
604
|
|
|
514
605
|
// src/adapters.ts
|
|
515
|
-
import { mkdir as
|
|
516
|
-
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";
|
|
517
608
|
var ADAPTER_KINDS = ["skills", "commands"];
|
|
518
609
|
async function canonicalAdapterEntries(repoDir) {
|
|
519
610
|
const entries = /* @__PURE__ */ new Map();
|
|
520
611
|
for (const kind of ADAPTER_KINDS) {
|
|
521
|
-
const directory =
|
|
612
|
+
const directory = join6(repoDir, ".agents", kind);
|
|
522
613
|
for (const name of await readdir(directory).catch(() => [])) {
|
|
523
|
-
entries.set(`${kind}/${name}`,
|
|
614
|
+
entries.set(`${kind}/${name}`, join6(directory, name));
|
|
524
615
|
}
|
|
525
616
|
}
|
|
526
617
|
return entries;
|
|
@@ -540,7 +631,7 @@ async function entriesMatch(src, dst) {
|
|
|
540
631
|
dstEntries.sort();
|
|
541
632
|
for (let i = 0; i < srcEntries.length; i++) {
|
|
542
633
|
if (srcEntries[i] !== dstEntries[i]) return false;
|
|
543
|
-
if (!await entriesMatch(
|
|
634
|
+
if (!await entriesMatch(join6(src, srcEntries[i]), join6(dst, dstEntries[i]))) return false;
|
|
544
635
|
}
|
|
545
636
|
return true;
|
|
546
637
|
}
|
|
@@ -552,30 +643,30 @@ async function entriesMatch(src, dst) {
|
|
|
552
643
|
async function expose(repoDir, harness, mode) {
|
|
553
644
|
const created = [];
|
|
554
645
|
for (const kind of ADAPTER_KINDS) {
|
|
555
|
-
const srcDir =
|
|
646
|
+
const srcDir = join6(repoDir, ".agents", kind);
|
|
556
647
|
let entries;
|
|
557
648
|
try {
|
|
558
649
|
entries = await readdir(srcDir);
|
|
559
650
|
} catch {
|
|
560
651
|
continue;
|
|
561
652
|
}
|
|
562
|
-
const dstDir =
|
|
563
|
-
await
|
|
653
|
+
const dstDir = join6(repoDir, `.${harness}`, kind);
|
|
654
|
+
await mkdir4(dstDir, { recursive: true });
|
|
564
655
|
for (const name of entries) {
|
|
565
|
-
const dst =
|
|
566
|
-
const adapterPath =
|
|
567
|
-
const relTarget =
|
|
656
|
+
const dst = join6(dstDir, name);
|
|
657
|
+
const adapterPath = join6(`.${harness}`, kind, name);
|
|
658
|
+
const relTarget = join6("..", "..", ".agents", kind, name);
|
|
568
659
|
const stat = await lstat(dst).catch(() => null);
|
|
569
660
|
if (mode === "symlink") {
|
|
570
661
|
if (stat?.isSymbolicLink() && await readlink(dst) === relTarget) continue;
|
|
571
662
|
if (stat && !stat.isSymbolicLink()) throw new Error(`Refusing to replace existing adapter entry: ${adapterPath}`);
|
|
572
|
-
if (stat) await
|
|
663
|
+
if (stat) await rm3(dst, { recursive: true });
|
|
573
664
|
await symlink2(relTarget, dst);
|
|
574
665
|
} else {
|
|
575
|
-
const src =
|
|
666
|
+
const src = join6(srcDir, name);
|
|
576
667
|
if (stat && await entriesMatch(src, dst)) continue;
|
|
577
668
|
if (stat && !stat.isSymbolicLink()) throw new Error(`Refusing to replace existing adapter entry: ${adapterPath}`);
|
|
578
|
-
if (stat) await
|
|
669
|
+
if (stat) await rm3(dst, { recursive: true });
|
|
579
670
|
await cp(src, dst, { recursive: true });
|
|
580
671
|
}
|
|
581
672
|
created.push(adapterPath);
|
|
@@ -607,14 +698,14 @@ async function plannedAdapterSources(repoDir) {
|
|
|
607
698
|
const prefix = `.agents/${kind}/`;
|
|
608
699
|
if (entry.target.startsWith(prefix)) {
|
|
609
700
|
const name = entry.target.slice(prefix.length).split("/")[0];
|
|
610
|
-
sources[kind].set(name,
|
|
701
|
+
sources[kind].set(name, join7(templatesDir(), "agents", kind, name));
|
|
611
702
|
}
|
|
612
703
|
}
|
|
613
704
|
}
|
|
614
705
|
for (const kind of KINDS) {
|
|
615
|
-
const srcDir =
|
|
706
|
+
const srcDir = join7(repoDir, ".agents", kind);
|
|
616
707
|
for (const name of await readdir2(srcDir).catch(() => [])) {
|
|
617
|
-
if (!sources[kind].has(name)) sources[kind].set(name,
|
|
708
|
+
if (!sources[kind].has(name)) sources[kind].set(name, join7(srcDir, name));
|
|
618
709
|
}
|
|
619
710
|
}
|
|
620
711
|
return sources;
|
|
@@ -624,8 +715,8 @@ async function preflightAdapterCollisions(repoDir, harnesses, exposure) {
|
|
|
624
715
|
for (const h of harnesses) {
|
|
625
716
|
for (const kind of KINDS) {
|
|
626
717
|
for (const [name, source] of sources[kind]) {
|
|
627
|
-
const adapterPath =
|
|
628
|
-
const target =
|
|
718
|
+
const adapterPath = join7(`.${h}`, kind, name);
|
|
719
|
+
const target = join7(repoDir, adapterPath);
|
|
629
720
|
const stat = await lstat2(target).catch(() => null);
|
|
630
721
|
if (!stat || stat.isSymbolicLink()) continue;
|
|
631
722
|
if (exposure === "copy" && await entriesMatch(source, target)) continue;
|
|
@@ -692,7 +783,7 @@ async function runFreshInit(repoDir, flags, version) {
|
|
|
692
783
|
async function runInit(repoDir, flags, version) {
|
|
693
784
|
const state = await inspectManifest(repoDir);
|
|
694
785
|
if (state.kind === "absent") return runFreshInit(repoDir, flags, version);
|
|
695
|
-
const { runUpdate } = await import("./update-
|
|
786
|
+
const { runUpdate } = await import("./update-QTWNGAGX.js");
|
|
696
787
|
return runUpdate(repoDir, flags, version, state);
|
|
697
788
|
}
|
|
698
789
|
|
|
@@ -706,6 +797,8 @@ export {
|
|
|
706
797
|
sha256,
|
|
707
798
|
inspectManifest,
|
|
708
799
|
migrateV1,
|
|
800
|
+
freeBackupPath,
|
|
801
|
+
writeBackup,
|
|
709
802
|
ADAPTER_KINDS,
|
|
710
803
|
canonicalAdapterEntries,
|
|
711
804
|
entriesMatch,
|
package/dist/cli.js
CHANGED
|
@@ -8,7 +8,7 @@ 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";
|
|
@@ -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,23 +161,48 @@ async function runVerify(repoDir) {
|
|
|
150
161
|
return v.failures.length ? 1 : 0;
|
|
151
162
|
}
|
|
152
163
|
|
|
153
|
-
// src/commands/
|
|
164
|
+
// src/commands/checklist.ts
|
|
165
|
+
import { spawn } from "child_process";
|
|
154
166
|
import { access as access2 } from "fs/promises";
|
|
155
167
|
import { join as join2 } from "path";
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
const
|
|
159
|
-
try {
|
|
160
|
-
await access2(launcher);
|
|
161
|
-
} catch {
|
|
162
|
-
throw new Error("Manual test-plan viewer is not installed. Run `bearings init` to update this repository.");
|
|
163
|
-
}
|
|
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];
|
|
164
171
|
return new Promise((resolve2, reject) => {
|
|
165
|
-
const child =
|
|
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" });
|
|
166
181
|
child.once("error", reject);
|
|
167
182
|
child.once("exit", (code) => resolve2(code ?? 1));
|
|
168
183
|
});
|
|
169
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
|
+
}
|
|
170
206
|
|
|
171
207
|
// src/cli.ts
|
|
172
208
|
var pkg = JSON.parse(
|
|
@@ -184,8 +220,8 @@ program.command("init").description("Scaffold the agent-friendly setup in the cu
|
|
|
184
220
|
program.command("verify").description("Check the agent-friendly setup for drift and breakage").action(async () => {
|
|
185
221
|
process.exitCode = await runVerify(process.cwd());
|
|
186
222
|
});
|
|
187
|
-
program.command("
|
|
188
|
-
process.exitCode = await
|
|
223
|
+
program.command("checklist").description("Build and open the checklist previewer").action(async () => {
|
|
224
|
+
process.exitCode = await runChecklist(process.cwd());
|
|
189
225
|
});
|
|
190
226
|
program.parseAsync().catch((error) => {
|
|
191
227
|
console.error(error instanceof Error ? error.message : String(error));
|