@basou/cli 0.13.1 → 0.14.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
@@ -633,6 +633,9 @@ function printNoApprovals(options) {
633
633
  }
634
634
 
635
635
  // src/commands/decision.ts
636
+ import { readFile } from "fs/promises";
637
+ import { homedir } from "os";
638
+ import { resolve } from "path";
636
639
  import {
637
640
  acquireLock as acquireLock2,
638
641
  appendEventToExistingSession,
@@ -640,12 +643,34 @@ import {
640
643
  basouPaths as basouPaths2,
641
644
  createAdHocSessionWithEvent,
642
645
  findErrorCode as findErrorCode2,
646
+ isValidPrefixedId,
643
647
  prefixedUlid as prefixedUlid2,
644
648
  readManifest,
645
649
  resolveRepositoryRoot as resolveRepositoryRoot2,
646
- resolveSessionId
650
+ resolveSessionId,
651
+ sanitizePath
647
652
  } from "@basou/core";
648
653
  import { InvalidArgumentError } from "commander";
654
+
655
+ // src/lib/repo-root.ts
656
+ import { resolveBasouRepositoryRoot } from "@basou/core";
657
+ async function resolveBasouRootForCommand(cwd, commandName) {
658
+ try {
659
+ return await resolveBasouRepositoryRoot(cwd, {
660
+ onRedirect: ({ via, root }) => console.error(`Resolved workspace view to ${root} (via ${via}).`)
661
+ });
662
+ } catch (error) {
663
+ if (error instanceof Error && error.message === "Not a git repository") {
664
+ throw new Error(
665
+ `Not a git repository. Run 'git init' first, then re-run 'basou ${commandName}'.`,
666
+ { cause: error }
667
+ );
668
+ }
669
+ throw error;
670
+ }
671
+ }
672
+
673
+ // src/commands/decision.ts
649
674
  var LABEL_TITLE_MAX = 80;
650
675
  var LABEL_TRUNCATE_HEAD = LABEL_TITLE_MAX - 3;
651
676
  function registerDecisionCommand(program2) {
@@ -675,7 +700,34 @@ function registerDecisionCommand(program2) {
675
700
  ).option("--json", "Output the result as JSON").option("-v, --verbose", "Show error causes").action(async (options) => {
676
701
  await runDecisionRecord(options);
677
702
  });
703
+ decision.command("capture").description(
704
+ "Capture a batch of decisions from a JSON array (stdin or --file). The in-loop agent extracts a session's conversational decisions -- with rationale, alternatives, and rejected reasons -- and pipes them in; basou writes them deterministically into one ad-hoc session."
705
+ ).option("--file <path>", "Read the JSON array from a file instead of stdin").option("--dry-run", "Validate and preview the decisions without writing them").option("--json", "Output the result as JSON").option("-v, --verbose", "Show error causes").addHelpText("after", CAPTURE_HELP).action(async (options) => {
706
+ await runDecisionCapture(options);
707
+ });
678
708
  }
709
+ var CAPTURE_HELP = `
710
+ Input format (a JSON array; one object per decision):
711
+ [
712
+ {
713
+ "title": "Adopt pnpm for the monorepo",
714
+ "rationale": "Workspace protocol and a content-addressed store fit our layout.",
715
+ "alternatives": ["npm workspaces", "yarn"],
716
+ "rejected_reason": "npm hoisting caused phantom-dependency bugs",
717
+ "linked_files": ["pnpm-workspace.yaml"]
718
+ }
719
+ ]
720
+
721
+ Only "title" is required; every other field is optional. All decisions are
722
+ written into one ad-hoc session timestamped now, so orientation surfaces them
723
+ as the latest decisions. Run from a workspace-view directory and it resolves to
724
+ the planning repo, like 'basou orient' / 'basou refresh' / 'basou note'.
725
+
726
+ Example (heredoc on stdin):
727
+ basou decision capture <<'JSON'
728
+ [{ "title": "Ship the capture command", "rationale": "Close the why-capture gap" }]
729
+ JSON
730
+ `;
679
731
  async function runDecisionRecord(options, ctx = {}) {
680
732
  try {
681
733
  await doRunDecisionRecord(options, ctx);
@@ -761,6 +813,259 @@ async function doRunDecisionRecord(options, ctx) {
761
813
  rich
762
814
  });
763
815
  }
816
+ async function runDecisionCapture(options, ctx = {}) {
817
+ try {
818
+ await doRunDecisionCapture(options, ctx);
819
+ } catch (error) {
820
+ renderCliError(error, {
821
+ verbose: isVerbose(options),
822
+ classifiers: [failedToFinalizeClassifier]
823
+ });
824
+ process.exitCode = 1;
825
+ }
826
+ }
827
+ async function doRunDecisionCapture(options, ctx) {
828
+ const cwd = ctx.cwd ?? process.cwd();
829
+ const repositoryRoot = await resolveBasouRootForCommand(cwd, "decision capture");
830
+ const paths = basouPaths2(repositoryRoot);
831
+ await assertWorkspaceInitialized2(paths.root);
832
+ const raw = await readCaptureInput(options, ctx);
833
+ const decisions = parseCaptureInput(raw);
834
+ if (options.dryRun === true) {
835
+ printCapturePreview(options, decisions);
836
+ return;
837
+ }
838
+ const now = ctx.nowProvider !== void 0 ? ctx.nowProvider() : /* @__PURE__ */ new Date();
839
+ const occurredAt = now.toISOString();
840
+ const decisionIds = decisions.map(() => prefixedUlid2("decision"));
841
+ const manifest = await readManifest(paths);
842
+ const invocationArgs = options.file !== void 0 ? [
843
+ "--file",
844
+ sanitizePath(resolve(cwd, options.file), {
845
+ workingDirectory: repositoryRoot,
846
+ homedir: homedir()
847
+ })
848
+ ] : [];
849
+ const adHoc = await createAdHocSessionWithEvent({
850
+ paths,
851
+ manifest,
852
+ label: buildCaptureLabel(decisions.length),
853
+ occurredAt,
854
+ sessionSource: "human",
855
+ workingDirectory: repositoryRoot,
856
+ invocation: {
857
+ command: "basou decision capture",
858
+ args: invocationArgs
859
+ },
860
+ targetEventBuilders: decisions.map(
861
+ (decision, index) => (sessionId, eventId) => buildDecisionEvent({
862
+ eventId,
863
+ sessionId,
864
+ decisionId: decisionIds[index],
865
+ title: decision.title,
866
+ occurredAt,
867
+ rich: toRichFields(decision)
868
+ })
869
+ )
870
+ });
871
+ printCaptureResult(options, {
872
+ sessionId: adHoc.sessionId,
873
+ items: decisions.map((decision, index) => ({
874
+ decisionId: decisionIds[index],
875
+ eventId: adHoc.targetEventIds[index],
876
+ input: decision
877
+ }))
878
+ });
879
+ }
880
+ async function readCaptureInput(options, ctx) {
881
+ if (options.file !== void 0) {
882
+ try {
883
+ return await readFile(options.file, "utf8");
884
+ } catch (error) {
885
+ if (findErrorCode2(error, "ENOENT")) {
886
+ throw new Error(`Input file not found: ${options.file}`);
887
+ }
888
+ throw error;
889
+ }
890
+ }
891
+ if (ctx.readInput !== void 0) {
892
+ return await ctx.readInput();
893
+ }
894
+ if (process.stdin.isTTY === true) {
895
+ throw new Error(NO_INPUT_HINT);
896
+ }
897
+ return await readStdinToEnd();
898
+ }
899
+ async function readStdinToEnd() {
900
+ const chunks = [];
901
+ for await (const chunk of process.stdin) {
902
+ chunks.push(Buffer.from(chunk));
903
+ }
904
+ return Buffer.concat(chunks).toString("utf8");
905
+ }
906
+ var NO_INPUT_HINT = "No input: pipe a JSON array of decisions to stdin or pass --file <path>.";
907
+ var CAPTURE_ALLOWED_KEYS = /* @__PURE__ */ new Set([
908
+ "title",
909
+ "rationale",
910
+ "rejected_reason",
911
+ "alternatives",
912
+ "linked_events",
913
+ "linked_files"
914
+ ]);
915
+ function parseCaptureInput(raw) {
916
+ if (raw.trim().length === 0) {
917
+ throw new Error(NO_INPUT_HINT);
918
+ }
919
+ let parsed;
920
+ try {
921
+ parsed = JSON.parse(raw);
922
+ } catch (error) {
923
+ const detail = error instanceof Error ? error.message : String(error);
924
+ throw new Error(`Input is not valid JSON: ${detail}`);
925
+ }
926
+ if (!Array.isArray(parsed)) {
927
+ throw new Error("Input must be a JSON array of decision objects.");
928
+ }
929
+ if (parsed.length === 0) {
930
+ throw new Error("Input array must contain at least one decision.");
931
+ }
932
+ return parsed.map((item, index) => validateCaptureItem(item, index));
933
+ }
934
+ function validateCaptureItem(item, index) {
935
+ if (typeof item !== "object" || item === null || Array.isArray(item)) {
936
+ throw new Error(`decision[${index}] must be a JSON object.`);
937
+ }
938
+ const obj = item;
939
+ for (const key of Object.keys(obj)) {
940
+ if (!CAPTURE_ALLOWED_KEYS.has(key)) {
941
+ throw new Error(
942
+ `decision[${index}]: unknown field '${key}'. Allowed: title, rationale, rejected_reason, alternatives, linked_events, linked_files.`
943
+ );
944
+ }
945
+ }
946
+ if (typeof obj.title !== "string" || isBlank(obj.title)) {
947
+ throw new Error(`decision[${index}].title must be a non-empty string.`);
948
+ }
949
+ const out = { title: obj.title };
950
+ if (obj.rationale !== void 0) {
951
+ out.rationale = requireNonEmptyString(obj.rationale, index, "rationale");
952
+ }
953
+ if (obj.rejected_reason !== void 0) {
954
+ out.rejected_reason = requireNonEmptyString(obj.rejected_reason, index, "rejected_reason");
955
+ }
956
+ if (obj.alternatives !== void 0) {
957
+ out.alternatives = validateStringArray(obj.alternatives, index, "alternatives", (value, i) => {
958
+ if (isBlank(value)) {
959
+ throw new Error(`decision[${index}].alternatives[${i}] must not be empty.`);
960
+ }
961
+ });
962
+ }
963
+ if (obj.linked_events !== void 0) {
964
+ out.linked_events = validateStringArray(
965
+ obj.linked_events,
966
+ index,
967
+ "linked_events",
968
+ (value, i) => {
969
+ if (!isValidEventId(value)) {
970
+ throw new Error(
971
+ `decision[${index}].linked_events[${i}] must match evt_<ULID>, got '${value}'.`
972
+ );
973
+ }
974
+ }
975
+ );
976
+ }
977
+ if (obj.linked_files !== void 0) {
978
+ out.linked_files = validateStringArray(obj.linked_files, index, "linked_files", (value, i) => {
979
+ if (isBlank(value)) {
980
+ throw new Error(`decision[${index}].linked_files[${i}] must not be empty.`);
981
+ }
982
+ if (value.length > 4096) {
983
+ throw new Error(`decision[${index}].linked_files[${i}] exceeds 4096 chars.`);
984
+ }
985
+ });
986
+ }
987
+ return out;
988
+ }
989
+ function requireNonEmptyString(value, index, field) {
990
+ if (typeof value !== "string" || isBlank(value)) {
991
+ throw new Error(`decision[${index}].${field} must be a non-empty string.`);
992
+ }
993
+ return value;
994
+ }
995
+ function isBlank(value) {
996
+ return value.trim().length === 0;
997
+ }
998
+ function validateStringArray(value, index, field, checkEach) {
999
+ if (!Array.isArray(value)) {
1000
+ throw new Error(`decision[${index}].${field} must be an array of strings.`);
1001
+ }
1002
+ return value.map((entry, i) => {
1003
+ if (typeof entry !== "string") {
1004
+ throw new Error(`decision[${index}].${field}[${i}] must be a string.`);
1005
+ }
1006
+ checkEach(entry, i);
1007
+ return entry;
1008
+ });
1009
+ }
1010
+ function toRichFields(decision) {
1011
+ const out = {};
1012
+ if (decision.rationale !== void 0) out.rationale = decision.rationale;
1013
+ if (decision.rejected_reason !== void 0) out.rejected_reason = decision.rejected_reason;
1014
+ if (decision.alternatives !== void 0) out.alternatives = [...decision.alternatives];
1015
+ if (decision.linked_events !== void 0) out.linked_events = [...decision.linked_events];
1016
+ if (decision.linked_files !== void 0) out.linked_files = [...decision.linked_files];
1017
+ return out;
1018
+ }
1019
+ function buildCaptureLabel(count) {
1020
+ return `Ad-hoc capture: ${count} decision${count === 1 ? "" : "s"}`;
1021
+ }
1022
+ function captureItemToPayload(item) {
1023
+ const payload = {
1024
+ decision_id: item.decisionId,
1025
+ event_id: item.eventId,
1026
+ title: item.input.title
1027
+ };
1028
+ if (item.input.rationale !== void 0) payload.rationale = item.input.rationale;
1029
+ if (item.input.alternatives !== void 0) payload.alternatives = item.input.alternatives;
1030
+ if (item.input.rejected_reason !== void 0)
1031
+ payload.rejected_reason = item.input.rejected_reason;
1032
+ if (item.input.linked_events !== void 0) payload.linked_events = item.input.linked_events;
1033
+ if (item.input.linked_files !== void 0) payload.linked_files = item.input.linked_files;
1034
+ return payload;
1035
+ }
1036
+ function printCapturePreview(options, decisions) {
1037
+ if (options.json === true) {
1038
+ console.log(JSON.stringify({ dry_run: true, count: decisions.length, decisions }));
1039
+ return;
1040
+ }
1041
+ console.log(
1042
+ `Would capture ${decisions.length} decision${decisions.length === 1 ? "" : "s"} (dry run; nothing written):`
1043
+ );
1044
+ for (const decision of decisions) {
1045
+ console.log(`- ${decision.title}`);
1046
+ }
1047
+ }
1048
+ function printCaptureResult(options, result) {
1049
+ const sid = shortSessionId(result.sessionId);
1050
+ if (options.json === true) {
1051
+ console.log(
1052
+ JSON.stringify({
1053
+ mode: "ad-hoc",
1054
+ session_id: result.sessionId,
1055
+ session_status: "completed",
1056
+ count: result.items.length,
1057
+ decisions: result.items.map(captureItemToPayload)
1058
+ })
1059
+ );
1060
+ return;
1061
+ }
1062
+ console.log(
1063
+ `Captured ${result.items.length} decision${result.items.length === 1 ? "" : "s"} in ad-hoc session ${sid}:`
1064
+ );
1065
+ for (const item of result.items) {
1066
+ console.log(`- ${item.decisionId}: ${item.input.title}`);
1067
+ }
1068
+ }
764
1069
  function pickRichFields(options) {
765
1070
  const out = {};
766
1071
  if (options.rationale !== void 0) out.rationale = options.rationale;
@@ -798,38 +1103,40 @@ function buildAdHocLabel(title) {
798
1103
  return `Ad-hoc decision: ${truncated}`;
799
1104
  }
800
1105
  function parseTitle(raw) {
801
- if (raw.length === 0) {
1106
+ if (isBlank(raw)) {
802
1107
  throw new InvalidArgumentError("Title must not be empty");
803
1108
  }
804
1109
  return raw;
805
1110
  }
806
1111
  function parseRationale(raw) {
807
- if (raw.length === 0) {
1112
+ if (isBlank(raw)) {
808
1113
  throw new InvalidArgumentError("Rationale must not be empty");
809
1114
  }
810
1115
  return raw;
811
1116
  }
812
1117
  function parseRejectedReason(raw) {
813
- if (raw.length === 0) {
1118
+ if (isBlank(raw)) {
814
1119
  throw new InvalidArgumentError("Rejected reason must not be empty");
815
1120
  }
816
1121
  return raw;
817
1122
  }
818
1123
  function collectAlternative(value, prev) {
819
- if (value.length === 0) {
1124
+ if (isBlank(value)) {
820
1125
  throw new InvalidArgumentError("Alternative must not be empty");
821
1126
  }
822
1127
  return prev.concat(value);
823
1128
  }
824
- var EVENT_ID_RE = /^evt_[A-Z0-9]+$/;
1129
+ function isValidEventId(value) {
1130
+ return isValidPrefixedId(value) && value.startsWith("evt_");
1131
+ }
825
1132
  function collectLinkedEvent(value, prev) {
826
- if (!EVENT_ID_RE.test(value)) {
1133
+ if (!isValidEventId(value)) {
827
1134
  throw new InvalidArgumentError(`Linked event id must match evt_<ULID>, got '${value}'`);
828
1135
  }
829
1136
  return prev.concat(value);
830
1137
  }
831
1138
  function collectLinkedFile(value, prev) {
832
- if (value.length === 0) {
1139
+ if (isBlank(value)) {
833
1140
  throw new InvalidArgumentError("Linked file path must not be empty");
834
1141
  }
835
1142
  if (value.length > 4096) {
@@ -960,7 +1267,7 @@ async function assertWorkspaceInitialized3(basouRoot) {
960
1267
 
961
1268
  // src/commands/exec.ts
962
1269
  import { mkdir } from "fs/promises";
963
- import { homedir } from "os";
1270
+ import { homedir as homedir2 } from "os";
964
1271
  import { join as join2 } from "path";
965
1272
  import {
966
1273
  acquireLock as acquireLock3,
@@ -1212,7 +1519,7 @@ function buildInitialSession(input) {
1212
1519
  source: { kind: "terminal", version: "0.1.0" },
1213
1520
  started_at: input.startedAt,
1214
1521
  status: "initialized",
1215
- working_directory: sanitizeWorkingDirectory(input.cwd, { homedir: homedir() }),
1522
+ working_directory: sanitizeWorkingDirectory(input.cwd, { homedir: homedir2() }),
1216
1523
  invocation: {
1217
1524
  command: input.command,
1218
1525
  args: [...input.args],
@@ -1355,9 +1662,9 @@ async function assertWorkspaceInitialized4(basouRoot) {
1355
1662
 
1356
1663
  // src/commands/import.ts
1357
1664
  import { createReadStream } from "fs";
1358
- import { readdir, readFile, rm, stat } from "fs/promises";
1359
- import { homedir as homedir2 } from "os";
1360
- import { basename, join as join3, resolve } from "path";
1665
+ import { readdir, readFile as readFile2, rm, stat } from "fs/promises";
1666
+ import { homedir as homedir3 } from "os";
1667
+ import { basename, join as join3, resolve as resolve2 } from "path";
1361
1668
  import { createInterface } from "readline";
1362
1669
  import {
1363
1670
  assertBasouRootSafe as assertBasouRootSafe6,
@@ -1425,10 +1732,10 @@ function resolveSourceRoots(args) {
1425
1732
  const { projectFlags, manifest, repoRoot, cwd } = args;
1426
1733
  let resolved;
1427
1734
  if (projectFlags.length > 0) {
1428
- resolved = projectFlags.map((p) => resolve(cwd, p));
1735
+ resolved = projectFlags.map((p) => resolve2(cwd, p));
1429
1736
  } else {
1430
1737
  const roots = manifest.import?.source_roots;
1431
- resolved = roots !== void 0 && roots.length > 0 ? roots.map((r) => resolve(repoRoot, r)) : [repoRoot];
1738
+ resolved = roots !== void 0 && roots.length > 0 ? roots.map((r) => resolve2(repoRoot, r)) : [repoRoot];
1432
1739
  }
1433
1740
  return [...new Set(resolved)];
1434
1741
  }
@@ -1441,7 +1748,7 @@ async function doRunImportClaudeCode(options, ctx) {
1441
1748
  repoRoot: repositoryRoot,
1442
1749
  cwd: ctx.cwd ?? process.cwd()
1443
1750
  });
1444
- const projectsRoot = ctx.claudeProjectsDir ?? join3(homedir2(), ".claude", "projects");
1751
+ const projectsRoot = ctx.claudeProjectsDir ?? join3(homedir3(), ".claude", "projects");
1445
1752
  const files = await selectTranscriptFiles(projectsRoot, projectPaths, options);
1446
1753
  const projectSet = new Set(projectPaths);
1447
1754
  const candidates = files.map((file) => {
@@ -1472,7 +1779,7 @@ async function doRunImportCodex(options, ctx) {
1472
1779
  repoRoot: repositoryRoot,
1473
1780
  cwd: ctx.cwd ?? process.cwd()
1474
1781
  });
1475
- const sessionsRoot = ctx.codexSessionsDir ?? join3(homedir2(), ".codex", "sessions");
1782
+ const sessionsRoot = ctx.codexSessionsDir ?? join3(homedir3(), ".codex", "sessions");
1476
1783
  const rollouts = await discoverCodexRollouts(sessionsRoot, projectPaths, options);
1477
1784
  const candidates = rollouts.map(({ file, externalId }) => ({
1478
1785
  externalId,
@@ -1799,7 +2106,7 @@ async function readFirstLine(file) {
1799
2106
  async function readJsonlRecords(file) {
1800
2107
  let buffer;
1801
2108
  try {
1802
- buffer = await readFile(file);
2109
+ buffer = await readFile2(file);
1803
2110
  } catch (error) {
1804
2111
  if (findErrorCode5(error, "ENOENT")) {
1805
2112
  throw new Error("Source log not found", { cause: error });
@@ -1929,7 +2236,7 @@ async function assertWorkspaceInitialized5(basouRoot) {
1929
2236
  }
1930
2237
 
1931
2238
  // src/commands/init.ts
1932
- import { basename as basename2, relative, resolve as resolve2 } from "path";
2239
+ import { basename as basename2, relative, resolve as resolve3 } from "path";
1933
2240
  import {
1934
2241
  appendBasouGitignore,
1935
2242
  createManifest,
@@ -1976,7 +2283,7 @@ async function doRunInit(options, ctx) {
1976
2283
  repositoryUrl = await tryRemoteUrl(repositoryRoot);
1977
2284
  }
1978
2285
  const sourceRoots = (options.sourceRoot ?? []).map((p) => {
1979
- const rel = relative(repositoryRoot, resolve2(cwd, p));
2286
+ const rel = relative(repositoryRoot, resolve3(cwd, p));
1980
2287
  return rel === "" ? "." : rel;
1981
2288
  });
1982
2289
  const paths = await ensureBasouDirectory(repositoryRoot);
@@ -2030,26 +2337,6 @@ import {
2030
2337
  resolveSessionId as resolveSessionId2
2031
2338
  } from "@basou/core";
2032
2339
  import { InvalidArgumentError as InvalidArgumentError2 } from "commander";
2033
-
2034
- // src/lib/repo-root.ts
2035
- import { resolveBasouRepositoryRoot } from "@basou/core";
2036
- async function resolveBasouRootForCommand(cwd, commandName) {
2037
- try {
2038
- return await resolveBasouRepositoryRoot(cwd, {
2039
- onRedirect: ({ via, root }) => console.error(`Resolved workspace view to ${root} (via ${via}).`)
2040
- });
2041
- } catch (error) {
2042
- if (error instanceof Error && error.message === "Not a git repository") {
2043
- throw new Error(
2044
- `Not a git repository. Run 'git init' first, then re-run 'basou ${commandName}'.`,
2045
- { cause: error }
2046
- );
2047
- }
2048
- throw error;
2049
- }
2050
- }
2051
-
2052
- // src/commands/note.ts
2053
2340
  var LABEL_BODY_MAX = 80;
2054
2341
  var LABEL_TRUNCATE_HEAD2 = LABEL_BODY_MAX - 3;
2055
2342
  function registerNoteCommand(program2) {
@@ -2443,7 +2730,7 @@ import {
2443
2730
  unlinkSync,
2444
2731
  writeFileSync
2445
2732
  } from "fs";
2446
- import { basename as basename3, dirname, isAbsolute, join as join4, relative as relative2, resolve as resolve3 } from "path";
2733
+ import { basename as basename3, dirname, isAbsolute, join as join4, relative as relative2, resolve as resolve4 } from "path";
2447
2734
  import {
2448
2735
  basouPaths as basouPaths9,
2449
2736
  GENERATED_END,
@@ -2695,7 +2982,7 @@ async function runProjectAdopt(options, ctx = {}) {
2695
2982
  }
2696
2983
  }
2697
2984
  function classifySourceRoot(repositoryRoot, declaredPath) {
2698
- const absolute = resolve3(repositoryRoot, declaredPath);
2985
+ const absolute = resolve4(repositoryRoot, declaredPath);
2699
2986
  let real;
2700
2987
  try {
2701
2988
  real = realpathSync(absolute);
@@ -2797,7 +3084,7 @@ async function gatherRepoWiring(repositoryRoot, entry) {
2797
3084
  };
2798
3085
  let real;
2799
3086
  try {
2800
- real = realpathSync(resolve3(repositoryRoot, entry.path));
3087
+ real = realpathSync(resolve4(repositoryRoot, entry.path));
2801
3088
  } catch {
2802
3089
  return { ...base, reachable: false, instructionFiles: [] };
2803
3090
  }
@@ -2902,7 +3189,7 @@ function gatherRepoGitignore(repositoryRoot, entry) {
2902
3189
  };
2903
3190
  let real;
2904
3191
  try {
2905
- real = realpathSync(resolve3(repositoryRoot, entry.path));
3192
+ real = realpathSync(resolve4(repositoryRoot, entry.path));
2906
3193
  } catch {
2907
3194
  return { ...base, reachable: false, currentLines: [] };
2908
3195
  }
@@ -2923,7 +3210,7 @@ function readGitignoreLines(file) {
2923
3210
  }
2924
3211
  }
2925
3212
  function applyGitignorePlan(repositoryRoot, plan) {
2926
- const file = join4(realpathSync(resolve3(repositoryRoot, plan.path)), ".gitignore");
3213
+ const file = join4(realpathSync(resolve4(repositoryRoot, plan.path)), ".gitignore");
2927
3214
  let existing = "";
2928
3215
  try {
2929
3216
  existing = readFileSync(file, "utf8");
@@ -3035,7 +3322,7 @@ function gatherRepoSymlinks(repositoryRoot, anchorReal, entry) {
3035
3322
  const base = { path: entry.path };
3036
3323
  let real;
3037
3324
  try {
3038
- real = realpathSync(resolve3(repositoryRoot, entry.path));
3325
+ real = realpathSync(resolve4(repositoryRoot, entry.path));
3039
3326
  } catch {
3040
3327
  return { ...base, isAnchor: false, reachable: false, canonicalPresent: false, files: [] };
3041
3328
  }
@@ -3072,7 +3359,7 @@ function gatherRepoSymlinks(repositoryRoot, anchorReal, entry) {
3072
3359
  function applySymlinkPlan(repositoryRoot, plan) {
3073
3360
  let real;
3074
3361
  try {
3075
- real = realpathSync(resolve3(repositoryRoot, plan.path));
3362
+ real = realpathSync(resolve4(repositoryRoot, plan.path));
3076
3363
  } catch (error) {
3077
3364
  const message = failureReason(error);
3078
3365
  return { created: [], failed: plan.toCreate.map((c) => ({ file: c.name, message })) };
@@ -3217,7 +3504,7 @@ async function runProjectWorkspace(options, ctx = {}) {
3217
3504
  }
3218
3505
  }
3219
3506
  function resolveViewDir(repositoryRoot, viewPath) {
3220
- const abs = resolve3(repositoryRoot, viewPath);
3507
+ const abs = resolve4(repositoryRoot, viewPath);
3221
3508
  try {
3222
3509
  return realpathSync(abs);
3223
3510
  } catch {
@@ -3231,7 +3518,7 @@ function resolveViewDir(repositoryRoot, viewPath) {
3231
3518
  function gatherViewRepo(repositoryRoot, viewDir, entry) {
3232
3519
  let repoReal;
3233
3520
  try {
3234
- repoReal = realpathSync(resolve3(repositoryRoot, entry.path));
3521
+ repoReal = realpathSync(resolve4(repositoryRoot, entry.path));
3235
3522
  } catch {
3236
3523
  return { path: entry.path, reachable: false };
3237
3524
  }
@@ -3283,7 +3570,7 @@ function classifyViewLink(viewDir, name, rosterRealpaths) {
3283
3570
  } catch {
3284
3571
  return null;
3285
3572
  }
3286
- const resolved = isAbsolute(target) ? target : resolve3(viewDir, target);
3573
+ const resolved = isAbsolute(target) ? target : resolve4(viewDir, target);
3287
3574
  try {
3288
3575
  if (rosterRealpaths.has(realpathSync(resolved))) return null;
3289
3576
  } catch {
@@ -3369,11 +3656,11 @@ async function doRunProjectWorkspace(options, ctx) {
3369
3656
  } else {
3370
3657
  const viewDir = resolveViewDir(repositoryRoot, viewPath);
3371
3658
  const facts = roster.map((entry) => gatherViewRepo(repositoryRoot, viewDir, entry));
3372
- const rosterNames = roster.map((entry) => basename3(resolve3(repositoryRoot, entry.path)));
3659
+ const rosterNames = roster.map((entry) => basename3(resolve4(repositoryRoot, entry.path)));
3373
3660
  const rosterRealpaths = /* @__PURE__ */ new Set();
3374
3661
  for (const entry of roster) {
3375
3662
  try {
3376
- rosterRealpaths.add(realpathSync(resolve3(repositoryRoot, entry.path)));
3663
+ rosterRealpaths.add(realpathSync(resolve4(repositoryRoot, entry.path)));
3377
3664
  } catch {
3378
3665
  }
3379
3666
  }
@@ -3548,7 +3835,7 @@ async function gatherRepoPreset(repositoryRoot, anchorReal, entry) {
3548
3835
  };
3549
3836
  let real;
3550
3837
  try {
3551
- real = realpathSync(resolve3(repositoryRoot, entry.path));
3838
+ real = realpathSync(resolve4(repositoryRoot, entry.path));
3552
3839
  } catch {
3553
3840
  return { ...declared, isAnchor: false, reachable: false, canonicalPresent: false };
3554
3841
  }
@@ -3777,7 +4064,7 @@ function gatherArchiveTeardown(repositoryRoot, manifest, target) {
3777
4064
  };
3778
4065
  let real;
3779
4066
  try {
3780
- real = realpathSync(resolve3(repositoryRoot, target));
4067
+ real = realpathSync(resolve4(repositoryRoot, target));
3781
4068
  } catch {
3782
4069
  return empty;
3783
4070
  }
@@ -3845,7 +4132,7 @@ async function doRunProjectArchive(target, options, ctx) {
3845
4132
  const roster = manifest.repos ?? [];
3846
4133
  let targetIsAnchor = false;
3847
4134
  try {
3848
- targetIsAnchor = realpathSync(resolve3(repositoryRoot, target)) === realpathSync(repositoryRoot);
4135
+ targetIsAnchor = realpathSync(resolve4(repositoryRoot, target)) === realpathSync(repositoryRoot);
3849
4136
  } catch {
3850
4137
  targetIsAnchor = false;
3851
4138
  }
@@ -3996,7 +4283,7 @@ async function doRunProjectRename(oldPath, newPath, options, ctx) {
3996
4283
  const roster = manifest.repos ?? [];
3997
4284
  let oldIsAnchor = false;
3998
4285
  try {
3999
- oldIsAnchor = realpathSync(resolve3(repositoryRoot, oldPath)) === realpathSync(repositoryRoot);
4286
+ oldIsAnchor = realpathSync(resolve4(repositoryRoot, oldPath)) === realpathSync(repositoryRoot);
4000
4287
  } catch {
4001
4288
  oldIsAnchor = false;
4002
4289
  }
@@ -4111,13 +4398,13 @@ import { assertBasouRootSafe as assertBasouRootSafe9, basouPaths as basouPaths10
4111
4398
  import { InvalidArgumentError as InvalidArgumentError3 } from "commander";
4112
4399
 
4113
4400
  // src/lib/portfolio-config.ts
4114
- import { homedir as homedir3 } from "os";
4115
- import { isAbsolute as isAbsolute2, join as join5, resolve as resolve4 } from "path";
4401
+ import { homedir as homedir4 } from "os";
4402
+ import { isAbsolute as isAbsolute2, join as join5, resolve as resolve5 } from "path";
4116
4403
  import { readYamlFile as readYamlFile3 } from "@basou/core";
4117
- var DEFAULT_PORTFOLIO_CONFIG_PATH = join5(homedir3(), ".basou", "portfolio.yaml");
4404
+ var DEFAULT_PORTFOLIO_CONFIG_PATH = join5(homedir4(), ".basou", "portfolio.yaml");
4118
4405
  function expandTilde(p) {
4119
- if (p === "~") return homedir3();
4120
- if (p.startsWith("~/")) return join5(homedir3(), p.slice(2));
4406
+ if (p === "~") return homedir4();
4407
+ if (p.startsWith("~/")) return join5(homedir4(), p.slice(2));
4121
4408
  return p;
4122
4409
  }
4123
4410
  function isRecord(value) {
@@ -4156,7 +4443,7 @@ async function loadPortfolioConfig(configPath = DEFAULT_PORTFOLIO_CONFIG_PATH) {
4156
4443
  "Portfolio workspace paths must be absolute (or start with '~'); use --workspace for relative ad-hoc paths."
4157
4444
  );
4158
4445
  }
4159
- const abs = resolve4(expanded);
4446
+ const abs = resolve5(expanded);
4160
4447
  if (seen.has(abs)) continue;
4161
4448
  seen.add(abs);
4162
4449
  result.push(entry.label !== void 0 ? { path: abs, label: entry.label } : { path: abs });
@@ -4169,7 +4456,7 @@ async function loadPortfolioConfig(configPath = DEFAULT_PORTFOLIO_CONFIG_PATH) {
4169
4456
 
4170
4457
  // src/commands/refresh-watch.ts
4171
4458
  import { readdir as readdir2, stat as stat2 } from "fs/promises";
4172
- import { homedir as homedir4 } from "os";
4459
+ import { homedir as homedir5 } from "os";
4173
4460
  import { join as join6 } from "path";
4174
4461
  import { findErrorCode as findErrorCode8 } from "@basou/core";
4175
4462
  var DEFAULT_WATCH_INTERVAL_SEC = 30;
@@ -4177,8 +4464,8 @@ var MIN_WATCH_INTERVAL_SEC = 5;
4177
4464
  var MAX_WATCH_INTERVAL_SEC = 86400;
4178
4465
  function watchedRoots(ctx) {
4179
4466
  return [
4180
- ctx.codexSessionsDir ?? join6(homedir4(), ".codex", "sessions"),
4181
- ctx.claudeProjectsDir ?? join6(homedir4(), ".claude", "projects")
4467
+ ctx.codexSessionsDir ?? join6(homedir5(), ".codex", "sessions"),
4468
+ ctx.claudeProjectsDir ?? join6(homedir5(), ".claude", "projects")
4182
4469
  ];
4183
4470
  }
4184
4471
  async function scanSourceLogs(roots) {
@@ -4299,19 +4586,19 @@ function parseInterval(value) {
4299
4586
  return seconds;
4300
4587
  }
4301
4588
  function abortableSleep(ms, signal) {
4302
- return new Promise((resolve8) => {
4589
+ return new Promise((resolve9) => {
4303
4590
  if (signal.aborted) {
4304
- resolve8();
4591
+ resolve9();
4305
4592
  return;
4306
4593
  }
4307
4594
  let timer;
4308
4595
  const onAbort = () => {
4309
4596
  clearTimeout(timer);
4310
- resolve8();
4597
+ resolve9();
4311
4598
  };
4312
4599
  timer = setTimeout(() => {
4313
4600
  signal.removeEventListener("abort", onAbort);
4314
- resolve8();
4601
+ resolve9();
4315
4602
  }, ms);
4316
4603
  signal.addEventListener("abort", onAbort, { once: true });
4317
4604
  });
@@ -4482,7 +4769,7 @@ function printRefreshSummary(result) {
4482
4769
  if (result.decisions.decisionCount === 0) {
4483
4770
  const hasSessions = result.handoff.status === "generated" && result.handoff.sessionCount > 0;
4484
4771
  console.log(
4485
- hasSessions ? "decisions: 0 (none auto-recorded from these sessions; record any made with 'basou decision record')" : "decisions: 0"
4772
+ hasSessions ? "decisions: 0 (none auto-recorded from these sessions; capture any made with 'basou decision capture')" : "decisions: 0"
4486
4773
  );
4487
4774
  } else {
4488
4775
  console.log(`decisions: regenerated (${result.decisions.decisionCount})`);
@@ -4510,7 +4797,7 @@ async function assertWorkspaceInitialized8(basouRoot) {
4510
4797
  }
4511
4798
 
4512
4799
  // src/commands/report.ts
4513
- import { isAbsolute as isAbsolute3, resolve as resolve5 } from "path";
4800
+ import { isAbsolute as isAbsolute3, resolve as resolve6 } from "path";
4514
4801
  import {
4515
4802
  assertBasouRootSafe as assertBasouRootSafe10,
4516
4803
  basouPaths as basouPaths11,
@@ -4550,7 +4837,7 @@ async function doRunReportGenerate(options, ctx) {
4550
4837
  onTaskSkip: (taskId, reason) => printTaskSkip(taskId, reason)
4551
4838
  });
4552
4839
  if (options.out !== void 0) {
4553
- const outPath = isAbsolute3(options.out) ? options.out : resolve5(cwd, options.out);
4840
+ const outPath = isAbsolute3(options.out) ? options.out : resolve6(cwd, options.out);
4554
4841
  await writeMarkdownFile6(outPath, result.body);
4555
4842
  const { sessions, decisions, tasks } = result.data;
4556
4843
  console.error(
@@ -4713,7 +5000,7 @@ function renderReviewGaps(summary) {
4713
5000
 
4714
5001
  // src/commands/run.ts
4715
5002
  import { mkdir as mkdir2 } from "fs/promises";
4716
- import { homedir as homedir5 } from "os";
5003
+ import { homedir as homedir6 } from "os";
4717
5004
  import { join as join7 } from "path";
4718
5005
  import {
4719
5006
  acquireLock as acquireLock5,
@@ -4899,7 +5186,7 @@ async function runClaudeCode(args, options, ctx = {}) {
4899
5186
  const rawRelated = computeRelatedFiles(preSnapshot, postSnapshot, diff);
4900
5187
  const relatedFiles = sanitizeRelatedFiles(rawRelated, {
4901
5188
  workingDirectory: repoRoot,
4902
- homedir: homedir5()
5189
+ homedir: homedir6()
4903
5190
  }).sanitized;
4904
5191
  const finalStatus = decideFinalStatus2(result, signalReceived);
4905
5192
  await appendEvent(sessionDir, {
@@ -5043,7 +5330,7 @@ function buildInitialSession2(input) {
5043
5330
  source: { ...claudeCodeAdapterMetadata },
5044
5331
  started_at: input.startedAt,
5045
5332
  status: "initialized",
5046
- working_directory: sanitizeWorkingDirectory2(input.cwd, { homedir: homedir5() }),
5333
+ working_directory: sanitizeWorkingDirectory2(input.cwd, { homedir: homedir6() }),
5047
5334
  invocation: {
5048
5335
  command: input.command,
5049
5336
  args: [...input.args],
@@ -5115,7 +5402,7 @@ async function resolveRepositoryRootForRun(cwd) {
5115
5402
  }
5116
5403
 
5117
5404
  // src/commands/session.ts
5118
- import { readFile as readFile2 } from "fs/promises";
5405
+ import { readFile as readFile3 } from "fs/promises";
5119
5406
  import { basename as basename4, isAbsolute as isAbsolute4, join as join8, relative as relative3 } from "path";
5120
5407
  import {
5121
5408
  acquireLock as acquireLock6,
@@ -5555,7 +5842,7 @@ async function doRunSessionImport(options, ctx) {
5555
5842
  }
5556
5843
  async function readInputFile(path) {
5557
5844
  try {
5558
- return await readFile2(path, "utf8");
5845
+ return await readFile3(path, "utf8");
5559
5846
  } catch (error) {
5560
5847
  if (findErrorCode11(error, "ENOENT")) {
5561
5848
  throw new Error("Import source not found", { cause: error });
@@ -5672,7 +5959,7 @@ async function doRunSessionNote(sessionIdInput, options, ctx) {
5672
5959
  }
5673
5960
  async function readNoteFile(path) {
5674
5961
  try {
5675
- return await readFile2(path, "utf8");
5962
+ return await readFile3(path, "utf8");
5676
5963
  } catch (error) {
5677
5964
  if (findErrorCode11(error, "ENOENT")) {
5678
5965
  throw new Error("Note source not found", { cause: error });
@@ -5979,7 +6266,7 @@ async function resolveRepositoryRootForStatus(cwd) {
5979
6266
  }
5980
6267
 
5981
6268
  // src/commands/task.ts
5982
- import { readFile as readFile3 } from "fs/promises";
6269
+ import { readFile as readFile4 } from "fs/promises";
5983
6270
  import { join as join9 } from "path";
5984
6271
  import {
5985
6272
  archiveTask,
@@ -6995,7 +7282,7 @@ function parsePositiveInt2(raw) {
6995
7282
  }
6996
7283
  async function readDescriptionFile(path) {
6997
7284
  try {
6998
- return await readFile3(path, "utf8");
7285
+ return await readFile4(path, "utf8");
6999
7286
  } catch (error) {
7000
7287
  if (findErrorCode14(error, "ENOENT")) {
7001
7288
  throw new Error("Description source not found", { cause: error });
@@ -7210,7 +7497,7 @@ async function assertWorkspaceInitialized13(basouRoot) {
7210
7497
  // src/commands/view.ts
7211
7498
  import { spawn } from "child_process";
7212
7499
  import { createHash } from "crypto";
7213
- import { basename as basename5, resolve as resolve7 } from "path";
7500
+ import { basename as basename5, resolve as resolve8 } from "path";
7214
7501
  import {
7215
7502
  assertBasouRootSafe as assertBasouRootSafe17,
7216
7503
  basouPaths as basouPaths19,
@@ -7223,7 +7510,7 @@ import { InvalidArgumentError as InvalidArgumentError7 } from "commander";
7223
7510
  // src/lib/portfolio-safety.ts
7224
7511
  import { execFile } from "child_process";
7225
7512
  import { lstat, realpath } from "fs/promises";
7226
- import { isAbsolute as isAbsolute5, join as join10, relative as relative4, resolve as resolve6 } from "path";
7513
+ import { isAbsolute as isAbsolute5, join as join10, relative as relative4, resolve as resolve7 } from "path";
7227
7514
  import { promisify } from "util";
7228
7515
  import { readManifest as readManifest10 } from "@basou/core";
7229
7516
  var execFileAsync = promisify(execFile);
@@ -7234,7 +7521,7 @@ async function canonical(p) {
7234
7521
  try {
7235
7522
  return await realpath(p);
7236
7523
  } catch {
7237
- return resolve6(p);
7524
+ return resolve7(p);
7238
7525
  }
7239
7526
  }
7240
7527
  function isInside(child, parent) {
@@ -7296,7 +7583,7 @@ async function checkPortfolioSafety(workspaces) {
7296
7583
  }
7297
7584
  const monitored = /* @__PURE__ */ new Map();
7298
7585
  for (const root of sourceRoots) {
7299
- const display = resolve6(ws.repoRoot, root);
7586
+ const display = resolve7(ws.repoRoot, root);
7300
7587
  const real = await canonical(display);
7301
7588
  if (real !== wsReal) monitored.set(real, display);
7302
7589
  }
@@ -8005,7 +8292,7 @@ function startViewServer(opts) {
8005
8292
  };
8006
8293
  let boundPort = port;
8007
8294
  const getPort = () => boundPort;
8008
- return new Promise((resolve8, reject) => {
8295
+ return new Promise((resolve9, reject) => {
8009
8296
  const server = createServer((req, res) => {
8010
8297
  handleRequest(req, res, deps, getPort, runExclusive).catch((error) => {
8011
8298
  sendError(res, error instanceof HttpError ? error.status : 500, pathlessMessage(error));
@@ -8016,7 +8303,7 @@ function startViewServer(opts) {
8016
8303
  const address = server.address();
8017
8304
  boundPort = isAddressInfo(address) ? address.port : port;
8018
8305
  server.off("error", reject);
8019
- resolve8({
8306
+ resolve9({
8020
8307
  url: `http://${host}:${boundPort}`,
8021
8308
  port: boundPort,
8022
8309
  close: () => closeServer(server)
@@ -8028,8 +8315,8 @@ function isAddressInfo(value) {
8028
8315
  return value !== null && typeof value === "object";
8029
8316
  }
8030
8317
  function closeServer(server) {
8031
- return new Promise((resolve8) => {
8032
- server.close(() => resolve8());
8318
+ return new Promise((resolve9) => {
8319
+ server.close(() => resolve9());
8033
8320
  server.closeAllConnections();
8034
8321
  });
8035
8322
  }
@@ -8523,12 +8810,12 @@ async function buildSingleDeps(ctx, cwd) {
8523
8810
  return { workspaces: [entry], mode: "single", nowProvider: nowProviderOf(ctx) };
8524
8811
  }
8525
8812
  async function buildPortfolioDeps(workspaceFlags, ctx, cwd) {
8526
- const specs = workspaceFlags.length > 0 ? workspaceFlags.map((p) => ({ path: resolve7(cwd, p) })) : await loadPortfolioConfig(ctx.portfolioConfigPath);
8813
+ const specs = workspaceFlags.length > 0 ? workspaceFlags.map((p) => ({ path: resolve8(cwd, p) })) : await loadPortfolioConfig(ctx.portfolioConfigPath);
8527
8814
  const entries = [];
8528
8815
  const seenPath = /* @__PURE__ */ new Set();
8529
8816
  const seenKey = /* @__PURE__ */ new Set();
8530
8817
  for (const spec of specs) {
8531
- const repoRoot = resolve7(spec.path);
8818
+ const repoRoot = resolve8(spec.path);
8532
8819
  if (seenPath.has(repoRoot)) continue;
8533
8820
  seenPath.add(repoRoot);
8534
8821
  const entry = await buildWorkspaceEntry(repoRoot, ctx, spec.label);
@@ -8600,7 +8887,7 @@ function openInBrowser(url, override) {
8600
8887
  }
8601
8888
  }
8602
8889
  function waitForShutdown(signal) {
8603
- return new Promise((resolve8) => {
8890
+ return new Promise((resolve9) => {
8604
8891
  const cleanup = () => {
8605
8892
  process.off("SIGINT", onSignal);
8606
8893
  process.off("SIGTERM", onSignal);
@@ -8608,18 +8895,18 @@ function waitForShutdown(signal) {
8608
8895
  };
8609
8896
  const onSignal = () => {
8610
8897
  cleanup();
8611
- resolve8();
8898
+ resolve9();
8612
8899
  };
8613
8900
  const onAbort = () => {
8614
8901
  cleanup();
8615
- resolve8();
8902
+ resolve9();
8616
8903
  };
8617
8904
  process.on("SIGINT", onSignal);
8618
8905
  process.on("SIGTERM", onSignal);
8619
8906
  if (signal !== void 0) {
8620
8907
  if (signal.aborted) {
8621
8908
  cleanup();
8622
- resolve8();
8909
+ resolve9();
8623
8910
  return;
8624
8911
  }
8625
8912
  signal.addEventListener("abort", onAbort);