bearings 0.5.2 → 0.5.4

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.
@@ -1,22 +1,35 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  ADAPTER_KINDS,
4
+ PLACEHOLDER,
4
5
  SCAFFOLD,
6
+ applyUpdate,
5
7
  canonicalAdapterEntries,
6
8
  entriesMatch,
7
9
  freeBackupPath,
10
+ isChecklistPayload,
8
11
  isStarterSkillTarget,
9
12
  migrateV1,
13
+ normalizeSetup,
14
+ pendingSetupReport,
15
+ protectSkills,
16
+ reconciliationTasks,
10
17
  renderUpdatePlan,
11
18
  renderUpdateReport,
19
+ saveManifest,
20
+ setupArtifacts,
12
21
  sha256,
13
22
  skillBaselinePath,
14
23
  skillIncomingPath,
15
24
  starterSkillNameFromTarget,
25
+ tailoringTask,
16
26
  templatesDir,
17
- validateHarnesses,
18
- writeBackup
19
- } from "./chunk-WYXKSCUW.js";
27
+ uniqueSetupTasks,
28
+ validateHarnesses
29
+ } from "./chunk-MOZJECCW.js";
30
+
31
+ // src/commands/update.ts
32
+ import { isDeepStrictEqual } from "util";
20
33
 
21
34
  // src/semver.ts
