@basou/core 0.11.0 → 0.12.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
@@ -1720,6 +1720,8 @@ function splitLinesBytes2(buf) {
1720
1720
  }
1721
1721
 
1722
1722
  // src/git/snapshot.ts
1723
+ import { readdir as readdir3, stat as stat2 } from "fs/promises";
1724
+ import { join as join9 } from "path";
1723
1725
  import { simpleGit } from "simple-git";
1724
1726
 
1725
1727
  // src/storage/status.ts
@@ -1757,19 +1759,19 @@ var DIRECTORY_CHECKS = {
1757
1759
  tmp: (p) => p.tmp
1758
1760
  };
1759
1761
  async function assertBasouRootSafe(rootPath) {
1760
- let stat3;
1762
+ let stat4;
1761
1763
  try {
1762
- stat3 = await fsp.lstat(rootPath);
1764
+ stat4 = await fsp.lstat(rootPath);
1763
1765
  } catch (error) {
1764
1766
  if (hasErrorCode2(error) && error.code === "ENOENT") {
1765
1767
  throw new Error("Basou workspace not found", { cause: error });
1766
1768
  }
1767
1769
  throw new Error("Failed to inspect .basou root", { cause: error });
1768
1770
  }
1769
- if (stat3.isSymbolicLink()) {
1771
+ if (stat4.isSymbolicLink()) {
1770
1772
  throw new Error(".basou root is a symlink; refusing to operate");
1771
1773
  }
1772
- if (!stat3.isDirectory()) {
1774
+ if (!stat4.isDirectory()) {
1773
1775
  throw new Error(".basou root exists but is not a directory");
1774
1776
  }
1775
1777
  }
@@ -1849,6 +1851,14 @@ function isGitNotFound(error) {
1849
1851
  }
1850
1852
  return false;
1851
1853
  }
1854
+ function isNotAGitRepository(error) {
1855
+ let cur = error;
1856
+ for (let i = 0; i < 4 && cur instanceof Error; i++) {
1857
+ if (/not a git repository/i.test(cur.message)) return true;
1858
+ cur = cur.cause;
1859
+ }
1860
+ return false;
1861
+ }
1852
1862
  async function resolveRepositoryRoot(cwd) {
1853
1863
  const git = safeSimpleGit(cwd);
1854
1864
  try {
@@ -1864,9 +1874,54 @@ async function resolveRepositoryRoot(cwd) {
1864
1874
  if (error instanceof Error && error.message === "Not a git repository") {
1865
1875
  throw error;
1866
1876
  }
1867
- throw new Error("Not a git repository", { cause: error });
1877
+ if (isNotAGitRepository(error)) {
1878
+ throw new Error("Not a git repository", { cause: error });
1879
+ }
1880
+ throw new Error("Git command failed", { cause: error });
1868
1881
  }
1869
1882
  }
1883
+ async function resolveBasouRepositoryRoot(cwd, opts) {
1884
+ try {
1885
+ return await resolveRepositoryRoot(cwd);
1886
+ } catch (error) {
1887
+ if (!(error instanceof Error) || error.message !== "Not a git repository") throw error;
1888
+ const linked = await findLinkedBasouRepos(cwd);
1889
+ const only = linked[0];
1890
+ if (only !== void 0 && linked.length === 1) {
1891
+ opts?.onRedirect?.({ via: only.name, root: only.root });
1892
+ return only.root;
1893
+ }
1894
+ if (linked.length > 1) {
1895
+ const names = linked.map((l) => l.name).join(", ");
1896
+ throw new Error(
1897
+ `Ambiguous workspace view: ${linked.length} linked repos have a .basou store (${names}). cd into the one you want and re-run.`
1898
+ );
1899
+ }
1900
+ throw error;
1901
+ }
1902
+ }
1903
+ async function findLinkedBasouRepos(dir) {
1904
+ const entries = await readdir3(dir, { withFileTypes: true }).catch(() => null);
1905
+ if (entries === null) return [];
1906
+ const byRoot = /* @__PURE__ */ new Map();
1907
+ for (const entry of entries) {
1908
+ if (!entry.isSymbolicLink()) continue;
1909
+ let root;
1910
+ try {
1911
+ root = await resolveRepositoryRoot(join9(dir, entry.name));
1912
+ } catch {
1913
+ continue;
1914
+ }
1915
+ try {
1916
+ if (!(await stat2(join9(root, ".basou"))).isDirectory()) continue;
1917
+ } catch {
1918
+ continue;
1919
+ }
1920
+ const existing = byRoot.get(root);
1921
+ if (existing === void 0 || entry.name < existing) byRoot.set(root, entry.name);
1922
+ }
1923
+ return [...byRoot.entries()].map(([root, name]) => ({ name, root })).sort((a, b) => a.name.localeCompare(b.name));
1924
+ }
1870
1925
  async function tryRemoteUrl(repositoryRoot) {
1871
1926
  const git = safeSimpleGit(repositoryRoot);
1872
1927
  try {
@@ -2013,12 +2068,12 @@ function parseDiffNameStatus(raw) {
2013
2068
  }
2014
2069
 
2015
2070
  // src/handoff/handoff-renderer.ts
2016
- import { join as join12 } from "path";
2071
+ import { join as join13 } from "path";
2017
2072
 
2018
2073
  // src/storage/tasks.ts
2019
2074
  import { createHash as createHash2 } from "crypto";
2020
- import { mkdir as mkdir3, readdir as readdir3, readFile as readFile7, rename as rename2, stat as stat2, unlink as unlink3 } from "fs/promises";
2021
- import { join as join11 } from "path";
2075
+ import { mkdir as mkdir3, readdir as readdir4, readFile as readFile7, rename as rename2, stat as stat3, unlink as unlink3 } from "fs/promises";
2076
+ import { join as join12 } from "path";
2022
2077
  import { parse as parseYaml, stringify as stringifyYaml } from "yaml";
2023
2078
  import { z as z8 } from "zod";
2024
2079
 
@@ -2062,7 +2117,7 @@ var TaskSchema = z6.object({
2062
2117
  // src/storage/ad-hoc-session.ts
2063
2118
  import { mkdir as mkdir2, rm } from "fs/promises";
2064
2119
  import { homedir } from "os";
2065
- import { join as join9 } from "path";
2120
+ import { join as join10 } from "path";
2066
2121
 
2067
2122
  // src/lib/path-sanitizer.ts
2068
2123
  import { posix as path } from "path";
@@ -2142,8 +2197,8 @@ async function createAdHocSessionWithEvent(input) {
2142
2197
  taskId: input.taskId ?? null
2143
2198
  })
2144
2199
  );
2145
- const sessionDir = join9(input.paths.sessions, sessionId);
2146
- const sessionYamlPath = join9(sessionDir, "session.yaml");
2200
+ const sessionDir = join10(input.paths.sessions, sessionId);
2201
+ const sessionYamlPath = join10(sessionDir, "session.yaml");
2147
2202
  const lock = await acquireLock(input.paths, "session", sessionId);
2148
2203
  let bulkResult = null;
2149
2204
  try {
@@ -2293,7 +2348,7 @@ function assertTargetEventIdentity(event, expectedSessionId, expectedEventId) {
2293
2348
 
2294
2349
  // src/storage/task-index.ts
2295
2350
  import { readFile as readFile6 } from "fs/promises";
2296
- import { join as join10 } from "path";
2351
+ import { join as join11 } from "path";
2297
2352
 
2298
2353
  // src/schemas/task-index.schema.ts
2299
2354
  import { z as z7 } from "zod";
@@ -2312,7 +2367,7 @@ var TASK_INDEX_SCHEMA_VERSION = "0.1.0";
2312
2367
 
2313
2368
  // src/storage/task-index.ts
2314
2369
  function taskIndexPath(paths) {
2315
- return join10(paths.tasks, "index.json");
2370
+ return join11(paths.tasks, "index.json");
2316
2371
  }
2317
2372
  async function readTaskIndex(paths) {
2318
2373
  const filePath = taskIndexPath(paths);
@@ -2426,7 +2481,7 @@ function splitFrontMatter(raw) {
2426
2481
  return { yamlText, body };
2427
2482
  }
2428
2483
  async function readTaskFile(paths, taskId) {
2429
- const filePath = join11(paths.tasks, `${taskId}.md`);
2484
+ const filePath = join12(paths.tasks, `${taskId}.md`);
2430
2485
  let raw;
2431
2486
  try {
2432
2487
  raw = await readFile7(filePath, "utf8");
@@ -2459,7 +2514,7 @@ async function readTaskFile(paths, taskId) {
2459
2514
  }
2460
2515
  async function writeTaskFile(paths, taskId, doc, options) {
2461
2516
  const validated = TaskSchema.parse(doc.task);
2462
- const filePath = join11(paths.tasks, `${taskId}.md`);
2517
+ const filePath = join12(paths.tasks, `${taskId}.md`);
2463
2518
  const yamlText = stringifyYaml(validated);
2464
2519
  const trimmedBody = doc.body.length === 0 ? "" : `
2465
2520
  ${doc.body.endsWith("\n") ? doc.body : `${doc.body}
@@ -2511,7 +2566,7 @@ async function enumerateTaskIds(paths) {
2511
2566
  async function enumerateTaskIdsFromDisk(paths) {
2512
2567
  let entries;
2513
2568
  try {
2514
- entries = (await readdir3(paths.tasks, { withFileTypes: true })).filter((d) => d.isFile()).map((d) => d.name);
2569
+ entries = (await readdir4(paths.tasks, { withFileTypes: true })).filter((d) => d.isFile()).map((d) => d.name);
2515
2570
  } catch (error) {
2516
2571
  if (findErrorCode(error, "ENOENT")) return [];
2517
2572
  throw new Error("Failed to enumerate tasks", { cause: error });
@@ -2544,12 +2599,12 @@ async function safeUpdateTaskIndex(paths, op) {
2544
2599
  }
2545
2600
  var ARCHIVE_DIR_NAME = "archive";
2546
2601
  function archiveTasksDir(paths) {
2547
- return join11(paths.tasks, ARCHIVE_DIR_NAME);
2602
+ return join12(paths.tasks, ARCHIVE_DIR_NAME);
2548
2603
  }
2549
2604
  async function enumerateArchivedTaskIds(paths) {
2550
2605
  let entries;
2551
2606
  try {
2552
- entries = (await readdir3(archiveTasksDir(paths), { withFileTypes: true })).filter((d) => d.isFile()).map((d) => d.name);
2607
+ entries = (await readdir4(archiveTasksDir(paths), { withFileTypes: true })).filter((d) => d.isFile()).map((d) => d.name);
2553
2608
  } catch (error) {
2554
2609
  if (findErrorCode(error, "ENOENT")) return [];
2555
2610
  throw new Error("Failed to enumerate archived tasks", { cause: error });
@@ -2574,7 +2629,7 @@ async function readTaskFileWithArchiveFallback(paths, taskId) {
2574
2629
  throw error;
2575
2630
  }
2576
2631
  }
2577
- const archiveFilePath = join11(archiveTasksDir(paths), `${taskId}.md`);
2632
+ const archiveFilePath = join12(archiveTasksDir(paths), `${taskId}.md`);
2578
2633
  let raw;
2579
2634
  try {
2580
2635
  raw = await readFile7(archiveFilePath, "utf8");
@@ -2868,7 +2923,7 @@ async function createTaskAttachLocked(input) {
2868
2923
  ...sessionDoc,
2869
2924
  session: { ...sessionDoc.session, task_id: input.taskId }
2870
2925
  };
2871
- await overwriteYamlFile(join11(input.paths.sessions, input.sessionId, "session.yaml"), updated);
2926
+ await overwriteYamlFile(join12(input.paths.sessions, input.sessionId, "session.yaml"), updated);
2872
2927
  } catch (error) {
2873
2928
  throw new TaskWriteAfterEventError({
2874
2929
  taskId: input.taskId,
@@ -3127,17 +3182,17 @@ function buildUpdatedDoc(input) {
3127
3182
  return { task: next, body: input.currentDoc.body };
3128
3183
  }
3129
3184
  async function computeTaskMdSnapshot(paths, taskId) {
3130
- const filePath = join11(paths.tasks, `${taskId}.md`);
3131
- const [stats, raw] = await Promise.all([stat2(filePath), readFile7(filePath)]);
3185
+ const filePath = join12(paths.tasks, `${taskId}.md`);
3186
+ const [stats, raw] = await Promise.all([stat3(filePath), readFile7(filePath)]);
3132
3187
  const hash = createHash2("sha256").update(raw).digest("hex");
3133
3188
  return { mtimeMs: stats.mtimeMs, hash };
3134
3189
  }
3135
3190
  async function readTaskFileWithSnapshot(paths, taskId) {
3136
- const filePath = join11(paths.tasks, `${taskId}.md`);
3191
+ const filePath = join12(paths.tasks, `${taskId}.md`);
3137
3192
  let rawBuffer;
3138
3193
  let stats;
3139
3194
  try {
3140
- [rawBuffer, stats] = await Promise.all([readFile7(filePath), stat2(filePath)]);
3195
+ [rawBuffer, stats] = await Promise.all([readFile7(filePath), stat3(filePath)]);
3141
3196
  } catch (error) {
3142
3197
  if (findErrorCode(error, "ENOENT")) {
3143
3198
  throw new Error("Task file not found", { cause: error });
@@ -3625,7 +3680,7 @@ async function deleteTaskLocked(input) {
3625
3680
  });
3626
3681
  const eventId = adHoc.targetEventIds[0];
3627
3682
  try {
3628
- await unlink3(join11(input.paths.tasks, `${input.taskId}.md`));
3683
+ await unlink3(join12(input.paths.tasks, `${input.taskId}.md`));
3629
3684
  } catch (error) {
3630
3685
  throw new TaskWriteAfterEventError({
3631
3686
  taskId: input.taskId,
@@ -3697,8 +3752,8 @@ async function archiveTaskLocked(input) {
3697
3752
  );
3698
3753
  await mkdir3(archiveTasksDir(input.paths), { recursive: true });
3699
3754
  await rename2(
3700
- join11(input.paths.tasks, `${input.taskId}.md`),
3701
- join11(archiveTasksDir(input.paths), `${input.taskId}.md`)
3755
+ join12(input.paths.tasks, `${input.taskId}.md`),
3756
+ join12(archiveTasksDir(input.paths), `${input.taskId}.md`)
3702
3757
  );
3703
3758
  } catch (error) {
3704
3759
  throw new TaskWriteAfterEventError({
@@ -3734,7 +3789,7 @@ async function renderHandoff(input) {
3734
3789
  const tasksCreated = [];
3735
3790
  const tasksStatusChanged = [];
3736
3791
  for (const entry of entries) {
3737
- const sessionDir = join12(input.paths.sessions, entry.sessionId);
3792
+ const sessionDir = join13(input.paths.sessions, entry.sessionId);
3738
3793
  try {
3739
3794
  for await (const ev of replayEvents(sessionDir, {
3740
3795
  onWarning: (w) => input.onWarning?.(w, entry.sessionId)
@@ -3850,11 +3905,11 @@ function formatHandoffBody(args) {
3850
3905
  if (args.latestSession !== void 0) {
3851
3906
  const status = args.latestSession.session.session.status;
3852
3907
  const label = args.latestSession.session.session.label;
3853
- const shortId = shortIdWithPrefix(args.latestSession.sessionId);
3908
+ const shortId2 = shortIdWithPrefix(args.latestSession.sessionId);
3854
3909
  if (label !== void 0 && label !== "") {
3855
- lines.push(`- \u6700\u7D42 session: ${label} (${status}) [${shortId}]`);
3910
+ lines.push(`- \u6700\u7D42 session: ${label} (${status}) [${shortId2}]`);
3856
3911
  } else {
3857
- lines.push(`- \u6700\u7D42 session: ${shortId} (${status})`);
3912
+ lines.push(`- \u6700\u7D42 session: ${shortId2} (${status})`);
3858
3913
  }
3859
3914
  } else {
3860
3915
  lines.push("- \u6700\u7D42 session: (no live sessions)");
@@ -4091,11 +4146,455 @@ async function resolveIdInternal(paths, input, kind, options = {}) {
4091
4146
  return matches[0];
4092
4147
  }
4093
4148
 
4094
- // src/report/report-renderer.ts
4149
+ // src/orientation/orientation-renderer.ts
4095
4150
  import { join as join14 } from "path";
4096
4151
 
4152
+ // src/storage/manifest.ts
4153
+ import { lstat as lstat3 } from "fs/promises";
4154
+
4155
+ // src/schemas/manifest.schema.ts
4156
+ import { z as z9 } from "zod";
4157
+ var ProjectSchema = z9.object({
4158
+ name: z9.string().optional(),
4159
+ description: z9.string().optional(),
4160
+ repository_url: z9.string().nullable().optional()
4161
+ });
4162
+ var CapabilitiesSchema = z9.object({
4163
+ enabled: z9.array(z9.string())
4164
+ });
4165
+ var ApprovalConfigSchema = z9.object({
4166
+ required_for: z9.array(z9.string()).optional(),
4167
+ default_risk_level: z9.enum(["low", "medium", "high", "critical"])
4168
+ });
4169
+ var ClaudeCodeAdapterConfigSchema = z9.object({
4170
+ enabled: z9.boolean(),
4171
+ config_path: z9.string().optional()
4172
+ });
4173
+ var AdaptersSchema = z9.object({
4174
+ "claude-code": ClaudeCodeAdapterConfigSchema
4175
+ });
4176
+ var GitConfigSchema = z9.object({
4177
+ events_log: z9.enum(["ignore", "commit"]).default("ignore")
4178
+ });
4179
+ var SOURCE_ROOT_PATTERN = /^(?![~/\\])(?![A-Za-z]:)[^\0\\]+$/;
4180
+ var SourceRootSchema = z9.string().min(1).regex(SOURCE_ROOT_PATTERN, {
4181
+ message: "source_roots entries must be relative paths (no absolute path, '~', '\\', or null byte)"
4182
+ });
4183
+ var ImportConfigSchema = z9.object({
4184
+ source_roots: z9.array(SourceRootSchema).min(1).optional()
4185
+ });
4186
+ var WorkspaceMetaSchema = z9.object({
4187
+ id: WorkspaceIdSchema,
4188
+ name: z9.string().min(1),
4189
+ created_at: IsoTimestampSchema,
4190
+ updated_at: IsoTimestampSchema
4191
+ });
4192
+ var ManifestSchema = z9.object({
4193
+ schema_version: SchemaVersionSchema,
4194
+ basou_version: z9.literal("0.1.0"),
4195
+ workspace: WorkspaceMetaSchema,
4196
+ project: ProjectSchema,
4197
+ capabilities: CapabilitiesSchema,
4198
+ approval: ApprovalConfigSchema,
4199
+ adapters: AdaptersSchema,
4200
+ git: GitConfigSchema,
4201
+ import: ImportConfigSchema.optional()
4202
+ });
4203
+
4204
+ // src/storage/manifest.ts
4205
+ function createManifest(input) {
4206
+ if (input.workspaceName.length === 0) {
4207
+ throw new Error("Workspace name is empty. Pass --name explicitly.");
4208
+ }
4209
+ const now = (input.now ?? /* @__PURE__ */ new Date()).toISOString();
4210
+ const workspaceId = input.workspaceId ?? prefixedUlid("ws");
4211
+ const project = {
4212
+ ...input.projectName !== void 0 ? { name: input.projectName } : {},
4213
+ ...input.projectDescription !== void 0 ? { description: input.projectDescription } : {},
4214
+ ...input.repositoryUrl !== void 0 ? { repository_url: input.repositoryUrl } : {}
4215
+ };
4216
+ const manifest = {
4217
+ schema_version: "0.1.0",
4218
+ basou_version: "0.1.0",
4219
+ workspace: {
4220
+ id: workspaceId,
4221
+ name: input.workspaceName,
4222
+ created_at: now,
4223
+ updated_at: now
4224
+ },
4225
+ project,
4226
+ capabilities: {
4227
+ enabled: ["core", "claude-code-adapter", "terminal-recording", "git-capability", "approval"]
4228
+ },
4229
+ approval: {
4230
+ required_for: ["destructive_command", "external_send"],
4231
+ default_risk_level: "medium"
4232
+ },
4233
+ adapters: {
4234
+ "claude-code": { enabled: true }
4235
+ },
4236
+ git: { events_log: "ignore" },
4237
+ ...input.sourceRoots !== void 0 && input.sourceRoots.length > 0 ? { import: { source_roots: input.sourceRoots } } : {}
4238
+ };
4239
+ return ManifestSchema.parse(manifest);
4240
+ }
4241
+ async function writeManifest(paths, manifest, options) {
4242
+ const force = options?.force === true;
4243
+ const validated = ManifestSchema.parse(manifest);
4244
+ if (!force) {
4245
+ let existed = false;
4246
+ try {
4247
+ await lstat3(paths.files.manifest);
4248
+ existed = true;
4249
+ } catch (error) {
4250
+ if (!hasErrorCode3(error) || error.code !== "ENOENT") {
4251
+ throw new Error("Failed to inspect existing manifest", { cause: error });
4252
+ }
4253
+ }
4254
+ if (existed) {
4255
+ throw new Error("Already initialized. Use --force to overwrite.");
4256
+ }
4257
+ }
4258
+ await writeYamlFile(paths.files.manifest, validated);
4259
+ }
4260
+ async function readManifest(paths) {
4261
+ const raw = await readYamlFile(paths.files.manifest);
4262
+ return ManifestSchema.parse(raw);
4263
+ }
4264
+ function hasErrorCode3(error) {
4265
+ if (!(error instanceof Error)) return false;
4266
+ return typeof error.code === "string";
4267
+ }
4268
+
4269
+ // src/orientation/orientation-renderer.ts
4270
+ async function summarizeOrientation(input) {
4271
+ const limit = input.relatedFilesLimit ?? 10;
4272
+ const now = new Date(input.nowIso);
4273
+ const loadOpts = { now };
4274
+ if (input.onSessionSkip !== void 0) loadOpts.onSkip = input.onSessionSkip;
4275
+ if (input.onWarning !== void 0) loadOpts.onWarning = input.onWarning;
4276
+ const entries = await loadSessionEntries(input.paths, loadOpts);
4277
+ const decisions = [];
4278
+ for (const entry of entries) {
4279
+ const sessionDir = join14(input.paths.sessions, entry.sessionId);
4280
+ try {
4281
+ for await (const ev of replayEvents(sessionDir, {
4282
+ onWarning: (w) => input.onWarning?.(w, entry.sessionId)
4283
+ })) {
4284
+ if (ev.type === "decision_recorded") {
4285
+ decisions.push({
4286
+ decisionId: ev.decision_id,
4287
+ title: ev.title,
4288
+ occurredAt: ev.occurred_at
4289
+ });
4290
+ }
4291
+ }
4292
+ } catch {
4293
+ input.onSessionSkip?.(entry.sessionId, "events_jsonl_unreadable");
4294
+ }
4295
+ }
4296
+ decisions.sort((a, b) => {
4297
+ const c = Date.parse(a.occurredAt) - Date.parse(b.occurredAt);
4298
+ return c !== 0 ? c : a.decisionId.localeCompare(b.decisionId);
4299
+ });
4300
+ const latestDecision = decisions[decisions.length - 1];
4301
+ const taskLoadOpts = {};
4302
+ if (input.onTaskSkip !== void 0) taskLoadOpts.onSkip = input.onTaskSkip;
4303
+ const taskEntries = await loadTaskEntries(input.paths, taskLoadOpts);
4304
+ const inFlightTasks = taskEntries.filter((t) => t.task.task.status === "in_progress" || t.task.task.status === "planned").map((t) => ({
4305
+ id: t.task.task.id,
4306
+ title: t.task.task.title,
4307
+ status: t.task.task.status,
4308
+ linkedSessions: t.task.task.linked_sessions?.length ?? 0
4309
+ }));
4310
+ const plannedTasks = taskEntries.filter((t) => t.task.task.status === "planned").map((t) => ({ id: t.task.task.id, title: t.task.task.title }));
4311
+ const { pending: pendingIds } = await enumerateApprovals(input.paths);
4312
+ const pendingApprovals = [];
4313
+ for (const id of [...pendingIds].sort()) {
4314
+ const loaded = await loadApproval(input.paths, id);
4315
+ if (loaded === null) continue;
4316
+ const a = loaded.approval;
4317
+ pendingApprovals.push({
4318
+ id,
4319
+ risk: a.risk_level,
4320
+ kind: a.action.kind,
4321
+ reason: a.reason,
4322
+ sessionId: a.session_id,
4323
+ createdAt: a.created_at,
4324
+ expired: isLazyExpired(a, now)
4325
+ });
4326
+ }
4327
+ const suspects = entries.filter((e) => e.suspect).map((e) => ({
4328
+ sessionId: e.sessionId,
4329
+ status: e.session.session.status,
4330
+ reason: e.suspectReason
4331
+ }));
4332
+ const liveEntries = entries.filter(
4333
+ (e) => e.session.session.status !== "archived" && e.session.session.source.kind !== "import"
4334
+ );
4335
+ const latestEntry = [...liveEntries].sort(
4336
+ (a, b) => Date.parse(b.session.session.started_at) - Date.parse(a.session.session.started_at)
4337
+ )[0];
4338
+ const latestSession = latestEntry !== void 0 ? {
4339
+ sessionId: latestEntry.sessionId,
4340
+ label: latestEntry.session.session.label ?? null,
4341
+ status: latestEntry.session.session.status
4342
+ } : null;
4343
+ const activityEntries = entries.filter((e) => e.session.session.status !== "archived");
4344
+ const newest = [...activityEntries].sort(
4345
+ (a, b) => Date.parse(b.session.session.started_at) - Date.parse(a.session.session.started_at)
4346
+ )[0];
4347
+ const bySourceMap = /* @__PURE__ */ new Map();
4348
+ for (const e of entries) {
4349
+ const k = e.session.session.source.kind;
4350
+ bySourceMap.set(k, (bySourceMap.get(k) ?? 0) + 1);
4351
+ }
4352
+ const bySource = [...bySourceMap.entries()].sort((a, b) => a[0].localeCompare(b[0])).map(([kind, count]) => ({ kind, count }));
4353
+ let sourceRoots = null;
4354
+ try {
4355
+ const manifest = await readManifest(input.paths);
4356
+ sourceRoots = manifest.import?.source_roots ?? null;
4357
+ } catch {
4358
+ sourceRoots = null;
4359
+ }
4360
+ const latestFiles = latestEntry?.session.session.related_files ?? [];
4361
+ const uniqueFiles = new Set(latestFiles);
4362
+ const displayed = [...uniqueFiles].sort().slice(0, limit);
4363
+ const overflow = Math.max(0, uniqueFiles.size - limit);
4364
+ return {
4365
+ generatedAt: input.nowIso,
4366
+ sessionCount: entries.length,
4367
+ latestSession,
4368
+ latestDecision: latestDecision ?? null,
4369
+ decisionCount: decisions.length,
4370
+ relatedFiles: { displayed, overflow },
4371
+ inFlightTasks,
4372
+ plannedTasks,
4373
+ pendingApprovals,
4374
+ suspects,
4375
+ freshness: {
4376
+ newestStartedAt: newest?.session.session.started_at ?? null,
4377
+ newestSource: newest?.session.session.source.kind ?? null,
4378
+ bySource,
4379
+ sourceRoots
4380
+ }
4381
+ };
4382
+ }
4383
+ async function renderOrientation(input) {
4384
+ const summary = await summarizeOrientation(input);
4385
+ return {
4386
+ body: formatOrientationBody(summary, {
4387
+ staleness: input.staleness ?? null,
4388
+ verbose: input.verbose === true
4389
+ }),
4390
+ sessionCount: summary.sessionCount,
4391
+ pendingApprovalsCount: summary.pendingApprovals.length,
4392
+ suspectCount: summary.suspects.length,
4393
+ inFlightTaskCount: summary.inFlightTasks.length,
4394
+ decisionCount: summary.decisionCount
4395
+ };
4396
+ }
4397
+ function formatOrientationBody(summary, opts) {
4398
+ const lines = [];
4399
+ const now = new Date(summary.generatedAt);
4400
+ const newestRel = relativeAge(summary.freshness.newestStartedAt ?? void 0, now);
4401
+ lines.push("# Orientation");
4402
+ lines.push("");
4403
+ lines.push(
4404
+ `> Generated at ${summary.generatedAt} \xB7 sessions ${summary.sessionCount} \xB7 newest ${newestRel} \xB7 pending ${summary.pendingApprovals.length} \xB7 suspect ${summary.suspects.length}`
4405
+ );
4406
+ lines.push("");
4407
+ lines.push("## \u4ECA\u3069\u3053\u306B\u3044\u308B");
4408
+ lines.push("");
4409
+ if (summary.latestSession !== null) {
4410
+ const s = summary.latestSession;
4411
+ const sid = shortId(s.sessionId);
4412
+ if (s.label !== null && s.label !== "") {
4413
+ lines.push(`- \u6700\u7D42 session: ${s.label} (${s.status}) [${sid}]`);
4414
+ } else {
4415
+ lines.push(`- \u6700\u7D42 session: ${sid} (${s.status})`);
4416
+ }
4417
+ } else {
4418
+ lines.push("- \u6700\u7D42 session: (no live sessions)");
4419
+ }
4420
+ if (summary.latestDecision !== null) {
4421
+ lines.push(
4422
+ `- \u76F4\u8FD1\u306E\u5224\u65AD: ${summary.latestDecision.title} [${shortId(summary.latestDecision.decisionId)}]`
4423
+ );
4424
+ if (summary.decisionCount > 1) {
4425
+ lines.push(` - ${summary.decisionCount} decisions total \u2014 see decisions.md`);
4426
+ }
4427
+ } else {
4428
+ lines.push("- \u76F4\u8FD1\u306E\u5224\u65AD: (no decisions recorded yet)");
4429
+ }
4430
+ if (summary.relatedFiles.displayed.length > 0) {
4431
+ const shown = summary.relatedFiles.displayed.join(", ");
4432
+ const more = summary.relatedFiles.overflow > 0 ? ` (... +${summary.relatedFiles.overflow} more)` : "";
4433
+ lines.push(`- \u76F4\u8FD1\u306E\u5909\u66F4\u30D5\u30A1\u30A4\u30EB: ${shown}${more}`);
4434
+ } else {
4435
+ lines.push("- \u76F4\u8FD1\u306E\u5909\u66F4\u30D5\u30A1\u30A4\u30EB: (none recorded)");
4436
+ }
4437
+ lines.push("");
4438
+ lines.push("## \u4F55\u304C\u52D5\u304F");
4439
+ lines.push("");
4440
+ lines.push(`### \u9032\u884C\u4E2D task (${summary.inFlightTasks.length})`);
4441
+ if (summary.inFlightTasks.length === 0) {
4442
+ lines.push("- (none)");
4443
+ } else {
4444
+ for (const t of summary.inFlightTasks) {
4445
+ const linkedSuffix = t.linkedSessions > 1 ? ` \u2014 linked_sessions: ${t.linkedSessions}` : "";
4446
+ lines.push(`- ${t.title} (${t.status}) [${shortId(t.id)}]${linkedSuffix}`);
4447
+ }
4448
+ }
4449
+ lines.push("");
4450
+ lines.push(`### \u627F\u8A8D\u5F85\u3061 (${summary.pendingApprovals.length})`);
4451
+ if (summary.pendingApprovals.length === 0) {
4452
+ lines.push("- (none)");
4453
+ } else {
4454
+ for (const a of summary.pendingApprovals) {
4455
+ const expired = a.expired ? " (expired)" : "";
4456
+ lines.push(
4457
+ `- [${a.risk}] ${a.kind}: ${a.reason} \u2014 session ${shortId(a.sessionId)}, since ${a.createdAt}${expired}`
4458
+ );
4459
+ }
4460
+ }
4461
+ lines.push("");
4462
+ lines.push(`### \u8981\u6CE8\u610F session (${summary.suspects.length})`);
4463
+ if (summary.suspects.length === 0) {
4464
+ lines.push("- (none)");
4465
+ } else {
4466
+ for (const e of summary.suspects) {
4467
+ lines.push(`- ${shortId(e.sessionId)} (${e.status}) \u2014 ${suspectText(e.reason)}`);
4468
+ }
4469
+ }
4470
+ lines.push("");
4471
+ lines.push("## \u3069\u3053\u3078\u5411\u304B\u3046");
4472
+ lines.push("");
4473
+ if (summary.plannedTasks.length === 0) {
4474
+ lines.push("- (no planned tasks \u2014 direction is inferred from recent decisions)");
4475
+ if (summary.latestDecision !== null) {
4476
+ lines.push(` - \u76F4\u8FD1\u306E\u5224\u65AD: ${summary.latestDecision.title}`);
4477
+ }
4478
+ } else {
4479
+ for (const t of summary.plannedTasks) {
4480
+ lines.push(`- ${t.title} [${shortId(t.id)}]`);
4481
+ }
4482
+ }
4483
+ lines.push("");
4484
+ lines.push("## \u3053\u308C\u306F\u6700\u65B0\u304B");
4485
+ lines.push("");
4486
+ for (const line of freshnessVerdict(summary, opts.staleness, now)) lines.push(line);
4487
+ if (opts.verbose) {
4488
+ lines.push("");
4489
+ lines.push("<!-- verbose: raw freshness telemetry -->");
4490
+ if (summary.freshness.newestStartedAt !== null) {
4491
+ lines.push(`- newest captured session: ${summary.freshness.newestStartedAt} (${newestRel})`);
4492
+ } else {
4493
+ lines.push("- newest captured session: (no sessions captured yet)");
4494
+ }
4495
+ const sourceBreakdown = summary.freshness.bySource.map(({ kind, count }) => `${kind} ${count}`).join(", ");
4496
+ lines.push(
4497
+ `- sessions: ${summary.sessionCount}${sourceBreakdown !== "" ? ` (${sourceBreakdown})` : ""}`
4498
+ );
4499
+ if (summary.freshness.sourceRoots !== null && summary.freshness.sourceRoots.length > 0) {
4500
+ lines.push(`- source roots: ${summary.freshness.sourceRoots.join(", ")}`);
4501
+ } else {
4502
+ lines.push("- source roots: (single root)");
4503
+ }
4504
+ lines.push(`- suspect sessions: ${summary.suspects.length}`);
4505
+ const probe = opts.staleness === null ? "not run" : `new ${opts.staleness.newSessions}, updated ${opts.staleness.updatedSessions}, unverifiable ${opts.staleness.unverifiableSessions ?? 0}`;
4506
+ lines.push(`- staleness probe: ${probe}`);
4507
+ }
4508
+ return lines.join("\n");
4509
+ }
4510
+ function toolDisplayName(kind) {
4511
+ switch (kind) {
4512
+ case "claude-code-import":
4513
+ case "claude-code-adapter":
4514
+ return "Claude Code";
4515
+ case "codex-import":
4516
+ return "Codex";
4517
+ case "terminal":
4518
+ return "\u30BF\u30FC\u30DF\u30CA\u30EB";
4519
+ case "human":
4520
+ return "\u624B\u52D5\u30E1\u30E2";
4521
+ case "import":
4522
+ return "\u4ED6\u30EF\u30FC\u30AF\u30B9\u30DA\u30FC\u30B9";
4523
+ default:
4524
+ return kind ?? "\u4E0D\u660E";
4525
+ }
4526
+ }
4527
+ function freshnessVerdict(summary, staleness, now) {
4528
+ if (staleness !== null && (staleness.unverifiableSessions ?? 0) > 0) {
4529
+ return [
4530
+ `\u26A0\uFE0F \u6700\u65B0\u304B\u78BA\u8A8D\u3067\u304D\u307E\u305B\u3093\u3002\u5909\u5316\u3057\u305F\u304C\u5B89\u5168\u306B\u53D6\u308A\u8FBC\u3081\u306A\u3044\u30BB\u30C3\u30B7\u30E7\u30F3\u304C ${staleness.unverifiableSessions} \u4EF6\u3042\u308A\u307E\u3059(\u30CF\u30C3\u30B7\u30E5\u30C1\u30A7\u30FC\u30F3\u7834\u640D\u30FB\u975E\u8FFD\u8A18\u5909\u66F4\u306A\u3069)\u3002`,
4531
+ "`basou verify` \u3067\u78BA\u8A8D\u3057\u3001`basou refresh --force` \u3067\u518D\u53D6\u308A\u8FBC\u307F\u3057\u3066\u304F\u3060\u3055\u3044\u3002"
4532
+ ];
4533
+ }
4534
+ if (staleness !== null && (staleness.newSessions > 0 || staleness.updatedSessions > 0)) {
4535
+ const parts = [];
4536
+ if (staleness.newSessions > 0) parts.push(`\u65B0\u898F ${staleness.newSessions} \u4EF6`);
4537
+ if (staleness.updatedSessions > 0) parts.push(`\u66F4\u65B0 ${staleness.updatedSessions} \u4EF6`);
4538
+ return [
4539
+ `\u26A0\uFE0F \u53E4\u3044\u304B\u3082\u3057\u308C\u307E\u305B\u3093\u3002\u6700\u5F8C\u306E\u53D6\u308A\u8FBC\u307F\u4EE5\u964D\u306B\u672A\u53D6\u308A\u8FBC\u307F\u306E\u4F5C\u696D\u304C\u3042\u308A\u307E\u3059(${parts.join("\u30FB")})\u3002`,
4540
+ "`basou refresh` \u3067\u66F4\u65B0\u3057\u3066\u304F\u3060\u3055\u3044\u3002"
4541
+ ];
4542
+ }
4543
+ if (summary.freshness.newestStartedAt === null) {
4544
+ return [
4545
+ "\u2139\uFE0F \u307E\u3060\u8A18\u9332\u304C\u3042\u308A\u307E\u305B\u3093\u3002",
4546
+ "\u3053\u306E\u30EF\u30FC\u30AF\u30B9\u30DA\u30FC\u30B9\u3067\u4F5C\u696D\u3059\u308B\u3068\u3001\u3053\u3053\u306B\u73FE\u5728\u5730\u304C\u8868\u793A\u3055\u308C\u307E\u3059\u3002"
4547
+ ];
4548
+ }
4549
+ const rel = relativeAgeJa(summary.freshness.newestStartedAt, now);
4550
+ const tool = toolDisplayName(summary.freshness.newestSource);
4551
+ const suspectCount = summary.suspects.length;
4552
+ const suspectClause = suspectCount > 0 ? `\u8981\u6CE8\u610F\u30BB\u30C3\u30B7\u30E7\u30F3\u304C ${suspectCount} \u4EF6\u3042\u308A\u307E\u3059\u3002` : "\u53D6\u308A\u3053\u307C\u3057\u30FB\u8981\u6CE8\u610F\u306A\u3057\u3002";
4553
+ if (staleness === null) {
4554
+ return [
4555
+ `\u2139\uFE0F \u53D6\u308A\u8FBC\u307F\u6E08\u307F\u306E\u72B6\u614B\u3092\u8868\u793A\u3057\u3066\u3044\u307E\u3059\u3002\u6700\u5F8C\u306E\u4F5C\u696D\u306F ${rel}(${tool})\u3002`,
4556
+ "\u6700\u65B0\u304B\u78BA\u8A8D\u3059\u308B\u306B\u306F `basou refresh` \u3092\u5B9F\u884C\u3057\u3066\u304F\u3060\u3055\u3044\u3002"
4557
+ ];
4558
+ }
4559
+ return [`\u2705 \u6700\u65B0\u3067\u3059\u3002\u6700\u5F8C\u306E\u4F5C\u696D\u306F ${rel}(${tool})\u3002${suspectClause}`];
4560
+ }
4561
+ function relativeAgeJa(startedAt, now) {
4562
+ if (startedAt === null) return "(\u4E0D\u660E)";
4563
+ const ms = now.getTime() - Date.parse(startedAt);
4564
+ if (!Number.isFinite(ms) || ms < 0) return "\u305F\u3063\u305F\u4ECA";
4565
+ if (ms < 6e4) return "\u305F\u3063\u305F\u4ECA";
4566
+ const totalMin = Math.floor(ms / 6e4);
4567
+ const days = Math.floor(totalMin / 1440);
4568
+ const hours = Math.floor(totalMin % 1440 / 60);
4569
+ const mins = totalMin % 60;
4570
+ if (days > 0) return hours > 0 ? `${days}\u65E5${hours}\u6642\u9593\u524D` : `${days}\u65E5\u524D`;
4571
+ if (hours > 0) return mins > 0 ? `${hours}\u6642\u9593${mins}\u5206\u524D` : `${hours}\u6642\u9593\u524D`;
4572
+ return `${mins}\u5206\u524D`;
4573
+ }
4574
+ function relativeAge(startedAt, now) {
4575
+ if (startedAt === void 0) return "(unknown)";
4576
+ const ms = now.getTime() - Date.parse(startedAt);
4577
+ if (!Number.isFinite(ms)) return "(unknown)";
4578
+ if (ms < 0) return "just now";
4579
+ if (ms < 1e3) return "just now";
4580
+ return `${formatDurationMs(ms)} ago`;
4581
+ }
4582
+ function suspectText(reason) {
4583
+ if (reason === "events_say_ended_but_yaml_running") return "ended (yaml stale)";
4584
+ if (reason === "running_no_end_event") return "no end event";
4585
+ return "suspect";
4586
+ }
4587
+ function shortId(id) {
4588
+ const sep = id.indexOf("_");
4589
+ if (sep === -1) return id.slice(0, 10);
4590
+ return id.slice(0, sep + 1) + id.slice(sep + 1, sep + 1 + 10);
4591
+ }
4592
+
4593
+ // src/report/report-renderer.ts
4594
+ import { join as join16 } from "path";
4595
+
4097
4596
  // src/stats/work-stats.ts
4098
- import { join as join13 } from "path";
4597
+ import { join as join15 } from "path";
4099
4598
  function resolveTimeZone(timeZone) {
4100
4599
  if (timeZone !== void 0 && timeZone.length > 0) return timeZone;
4101
4600
  return Intl.DateTimeFormat().resolvedOptions().timeZone;
@@ -4126,7 +4625,7 @@ async function computeWorkStats(input) {
4126
4625
  const events = [];
4127
4626
  let eventsUnreadable = false;
4128
4627
  try {
4129
- for await (const ev of replayEvents(join13(input.paths.sessions, entry.sessionId), {
4628
+ for await (const ev of replayEvents(join15(input.paths.sessions, entry.sessionId), {
4130
4629
  onWarning: (w) => input.onWarning?.(w, entry.sessionId)
4131
4630
  })) {
4132
4631
  events.push(ev);
@@ -4413,7 +4912,7 @@ async function renderReport(input) {
4413
4912
  const statsBySession = new Map(stats.sessions.map((s) => [s.sessionId, s]));
4414
4913
  const decisions = [];
4415
4914
  for (const entry of entries) {
4416
- const sessionDir = join14(input.paths.sessions, entry.sessionId);
4915
+ const sessionDir = join16(input.paths.sessions, entry.sessionId);
4417
4916
  try {
4418
4917
  for await (const ev of replayEvents(sessionDir, {
4419
4918
  onWarning: (w) => input.onWarning?.(w, entry.sessionId)
@@ -4818,55 +5317,6 @@ function classifySpawnError(error) {
4818
5317
  // src/schemas/json-schema.ts
4819
5318
  import { z as z11 } from "zod";
4820
5319
 
4821
- // src/schemas/manifest.schema.ts
4822
- import { z as z9 } from "zod";
4823
- var ProjectSchema = z9.object({
4824
- name: z9.string().optional(),
4825
- description: z9.string().optional(),
4826
- repository_url: z9.string().nullable().optional()
4827
- });
4828
- var CapabilitiesSchema = z9.object({
4829
- enabled: z9.array(z9.string())
4830
- });
4831
- var ApprovalConfigSchema = z9.object({
4832
- required_for: z9.array(z9.string()).optional(),
4833
- default_risk_level: z9.enum(["low", "medium", "high", "critical"])
4834
- });
4835
- var ClaudeCodeAdapterConfigSchema = z9.object({
4836
- enabled: z9.boolean(),
4837
- config_path: z9.string().optional()
4838
- });
4839
- var AdaptersSchema = z9.object({
4840
- "claude-code": ClaudeCodeAdapterConfigSchema
4841
- });
4842
- var GitConfigSchema = z9.object({
4843
- events_log: z9.enum(["ignore", "commit"]).default("ignore")
4844
- });
4845
- var SOURCE_ROOT_PATTERN = /^(?![~/\\])(?![A-Za-z]:)[^\0\\]+$/;
4846
- var SourceRootSchema = z9.string().min(1).regex(SOURCE_ROOT_PATTERN, {
4847
- message: "source_roots entries must be relative paths (no absolute path, '~', '\\', or null byte)"
4848
- });
4849
- var ImportConfigSchema = z9.object({
4850
- source_roots: z9.array(SourceRootSchema).min(1).optional()
4851
- });
4852
- var WorkspaceMetaSchema = z9.object({
4853
- id: WorkspaceIdSchema,
4854
- name: z9.string().min(1),
4855
- created_at: IsoTimestampSchema,
4856
- updated_at: IsoTimestampSchema
4857
- });
4858
- var ManifestSchema = z9.object({
4859
- schema_version: SchemaVersionSchema,
4860
- basou_version: z9.literal("0.1.0"),
4861
- workspace: WorkspaceMetaSchema,
4862
- project: ProjectSchema,
4863
- capabilities: CapabilitiesSchema,
4864
- approval: ApprovalConfigSchema,
4865
- adapters: AdaptersSchema,
4866
- git: GitConfigSchema,
4867
- import: ImportConfigSchema.optional()
4868
- });
4869
-
4870
5320
  // src/schemas/session-import.schema.ts
4871
5321
  import { z as z10 } from "zod";
4872
5322
  var SessionInnerImportSchema = z10.object({
@@ -4986,28 +5436,29 @@ function serializeJsonSchema(schema) {
4986
5436
  }
4987
5437
 
4988
5438
  // src/storage/basou-dir.ts
4989
- import { lstat as lstat3, mkdir as mkdir4 } from "fs/promises";
4990
- import { join as join15 } from "path";
5439
+ import { lstat as lstat4, mkdir as mkdir4 } from "fs/promises";
5440
+ import { join as join17 } from "path";
4991
5441
  function basouPaths(repositoryRoot) {
4992
- const root = join15(repositoryRoot, ".basou");
4993
- const approvalsBase = join15(root, "approvals");
5442
+ const root = join17(repositoryRoot, ".basou");
5443
+ const approvalsBase = join17(root, "approvals");
4994
5444
  return {
4995
5445
  root,
4996
- sessions: join15(root, "sessions"),
4997
- tasks: join15(root, "tasks"),
5446
+ sessions: join17(root, "sessions"),
5447
+ tasks: join17(root, "tasks"),
4998
5448
  approvals: {
4999
- pending: join15(approvalsBase, "pending"),
5000
- resolved: join15(approvalsBase, "resolved")
5449
+ pending: join17(approvalsBase, "pending"),
5450
+ resolved: join17(approvalsBase, "resolved")
5001
5451
  },
5002
- locks: join15(root, "locks"),
5003
- logs: join15(root, "logs"),
5004
- raw: join15(root, "raw"),
5005
- tmp: join15(root, "tmp"),
5452
+ locks: join17(root, "locks"),
5453
+ logs: join17(root, "logs"),
5454
+ raw: join17(root, "raw"),
5455
+ tmp: join17(root, "tmp"),
5006
5456
  files: {
5007
- manifest: join15(root, "manifest.yaml"),
5008
- status: join15(root, "status.json"),
5009
- handoff: join15(root, "handoff.md"),
5010
- decisions: join15(root, "decisions.md")
5457
+ manifest: join17(root, "manifest.yaml"),
5458
+ status: join17(root, "status.json"),
5459
+ handoff: join17(root, "handoff.md"),
5460
+ decisions: join17(root, "decisions.md"),
5461
+ orientation: join17(root, "orientation.md")
5011
5462
  }
5012
5463
  };
5013
5464
  }
@@ -5025,9 +5476,9 @@ async function ensureBasouDirectory(repositoryRoot) {
5025
5476
  const paths = basouPaths(repositoryRoot);
5026
5477
  let existing;
5027
5478
  try {
5028
- existing = await lstat3(paths.root);
5479
+ existing = await lstat4(paths.root);
5029
5480
  } catch (error) {
5030
- if (!hasErrorCode3(error) || error.code !== "ENOENT") {
5481
+ if (!hasErrorCode4(error) || error.code !== "ENOENT") {
5031
5482
  throw new Error("Failed to inspect .basou directory", { cause: error });
5032
5483
  }
5033
5484
  }
@@ -5050,13 +5501,13 @@ async function mkdirLabeled(target, label) {
5050
5501
  try {
5051
5502
  await mkdir4(target, { recursive: true });
5052
5503
  } catch (error) {
5053
- if (hasErrorCode3(error) && (error.code === "ENOTDIR" || error.code === "EEXIST")) {
5504
+ if (hasErrorCode4(error) && (error.code === "ENOTDIR" || error.code === "EEXIST")) {
5054
5505
  throw new Error(`${label} exists but is not a directory`, { cause: error });
5055
5506
  }
5056
5507
  throw new Error(`Failed to create ${label}`, { cause: error });
5057
5508
  }
5058
5509
  }
5059
- function hasErrorCode3(error) {
5510
+ function hasErrorCode4(error) {
5060
5511
  if (!(error instanceof Error)) return false;
5061
5512
  const codeProp = error.code;
5062
5513
  return typeof codeProp === "string";
@@ -5064,28 +5515,30 @@ function hasErrorCode3(error) {
5064
5515
 
5065
5516
  // src/storage/gitignore.ts
5066
5517
  import { readFile as readFile8, writeFile as writeFile2 } from "fs/promises";
5067
- import { join as join16 } from "path";
5518
+ import { join as join18 } from "path";
5068
5519
  var MARKER = "# Basou - default ignore";
5069
- var BASOU_GITIGNORE_BLOCK = "# Basou - default ignore\n.basou/logs/\n.basou/raw/\n.basou/tmp/\n.basou/locks/\n.basou/status.json\n.basou/sessions/*/events.jsonl\n.basou/sessions/*/artifacts/\n.basou/approvals/pending/\n.basou/approvals/resolved/\n\n# Basou - default commit\n# .basou/manifest.yaml\n# .basou/handoff.md\n# .basou/decisions.md\n# .basou/tasks/\n# .basou/sessions/*/session.yaml\n# .basou/sessions/*/transcript.md\n# .basou/sessions/*/changed-files.json\n";
5070
- async function appendBasouGitignore(repositoryRoot) {
5071
- const gitignorePath = join16(repositoryRoot, ".gitignore");
5520
+ var BASOU_GITIGNORE_BLOCK = "# Basou - default ignore\n.basou/logs/\n.basou/raw/\n.basou/tmp/\n.basou/locks/\n.basou/status.json\n.basou/orientation.md\n.basou/sessions/*/events.jsonl\n.basou/sessions/*/artifacts/\n.basou/approvals/pending/\n.basou/approvals/resolved/\n\n# Basou - default commit\n# .basou/manifest.yaml\n# .basou/handoff.md\n# .basou/decisions.md\n# .basou/tasks/\n# .basou/sessions/*/session.yaml\n# .basou/sessions/*/transcript.md\n# .basou/sessions/*/changed-files.json\n";
5521
+ var BASOU_GITIGNORE_BLOCK_LOCAL_ONLY = "# Basou - default ignore\n# Local-only: basou's trail is never committed (personal/local state,\n# regenerable by re-importing from the agents' own logs). Recommended for\n# monitored repos and any workspace kept out of version control.\n.basou/\n";
5522
+ async function appendBasouGitignore(repositoryRoot, options = {}) {
5523
+ const gitignorePath = join18(repositoryRoot, ".gitignore");
5072
5524
  let body;
5073
5525
  let existed;
5074
5526
  try {
5075
5527
  body = await readFile8(gitignorePath, "utf8");
5076
5528
  existed = true;
5077
5529
  } catch (error) {
5078
- if (hasErrorCode4(error) && error.code === "ENOENT") {
5530
+ if (hasErrorCode5(error) && error.code === "ENOENT") {
5079
5531
  body = "";
5080
5532
  existed = false;
5081
5533
  } else {
5082
5534
  throw new Error("Failed to read .gitignore", { cause: error });
5083
5535
  }
5084
5536
  }
5085
- if (existed && hasBasouMarker(body)) {
5537
+ if (existed && hasBasouGitignore(body)) {
5086
5538
  return { appended: false };
5087
5539
  }
5088
- const next = composeNextBody(body);
5540
+ const block = options.localOnly === true ? BASOU_GITIGNORE_BLOCK_LOCAL_ONLY : BASOU_GITIGNORE_BLOCK;
5541
+ const next = composeNextBody(body, block);
5089
5542
  try {
5090
5543
  await writeFile2(gitignorePath, next, { encoding: "utf8" });
5091
5544
  } catch (error) {
@@ -5093,84 +5546,20 @@ async function appendBasouGitignore(repositoryRoot) {
5093
5546
  }
5094
5547
  return { appended: true };
5095
5548
  }
5096
- function hasBasouMarker(body) {
5549
+ function hasBasouGitignore(body) {
5097
5550
  for (const rawLine of body.split("\n")) {
5098
- if (rawLine.trimEnd().startsWith(MARKER)) return true;
5551
+ const line = rawLine.trimEnd();
5552
+ if (line.startsWith(MARKER)) return true;
5553
+ if (line === ".basou/" || line === "/.basou/") return true;
5099
5554
  }
5100
5555
  return false;
5101
5556
  }
5102
- function composeNextBody(existing) {
5103
- if (existing.length === 0) return BASOU_GITIGNORE_BLOCK;
5557
+ function composeNextBody(existing, block) {
5558
+ if (existing.length === 0) return block;
5104
5559
  const normalized = existing.endsWith("\n") ? existing : `${existing}
5105
5560
  `;
5106
5561
  return `${normalized}
5107
- ${BASOU_GITIGNORE_BLOCK}`;
5108
- }
5109
- function hasErrorCode4(error) {
5110
- if (!(error instanceof Error)) return false;
5111
- return typeof error.code === "string";
5112
- }
5113
-
5114
- // src/storage/manifest.ts
5115
- import { lstat as lstat4 } from "fs/promises";
5116
- function createManifest(input) {
5117
- if (input.workspaceName.length === 0) {
5118
- throw new Error("Workspace name is empty. Pass --name explicitly.");
5119
- }
5120
- const now = (input.now ?? /* @__PURE__ */ new Date()).toISOString();
5121
- const workspaceId = input.workspaceId ?? prefixedUlid("ws");
5122
- const project = {
5123
- ...input.projectName !== void 0 ? { name: input.projectName } : {},
5124
- ...input.projectDescription !== void 0 ? { description: input.projectDescription } : {},
5125
- ...input.repositoryUrl !== void 0 ? { repository_url: input.repositoryUrl } : {}
5126
- };
5127
- const manifest = {
5128
- schema_version: "0.1.0",
5129
- basou_version: "0.1.0",
5130
- workspace: {
5131
- id: workspaceId,
5132
- name: input.workspaceName,
5133
- created_at: now,
5134
- updated_at: now
5135
- },
5136
- project,
5137
- capabilities: {
5138
- enabled: ["core", "claude-code-adapter", "terminal-recording", "git-capability", "approval"]
5139
- },
5140
- approval: {
5141
- required_for: ["destructive_command", "external_send"],
5142
- default_risk_level: "medium"
5143
- },
5144
- adapters: {
5145
- "claude-code": { enabled: true }
5146
- },
5147
- git: { events_log: "ignore" },
5148
- ...input.sourceRoots !== void 0 && input.sourceRoots.length > 0 ? { import: { source_roots: input.sourceRoots } } : {}
5149
- };
5150
- return ManifestSchema.parse(manifest);
5151
- }
5152
- async function writeManifest(paths, manifest, options) {
5153
- const force = options?.force === true;
5154
- const validated = ManifestSchema.parse(manifest);
5155
- if (!force) {
5156
- let existed = false;
5157
- try {
5158
- await lstat4(paths.files.manifest);
5159
- existed = true;
5160
- } catch (error) {
5161
- if (!hasErrorCode5(error) || error.code !== "ENOENT") {
5162
- throw new Error("Failed to inspect existing manifest", { cause: error });
5163
- }
5164
- }
5165
- if (existed) {
5166
- throw new Error("Already initialized. Use --force to overwrite.");
5167
- }
5168
- }
5169
- await writeYamlFile(paths.files.manifest, validated);
5170
- }
5171
- async function readManifest(paths) {
5172
- const raw = await readYamlFile(paths.files.manifest);
5173
- return ManifestSchema.parse(raw);
5562
+ ${block}`;
5174
5563
  }
5175
5564
  function hasErrorCode5(error) {
5176
5565
  if (!(error instanceof Error)) return false;
@@ -5283,7 +5672,7 @@ function hasErrorCode6(error) {
5283
5672
  // src/storage/session-import.ts
5284
5673
  import { mkdir as mkdir5, readFile as readFile10, rm as rm2 } from "fs/promises";
5285
5674
  import { homedir as homedir2 } from "os";
5286
- import { join as join17 } from "path";
5675
+ import { join as join19 } from "path";
5287
5676
  async function importSessionFromJson(paths, manifest, payload, options) {
5288
5677
  if (options.taskIdOverride !== void 0 && !TaskIdSchema.safeParse(options.taskIdOverride).success) {
5289
5678
  throw new Error(`Invalid task_id: ${options.taskIdOverride}`);
@@ -5308,7 +5697,7 @@ async function importSessionFromJson(paths, manifest, payload, options) {
5308
5697
  pathSanitizeReport
5309
5698
  };
5310
5699
  }
5311
- const sessionDir = join17(paths.sessions, newSessionId);
5700
+ const sessionDir = join19(paths.sessions, newSessionId);
5312
5701
  try {
5313
5702
  await mkdir5(sessionDir, { recursive: true });
5314
5703
  } catch (error) {
@@ -5322,7 +5711,7 @@ async function importSessionFromJson(paths, manifest, payload, options) {
5322
5711
  throw error;
5323
5712
  }
5324
5713
  try {
5325
- const sessionYamlPath = join17(sessionDir, "session.yaml");
5714
+ const sessionYamlPath = join19(sessionDir, "session.yaml");
5326
5715
  await linkYamlFile(sessionYamlPath, withIntegrity(sessionRecord, chainResult));
5327
5716
  } catch (error) {
5328
5717
  await rm2(sessionDir, { recursive: true, force: true }).catch(() => void 0);
@@ -5490,7 +5879,7 @@ function reuseDerivedIds(priorDerived, freshDerived, sessionId) {
5490
5879
  async function reimportPreservingId(paths, manifest, priorSessionId, freshPayload, options = {}) {
5491
5880
  const sessionId = priorSessionId;
5492
5881
  const importSource = freshPayload.session.source.kind;
5493
- const sessionDir = join17(paths.sessions, priorSessionId);
5882
+ const sessionDir = join19(paths.sessions, priorSessionId);
5494
5883
  const lock = options.dryRun === true ? null : await acquireLock(paths, "session", priorSessionId);
5495
5884
  try {
5496
5885
  const priorVerdict = await verifyEventsChain(paths, priorSessionId);
@@ -5532,7 +5921,7 @@ async function reimportPreservingId(paths, manifest, priorSessionId, freshPayloa
5532
5921
  };
5533
5922
  const updatedRecord = { schema_version: "0.1.0", session: preservedInner };
5534
5923
  if (options.dryRun !== true) {
5535
- const eventsPath = join17(sessionDir, "events.jsonl");
5924
+ const eventsPath = join19(sessionDir, "events.jsonl");
5536
5925
  let priorEventsRaw = null;
5537
5926
  try {
5538
5927
  priorEventsRaw = await readFile10(eventsPath);
@@ -5544,7 +5933,7 @@ async function reimportPreservingId(paths, manifest, priorSessionId, freshPayloa
5544
5933
  const chainResult = await writeEventsBulk(sessionDir, mergedEvents, { chain: true });
5545
5934
  try {
5546
5935
  await overwriteYamlFile(
5547
- join17(sessionDir, "session.yaml"),
5936
+ join19(sessionDir, "session.yaml"),
5548
5937
  withIntegrity(updatedRecord, chainResult)
5549
5938
  );
5550
5939
  } catch (error) {
@@ -5568,7 +5957,7 @@ async function reimportPreservingId(paths, manifest, priorSessionId, freshPayloa
5568
5957
  }
5569
5958
  }
5570
5959
  async function rechainSessionInPlace(paths, sessionId, options = {}) {
5571
- const sessionDir = join17(paths.sessions, sessionId);
5960
+ const sessionDir = join19(paths.sessions, sessionId);
5572
5961
  let lock;
5573
5962
  try {
5574
5963
  lock = await acquireLock(paths, "session", sessionId);
@@ -5601,7 +5990,7 @@ async function rechainSessionInPlace(paths, sessionId, options = {}) {
5601
5990
  if (verdict.status !== "unchained") {
5602
5991
  return { status: "skipped", reason: "tampered" };
5603
5992
  }
5604
- const eventsPath = join17(sessionDir, "events.jsonl");
5993
+ const eventsPath = join19(sessionDir, "events.jsonl");
5605
5994
  let priorRaw;
5606
5995
  try {
5607
5996
  priorRaw = await readFile10(eventsPath);
@@ -5649,7 +6038,7 @@ async function rechainSessionInPlace(paths, sessionId, options = {}) {
5649
6038
  }
5650
6039
  try {
5651
6040
  await overwriteYamlFile(
5652
- join17(sessionDir, "session.yaml"),
6041
+ join19(sessionDir, "session.yaml"),
5653
6042
  withIntegrity(record, { headHash: chainResult.headHash, count: chainResult.count })
5654
6043
  );
5655
6044
  } catch (error) {
@@ -5764,9 +6153,11 @@ export {
5764
6153
  reimportPreservingId,
5765
6154
  renderDecisions,
5766
6155
  renderHandoff,
6156
+ renderOrientation,
5767
6157
  renderReport,
5768
6158
  renderWithMarkers,
5769
6159
  replayEvents,
6160
+ resolveBasouRepositoryRoot,
5770
6161
  resolveClaudeCodeCommand,
5771
6162
  resolveRepositoryRoot,
5772
6163
  resolveSessionId,
@@ -5778,6 +6169,7 @@ export {
5778
6169
  serializeJsonSchema,
5779
6170
  sessionWorkStatsFromEvents,
5780
6171
  summarizeAdapterOutput,
6172
+ summarizeOrientation,
5781
6173
  tryRemoteUrl,
5782
6174
  ulid,
5783
6175
  updateTaskStatusWithEvent,