@basou/core 0.15.0 → 0.17.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
@@ -11,10 +11,10 @@ async function resolveClaudeCodeCommand(lookup = isOnPath) {
11
11
  throw new Error("Claude Code CLI not found in PATH. Install claude-code (or claude) first.");
12
12
  }
13
13
  async function isOnPath(command) {
14
- return new Promise((resolve2) => {
14
+ return new Promise((resolve3) => {
15
15
  const child = spawn("which", [command], { stdio: "ignore" });
16
- child.on("error", () => resolve2(false));
17
- child.on("exit", (code) => resolve2(code === 0));
16
+ child.on("error", () => resolve3(false));
17
+ child.on("exit", (code) => resolve3(code === 0));
18
18
  });
19
19
  }
20
20
  function summarizeAdapterOutput(_stream, _raw) {
@@ -1372,7 +1372,8 @@ async function classifySuspect(paths, sessionId, session, now, onWarning) {
1372
1372
  }
1373
1373
  return { suspect: false, suspectReason: null };
1374
1374
  }
1375
- async function loadSessionEntries(paths, options) {
1375
+ async function loadEntriesFromRoot(root, options) {
1376
+ const { paths } = root;
1376
1377
  const sessionIds = await enumerateSessionDirs(paths);
1377
1378
  const entries = [];
1378
1379
  for (const sid of sessionIds) {
@@ -1402,10 +1403,50 @@ async function loadSessionEntries(paths, options) {
1402
1403
  } catch {
1403
1404
  options.onSkip?.(sid, "events_jsonl_unreadable");
1404
1405
  }
1405
- entries.push({ sessionId: sid, session, suspect, suspectReason });
1406
+ entries.push({
1407
+ sessionId: sid,
1408
+ session,
1409
+ suspect,
1410
+ suspectReason,
1411
+ sourceRoot: paths,
1412
+ host: root.host
1413
+ });
1406
1414
  }
1407
1415
  return entries;
1408
1416
  }
1417
+ async function loadSessionEntries(paths, options) {
1418
+ return loadEntriesFromRoot({ paths, host: null }, options);
1419
+ }
1420
+ async function loadFederatedSessionEntries(roots, options) {
1421
+ const out = [];
1422
+ const seenIds = /* @__PURE__ */ new Set();
1423
+ const seenExternal = /* @__PURE__ */ new Set();
1424
+ for (const root of roots) {
1425
+ let entries;
1426
+ if (root.host === null) {
1427
+ entries = await loadEntriesFromRoot(root, options);
1428
+ } else {
1429
+ try {
1430
+ entries = await loadEntriesFromRoot(root, options);
1431
+ } catch (error) {
1432
+ options.onRootUnavailable?.(root.host, error);
1433
+ continue;
1434
+ }
1435
+ }
1436
+ for (const entry of entries) {
1437
+ if (seenIds.has(entry.sessionId)) continue;
1438
+ const ext = entry.session.session.source.external_id;
1439
+ if (typeof ext === "string" && ext.length > 0) {
1440
+ const extKey = `${entry.session.session.source.kind}:${ext}`;
1441
+ if (seenExternal.has(extKey)) continue;
1442
+ seenExternal.add(extKey);
1443
+ }
1444
+ seenIds.add(entry.sessionId);
1445
+ out.push(entry);
1446
+ }
1447
+ }
1448
+ return out;
1449
+ }
1409
1450
 
1410
1451
  // src/decisions/decisions-renderer.ts
1411
1452
  async function renderDecisions(input) {
@@ -4190,8 +4231,82 @@ async function resolveIdInternal(paths, input, kind, options = {}) {
4190
4231
  return matches[0];
4191
4232
  }
4192
4233
 
4234
+ // src/lib/source-root-scope.ts
4235
+ import { promises as fs } from "fs";
4236
+ import { homedir as osHomedir } from "os";
4237
+ import { basename as basename2, dirname as dirname3, isAbsolute, join as join14, normalize, relative, resolve as resolve2 } from "path";
4238
+ var AGENT_INFRA_DIRS = ["~/.claude", "~/.codex", "~/.basou"];
4239
+ async function realpathBestEffort(absPath) {
4240
+ let current = normalize(absPath);
4241
+ const tail = [];
4242
+ for (let guard = 0; guard < 4096; guard += 1) {
4243
+ try {
4244
+ const real = await fs.realpath(current);
4245
+ return tail.length > 0 ? join14(real, ...tail.reverse()) : real;
4246
+ } catch (error) {
4247
+ const code = error?.code;
4248
+ if (code !== "ENOENT" && code !== "ENOTDIR") {
4249
+ return normalize(absPath);
4250
+ }
4251
+ const parent = dirname3(current);
4252
+ if (parent === current) return normalize(absPath);
4253
+ tail.push(basename2(current));
4254
+ current = parent;
4255
+ }
4256
+ }
4257
+ return normalize(absPath);
4258
+ }
4259
+ function expandTilde(p, homedir4) {
4260
+ if (p === "~") return homedir4;
4261
+ if (p.startsWith("~/")) return join14(homedir4, p.slice(2));
4262
+ return p;
4263
+ }
4264
+ function toAbsolute(p, workingDirAbs, homedir4) {
4265
+ const expanded = expandTilde(p, homedir4);
4266
+ if (isAbsolute(expanded)) return normalize(expanded);
4267
+ return normalize(resolve2(workingDirAbs, expanded));
4268
+ }
4269
+ function isUnder(child, parent) {
4270
+ if (child === parent) return true;
4271
+ const rel = relative(parent, child);
4272
+ return rel !== "" && !rel.startsWith("..") && !isAbsolute(rel);
4273
+ }
4274
+ async function classifyFilesBySourceRoot(input) {
4275
+ const inRoot = [];
4276
+ const outOfRoot = [];
4277
+ if (input.files.length === 0) return { inRoot, outOfRoot };
4278
+ const homedir4 = input.homedir ?? osHomedir();
4279
+ const workingDirAbs = toAbsolute(input.workingDirectory, homedir4, homedir4);
4280
+ const declared = input.sourceRoots && input.sourceRoots.length > 0 ? [...input.sourceRoots] : ["."];
4281
+ const rootsAbs = [];
4282
+ for (const r of declared) {
4283
+ const expanded = expandTilde(r, homedir4);
4284
+ const abs = isAbsolute(expanded) ? normalize(expanded) : normalize(resolve2(input.masterRoot, expanded));
4285
+ rootsAbs.push(await realpathBestEffort(abs));
4286
+ }
4287
+ for (const e of input.extraInRoot ?? []) {
4288
+ const expanded = expandTilde(e, homedir4);
4289
+ const abs = isAbsolute(expanded) ? normalize(expanded) : normalize(resolve2(homedir4, expanded));
4290
+ rootsAbs.push(await realpathBestEffort(abs));
4291
+ }
4292
+ if (rootsAbs.length === 0) {
4293
+ return { inRoot: [...input.files], outOfRoot };
4294
+ }
4295
+ for (const file of input.files) {
4296
+ try {
4297
+ const abs = toAbsolute(file, workingDirAbs, homedir4);
4298
+ const real = await realpathBestEffort(abs);
4299
+ const within = rootsAbs.some((root) => isUnder(real, root));
4300
+ (within ? inRoot : outOfRoot).push(file);
4301
+ } catch {
4302
+ inRoot.push(file);
4303
+ }
4304
+ }
4305
+ return { inRoot, outOfRoot };
4306
+ }
4307
+
4193
4308
  // src/orientation/orientation-renderer.ts
4194
- import { join as join14 } from "path";
4309
+ import { dirname as dirname4, join as join15 } from "path";
4195
4310
 
4196
4311
  // src/storage/manifest.ts
4197
4312
  import { lstat as lstat3 } from "fs/promises";
@@ -4344,7 +4459,13 @@ async function summarizeOrientation(input) {
4344
4459
  const loadOpts = { now };
4345
4460
  if (input.onSessionSkip !== void 0) loadOpts.onSkip = input.onSessionSkip;
4346
4461
  if (input.onWarning !== void 0) loadOpts.onWarning = input.onWarning;
4347
- const entries = await loadSessionEntries(input.paths, loadOpts);
4462
+ const entries = input.federatedRoots !== void 0 && input.federatedRoots.length > 0 ? await loadFederatedSessionEntries(
4463
+ [{ paths: input.paths, host: null }, ...input.federatedRoots],
4464
+ {
4465
+ ...loadOpts,
4466
+ ...input.onHostUnavailable !== void 0 ? { onRootUnavailable: input.onHostUnavailable } : {}
4467
+ }
4468
+ ) : await loadSessionEntries(input.paths, loadOpts);
4348
4469
  const decisions = [];
4349
4470
  let latestActivityAt = null;
4350
4471
  let latestNote = null;
@@ -4354,7 +4475,7 @@ async function summarizeOrientation(input) {
4354
4475
  }
4355
4476
  };
4356
4477
  for (const entry of entries) {
4357
- const sessionDir = join14(input.paths.sessions, entry.sessionId);
4478
+ const sessionDir = join15(entry.sourceRoot.sessions, entry.sessionId);
4358
4479
  const counted = entry.session.session.status !== "archived";
4359
4480
  if (counted) noteActivity(entry.session.session.ended_at ?? entry.session.session.started_at);
4360
4481
  try {
@@ -4366,12 +4487,18 @@ async function summarizeOrientation(input) {
4366
4487
  decisionId: ev.decision_id,
4367
4488
  title: ev.title,
4368
4489
  occurredAt: ev.occurred_at,
4369
- sessionId: entry.sessionId
4490
+ sessionId: entry.sessionId,
4491
+ host: entry.host
4370
4492
  });
4371
4493
  }
4372
4494
  if (counted && ev.type === "note_added" && ev.kind === "next_step") {
4373
4495
  if (latestNote === null || Date.parse(ev.occurred_at) > Date.parse(latestNote.occurredAt)) {
4374
- latestNote = { body: ev.body, sessionId: entry.sessionId, occurredAt: ev.occurred_at };
4496
+ latestNote = {
4497
+ body: ev.body,
4498
+ sessionId: entry.sessionId,
4499
+ occurredAt: ev.occurred_at,
4500
+ host: entry.host
4501
+ };
4375
4502
  }
4376
4503
  }
4377
4504
  if (counted) noteActivity(ev.occurred_at);
@@ -4414,7 +4541,8 @@ async function summarizeOrientation(input) {
4414
4541
  const suspects = entries.filter((e) => e.suspect).map((e) => ({
4415
4542
  sessionId: e.sessionId,
4416
4543
  status: e.session.session.status,
4417
- reason: e.suspectReason
4544
+ reason: e.suspectReason,
4545
+ host: e.host
4418
4546
  }));
4419
4547
  const liveEntries = entries.filter(
4420
4548
  (e) => e.session.session.status !== "archived" && e.session.session.source.kind !== "import"
@@ -4423,7 +4551,8 @@ async function summarizeOrientation(input) {
4423
4551
  const latestSession = latestEntry !== void 0 ? {
4424
4552
  sessionId: latestEntry.sessionId,
4425
4553
  label: latestEntry.session.session.label ?? null,
4426
- status: latestEntry.session.session.status
4554
+ status: latestEntry.session.session.status,
4555
+ host: latestEntry.host
4427
4556
  } : null;
4428
4557
  const activityEntries = entries.filter((e) => e.session.session.status !== "archived");
4429
4558
  const newest = [...activityEntries].sort(
@@ -4444,8 +4573,27 @@ async function summarizeOrientation(input) {
4444
4573
  }
4445
4574
  const latestFiles = latestEntry?.session.session.related_files ?? [];
4446
4575
  const uniqueFiles = new Set(latestFiles);
4447
- const displayed = [...uniqueFiles].sort().slice(0, limit);
4576
+ const sortedFiles = [...uniqueFiles].sort();
4577
+ const displayed = sortedFiles.slice(0, limit);
4448
4578
  const overflow = Math.max(0, uniqueFiles.size - limit);
4579
+ let outOfRoot = [];
4580
+ if (latestEntry !== void 0 && latestEntry.host === null && sortedFiles.length > 0 && sourceRoots !== null && sourceRoots.length > 0) {
4581
+ try {
4582
+ const scope = await classifyFilesBySourceRoot({
4583
+ files: sortedFiles,
4584
+ workingDirectory: latestEntry.session.session.working_directory,
4585
+ sourceRoots,
4586
+ masterRoot: dirname4(input.paths.root),
4587
+ extraInRoot: AGENT_INFRA_DIRS
4588
+ });
4589
+ outOfRoot = scope.outOfRoot;
4590
+ } catch {
4591
+ outOfRoot = [];
4592
+ }
4593
+ }
4594
+ const hosts = [
4595
+ ...new Set(entries.map((e) => e.host).filter((h) => h !== null))
4596
+ ].sort();
4449
4597
  return {
4450
4598
  generatedAt: input.nowIso,
4451
4599
  sessionCount: entries.length,
@@ -4453,11 +4601,12 @@ async function summarizeOrientation(input) {
4453
4601
  latestDecision: latestDecision ?? null,
4454
4602
  decisionCount: decisions.length,
4455
4603
  latestNote,
4456
- relatedFiles: { displayed, overflow },
4604
+ relatedFiles: { displayed, overflow, outOfRoot },
4457
4605
  inFlightTasks,
4458
4606
  plannedTasks,
4459
4607
  pendingApprovals,
4460
4608
  suspects,
4609
+ hosts,
4461
4610
  freshness: {
4462
4611
  newestStartedAt: newest?.session.session.started_at ?? null,
4463
4612
  newestSource: newest?.session.session.source.kind ?? null,
@@ -4485,11 +4634,15 @@ function formatOrientationBody(summary, opts) {
4485
4634
  const lines = [];
4486
4635
  const now = new Date(summary.generatedAt);
4487
4636
  const newestRel = relativeAge(summary.freshness.newestStartedAt ?? void 0, now);
4637
+ const hostSuffix = (h) => h !== null ? ` @${h}` : "";
4488
4638
  lines.push("# Orientation");
4489
4639
  lines.push("");
4490
4640
  lines.push(
4491
4641
  `> Generated at ${summary.generatedAt} \xB7 sessions ${summary.sessionCount} \xB7 newest ${newestRel} \xB7 pending ${summary.pendingApprovals.length} \xB7 suspect ${summary.suspects.length}`
4492
4642
  );
4643
+ if (summary.hosts.length > 0) {
4644
+ lines.push(`> hosts: local, ${summary.hosts.join(", ")}`);
4645
+ }
4493
4646
  lines.push("");
4494
4647
  lines.push("## \u4ECA\u3069\u3053\u306B\u3044\u308B");
4495
4648
  lines.push("");
@@ -4497,9 +4650,9 @@ function formatOrientationBody(summary, opts) {
4497
4650
  const s = summary.latestSession;
4498
4651
  const sid = shortId(s.sessionId);
4499
4652
  if (s.label !== null && s.label !== "") {
4500
- lines.push(`- \u6700\u7D42 session: ${s.label} (${s.status}) [${sid}]`);
4653
+ lines.push(`- \u6700\u7D42 session: ${s.label} (${s.status}) [${sid}]${hostSuffix(s.host)}`);
4501
4654
  } else {
4502
- lines.push(`- \u6700\u7D42 session: ${sid} (${s.status})`);
4655
+ lines.push(`- \u6700\u7D42 session: ${sid} (${s.status})${hostSuffix(s.host)}`);
4503
4656
  }
4504
4657
  } else {
4505
4658
  lines.push("- \u6700\u7D42 session: (no live sessions)");
@@ -4507,7 +4660,9 @@ function formatOrientationBody(summary, opts) {
4507
4660
  if (summary.latestDecision !== null) {
4508
4661
  const dec = summary.latestDecision;
4509
4662
  const decAge = relativeAgeJa(dec.occurredAt, now);
4510
- lines.push(`- \u76F4\u8FD1\u306E\u5224\u65AD: ${dec.title} [${shortId(dec.decisionId)}] (${decAge})`);
4663
+ lines.push(
4664
+ `- \u76F4\u8FD1\u306E\u5224\u65AD: ${dec.title} [${shortId(dec.decisionId)}] (${decAge})${hostSuffix(dec.host)}`
4665
+ );
4511
4666
  const activityAt = summary.freshness.latestActivityAt;
4512
4667
  if (activityAt !== null && isTrailingStale(activityAt, dec.occurredAt)) {
4513
4668
  lines.push(
@@ -4529,6 +4684,15 @@ function formatOrientationBody(summary, opts) {
4529
4684
  const shown = summary.relatedFiles.displayed.join(", ");
4530
4685
  const more = summary.relatedFiles.overflow > 0 ? ` (... +${summary.relatedFiles.overflow} more)` : "";
4531
4686
  lines.push(`- \u76F4\u8FD1\u306E\u5909\u66F4\u30D5\u30A1\u30A4\u30EB: ${shown}${more}`);
4687
+ if (summary.relatedFiles.outOfRoot.length > 0) {
4688
+ const OUT_OF_ROOT_DISPLAY = 10;
4689
+ const out = summary.relatedFiles.outOfRoot;
4690
+ const shownOut = out.slice(0, OUT_OF_ROOT_DISPLAY).join(", ");
4691
+ const outMore = out.length > OUT_OF_ROOT_DISPLAY ? ` (... +${out.length - OUT_OF_ROOT_DISPLAY} more)` : "";
4692
+ lines.push(
4693
+ ` - \u26A0 source_roots \u5916 ${out.length} \u4EF6 (\u5225\u30D7\u30ED\u30B8\u30A7\u30AF\u30C8\u306E\u53EF\u80FD\u6027): ${shownOut}${outMore}`
4694
+ );
4695
+ }
4532
4696
  } else {
4533
4697
  lines.push("- \u76F4\u8FD1\u306E\u5909\u66F4\u30D5\u30A1\u30A4\u30EB: (none recorded)");
4534
4698
  }
@@ -4562,7 +4726,9 @@ function formatOrientationBody(summary, opts) {
4562
4726
  lines.push("- (none)");
4563
4727
  } else {
4564
4728
  for (const e of summary.suspects) {
4565
- lines.push(`- ${shortId(e.sessionId)} (${e.status}) \u2014 ${suspectText(e.reason)}`);
4729
+ lines.push(
4730
+ `- ${shortId(e.sessionId)} (${e.status}) \u2014 ${suspectText(e.reason)}${hostSuffix(e.host)}`
4731
+ );
4566
4732
  }
4567
4733
  }
4568
4734
  lines.push("");
@@ -4571,7 +4737,7 @@ function formatOrientationBody(summary, opts) {
4571
4737
  if (summary.latestNote !== null) {
4572
4738
  const noteAge = relativeAgeJa(summary.latestNote.occurredAt, now);
4573
4739
  lines.push(
4574
- `- \u6B21\u306E\u8D77\u70B9 (\u8A18\u9332\u6E08\u307F, ${noteAge}): ${noteSummary(summary.latestNote.body)} [session ${shortId(summary.latestNote.sessionId)}]`
4740
+ `- \u6B21\u306E\u8D77\u70B9 (\u8A18\u9332\u6E08\u307F, ${noteAge}): ${noteSummary(summary.latestNote.body)} [session ${shortId(summary.latestNote.sessionId)}]${hostSuffix(summary.latestNote.host)}`
4575
4741
  );
4576
4742
  const activityAt = summary.freshness.latestActivityAt;
4577
4743
  if (activityAt !== null && isTrailingStale(activityAt, summary.latestNote.occurredAt)) {
@@ -4601,6 +4767,12 @@ function formatOrientationBody(summary, opts) {
4601
4767
  lines.push("## \u3053\u308C\u306F\u6700\u65B0\u304B");
4602
4768
  lines.push("");
4603
4769
  for (const line of freshnessVerdict(summary, opts.staleness, now)) lines.push(line);
4770
+ if (summary.hosts.length > 0) {
4771
+ lines.push("");
4772
+ lines.push(
4773
+ "\u6CE8: \u9BAE\u5EA6\u5224\u5B9A\u306F\u3053\u306E\u30DE\u30B7\u30F3\u306E\u30ED\u30FC\u30AB\u30EB\u30B9\u30C8\u30A2\u306E\u307F\u304C\u5BFE\u8C61\u3067\u3059\u3002\u4ED6\u30DB\u30B9\u30C8\u306E\u53D6\u308A\u3053\u307C\u3057\u306F\u5224\u5B9A\u3067\u304D\u307E\u305B\u3093(\u5404\u30DB\u30B9\u30C8\u3067 basou refresh \u3092\u5B9F\u884C\u3057\u540C\u671F\u3057\u3066\u304F\u3060\u3055\u3044)\u3002"
4774
+ );
4775
+ }
4604
4776
  if (opts.verbose) {
4605
4777
  lines.push("");
4606
4778
  lines.push("<!-- verbose: raw freshness telemetry -->");
@@ -4677,8 +4849,9 @@ function freshnessVerdict(summary, staleness, now) {
4677
4849
  "\u6700\u65B0\u304B\u78BA\u8A8D\u3059\u308B\u306B\u306F `basou refresh` \u3092\u5B9F\u884C\u3057\u3066\u304F\u3060\u3055\u3044\u3002"
4678
4850
  ];
4679
4851
  }
4852
+ const localScope = summary.hosts.length > 0 ? "\u3053\u306E\u30DB\u30B9\u30C8(\u30ED\u30FC\u30AB\u30EB)\u306E" : "";
4680
4853
  const lines = [
4681
- `\u2705 \u53D6\u308A\u8FBC\u307F\u306F\u6700\u65B0\u3067\u3059\u3002\u6700\u5F8C\u306E\u4F5C\u696D\u306F ${rel}(${tool})\u3002\u672A\u53D6\u308A\u8FBC\u307F\u306E native \u30BB\u30C3\u30B7\u30E7\u30F3\u306F\u3042\u308A\u307E\u305B\u3093\u3002`
4854
+ `\u2705 ${localScope}\u53D6\u308A\u8FBC\u307F\u306F\u6700\u65B0\u3067\u3059\u3002\u6700\u5F8C\u306E\u4F5C\u696D\u306F ${rel}(${tool})\u3002\u672A\u53D6\u308A\u8FBC\u307F\u306E native \u30BB\u30C3\u30B7\u30E7\u30F3\u306F\u3042\u308A\u307E\u305B\u3093\u3002`
4682
4855
  ];
4683
4856
  if (suspectCount > 0) {
4684
4857
  lines.push(`\u305F\u3060\u3057\u8981\u6CE8\u610F\u30BB\u30C3\u30B7\u30E7\u30F3\u304C ${suspectCount} \u4EF6\u3042\u308A\u307E\u3059(\u4E0A\u8A18\u300C\u8981\u6CE8\u610F session\u300D\u53C2\u7167)\u3002`);
@@ -5301,10 +5474,10 @@ function planWorkspaceView(facts, existing = [], rosterNames = []) {
5301
5474
  }
5302
5475
 
5303
5476
  // src/report/report-renderer.ts
5304
- import { join as join16 } from "path";
5477
+ import { join as join17 } from "path";
5305
5478
 
5306
5479
  // src/stats/work-stats.ts
5307
- import { join as join15 } from "path";
5480
+ import { join as join16 } from "path";
5308
5481
  function resolveTimeZone(timeZone) {
5309
5482
  if (timeZone !== void 0 && timeZone.length > 0) return timeZone;
5310
5483
  return Intl.DateTimeFormat().resolvedOptions().timeZone;
@@ -5335,7 +5508,7 @@ async function computeWorkStats(input) {
5335
5508
  const events = [];
5336
5509
  let eventsUnreadable = false;
5337
5510
  try {
5338
- for await (const ev of replayEvents(join15(input.paths.sessions, entry.sessionId), {
5511
+ for await (const ev of replayEvents(join16(input.paths.sessions, entry.sessionId), {
5339
5512
  onWarning: (w) => input.onWarning?.(w, entry.sessionId)
5340
5513
  })) {
5341
5514
  events.push(ev);
@@ -5622,7 +5795,7 @@ async function renderReport(input) {
5622
5795
  const statsBySession = new Map(stats.sessions.map((s) => [s.sessionId, s]));
5623
5796
  const decisions = [];
5624
5797
  for (const entry of entries) {
5625
- const sessionDir = join16(input.paths.sessions, entry.sessionId);
5798
+ const sessionDir = join17(input.paths.sessions, entry.sessionId);
5626
5799
  try {
5627
5800
  for await (const ev of replayEvents(sessionDir, {
5628
5801
  onWarning: (w) => input.onWarning?.(w, entry.sessionId)
@@ -5903,7 +6076,7 @@ function formatInt(n) {
5903
6076
  // src/review/review-gaps.ts
5904
6077
  import { existsSync, realpathSync } from "fs";
5905
6078
  import { homedir as homedir2 } from "os";
5906
- import { basename as basename2, isAbsolute, join as join17 } from "path";
6079
+ import { basename as basename3, isAbsolute as isAbsolute2, join as join18 } from "path";
5907
6080
  function stripQuotes(s) {
5908
6081
  if (s.length >= 2 && (s[0] === '"' && s.at(-1) === '"' || s[0] === "'" && s.at(-1) === "'")) {
5909
6082
  return s.slice(1, -1);
@@ -5927,7 +6100,7 @@ var repoRootCache = /* @__PURE__ */ new Map();
5927
6100
  function isRepoRoot(realPath) {
5928
6101
  const cached = repoRootCache.get(realPath);
5929
6102
  if (cached !== void 0) return cached;
5930
- const result = existsSync(join17(realPath, ".git"));
6103
+ const result = existsSync(join18(realPath, ".git"));
5931
6104
  repoRootCache.set(realPath, result);
5932
6105
  return result;
5933
6106
  }
@@ -5936,7 +6109,7 @@ function normalizeRepoPath(p) {
5936
6109
  let s = stripQuotes(p.trim()).replace(/\/+$/, "");
5937
6110
  if (s.length === 0 || s === "~") return null;
5938
6111
  if (s.startsWith("~/")) s = homedir2() + s.slice(1);
5939
- if (isAbsolute(s)) {
6112
+ if (isAbsolute2(s)) {
5940
6113
  const real = resolveRealpath(s);
5941
6114
  if (real !== null) {
5942
6115
  return isRepoRoot(real) ? real : null;
@@ -5950,7 +6123,7 @@ function normalizeRepoPath(p) {
5950
6123
  }
5951
6124
  function normalizeRepoKey(p) {
5952
6125
  const full = normalizeRepoPath(p);
5953
- return full === null ? null : basename2(full);
6126
+ return full === null ? null : basename3(full);
5954
6127
  }
5955
6128
  function inspectCommand(args) {
5956
6129
  const a = args.join(" ");
@@ -5964,7 +6137,7 @@ function inspectCommand(args) {
5964
6137
  let m;
5965
6138
  while ((m = re.exec(a)) !== null) {
5966
6139
  const f = m[1];
5967
- if (f !== void 0) files.add(basename2(f));
6140
+ if (f !== void 0) files.add(basename3(f));
5968
6141
  }
5969
6142
  }
5970
6143
  return { files: [...files], examinedDiff };
@@ -5981,7 +6154,7 @@ function commitFiles(args) {
5981
6154
  const a = args.join(" ");
5982
6155
  const add = a.match(/git add\s+([^&|;]+)/);
5983
6156
  if (!add?.[1]) return [];
5984
- return add[1].split(/\s+/).filter((t) => /\.[A-Za-z]/.test(t) && !t.startsWith("-")).map((t) => basename2(t));
6157
+ return add[1].split(/\s+/).filter((t) => /\.[A-Za-z]/.test(t) && !t.startsWith("-")).map((t) => basename3(t));
5985
6158
  }
5986
6159
  var REVIEW_SOURCE = "codex-import";
5987
6160
  var DEFAULT_WINDOW_HOURS = 24;
@@ -5997,7 +6170,7 @@ async function findReviewGaps(input) {
5997
6170
  const workUnits = /* @__PURE__ */ new Map();
5998
6171
  const unknownCommits = /* @__PURE__ */ new Map();
5999
6172
  for (const entry of entries) {
6000
- const sessionDir = join17(input.paths.sessions, entry.sessionId);
6173
+ const sessionDir = join18(input.paths.sessions, entry.sessionId);
6001
6174
  const isReview = entry.session.session.source.kind === REVIEW_SOURCE;
6002
6175
  const reviewRepos = /* @__PURE__ */ new Map();
6003
6176
  let reviewEnd = null;
@@ -6046,7 +6219,7 @@ async function findReviewGaps(input) {
6046
6219
  let newestCommit = null;
6047
6220
  for (const [sessionId, byRepo] of workUnits) {
6048
6221
  for (const [repoPath, commits] of byRepo) {
6049
- const label = basename2(repoPath);
6222
+ const label = basename3(repoPath);
6050
6223
  if (scope !== null && !scope.includes(label)) continue;
6051
6224
  const times = commits.map((c) => c.at).sort((a, b) => a - b);
6052
6225
  const first = times[0] ?? null;
@@ -6205,7 +6378,7 @@ var ChildProcessRunner = class {
6205
6378
  if (killTimer !== null) clearTimeout(killTimer);
6206
6379
  options.signal?.removeEventListener("abort", onAbort);
6207
6380
  };
6208
- return new Promise((resolve2, reject) => {
6381
+ return new Promise((resolve3, reject) => {
6209
6382
  child.once("error", (error) => {
6210
6383
  if (settled) return;
6211
6384
  settled = true;
@@ -6217,7 +6390,7 @@ var ChildProcessRunner = class {
6217
6390
  settled = true;
6218
6391
  cleanup();
6219
6392
  const ended_at = /* @__PURE__ */ new Date();
6220
- resolve2({
6393
+ resolve3({
6221
6394
  command: snapshotCommand,
6222
6395
  args: snapshotArgs,
6223
6396
  cwd: snapshotCwd,
@@ -6372,28 +6545,28 @@ function serializeJsonSchema(schema) {
6372
6545
 
6373
6546
  // src/storage/basou-dir.ts
6374
6547
  import { lstat as lstat4, mkdir as mkdir4 } from "fs/promises";
6375
- import { join as join18 } from "path";
6548
+ import { join as join19 } from "path";
6376
6549
  function basouPaths(repositoryRoot) {
6377
- const root = join18(repositoryRoot, ".basou");
6378
- const approvalsBase = join18(root, "approvals");
6550
+ const root = join19(repositoryRoot, ".basou");
6551
+ const approvalsBase = join19(root, "approvals");
6379
6552
  return {
6380
6553
  root,
6381
- sessions: join18(root, "sessions"),
6382
- tasks: join18(root, "tasks"),
6554
+ sessions: join19(root, "sessions"),
6555
+ tasks: join19(root, "tasks"),
6383
6556
  approvals: {
6384
- pending: join18(approvalsBase, "pending"),
6385
- resolved: join18(approvalsBase, "resolved")
6557
+ pending: join19(approvalsBase, "pending"),
6558
+ resolved: join19(approvalsBase, "resolved")
6386
6559
  },
6387
- locks: join18(root, "locks"),
6388
- logs: join18(root, "logs"),
6389
- raw: join18(root, "raw"),
6390
- tmp: join18(root, "tmp"),
6560
+ locks: join19(root, "locks"),
6561
+ logs: join19(root, "logs"),
6562
+ raw: join19(root, "raw"),
6563
+ tmp: join19(root, "tmp"),
6391
6564
  files: {
6392
- manifest: join18(root, "manifest.yaml"),
6393
- status: join18(root, "status.json"),
6394
- handoff: join18(root, "handoff.md"),
6395
- decisions: join18(root, "decisions.md"),
6396
- orientation: join18(root, "orientation.md")
6565
+ manifest: join19(root, "manifest.yaml"),
6566
+ status: join19(root, "status.json"),
6567
+ handoff: join19(root, "handoff.md"),
6568
+ decisions: join19(root, "decisions.md"),
6569
+ orientation: join19(root, "orientation.md")
6397
6570
  }
6398
6571
  };
6399
6572
  }
@@ -6450,12 +6623,12 @@ function hasErrorCode4(error) {
6450
6623
 
6451
6624
  // src/storage/gitignore.ts
6452
6625
  import { readFile as readFile8, writeFile as writeFile2 } from "fs/promises";
6453
- import { join as join19 } from "path";
6626
+ import { join as join20 } from "path";
6454
6627
  var MARKER = "# Basou - default ignore";
6455
6628
  var BASOU_GITIGNORE_BLOCK = "# Basou - default ignore\n.basou/logs/\n.basou/raw/\n.basou/tmp/\n.basou/locks/\n.basou/status.json\n.basou/orientation.md\n.basou/sessions/*/events.jsonl\n.basou/sessions/*/artifacts/\n.basou/approvals/pending/\n.basou/approvals/resolved/\n\n# Basou - default commit\n# .basou/manifest.yaml\n# .basou/handoff.md\n# .basou/decisions.md\n# .basou/tasks/\n# .basou/sessions/*/session.yaml\n# .basou/sessions/*/transcript.md\n# .basou/sessions/*/changed-files.json\n";
6456
6629
  var BASOU_GITIGNORE_BLOCK_LOCAL_ONLY = "# Basou - default ignore\n# Local-only: basou's trail is never committed (personal/local state,\n# regenerable by re-importing from the agents' own logs). Recommended for\n# monitored repos and any workspace kept out of version control.\n.basou/\n";
6457
6630
  async function appendBasouGitignore(repositoryRoot, options = {}) {
6458
- const gitignorePath = join19(repositoryRoot, ".gitignore");
6631
+ const gitignorePath = join20(repositoryRoot, ".gitignore");
6459
6632
  let body;
6460
6633
  let existed;
6461
6634
  try {
@@ -6628,7 +6801,7 @@ function hasErrorCode6(error) {
6628
6801
  // src/storage/session-import.ts
6629
6802
  import { mkdir as mkdir5, readFile as readFile10, rm as rm2 } from "fs/promises";
6630
6803
  import { homedir as homedir3 } from "os";
6631
- import { join as join20 } from "path";
6804
+ import { join as join21 } from "path";
6632
6805
  async function importSessionFromJson(paths, manifest, payload, options) {
6633
6806
  if (options.taskIdOverride !== void 0 && !TaskIdSchema.safeParse(options.taskIdOverride).success) {
6634
6807
  throw new Error(`Invalid task_id: ${options.taskIdOverride}`);
@@ -6653,7 +6826,7 @@ async function importSessionFromJson(paths, manifest, payload, options) {
6653
6826
  pathSanitizeReport
6654
6827
  };
6655
6828
  }
6656
- const sessionDir = join20(paths.sessions, newSessionId);
6829
+ const sessionDir = join21(paths.sessions, newSessionId);
6657
6830
  try {
6658
6831
  await mkdir5(sessionDir, { recursive: true });
6659
6832
  } catch (error) {
@@ -6667,7 +6840,7 @@ async function importSessionFromJson(paths, manifest, payload, options) {
6667
6840
  throw error;
6668
6841
  }
6669
6842
  try {
6670
- const sessionYamlPath = join20(sessionDir, "session.yaml");
6843
+ const sessionYamlPath = join21(sessionDir, "session.yaml");
6671
6844
  await linkYamlFile(sessionYamlPath, withIntegrity(sessionRecord, chainResult));
6672
6845
  } catch (error) {
6673
6846
  await rm2(sessionDir, { recursive: true, force: true }).catch(() => void 0);
@@ -6835,7 +7008,7 @@ function reuseDerivedIds(priorDerived, freshDerived, sessionId) {
6835
7008
  async function reimportPreservingId(paths, manifest, priorSessionId, freshPayload, options = {}) {
6836
7009
  const sessionId = priorSessionId;
6837
7010
  const importSource = freshPayload.session.source.kind;
6838
- const sessionDir = join20(paths.sessions, priorSessionId);
7011
+ const sessionDir = join21(paths.sessions, priorSessionId);
6839
7012
  const lock = options.dryRun === true ? null : await acquireLock(paths, "session", priorSessionId);
6840
7013
  try {
6841
7014
  const priorVerdict = await verifyEventsChain(paths, priorSessionId);
@@ -6877,7 +7050,7 @@ async function reimportPreservingId(paths, manifest, priorSessionId, freshPayloa
6877
7050
  };
6878
7051
  const updatedRecord = { schema_version: "0.1.0", session: preservedInner };
6879
7052
  if (options.dryRun !== true) {
6880
- const eventsPath = join20(sessionDir, "events.jsonl");
7053
+ const eventsPath = join21(sessionDir, "events.jsonl");
6881
7054
  let priorEventsRaw = null;
6882
7055
  try {
6883
7056
  priorEventsRaw = await readFile10(eventsPath);
@@ -6889,7 +7062,7 @@ async function reimportPreservingId(paths, manifest, priorSessionId, freshPayloa
6889
7062
  const chainResult = await writeEventsBulk(sessionDir, mergedEvents, { chain: true });
6890
7063
  try {
6891
7064
  await overwriteYamlFile(
6892
- join20(sessionDir, "session.yaml"),
7065
+ join21(sessionDir, "session.yaml"),
6893
7066
  withIntegrity(updatedRecord, chainResult)
6894
7067
  );
6895
7068
  } catch (error) {
@@ -6913,7 +7086,7 @@ async function reimportPreservingId(paths, manifest, priorSessionId, freshPayloa
6913
7086
  }
6914
7087
  }
6915
7088
  async function rechainSessionInPlace(paths, sessionId, options = {}) {
6916
- const sessionDir = join20(paths.sessions, sessionId);
7089
+ const sessionDir = join21(paths.sessions, sessionId);
6917
7090
  let lock;
6918
7091
  try {
6919
7092
  lock = await acquireLock(paths, "session", sessionId);
@@ -6946,7 +7119,7 @@ async function rechainSessionInPlace(paths, sessionId, options = {}) {
6946
7119
  if (verdict.status !== "unchained") {
6947
7120
  return { status: "skipped", reason: "tampered" };
6948
7121
  }
6949
- const eventsPath = join20(sessionDir, "events.jsonl");
7122
+ const eventsPath = join21(sessionDir, "events.jsonl");
6950
7123
  let priorRaw;
6951
7124
  try {
6952
7125
  priorRaw = await readFile10(eventsPath);
@@ -6994,7 +7167,7 @@ async function rechainSessionInPlace(paths, sessionId, options = {}) {
6994
7167
  }
6995
7168
  try {
6996
7169
  await overwriteYamlFile(
6997
- join20(sessionDir, "session.yaml"),
7170
+ join21(sessionDir, "session.yaml"),
6998
7171
  withIntegrity(record, { headHash: chainResult.headHash, count: chainResult.count })
6999
7172
  );
7000
7173
  } catch (error) {
@@ -7011,6 +7184,7 @@ async function rechainSessionInPlace(paths, sessionId, options = {}) {
7011
7184
  var BASOU_CORE_VERSION = "0.1.0";
7012
7185
  export {
7013
7186
  ACTIVE_GAP_CAP_MS,
7187
+ AGENT_INFRA_DIRS,
7014
7188
  ApprovalIdSchema,
7015
7189
  ApprovalSchema,
7016
7190
  ApprovalStatusSchema,
@@ -7061,6 +7235,7 @@ export {
7061
7235
  buildStatusSnapshot,
7062
7236
  chainEvents,
7063
7237
  chainRawJsonLines,
7238
+ classifyFilesBySourceRoot,
7064
7239
  classifySuspect,
7065
7240
  claudeCodeAdapterMetadata,
7066
7241
  claudeTranscriptToImportPayload,
@@ -7093,6 +7268,7 @@ export {
7093
7268
  lineHash,
7094
7269
  linkYamlFile,
7095
7270
  loadApproval,
7271
+ loadFederatedSessionEntries,
7096
7272
  loadSessionEntries,
7097
7273
  loadTaskEntries,
7098
7274
  normalizeRepoKey,