22
35
  function parseSemver(value) {
@@ -68,7 +81,7 @@ function compareSemver(left, right) {
68
81
  // src/update/planner.ts
69
82
  import { access, readFile } from "fs/promises";
70
83
  import { join } from "path";
71
- var SETUP_COMMAND_PATH = ".agents/commands/setup-repo.md";
84
+ var SETUP_COMMAND_PATH = ".agents/commands/update-bearings-setup.md";
72
85
  async function exists(path) {
73
86
  try {
74
87
  await access(path);
@@ -104,6 +117,18 @@ async function planFiles(input) {
104
117
  const templateContent = await readFile(join(source.templatesDir, entry.template), "utf8");
105
118
  const incomingHash = sha256(templateContent);
106
119
  const prevRecord = previous?.files.find((file) => file.path === entry.target);
120
+ if (!prevRecord && reconstruction && currentExists && entry.target.startsWith(".agents/skills/") && currentHash !== incomingHash) {
121
+ actions.push({ kind: "keep", record: {
122
+ path: entry.target,
123
+ template: entry.template,
124
+ templateVersion: source.bearingsVersion,
125
+ hash: isStarterSkillTarget(entry.target) ? currentHash : incomingHash,
126
+ owner: entry.owner,
127
+ ...isStarterSkillTarget(entry.target) ? { lastTemplateHash: incomingHash } : {},
128
+ skippedTemplate: { templateVersion: source.bearingsVersion, hash: incomingHash }
129
+ } });
130
+ continue;
131
+ }
107
132
  if (isStarterSkillTarget(entry.target)) {
108
133
  const prevOwner = prevRecord?.owner ?? entry.owner;
109
134
  const lastTemplateHash = prevRecord?.lastTemplateHash ?? prevRecord?.hash;
@@ -113,7 +138,8 @@ async function planFiles(input) {
113
138
  templateVersion: source.bearingsVersion,
114
139
  hash: incomingHash,
115
140
  owner: prevOwner === "agent" ? "agent" : entry.owner,
116
- lastTemplateHash: incomingHash
141
+ lastTemplateHash: incomingHash,
142
+ ...prevRecord?.reconciliations ? { reconciliations: prevRecord.reconciliations } : {}
117
143
  };
118
144
  if (!currentExists) {
119
145
  actions.push({
@@ -146,16 +172,13 @@ async function planFiles(input) {
146
172
  actions.push({ kind: "keep", record: prevRecord });
147
173
  continue;
148
174
  }
149
- if (currentHash === prevRecord.hash) {
175
+ if (currentHash === lastTemplateHash) {
150
176
  actions.push({
151
177
  kind: "write",
152
178
  reason: "replace",
153
179
  path: entry.target,
154
180
  content: templateContent,
155
- record: {
156
- ...incomingRecordBase,
157
- ...prevRecord.reconciliations ? { reconciliations: prevRecord.reconciliations } : {}
158
- }
181
+ record: incomingRecordBase
159
182
  });
160
183
  continue;
161
184
  }
@@ -182,7 +205,8 @@ async function planFiles(input) {
182
205
  template: entry.template,
183
206
  templateVersion: source.bearingsVersion,
184
207
  hash: incomingHash,
185
- owner: entry.owner
208
+ owner: entry.owner,
209
+ ...prevRecord?.reconciliations ? { reconciliations: prevRecord.reconciliations } : {}
186
210
  };
187
211
  const canSkip = entry.target !== SETUP_COMMAND_PATH;
188
212
  if (!currentExists) {
@@ -325,7 +349,6 @@ async function resolveFilePlan(repoDir, draft, decisions) {
325
349
  }
326
350
  const actions = [];
327
351
  const files = [];
328
- let setupRequired = draft.reconstruction;
329
352
  for (const action of draft.actions) {
330
353
  if (action.kind === "keep") {
331
354
  actions.push(action);
@@ -335,19 +358,16 @@ async function resolveFilePlan(repoDir, draft, decisions) {
335
358
  if (action.kind === "write") {
336
359
  actions.push(action);
337
360
  files.push(action.record);
338
- setupRequired = true;
339
361
  continue;
340
362
  }
341
363
  if (action.kind === "delete") {
342
364
  actions.push(action);
343
- setupRequired = true;
344
365
  continue;
345
366
  }
346
367
  if (action.kind === "untrack-missing") {
347
368
  actions.push(action);
348
369
  if (action.previous.reconciliations?.length) {
349
370
  files.push({ ...action.previous, retired: true });
350
- setupRequired = true;
351
371
  }
352
372
  continue;
353
373
  }
@@ -375,7 +395,6 @@ async function resolveFilePlan(repoDir, draft, decisions) {
375
395
  };
376
396
  actions.push({ kind: "skill-handoff", path: action.path, content: action.content, backup, basePath, incomingPath, record: record2 });
377
397
  files.push(record2);
378
- setupRequired = true;
379
398
  continue;
380
399
  }
381
400
  if (action.kind === "conflict") {
@@ -391,7 +410,6 @@ async function resolveFilePlan(repoDir, draft, decisions) {
391
410
  actions.push(resolved2);
392
411
  if (record2) files.push(record2);
393
412
  }
394
- setupRequired = true;
395
413
  continue;
396
414
  }
397
415
  const decision = decisionsByPath.get(action.path);
@@ -400,13 +418,27 @@ async function resolveFilePlan(repoDir, draft, decisions) {
400
418
  const { resolved, record } = resolveRemovedConflict(action, decision);
401
419
  actions.push(resolved);
402
420
  if (record) files.push(record);
403
- setupRequired = true;
404
421
  }
405
422
  if (decisionsByPath.size > 0) {
406
423
  const [leftoverPath] = decisionsByPath.keys();
407
424
  fail(`Unexpected decision for path: ${leftoverPath}`);
408
425
  }
409
- return { actions, files, setupRequired };
426
+ const tasks = uniqueSetupTasks([
427
+ ...actions.flatMap((action) => {
428
+ if (action.kind !== "write" && action.kind !== "merge") return [];
429
+ const task = tailoringTask(action.record, action.content);
430
+ return task ? [task] : [];
431
+ }),
432
+ ...reconciliationTasks({ files })
433
+ ]);
434
+ if (draft.reconstruction) {
435
+ for (const action of actions) {
436
+ if (action.kind !== "keep") continue;
437
+ const task = tailoringTask(action.record, await readFile(join(repoDir, action.record.path), "utf8"));
438
+ if (task && PLACEHOLDER.test(await readFile(join(repoDir, action.record.path), "utf8"))) tasks.push(task);
439
+ }
440
+ }
441
+ return { actions, files, tasks: uniqueSetupTasks(tasks), setupRequired: tasks.length > 0 };
410
442
  }
411
443
 
412
444
  // src/update/adapter-plan.ts
@@ -514,7 +546,7 @@ async function preflightAdapters(repoDir, manifest) {
514
546
  }
515
547
  if (!adapterStat.isDirectory()) driftError(adapterRel);
516
548
  const expected = filesUnderRoot(manifest.files, kind, name);
517
- const actualRelFiles = await listFilesRecursive(adapterAbs);
549
+ const actualRelFiles = (await listFilesRecursive(adapterAbs)).filter((path) => !isChecklistPayload(`.agents/${kind}/${name}/${path}`));
518
550
  if (actualRelFiles.length !== expected.length) driftError(adapterRel);
519
551
  for (const relFile of actualRelFiles) {
520
552
  const record = expected.find((file) => file.path === `.agents/${kind}/${name}/${relFile}`);
@@ -536,12 +568,16 @@ async function preflightAdapterCollisions(input) {
536
568
  const currentHarnessSet = new Set(currentHarnesses);
537
569
  for (const harness of desiredHarnesses) {
538
570
  for (const root of candidateRoots) {
539
- if (currentHarnessSet.has(harness) && previousRootKeys.has(root)) continue;
540
571
  const [kind, name] = splitRoot(root);
541
572
  const adapterRel = join2(`.${harness}`, kind, name);
542
- if (await lstat(join2(repoDir, adapterRel)).catch(() => null)) {
543
- throw new Error(`Adapter collision: ${adapterRel} already exists and is not managed by bearings`);
573
+ const adapterAbs = join2(repoDir, adapterRel);
574
+ const existing = await lstat(adapterAbs).catch(() => null);
575
+ if (!existing) continue;
576
+ if (currentHarnessSet.has(harness) && previousRootKeys.has(root)) {
577
+ const matches = existing.isSymbolicLink() ? await readlink(adapterAbs) === join2("..", "..", ".agents", kind, name) : await entriesMatch(join2(repoDir, ".agents", kind, name), adapterAbs);
578
+ if (matches) continue;
544
579
  }
580
+ throw new Error(`Adapter collision: ${adapterRel} already exists and is not managed by bearings`);
545
581
  }
546
582
  }
547
583
  }
@@ -618,191 +654,9 @@ async function planAdapterActions(input) {
618
654
  return actions;
619
655
  }
620
656
 
621
- // src/update/transaction.ts
622
- import { randomUUID } from "crypto";
623
- import { cp, lstat as lstat2, mkdir, readFile as readFile3, readdir as readdir2, rename, rm, rmdir, symlink, writeFile } from "fs/promises";
624
- import { dirname, join as join3, relative, sep } from "path";
625
- var MANIFEST_RELATIVE = join3(".agents", "bearings.json");
626
- async function capture(repoDir, txDir, relativePath, snapshots) {
627
- const target = join3(repoDir, relativePath);
628
- const stat = await lstat2(target).catch(() => null);
629
- if (!stat) {
630
- snapshots.push({ target });
631
- return;
632
- }
633
- const stored = join3(txDir, "snapshots", String(snapshots.length));
634
- await mkdir(dirname(stored), { recursive: true });
635
- await rename(target, stored);
636
- snapshots.push({ target, stored });
637
- }
638
- async function captureToBackup(repoDir, txDir, relativePath, backupRelativePath, snapshots) {
639
- await capture(repoDir, txDir, relativePath, snapshots);
640
- const sourceSnapshot = snapshots.at(-1);
641
- if (!sourceSnapshot?.stored) throw new Error(`Cannot back up missing path: ${relativePath}`);
642
- await writeBackup(repoDir, relativePath, await readFile3(sourceSnapshot.stored), { path: backupRelativePath });
643
- snapshots.push({ target: join3(repoDir, backupRelativePath) });
644
- }
645
- async function ensureDir(repoDir, absDir, createdDirs) {
646
- const rel = relative(repoDir, absDir);
647
- if (!rel || rel.startsWith("..")) return;
648
- let current = repoDir;
649
- for (const part of rel.split(sep)) {
650
- current = join3(current, part);
651
- const stat = await lstat2(current).catch(() => null);
652
- if (!stat) {
653
- await mkdir(current);
654
- createdDirs.push(current);
655
- }
656
- }
657
- }
658
- async function writeContent(repoDir, txDir, relativePath, content, snapshots, createdDirs) {
659
- await capture(repoDir, txDir, relativePath, snapshots);
660
- const target = join3(repoDir, relativePath);
661
- await ensureDir(repoDir, dirname(target), createdDirs);
662
- await writeFile(target, content);
663
- }
664
- async function restore(snapshots, hooks, txDir) {
665
- for (const snapshot of [...snapshots].reverse()) {
666
- try {
667
- await hooks.beforeRollbackRestore?.(snapshot.target);
668
- await rm(snapshot.target, { recursive: true, force: true });
669
- if (snapshot.stored) {
670
- await mkdir(dirname(snapshot.target), { recursive: true });
671
- await rename(snapshot.stored, snapshot.target);
672
- }
673
- } catch (error) {
674
- throw new Error(
675
- `Rollback failed while restoring ${snapshot.target}; transaction data retained at ${txDir}. Underlying error: ${error.message}`
676
- );
677
- }
678
- }
679
- }
680
- async function removeEmptyDirs(createdDirs) {
681
- for (const dir of [...createdDirs].reverse()) {
682
- try {
683
- const entries = await readdir2(dir);
684
- if (entries.length === 0) await rmdir(dir);
685
- } catch {
686
- }
687
- }
688
- }
689
- async function removeEmptyCanonicalParents(repoDir, paths) {
690
- const skillsRoot = join3(repoDir, ".agents", "skills");
691
- const candidates = /* @__PURE__ */ new Set();
692
- for (const path of paths) {
693
- let current = dirname(join3(repoDir, path));
694
- while (current.startsWith(`${skillsRoot}${sep}`)) {
695
- candidates.add(current);
696
- current = dirname(current);
697
- }
698
- }
699
- for (const dir of [...candidates].sort((a, b) => b.length - a.length)) {
700
- try {
701
- await rmdir(dir);
702
- } catch (error) {
703
- const code = error.code;
704
- if (code !== "ENOENT" && code !== "ENOTEMPTY") throw error;
705
- }
706
- }
707
- }
708
- async function applyUpdate(repoDir, plan, hooks = {}) {
709
- const txDir = join3(repoDir, ".agents", `.bearings-txn-${randomUUID()}`);
710
- const snapshots = [];
711
- const createdDirs = [];
712
- const abs = (relativePath) => join3(repoDir, relativePath);
713
- let opIndex = 0;
714
- const afterOp = async () => {
715
- const current = opIndex;
716
- opIndex += 1;
717
- await hooks.afterOperation?.(current);
718
- };
719
- try {
720
- for (const action of plan.files) {
721
- if (action.kind === "write") {
722
- await writeContent(repoDir, txDir, action.path, action.content, snapshots, createdDirs);
723
- if (isStarterSkillTarget(action.path)) {
724
- await writeContent(
725
- repoDir,
726
- txDir,
727
- skillBaselinePath(starterSkillNameFromTarget(action.path)),
728
- action.content,
729
- snapshots,
730
- createdDirs
731
- );
732
- }
733
- await afterOp();
734
- } else if (action.kind === "merge") {
735
- await captureToBackup(repoDir, txDir, action.path, action.backup, snapshots);
736
- await writeFile(abs(action.path), action.content);
737
- await afterOp();
738
- } else if (action.kind === "skill-handoff") {
739
- await writeBackup(repoDir, action.path, await readFile3(abs(action.path)), { path: action.backup });
740
- snapshots.push({ target: abs(action.backup) });
741
- await writeContent(repoDir, txDir, action.incomingPath, action.content, snapshots, createdDirs);
742
- await afterOp();
743
- } else if (action.kind === "delete") {
744
- await capture(repoDir, txDir, action.path, snapshots);
745
- await afterOp();
746
- }
747
- }
748
- await removeEmptyCanonicalParents(
749
- repoDir,
750
- plan.files.filter((action) => action.kind === "delete").map((action) => action.path)
751
- );
752
- for (const action of plan.migrations ?? []) {
753
- if (action.kind === "migrate-testplan") {
754
- if (action.backup) await captureToBackup(repoDir, txDir, action.target, action.backup, snapshots);
755
- else await capture(repoDir, txDir, action.target, snapshots);
756
- await capture(repoDir, txDir, action.source, snapshots);
757
- await ensureDir(repoDir, dirname(abs(action.target)), createdDirs);
758
- await writeFile(abs(action.target), action.content);
759
- await afterOp();
760
- } else if (action.kind === "preserve-invalid-testplan") {
761
- await captureToBackup(repoDir, txDir, action.source, action.backup, snapshots);
762
- await afterOp();
763
- } else {
764
- const entries = await readdir2(abs(action.path));
765
- if (entries.length === 0) await capture(repoDir, txDir, action.path, snapshots);
766
- await afterOp();
767
- }
768
- }
769
- for (const action of plan.adapters) {
770
- if (action.kind === "write-symlink") {
771
- await capture(repoDir, txDir, action.path, snapshots);
772
- await ensureDir(repoDir, dirname(abs(action.path)), createdDirs);
773
- await symlink(action.target, abs(action.path));
774
- await afterOp();
775
- } else if (action.kind === "write-copy") {
776
- await capture(repoDir, txDir, action.path, snapshots);
777
- await ensureDir(repoDir, dirname(abs(action.path)), createdDirs);
778
- await cp(action.source, abs(action.path), { recursive: true });
779
- await afterOp();
780
- } else {
781
- await capture(repoDir, txDir, action.path, snapshots);
782
- await afterOp();
783
- }
784
- }
785
- await capture(repoDir, txDir, MANIFEST_RELATIVE, snapshots);
786
- await ensureDir(repoDir, dirname(abs(MANIFEST_RELATIVE)), createdDirs);
787
- await writeFile(abs(MANIFEST_RELATIVE), JSON.stringify(plan.manifest, null, 2) + "\n");
788
- await afterOp();
789
- } catch (error) {
790
- await restore(snapshots, hooks, txDir);
791
- await removeEmptyDirs(createdDirs);
792
- await rm(txDir, { recursive: true, force: true }).catch(() => {
793
- });
794
- throw error;
795
- }
796
- try {
797
- await hooks.beforeCleanup?.();
798
- await rm(txDir, { recursive: true, force: true });
799
- } catch {
800
- }
801
- }
802
-
803
657
  // src/update/testplan-migration.ts
804
- import { access as access2, readFile as readFile4, readdir as readdir3 } from "fs/promises";
805
- import { join as join4 } from "path";
658
+ import { access as access2, readFile as readFile3, readdir as readdir2 } from "fs/promises";
659
+ import { join as join3 } from "path";
806
660
  var LEGACY_NAME = /^(.+)\.testplan\.json$/;
807
661
  var SCOPE = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
808
662
  async function exists2(path) {
@@ -887,8 +741,8 @@ function convertLegacyPlan(plan) {
887
741
  return JSON.stringify(checklist, null, 2) + "\n";
888
742
  }
889
743
  async function planTestplanMigrations(repoDir) {
890
- const dir = join4(repoDir, "testplans");
891
- const entries = await readdir3(dir, { withFileTypes: true }).catch((error) => {
744
+ const dir = join3(repoDir, "testplans");
745
+ const entries = await readdir2(dir, { withFileTypes: true }).catch((error) => {
892
746
  if (error.code === "ENOENT") return null;
893
747
  throw error;
894
748
  });
@@ -902,7 +756,7 @@ async function planTestplanMigrations(repoDir) {
902
756
  const source = `testplans/${entry.name}`;
903
757
  const slug = match[1];
904
758
  const target = SCOPE.test(slug) ? `checklists/tests/${slug}.json` : void 0;
905
- const raw = await readFile4(join4(repoDir, source), "utf8");
759
+ const raw = await readFile3(join3(repoDir, source), "utf8");
906
760
  let parsed;
907
761
  try {
908
762
  parsed = JSON.parse(raw);
@@ -922,9 +776,9 @@ async function planTestplanMigrations(repoDir) {
922
776
  continue;
923
777
  }
924
778
  const content = convertLegacyPlan(parsed);
925
- if (await exists2(join4(repoDir, target))) {
779
+ if (await exists2(join3(repoDir, target))) {
926
780
  const backup = await freeBackupPath(repoDir, target);
927
- const collided = await readFile4(join4(repoDir, target), "utf8");
781
+ const collided = await readFile3(join3(repoDir, target), "utf8");
928
782
  actions.push({ kind: "migrate-testplan", source, target, content, backup });
929
783
  reconciliations.push({
930
784
  kind: "testplan-collision",
@@ -953,7 +807,7 @@ function cancelUpdate(p) {
953
807
  async function chooseConflict(p, action) {
954
808
  const options = [
955
809
  { value: "replace", label: "Replace", hint: "discard current content" },
956
- { value: "merge", label: "Merge", hint: "back up current content for /setup-repo" },
810
+ { value: "merge", label: "Merge", hint: "back up current content for /update-bearings-setup" },
957
811
  ...action.canSkip ? [{ value: "skip", label: "Skip", hint: "decline this template revision" }] : []
958
812
  ];
959
813
  const selected = await p.select({
@@ -1010,14 +864,21 @@ async function confirmUpdatePlan(p, message) {
1010
864
 
1011
865
  // src/commands/update.ts
1012
866
  async function runUpdate(repoDir, flags, version, state) {
867
+ let normalizationChanged = false;
868
+ if (state.kind === "valid" && state.manifest.version === 2) {
869
+ const normalized = await normalizeSetup(repoDir, state.manifest);
870
+ normalizationChanged = !isDeepStrictEqual(normalized, state.manifest);
871
+ state = { kind: "valid", manifest: normalized };
872
+ if (normalized.setupPending) {
873
+ if (normalizationChanged) await saveManifest(repoDir, normalized);
874
+ return { manifest: normalized, report: pendingSetupReport(normalized) };
875
+ }
876
+ }
1013
877
  if (!process.stdin.isTTY) {
1014
878
  throw new Error("Updating an initialized repository requires an interactive terminal.");
1015
879
  }
1016
880
  const migratedFromV1 = state.kind === "valid" && state.manifest.version === 1;
1017
881
  const current = state.kind === "valid" ? migratedFromV1 ? await migrateV1(repoDir, state.manifest) : state.manifest : null;
1018
- if (!migratedFromV1 && (current?.setupPending || current?.migrationReconciliations?.length || current?.files.some((file) => file.reconciliations?.length))) {
1019
- throw new Error("Finish the pending /setup-repo handoff before running bearings init again.");
1020
- }
1021
882
  if (current && compareSemver(version, current.bearingsVersion) < 0) {
1022
883
  throw new Error(`Refusing to downgrade bearings from ${current.bearingsVersion} to ${version}.`);
1023
884
  }
@@ -1068,11 +929,11 @@ async function runUpdate(repoDir, flags, version, state) {
1068
929
  const exposureUnchanged = current ? current.exposure === exposure : false;
1069
930
  const noArtifactChanges = resolved.actions.every((a) => a.kind === "keep") && migrationPlan.actions.length === 0 && adapterActions.length === 0;
1070
931
  const versionUnchanged = !!current && version === current.bearingsVersion;
1071
- if (!reconstruction && !migratedFromV1 && noArtifactChanges && versionUnchanged && harnessesUnchanged && exposureUnchanged) {
932
+ if (!reconstruction && !migratedFromV1 && !normalizationChanged && noArtifactChanges && versionUnchanged && harnessesUnchanged && exposureUnchanged) {
1072
933
  return { manifest: current, report: "bearings init: already up to date." };
1073
934
  }
1074
- const migratedAbsorbedBackup = migratedFromV1 && (current?.files.some((file) => file.reconciliations?.length) ?? false);
1075
- const setupRequired = resolved.setupRequired || migrationPlan.actions.length > 0 || adapterActions.length > 0 || migratedAbsorbedBackup;
935
+ const tasks = uniqueSetupTasks([...resolved.tasks, ...reconciliationTasks({ files: [...resolved.files], migrationReconciliations: [...migrationPlan.reconciliations] })]);
936
+ const setupRequired = tasks.length > 0;
1076
937
  const finalManifest = {
1077
938
  version: 2,
1078
939
  bearingsVersion: version,
@@ -1082,7 +943,9 @@ async function runUpdate(repoDir, flags, version, state) {
1082
943
  setupPending: {
1083
944
  kind: reconstruction ? "reconstruction" : "update",
1084
945
  ...current ? { fromVersion: current.bearingsVersion } : {},
1085
- toVersion: version
946
+ toVersion: version,
947
+ tasks,
948
+ protectedSkills: await protectSkills(repoDir, tasks, resolved.actions, setupArtifacts({ files: [...resolved.files], migrationReconciliations: [...migrationPlan.reconciliations] }))
1086
949
  }
1087
950
  } : {},
1088
951
  ...migrationPlan.reconciliations.length > 0 ? { migrationReconciliations: [...migrationPlan.reconciliations] } : {},
@@ -1105,7 +968,7 @@ async function runUpdate(repoDir, flags, version, state) {
1105
968
  adapters: adapterActions,
1106
969
  manifest: finalManifest
1107
970
  });
1108
- return { manifest: finalManifest, report: renderUpdateReport({ ...summaryInput, setupRequired }) };
971
+ return { manifest: finalManifest, report: renderUpdateReport({ ...summaryInput, setupRequired, manifest: finalManifest }) };
1109
972
  }
1110
973
  export {
1111
974
  runUpdate
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "bearings",
3
- "version": "0.5.2",
3
+ "version": "0.5.4",
4
4
  "description": "Give your AI agents their bearings — scaffold an agent-friendly setup in any repository.",
5
5
  "type": "module",
6
6
  "engines": {
@@ -1,9 +1,5 @@
1
1
  # <agent: project name> - Agent Seed
2
2
 
3
- ## Setup Required
4
-
5
- Until this section is removed, do not inspect, modify, or run anything in this repository except `/setup-repo`. For every other request, respond only: Run `/setup-repo` to finalize bearings, then retry your request.
6
-
7
3
  ## Project
8
4
 
9
5
  <agent: fill during handoff — One sentence: problem, primary users, and outcome.>
@@ -22,4 +18,4 @@ Primary stack: <agent: fill during handoff — language/runtime/framework/platfo
22
18
 
23
19
  ## User-Facing Output
24
20
 
25
- Assume the user has ADHD and cannot comprehend long descriptions. Tailor all your responses to the user's reading ability.
21
+ The user has ADHD and cannot comprehend long descriptions. Tailor all your responses to the user's reading ability.
@@ -1,5 +1,5 @@
1
1
  ---
2
- description: Reconcile the domain, architecture, current codebase map, and C4 component source when meaningful repository drift or staleness is detected
2
+ description: Reconcile repository maps and C4 component source after meaningful drift, or only explicit map targets supplied by bearings setup
3
3
  ---
4
4
 
5
5
  # /refresh-repo-map
@@ -25,8 +25,25 @@ when the maps remain accurate.
25
25
  | `docs/CODEBASE_MAP.md` | this command |
26
26
  | `docs/diagrams/c4-component.puml` | this command |
27
27
 
28
- Always create `docs/diagrams/` and `c4-component.puml` when absent. Maintain
29
- source-only PlantUML: never render it and never modify another diagram.
28
+ ## Target Scope
29
+
30
+ Ordinary developer-invoked refresh covers all four owned artifacts. In that
31
+ mode, create `docs/diagrams/` and `c4-component.puml` when absent.
32
+
33
+ Setup callers must supply an explicit list of target paths from their pending
34
+ `reason: map` tasks. Select only those paths from the ownership table. An empty
35
+ setup list means no refresh; a missing or inconsistent setup scope is a blocker,
36
+ not a request for the ordinary full set. One map task selects one artifact.
37
+
38
+ Read related repository evidence as needed, but edit or create only selected
39
+ targets, including their sync markers. Do not create missing C4 source when
40
+ it is outside the supplied scope. Apply supplied reconciliation choices through
41
+ this command while retaining the selected artifact's contract. Report related
42
+ out-of-scope drift without expanding setup's write scope.
43
+
44
+ The contracts, mode selection, evidence rules, and validation below apply per
45
+ selected artifact. Maintain source-only PlantUML: never render it and never
46
+ modify another diagram.
30
47
 
31
48
  ## Artifact Contracts
32
49
 
@@ -75,9 +92,10 @@ existing map wording.
75
92
  ## Baselines and Mode Selection
76
93
 
77
94
  Each artifact records its own reconciliation baseline, so mode selection is
78
- independent per artifact:
95
+ independent per selected artifact; full reconciliation never expands the
96
+ selected target set:
79
97
 
80
- - Full reconciliation when the artifact (or the C4 source) is missing, its
98
+ - Full reconciliation when the selected artifact is missing, its
81
99
  marker is missing, malformed, unavailable locally, or not an ancestor of
82
100
  pre-command `HEAD`, the user requests a full refresh, or incremental
83
101
  evidence reveals structural drift outside the expected impact closure.
@@ -95,8 +113,8 @@ independent per artifact:
95
113
  5. Trace implementations and tests for critical flows and hazards.
96
114
  6. Read accepted ADRs and confirmed domain/business intent that constrain
97
115
  current code.
98
- 7. Reconcile the artifact's complete required output and the C4 component
99
- model when its topology is in scope.
116
+ 7. Reconcile the selected artifact's complete required output. Reconcile the
117
+ C4 component model only when its path is selected.
100
118
  8. Set that artifact's sync marker to the captured pre-command `HEAD` only
101
119
  after its reconciliation succeeds.
102
120
 
@@ -116,8 +134,8 @@ its largest file.
116
134
  relationships to establish impact closure.
117
135
  5. Edit only the affected sections of that artifact and preserve unrelated
118
136
  verified content.
119
- 6. Update the C4 model only when component boundaries or relationships
120
- changed.
137
+ 6. Update the C4 model only when its path is selected and component boundaries
138
+ or relationships changed.
121
139
  7. Set that artifact's sync marker to the captured pre-command `HEAD` only
122
140
  after its reconciliation succeeds.
123
141
  8. Escalate that artifact to full reconciliation when impact closure cannot
@@ -127,10 +145,12 @@ its largest file.
127
145
 
128
146
  Before reporting completion:
129
147
 
130
- 1. Validate every retained path, symbol, command, and relationship against
131
- the repository.
148
+ 1. Validate every retained path, symbol, command, and relationship in each
149
+ selected artifact against the repository.
132
150
  2. Run these probes in fresh context that receives only the draft
133
- artifacts and the scenario, never the source-reading context:
151
+ selected artifacts and the scenario, never the source-reading context.
152
+ Judge each probe against those artifacts' authority and required output;
153
+ do not require an unselected artifact's content:
134
154
  - Explain: select a critical behavior; identify entry point, flow,
135
155
  state/side effects, failure semantics, and targeted source.
136
156
  - Feature: select a plausible extension; identify owner, extension seam,
@@ -138,7 +158,10 @@ Before reporting completion:
138
158
  - Bug: select a plausible public symptom; narrow diagnosis to an owning
139
159
  flow, likely files, and regression-test location.
140
160
  3. Rerun any failed probe after adding the missing knowledge. A failed
141
- probe blocks completion for the affected artifact.
161
+ probe blocks completion for the affected artifact.
162
+
163
+ Keep probe fixes within selected targets. Report missing out-of-scope knowledge
164
+ separately; probes do not authorize creating or refreshing other maps.
142
165
 
143
166
  ## Completion
144
167