@basou/core 0.30.0 → 0.32.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/index.js CHANGED
@@ -910,7 +910,10 @@ import { z as z2 } from "zod";
910
910
 
911
911
  // src/schemas/shared.schema.ts
912
912
  import { z } from "zod";
913
- var SchemaVersionSchema = z.literal("0.1.0");
913
+ var SchemaVersionSchema = z.string().regex(/^0\.\d+\.\d+$/, {
914
+ message: "unsupported .basou format version: this basou reads format major 0 (0.x.y). If this workspace was written by a newer basou, upgrade basou to open it."
915
+ });
916
+ var CacheVersionSchema = z.literal("0.1.0");
914
917
  var IsoTimestampSchema = z.string().datetime({ offset: true });
915
918
  var createPrefixedIdSchema = (prefix) => {
916
919
  const refiner = (value) => isValidPrefixedId(value) && value.startsWith(`${prefix}_`);
@@ -930,14 +933,14 @@ var EventSourceSchema = z.string().min(1);
930
933
 
931
934
  // src/schemas/approval.schema.ts
932
935
  var ApprovalStatusSchema = z2.enum(["pending", "approved", "rejected", "expired"]);
933
- var ApprovalSchema = z2.object({
936
+ var ApprovalSchema = z2.looseObject({
934
937
  schema_version: SchemaVersionSchema,
935
938
  id: ApprovalIdSchema,
936
939
  session_id: SessionIdSchema,
937
940
  created_at: IsoTimestampSchema,
938
941
  status: ApprovalStatusSchema,
939
942
  risk_level: RiskLevelSchema,
940
- action: z2.object({ kind: z2.string() }).passthrough(),
943
+ action: z2.looseObject({ kind: z2.string() }).passthrough(),
941
944
  reason: z2.string(),
942
945
  expires_at: IsoTimestampSchema.nullable().default(null),
943
946
  // The four fields below are null while `status === "pending"` and set
@@ -1590,7 +1593,7 @@ var SessionSourceKindSchema = z4.enum([
1590
1593
  "import",
1591
1594
  "terminal"
1592
1595
  ]);
1593
- var SessionSourceSchema = z4.object({
1596
+ var SessionSourceSchema = z4.looseObject({
1594
1597
  kind: SessionSourceKindSchema,
1595
1598
  version: z4.literal("0.1.0"),
1596
1599
  // Optional id of the originating session in the SOURCE tool's own
@@ -1606,20 +1609,20 @@ var SessionSourceSchema = z4.object({
1606
1609
  // fresh import or `--force`).
1607
1610
  source_size_bytes: z4.number().int().nonnegative().optional()
1608
1611
  });
1609
- var InvocationSchema = z4.object({
1612
+ var InvocationSchema = z4.looseObject({
1610
1613
  command: z4.string().min(1),
1611
1614
  args: z4.array(z4.string()).default([]),
1612
1615
  // Nullable to record signal-terminated runs where the child has no exit
1613
1616
  // code; the same nullability is mirrored in CommandExecutedEventSchema.
1614
1617
  exit_code: z4.number().int().nullable()
1615
1618
  });
1616
- var SessionMetricsSchema = z4.object({
1619
+ var SessionMetricsSchema = z4.looseObject({
1617
1620
  output_tokens: z4.number().int().nonnegative().optional(),
1618
1621
  input_tokens: z4.number().int().nonnegative().optional(),
1619
1622
  cached_input_tokens: z4.number().int().nonnegative().optional(),
1620
1623
  reasoning_output_tokens: z4.number().int().nonnegative().optional(),
1621
1624
  active_time_ms: z4.number().int().nonnegative().optional(),
1622
- active_intervals: z4.array(z4.object({ start: IsoTimestampSchema, end: IsoTimestampSchema })).optional(),
1625
+ active_intervals: z4.array(z4.looseObject({ start: IsoTimestampSchema, end: IsoTimestampSchema })).optional(),
1623
1626
  active_gap_cap_ms: z4.number().int().nonnegative().optional(),
1624
1627
  active_time_method: z4.string().optional(),
1625
1628
  machine_active_time_ms: z4.number().int().nonnegative().optional()
@@ -1628,7 +1631,7 @@ var SessionIntegritySchema = z4.object({
1628
1631
  head_hash: z4.string(),
1629
1632
  event_count: z4.number().int().nonnegative()
1630
1633
  }).strict();
1631
- var SessionInnerSchema = z4.object({
1634
+ var SessionInnerSchema = z4.looseObject({
1632
1635
  id: SessionIdSchema,
1633
1636
  label: z4.string().optional(),
1634
1637
  task_id: TaskIdSchema.nullable().optional(),
@@ -1646,7 +1649,7 @@ var SessionInnerSchema = z4.object({
1646
1649
  metrics: SessionMetricsSchema.optional(),
1647
1650
  integrity: SessionIntegritySchema.optional()
1648
1651
  });
1649
- var SessionSchema = z4.object({
1652
+ var SessionSchema = z4.looseObject({
1650
1653
  schema_version: SchemaVersionSchema,
1651
1654
  session: SessionInnerSchema
1652
1655
  });
@@ -2146,12 +2149,16 @@ import * as fsp from "fs/promises";
2146
2149
  // src/schemas/status.schema.ts
2147
2150
  import { z as z5 } from "zod";
2148
2151
  var StatusSchema = z5.object({
2149
- schema_version: SchemaVersionSchema,
2152
+ // status.json is a rebuildable cache: exact-match-or-rebuild, not the
2153
+ // durable forward-compat gate.
2154
+ schema_version: CacheVersionSchema,
2150
2155
  generated_at: IsoTimestampSchema,
2151
2156
  workspace: z5.object({
2152
2157
  id: WorkspaceIdSchema,
2153
2158
  name: z5.string().min(1),
2154
- basou_version: z5.literal("0.1.0")
2159
+ // Mirrors the manifest's basou_version, so it uses the same
2160
+ // forward-compatible format gate (accept 0.x.y) rather than a literal.
2161
+ basou_version: SchemaVersionSchema
2155
2162
  }).strict(),
2156
2163
  directories_present: z5.object({
2157
2164
  sessions: z5.boolean(),
@@ -2511,7 +2518,7 @@ import { z as z8 } from "zod";
2511
2518
  // src/schemas/task.schema.ts
2512
2519
  import { z as z6 } from "zod";
2513
2520
  var TaskStatusSchema = z6.enum(["planned", "in_progress", "done", "cancelled"]);
2514
- var TaskInnerSchema = z6.object({
2521
+ var TaskInnerSchema = z6.looseObject({
2515
2522
  id: TaskIdSchema,
2516
2523
  title: z6.string().min(1),
2517
2524
  label: z6.string().min(1).optional(),
@@ -2540,7 +2547,7 @@ var TaskInnerSchema = z6.object({
2540
2547
  */
2541
2548
  linked_sessions: z6.array(SessionIdSchema).default([])
2542
2549
  });
2543
- var TaskSchema = z6.object({
2550
+ var TaskSchema = z6.looseObject({
2544
2551
  schema_version: SchemaVersionSchema,
2545
2552
  task: TaskInnerSchema
2546
2553
  });
@@ -2790,7 +2797,8 @@ var TaskIndexEntrySchema = z7.object({
2790
2797
  updated_at: IsoTimestampSchema
2791
2798
  }).strict();
2792
2799
  var TaskIndexSchema = z7.object({
2793
- schema_version: SchemaVersionSchema,
2800
+ // Rebuildable cache: exact-match-or-rebuild, not the durable forward-compat gate.
2801
+ schema_version: CacheVersionSchema,
2794
2802
  tasks: z7.array(TaskIndexEntrySchema),
2795
2803
  last_rebuilt_at: IsoTimestampSchema
2796
2804
  }).strict();
@@ -4728,8 +4736,7 @@ import { lstat as lstat3 } from "fs/promises";
4728
4736
  import { z as z9 } from "zod";
4729
4737
  var ProjectSchema = z9.looseObject({
4730
4738
  name: z9.string().optional(),
4731
- description: z9.string().optional(),
4732
- repository_url: z9.string().nullable().optional()
4739
+ description: z9.string().optional()
4733
4740
  });
4734
4741
  var CapabilitiesSchema = z9.looseObject({
4735
4742
  enabled: z9.array(z9.string())
@@ -4787,7 +4794,11 @@ var WorkspaceMetaSchema = z9.looseObject({
4787
4794
  });
4788
4795
  var ManifestSchema = z9.looseObject({
4789
4796
  schema_version: SchemaVersionSchema,
4790
- basou_version: z9.literal("0.1.0"),
4797
+ // Same forward-compatible format gate as schema_version (accept 0.x.y, gate a
4798
+ // higher major with an upgrade error) rather than a hard literal. `basou_version`
4799
+ // is a format stamp, not the npm/product version; consolidating it with
4800
+ // schema_version is a candidate cleanup for the M4 freeze pass.
4801
+ basou_version: SchemaVersionSchema,
4791
4802
  workspace: WorkspaceMetaSchema,
4792
4803
  project: ProjectSchema,
4793
4804
  capabilities: CapabilitiesSchema,
@@ -4811,8 +4822,7 @@ function createManifest(input) {
4811
4822
  const workspaceId = input.workspaceId ?? prefixedUlid("ws");
4812
4823
  const project = {
4813
4824
  ...input.projectName !== void 0 ? { name: input.projectName } : {},
4814
- ...input.projectDescription !== void 0 ? { description: input.projectDescription } : {},
4815
- ...input.repositoryUrl !== void 0 ? { repository_url: input.repositoryUrl } : {}
4825
+ ...input.projectDescription !== void 0 ? { description: input.projectDescription } : {}
4816
4826
  };
4817
4827
  const manifest = {
4818
4828
  schema_version: "0.1.0",
@@ -4842,6 +4852,7 @@ function createManifest(input) {
4842
4852
  async function writeManifest(paths, manifest, options) {
4843
4853
  const force = options?.force === true;
4844
4854
  const validated = ManifestSchema.parse(manifest);
4855
+ delete validated.project.repository_url;
4845
4856
  if (!force) {
4846
4857
  let existed = false;
4847
4858
  try {
@@ -5470,6 +5481,65 @@ function shortId(id) {
5470
5481
  return id.slice(0, sep + 1) + id.slice(sep + 1, sep + 1 + 10);
5471
5482
  }
5472
5483
 
5484
+ // src/project/anchor-starter.ts
5485
+ function renderAnchorStarter(input) {
5486
+ const lines = [];
5487
+ const title = input.projectName ?? input.anchorName;
5488
+ lines.push(`# AGENTS.md (${input.anchorName})`);
5489
+ lines.push("");
5490
+ lines.push(
5491
+ `> \u3053\u306E\u30EA\u30DD\u30B8\u30C8\u30EA\u306F **${title} \u306E planning master(anchor)** \u3067\u3059\u3002\u3053\u3053\u3067\u4F5C\u696D\u3059\u308B AI \u30A8\u30FC\u30B8\u30A7\u30F3\u30C8\u306F\u3001\u307E\u305A\u3053\u306E\u30D5\u30A1\u30A4\u30EB\u3092\u8AAD\u3093\u3067\u304F\u3060\u3055\u3044\u3002`
5492
+ );
5493
+ lines.push(">");
5494
+ lines.push(
5495
+ "> \u3053\u306E\u30D5\u30A1\u30A4\u30EB\u306F `basou project derive` \u304C greenfield \u7ACB\u3061\u4E0A\u3052\u6642\u306B **\u4E00\u5EA6\u3060\u3051\u751F\u6210\u3057\u305F starter** \u3067\u3059\u3002\u4EE5\u5F8C\u306F\u624B\u7BA1\u7406\u3057\u3066\u304F\u3060\u3055\u3044 \u2014 basou \u306F\u518D\u751F\u6210\u3082\u4E0A\u66F8\u304D\u3082\u3057\u307E\u305B\u3093(BASOU:GENERATED \u30DE\u30FC\u30AB\u30FC\u306F\u7121\u304F\u3001\u81EA\u7531\u306B\u7DE8\u96C6\u3067\u304D\u307E\u3059)\u3002"
5496
+ );
5497
+ lines.push("");
5498
+ lines.push("## \u30D7\u30ED\u30B8\u30A7\u30AF\u30C8\u306E\u57FA\u672C\u60C5\u5831");
5499
+ lines.push("");
5500
+ lines.push("<!-- TODO: manifest \u304B\u3089\u306F\u5C0E\u51FA\u3067\u304D\u306A\u3044\u9805\u76EE\u3067\u3059\u3002\u57CB\u3081\u3066\u304F\u3060\u3055\u3044\u3002 -->");
5501
+ lines.push("");
5502
+ lines.push("```text");
5503
+ lines.push(`Product name: ${input.projectName ?? "<!-- TODO -->"}`);
5504
+ lines.push("Domain: <!-- TODO -->");
5505
+ lines.push("GitHub Organization: <!-- TODO -->");
5506
+ lines.push("Public repository: <!-- TODO -->");
5507
+ lines.push("Planning repository: <!-- TODO -->");
5508
+ lines.push("License: <!-- TODO -->");
5509
+ lines.push("```");
5510
+ lines.push("");
5511
+ lines.push("## \u3069\u3053\u3067 commit \u3059\u308B\u304B");
5512
+ lines.push("");
5513
+ lines.push("- **\u3053\u306E\u30EA\u30DD\u30B8\u30C8\u30EA(planning master)**: \u69CB\u60F3\u30FB\u8A08\u753B\u30FB\u8A2D\u8A08\u30C9\u30AD\u30E5\u30E1\u30F3\u30C8\u3002");
5514
+ lines.push("- **\u5404\u5B9F\u88C5 repo**: \u5B9F\u88C5\u30B3\u30FC\u30C9\u3002\u5FC5\u305A\u5BFE\u8C61 repo \u306B `cd` \u3057\u3066\u304B\u3089 commit \u3057\u3066\u304F\u3060\u3055\u3044\u3002");
5515
+ lines.push("- **workspace view**: git \u7BA1\u7406\u5916\u3002view \u3067\u306F commit \u3067\u304D\u307E\u305B\u3093\u3002");
5516
+ lines.push("");
5517
+ lines.push("## \u5FC5\u305A\u8AAD\u3080\u3079\u304D\u898F\u7D04");
5518
+ lines.push("");
5519
+ lines.push("\u4F5C\u696D\u898F\u7D04\u306F\u5404 repo \u306E AGENTS.md \u306B\u3042\u308A\u307E\u3059\u3002\u4EE5\u4E0B\u3092\u8AAD\u3093\u3067\u304B\u3089\u4F5C\u696D\u3057\u3066\u304F\u3060\u3055\u3044\u3002");
5520
+ lines.push("");
5521
+ for (const r of input.repos) {
5522
+ if (r.anchor === true) continue;
5523
+ lines.push(`- ${r.name}/AGENTS.md`);
5524
+ }
5525
+ if (input.viewName !== void 0) {
5526
+ lines.push(
5527
+ `- ${input.viewName}/AGENTS.md(workspace view\u30FBbasou \u304C\u751F\u6210)\u2014 **\u6700\u65B0\u306E repo \u69CB\u6210(roster)\u306F\u3053\u3053\u3092\u6B63\u3068\u3059\u308B**`
5528
+ );
5529
+ }
5530
+ lines.push("");
5531
+ lines.push("## \u4F5C\u696D\u65B9\u91DD(\u30D7\u30ED\u30B8\u30A7\u30AF\u30C8\u56FA\u6709\u4E8B\u9805)");
5532
+ lines.push("");
5533
+ lines.push("<!-- TODO: \u4EE5\u4E0B\u3092\u30D7\u30ED\u30B8\u30A7\u30AF\u30C8\u306B\u5408\u308F\u305B\u3066\u8A18\u8FF0\u3057\u3066\u304F\u3060\u3055\u3044\u3002");
5534
+ lines.push(" - \u73FE\u5728\u306E\u30D5\u30A7\u30FC\u30BA / \u91CD\u8981\u30C9\u30AD\u30E5\u30E1\u30F3\u30C8");
5535
+ lines.push(" - \u6A5F\u5BC6\u60C5\u5831\u306E\u6271\u3044(\u3069\u3053\u306B\u66F8\u304B\u306A\u3044\u304B)");
5536
+ lines.push(" - \u8A00\u8A9E\u30DD\u30EA\u30B7\u30FC(commit / \u30B3\u30E1\u30F3\u30C8 / \u30C9\u30AD\u30E5\u30E1\u30F3\u30C8\u306E\u8A00\u8A9E)");
5537
+ lines.push(" - commit \u904B\u7528(\u6DF7\u5728\u30B3\u30DF\u30C3\u30C8\u3092\u907F\u3051\u308B \u7B49)");
5538
+ lines.push("-->");
5539
+ return `${lines.join("\n")}
5540
+ `;
5541
+ }
5542
+
5473
5543
  // src/project/relative-path.ts
5474
5544
  function normalizeRelativePath(p) {
5475
5545
  const trimmed = p.trim();
@@ -5646,10 +5716,69 @@ function renderPresetBlock(repo) {
5646
5716
  }
5647
5717
  return lines.join("\n");
5648
5718
  }
5719
+ function visibilityShortLabel(v) {
5720
+ return v ?? "\u672A\u8A2D\u5B9A";
5721
+ }
5722
+ function languageShortLabel(l) {
5723
+ return l ?? "\u672A\u8A2D\u5B9A";
5724
+ }
5725
+ function instructionsLabel(repo) {
5726
+ if (repo.anchor === true) return "anchor(\u624B\u7BA1\u7406)";
5727
+ return repo.self === true ? "self(repo \u304C\u81EA\u5DF1\u7BA1\u7406)" : "hub(basou \u304C\u751F\u6210)";
5728
+ }
5729
+ function renderViewPresetBlock(input) {
5730
+ const lines = [];
5731
+ lines.push("## workspace view \u69CB\u6210(basou \u304C\u751F\u6210 \u2014 manifest \u304C\u6B63\u672C)");
5732
+ lines.push("");
5733
+ lines.push(
5734
+ "\u3053\u306E\u30BB\u30AF\u30B7\u30E7\u30F3\u306F `.basou/manifest.yaml` \u306E\u5BA3\u8A00\u304B\u3089 `basou project preset` \u304C\u751F\u6210\u3057\u307E\u3059\u3002\u7DE8\u96C6\u306F manifest \u5074\u3067\u884C\u3063\u3066\u304F\u3060\u3055\u3044(\u30DE\u30FC\u30AB\u30FC\u5916\u306E\u8A18\u8FF0\u306F\u4FDD\u6301\u3055\u308C\u307E\u3059)\u3002"
5735
+ );
5736
+ lines.push(
5737
+ `\u3053\u306E AGENTS.md \u81EA\u8EAB\u3082 basou \u306E\u751F\u6210\u7269\u3067\u3059(\u5B9F\u4F53: \`agents/${input.viewName}/AGENTS.md\`\u3001\u30DE\u30FC\u30AB\u30FC\u5916\u306E\u8A18\u8FF0\u306F\u4FDD\u6301\u3055\u308C\u307E\u3059)\u3002`
5738
+ );
5739
+ lines.push("");
5740
+ lines.push(
5741
+ `\u3053\u306E\u30C7\u30A3\u30EC\u30AF\u30C8\u30EA\u306F\u3001\u5BA3\u8A00\u3055\u308C\u305F ${input.repos.length} \u500B\u306E repo \u3092 symlink \u3067\u96C6\u7D04\u3059\u308B **view** \u3067\u3059\u3002\u5B9F\u4F53\u3092\u6301\u305F\u305A\u3001git \u7BA1\u7406\u5916\u3067\u3059\u3002`
5742
+ );
5743
+ lines.push("");
5744
+ lines.push("### \u96C6\u7D04\u3057\u3066\u3044\u308B repo");
5745
+ lines.push("");
5746
+ lines.push("| repo | \u53EF\u8996\u6027 | \u8A00\u8A9E | \u6307\u793A\u66F8 |");
5747
+ lines.push("|---|---|---|---|");
5748
+ for (const r of input.repos) {
5749
+ lines.push(
5750
+ `| ${r.name} | ${visibilityShortLabel(r.visibility)} | ${languageShortLabel(r.language)} | ${instructionsLabel(r)} |`
5751
+ );
5752
+ }
5753
+ lines.push("");
5754
+ lines.push("### \u3069\u3053\u3067 commit \u3059\u308B\u304B");
5755
+ lines.push("");
5756
+ lines.push(
5757
+ "view \u3067\u306F commit \u3067\u304D\u307E\u305B\u3093(git \u7BA1\u7406\u5916)\u3002\u5909\u66F4\u306F\u5FC5\u305A\u5B9F\u4F53\u306E repo \u306B `cd` \u3057\u3066\u304B\u3089 commit \u3057\u3066\u304F\u3060\u3055\u3044\u3002"
5758
+ );
5759
+ lines.push("");
5760
+ for (const r of input.repos) {
5761
+ lines.push(`- ${r.name} \u2192 \`cd ${r.name}\``);
5762
+ }
5763
+ lines.push("");
5764
+ lines.push("### \u5FC5\u305A\u8AAD\u3080\u3079\u304D\u898F\u7D04");
5765
+ lines.push("");
5766
+ lines.push("\u4F5C\u696D\u898F\u7D04\u306F\u5404 repo \u306E AGENTS.md \u306B\u3042\u308A\u307E\u3059\u3002\u4EE5\u4E0B\u3092\u8AAD\u3093\u3067\u304B\u3089\u4F5C\u696D\u3057\u3066\u304F\u3060\u3055\u3044\u3002");
5767
+ lines.push("");
5768
+ for (const r of input.repos) {
5769
+ lines.push(`- ${r.name}/AGENTS.md`);
5770
+ }
5771
+ lines.push("");
5772
+ lines.push("### \u91CD\u8981\u539F\u5247");
5773
+ lines.push("");
5774
+ lines.push("- \u3053\u306E\u30C7\u30A3\u30EC\u30AF\u30C8\u30EA\u306F\u72B6\u614B\u3092\u6301\u305F\u306A\u3044(git \u7BA1\u7406\u5916)");
5775
+ lines.push("- \u91CD\u8981\u306A\u30D5\u30A1\u30A4\u30EB\u3092\u3053\u3053\u306B\u76F4\u63A5\u7F6E\u304B\u306A\u3044(\u5B9F\u4F53\u306F\u5404 repo \u306B\u7F6E\u304F)");
5776
+ return lines.join("\n");
5777
+ }
5649
5778
  function normalizeBlock(s) {
5650
5779
  return s.replace(/\r\n/g, "\n").replace(/\n+$/, "");
5651
5780
  }
5652
- function summarizePresetPlan(facts) {
5781
+ function summarizePresetPlan(facts, opts) {
5653
5782
  const deduped = [];
5654
5783
  const seenPath = /* @__PURE__ */ new Set();
5655
5784
  for (const f of facts) {
@@ -5669,8 +5798,9 @@ function summarizePresetPlan(facts) {
5669
5798
  const collisions = [];
5670
5799
  const collidingPaths = /* @__PURE__ */ new Set();
5671
5800
  for (const [canonicalName, repos] of byCanonical) {
5672
- if (repos.length > 1) {
5673
- collisions.push({ canonicalName, repos });
5801
+ const viewCollision = opts?.viewCanonicalName !== void 0 && canonicalName === opts.viewCanonicalName;
5802
+ if (repos.length > 1 || viewCollision) {
5803
+ collisions.push({ canonicalName, repos, ...viewCollision ? { view: true } : {} });
5674
5804
  for (const r of repos) collidingPaths.add(r);
5675
5805
  }
5676
5806
  }
@@ -5835,6 +5965,8 @@ function classifyRetrofit(facts) {
5835
5965
  if (facts.agentsState === "symlink")
5836
5966
  return { ...base, action: "skip", reason: "already-symlink" };
5837
5967
  if (facts.agentsState === "absent") return { ...base, action: "skip", reason: "absent" };
5968
+ if (facts.viewCanonicalName !== void 0 && facts.canonicalName === facts.viewCanonicalName)
5969
+ return { ...base, action: "refuse", reason: "view-collision" };
5838
5970
  if (facts.canonicalExists) return { ...base, action: "refuse", reason: "canonical-exists" };
5839
5971
  return {
5840
5972
  ...base,
@@ -5931,7 +6063,7 @@ function summarizeSymlinkPlan(facts) {
5931
6063
  const selfAgentsMissing = [];
5932
6064
  const unreachable = [];
5933
6065
  for (const f of deduped) {
5934
- if (f.isAnchor) continue;
6066
+ if (f.isAnchor && !f.canonicalPresent) continue;
5935
6067
  if (!f.reachable) {
5936
6068
  unreachable.push(f.path);
5937
6069
  continue;
@@ -6010,6 +6142,68 @@ function summarizeWiring(facts) {
6010
6142
  };
6011
6143
  }
6012
6144
 
6145
+ // src/project/wiring-drift.ts
6146
+ function summarizeWiringDrift(input) {
6147
+ const plan = summarizeSymlinkPlan(input.repos);
6148
+ const missingCanonicals = [
6149
+ ...plan.missingCanonical.map((path2) => ({ target: "repo-hub", name: path2 })),
6150
+ ...plan.selfAgentsMissing.map(
6151
+ (path2) => ({ target: "repo-self", name: path2 })
6152
+ )
6153
+ ];
6154
+ const incompleteWiring = plan.plans.map((p) => ({
6155
+ target: "repo",
6156
+ path: p.path,
6157
+ files: p.toCreate.map((c) => c.name)
6158
+ }));
6159
+ const conflicts = plan.conflicts.map((c) => ({
6160
+ target: "repo",
6161
+ path: c.repo,
6162
+ file: c.file,
6163
+ reason: c.reason,
6164
+ ...c.actualTarget !== void 0 ? { actualTarget: c.actualTarget } : {}
6165
+ }));
6166
+ const collisions = plan.collisions.map((c) => ({
6167
+ canonicalName: c.canonicalName,
6168
+ repos: c.repos
6169
+ }));
6170
+ const view = input.view;
6171
+ if (view.kind === "collision") {
6172
+ collisions.push({ canonicalName: view.viewName, repos: [view.repoPath], view: true });
6173
+ } else if (view.kind === "missing-canonical") {
6174
+ missingCanonicals.push({ target: "view", name: view.viewName });
6175
+ } else if (view.kind === "gathered") {
6176
+ const missingSpokes = [];
6177
+ for (const f of view.files) {
6178
+ if (f.state === "missing") {
6179
+ missingSpokes.push(f.name);
6180
+ } else if (f.state === "mismatch" || f.state === "occupied" || f.state === "blocked") {
6181
+ conflicts.push({
6182
+ target: "view",
6183
+ path: view.viewName,
6184
+ file: f.name,
6185
+ reason: f.state,
6186
+ ...f.actualTarget !== void 0 ? { actualTarget: f.actualTarget } : {}
6187
+ });
6188
+ }
6189
+ }
6190
+ if (missingSpokes.length > 0) {
6191
+ incompleteWiring.push({ target: "view", path: view.viewName, files: missingSpokes });
6192
+ }
6193
+ }
6194
+ return {
6195
+ missingCanonicals,
6196
+ incompleteWiring,
6197
+ conflicts,
6198
+ collisions,
6199
+ unreachable: plan.unreachable,
6200
+ // `unreachable` is intentionally NOT part of `ok`: a declared repo that is not
6201
+ // cloned on this machine is an advisory fact (partial checkout), not drift the
6202
+ // operator must fix here. Only actionable drift denies `ok`.
6203
+ ok: missingCanonicals.length === 0 && incompleteWiring.length === 0 && conflicts.length === 0 && collisions.length === 0
6204
+ };
6205
+ }
6206
+
6013
6207
  // src/project/workspace-view.ts
6014
6208
  function planWorkspaceView(facts, existing = [], rosterNames = []) {
6015
6209
  const deduped = [];
@@ -7534,6 +7728,33 @@ ${normalized}${markers.end}${section.after}`;
7534
7728
  throw new Error(`Markers mismatched in ${fileLabel}`);
7535
7729
  }
7536
7730
  }
7731
+ function seedMarkers(existing, generated, fileLabel, markers = DEFAULT_MARKERS) {
7732
+ const normalized = generated.endsWith("\n") ? generated : `${generated}
7733
+ `;
7734
+ if (existing === null) {
7735
+ return `${markers.start}
7736
+ ${normalized}${markers.end}
7737
+ `;
7738
+ }
7739
+ const section = parseMarkers(existing, markers);
7740
+ switch (section.kind) {
7741
+ case "ok":
7742
+ return renderWithMarkers(existing, generated, fileLabel, markers);
7743
+ case "no_markers": {
7744
+ const bom = existing.charCodeAt(0) === 65279 ? "\uFEFF" : "";
7745
+ const body = bom === "" ? existing : existing.slice(1);
7746
+ return `${bom}${markers.start}
7747
+ ${normalized}${markers.end}
7748
+
7749
+ ${body}`;
7750
+ }
7751
+ case "missing_start":
7752
+ case "missing_end":
7753
+ case "multiple_pairs":
7754
+ case "wrong_order":
7755
+ throw new Error(`Markers mismatched in ${fileLabel}`);
7756
+ }
7757
+ }
7537
7758
  function removeMarkerSection(existing, fileLabel, markers = DEFAULT_MARKERS) {
7538
7759
  const section = parseMarkers(existing, markers);
7539
7760
  switch (section.kind) {
@@ -8102,11 +8323,13 @@ export {
8102
8323
  reimportPreservingId,
8103
8324
  removeMarkerSection,
8104
8325
  removeStopHook,
8326
+ renderAnchorStarter,
8105
8327
  renderDecisions,
8106
8328
  renderHandoff,
8107
8329
  renderOrientation,
8108
8330
  renderPresetBlock,
8109
8331
  renderReport,
8332
+ renderViewPresetBlock,
8110
8333
  renderWithMarkers,
8111
8334
  replayEvents,
8112
8335
  resolveBasouRepositoryRoot,
@@ -8119,6 +8342,7 @@ export {
8119
8342
  sanitizePath,
8120
8343
  sanitizeRelatedFiles,
8121
8344
  sanitizeWorkingDirectory,
8345
+ seedMarkers,
8122
8346
  serializeEventLine,
8123
8347
  serializeJsonSchema,
8124
8348
  sessionWorkStatsFromEvents,
@@ -8128,6 +8352,7 @@ export {
8128
8352
  summarizeRosterDrift,
8129
8353
  summarizeSymlinkPlan,
8130
8354
  summarizeWiring,
8355
+ summarizeWiringDrift,
8131
8356
  tryRemoteUrl,
8132
8357
  ulid,
8133
8358
  unknownManifestKeys,