@basou/cli 0.43.0 → 0.45.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
@@ -119,6 +119,7 @@ function printTaskSkip(taskId, reason) {
119
119
 
120
120
  // src/program.ts
121
121
  import { createRequire } from "module";
122
+ import * as basouCore from "@basou/core";
122
123
  import { Command } from "commander";
123
124
 
124
125
  // src/commands/approval.ts
@@ -135,6 +136,7 @@ import {
135
136
  enumerateApprovals,
136
137
  findErrorCode,
137
138
  isLazyExpired,
139
+ LOCAL_CLI_EVENT_SOURCE,
138
140
  linkYamlFile,
139
141
  loadApproval,
140
142
  prefixedUlid,
@@ -369,7 +371,7 @@ async function doRunApprovalResolve(idInput, options, ctx, decision) {
369
371
  id: eventId,
370
372
  session_id: approval.session_id,
371
373
  occurred_at: occurredAt,
372
- source: "local-cli",
374
+ source: LOCAL_CLI_EVENT_SOURCE,
373
375
  type: "approval_approved",
374
376
  approval_id: approval.id,
375
377
  resolver: "local-cli",
@@ -382,7 +384,7 @@ async function doRunApprovalResolve(idInput, options, ctx, decision) {
382
384
  id: eventId,
383
385
  session_id: approval.session_id,
384
386
  occurred_at: occurredAt,
385
- source: "local-cli",
387
+ source: LOCAL_CLI_EVENT_SOURCE,
386
388
  type: "approval_rejected",
387
389
  approval_id: approval.id,
388
390
  resolver: "local-cli",
@@ -861,12 +863,13 @@ import {
861
863
  acquireLock as acquireLock2,
862
864
  appendEventToExistingSession,
863
865
  assertBasouRootSafe as assertBasouRootSafe2,
864
- basouPaths as basouPaths3,
866
+ basouPaths as basouPaths4,
865
867
  classifyFilesBySourceRoot,
866
868
  createAdHocSessionWithEvent,
867
869
  EVENT_SCHEMA_VERSION as EVENT_SCHEMA_VERSION2,
868
870
  findErrorCode as findErrorCode2,
869
871
  isValidPrefixedId,
872
+ LOCAL_CLI_EVENT_SOURCE as LOCAL_CLI_EVENT_SOURCE2,
870
873
  loadSessionEntries,
871
874
  prefixedUlid as prefixedUlid2,
872
875
  readManifest as readManifest2,
@@ -875,7 +878,7 @@ import {
875
878
  resolveSessionId,
876
879
  sanitizePath
877
880
  } from "@basou/core";
878
- import { InvalidArgumentError } from "commander";
881
+ import { InvalidArgumentError as InvalidArgumentError2 } from "commander";
879
882
 
880
883
  // src/lib/repo-root.ts
881
884
  import { realpath, stat as stat2 } from "fs/promises";
@@ -1035,11 +1038,165 @@ async function realpathOrNull(p) {
1035
1038
  }
1036
1039
  }
1037
1040
 
1041
+ // src/commands/decision-gaps.ts
1042
+ import {
1043
+ basouPaths as basouPaths3,
1044
+ findDecisionGaps
1045
+ } from "@basou/core";
1046
+ import { InvalidArgumentError } from "commander";
1047
+ var DEFAULT_GAP_LIMIT = 20;
1048
+ function parseLimit(value) {
1049
+ const n = /^\d+$/u.test(value.trim()) ? Number(value.trim()) : Number.NaN;
1050
+ if (!Number.isInteger(n) || n < 0) {
1051
+ throw new InvalidArgumentError("--limit must be a non-negative integer.");
1052
+ }
1053
+ return n;
1054
+ }
1055
+ function parseSince(value, now = /* @__PURE__ */ new Date()) {
1056
+ const duration = /^(\d+)([dhm])$/u.exec(value.trim());
1057
+ if (duration !== null) {
1058
+ const n = Number(duration[1]);
1059
+ const ms = { d: 864e5, h: 36e5, m: 6e4 }[duration[2]];
1060
+ return new Date(now.getTime() - n * ms).toISOString();
1061
+ }
1062
+ if (/^\d+$/u.test(value.trim())) {
1063
+ throw new InvalidArgumentError("--since needs a unit (7d, 36h, 90m) or a full ISO timestamp.");
1064
+ }
1065
+ const parsed = Date.parse(value);
1066
+ if (Number.isNaN(parsed)) {
1067
+ throw new InvalidArgumentError(
1068
+ "--since must be an ISO timestamp or a duration (7d, 36h, 90m)."
1069
+ );
1070
+ }
1071
+ return new Date(parsed).toISOString();
1072
+ }
1073
+ function registerDecisionGapsCommand(decision) {
1074
+ decision.command("gaps").description("List recorded decisions that no task carries (read-only, advisory)").option(
1075
+ "--since <when>",
1076
+ "Only decisions recorded at or after this ISO instant or duration back (7d, 36h)"
1077
+ ).option(
1078
+ "--limit <n>",
1079
+ `Maximum entries to list; the rest are counted (default ${DEFAULT_GAP_LIMIT}, 0 = no limit)`,
1080
+ parseLimit
1081
+ ).option("--json", "Output the result as JSON").option("-v, --verbose", "Show error causes").action(async (opts) => {
1082
+ await runDecisionGaps(opts);
1083
+ });
1084
+ }
1085
+ async function runDecisionGaps(options, ctx = {}) {
1086
+ try {
1087
+ await doRunDecisionGaps(options, ctx);
1088
+ } catch (error) {
1089
+ renderCliError(error, { verbose: isVerbose(options) });
1090
+ process.exitCode = 1;
1091
+ }
1092
+ }
1093
+ async function doRunDecisionGaps(options, ctx) {
1094
+ const cwd = ctx.cwd ?? process.cwd();
1095
+ const repositoryRoot = await resolveBasouRootForCommand(cwd, "decision gaps");
1096
+ const paths = basouPaths3(repositoryRoot);
1097
+ const now = ctx.nowProvider?.() ?? /* @__PURE__ */ new Date();
1098
+ const limit = options.limit ?? DEFAULT_GAP_LIMIT;
1099
+ const summary = await findDecisionGaps({
1100
+ paths,
1101
+ nowIso: now.toISOString(),
1102
+ ...options.since !== void 0 ? { start: parseSince(options.since, now) } : {},
1103
+ ...limit > 0 ? { limit } : {},
1104
+ onWarning: (w, sid) => printReplayWarning(w, sid),
1105
+ onSessionSkip: (sid, reason) => printSessionSkip(sid, reason)
1106
+ });
1107
+ if (options.json === true) {
1108
+ console.log(JSON.stringify(summary));
1109
+ } else {
1110
+ console.log(renderDecisionGaps(summary));
1111
+ }
1112
+ return summary;
1113
+ }
1114
+ function relAge(iso, now) {
1115
+ const ms = now.getTime() - Date.parse(iso);
1116
+ if (!Number.isFinite(ms)) return "(unknown)";
1117
+ if (ms < 0) return "clock ahead";
1118
+ const days = Math.floor(ms / 864e5);
1119
+ if (days >= 1) return `${days}d ago`;
1120
+ const hours = Math.floor(ms / 36e5);
1121
+ if (hours >= 1) return `${hours}h ago`;
1122
+ return `${Math.max(1, Math.floor(ms / 6e4))}m ago`;
1123
+ }
1124
+ var GRAPHEMES = new Intl.Segmenter(void 0, { granularity: "grapheme" });
1125
+ function oneLine(value, max) {
1126
+ const flat = [...GRAPHEMES.segment(value.replace(/\s+/gu, " ").trim())].map((s) => s.segment);
1127
+ return flat.length > max ? `${flat.slice(0, max - 1).join("")}\u2026` : flat.join("");
1128
+ }
1129
+ function gapLine(d, now) {
1130
+ return `- ${oneLine(d.title, 100)}
1131
+ ${relAge(d.recordedAt, now)} \xB7 ${d.decisionId}`;
1132
+ }
1133
+ function incompleteLine(summary) {
1134
+ const { sessions, tasks, unknownReferences } = summary.incomplete;
1135
+ const parts = [];
1136
+ if (sessions > 0) {
1137
+ parts.push(
1138
+ `${sessions} session${sessions === 1 ? "" : "s"} could not be read in full, so a decision recorded there may be missing from these numbers, and a \`decision void\` recorded there may not have been applied`
1139
+ );
1140
+ }
1141
+ if (tasks > 0) {
1142
+ parts.push(
1143
+ `${tasks} task file${tasks === 1 ? "" : "s"} could not be read, so a decision one of them carries is listed above as if nothing did`
1144
+ );
1145
+ }
1146
+ if (unknownReferences > 0) {
1147
+ parts.push(
1148
+ `${unknownReferences} decision id${unknownReferences === 1 ? "" : "s"} named by a task ${unknownReferences === 1 ? "matches" : "match"} no decision in this store, and ${unknownReferences === 1 ? "was" : "were"} not counted as carrying anything`
1149
+ );
1150
+ }
1151
+ return parts.length === 0 ? null : `\u26A0\uFE0F ${parts.join("; ")}.`;
1152
+ }
1153
+ function renderDecisionGaps(summary) {
1154
+ const now = new Date(summary.generatedAt);
1155
+ const { excluded, scope } = summary;
1156
+ const lines = ["# Decision gaps", ""];
1157
+ if (summary.populationCount === 0) {
1158
+ lines.push(
1159
+ `No decision recorded since ${scope.start} by \`basou decision capture\` or \`basou decision record\` is still open, so there is nothing to check yet. This list fills as decisions are recorded.`
1160
+ );
1161
+ } else if (summary.gaps.length === 0) {
1162
+ lines.push(
1163
+ `\u2705 Within what was checked, each of the ${summary.populationCount} open decision${summary.populationCount === 1 ? "" : "s"} in scope has a task carrying it.`
1164
+ );
1165
+ } else {
1166
+ const total = summary.gaps.length + summary.truncated;
1167
+ lines.push(`\u26A0\uFE0F Open decisions no task carries: ${total} of ${summary.populationCount} in scope`);
1168
+ lines.push("");
1169
+ for (const d of summary.gaps) lines.push(gapLine(d, now));
1170
+ if (summary.truncated > 0) {
1171
+ lines.push(` ... +${summary.truncated} more (--limit 0 to list them all, or --json)`);
1172
+ }
1173
+ }
1174
+ lines.push("");
1175
+ lines.push("## Scope");
1176
+ lines.push(
1177
+ `- Checked: decisions recorded at or after ${scope.start} with source \`${scope.source}\` (i.e. recorded by running basou, not derived from a transcript by an importer), still open, and not a track.`
1178
+ );
1179
+ lines.push(
1180
+ `- Not checked: ${excluded.byStart} recorded earlier, then ${excluded.bySource} recorded by something other than basou itself, then ${excluded.track} tracks (already shown every session by \`basou orient\` until closed), then ${excluded.voided} closed with \`basou decision void\`.`
1181
+ );
1182
+ lines.push(
1183
+ `- Task files read: ${summary.tasksScanned} (live and archived); ${summary.carried} of the decisions in scope are carried by one.`
1184
+ );
1185
+ const incomplete = incompleteLine(summary);
1186
+ if (incomplete !== null) lines.push(`- ${incomplete}`);
1187
+ lines.push("");
1188
+ lines.push(
1189
+ "Note: read-only advisory. A task carries a decision when it names that decision's full id; an abbreviated id names no one decision, because a captured batch shares a millisecond and its ids differ only at the end. It does not read what a decision means, and it does not enforce."
1190
+ );
1191
+ return lines.join("\n");
1192
+ }
1193
+
1038
1194
  // src/commands/decision.ts
1039
1195
  var LABEL_TITLE_MAX = 80;
1040
1196
  var LABEL_TRUNCATE_HEAD = LABEL_TITLE_MAX - 3;
1041
1197
  function registerDecisionCommand(program2) {
1042
1198
  const decision = program2.command("decision").description("Record human-authored decisions as events");
1199
+ registerDecisionGapsCommand(decision);
1043
1200
  decision.command("record").description("Record a decision_recorded event").requiredOption("--title <text>", "Decision title", parseTitle).option("--rationale <text>", "Rationale for the decision", parseRationale).option(
1044
1201
  "--rejected-reason <text>",
1045
1202
  "Reason rejected alternatives were not chosen",
@@ -1161,7 +1318,7 @@ function warnTrackMarkerWithoutKind(decisions, markerWithoutKind) {
1161
1318
  async function doRunDecisionRecord(options, ctx) {
1162
1319
  const cwd = ctx.cwd ?? process.cwd();
1163
1320
  const repositoryRoot = await resolveRepositoryRootForDecision(cwd);
1164
- const paths = basouPaths3(repositoryRoot);
1321
+ const paths = basouPaths4(repositoryRoot);
1165
1322
  await assertWorkspaceInitialized2(paths.root);
1166
1323
  const now = ctx.nowProvider !== void 0 ? ctx.nowProvider() : /* @__PURE__ */ new Date();
1167
1324
  const occurredAt = now.toISOString();
@@ -1252,7 +1409,7 @@ async function runDecisionCapture(options, ctx = {}) {
1252
1409
  async function doRunDecisionCapture(options, ctx) {
1253
1410
  const cwd = ctx.cwd ?? process.cwd();
1254
1411
  const repositoryRoot = await resolveBasouRootForCommand(cwd, "decision capture");
1255
- const paths = basouPaths3(repositoryRoot);
1412
+ const paths = basouPaths4(repositoryRoot);
1256
1413
  await assertWorkspaceInitialized2(paths.root);
1257
1414
  const raw = await readCaptureInput(options, ctx);
1258
1415
  const { decisions, markerWithoutKind } = parseCaptureInput(raw);
@@ -1334,7 +1491,7 @@ async function doRunDecisionVoid(decisionId, options, ctx) {
1334
1491
  }
1335
1492
  const cwd = ctx.cwd ?? process.cwd();
1336
1493
  const repositoryRoot = await resolveBasouRootForCommand(cwd, "decision void");
1337
- const paths = basouPaths3(repositoryRoot);
1494
+ const paths = basouPaths4(repositoryRoot);
1338
1495
  await assertWorkspaceInitialized2(paths.root);
1339
1496
  if (!await decisionExists(paths, decisionId)) {
1340
1497
  throw new Error(
@@ -1428,7 +1585,7 @@ function buildDecisionVoidedEvent(input) {
1428
1585
  id: input.eventId,
1429
1586
  session_id: input.sessionId,
1430
1587
  occurred_at: input.occurredAt,
1431
- source: "local-cli",
1588
+ source: LOCAL_CLI_EVENT_SOURCE2,
1432
1589
  type: "decision_voided",
1433
1590
  decision_id: input.decisionId,
1434
1591
  ...input.reason !== void 0 ? { reason: input.reason } : {},
@@ -1460,7 +1617,7 @@ function printVoidResult(options, result) {
1460
1617
  }
1461
1618
  function parseReason(raw) {
1462
1619
  if (raw.trim().length === 0) {
1463
- throw new InvalidArgumentError("--reason must not be empty");
1620
+ throw new InvalidArgumentError2("--reason must not be empty");
1464
1621
  }
1465
1622
  return raw;
1466
1623
  }
@@ -1694,7 +1851,7 @@ function buildDecisionEvent(input) {
1694
1851
  id: input.eventId,
1695
1852
  session_id: input.sessionId,
1696
1853
  occurred_at: input.occurredAt,
1697
- source: "local-cli",
1854
+ source: LOCAL_CLI_EVENT_SOURCE2,
1698
1855
  type: "decision_recorded",
1699
1856
  decision_id: input.decisionId,
1700
1857
  title: input.title,
@@ -1712,25 +1869,25 @@ function buildAdHocLabel(title) {
1712
1869
  }
1713
1870
  function parseTitle(raw) {
1714
1871
  if (isBlank(raw)) {
1715
- throw new InvalidArgumentError("Title must not be empty");
1872
+ throw new InvalidArgumentError2("Title must not be empty");
1716
1873
  }
1717
1874
  return raw;
1718
1875
  }
1719
1876
  function parseRationale(raw) {
1720
1877
  if (isBlank(raw)) {
1721
- throw new InvalidArgumentError("Rationale must not be empty");
1878
+ throw new InvalidArgumentError2("Rationale must not be empty");
1722
1879
  }
1723
1880
  return raw;
1724
1881
  }
1725
1882
  function parseRejectedReason(raw) {
1726
1883
  if (isBlank(raw)) {
1727
- throw new InvalidArgumentError("Rejected reason must not be empty");
1884
+ throw new InvalidArgumentError2("Rejected reason must not be empty");
1728
1885
  }
1729
1886
  return raw;
1730
1887
  }
1731
1888
  function collectAlternative(value, prev) {
1732
1889
  if (isBlank(value)) {
1733
- throw new InvalidArgumentError("Alternative must not be empty");
1890
+ throw new InvalidArgumentError2("Alternative must not be empty");
1734
1891
  }
1735
1892
  return prev.concat(value);
1736
1893
  }
@@ -1739,16 +1896,16 @@ function isValidEventId(value) {
1739
1896
  }
1740
1897
  function collectLinkedEvent(value, prev) {
1741
1898
  if (!isValidEventId(value)) {
1742
- throw new InvalidArgumentError(`Linked event id must match evt_<ULID>, got '${value}'`);
1899
+ throw new InvalidArgumentError2(`Linked event id must match evt_<ULID>, got '${value}'`);
1743
1900
  }
1744
1901
  return prev.concat(value);
1745
1902
  }
1746
1903
  function collectLinkedFile(value, prev) {
1747
1904
  if (isBlank(value)) {
1748
- throw new InvalidArgumentError("Linked file path must not be empty");
1905
+ throw new InvalidArgumentError2("Linked file path must not be empty");
1749
1906
  }
1750
1907
  if (value.length > 4096) {
1751
- throw new InvalidArgumentError("Linked file path exceeds 4096 chars");
1908
+ throw new InvalidArgumentError2("Linked file path exceeds 4096 chars");
1752
1909
  }
1753
1910
  return prev.concat(value);
1754
1911
  }
@@ -1813,7 +1970,7 @@ async function assertWorkspaceInitialized2(basouRoot) {
1813
1970
  // src/commands/decisions.ts
1814
1971
  import {
1815
1972
  assertBasouRootSafe as assertBasouRootSafe3,
1816
- basouPaths as basouPaths4,
1973
+ basouPaths as basouPaths5,
1817
1974
  findErrorCode as findErrorCode3,
1818
1975
  readMarkdownFile as readMarkdownFile2,
1819
1976
  renderDecisions,
@@ -1839,7 +1996,7 @@ async function doRunDecisionsGenerate(options, ctx) {
1839
1996
  void options;
1840
1997
  const cwd = ctx.cwd ?? process.cwd();
1841
1998
  const repositoryRoot = await resolveRepositoryRootForDecisions(cwd);
1842
- const paths = basouPaths4(repositoryRoot);
1999
+ const paths = basouPaths5(repositoryRoot);
1843
2000
  await assertWorkspaceInitialized3(paths.root);
1844
2001
  const nowIso = (ctx.nowProvider?.() ?? /* @__PURE__ */ new Date()).toISOString();
1845
2002
  const result = await renderDecisions({
@@ -1884,7 +2041,7 @@ import { join as join6 } from "path";
1884
2041
  import {
1885
2042
  acquireLock as acquireLock3,
1886
2043
  assertBasouRootSafe as assertBasouRootSafe4,
1887
- basouPaths as basouPaths5,
2044
+ basouPaths as basouPaths6,
1888
2045
  ChildProcessRunner,
1889
2046
  appendChainedEvent as coreAppendChainedEvent,
1890
2047
  EVENT_SCHEMA_VERSION as EVENT_SCHEMA_VERSION3,
@@ -1896,6 +2053,7 @@ import {
1896
2053
  readManifest as readManifest3,
1897
2054
  readYamlFile as readYamlFile3,
1898
2055
  resolveRepositoryRoot as resolveRepositoryRoot4,
2056
+ SESSION_SCHEMA_VERSION,
1899
2057
  SessionSchema,
1900
2058
  sanitizeWorkingDirectory,
1901
2059
  writeObservedDuration,
@@ -1918,7 +2076,7 @@ async function runExec(command, args, options, ctx = {}) {
1918
2076
  const cwd = options.cwd ?? process.cwd();
1919
2077
  const timeout_ms = options.timeout !== void 0 ? parseDuration(options.timeout) : void 0;
1920
2078
  const repoRoot = await resolveRepositoryRootForExec(cwd);
1921
- const paths = basouPaths5(repoRoot);
2079
+ const paths = basouPaths6(repoRoot);
1922
2080
  await assertBasouRootSafe4(paths.root);
1923
2081
  const manifest = await readManifest3(paths);
1924
2082
  const sessionId = prefixedUlid3("ses");
@@ -2131,7 +2289,7 @@ function normalizeGitSnapshotSkipMessage(error) {
2131
2289
  function buildInitialSession(input) {
2132
2290
  const cmdline = [input.command, ...input.args].join(" ");
2133
2291
  return {
2134
- schema_version: "0.1.0",
2292
+ schema_version: SESSION_SCHEMA_VERSION,
2135
2293
  session: {
2136
2294
  id: input.id,
2137
2295
  label: `basou exec ${cmdline} (${input.startedAt})`,
@@ -2216,12 +2374,11 @@ async function resolveRepositoryRootForExec(cwd) {
2216
2374
  // src/commands/handoff.ts
2217
2375
  import {
2218
2376
  assertBasouRootSafe as assertBasouRootSafe5,
2219
- basouPaths as basouPaths6,
2377
+ basouPaths as basouPaths7,
2220
2378
  findErrorCode as findErrorCode4,
2221
2379
  readMarkdownFile as readMarkdownFile3,
2222
2380
  renderHandoff,
2223
2381
  renderWithMarkers as renderWithMarkers2,
2224
- resolveRepositoryRoot as resolveRepositoryRoot5,
2225
2382
  writeMarkdownFile as writeMarkdownFile2
2226
2383
  } from "@basou/core";
2227
2384
  function registerHandoffCommand(program2) {
@@ -2242,7 +2399,7 @@ async function doRunHandoffGenerate(options, ctx) {
2242
2399
  void options;
2243
2400
  const cwd = ctx.cwd ?? process.cwd();
2244
2401
  const repositoryRoot = await resolveRepositoryRootForHandoff(cwd);
2245
- const paths = basouPaths6(repositoryRoot);
2402
+ const paths = basouPaths7(repositoryRoot);
2246
2403
  await assertWorkspaceInitialized4(paths.root);
2247
2404
  const nowIso = (ctx.nowProvider?.() ?? /* @__PURE__ */ new Date()).toISOString();
2248
2405
  const result = await renderHandoff({
@@ -2261,7 +2418,7 @@ async function doRunHandoffGenerate(options, ctx) {
2261
2418
  }
2262
2419
  async function resolveRepositoryRootForHandoff(cwd) {
2263
2420
  try {
2264
- return await resolveRepositoryRoot5(cwd);
2421
+ return await resolveBasouRootForCommand(cwd, "handoff generate");
2265
2422
  } catch (error) {
2266
2423
  if (error instanceof Error && error.message === "Not a git repository") {
2267
2424
  throw new Error(
@@ -2284,10 +2441,12 @@ async function assertWorkspaceInitialized4(basouRoot) {
2284
2441
  }
2285
2442
 
2286
2443
  // src/commands/hook.ts
2444
+ import { execFile } from "child_process";
2287
2445
  import { open as open2, readFile as readFile3, realpath as realpath3, stat as stat4 } from "fs/promises";
2288
2446
  import { homedir as homedir7 } from "os";
2289
2447
  import { join as join9 } from "path";
2290
2448
  import { fileURLToPath } from "url";
2449
+ import { promisify } from "util";
2291
2450
  import {
2292
2451
  buildSessionStartHookCommand,
2293
2452
  buildStopHookCommand,
@@ -2558,7 +2717,7 @@ async function warnIfPositionNamesOtherWorkspaces(args) {
2558
2717
  // src/commands/orient.ts
2559
2718
  import {
2560
2719
  assertBasouRootSafe as assertBasouRootSafe7,
2561
- basouPaths as basouPaths8,
2720
+ basouPaths as basouPaths9,
2562
2721
  findErrorCode as findErrorCode6,
2563
2722
  renderOrientation as renderOrientation2,
2564
2723
  writeMarkdownFile as writeMarkdownFile4
@@ -2639,7 +2798,7 @@ import { createInterface } from "readline";
2639
2798
  import {
2640
2799
  AGENT_INFRA_DIRS as AGENT_INFRA_DIRS2,
2641
2800
  assertBasouRootSafe as assertBasouRootSafe6,
2642
- basouPaths as basouPaths7,
2801
+ basouPaths as basouPaths8,
2643
2802
  CLAUDE_IMPORT_SOURCE,
2644
2803
  CODEX_IMPORT_SOURCE,
2645
2804
  classifyFilesBySourceRoot as classifyFilesBySourceRoot2,
@@ -2651,7 +2810,7 @@ import {
2651
2810
  readManifest as readManifest4,
2652
2811
  readSessionYaml as readSessionYaml2,
2653
2812
  reimportPreservingId,
2654
- resolveRepositoryRoot as resolveRepositoryRoot6,
2813
+ resolveRepositoryRoot as resolveRepositoryRoot5,
2655
2814
  SESSION_IMPORT_SCHEMA_VERSION,
2656
2815
  SessionImportPayloadSchema
2657
2816
  } from "@basou/core";
@@ -2798,7 +2957,7 @@ function assertSelector(options) {
2798
2957
  async function resolveImportTarget(ctx) {
2799
2958
  const cwd = ctx.cwd ?? process.cwd();
2800
2959
  const repositoryRoot = await resolveRepositoryRootForImport(cwd);
2801
- const paths = basouPaths7(repositoryRoot);
2960
+ const paths = basouPaths8(repositoryRoot);
2802
2961
  await assertWorkspaceInitialized5(paths.root);
2803
2962
  const manifest = await readManifest4(paths);
2804
2963
  return { repositoryRoot, paths, manifest };
@@ -2841,7 +3000,9 @@ async function importDerivedSessions(paths, manifest, options, sourceKind, candi
2841
3000
  throw new Error("Invalid import payload", { cause: parsed.error });
2842
3001
  }
2843
3002
  if (parsed.data.schema_version !== SESSION_IMPORT_SCHEMA_VERSION) {
2844
- throw new Error(`Unsupported import schema_version: ${parsed.data.schema_version}`);
3003
+ throw new Error(
3004
+ `Unsupported import schema_version: ${parsed.data.schema_version} (expected ${SESSION_IMPORT_SCHEMA_VERSION}). The envelope's shape is published as @basou/core/schemas/session-import.schema.json; this version may move at a minor release, so a producer should read it from the installed @basou/core rather than pinning it.`
3005
+ );
2845
3006
  }
2846
3007
  return parsed.data;
2847
3008
  };
@@ -3234,7 +3395,7 @@ function shortId2(id) {
3234
3395
  }
3235
3396
  async function resolveRepositoryRootForImport(cwd) {
3236
3397
  try {
3237
- return await resolveRepositoryRoot6(cwd);
3398
+ return await resolveRepositoryRoot5(cwd);
3238
3399
  } catch (error) {
3239
3400
  if (error instanceof Error && error.message === "Not a git repository") {
3240
3401
  throw new Error("Not a git repository. Run 'git init' first, then re-run 'basou import'.", {
@@ -3467,7 +3628,7 @@ async function warnIfPositionNamesOtherWorkspaces2(result, ctx) {
3467
3628
  });
3468
3629
  if (report !== null) {
3469
3630
  console.error(
3470
- positionForeignWorkspaceWarning(report, basouPaths8(result.workspaceRoot).files.orientation)
3631
+ positionForeignWorkspaceWarning(report, basouPaths9(result.workspaceRoot).files.orientation)
3471
3632
  );
3472
3633
  }
3473
3634
  }
@@ -3477,7 +3638,7 @@ async function renderOrientationForCwd(options, ctx) {
3477
3638
  return renderOrientationForRoot(repositoryRoot, options, ctx, { write: true });
3478
3639
  }
3479
3640
  async function renderOrientationForRoot(repositoryRoot, options, ctx, behaviour) {
3480
- const paths = basouPaths8(repositoryRoot);
3641
+ const paths = basouPaths9(repositoryRoot);
3481
3642
  await assertWorkspaceInitialized6(paths.root);
3482
3643
  const nowIso = (ctx.nowProvider?.() ?? /* @__PURE__ */ new Date()).toISOString();
3483
3644
  const probeCtx = { cwd: repositoryRoot };
@@ -3491,7 +3652,7 @@ async function renderOrientationForRoot(repositoryRoot, options, ctx, behaviour)
3491
3652
  try {
3492
3653
  const hosts = await loadHostsConfig(ctx.hostsConfigPath);
3493
3654
  if (hosts !== null) {
3494
- federatedRoots = hosts.map((h) => ({ paths: basouPaths8(h.path), host: h.label }));
3655
+ federatedRoots = hosts.map((h) => ({ paths: basouPaths9(h.path), host: h.label }));
3495
3656
  }
3496
3657
  } catch (error) {
3497
3658
  console.error(
@@ -3535,6 +3696,7 @@ async function assertWorkspaceInitialized6(basouRoot) {
3535
3696
 
3536
3697
  // src/commands/hook.ts
3537
3698
  var MAX_TRANSCRIPT_BYTES = 8 * 1024 * 1024;
3699
+ var execFileAsync = promisify(execFile);
3538
3700
  function registerHookCommand(program2) {
3539
3701
  const hook = program2.command("hook").description(
3540
3702
  "Hook handlers for AI coding tools (Claude Code, Codex): read a hook payload on stdin, emit the tool's hook output on stdout"
@@ -3984,6 +4146,90 @@ async function doRunHookStatus(options) {
3984
4146
  review: / --require-review\b/.test(command)
3985
4147
  });
3986
4148
  console.log(`basou Stop hook: registered, ${mode}.`);
4149
+ await reportHookEntryBuild(command);
4150
+ }
4151
+ async function reportHookEntryBuild(command) {
4152
+ console.log(` this basou is: ${BASOU_VERSION_LINE}`);
4153
+ const entry = extractHookEntryPath(command);
4154
+ if (entry === void 0) {
4155
+ console.log(
4156
+ " runs: (registered by alias, not by path) \u2014 which build that resolves to depends on the hook's PATH, so this cannot tell you."
4157
+ );
4158
+ return;
4159
+ }
4160
+ try {
4161
+ const { stdout } = await execFileAsync(process.execPath, [entry, "--version"], {
4162
+ timeout: 1e4
4163
+ });
4164
+ const reported = stdout.trim();
4165
+ console.log(` the hook runs: ${entry}`);
4166
+ console.log(` that build is: ${reported}`);
4167
+ if (!reported.includes("(build ")) {
4168
+ console.log(
4169
+ " note: that build predates build stamping, so what it reports is its package.json rather than itself \u2014 it cannot tell you which build it is. Update it (rebuild a source checkout, or reinstall the package) to find out."
4170
+ );
4171
+ }
4172
+ } catch {
4173
+ console.log(` the hook runs: ${entry}`);
4174
+ console.log(
4175
+ " that build is: could not be executed \u2014 the hook's wrapper fails open, so it is silently doing nothing."
4176
+ );
4177
+ }
4178
+ }
4179
+ function tokenizeShellCommand(command) {
4180
+ const tokens = [];
4181
+ let current = "";
4182
+ let started = false;
4183
+ let quote;
4184
+ for (let i = 0; i < command.length; i++) {
4185
+ const ch = command[i];
4186
+ if (quote === "'") {
4187
+ if (ch === "'") quote = void 0;
4188
+ else current += ch;
4189
+ continue;
4190
+ }
4191
+ if (quote === '"') {
4192
+ if (ch === '"') quote = void 0;
4193
+ else if (ch === "\\" && i + 1 < command.length) current += command[++i];
4194
+ else current += ch;
4195
+ continue;
4196
+ }
4197
+ if (ch === "'" || ch === '"') {
4198
+ quote = ch;
4199
+ started = true;
4200
+ continue;
4201
+ }
4202
+ if (ch === "\\" && i + 1 < command.length) {
4203
+ current += command[++i];
4204
+ started = true;
4205
+ continue;
4206
+ }
4207
+ if (/\s/.test(ch)) {
4208
+ if (started) tokens.push(current);
4209
+ current = "";
4210
+ started = false;
4211
+ continue;
4212
+ }
4213
+ current += ch;
4214
+ started = true;
4215
+ }
4216
+ if (quote !== void 0) return void 0;
4217
+ if (started) tokens.push(current);
4218
+ return tokens;
4219
+ }
4220
+ function extractHookEntryPath(command) {
4221
+ const tokens = tokenizeShellCommand(command);
4222
+ if (tokens === void 0) return void 0;
4223
+ let index = 0;
4224
+ while (index < tokens.length && /^[A-Za-z_][A-Za-z0-9_]*=/.test(tokens[index])) index++;
4225
+ const interpreter = tokens[index];
4226
+ if (interpreter === void 0) return void 0;
4227
+ const base = interpreter.replace(/\\/g, "/").split("/").pop() ?? "";
4228
+ if (base !== "node" && base !== "node.exe") return void 0;
4229
+ index++;
4230
+ while (index < tokens.length && tokens[index].startsWith("-")) index++;
4231
+ const entry = tokens[index];
4232
+ return entry === void 0 || entry === "" ? void 0 : entry;
3987
4233
  }
3988
4234
  function describeHookMode(tiers) {
3989
4235
  const enforcement = tiers.block ? "blocking (opt-in enforcement)" : "advisory (non-blocking)";
@@ -4140,6 +4386,7 @@ async function doRunCodexHookStatus(options) {
4140
4386
  console.log(
4141
4387
  `basou Codex SessionStart hook: registered in ${hooksPath} (matcher: ${matcher}); speaks only for workspaces registered in ~/.basou/portfolio.yaml.`
4142
4388
  );
4389
+ await reportHookEntryBuild(location.command);
4143
4390
  await reportCodexHookState(hooksPath, parsed, options);
4144
4391
  }
4145
4392
  async function codexHookTrustFor(hooksPath, location, configPath) {
@@ -4179,7 +4426,7 @@ import {
4179
4426
  appendBasouGitignore,
4180
4427
  createManifest,
4181
4428
  ensureBasouDirectory,
4182
- resolveRepositoryRoot as resolveRepositoryRoot7,
4429
+ resolveRepositoryRoot as resolveRepositoryRoot6,
4183
4430
  writeManifest
4184
4431
  } from "@basou/core";
4185
4432
  function collectValue(value, previous) {
@@ -4249,7 +4496,7 @@ function renderGitignoreWarning(error, verbose) {
4249
4496
  }
4250
4497
  async function resolveRepositoryRootForInit(cwd) {
4251
4498
  try {
4252
- return await resolveRepositoryRoot7(cwd);
4499
+ return await resolveRepositoryRoot6(cwd);
4253
4500
  } catch (error) {
4254
4501
  if (error instanceof Error && error.message === "Not a git repository") {
4255
4502
  throw new Error("Not a git repository. Run 'git init' first, then re-run 'basou init'.", {
@@ -4265,14 +4512,15 @@ import {
4265
4512
  acquireLock as acquireLock4,
4266
4513
  appendEventToExistingSession as appendEventToExistingSession2,
4267
4514
  assertBasouRootSafe as assertBasouRootSafe8,
4268
- basouPaths as basouPaths9,
4515
+ basouPaths as basouPaths10,
4269
4516
  createAdHocSessionWithEvent as createAdHocSessionWithEvent2,
4270
4517
  EVENT_SCHEMA_VERSION as EVENT_SCHEMA_VERSION4,
4271
4518
  findErrorCode as findErrorCode7,
4519
+ LOCAL_CLI_EVENT_SOURCE as LOCAL_CLI_EVENT_SOURCE3,
4272
4520
  readManifest as readManifest5,
4273
4521
  resolveSessionId as resolveSessionId2
4274
4522
  } from "@basou/core";
4275
- import { InvalidArgumentError as InvalidArgumentError2 } from "commander";
4523
+ import { InvalidArgumentError as InvalidArgumentError3 } from "commander";
4276
4524
  var NOTE_SUBCOMMAND_LOOKALIKES = /* @__PURE__ */ new Set([
4277
4525
  "list",
4278
4526
  "ls",
@@ -4319,7 +4567,7 @@ async function doRunNote(body, options, ctx) {
4319
4567
  }
4320
4568
  const cwd = ctx.cwd ?? process.cwd();
4321
4569
  const repositoryRoot = await resolveBasouRootForCommand(cwd, "note");
4322
- const paths = basouPaths9(repositoryRoot);
4570
+ const paths = basouPaths10(repositoryRoot);
4323
4571
  await assertWorkspaceInitialized7(paths.root);
4324
4572
  const now = ctx.nowProvider !== void 0 ? ctx.nowProvider() : /* @__PURE__ */ new Date();
4325
4573
  const occurredAt = now.toISOString();
@@ -4376,7 +4624,7 @@ function buildNoteEvent(input) {
4376
4624
  id: input.eventId,
4377
4625
  session_id: input.sessionId,
4378
4626
  occurred_at: input.occurredAt,
4379
- source: "local-cli",
4627
+ source: LOCAL_CLI_EVENT_SOURCE3,
4380
4628
  type: "note_added",
4381
4629
  body: input.body,
4382
4630
  // `basou note` is the resume-hint command; mark it so orientation surfaces
@@ -4385,13 +4633,13 @@ function buildNoteEvent(input) {
4385
4633
  };
4386
4634
  }
4387
4635
  function buildAdHocLabel2(body) {
4388
- const oneLine2 = body.replace(/\s+/g, " ").trim();
4389
- const truncated = oneLine2.length > LABEL_BODY_MAX ? `${oneLine2.slice(0, LABEL_TRUNCATE_HEAD2)}...` : oneLine2;
4636
+ const oneLine3 = body.replace(/\s+/g, " ").trim();
4637
+ const truncated = oneLine3.length > LABEL_BODY_MAX ? `${oneLine3.slice(0, LABEL_TRUNCATE_HEAD2)}...` : oneLine3;
4390
4638
  return `Ad-hoc note: ${truncated}`;
4391
4639
  }
4392
4640
  function parseBody(raw) {
4393
4641
  if (raw.trim().length === 0) {
4394
- throw new InvalidArgumentError2("Note body must not be empty");
4642
+ throw new InvalidArgumentError3("Note body must not be empty");
4395
4643
  }
4396
4644
  return raw;
4397
4645
  }
@@ -4542,7 +4790,7 @@ import {
4542
4790
  import { basename as basename6, dirname as dirname4, isAbsolute as isAbsolute3, join as join11, relative as relative2, resolve as resolve7 } from "path";
4543
4791
  import {
4544
4792
  appendBasouGitignore as appendBasouGitignore2,
4545
- basouPaths as basouPaths10,
4793
+ basouPaths as basouPaths11,
4546
4794
  classifyRetrofit,
4547
4795
  createManifest as createManifest2,
4548
4796
  ensureBasouDirectory as ensureBasouDirectory2,
@@ -4564,7 +4812,7 @@ import {
4564
4812
  renderAnchorStarter,
4565
4813
  renderViewPresetBlock,
4566
4814
  renderWithMarkers as renderWithMarkers4,
4567
- resolveRepositoryRoot as resolveRepositoryRoot8,
4815
+ resolveRepositoryRoot as resolveRepositoryRoot7,
4568
4816
  safeSimpleGit,
4569
4817
  seedMarkers,
4570
4818
  summarizePresetPlan,
@@ -4705,7 +4953,7 @@ function preservedUnknownLines(fields) {
4705
4953
  async function doRunProjectCheck(options, ctx) {
4706
4954
  const cwd = ctx.cwd ?? process.cwd();
4707
4955
  const repositoryRoot = await resolveBasouRootForCommand(cwd, "project check");
4708
- const paths = basouPaths10(repositoryRoot);
4956
+ const paths = basouPaths11(repositoryRoot);
4709
4957
  const manifest = await readManifest6(paths);
4710
4958
  const roster = summarizeRosterDrift({
4711
4959
  ...manifest.repos !== void 0 ? { repos: manifest.repos } : {},
@@ -4840,7 +5088,7 @@ async function runProjectSync(options, ctx = {}) {
4840
5088
  async function doRunProjectSync(options, ctx) {
4841
5089
  const cwd = ctx.cwd ?? process.cwd();
4842
5090
  const repositoryRoot = await resolveBasouRootForCommand(cwd, "project sync");
4843
- const paths = basouPaths10(repositoryRoot);
5091
+ const paths = basouPaths11(repositoryRoot);
4844
5092
  const manifest = await readManifest6(paths);
4845
5093
  const hasRoster = manifest.repos !== void 0 && manifest.repos.length > 0;
4846
5094
  const reconcile = reconcileSourceRoots({
@@ -4926,7 +5174,7 @@ function classifySourceRoot(repositoryRoot, declaredPath) {
4926
5174
  async function doRunProjectAdopt(options, ctx) {
4927
5175
  const cwd = ctx.cwd ?? process.cwd();
4928
5176
  const repositoryRoot = await resolveBasouRootForCommand(cwd, "project adopt");
4929
- const paths = basouPaths10(repositoryRoot);
5177
+ const paths = basouPaths11(repositoryRoot);
4930
5178
  const manifest = await readManifest6(paths);
4931
5179
  const alreadyDeclared = manifest.repos !== void 0 && manifest.repos.length > 0;
4932
5180
  const candidates = effectiveSourceRoots(manifest).map(
@@ -5048,7 +5296,7 @@ async function gatherRepoWiring(repositoryRoot, entry) {
5048
5296
  async function doRunProjectWiring(options, ctx) {
5049
5297
  const cwd = ctx.cwd ?? process.cwd();
5050
5298
  const repositoryRoot = await resolveBasouRootForCommand(cwd, "project wiring");
5051
- const paths = basouPaths10(repositoryRoot);
5299
+ const paths = basouPaths11(repositoryRoot);
5052
5300
  const manifest = await readManifest6(paths);
5053
5301
  const roster = manifest.repos ?? [];
5054
5302
  const facts = [];
@@ -5181,7 +5429,7 @@ function applyGitignorePlan(repositoryRoot, plan) {
5181
5429
  async function doRunProjectGitignore(options, ctx) {
5182
5430
  const cwd = ctx.cwd ?? process.cwd();
5183
5431
  const repositoryRoot = await resolveBasouRootForCommand(cwd, "project gitignore");
5184
- const paths = basouPaths10(repositoryRoot);
5432
+ const paths = basouPaths11(repositoryRoot);
5185
5433
  const manifest = await readManifest6(paths);
5186
5434
  const roster = manifest.repos ?? [];
5187
5435
  const facts = roster.map((entry) => gatherRepoGitignore(repositoryRoot, entry));
@@ -5440,7 +5688,7 @@ function applyViewSymlinks(viewDir, files) {
5440
5688
  async function doRunProjectSymlinks(options, ctx) {
5441
5689
  const cwd = ctx.cwd ?? process.cwd();
5442
5690
  const repositoryRoot = await resolveBasouRootForCommand(cwd, "project symlinks");
5443
- const paths = basouPaths10(repositoryRoot);
5691
+ const paths = basouPaths11(repositoryRoot);
5444
5692
  const manifest = await readManifest6(paths);
5445
5693
  const roster = manifest.repos ?? [];
5446
5694
  const anchorReal = realpathSync(repositoryRoot);
@@ -5770,7 +6018,7 @@ function pruneViewLinks(viewDir, toPrune, rosterRealpaths) {
5770
6018
  async function doRunProjectWorkspace(options, ctx) {
5771
6019
  const cwd = ctx.cwd ?? process.cwd();
5772
6020
  const repositoryRoot = await resolveBasouRootForCommand(cwd, "project workspace");
5773
- const paths = basouPaths10(repositoryRoot);
6021
+ const paths = basouPaths11(repositoryRoot);
5774
6022
  const manifest = await readManifest6(paths);
5775
6023
  const viewPath = manifest.workspace.view;
5776
6024
  const roster = manifest.repos ?? [];
@@ -6130,7 +6378,7 @@ function presetFailureReason(error) {
6130
6378
  async function doRunProjectPreset(options, ctx) {
6131
6379
  const cwd = ctx.cwd ?? process.cwd();
6132
6380
  const repositoryRoot = await resolveBasouRootForCommand(cwd, "project preset");
6133
- const paths = basouPaths10(repositoryRoot);
6381
+ const paths = basouPaths11(repositoryRoot);
6134
6382
  const manifest = await readManifest6(paths);
6135
6383
  const roster = manifest.repos ?? [];
6136
6384
  const anchorReal = realpathSync(repositoryRoot);
@@ -6789,7 +7037,7 @@ function renderProjectTeardown(result) {
6789
7037
  async function doRunProjectTeardown(target, options, ctx = {}) {
6790
7038
  const cwd = ctx.cwd ?? process.cwd();
6791
7039
  const repositoryRoot = await resolveBasouRootForCommand(cwd, "project teardown");
6792
- const paths = basouPaths10(repositoryRoot);
7040
+ const paths = basouPaths11(repositoryRoot);
6793
7041
  const manifest = await readManifest6(paths);
6794
7042
  const plan = gatherRepoTeardown(repositoryRoot, manifest, target);
6795
7043
  const willApply = options.apply === true && !plan.isAnchor && plan.removableCount > 0;
@@ -6831,7 +7079,7 @@ function buildArchivedManifest(manifest, plan, updatedAt) {
6831
7079
  async function doRunProjectArchive(target, options, ctx) {
6832
7080
  const cwd = ctx.cwd ?? process.cwd();
6833
7081
  const repositoryRoot = await resolveBasouRootForCommand(cwd, "project archive");
6834
- const paths = basouPaths10(repositoryRoot);
7082
+ const paths = basouPaths11(repositoryRoot);
6835
7083
  const manifest = await readManifest6(paths);
6836
7084
  const roster = manifest.repos ?? [];
6837
7085
  let targetIsAnchor = false;
@@ -6988,7 +7236,7 @@ function buildRenamedManifest(manifest, plan, updatedAt) {
6988
7236
  async function doRunProjectRename(oldPath, newPath, options, ctx) {
6989
7237
  const cwd = ctx.cwd ?? process.cwd();
6990
7238
  const repositoryRoot = await resolveBasouRootForCommand(cwd, "project rename");
6991
- const paths = basouPaths10(repositoryRoot);
7239
+ const paths = basouPaths11(repositoryRoot);
6992
7240
  const manifest = await readManifest6(paths);
6993
7241
  const roster = manifest.repos ?? [];
6994
7242
  let oldIsAnchor = false;
@@ -7112,7 +7360,7 @@ async function runProjectNew(repos, options, ctx = {}) {
7112
7360
  }
7113
7361
  async function resolveRepositoryRootForNew(cwd) {
7114
7362
  try {
7115
- return await resolveRepositoryRoot8(cwd);
7363
+ return await resolveRepositoryRoot7(cwd);
7116
7364
  } catch (error) {
7117
7365
  if (error instanceof Error && error.message === "Not a git repository") {
7118
7366
  throw new Error(
@@ -7164,7 +7412,7 @@ async function doRunProjectNew(repos, options, ctx) {
7164
7412
  const roster = rosterPaths.map((path) => ({ path }));
7165
7413
  const viewPath = options.view === false ? null : options.view ?? `../${viewStem}-workspace`;
7166
7414
  const sourceRoots = [...rosterPaths, ...viewPath !== null ? [viewPath] : []];
7167
- const paths = basouPaths10(repositoryRoot);
7415
+ const paths = basouPaths11(repositoryRoot);
7168
7416
  const existed = existsSync2(paths.files.manifest);
7169
7417
  const manifest = createManifest2({
7170
7418
  workspaceName,
@@ -7275,7 +7523,7 @@ async function runProjectDerive(options, ctx = {}) {
7275
7523
  async function doRunProjectDerive(options, ctx) {
7276
7524
  const cwd = ctx.cwd ?? process.cwd();
7277
7525
  const repositoryRoot = await resolveBasouRootForCommand(cwd, "project derive");
7278
- const paths = basouPaths10(repositoryRoot);
7526
+ const paths = basouPaths11(repositoryRoot);
7279
7527
  const manifest = await readManifest6(paths);
7280
7528
  if (manifest.repos === void 0 || manifest.repos.length === 0) {
7281
7529
  console.log(
@@ -7316,7 +7564,7 @@ async function doRunProjectDerive(options, ctx) {
7316
7564
  async function doRunProjectSeedAnchor(options, ctx) {
7317
7565
  const cwd = ctx.cwd ?? process.cwd();
7318
7566
  const repositoryRoot = await resolveBasouRootForCommand(cwd, "project derive");
7319
- const paths = basouPaths10(repositoryRoot);
7567
+ const paths = basouPaths11(repositoryRoot);
7320
7568
  const manifest = await readManifest6(paths);
7321
7569
  const roster = manifest.repos ?? [];
7322
7570
  console.log("# Anchor instruction-file seed (the planning master's own AGENTS.md)");
@@ -7402,7 +7650,7 @@ function pathPresent(p) {
7402
7650
  return false;
7403
7651
  }
7404
7652
  }
7405
- function gatherRetrofit(repositoryRoot, anchorReal, roster, argPath, argAbs, argReal, viewCanonicalName) {
7653
+ function gatherRetrofit(repositoryRoot, anchorReal, roster, argAbs, argReal, viewCanonicalName) {
7406
7654
  const declaredEntry = roster.find((entry) => {
7407
7655
  const entryAbs = resolve7(repositoryRoot, entry.path);
7408
7656
  if (argReal !== void 0) {
@@ -7510,7 +7758,7 @@ async function applyViewRetrofit(anchorReal, outcome) {
7510
7758
  async function doRunProjectRetrofit(repo, options, ctx) {
7511
7759
  const cwd = ctx.cwd ?? process.cwd();
7512
7760
  const repositoryRoot = await resolveBasouRootForCommand(cwd, "project retrofit");
7513
- const paths = basouPaths10(repositoryRoot);
7761
+ const paths = basouPaths11(repositoryRoot);
7514
7762
  const manifest = await readManifest6(paths);
7515
7763
  const roster = manifest.repos ?? [];
7516
7764
  const anchorReal = realpathSync(repositoryRoot);
@@ -7556,7 +7804,6 @@ async function doRunProjectRetrofit(repo, options, ctx) {
7556
7804
  repositoryRoot,
7557
7805
  anchorReal,
7558
7806
  roster,
7559
- repo,
7560
7807
  argAbs,
7561
7808
  argReal,
7562
7809
  viewCanonicalName
@@ -7987,11 +8234,11 @@ async function doRunProtocolUnsync(options) {
7987
8234
  // src/commands/refresh.ts
7988
8235
  import {
7989
8236
  assertBasouRootSafe as assertBasouRootSafe9,
7990
- basouPaths as basouPaths11,
8237
+ basouPaths as basouPaths12,
7991
8238
  findErrorCode as findErrorCode9,
7992
8239
  readManifest as readManifest7
7993
8240
  } from "@basou/core";
7994
- import { InvalidArgumentError as InvalidArgumentError3 } from "commander";
8241
+ import { InvalidArgumentError as InvalidArgumentError4 } from "commander";
7995
8242
 
7996
8243
  // src/commands/refresh-watch.ts
7997
8244
  import { readdir as readdir2, stat as stat5 } from "fs/promises";
@@ -8118,7 +8365,7 @@ function collectPath2(value, previous) {
8118
8365
  function parseInterval(value) {
8119
8366
  const seconds = Number(value);
8120
8367
  if (!Number.isInteger(seconds) || seconds < MIN_WATCH_INTERVAL_SEC || seconds > MAX_WATCH_INTERVAL_SEC) {
8121
- throw new InvalidArgumentError3(
8368
+ throw new InvalidArgumentError4(
8122
8369
  `--interval must be an integer between ${MIN_WATCH_INTERVAL_SEC} and ${MAX_WATCH_INTERVAL_SEC} (seconds).`
8123
8370
  );
8124
8371
  }
@@ -8229,7 +8476,7 @@ async function doRunRefreshWatch(options, ctx) {
8229
8476
  if (options.force === true) throw new Error("--watch cannot be combined with --force.");
8230
8477
  const cwd = ctx.cwd ?? process.cwd();
8231
8478
  const repositoryRoot = await resolveBasouRootForCommand(cwd, "refresh");
8232
- const paths = basouPaths11(repositoryRoot);
8479
+ const paths = basouPaths12(repositoryRoot);
8233
8480
  await assertWorkspaceInitialized8(paths.root);
8234
8481
  const intervalMs = (options.interval ?? DEFAULT_WATCH_INTERVAL_SEC) * 1e3;
8235
8482
  const controller = new AbortController();
@@ -8257,7 +8504,7 @@ async function doRunRefreshWatch(options, ctx) {
8257
8504
  async function computeRefresh(options, ctx) {
8258
8505
  const cwd = ctx.cwd ?? process.cwd();
8259
8506
  const repositoryRoot = await resolveBasouRootForCommand(cwd, "refresh");
8260
- const paths = basouPaths11(repositoryRoot);
8507
+ const paths = basouPaths12(repositoryRoot);
8261
8508
  await assertWorkspaceInitialized8(paths.root);
8262
8509
  const nowIso = (ctx.nowProvider?.() ?? /* @__PURE__ */ new Date()).toISOString();
8263
8510
  const result = await refreshAll({
@@ -8366,10 +8613,10 @@ async function assertWorkspaceInitialized8(basouRoot) {
8366
8613
  import { isAbsolute as isAbsolute5, resolve as resolve9 } from "path";
8367
8614
  import {
8368
8615
  assertBasouRootSafe as assertBasouRootSafe10,
8369
- basouPaths as basouPaths12,
8616
+ basouPaths as basouPaths13,
8370
8617
  findErrorCode as findErrorCode10,
8371
8618
  renderReport,
8372
- resolveRepositoryRoot as resolveRepositoryRoot9,
8619
+ resolveRepositoryRoot as resolveRepositoryRoot8,
8373
8620
  writeMarkdownFile as writeMarkdownFile6
8374
8621
  } from "@basou/core";
8375
8622
  function registerReportCommand(program2) {
@@ -8391,7 +8638,7 @@ async function runReportGenerate(options, ctx = {}) {
8391
8638
  async function doRunReportGenerate(options, ctx) {
8392
8639
  const cwd = ctx.cwd ?? process.cwd();
8393
8640
  const repositoryRoot = await resolveRepositoryRootForReport(cwd);
8394
- const paths = basouPaths12(repositoryRoot);
8641
+ const paths = basouPaths13(repositoryRoot);
8395
8642
  await assertWorkspaceInitialized9(paths.root);
8396
8643
  const nowIso = (ctx.nowProvider?.() ?? /* @__PURE__ */ new Date()).toISOString();
8397
8644
  const result = await renderReport({
@@ -8418,7 +8665,7 @@ async function doRunReportGenerate(options, ctx) {
8418
8665
  }
8419
8666
  async function resolveRepositoryRootForReport(cwd) {
8420
8667
  try {
8421
- return await resolveRepositoryRoot9(cwd);
8668
+ return await resolveRepositoryRoot8(cwd);
8422
8669
  } catch (error) {
8423
8670
  if (error instanceof Error && error.message === "Not a git repository") {
8424
8671
  throw new Error(
@@ -8446,7 +8693,7 @@ import { homedir as homedir10 } from "os";
8446
8693
  import { resolve as resolve10 } from "path";
8447
8694
  import {
8448
8695
  assertBasouRootSafe as assertBasouRootSafe11,
8449
- basouPaths as basouPaths13,
8696
+ basouPaths as basouPaths14,
8450
8697
  buildReviewRecordedEvent,
8451
8698
  buildReviewRecordLabel,
8452
8699
  createAdHocSessionWithEvent as createAdHocSessionWithEvent3,
@@ -8528,7 +8775,7 @@ async function runReviewRecord(options, ctx = {}) {
8528
8775
  async function doRunReviewRecord(options, ctx) {
8529
8776
  const cwd = ctx.cwd ?? process.cwd();
8530
8777
  const repositoryRoot = await resolveBasouRootForCommand(cwd, "review record");
8531
- const paths = basouPaths13(repositoryRoot);
8778
+ const paths = basouPaths14(repositoryRoot);
8532
8779
  await assertWorkspaceInitialized10(paths.root);
8533
8780
  const raw = await readReviewInput(options, ctx);
8534
8781
  const review = parseReviewRecordInput(raw);
@@ -8675,17 +8922,17 @@ async function assertWorkspaceInitialized10(basouRoot) {
8675
8922
 
8676
8923
  // src/commands/review-gaps.ts
8677
8924
  import {
8678
- basouPaths as basouPaths14,
8925
+ basouPaths as basouPaths15,
8679
8926
  findReviewGaps
8680
8927
  } from "@basou/core";
8681
- import { InvalidArgumentError as InvalidArgumentError4 } from "commander";
8928
+ import { InvalidArgumentError as InvalidArgumentError5 } from "commander";
8682
8929
  function collectRepo(value, previous) {
8683
8930
  return [...previous, value];
8684
8931
  }
8685
8932
  function parseWindow(value) {
8686
8933
  const hours = Number(value);
8687
8934
  if (!Number.isInteger(hours) || hours <= 0) {
8688
- throw new InvalidArgumentError4("--window must be a positive integer (hours).");
8935
+ throw new InvalidArgumentError5("--window must be a positive integer (hours).");
8689
8936
  }
8690
8937
  return hours;
8691
8938
  }
@@ -8716,7 +8963,7 @@ async function runReviewGaps(options, ctx = {}) {
8716
8963
  async function doRunReviewGaps(options, ctx) {
8717
8964
  const cwd = ctx.cwd ?? process.cwd();
8718
8965
  const repositoryRoot = await resolveBasouRootForCommand(cwd, "review-gaps");
8719
- const paths = basouPaths14(repositoryRoot);
8966
+ const paths = basouPaths15(repositoryRoot);
8720
8967
  const nowIso = (ctx.nowProvider?.() ?? /* @__PURE__ */ new Date()).toISOString();
8721
8968
  const summary = await findReviewGaps({
8722
8969
  paths,
@@ -8733,7 +8980,7 @@ async function doRunReviewGaps(options, ctx) {
8733
8980
  }
8734
8981
  return summary;
8735
8982
  }
8736
- function relAge(iso, now) {
8983
+ function relAge2(iso, now) {
8737
8984
  if (iso === null) return "(unknown)";
8738
8985
  const ms = now.getTime() - Date.parse(iso);
8739
8986
  if (!Number.isFinite(ms) || ms < 0) return "just now";
@@ -8746,11 +8993,11 @@ function relAge(iso, now) {
8746
8993
  function flatten(value) {
8747
8994
  return value.replace(/\s+/gu, " ").trim();
8748
8995
  }
8749
- var GRAPHEMES = new Intl.Segmenter(void 0, { granularity: "grapheme" });
8996
+ var GRAPHEMES2 = new Intl.Segmenter(void 0, { granularity: "grapheme" });
8750
8997
  function graphemes(value) {
8751
- return [...GRAPHEMES.segment(value)].map((s) => s.segment);
8998
+ return [...GRAPHEMES2.segment(value)].map((s) => s.segment);
8752
8999
  }
8753
- function oneLine(value, max) {
9000
+ function oneLine2(value, max) {
8754
9001
  const flat = graphemes(flatten(value));
8755
9002
  return flat.length > max ? `${flat.slice(0, max - 1).join("")}\u2026` : flat.join("");
8756
9003
  }
@@ -8763,7 +9010,7 @@ var SELF_REPORTS_SHOWN = 3;
8763
9010
  function selfReportSuffix(u, stillCounted) {
8764
9011
  if (u.selfReports.length === 0) return "";
8765
9012
  const parts = u.selfReports.slice(0, SELF_REPORTS_SHOWN).map(
8766
- (r) => `${oneLine(r.reviewer, 40)}${claimedCommits(r.commits)}${r.recordedAfterCommit ? " (recorded after the commit)" : ""}`
9013
+ (r) => `${oneLine2(r.reviewer, 40)}${claimedCommits(r.commits)}${r.recordedAfterCommit ? " (recorded after the commit)" : ""}`
8767
9014
  );
8768
9015
  const rest = u.selfReports.length - parts.length;
8769
9016
  if (rest > 0) parts.push(`+${rest} more`);
@@ -8776,7 +9023,7 @@ function unobservedOutcomeSuffix(u) {
8776
9023
  return ` \xB7 ${scope}exited with no recorded status \u2014 landing assumed, not observed`;
8777
9024
  }
8778
9025
  function unitLine(u, now) {
8779
- const when = relAge(u.lastCommitAt, now);
9026
+ const when = relAge2(u.lastCommitAt, now);
8780
9027
  const head = `- ${u.repo} ${when} (${u.commitCount} commit${u.commitCount === 1 ? "" : "s"})`;
8781
9028
  if (u.verdict === "near_unbound") {
8782
9029
  const ids = u.reviews.map((r) => r.sessionId.slice(0, 14)).join(", ");
@@ -8785,7 +9032,7 @@ function unitLine(u, now) {
8785
9032
  return `${head} \u2014 no bound cross-model review${selfReportSuffix(u, true)}${unobservedOutcomeSuffix(u)}`;
8786
9033
  }
8787
9034
  function candidateLine(u, now) {
8788
- const when = relAge(u.lastCommitAt, now);
9035
+ const when = relAge2(u.lastCommitAt, now);
8789
9036
  const cite = u.reviews.map((r) => `${r.sessionId.slice(0, 14)}${r.examinedDiff ? "(diff)" : ""}`).join(", ");
8790
9037
  return `- ${u.repo} ${when} (${u.commitCount} commit${u.commitCount === 1 ? "" : "s"}) \u2014 review trace: ${cite}${selfReportSuffix(u, false)}${unobservedOutcomeSuffix(u)}`;
8791
9038
  }
@@ -8821,7 +9068,7 @@ function renderReviewGaps(summary) {
8821
9068
  );
8822
9069
  for (const u of summary.unknowns) {
8823
9070
  lines.push(
8824
- `- ${relAge(u.lastCommitAt, now)} (${u.commitCount} commit${u.commitCount === 1 ? "" : "s"}) [${u.sessionId}]`
9071
+ `- ${relAge2(u.lastCommitAt, now)} (${u.commitCount} commit${u.commitCount === 1 ? "" : "s"}) [${u.sessionId}]`
8825
9072
  );
8826
9073
  }
8827
9074
  lines.push("");
@@ -8834,7 +9081,7 @@ function renderReviewGaps(summary) {
8834
9081
  }
8835
9082
  lines.push("");
8836
9083
  lines.push(
8837
- `Note: read-only advisory. Only captured commits are in scope (newest captured commit: ${summary.newestCommitAt === null ? "none" : relAge(summary.newestCommitAt, now)}). It never auto-judges that a review "happened", and temporal proximity alone is not a pass. It does not enforce.`
9084
+ `Note: read-only advisory. Only captured commits are in scope (newest captured commit: ${summary.newestCommitAt === null ? "none" : relAge2(summary.newestCommitAt, now)}). It never auto-judges that a review "happened", and temporal proximity alone is not a pass. It does not enforce.`
8838
9085
  );
8839
9086
  if (summary.gaps.some((u) => u.selfReports.length > 0)) {
8840
9087
  lines.push(
@@ -8884,7 +9131,7 @@ import { join as join14 } from "path";
8884
9131
  import {
8885
9132
  acquireLock as acquireLock5,
8886
9133
  assertBasouRootSafe as assertBasouRootSafe12,
8887
- basouPaths as basouPaths15,
9134
+ basouPaths as basouPaths16,
8888
9135
  ChildProcessRunner as ChildProcessRunner2,
8889
9136
  claudeCodeAdapterMetadata,
8890
9137
  codexAdapterMetadata,
@@ -8900,7 +9147,8 @@ import {
8900
9147
  readYamlFile as readYamlFile6,
8901
9148
  resolveClaudeCodeCommand,
8902
9149
  resolveCodexCommand,
8903
- resolveRepositoryRoot as resolveRepositoryRoot10,
9150
+ resolveRepositoryRoot as resolveRepositoryRoot9,
9151
+ SESSION_SCHEMA_VERSION as SESSION_SCHEMA_VERSION2,
8904
9152
  SessionSchema as SessionSchema2,
8905
9153
  sanitizeRelatedFiles,
8906
9154
  sanitizeWorkingDirectory as sanitizeWorkingDirectory2,
@@ -8955,7 +9203,7 @@ async function runTrackedTool(args, options, ctx, adapter) {
8955
9203
  const childArgs = adapter.transformArgs ? adapter.transformArgs(args) : args;
8956
9204
  const cwd = options.cwd ?? process.cwd();
8957
9205
  const repoRoot = await resolveRepositoryRootForRun(cwd);
8958
- const paths = basouPaths15(repoRoot);
9206
+ const paths = basouPaths16(repoRoot);
8959
9207
  await assertBasouRootSafe12(paths.root);
8960
9208
  const manifest = await readManifest9(paths);
8961
9209
  const sessionId = prefixedUlid4("ses");
@@ -9239,7 +9487,7 @@ function normalizeFileChangedSkipMessage(error) {
9239
9487
  function buildInitialSession2(input) {
9240
9488
  const cmdline = [input.command, ...input.args].join(" ");
9241
9489
  return {
9242
- schema_version: "0.1.0",
9490
+ schema_version: SESSION_SCHEMA_VERSION2,
9243
9491
  session: {
9244
9492
  id: input.id,
9245
9493
  label: `basou run ${cmdline} (${input.startedAt})`,
@@ -9310,7 +9558,7 @@ async function finalizeSessionAsFailed2(paths, sessionDir, sessionId, appendEven
9310
9558
  }
9311
9559
  async function resolveRepositoryRootForRun(cwd) {
9312
9560
  try {
9313
- return await resolveRepositoryRoot10(cwd);
9561
+ return await resolveRepositoryRoot9(cwd);
9314
9562
  } catch (error) {
9315
9563
  if (error instanceof Error && error.message === "Not a git repository") {
9316
9564
  throw new Error("Not a git repository. Run 'git init' first, then re-run 'basou run'.", {
@@ -9344,11 +9592,12 @@ import {
9344
9592
  acquireLock as acquireLock6,
9345
9593
  appendEventToExistingSession as appendEventToExistingSession3,
9346
9594
  assertBasouRootSafe as assertBasouRootSafe13,
9347
- basouPaths as basouPaths16,
9595
+ basouPaths as basouPaths17,
9348
9596
  EVENT_SCHEMA_VERSION as EVENT_SCHEMA_VERSION6,
9349
9597
  enumerateSessionDirs as enumerateSessionDirs2,
9350
9598
  findErrorCode as findErrorCode12,
9351
9599
  importSessionFromJson as importSessionFromJson2,
9600
+ LOCAL_CLI_EVENT_SOURCE as LOCAL_CLI_EVENT_SOURCE4,
9352
9601
  loadSessionEntries as loadSessionEntries2,
9353
9602
  readAllEvents,
9354
9603
  readManifest as readManifest10,
@@ -9363,7 +9612,7 @@ import {
9363
9612
  SessionStatusSchema,
9364
9613
  sessionWorkStatsFromEvents
9365
9614
  } from "@basou/core";
9366
- import { InvalidArgumentError as InvalidArgumentError5 } from "commander";
9615
+ import { InvalidArgumentError as InvalidArgumentError6 } from "commander";
9367
9616
 
9368
9617
  // src/lib/format-duration.ts
9369
9618
  import { formatDurationMs } from "@basou/core";
@@ -9412,7 +9661,7 @@ async function runSessionList(options, ctx = {}) {
9412
9661
  async function doRunSessionList(options, ctx) {
9413
9662
  const cwd = ctx.cwd ?? process.cwd();
9414
9663
  const repositoryRoot = await resolveRepositoryRootForSession(cwd, "list");
9415
- const paths = basouPaths16(repositoryRoot);
9664
+ const paths = basouPaths17(repositoryRoot);
9416
9665
  await assertWorkspaceInitialized11(paths.root);
9417
9666
  const now = /* @__PURE__ */ new Date();
9418
9667
  const records = (await loadSessionEntries2(paths, {
@@ -9464,7 +9713,7 @@ async function runSessionShow(idInput, options, ctx = {}) {
9464
9713
  async function doRunSessionShow(idInput, options, ctx) {
9465
9714
  const cwd = ctx.cwd ?? process.cwd();
9466
9715
  const repositoryRoot = await resolveRepositoryRootForSession(cwd, "show");
9467
- const paths = basouPaths16(repositoryRoot);
9716
+ const paths = basouPaths17(repositoryRoot);
9468
9717
  await assertWorkspaceInitialized11(paths.root);
9469
9718
  const sessionId = await resolveSessionId3(paths, idInput);
9470
9719
  const sessionDir = join15(paths.sessions, sessionId);
@@ -9776,7 +10025,7 @@ async function runSessionImport(options, ctx = {}) {
9776
10025
  async function doRunSessionImport(options, ctx) {
9777
10026
  const cwd = ctx.cwd ?? process.cwd();
9778
10027
  const repositoryRoot = await resolveRepositoryRootForSession(cwd, "import");
9779
- const paths = basouPaths16(repositoryRoot);
10028
+ const paths = basouPaths17(repositoryRoot);
9780
10029
  await assertWorkspaceInitialized11(paths.root);
9781
10030
  const manifest = await readManifest10(paths);
9782
10031
  const rawBody = await readInputFile(options.from);
@@ -9786,7 +10035,9 @@ async function doRunSessionImport(options, ctx) {
9786
10035
  throw new Error("Invalid import payload", { cause: parsed.error });
9787
10036
  }
9788
10037
  if (parsed.data.schema_version !== SESSION_IMPORT_SCHEMA_VERSION2) {
9789
- throw new Error(`Unsupported import schema_version: ${parsed.data.schema_version}`);
10038
+ throw new Error(
10039
+ `Unsupported import schema_version: ${parsed.data.schema_version} (expected ${SESSION_IMPORT_SCHEMA_VERSION2}). The envelope's shape is published as @basou/core/schemas/session-import.schema.json; this version may move at a minor release, so a producer should read it from the installed @basou/core rather than pinning it.`
10040
+ );
9790
10041
  }
9791
10042
  const importOptions2 = { dryRun: options.dryRun === true };
9792
10043
  if (options.label !== void 0) importOptions2.labelOverride = options.label;
@@ -9825,19 +10076,19 @@ function parseJsonStrict(body) {
9825
10076
  }
9826
10077
  function parseImportFormat(raw) {
9827
10078
  if (raw !== "json") {
9828
- throw new InvalidArgumentError5(`Unsupported format: ${raw}. Valid values: json`);
10079
+ throw new InvalidArgumentError6(`Unsupported format: ${raw}. Valid values: json`);
9829
10080
  }
9830
10081
  return "json";
9831
10082
  }
9832
10083
  function parseLabelOverride(raw) {
9833
10084
  if (raw.length === 0) {
9834
- throw new InvalidArgumentError5("Label must not be empty");
10085
+ throw new InvalidArgumentError6("Label must not be empty");
9835
10086
  }
9836
10087
  return raw;
9837
10088
  }
9838
10089
  function parseTaskIdOverride(raw) {
9839
10090
  if (raw.length === 0) {
9840
- throw new InvalidArgumentError5("Task id is empty");
10091
+ throw new InvalidArgumentError6("Task id is empty");
9841
10092
  }
9842
10093
  return raw;
9843
10094
  }
@@ -9890,7 +10141,7 @@ async function doRunSessionNote(sessionIdInput, options, ctx) {
9890
10141
  }
9891
10142
  const cwd = ctx.cwd ?? process.cwd();
9892
10143
  const repositoryRoot = await resolveRepositoryRootForSession(cwd, "note");
9893
- const paths = basouPaths16(repositoryRoot);
10144
+ const paths = basouPaths17(repositoryRoot);
9894
10145
  await assertWorkspaceInitialized11(paths.root);
9895
10146
  const sessionId = await resolveSessionId3(paths, sessionIdInput);
9896
10147
  const body = hasBody ? options.body : await readNoteFile(options.fromFile);
@@ -9910,7 +10161,7 @@ async function doRunSessionNote(sessionIdInput, options, ctx) {
9910
10161
  id: eventId,
9911
10162
  session_id: sesId,
9912
10163
  occurred_at: occurredAt,
9913
- source: "local-cli",
10164
+ source: LOCAL_CLI_EVENT_SOURCE4,
9914
10165
  type: "note_added",
9915
10166
  body
9916
10167
  })
@@ -9935,7 +10186,7 @@ async function readNoteFile(path) {
9935
10186
  }
9936
10187
  function parseNoteBodyOption(raw) {
9937
10188
  if (raw.length === 0) {
9938
- throw new InvalidArgumentError5("--body must not be empty");
10189
+ throw new InvalidArgumentError6("--body must not be empty");
9939
10190
  }
9940
10191
  return raw;
9941
10192
  }
@@ -9972,7 +10223,7 @@ async function doRunSessionRechain(options, ctx) {
9972
10223
  }
9973
10224
  const cwd = ctx.cwd ?? process.cwd();
9974
10225
  const repositoryRoot = await resolveRepositoryRootForSession(cwd, "rechain");
9975
- const paths = basouPaths16(repositoryRoot);
10226
+ const paths = basouPaths17(repositoryRoot);
9976
10227
  await assertWorkspaceInitialized11(paths.root);
9977
10228
  const sessionIds = options.session !== void 0 ? [await resolveSessionId3(paths, options.session)] : await enumerateSessionDirs2(paths);
9978
10229
  const dryRun = options.dryRun === true;
@@ -10027,10 +10278,10 @@ function renderRechainRow(row, dryRun) {
10027
10278
  // src/commands/stats.ts
10028
10279
  import {
10029
10280
  assertBasouRootSafe as assertBasouRootSafe14,
10030
- basouPaths as basouPaths17,
10281
+ basouPaths as basouPaths18,
10031
10282
  computeWorkStats,
10032
10283
  findErrorCode as findErrorCode13,
10033
- resolveRepositoryRoot as resolveRepositoryRoot11
10284
+ resolveRepositoryRoot as resolveRepositoryRoot10
10034
10285
  } from "@basou/core";
10035
10286
  function registerStatsCommand(program2) {
10036
10287
  program2.command("stats").description("Report how much the AI worked (output volume + time proxies) across sessions").option("--by-source", "Break the totals down by session source kind").option("--by-day", "Break billable time and volume down by calendar day").option("--json", "Output the full stats as JSON").option("-v, --verbose", "Show error causes").action(async (options) => {
@@ -10048,7 +10299,7 @@ async function runStats(options, ctx = {}) {
10048
10299
  async function doRunStats(options, ctx) {
10049
10300
  const cwd = ctx.cwd ?? process.cwd();
10050
10301
  const repositoryRoot = await resolveRepositoryRootForStats(cwd);
10051
- const paths = basouPaths17(repositoryRoot);
10302
+ const paths = basouPaths18(repositoryRoot);
10052
10303
  await assertWorkspaceInitialized12(paths.root);
10053
10304
  const now = ctx.nowProvider?.() ?? /* @__PURE__ */ new Date();
10054
10305
  const result = await computeWorkStats({
@@ -10137,7 +10388,7 @@ function formatInt(n) {
10137
10388
  }
10138
10389
  async function resolveRepositoryRootForStats(cwd) {
10139
10390
  try {
10140
- return await resolveRepositoryRoot11(cwd);
10391
+ return await resolveRepositoryRoot10(cwd);
10141
10392
  } catch (error) {
10142
10393
  if (error instanceof Error && error.message === "Not a git repository") {
10143
10394
  throw new Error("Not a git repository. Run 'git init' first, then re-run 'basou stats'.", {
@@ -10161,11 +10412,11 @@ async function assertWorkspaceInitialized12(basouRoot) {
10161
10412
  // src/commands/status.ts
10162
10413
  import {
10163
10414
  assertBasouRootSafe as assertBasouRootSafe15,
10164
- basouPaths as basouPaths18,
10415
+ basouPaths as basouPaths19,
10165
10416
  buildStatusSnapshot,
10166
10417
  findErrorCode as findErrorCode14,
10167
10418
  readManifest as readManifest11,
10168
- resolveRepositoryRoot as resolveRepositoryRoot12,
10419
+ resolveRepositoryRoot as resolveRepositoryRoot11,
10169
10420
  writeStatus
10170
10421
  } from "@basou/core";
10171
10422
  function registerStatusCommand(program2) {
@@ -10184,7 +10435,7 @@ async function runStatus(options, ctx = {}) {
10184
10435
  async function doRunStatus(options, ctx) {
10185
10436
  const cwd = ctx.cwd ?? process.cwd();
10186
10437
  const repositoryRoot = await resolveRepositoryRootForStatus(cwd);
10187
- const paths = basouPaths18(repositoryRoot);
10438
+ const paths = basouPaths19(repositoryRoot);
10188
10439
  try {
10189
10440
  await assertBasouRootSafe15(paths.root);
10190
10441
  } catch (error) {
@@ -10223,7 +10474,7 @@ function renderTextStatus(s) {
10223
10474
  }
10224
10475
  async function resolveRepositoryRootForStatus(cwd) {
10225
10476
  try {
10226
- return await resolveRepositoryRoot12(cwd);
10477
+ return await resolveRepositoryRoot11(cwd);
10227
10478
  } catch (error) {
10228
10479
  if (error instanceof Error && error.message === "Not a git repository") {
10229
10480
  throw new Error("Not a git repository. Run 'git init' first, then re-run 'basou status'.", {
@@ -10252,7 +10503,7 @@ import { join as join16 } from "path";
10252
10503
  import {
10253
10504
  archiveTask,
10254
10505
  assertBasouRootSafe as assertBasouRootSafe16,
10255
- basouPaths as basouPaths19,
10506
+ basouPaths as basouPaths20,
10256
10507
  createTaskWithEvent,
10257
10508
  deleteTask,
10258
10509
  editTask,
@@ -10274,7 +10525,7 @@ import {
10274
10525
  TaskWriteAfterEventError,
10275
10526
  updateTaskStatusWithEvent
10276
10527
  } from "@basou/core";
10277
- import { InvalidArgumentError as InvalidArgumentError6 } from "commander";
10528
+ import { InvalidArgumentError as InvalidArgumentError7 } from "commander";
10278
10529
  var STATUS_VALUES3 = TaskStatusSchema.options;
10279
10530
  function registerTaskCommand(program2) {
10280
10531
  const task = program2.command("task").description("Manage Basou tasks (purpose units that span sessions)");
@@ -10353,7 +10604,7 @@ async function doRunTaskNew(options, ctx) {
10353
10604
  }
10354
10605
  const cwd = ctx.cwd ?? process.cwd();
10355
10606
  const repositoryRoot = await resolveRepositoryRootForTask(cwd, "new");
10356
- const paths = basouPaths19(repositoryRoot);
10607
+ const paths = basouPaths20(repositoryRoot);
10357
10608
  await assertWorkspaceInitialized13(paths.root);
10358
10609
  const description = options.description !== void 0 ? options.description : options.fromFile !== void 0 ? await readDescriptionFile(options.fromFile) : "";
10359
10610
  const now = ctx.nowProvider !== void 0 ? ctx.nowProvider() : /* @__PURE__ */ new Date();
@@ -10462,7 +10713,7 @@ async function runTaskList(options, ctx = {}) {
10462
10713
  async function doRunTaskList(options, ctx) {
10463
10714
  const cwd = ctx.cwd ?? process.cwd();
10464
10715
  const repositoryRoot = await resolveRepositoryRootForTask(cwd, "list");
10465
- const paths = basouPaths19(repositoryRoot);
10716
+ const paths = basouPaths20(repositoryRoot);
10466
10717
  await assertWorkspaceInitialized13(paths.root);
10467
10718
  const entries = await loadTaskEntries(paths, {
10468
10719
  onSkip: (id, reason) => printTaskSkip(id, reason)
@@ -10566,7 +10817,7 @@ async function runTaskShow(idInput, options, ctx = {}) {
10566
10817
  async function doRunTaskShow(idInput, options, ctx) {
10567
10818
  const cwd = ctx.cwd ?? process.cwd();
10568
10819
  const repositoryRoot = await resolveRepositoryRootForTask(cwd, "show");
10569
- const paths = basouPaths19(repositoryRoot);
10820
+ const paths = basouPaths20(repositoryRoot);
10570
10821
  await assertWorkspaceInitialized13(paths.root);
10571
10822
  const taskId = await resolveTaskId2(paths, idInput, { includeArchived: true });
10572
10823
  const { doc, archived } = await readTaskFileWithArchiveFallback(paths, taskId);
@@ -10710,7 +10961,7 @@ async function doRunTaskStatus(taskIdInput, newStatusInput, options, ctx) {
10710
10961
  const newStatus = parseTaskStatusPositional(newStatusInput);
10711
10962
  const cwd = ctx.cwd ?? process.cwd();
10712
10963
  const repositoryRoot = await resolveRepositoryRootForTask(cwd, "status");
10713
- const paths = basouPaths19(repositoryRoot);
10964
+ const paths = basouPaths20(repositoryRoot);
10714
10965
  await assertWorkspaceInitialized13(paths.root);
10715
10966
  const taskId = await resolveTaskId2(paths, taskIdInput);
10716
10967
  const now = ctx.nowProvider !== void 0 ? ctx.nowProvider() : /* @__PURE__ */ new Date();
@@ -10787,7 +11038,7 @@ async function runTaskReconcile(options, ctx = {}) {
10787
11038
  async function doRunTaskReconcile(options, ctx) {
10788
11039
  const cwd = ctx.cwd ?? process.cwd();
10789
11040
  const repositoryRoot = await resolveRepositoryRootForTask(cwd, "reconcile");
10790
- const paths = basouPaths19(repositoryRoot);
11041
+ const paths = basouPaths20(repositoryRoot);
10791
11042
  await assertWorkspaceInitialized13(paths.root);
10792
11043
  const manifest = await readManifest12(paths);
10793
11044
  const nowProvider = ctx.nowProvider ?? (() => /* @__PURE__ */ new Date());
@@ -10967,7 +11218,7 @@ async function doRunTaskRefreshLinkage(taskIdInput, options, ctx) {
10967
11218
  }
10968
11219
  const cwd = ctx.cwd ?? process.cwd();
10969
11220
  const repositoryRoot = await resolveRepositoryRootForTask(cwd, "refresh-linkage");
10970
- const paths = basouPaths19(repositoryRoot);
11221
+ const paths = basouPaths20(repositoryRoot);
10971
11222
  await assertWorkspaceInitialized13(paths.root);
10972
11223
  const manifest = await readManifest12(paths);
10973
11224
  const taskId = await resolveTaskId2(paths, taskIdInput);
@@ -11047,7 +11298,7 @@ async function doRunTaskEdit(taskIdInput, options, ctx) {
11047
11298
  }
11048
11299
  const cwd = ctx.cwd ?? process.cwd();
11049
11300
  const repositoryRoot = await resolveRepositoryRootForTask(cwd, "edit");
11050
- const paths = basouPaths19(repositoryRoot);
11301
+ const paths = basouPaths20(repositoryRoot);
11051
11302
  await assertWorkspaceInitialized13(paths.root);
11052
11303
  const manifest = await readManifest12(paths);
11053
11304
  const taskId = await resolveTaskId2(paths, taskIdInput);
@@ -11103,7 +11354,7 @@ async function doRunTaskDelete(taskIdInput, options, ctx) {
11103
11354
  }
11104
11355
  const cwd = ctx.cwd ?? process.cwd();
11105
11356
  const repositoryRoot = await resolveRepositoryRootForTask(cwd, "delete");
11106
- const paths = basouPaths19(repositoryRoot);
11357
+ const paths = basouPaths20(repositoryRoot);
11107
11358
  await assertWorkspaceInitialized13(paths.root);
11108
11359
  const manifest = await readManifest12(paths);
11109
11360
  const taskId = await resolveTaskId2(paths, taskIdInput);
@@ -11148,7 +11399,7 @@ async function doRunTaskArchive(taskIdInput, options, ctx) {
11148
11399
  }
11149
11400
  const cwd = ctx.cwd ?? process.cwd();
11150
11401
  const repositoryRoot = await resolveRepositoryRootForTask(cwd, "archive");
11151
- const paths = basouPaths19(repositoryRoot);
11402
+ const paths = basouPaths20(repositoryRoot);
11152
11403
  await assertWorkspaceInitialized13(paths.root);
11153
11404
  const manifest = await readManifest12(paths);
11154
11405
  const taskId = await resolveTaskId2(paths, taskIdInput);
@@ -11203,20 +11454,20 @@ async function readSingleLineFromStdin() {
11203
11454
  }
11204
11455
  function parseTitle2(raw) {
11205
11456
  if (raw.length === 0) {
11206
- throw new InvalidArgumentError6("Title must not be empty");
11457
+ throw new InvalidArgumentError7("Title must not be empty");
11207
11458
  }
11208
11459
  return raw;
11209
11460
  }
11210
11461
  function parseLabel(raw) {
11211
11462
  if (raw.length === 0) {
11212
- throw new InvalidArgumentError6("Label must not be empty");
11463
+ throw new InvalidArgumentError7("Label must not be empty");
11213
11464
  }
11214
11465
  return raw;
11215
11466
  }
11216
11467
  function parseInitialTaskStatus(raw) {
11217
11468
  const result = TaskStatusSchema.safeParse(raw);
11218
11469
  if (!result.success) {
11219
- throw new InvalidArgumentError6(
11470
+ throw new InvalidArgumentError7(
11220
11471
  `Initial task status must be one of: ${STATUS_VALUES3.join(", ")}`
11221
11472
  );
11222
11473
  }
@@ -11225,7 +11476,7 @@ function parseInitialTaskStatus(raw) {
11225
11476
  var ISO_DATE_RE = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d+)?(?:Z|[+-]\d{2}:\d{2})$/;
11226
11477
  function parseIsoTimestampOption(raw) {
11227
11478
  if (!ISO_DATE_RE.test(raw) || Number.isNaN(Date.parse(raw))) {
11228
- throw new InvalidArgumentError6(
11479
+ throw new InvalidArgumentError7(
11229
11480
  "Invalid --completed-at value; expected ISO-8601 timestamp like 2026-05-10T12:34:56+09:00"
11230
11481
  );
11231
11482
  }
@@ -11234,7 +11485,7 @@ function parseIsoTimestampOption(raw) {
11234
11485
  function parseTaskStatusFilter(raw) {
11235
11486
  const result = TaskStatusSchema.safeParse(raw);
11236
11487
  if (!result.success) {
11237
- throw new InvalidArgumentError6(
11488
+ throw new InvalidArgumentError7(
11238
11489
  `Invalid task status: ${raw}. Valid values: ${STATUS_VALUES3.join(", ")}`
11239
11490
  );
11240
11491
  }
@@ -11249,14 +11500,14 @@ function parseTaskStatusPositional(raw) {
11249
11500
  }
11250
11501
  function parseDescriptionOption(raw) {
11251
11502
  if (raw.length === 0) {
11252
- throw new InvalidArgumentError6("Description must not be empty");
11503
+ throw new InvalidArgumentError7("Description must not be empty");
11253
11504
  }
11254
11505
  return raw;
11255
11506
  }
11256
11507
  function parsePositiveInt2(raw) {
11257
11508
  const n = Number.parseInt(raw, 10);
11258
11509
  if (!Number.isInteger(n) || n < 1 || raw.trim() !== String(n)) {
11259
- throw new InvalidArgumentError6(`Invalid number: ${raw}`);
11510
+ throw new InvalidArgumentError7(`Invalid number: ${raw}`);
11260
11511
  }
11261
11512
  return n;
11262
11513
  }
@@ -11369,10 +11620,10 @@ function maxLen3(values, floor) {
11369
11620
  // src/commands/verify.ts
11370
11621
  import {
11371
11622
  assertBasouRootSafe as assertBasouRootSafe17,
11372
- basouPaths as basouPaths20,
11623
+ basouPaths as basouPaths21,
11373
11624
  enumerateSessionDirs as enumerateSessionDirs3,
11374
11625
  findErrorCode as findErrorCode16,
11375
- resolveRepositoryRoot as resolveRepositoryRoot13,
11626
+ resolveRepositoryRoot as resolveRepositoryRoot12,
11376
11627
  resolveSessionId as resolveSessionId5,
11377
11628
  verifyEventsChain
11378
11629
  } from "@basou/core";
@@ -11395,7 +11646,7 @@ async function doRunVerify(options, ctx) {
11395
11646
  }
11396
11647
  const cwd = ctx.cwd ?? process.cwd();
11397
11648
  const repositoryRoot = await resolveRepositoryRootForVerify(cwd);
11398
- const paths = basouPaths20(repositoryRoot);
11649
+ const paths = basouPaths21(repositoryRoot);
11399
11650
  await assertWorkspaceInitialized14(paths.root);
11400
11651
  const sessionIds = options.session !== void 0 ? [await resolveSessionId5(paths, options.session)] : await enumerateSessionDirs3(paths);
11401
11652
  const rows = [];
@@ -11443,7 +11694,7 @@ function renderVerdict(row) {
11443
11694
  }
11444
11695
  async function resolveRepositoryRootForVerify(cwd) {
11445
11696
  try {
11446
- return await resolveRepositoryRoot13(cwd);
11697
+ return await resolveRepositoryRoot12(cwd);
11447
11698
  } catch (error) {
11448
11699
  if (error instanceof Error && error.message === "Not a git repository") {
11449
11700
  throw new Error("Not a git repository. Run 'git init' first, then re-run 'basou verify'.", {
@@ -11470,12 +11721,12 @@ import { createHash as createHash2 } from "crypto";
11470
11721
  import { basename as basename10, resolve as resolve13 } from "path";
11471
11722
  import {
11472
11723
  assertBasouRootSafe as assertBasouRootSafe18,
11473
- basouPaths as basouPaths22,
11724
+ basouPaths as basouPaths23,
11474
11725
  findErrorCode as findErrorCode18,
11475
11726
  readManifest as readManifest16,
11476
- resolveRepositoryRoot as resolveRepositoryRoot15
11727
+ resolveRepositoryRoot as resolveRepositoryRoot14
11477
11728
  } from "@basou/core";
11478
- import { InvalidArgumentError as InvalidArgumentError7 } from "commander";
11729
+ import { InvalidArgumentError as InvalidArgumentError8 } from "commander";
11479
11730
 
11480
11731
  // src/lib/portfolio-coverage.ts
11481
11732
  import { createReadStream as createReadStream2 } from "fs";
@@ -11483,7 +11734,7 @@ import { readdir as readdir3, stat as stat6 } from "fs/promises";
11483
11734
  import { homedir as homedir12 } from "os";
11484
11735
  import { basename as basename8, dirname as dirname5, join as join17 } from "path";
11485
11736
  import { createInterface as createInterface2 } from "readline";
11486
- import { basouPaths as basouPaths21, readManifest as readManifest13, resolveRepositoryRoot as resolveRepositoryRoot14 } from "@basou/core";
11737
+ import { basouPaths as basouPaths22, readManifest as readManifest13, resolveRepositoryRoot as resolveRepositoryRoot13 } from "@basou/core";
11487
11738
  function uncapturedTotal(result) {
11488
11739
  return result.groups.reduce((sum, g) => sum + g.logs, 0) + result.unplaceable;
11489
11740
  }
@@ -11519,14 +11770,14 @@ async function collectDeclaredRoots(workspaces) {
11519
11770
  for (const ws of workspaces) {
11520
11771
  let importRoot;
11521
11772
  try {
11522
- importRoot = await resolveRepositoryRoot14(ws.repoRoot);
11773
+ importRoot = await resolveRepositoryRoot13(ws.repoRoot);
11523
11774
  } catch {
11524
11775
  inertWorkspaces.push({ path: ws.repoRoot, reason: "not_a_git_repo" });
11525
11776
  continue;
11526
11777
  }
11527
11778
  let resolved;
11528
11779
  try {
11529
- const manifest = await readManifest13(basouPaths21(importRoot));
11780
+ const manifest = await readManifest13(basouPaths22(importRoot));
11530
11781
  resolved = resolveSourceRoots({
11531
11782
  projectFlags: [],
11532
11783
  manifest,
@@ -11793,12 +12044,12 @@ function inertLines(result) {
11793
12044
  }
11794
12045
 
11795
12046
  // src/lib/portfolio-safety.ts
11796
- import { execFile } from "child_process";
12047
+ import { execFile as execFile2 } from "child_process";
11797
12048
  import { lstat as lstat2, realpath as realpath4 } from "fs/promises";
11798
12049
  import { isAbsolute as isAbsolute7, join as join18, relative as relative4, resolve as resolve11 } from "path";
11799
- import { promisify } from "util";
12050
+ import { promisify as promisify2 } from "util";
11800
12051
  import { readManifest as readManifest14 } from "@basou/core";
11801
- var execFileAsync = promisify(execFile);
12052
+ var execFileAsync2 = promisify2(execFile2);
11802
12053
  function errorCode(error) {
11803
12054
  return error instanceof Error ? error.code : void 0;
11804
12055
  }
@@ -11830,7 +12081,7 @@ async function inspectRepo(repoPath) {
11830
12081
  }
11831
12082
  }
11832
12083
  try {
11833
- const { stdout } = await execFileAsync("git", ["-C", repoPath, "ls-files", "-z"]);
12084
+ const { stdout } = await execFileAsync2("git", ["-C", repoPath, "ls-files", "-z"]);
11834
12085
  const tracked = stdout.split("\0").some((f) => f.length > 0 && isBasouPath(f));
11835
12086
  if (tracked) {
11836
12087
  return {
@@ -13163,7 +13414,7 @@ var DEFAULT_PORT = 4319;
13163
13414
  function parsePort(value) {
13164
13415
  const port = Number.parseInt(value, 10);
13165
13416
  if (!Number.isInteger(port) || port < 1 || port > 65535) {
13166
- throw new InvalidArgumentError7("Port must be an integer between 1 and 65535.");
13417
+ throw new InvalidArgumentError8("Port must be an integer between 1 and 65535.");
13167
13418
  }
13168
13419
  return port;
13169
13420
  }
@@ -13249,7 +13500,7 @@ async function doRunView(options, ctx) {
13249
13500
  }
13250
13501
  async function buildSingleDeps(ctx, cwd) {
13251
13502
  const repositoryRoot = await resolveRepositoryRootForView(cwd);
13252
- const paths = basouPaths22(repositoryRoot);
13503
+ const paths = basouPaths23(repositoryRoot);
13253
13504
  await assertWorkspaceInitialized15(paths.root);
13254
13505
  const entry = await buildWorkspaceEntry(repositoryRoot, ctx);
13255
13506
  return {
@@ -13283,7 +13534,7 @@ async function buildPortfolioDeps(workspaceFlags, ctx, cwd) {
13283
13534
  };
13284
13535
  }
13285
13536
  async function buildWorkspaceEntry(repoRoot, ctx, labelOverride) {
13286
- const paths = basouPaths22(repoRoot);
13537
+ const paths = basouPaths23(repoRoot);
13287
13538
  const importCtx = {
13288
13539
  cwd: repoRoot,
13289
13540
  ...ctx.claudeProjectsDir !== void 0 ? { claudeProjectsDir: ctx.claudeProjectsDir } : {},
@@ -13370,7 +13621,7 @@ function waitForShutdown(signal) {
13370
13621
  }
13371
13622
  async function resolveRepositoryRootForView(cwd) {
13372
13623
  try {
13373
- return await resolveRepositoryRoot15(cwd);
13624
+ return await resolveRepositoryRoot14(cwd);
13374
13625
  } catch (error) {
13375
13626
  if (error instanceof Error && error.message === "Not a git repository") {
13376
13627
  throw new Error("Not a git repository. Run 'git init' first, then re-run 'basou view'.", {
@@ -13392,12 +13643,30 @@ async function assertWorkspaceInitialized15(basouRoot) {
13392
13643
  }
13393
13644
 
13394
13645
  // src/program.ts
13646
+ function readBuildStamp() {
13647
+ if (false) return void 0;
13648
+ try {
13649
+ return JSON.parse('{"version":"0.45.0","commit":"6b04208","committedAt":"2026-09-19T01:07:28+09:00"}');
13650
+ } catch {
13651
+ return void 0;
13652
+ }
13653
+ }
13395
13654
  var require2 = createRequire(import.meta.url);
13396
13655
  var pkg = require2("../package.json");
13397
- var BASOU_CLI_VERSION = pkg.version;
13656
+ var BASOU_BUILD = readBuildStamp();
13657
+ var BASOU_CLI_VERSION = BASOU_BUILD?.version ?? pkg.version;
13658
+ var BASOU_VERSION_LINE = buildVersionLine();
13659
+ function buildVersionLine() {
13660
+ if (BASOU_BUILD === void 0) return `${pkg.version} (source)`;
13661
+ const coreBuild = basouCore.BASOU_CORE_BUILD;
13662
+ const self = `${BASOU_BUILD.version} (build ${BASOU_BUILD.commit}, ${BASOU_BUILD.committedAt})`;
13663
+ if (coreBuild === void 0) return `${self}; core build unknown (not stamped)`;
13664
+ if (coreBuild.commit === BASOU_BUILD.commit) return self;
13665
+ return `${self}; core build ${coreBuild.commit}, ${coreBuild.committedAt}`;
13666
+ }
13398
13667
  function buildProgram() {
13399
13668
  const program2 = new Command();
13400
- program2.name("basou").description("A harness for steering AI coding agents").version(BASOU_CLI_VERSION).enablePositionalOptions();
13669
+ program2.name("basou").description("A harness for steering AI coding agents").version(BASOU_VERSION_LINE).enablePositionalOptions();
13401
13670
  registerInitCommand(program2);
13402
13671
  registerStatusCommand(program2);
13403
13672
  registerStatsCommand(program2);