@basou/core 0.19.0 → 0.20.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.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/adapters/claude-code/claude-code-adapter.ts","../src/ids/ulid.ts","../src/stats/active-time.ts","../src/adapters/session-label.ts","../src/adapters/claude-code/transcript-importer.ts","../src/adapters/codex/rollout-importer.ts","../src/approval/approval-store.ts","../src/lib/error-codes.ts","../src/schemas/approval.schema.ts","../src/schemas/shared.schema.ts","../src/storage/yaml-store.ts","../src/storage/atomic.ts","../src/decisions/decisions-renderer.ts","../src/events/event-replay.ts","../src/schemas/event.schema.ts","../src/storage/sessions.ts","../src/events/chained-append.ts","../src/storage/lockfile.ts","../src/events/chain.ts","../src/schemas/session.schema.ts","../src/events/event-writer.ts","../src/events/verify.ts","../src/git/snapshot.ts","../src/storage/status.ts","../src/schemas/status.schema.ts","../src/git/diff.ts","../src/handoff/handoff-renderer.ts","../src/lib/recency.ts","../src/storage/tasks.ts","../src/schemas/task.schema.ts","../src/storage/ad-hoc-session.ts","../src/lib/path-sanitizer.ts","../src/storage/task-index.ts","../src/schemas/task-index.schema.ts","../src/lib/duration.ts","../src/lib/format-duration.ts","../src/lib/id-resolver.ts","../src/lib/source-root-scope.ts","../src/orientation/orientation-renderer.ts","../src/storage/manifest.ts","../src/schemas/manifest.schema.ts","../src/project/relative-path.ts","../src/project/archive.ts","../src/project/gitignore-plan.ts","../src/project/preset.ts","../src/project/rename.ts","../src/project/roster.ts","../src/project/symlinks.ts","../src/project/wiring.ts","../src/project/workspace-view.ts","../src/report/report-renderer.ts","../src/stats/work-stats.ts","../src/review/review-gaps.ts","../src/runtime/child-process-runner.ts","../src/schemas/json-schema.ts","../src/schemas/session-import.schema.ts","../src/storage/basou-dir.ts","../src/storage/gitignore.ts","../src/storage/markdown-store.ts","../src/storage/session-import.ts","../src/index.ts"],"sourcesContent":["import { spawn } from \"node:child_process\";\n\n/**\n * Static metadata identifying the claude-code adapter as the session source.\n * Consumed by the CLI orchestration when populating `session.yaml.source`\n * and event `source` fields. The literal `kind` is part of the wire format\n * defined by the session schema; do not change without coordinated schema\n * migration.\n */\nexport const claudeCodeAdapterMetadata = {\n kind: \"claude-code-adapter\",\n version: \"0.1.0\",\n} as const;\n\n/**\n * Lookup predicate used by {@link resolveClaudeCodeCommand} to decide\n * whether a candidate executable is reachable on PATH. Exposed as a\n * parameter so tests can substitute a deterministic mock; production\n * callers should omit it and rely on the default `which`-based lookup.\n */\nexport type CommandLookup = (command: string) => Promise<boolean>;\n\n/**\n * Resolve the Claude Code CLI executable name. Tries `claude-code` first\n * and falls back to `claude`; the first candidate found on PATH wins.\n *\n * Throws a fixed-message Error when neither candidate is reachable, so\n * callers can present a single user-facing prompt to install the CLI.\n *\n * @throws Error(\"Claude Code CLI not found in PATH. Install claude-code (or claude) first.\")\n */\nexport async function resolveClaudeCodeCommand(\n lookup: CommandLookup = isOnPath,\n): Promise<{ command: string }> {\n for (const candidate of [\"claude-code\", \"claude\"]) {\n if (await lookup(candidate)) return { command: candidate };\n }\n throw new Error(\"Claude Code CLI not found in PATH. Install claude-code (or claude) first.\");\n}\n\n/**\n * Default {@link CommandLookup} backed by `which` (POSIX) — the spawn\n * succeeds with exit code 0 iff the candidate is on PATH. Windows fallback\n * is intentionally not implemented in v0.1; call sites that target Windows\n * supply their own lookup.\n */\nasync function isOnPath(command: string): Promise<boolean> {\n return new Promise((resolve) => {\n const child = spawn(\"which\", [command], { stdio: \"ignore\" });\n child.on(\"error\", () => resolve(false));\n child.on(\"exit\", (code) => resolve(code === 0));\n });\n}\n\n/**\n * Stub for the future `adapter_output` summary generator.\n *\n * The current release keeps `capture: \"none\"` and intentionally does\n * not emit `adapter_output` events, so this hook has no production\n * callers yet. The signature is committed so a later release can\n * implement raw_ref generation without retrofitting the adapter\n * scaffold.\n *\n * @throws Error - always; not implemented in this release.\n */\nexport function summarizeAdapterOutput(_stream: \"stdout\" | \"stderr\", _raw: string): string {\n throw new Error(\"adapter_output summary is not implemented in this release\");\n}\n","import { isValid as isValidUlid, monotonicFactory } from \"ulid\";\n\n/**\n * Allowed ID type prefixes for Basou entities.\n *\n * Frozen at runtime so that mutating the exported array cannot diverge from\n * the validation set used internally. The single source of truth for both\n * the `IdPrefix` type and runtime prefix checks.\n */\nexport const ID_PREFIXES = Object.freeze([\"ws\", \"task\", \"ses\", \"evt\", \"appr\", \"decision\"] as const);\n\n/**\n * Type prefix used for Basou entity IDs.\n * Format: `<prefix>_<26-char ULID>`, e.g. `ws_01HXABCDEF1234567890ABCDEF`.\n */\nexport type IdPrefix = (typeof ID_PREFIXES)[number];\n\n/**\n * A Basou entity ID as a template literal type.\n *\n * `PrefixedId<\"ses\">` narrows to ``ses_${string}`` so a session schema can\n * preserve the prefix in its inferred type beyond runtime validation.\n */\nexport type PrefixedId<P extends IdPrefix = IdPrefix> = `${P}_${string}`;\n\nconst PREFIX_SET = new Set<string>(ID_PREFIXES);\n\n// ULID body shape: 26 chars, first char 0-7 (48-bit timestamp / 5-bit Crockford\n// symbols), remaining 25 chars use Crockford alphabet excluding I, L, O, U.\n// Enforced locally because npm `ulid`'s `isValid` does not reject leading 8 or 9.\nconst ULID_BODY_REGEX = /^[0-7][0-9A-HJKMNP-TV-Z]{25}$/;\n\n// Module-scope monotonic factory. Created once at module load. Pure function\n// so it does not violate `sideEffects: false` of the surrounding package.\nconst monotonic = monotonicFactory();\n\n/**\n * Generate a Crockford Base32 ULID.\n *\n * The result is a 26-character, lexicographically time-sortable identifier.\n * Multiple calls within the same millisecond are strictly increasing for the\n * lifetime of the current process.\n *\n * NOTE: `seedTime` is forwarded to the underlying monotonic factory and is\n * NOT a deterministic seed: repeated calls with the same `seedTime` still\n * return strictly increasing values, because the factory increments its\n * internal counter on each call.\n *\n * @param seedTime Optional millisecond timestamp passed to the monotonic\n * factory. Useful for ordered generation in tests; not deterministic.\n */\nexport function ulid(seedTime?: number): string {\n return monotonic(seedTime);\n}\n\n/**\n * Generate a prefixed Basou ID, e.g. `ses_01HXABCDEF1234567890ABCDEF`.\n *\n * The return type preserves the prefix as a template literal type so that\n * downstream zod schemas can narrow an `IdPrefix` parameter through the API.\n *\n * Throws if `prefix` is not one of {@link ID_PREFIXES}. The runtime guard\n * defends against JavaScript callers and casted TypeScript that bypass the\n * compile-time `IdPrefix` constraint.\n */\nexport function prefixedUlid<P extends IdPrefix>(prefix: P): PrefixedId<P> {\n if (!PREFIX_SET.has(prefix)) {\n throw new Error(`Unknown ID prefix: ${prefix}`);\n }\n return `${prefix}_${ulid()}` as PrefixedId<P>;\n}\n\n/**\n * Check whether the given string is a valid prefixed Basou ID.\n *\n * Returns true only if the string has shape `<prefix>_<ULID>` where prefix is\n * one of {@link ID_PREFIXES} and the trailing 26 characters form a valid\n * Crockford Base32 ULID. Validation combines a strict shape regex (to enforce\n * the 0-7 leading char and the I/L/O/U exclusion) with the npm `ulid`\n * library's `isValid` for forward compatibility.\n *\n * NOTE: This validates the prefix is known. Schemas that require a specific\n * prefix (e.g. only `ses_*` for a session ID) must add their own narrowing.\n */\nexport function isValidPrefixedId(value: string): boolean {\n const idx = value.indexOf(\"_\");\n if (idx <= 0) return false;\n const prefix = value.slice(0, idx);\n const ulidPart = value.slice(idx + 1);\n if (!PREFIX_SET.has(prefix)) return false;\n if (!ULID_BODY_REGEX.test(ulidPart)) return false;\n return isValidUlid(ulidPart);\n}\n","/**\n * Gap longer than this between two consecutive engagement timestamps is treated\n * as idle and not credited as active time. A deliberately coarse heuristic: a\n * focus / billable-attention proxy, NOT a measure of model compute. 5 minutes.\n */\nexport const ACTIVE_GAP_CAP_MS = 5 * 60 * 1000;\n\n/** A wall-clock range, in epoch milliseconds, expressed as `[start, end]`. */\nexport type IntervalMs = [start: number, end: number];\n\n/** A wall-clock range expressed as ISO-8601 strings (for persistence). */\nexport type IsoInterval = { start: string; end: string };\n\n/**\n * Identifier stored in `metrics.active_time_method` for active time derived\n * from genuine engagement timestamps (conversation turns plus action events).\n * Bump this string if the derivation method changes, so stored numbers remain\n * interpretable.\n */\nexport const ENGAGED_TURNS_METHOD = \"engaged-turns\";\n\n/**\n * Identifier stored in `metrics.active_time_method` for active time that uses a\n * source's explicit per-turn intervals (e.g. Codex `task_started` /\n * `task_complete`) for the in-turn duration, with the gap-capped engagement\n * series only bridging between turns. More faithful than {@link\n * ENGAGED_TURNS_METHOD}: in-turn time is the log's real wall-clock span, not a\n * gap-capped approximation, and a session's final turn is credited. The active\n * semantics are unchanged — still human-engaged time with idle gaps excluded —\n * only the in-turn precision improves. Bump this string if the derivation\n * method changes, so stored numbers stay interpretable.\n */\nexport const TURN_INTERVALS_METHOD = \"turn-intervals\";\n\n/**\n * Build active intervals from a list of engagement timestamps (epoch ms).\n *\n * Each consecutive pair credits the range `[t_prev, t_prev + min(gap, capMs)]`:\n * activity at a timestamp is assumed to continue until the next timestamp, or\n * for at most `capMs` if the next is further away (the remainder is idle).\n * Adjacent or overlapping ranges are merged into runs. The summed duration of\n * the merged intervals equals `sum(min(gap, capMs))`, so this both reproduces a\n * gap-capped active-time scalar and yields the real ranges needed for\n * cross-session union.\n *\n * Non-finite timestamps are skipped (the single invalid-timestamp policy) and\n * the input is sorted internally, so callers may pass timestamps in any order.\n */\nexport function activeTimeFromTimestamps(\n timestampsMs: ReadonlyArray<number>,\n capMs: number,\n): { ms: number; intervals: IntervalMs[] } {\n const sorted = timestampsMs.filter((t) => Number.isFinite(t)).sort((a, b) => a - b);\n const raw: IntervalMs[] = [];\n for (let i = 1; i < sorted.length; i++) {\n const prev = sorted[i - 1];\n const curr = sorted[i];\n if (prev === undefined || curr === undefined) continue;\n const gap = curr - prev;\n if (gap <= 0) continue;\n raw.push([prev, prev + Math.min(gap, capMs)]);\n }\n const intervals = mergeIntervals(raw);\n return { ms: sumDurations(intervals), intervals };\n}\n\n/** Merge a set of (possibly unsorted / overlapping) intervals into disjoint runs. */\nexport function mergeIntervals(intervals: ReadonlyArray<IntervalMs>): IntervalMs[] {\n const sorted = [...intervals].sort((a, b) => a[0] - b[0]);\n const merged: IntervalMs[] = [];\n for (const [start, end] of sorted) {\n const last = merged[merged.length - 1];\n // `start <= last end` joins adjacent runs (an interval ending exactly where\n // the next begins) as well as genuine overlaps.\n if (last !== undefined && start <= last[1]) {\n if (end > last[1]) last[1] = end;\n } else {\n merged.push([start, end]);\n }\n }\n return merged;\n}\n\n/**\n * De-duplicate active time across many sessions: merge all their intervals and\n * return the union duration (so concurrent sessions do not double-count human\n * wall-clock) together with the merged ranges.\n */\nexport function unionDurationMs(intervals: ReadonlyArray<IntervalMs>): {\n ms: number;\n merged: IntervalMs[];\n} {\n const merged = mergeIntervals(intervals);\n return { ms: sumDurations(merged), merged };\n}\n\n/** Convert epoch-ms intervals to ISO ranges for persistence. */\nexport function intervalsMsToIso(intervals: ReadonlyArray<IntervalMs>): IsoInterval[] {\n return intervals.map(([start, end]) => ({\n start: new Date(start).toISOString(),\n end: new Date(end).toISOString(),\n }));\n}\n\n/** Parse stored ISO ranges back to epoch-ms intervals, skipping unparseable ones. */\nexport function intervalsIsoToMs(intervals: ReadonlyArray<IsoInterval>): IntervalMs[] {\n const out: IntervalMs[] = [];\n for (const { start, end } of intervals) {\n const s = Date.parse(start);\n const e = Date.parse(end);\n if (Number.isFinite(s) && Number.isFinite(e) && e >= s) out.push([s, e]);\n }\n return out;\n}\n\nfunction sumDurations(intervals: ReadonlyArray<IntervalMs>): number {\n let total = 0;\n for (const [start, end] of intervals) total += end - start;\n return total;\n}\n","/**\n * The date portion of an imported session's human-readable label.\n *\n * A same-day session reads as a single date (`2026-06-22`). A session that\n * spans a day boundary — e.g. a long evening-into-morning run — reads as a\n * range (`2026-06-21..2026-06-22`) so the most recent day stays visible instead\n * of the work being buried under the (older) start date when scanning\n * `basou session list`. The label is sorted/listed by `started_at` elsewhere;\n * this only governs the displayed text.\n *\n * Dates are the raw ISO date prefix with no timezone normalization, matching\n * how `started_at` / `ended_at` are stored. `ended_at` is never earlier than\n * `started_at` by instant, but with mixed UTC offsets the earlier instant can\n * still carry the later calendar date, so the two dates are ordered\n * lexicographically (= chronologically for `YYYY-MM-DD`) and the range always\n * reads earliest..latest.\n */\nexport function sessionLabelDateSpan(startIso: string, endIso: string): string {\n const a = startIso.slice(0, 10);\n const b = endIso.slice(0, 10);\n if (a === b) return a;\n return a < b ? `${a}..${b}` : `${b}..${a}`;\n}\n","import { type PrefixedId, prefixedUlid } from \"../../ids/ulid.js\";\nimport type { Event } from \"../../schemas/event.schema.js\";\nimport type { Manifest } from \"../../schemas/manifest.schema.js\";\nimport type { SessionImportPayload } from \"../../schemas/session-import.schema.js\";\nimport {\n ACTIVE_GAP_CAP_MS,\n activeTimeFromTimestamps,\n ENGAGED_TURNS_METHOD,\n intervalsMsToIso,\n} from \"../../stats/active-time.js\";\nimport { sessionLabelDateSpan } from \"../session-label.js\";\n\n/**\n * The `source` string stamped on every event derived from a Claude Code\n * native transcript, and the matching session `source.kind`.\n */\nexport const CLAUDE_IMPORT_SOURCE = \"claude-code-import\";\n\n/**\n * One parsed line of a Claude Code native transcript\n * (`~/.claude/projects/<encoded-cwd>/<uuid>.jsonl`). The shape is the\n * vendor's internal message log, not Basou's event schema, so every field\n * is read defensively — unknown record types and missing fields are skipped\n * rather than rejected (transcripts are an undocumented format that may gain\n * fields between Claude Code releases).\n */\nexport type ClaudeTranscriptRecord = Record<string, unknown>;\n\n/** Options for {@link claudeTranscriptToImportPayload}. */\nexport type ClaudeTranscriptToPayloadOptions = {\n /** Workspace id of the target Basou workspace (from its manifest). */\n workspaceId: Manifest[\"workspace\"][\"id\"];\n /**\n * Claude Code session id (transcript filename / `sessionId`). Stored as\n * `session.source.external_id` so re-imports can be deduplicated. Falls\n * back to the `sessionId` read from the records when omitted.\n */\n externalId?: string;\n /**\n * Byte size of the source transcript that produced `records`, stored as\n * `session.source.source_size_bytes` so a later import can detect growth and\n * re-import the session. The caller passes the size of the buffer it actually\n * read (an immutable snapshot of the parsed bytes), so the stored size always\n * matches the imported content. Omitted => the field is not recorded.\n */\n sourceSizeBytes?: number;\n};\n\n/**\n * Transform a Claude Code native transcript into a Basou\n * {@link SessionImportPayload}, ready to hand to `importSessionFromJson`.\n *\n * This is a pure function: no disk or environment access. It DERIVES Basou's\n * provenance-level events from the transcript's message-level records, rather\n * than mapping one-to-one:\n *\n * - `session_started` / `session_ended` from the first / last timestamped record.\n * - `command_executed` from each `Bash` tool use, recorded as `bash -c \"<cmd>\"`\n * (the transcript carries the shell line, not a parsed argv).\n * - `file_changed` from each `Edit` / `Write` / `NotebookEdit` tool use.\n * - `decision_recorded` from each `AskUserQuestion` tool use: one decision per\n * question, titled `<question> -> <chosen answer>`. The chosen answer is read\n * from the paired result record's structured `toolUseResult.answers` map; a\n * question with no recorded string answer is skipped.\n *\n * Exit codes and per-command durations are not present in the transcript, so\n * `command_executed.exit_code` is `null` and `duration_ms` is `0`.\n *\n * Returns `null` when the transcript has no timestamped records, or no\n * observable command / file / decision action — such sessions carry no\n * provenance worth importing and are skipped by the caller.\n *\n * Event `id` / `session_id` are placeholders; `importSessionFromJson` mints\n * fresh ids on the way in. They are valid-by-construction so the payload\n * still passes `SessionImportPayloadSchema` validation upstream.\n */\nexport function claudeTranscriptToImportPayload(\n records: ReadonlyArray<ClaudeTranscriptRecord>,\n options: ClaudeTranscriptToPayloadOptions,\n): SessionImportPayload | null {\n const placeholderSessionId = prefixedUlid(\"ses\");\n // AskUserQuestion answers live on the *result* record, which arrives after\n // the originating tool_use; pre-index them so decisions can be derived at the\n // tool_use site in the single forward pass below.\n const askAnswers = indexAskAnswers(records);\n const derived: Event[] = [];\n const relatedFiles = new Set<string>();\n // Real transcripts are NOT strictly ordered by timestamp on disk\n // (sidechains and async writes interleave), so file order cannot be\n // trusted. Track the earliest / latest timestamp explicitly, and order the\n // derived events by occurred_at below.\n let minTs: string | undefined;\n let maxTs: string | undefined;\n let workingDir: string | undefined;\n let claudeSessionId: string | undefined;\n // Model-usage rollup: the transcript carries per-assistant-message token\n // usage; sum it into a session total (see metrics on the payload below).\n let outputTokens = 0;\n let inputTokens = 0;\n let cachedInputTokens = 0;\n // A single assistant message is split across multiple records (thinking /\n // text / tool_use), each carrying the SAME message.id and the SAME usage.\n // Count usage once per message.id to avoid multiplying the token totals.\n const seenMessageIds = new Set<string>();\n // Genuine engagement timestamps for the billing-oriented active-time metric:\n // real human prompts and assistant messages (action tool uses live inside\n // assistant messages, so they are subsumed). Sub-agent sidechains and\n // tool-result / meta records are excluded so autonomous loops and noise do\n // not inflate billable time. Assistant messages are counted once per\n // message.id, matching the token dedup above.\n const engagementTsMs: number[] = [];\n const seenEngagementMessageIds = new Set<string>();\n\n for (const record of records) {\n const ts = readString(record.timestamp);\n if (ts === undefined) continue;\n if (minTs === undefined || Date.parse(ts) < Date.parse(minTs)) minTs = ts;\n if (maxTs === undefined || Date.parse(ts) > Date.parse(maxTs)) maxTs = ts;\n if (workingDir === undefined) workingDir = readString(record.cwd);\n if (claudeSessionId === undefined) claudeSessionId = readString(record.sessionId);\n\n if (record.isSidechain !== true) {\n const tsMs = Date.parse(ts);\n if (Number.isFinite(tsMs)) {\n const recType = readString(record.type);\n if (recType === \"user\") {\n if (isHumanUserMessage(record)) engagementTsMs.push(tsMs);\n } else if (recType === \"assistant\") {\n const msg = isObject(record.message) ? record.message : undefined;\n const mid = msg !== undefined ? readString(msg.id) : undefined;\n if (mid === undefined || !seenEngagementMessageIds.has(mid)) {\n if (mid !== undefined) seenEngagementMessageIds.add(mid);\n engagementTsMs.push(tsMs);\n }\n }\n }\n }\n\n if (readString(record.type) !== \"assistant\") continue;\n\n const message = isObject(record.message) ? record.message : undefined;\n const usage = message !== undefined && isObject(message.usage) ? message.usage : undefined;\n if (usage !== undefined) {\n // Dedup by message.id; records without an id are counted individually.\n const messageId = message !== undefined ? readString(message.id) : undefined;\n const alreadyCounted = messageId !== undefined && seenMessageIds.has(messageId);\n if (!alreadyCounted) {\n if (messageId !== undefined) seenMessageIds.add(messageId);\n outputTokens += readNonNegInt(usage.output_tokens);\n inputTokens += readNonNegInt(usage.input_tokens);\n cachedInputTokens += readNonNegInt(usage.cache_read_input_tokens);\n }\n }\n\n const cwd = readString(record.cwd) ?? workingDir ?? \".\";\n for (const item of toolUses(record)) {\n const name = readString(item.name);\n const input = isObject(item.input) ? item.input : undefined;\n if (input === undefined) continue;\n\n if (name === \"Bash\") {\n const command = readString(input.command);\n if (command !== undefined) {\n derived.push(commandExecutedEvent(ts, placeholderSessionId, command, cwd));\n }\n continue;\n }\n\n if (name === \"AskUserQuestion\") {\n const useId = readString(item.id);\n const answers = useId !== undefined ? askAnswers.get(useId) : undefined;\n if (answers !== undefined) {\n // One decision per question; preserve the order the questions were\n // asked (Object.entries keeps insertion order for string keys, and\n // the stable sort below keeps it among the same-timestamp events).\n for (const [question, answer] of Object.entries(answers)) {\n if (question.length === 0) continue;\n const answerStr = typeof answer === \"string\" && answer.length > 0 ? answer : undefined;\n const title = answerStr !== undefined ? `${question} -> ${answerStr}` : question;\n derived.push(decisionRecordedEvent(ts, placeholderSessionId, title));\n }\n }\n continue;\n }\n\n if (name === \"Edit\" || name === \"Write\" || name === \"NotebookEdit\") {\n const path = readString(input.file_path) ?? readString(input.notebook_path);\n if (path !== undefined) {\n // Edit / NotebookEdit mutate an existing file; Write is treated as a\n // creation. The transcript does not reliably distinguish a Write\n // that overwrites, so \"added\" is the conservative default.\n const changeType = name === \"Write\" ? \"added\" : \"modified\";\n relatedFiles.add(path);\n derived.push(fileChangedEvent(ts, placeholderSessionId, path, changeType));\n }\n }\n }\n }\n\n if (minTs === undefined || maxTs === undefined) return null;\n if (derived.length === 0) return null;\n\n // Order derived events by occurred_at so the assembled stream is\n // non-decreasing — importSessionFromJson rejects out-of-order events.\n // Array.prototype.sort is stable, so same-timestamp events keep their\n // transcript order.\n derived.sort((a, b) => Date.parse(a.occurred_at) - Date.parse(b.occurred_at));\n\n const events: Event[] = [\n sessionStartedEvent(minTs, placeholderSessionId),\n ...derived,\n sessionEndedEvent(maxTs, placeholderSessionId),\n ];\n\n const externalId = options.externalId ?? claudeSessionId;\n // Human-readable label: when + how much, so the session reads as content in\n // `basou session list` / handoff rather than an opaque id. The source id is\n // kept structurally in `source.external_id` (not the label), and paths are\n // deliberately excluded — the label is NOT path-sanitized downstream, so a\n // raw file path here would leak an operator-private prefix.\n const commandCount = derived.reduce((n, e) => (e.type === \"command_executed\" ? n + 1 : n), 0);\n const fileCount = relatedFiles.size;\n const label = `claude-code ${sessionLabelDateSpan(minTs, maxTs)}: ${commandCount} ${commandCount === 1 ? \"command\" : \"commands\"}, ${fileCount} ${fileCount === 1 ? \"file\" : \"files\"}`;\n\n // Engaged-active time from the genuine engagement series (needs >= 2 points\n // to bound any gap); omitted when too sparse so stats falls back to the\n // event-derived measure.\n const active =\n engagementTsMs.length >= 2\n ? activeTimeFromTimestamps(engagementTsMs, ACTIVE_GAP_CAP_MS)\n : undefined;\n\n // Only include fields actually present; omit metrics entirely for a\n // transcript that carried neither token usage nor an engaged-time signal.\n const metricsFields = {\n ...(outputTokens > 0 ? { output_tokens: outputTokens } : {}),\n ...(inputTokens > 0 ? { input_tokens: inputTokens } : {}),\n ...(cachedInputTokens > 0 ? { cached_input_tokens: cachedInputTokens } : {}),\n ...(active !== undefined && active.ms > 0\n ? {\n active_time_ms: active.ms,\n active_intervals: intervalsMsToIso(active.intervals),\n active_gap_cap_ms: ACTIVE_GAP_CAP_MS,\n active_time_method: ENGAGED_TURNS_METHOD,\n }\n : {}),\n };\n const metrics = Object.keys(metricsFields).length > 0 ? metricsFields : undefined;\n\n const payload: SessionImportPayload = {\n schema_version: \"0.1.0\",\n session: {\n label,\n workspace_id: options.workspaceId,\n source: {\n kind: CLAUDE_IMPORT_SOURCE,\n version: \"0.1.0\",\n ...(externalId !== undefined ? { external_id: externalId } : {}),\n ...(options.sourceSizeBytes !== undefined\n ? { source_size_bytes: options.sourceSizeBytes }\n : {}),\n },\n started_at: minTs,\n ended_at: maxTs,\n // Validated against the canonical enum here; importSessionFromJson\n // overwrites it with the literal \"imported\" regardless.\n status: \"imported\",\n working_directory: workingDir ?? \".\",\n invocation: { command: \"claude\", args: [], exit_code: null },\n related_files: [...relatedFiles].sort(),\n summary: null,\n ...(metrics !== undefined ? { metrics } : {}),\n },\n events,\n };\n return payload;\n}\n\n// --- event builders -------------------------------------------------------\n\nfunction baseEvent(\n occurredAt: string,\n sessionId: PrefixedId<\"ses\">,\n): {\n schema_version: \"0.1.0\";\n id: PrefixedId<\"evt\">;\n session_id: PrefixedId<\"ses\">;\n occurred_at: string;\n source: string;\n} {\n return {\n schema_version: \"0.1.0\",\n id: prefixedUlid(\"evt\"),\n session_id: sessionId,\n occurred_at: occurredAt,\n source: CLAUDE_IMPORT_SOURCE,\n };\n}\n\nfunction sessionStartedEvent(occurredAt: string, sessionId: PrefixedId<\"ses\">): Event {\n return { ...baseEvent(occurredAt, sessionId), type: \"session_started\" };\n}\n\nfunction sessionEndedEvent(occurredAt: string, sessionId: PrefixedId<\"ses\">): Event {\n return { ...baseEvent(occurredAt, sessionId), type: \"session_ended\" };\n}\n\nfunction commandExecutedEvent(\n occurredAt: string,\n sessionId: PrefixedId<\"ses\">,\n command: string,\n cwd: string,\n): Event {\n return {\n ...baseEvent(occurredAt, sessionId),\n type: \"command_executed\",\n command: \"bash\",\n args: [\"-c\", command],\n cwd,\n exit_code: null,\n duration_ms: 0,\n };\n}\n\nfunction fileChangedEvent(\n occurredAt: string,\n sessionId: PrefixedId<\"ses\">,\n path: string,\n changeType: \"added\" | \"modified\",\n): Event {\n return {\n ...baseEvent(occurredAt, sessionId),\n type: \"file_changed\",\n path,\n change_type: changeType,\n };\n}\n\nfunction decisionRecordedEvent(\n occurredAt: string,\n sessionId: PrefixedId<\"ses\">,\n title: string,\n): Event {\n return {\n ...baseEvent(occurredAt, sessionId),\n type: \"decision_recorded\",\n decision_id: prefixedUlid(\"decision\"),\n title,\n };\n}\n\n// --- defensive readers ----------------------------------------------------\n\nfunction readString(value: unknown): string | undefined {\n return typeof value === \"string\" && value.length > 0 ? value : undefined;\n}\n\n/** Read a non-negative integer token count, treating anything else as 0. */\nfunction readNonNegInt(value: unknown): number {\n return typeof value === \"number\" && Number.isInteger(value) && value >= 0 ? value : 0;\n}\n\nfunction isObject(value: unknown): value is Record<string, unknown> {\n return typeof value === \"object\" && value !== null && !Array.isArray(value);\n}\n\n/**\n * A `user` record is a genuine human prompt when its message content is a\n * non-empty string, or an array containing at least one non-`tool_result`\n * block (e.g. text / image). A record whose content is only `tool_result`\n * blocks is the assistant's tool-feedback loop, not human input, and is\n * excluded from the engagement series.\n */\nfunction isHumanUserMessage(record: ClaudeTranscriptRecord): boolean {\n const message = isObject(record.message) ? record.message : undefined;\n if (message === undefined) return false;\n const content = message.content;\n if (typeof content === \"string\") return content.length > 0;\n if (Array.isArray(content)) {\n return content.some((block) => {\n if (!isObject(block)) return false;\n const type = readString(block.type);\n return type !== undefined && type !== \"tool_result\";\n });\n }\n return false;\n}\n\n/**\n * Extract the `tool_use` items from an assistant record's\n * `message.content[]`. Returns an empty array for any record that does not\n * match the expected nesting, so callers can iterate unconditionally.\n */\nfunction toolUses(record: ClaudeTranscriptRecord): Array<Record<string, unknown>> {\n const message = isObject(record.message) ? record.message : undefined;\n const content = message !== undefined && Array.isArray(message.content) ? message.content : [];\n const result: Array<Record<string, unknown>> = [];\n for (const item of content) {\n if (isObject(item) && readString(item.type) === \"tool_use\") {\n result.push(item);\n }\n }\n return result;\n}\n\n/**\n * Index the structured answers of every `AskUserQuestion` tool use by its\n * tool_use id. The chosen answers live on the *result* record's\n * `toolUseResult.answers` — a `{ \"<question>\": \"<chosen answer>\" }` map — which\n * is only present on AskUserQuestion results, so its presence is the\n * discriminator. The result record carries the originating tool_use id inside\n * its `message.content[].tool_use_id`.\n */\nfunction indexAskAnswers(\n records: ReadonlyArray<ClaudeTranscriptRecord>,\n): Map<string, Record<string, unknown>> {\n const byId = new Map<string, Record<string, unknown>>();\n for (const record of records) {\n const result = record.toolUseResult;\n if (!isObject(result)) continue;\n const answers = result.answers;\n if (!isObject(answers)) continue;\n const message = isObject(record.message) ? record.message : undefined;\n const content = message !== undefined && Array.isArray(message.content) ? message.content : [];\n for (const item of content) {\n if (isObject(item) && readString(item.type) === \"tool_result\") {\n const id = readString(item.tool_use_id);\n if (id !== undefined) byId.set(id, answers);\n }\n }\n }\n return byId;\n}\n","import { type PrefixedId, prefixedUlid } from \"../../ids/ulid.js\";\nimport type { Event } from \"../../schemas/event.schema.js\";\nimport type { Manifest } from \"../../schemas/manifest.schema.js\";\nimport type { SessionImportPayload } from \"../../schemas/session-import.schema.js\";\nimport {\n ACTIVE_GAP_CAP_MS,\n activeTimeFromTimestamps,\n ENGAGED_TURNS_METHOD,\n type IntervalMs,\n intervalsMsToIso,\n TURN_INTERVALS_METHOD,\n unionDurationMs,\n} from \"../../stats/active-time.js\";\nimport { sessionLabelDateSpan } from \"../session-label.js\";\n\n/**\n * The `source` string stamped on every event derived from an OpenAI Codex\n * native rollout log, and the matching session `source.kind`.\n */\nexport const CODEX_IMPORT_SOURCE = \"codex-import\";\n\n/**\n * One parsed line of a Codex rollout log\n * (`~/.codex/sessions/<YYYY>/<MM>/<DD>/rollout-*.jsonl`). Each line is an\n * envelope `{ type, timestamp, payload }` where `payload` shape depends on\n * `type`. As with the Claude importer the format is the vendor's internal\n * log, not Basou's schema, so every field is read defensively — unknown\n * record / payload types and missing fields are skipped rather than rejected.\n */\nexport type CodexRolloutRecord = Record<string, unknown>;\n\n/** Options for {@link codexRolloutToImportPayload}. */\nexport type CodexRolloutToPayloadOptions = {\n /** Workspace id of the target Basou workspace (from its manifest). */\n workspaceId: Manifest[\"workspace\"][\"id\"];\n /**\n * Codex session id (`session_meta.payload.id`). Stored as\n * `session.source.external_id` so re-imports can be deduplicated. Falls back\n * to the id read from the rollout's `session_meta` record when omitted.\n */\n externalId?: string;\n /**\n * Byte size of the source rollout that produced `records`, stored as\n * `session.source.source_size_bytes` so a later import can detect growth and\n * re-import the session. The caller passes the size of the buffer it actually\n * read (an immutable snapshot of the parsed bytes), so the stored size always\n * matches the imported content. Omitted => the field is not recorded.\n */\n sourceSizeBytes?: number;\n};\n\n/**\n * Transform a Codex native rollout log into a Basou {@link SessionImportPayload},\n * ready to hand to `importSessionFromJson`.\n *\n * This is a pure function: no disk or environment access. It DERIVES Basou's\n * provenance-level events from the rollout's message-level records:\n *\n * - `session_started` / `session_ended` from the first / last timestamped record.\n * - `command_executed` from each `exec_command` function call, recorded as\n * `bash -c \"<cmd>\"`. The shell line and working directory come from the\n * call's JSON `arguments` (`{ cmd, workdir }`); the exit code and duration\n * are parsed from the paired `function_call_output` (matched by `call_id`),\n * whose text carries `Process exited with code N` and `Wall time: X seconds`.\n *\n * Per-session `metrics` are also derived: token totals from the cumulative\n * `token_count` events; active time from the real `task_started` ->\n * `task_complete` turn spans (in-turn, uncapped) unioned with the gap-capped\n * engagement series (between-turn bridging), labeled `turn-intervals`; and\n * `machine_active_time_ms` from the summed `task_complete.duration_ms` (model\n * compute time, a subset of active time).\n *\n * Unlike the Claude importer this derives no `file_changed`: Codex has no\n * dedicated edit tool and applies edits inside `exec_command` (e.g.\n * `apply_patch`), so there is no clean file-change signal to map. Decisions\n * and approvals are likewise not derivable — Codex records an `approval_policy`\n * (a policy, not a per-action approval) and has no structured question/answer\n * record. Both are deferred.\n *\n * Returns `null` when the rollout has no timestamped records or no observable\n * `exec_command` — such sessions carry no provenance worth importing and are\n * skipped by the caller.\n *\n * Event `id` / `session_id` are placeholders; `importSessionFromJson` mints\n * fresh ids on the way in. They are valid-by-construction so the payload still\n * passes `SessionImportPayloadSchema` validation upstream.\n */\nexport function codexRolloutToImportPayload(\n records: ReadonlyArray<CodexRolloutRecord>,\n options: CodexRolloutToPayloadOptions,\n): SessionImportPayload | null {\n const placeholderSessionId = prefixedUlid(\"ses\");\n // A command's exit code and duration live on its `function_call_output`,\n // which arrives after the originating `function_call`; pre-index outputs by\n // call_id so commands can be completed in the single forward pass below.\n const outputsByCallId = indexOutputs(records);\n // A turn's start lives on its `task_started`; pair it with the matching\n // `task_complete` by turn_id so each turn yields a real wall-clock interval.\n // Pre-indexed (rather than matched inline) so it is robust to record order.\n const turnStartMsByTurnId = indexTaskStarts(records);\n const derived: Event[] = [];\n // Real rollouts are written in arrival order, but track the earliest /\n // latest timestamp explicitly (rather than trusting first / last line) and\n // order the derived events by occurred_at below, mirroring the Claude path.\n let minTs: string | undefined;\n let maxTs: string | undefined;\n let workingDir: string | undefined;\n let codexSessionId: string | undefined;\n // Codex emits cumulative token_count events; the last one's\n // total_token_usage is the session total (see metrics on the payload below).\n let lastTokenTotals: Record<string, unknown> | undefined;\n // Genuine engagement timestamps for the billing-oriented active-time metric:\n // conversation turns (user / agent messages and task boundaries) plus the\n // exec_command actions. Token-count heartbeats, reasoning, web-search and\n // tool-output records are excluded so they cannot inflate billable time.\n // These bridge BETWEEN turns (gap-capped); within a turn the explicit\n // task interval below supersedes them on merge.\n const engagementTsMs: number[] = [];\n // Real per-turn wall-clock spans (`task_started` -> `task_complete`). Used\n // for the in-turn portion of active time (uncapped, and crediting the final\n // turn), unioned with the gap-capped engagement series above.\n // One per (deduped) `task_complete`: the turn's wall-clock interval and its\n // reported model-compute duration. Resolved into active time + machine time\n // after the loop, once `minTs` is known (so a reconstructed start can be\n // clamped to the session floor). `durationMs` is 0 when the rollout records\n // none (older format).\n const completions: Array<{ interval: IntervalMs | undefined; durationMs: number }> = [];\n // De-dup completions by turn_id: a duplicate `task_complete` for the same\n // turn would double-count machine time (breaking machine <= active) while the\n // union-merged interval counts the turn once. First completion per turn wins.\n const completedTurnIds = new Set<string>();\n\n for (const record of records) {\n const ts = readString(record.timestamp);\n if (ts === undefined) continue;\n if (minTs === undefined || Date.parse(ts) < Date.parse(minTs)) minTs = ts;\n if (maxTs === undefined || Date.parse(ts) > Date.parse(maxTs)) maxTs = ts;\n\n const payload = isObject(record.payload) ? record.payload : undefined;\n if (payload === undefined) continue;\n\n if (readString(record.type) === \"session_meta\") {\n // The session-level cwd and id are the most reliable working directory\n // and dedup key; take the first occurrence and keep it.\n if (workingDir === undefined) workingDir = readString(payload.cwd);\n if (codexSessionId === undefined) codexSessionId = readString(payload.id);\n continue;\n }\n\n if (readString(record.type) === \"event_msg\" && readString(payload.type) === \"token_count\") {\n const info = isObject(payload.info) ? payload.info : undefined;\n const totals =\n info !== undefined && isObject(info.total_token_usage) ? info.total_token_usage : undefined;\n // Cumulative; keep the latest so the final value is the session total.\n if (totals !== undefined) lastTokenTotals = totals;\n continue;\n }\n\n if (readString(record.type) === \"event_msg\") {\n const pt = readString(payload.type);\n if (\n pt === \"user_message\" ||\n pt === \"agent_message\" ||\n pt === \"task_started\" ||\n pt === \"task_complete\"\n ) {\n const tsMs = Date.parse(ts);\n if (Number.isFinite(tsMs)) engagementTsMs.push(tsMs);\n }\n if (pt === \"task_complete\") {\n const turnId = readString(payload.turn_id);\n // Skip a duplicate completion for an already-counted turn (F1).\n if (turnId === undefined || !completedTurnIds.has(turnId)) {\n if (turnId !== undefined) completedTurnIds.add(turnId);\n completions.push({\n interval: turnIntervalFromComplete(ts, payload, turnStartMsByTurnId),\n durationMs: readNonNegInt(payload.duration_ms),\n });\n }\n }\n // event_msg records are never response_items; skip the rest.\n continue;\n }\n\n if (readString(record.type) !== \"response_item\") continue;\n if (readString(payload.type) !== \"function_call\") continue;\n if (readString(payload.name) !== \"exec_command\") continue;\n\n const command = readExecCommand(payload.arguments);\n if (command === undefined) continue;\n const cwd = command.workdir ?? workingDir ?? \".\";\n const output = readCallId(payload.call_id, outputsByCallId);\n const execTsMs = Date.parse(ts);\n if (Number.isFinite(execTsMs)) engagementTsMs.push(execTsMs);\n derived.push(\n commandExecutedEvent(ts, placeholderSessionId, command.cmd, cwd, {\n exitCode: parseExitCode(output),\n durationMs: parseWallTimeMs(output),\n }),\n );\n }\n\n if (minTs === undefined || maxTs === undefined) return null;\n if (derived.length === 0) return null;\n\n // Order derived events by occurred_at so the assembled stream is\n // non-decreasing — importSessionFromJson rejects out-of-order events.\n // Array.prototype.sort is stable, so same-timestamp events keep their\n // rollout order.\n derived.sort((a, b) => Date.parse(a.occurred_at) - Date.parse(b.occurred_at));\n\n const events: Event[] = [\n sessionStartedEvent(minTs, placeholderSessionId),\n ...derived,\n sessionEndedEvent(maxTs, placeholderSessionId),\n ];\n\n const externalId = options.externalId ?? codexSessionId;\n // Human-readable label: when + how much, so the session reads as content in\n // `basou session list` / handoff rather than an opaque id. The source id is\n // kept structurally in `source.external_id` (not the label), and paths are\n // deliberately excluded — the label is NOT path-sanitized downstream, so a\n // raw file path here would leak an operator-private prefix.\n const commandCount = derived.length;\n const label = `codex ${sessionLabelDateSpan(minTs, maxTs)}: ${commandCount} ${commandCount === 1 ? \"command\" : \"commands\"}`;\n\n // Resolve per-turn intervals + machine compute now that `minTs` is known.\n // A turn whose `task_started` is absent has its start reconstructed as\n // `end - duration_ms`, which can precede the earliest record (and thus\n // `started_at`); clamp to `minTs` so no active interval predates the session\n // (F3). Machine compute for each turn is bounded by its clamped in-session\n // span, so a clamped turn can never push the session's machine time above its\n // active time (the documented `machine <= active` subset; F1). For ordinary\n // fully-logged turns the span is >= the reported duration, so this is exactly\n // `duration_ms`. `machine` is honest only when EVERY completed turn carried a\n // duration: a mix of duration-bearing and duration-less completions would\n // report a partial compute total as if complete, so it is omitted then (F2).\n const minTsMs = Date.parse(minTs);\n const turnIntervals: IntervalMs[] = [];\n let machineActiveMs = 0;\n let allCompletedTurnsHaveDuration = true;\n for (const { interval, durationMs } of completions) {\n if (durationMs <= 0) allCompletedTurnsHaveDuration = false;\n if (interval === undefined) continue;\n const start = Number.isFinite(minTsMs) ? Math.max(interval[0], minTsMs) : interval[0];\n const end = interval[1];\n if (!(start < end)) continue;\n turnIntervals.push([start, end]);\n machineActiveMs += Math.min(durationMs, end - start);\n }\n\n // Active time = union of the real per-turn intervals (in-turn, uncapped) and\n // the gap-capped engagement series (between-turn bridging). The merge dedups\n // their overlap, so the in-turn portion is the turn span while between-turn\n // human time is still credited up to the cap. A single explicit turn is\n // enough to bound active time, so the >= 2-point fallback only matters when\n // no turn intervals exist. Omitted when neither yields a span, so stats falls\n // back to the event-derived measure. Method label reflects which was used.\n const pointResult = activeTimeFromTimestamps(engagementTsMs, ACTIVE_GAP_CAP_MS);\n const active =\n turnIntervals.length > 0 || pointResult.intervals.length > 0\n ? unionDurationMs([...turnIntervals, ...pointResult.intervals])\n : undefined;\n const activeMethod = turnIntervals.length > 0 ? TURN_INTERVALS_METHOD : ENGAGED_TURNS_METHOD;\n const machineActive = allCompletedTurnsHaveDuration ? machineActiveMs : 0;\n\n // Token totals from the last cumulative token_count event; include only the\n // fields actually present (> 0). Metrics is emitted if either token usage or\n // an engaged-time signal is present.\n const tokenFields =\n lastTokenTotals === undefined\n ? {}\n : {\n ...(readNonNegInt(lastTokenTotals.output_tokens) > 0\n ? { output_tokens: readNonNegInt(lastTokenTotals.output_tokens) }\n : {}),\n ...(readNonNegInt(lastTokenTotals.input_tokens) > 0\n ? { input_tokens: readNonNegInt(lastTokenTotals.input_tokens) }\n : {}),\n ...(readNonNegInt(lastTokenTotals.cached_input_tokens) > 0\n ? { cached_input_tokens: readNonNegInt(lastTokenTotals.cached_input_tokens) }\n : {}),\n ...(readNonNegInt(lastTokenTotals.reasoning_output_tokens) > 0\n ? { reasoning_output_tokens: readNonNegInt(lastTokenTotals.reasoning_output_tokens) }\n : {}),\n };\n const metricsFields = {\n ...tokenFields,\n ...(active !== undefined && active.ms > 0\n ? {\n active_time_ms: active.ms,\n active_intervals: intervalsMsToIso(active.merged),\n active_gap_cap_ms: ACTIVE_GAP_CAP_MS,\n active_time_method: activeMethod,\n }\n : {}),\n ...(machineActive > 0 ? { machine_active_time_ms: machineActive } : {}),\n };\n const metrics = Object.keys(metricsFields).length > 0 ? metricsFields : undefined;\n\n const payload: SessionImportPayload = {\n schema_version: \"0.1.0\",\n session: {\n label,\n workspace_id: options.workspaceId,\n source: {\n kind: CODEX_IMPORT_SOURCE,\n version: \"0.1.0\",\n ...(externalId !== undefined ? { external_id: externalId } : {}),\n ...(options.sourceSizeBytes !== undefined\n ? { source_size_bytes: options.sourceSizeBytes }\n : {}),\n },\n started_at: minTs,\n ended_at: maxTs,\n // Validated against the canonical enum here; importSessionFromJson\n // overwrites it with the literal \"imported\" regardless.\n status: \"imported\",\n working_directory: workingDir ?? \".\",\n invocation: { command: \"codex\", args: [], exit_code: null },\n related_files: [],\n summary: null,\n ...(metrics !== undefined ? { metrics } : {}),\n },\n events,\n };\n return payload;\n}\n\n// --- event builders -------------------------------------------------------\n\nfunction baseEvent(\n occurredAt: string,\n sessionId: PrefixedId<\"ses\">,\n): {\n schema_version: \"0.1.0\";\n id: PrefixedId<\"evt\">;\n session_id: PrefixedId<\"ses\">;\n occurred_at: string;\n source: string;\n} {\n return {\n schema_version: \"0.1.0\",\n id: prefixedUlid(\"evt\"),\n session_id: sessionId,\n occurred_at: occurredAt,\n source: CODEX_IMPORT_SOURCE,\n };\n}\n\nfunction sessionStartedEvent(occurredAt: string, sessionId: PrefixedId<\"ses\">): Event {\n return { ...baseEvent(occurredAt, sessionId), type: \"session_started\" };\n}\n\nfunction sessionEndedEvent(occurredAt: string, sessionId: PrefixedId<\"ses\">): Event {\n return { ...baseEvent(occurredAt, sessionId), type: \"session_ended\" };\n}\n\nfunction commandExecutedEvent(\n occurredAt: string,\n sessionId: PrefixedId<\"ses\">,\n command: string,\n cwd: string,\n outcome: { exitCode: number | null; durationMs: number },\n): Event {\n return {\n ...baseEvent(occurredAt, sessionId),\n type: \"command_executed\",\n command: \"bash\",\n args: [\"-c\", command],\n cwd,\n exit_code: outcome.exitCode,\n duration_ms: outcome.durationMs,\n };\n}\n\n// --- defensive readers ----------------------------------------------------\n\nfunction readString(value: unknown): string | undefined {\n return typeof value === \"string\" && value.length > 0 ? value : undefined;\n}\n\n/** Read a non-negative integer token count, treating anything else as 0. */\nfunction readNonNegInt(value: unknown): number {\n return typeof value === \"number\" && Number.isInteger(value) && value >= 0 ? value : 0;\n}\n\nfunction isObject(value: unknown): value is Record<string, unknown> {\n return typeof value === \"object\" && value !== null && !Array.isArray(value);\n}\n\n/**\n * Parse an `exec_command` call's JSON `arguments` string into its shell line\n * and optional working directory. Returns `undefined` when the arguments are\n * not parseable or carry no `cmd`, so the caller can skip the call.\n */\nfunction readExecCommand(value: unknown): { cmd: string; workdir: string | undefined } | undefined {\n const raw = readString(value);\n if (raw === undefined) return undefined;\n let parsed: unknown;\n try {\n parsed = JSON.parse(raw);\n } catch {\n return undefined;\n }\n if (!isObject(parsed)) return undefined;\n const cmd = readString(parsed.cmd);\n if (cmd === undefined) return undefined;\n return { cmd, workdir: readString(parsed.workdir) };\n}\n\nfunction readCallId(value: unknown, outputs: ReadonlyMap<string, string>): string | undefined {\n const callId = readString(value);\n return callId !== undefined ? outputs.get(callId) : undefined;\n}\n\n/**\n * Build a turn's `[start, end]` wall-clock interval from its `task_complete`.\n * `end` is the completion record's own timestamp (ISO, ms precision); `start`\n * is the matching `task_started`'s timestamp, or — when that record is absent\n * (a session whose first turn was already in progress at import) —\n * reconstructed as `end - duration_ms`. Returns `undefined` when no start can\n * be resolved or the span is non-positive, so the caller falls back to the\n * gap-capped engagement series for that turn.\n */\nfunction turnIntervalFromComplete(\n endTs: string,\n payload: Record<string, unknown>,\n startMsByTurnId: ReadonlyMap<string, number>,\n): IntervalMs | undefined {\n const endMs = Date.parse(endTs);\n if (!Number.isFinite(endMs)) return undefined;\n const turnId = readString(payload.turn_id);\n const indexedStart = turnId !== undefined ? startMsByTurnId.get(turnId) : undefined;\n const durationMs = readNonNegInt(payload.duration_ms);\n const startMs =\n indexedStart !== undefined ? indexedStart : durationMs > 0 ? endMs - durationMs : undefined;\n if (startMs === undefined || !(startMs < endMs)) return undefined;\n return [startMs, endMs];\n}\n\n/**\n * Index each turn's start time (epoch ms) by its `turn_id` from the\n * `task_started` records. First occurrence wins. Lets a `task_complete` recover\n * the real turn start regardless of record order.\n */\nfunction indexTaskStarts(records: ReadonlyArray<CodexRolloutRecord>): Map<string, number> {\n const byTurnId = new Map<string, number>();\n for (const record of records) {\n if (readString(record.type) !== \"event_msg\") continue;\n const payload = isObject(record.payload) ? record.payload : undefined;\n if (payload === undefined || readString(payload.type) !== \"task_started\") continue;\n const turnId = readString(payload.turn_id);\n const startMs = Date.parse(readString(record.timestamp) ?? \"\");\n if (turnId !== undefined && Number.isFinite(startMs) && !byTurnId.has(turnId)) {\n byTurnId.set(turnId, startMs);\n }\n }\n return byTurnId;\n}\n\n/**\n * Codex's `exec_command` output text reports the child's exit code as\n * `Process exited with code N` (N may be negative for signal termination).\n * Returns `null` when the line is absent — the command may have yielded before\n * completing or the session was cut off mid-command.\n */\nfunction parseExitCode(output: string | undefined): number | null {\n if (output === undefined) return null;\n const match = output.match(/Process exited with code (-?\\d+)/);\n return match?.[1] !== undefined ? Number.parseInt(match[1], 10) : null;\n}\n\n/**\n * Codex's `exec_command` output text reports wall-clock duration as\n * `Wall time: X seconds`. Returns `0` (the schema floor) when absent or\n * non-finite, matching the Claude importer's missing-duration default.\n */\nfunction parseWallTimeMs(output: string | undefined): number {\n if (output === undefined) return 0;\n const match = output.match(/Wall time:\\s*([\\d.]+)\\s*seconds/);\n if (match?.[1] === undefined) return 0;\n const seconds = Number.parseFloat(match[1]);\n return Number.isFinite(seconds) ? Math.round(seconds * 1000) : 0;\n}\n\n/**\n * Index every `function_call_output`'s text by its `call_id`, so a command's\n * exit code and duration can be looked up at the originating `function_call`.\n * Only string outputs are kept — image / structured tool results are arrays\n * and carry no command outcome.\n */\nfunction indexOutputs(records: ReadonlyArray<CodexRolloutRecord>): Map<string, string> {\n const byId = new Map<string, string>();\n for (const record of records) {\n if (readString(record.type) !== \"response_item\") continue;\n const payload = isObject(record.payload) ? record.payload : undefined;\n if (payload === undefined) continue;\n if (readString(payload.type) !== \"function_call_output\") continue;\n const callId = readString(payload.call_id);\n const output = readString(payload.output);\n if (callId !== undefined && output !== undefined) byId.set(callId, output);\n }\n return byId;\n}\n","import { readdir } from \"node:fs/promises\";\nimport { join } from \"node:path\";\nimport { findErrorCode } from \"../lib/error-codes.js\";\nimport { type Approval, ApprovalSchema } from \"../schemas/approval.schema.js\";\nimport type { BasouPaths } from \"../storage/basou-dir.js\";\nimport { readYamlFile } from \"../storage/yaml-store.js\";\n\n/** Which side of `.basou/approvals/` an approval YAML lives on. */\nexport type ApprovalLocation = \"pending\" | \"resolved\";\n\n/** Result returned by {@link loadApproval}: the parsed approval and where it was found. */\nexport type LoadedApproval = {\n approval: Approval;\n location: ApprovalLocation;\n};\n\n/**\n * Locate and load the approval YAML for `approvalId`. Searches resolved\n * first so that a duplicated YAML (the crash-window scenario where both\n * pending and resolved exist for the same id) returns the resolved-side\n * record — matching the dedupe rule used by `approval list` and\n * `resolveApprovalId`. Returns null if neither directory contains the\n * YAML. Throws with a pathless message on read or schema-validation\n * failure.\n */\nexport async function loadApproval(\n paths: BasouPaths,\n approvalId: string,\n): Promise<LoadedApproval | null> {\n for (const location of [\"resolved\", \"pending\"] as const) {\n const filePath = join(paths.approvals[location], `${approvalId}.yaml`);\n let raw: unknown;\n try {\n raw = await readYamlFile(filePath);\n } catch (error: unknown) {\n // ENOENT (i.e. \"YAML file not found\") → continue to the other directory.\n if (error instanceof Error && error.message === \"YAML file not found\") continue;\n throw new Error(\"Failed to read approval\", { cause: error });\n }\n const result = ApprovalSchema.safeParse(raw);\n if (!result.success) {\n throw new Error(\"Failed to read approval\", { cause: result.error });\n }\n // Defensive id check: a hand-edited YAML whose `id` field disagrees\n // with the filename-derived id would otherwise let the CLI render or\n // mutate one approval while citing another. Treat the mismatch as a\n // read failure rather than silently picking one side.\n if (result.data.id !== approvalId) {\n throw new Error(\"Failed to read approval\", {\n cause: new Error(\n `Approval id mismatch: filename id ${approvalId} vs YAML body id ${result.data.id}`,\n ),\n });\n }\n return { approval: result.data, location };\n }\n return null;\n}\n\n/**\n * Enumerate approval IDs by inspecting `<id>.yaml` filenames in pending\n * and resolved. ENOENT on either directory is treated as empty (e.g. a\n * workspace that has no resolved approvals yet). YAML parse and schema\n * validation are NOT performed; callers that need the parsed approval\n * should use {@link loadApproval} per ID.\n */\nexport async function enumerateApprovals(paths: BasouPaths): Promise<{\n pending: string[];\n resolved: string[];\n}> {\n const [pending, resolved] = await Promise.all([\n enumerateIds(paths.approvals.pending),\n enumerateIds(paths.approvals.resolved),\n ]);\n return { pending, resolved };\n}\n\nasync function enumerateIds(dir: string): Promise<string[]> {\n let entries: string[];\n try {\n const dirents = await readdir(dir, { withFileTypes: true });\n entries = dirents\n .filter((e) => e.isFile() && e.name.endsWith(\".yaml\"))\n .map((e) => e.name.slice(0, -\".yaml\".length));\n } catch (error: unknown) {\n if (findErrorCode(error, \"ENOENT\")) return [];\n throw new Error(\"Failed to enumerate approvals\", { cause: error });\n }\n return entries;\n}\n\n/**\n * Return true when an approval is in `pending` state and its `expires_at`\n * timestamp has elapsed. Used by `basou approval list` / `show` to surface\n * a `(expired)` label without mutating the YAML file. Approval expiry uses\n * lazy-evaluation semantics; actual `approval_expired` event firing is\n * deferred to a later step.\n *\n * `now` is taken as a parameter so a single CLI invocation can share one\n * \"now\" across every record it inspects (avoids boundary races where two\n * reads of `Date.now()` straddle an expiry instant).\n */\nexport function isLazyExpired(approval: Approval, now: Date): boolean {\n if (approval.status !== \"pending\") return false;\n if (approval.expires_at === null) return false;\n const expiresMs = Date.parse(approval.expires_at);\n if (!Number.isFinite(expiresMs)) return false;\n return expiresMs < now.getTime();\n}\n","/**\n * Walk the cause chain (up to `depth` levels) looking for an Error whose\n * errno-style `code` matches `code`. Returns true on the first match.\n * Resilient to wrapper depth changes so that ENOENT detection survives\n * future error-wrapping refactors.\n */\nexport function findErrorCode(error: unknown, code: string, depth = 4): boolean {\n let cur: unknown = error;\n for (let i = 0; i < depth && cur instanceof Error; i++) {\n const c = (cur as { code?: unknown }).code;\n if (typeof c === \"string\" && c === code) return true;\n cur = (cur as Error).cause;\n }\n return false;\n}\n","import { z } from \"zod\";\nimport {\n ApprovalIdSchema,\n IsoTimestampSchema,\n RiskLevelSchema,\n SchemaVersionSchema,\n SessionIdSchema,\n} from \"./shared.schema.js\";\n\n/**\n * Lifecycle states of a Basou approval. The status is stored directly on\n * the approval YAML (flat shape) so that pending → resolved transitions\n * are atomic-move + in-place rewrites rather than schema-variant swaps.\n */\nexport const ApprovalStatusSchema = z.enum([\"pending\", \"approved\", \"rejected\", \"expired\"]);\n/** Inferred runtime type for {@link ApprovalStatusSchema}. */\nexport type ApprovalStatus = z.infer<typeof ApprovalStatusSchema>;\n\n/**\n * Schema for `.basou/approvals/{pending,resolved}/<approval_id>.yaml`.\n *\n * The schema is intentionally flat (one shape regardless of `status`) so\n * that pending and resolved YAMLs share the same parser. Required vs.\n * optional semantics by status (e.g. `rejection_reason` MUST be set when\n * `status === \"rejected\"`) are enforced at the CLI orchestration layer\n * rather than here, mirroring the approval event variants in\n * `event.schema.ts`.\n *\n * The `action` field is `{ kind: string }` with `passthrough()` so that\n * adapter-defined keys (e.g. `command`, `path`, `target_url`) survive the\n * round-trip without being stripped — matching the approval_requested\n * event variant.\n */\nexport const ApprovalSchema = z.object({\n schema_version: SchemaVersionSchema,\n id: ApprovalIdSchema,\n session_id: SessionIdSchema,\n created_at: IsoTimestampSchema,\n status: ApprovalStatusSchema,\n risk_level: RiskLevelSchema,\n action: z.object({ kind: z.string() }).passthrough(),\n reason: z.string(),\n expires_at: IsoTimestampSchema.nullable().default(null),\n // The four fields below are null while `status === \"pending\"` and set\n // once a resolver records a decision. Defaulting to null keeps the\n // pending YAML free of explicit nulls if a producer omits them.\n resolver: z.string().nullable().default(null),\n resolved_at: IsoTimestampSchema.nullable().default(null),\n note: z.string().nullable().default(null),\n rejection_reason: z.string().nullable().default(null),\n});\n\n/** Inferred runtime type for {@link ApprovalSchema}. */\nexport type Approval = z.infer<typeof ApprovalSchema>;\n","import { z } from \"zod\";\nimport { type IdPrefix, isValidPrefixedId, type PrefixedId } from \"../ids/ulid.js\";\n\n/**\n * Schema version literal pinned to \"0.1.0\" for Basou v0.1.\n * Reused across every entity schema so inferred types narrow to the literal.\n */\nexport const SchemaVersionSchema = z.literal(\"0.1.0\");\n\n/**\n * ISO 8601 timestamp with explicit timezone offset (e.g. `+09:00`).\n *\n * The spec samples include offsets, so the default zod `.datetime()` (which\n * rejects offsets) is insufficient; `{ offset: true }` is required.\n */\nexport const IsoTimestampSchema = z.string().datetime({ offset: true });\n\n// Internal factory shared by every prefixed-ID schema. Not exported because\n// the public API surface should only expose the six fully-typed ID schemas.\n//\n// The `.refine` carries the real (ULID-aware) validation but is opaque to JSON\n// Schema generation, so the `.meta` mirrors the prefix + ULID-body shape as a\n// representable `pattern` (and a description). This is METADATA ONLY: it does\n// not affect parsing — `isValidPrefixedId` still gates acceptance — it just\n// lets `z.toJSONSchema` emit a faithful pattern for the published artifact. The\n// pattern mirrors `ULID_BODY_REGEX` (leading 0-7, then 25 Crockford symbols\n// excluding I/L/O/U); it is intentionally slightly looser than the library\n// `isValid` check, matching the documented id shape.\nconst createPrefixedIdSchema = <P extends IdPrefix>(prefix: P) => {\n const refiner = (value: string): value is PrefixedId<P> =>\n isValidPrefixedId(value) && value.startsWith(`${prefix}_`);\n return z\n .string()\n .refine(refiner, { message: `Expected ${prefix}_<ULID>` })\n .meta({\n pattern: `^${prefix}_[0-7][0-9A-HJKMNP-TV-Z]{25}$`,\n description: `Basou ${prefix} id: \\`${prefix}_\\` followed by a 26-character Crockford Base32 ULID.`,\n });\n};\n\n/** Workspace ID schema: validates `ws_<26-char ULID>`. */\nexport const WorkspaceIdSchema = createPrefixedIdSchema(\"ws\");\n/** Task ID schema: validates `task_<26-char ULID>`. */\nexport const TaskIdSchema = createPrefixedIdSchema(\"task\");\n/** Session ID schema: validates `ses_<26-char ULID>`. */\nexport const SessionIdSchema = createPrefixedIdSchema(\"ses\");\n/** Event ID schema: validates `evt_<26-char ULID>`. */\nexport const EventIdSchema = createPrefixedIdSchema(\"evt\");\n/** Approval ID schema: validates `appr_<26-char ULID>`. */\nexport const ApprovalIdSchema = createPrefixedIdSchema(\"appr\");\n/** Decision ID schema: validates `decision_<26-char ULID>`. */\nexport const DecisionIdSchema = createPrefixedIdSchema(\"decision\");\n\n/**\n * Risk level vocabulary fixed by the spec. Adapters MUST emit one of these\n * four values; arbitrary strings are rejected at schema parse time.\n */\nexport const RiskLevelSchema = z.enum([\"low\", \"medium\", \"high\", \"critical\"]);\n/** Inferred runtime type for {@link RiskLevelSchema}. */\nexport type RiskLevel = z.infer<typeof RiskLevelSchema>;\n\n/**\n * Source attribution for events (e.g. \"claude-code-adapter\",\n * \"git-capability\", \"terminal-recording\", \"local-cli\", \"human\"). Free-form\n * non-empty string in v0.1; a stricter enum may be introduced post-v0.1.\n */\nexport const EventSourceSchema = z.string().min(1);\n","import { readFile } from \"node:fs/promises\";\nimport { parse, stringify } from \"yaml\";\nimport { atomicCreate, atomicReplace } from \"./atomic.js\";\n\n/**\n * Read a YAML file as `unknown`. Caller MUST validate via a zod schema.\n *\n * Throws Error with pathless message and the original native error attached\n * as `cause` for I/O failures and YAML parse errors. All fs and parse exits\n * go through fixed messages so absolute paths cannot leak via `error.message`.\n */\nexport async function readYamlFile(filePath: string): Promise<unknown> {\n let body: string;\n try {\n body = await readFile(filePath, \"utf8\");\n } catch (error: unknown) {\n if (hasErrorCode(error) && error.code === \"ENOENT\") {\n throw new Error(\"YAML file not found\", { cause: error });\n }\n throw new Error(\"Failed to read YAML file\", { cause: error });\n }\n try {\n return parse(body);\n } catch (error: unknown) {\n throw new Error(\"Failed to parse YAML content\", { cause: error });\n }\n}\n\n/**\n * Write a value as YAML using {@link atomicReplace} for crash-resistant\n * atomicity. The shared helper handles the tmp-file + rename sequence,\n * `wx` collision guard, and best-effort tmp cleanup on failure. This\n * wrapper adds the YAML serialisation and the pathless error vocabulary.\n */\nexport async function writeYamlFile(filePath: string, value: unknown): Promise<void> {\n const body = stringify(value);\n try {\n await atomicReplace(filePath, body);\n } catch (error: unknown) {\n throw new Error(\"Failed to write YAML file\", { cause: error });\n }\n}\n\n/**\n * Atomically create a new YAML file. Like {@link writeYamlFile} but\n * delegates to {@link atomicCreate} so a pre-existing target fails with\n * EEXIST instead of being silently overwritten.\n *\n * Used by `basou approval approve` / `reject` to write the resolved-side\n * YAML, so a concurrent resolver cannot overwrite an already-resolved\n * approval.\n *\n * Throws `Error(\"Failed to write YAML file\", { cause })` on failure; if\n * `cause.code === \"EEXIST\"` the caller can detect a target-exists race.\n */\nexport async function linkYamlFile(filePath: string, value: unknown): Promise<void> {\n const body = stringify(value);\n try {\n await atomicCreate(filePath, body);\n } catch (error: unknown) {\n throw new Error(\"Failed to write YAML file\", { cause: error });\n }\n}\n\n/**\n * Overwrite an existing YAML file atomically. Like {@link writeYamlFile}\n * but with a distinct pathless message label, used for files that\n * legitimately need in-place mutation (e.g. session.yaml's status /\n * ended_at lifecycle updates).\n */\nexport async function overwriteYamlFile(filePath: string, value: unknown): Promise<void> {\n const body = stringify(value);\n try {\n await atomicReplace(filePath, body);\n } catch (error: unknown) {\n throw new Error(\"Failed to overwrite YAML file\", { cause: error });\n }\n}\n\nfunction hasErrorCode(error: unknown): error is Error & { code: string } {\n if (!(error instanceof Error)) return false;\n return typeof (error as unknown as Record<string, unknown>).code === \"string\";\n}\n","import { randomUUID } from \"node:crypto\";\nimport { link, rename, unlink, writeFile } from \"node:fs/promises\";\n\n/**\n * Atomically create a new file at `targetPath` via tmp + link.\n *\n * Strategy: write the body to a sibling tmp file (`${targetPath}.tmp.<uuid>`)\n * with the `wx` flag, then `link()` the tmp inode into place at `targetPath`.\n * If the target already exists, `link` fails with EEXIST — callers detect this\n * via `findErrorCode(error, \"EEXIST\")` to surface a domain-specific\n * \"already exists\" message.\n *\n * The tmp file lives in the SAME directory as the target so `link` cannot\n * fail with EXDEV. On every code path (success and failure) the tmp inode\n * is best-effort unlinked, so after a successful call the tmp side of the\n * hard-link pair is removed and only `targetPath` remains.\n *\n * The native fs error is re-thrown WITHOUT wrapping so callers can attach\n * their own pathless message via `new Error(\"<fixed msg>\", { cause })`. The\n * caller is responsible for the final error vocabulary (pathless contract).\n */\nexport async function atomicCreate(targetPath: string, content: string | Buffer): Promise<void> {\n const tmpPath = `${targetPath}.tmp.${randomUUID()}`;\n try {\n await writeFile(tmpPath, content, { encoding: \"utf8\", flag: \"wx\" });\n await link(tmpPath, targetPath);\n } catch (error: unknown) {\n await unlink(tmpPath).catch(() => undefined);\n throw error;\n }\n // tmp inode is now linked twice (tmp + target); unlink the tmp side so\n // disk does not carry a spurious sibling after a successful create.\n await unlink(tmpPath).catch(() => undefined);\n}\n\n/**\n * Atomically replace the file at `targetPath` via tmp + rename.\n *\n * Strategy: write the body to a sibling tmp file (`${targetPath}.tmp.<uuid>`)\n * with the `wx` flag, then `rename()` the tmp over `targetPath`. Silently\n * overwrites any existing file at `targetPath`. The tmp file lives in the\n * SAME directory as the target so `rename` cannot fail with EXDEV. `rename`\n * consumes the tmp file, so no post-success cleanup is needed.\n *\n * On failure the tmp file is best-effort unlinked so disk never carries a\n * half-written rename source. The native fs error is re-thrown WITHOUT\n * wrapping so callers can attach their own pathless message.\n */\nexport async function atomicReplace(targetPath: string, content: string | Buffer): Promise<void> {\n const tmpPath = `${targetPath}.tmp.${randomUUID()}`;\n try {\n await writeFile(tmpPath, content, { encoding: \"utf8\", flag: \"wx\" });\n await rename(tmpPath, targetPath);\n } catch (error: unknown) {\n await unlink(tmpPath).catch(() => undefined);\n throw error;\n }\n}\n","import { lstat } from \"node:fs/promises\";\nimport { dirname, join, resolve } from \"node:path\";\nimport { type ReplayWarning, replayEvents } from \"../events/event-replay.js\";\nimport type { BasouPaths } from \"../storage/basou-dir.js\";\nimport { loadSessionEntries, type SessionSkipReason } from \"../storage/sessions.js\";\n\nexport type DecisionsRendererInput = {\n paths: BasouPaths;\n nowIso: string;\n onWarning?: (warning: ReplayWarning, sessionId: string) => void;\n onSessionSkip?: (sessionId: string, reason: SessionSkipReason) => void;\n};\n\nexport type DecisionsRendererResult = {\n /** Generated body WITHOUT BASOU:GENERATED markers. */\n body: string;\n decisionCount: number;\n};\n\ntype DecisionRecord = {\n decisionId: string;\n title: string;\n occurredAt: string;\n sessionId: string;\n // Rich fields. All optional; populated only when the decision_recorded\n // event carried the field.\n rationale: string | null | undefined;\n alternatives: readonly string[] | undefined;\n rejectedReason: string | null | undefined;\n linkedEvents: readonly string[] | undefined;\n linkedFiles: readonly string[] | undefined;\n // \"track\" when the decision was recorded as a strategic, unfinished direction\n // (resurfaced by orientation/handoff until voided); undefined / \"decision\" is\n // a plain point-in-time decision.\n kind: \"decision\" | \"track\" | undefined;\n // Set when a later `decision_voided` event targets this decision. The\n // decision is kept (append-only) but rendered struck-through; orientation\n // skips it as the \"latest\" direction.\n voided: { reason: string | null | undefined; supersededBy: string | undefined } | undefined;\n};\n\n/**\n * Render the body of `decisions.md` from `decision_recorded` events across\n * every healthy session in the workspace.\n *\n * Session enumeration goes through {@link loadSessionEntries} (the same path\n * the handoff renderer uses) so that `session.yaml`-broken sessions are\n * skipped in BOTH outputs and the handoff's `decisionCount` summary stays\n * consistent with the number of sections rendered here.\n *\n * Order: `occurred_at` ascending with `decisionId` (= ULID) as tie-breaker.\n * Both fields are monotonic, so the result is a stable cross-session\n * timeline.\n *\n * The decision rich fields (rationale / alternatives / rejected_reason /\n * linked_events / linked_files) are rendered when the event carries them.\n * `linked_events` and `linked_files` are OPAQUE references: the schema only\n * validates the SHAPE, not existence — references that cannot be resolved\n * to a known event id or an existing file on disk are surfaced inline as\n * `(missing)` so cross-workspace round-trips never reject parse-time.\n */\nexport async function renderDecisions(\n input: DecisionsRendererInput,\n): Promise<DecisionsRendererResult> {\n const now = new Date(input.nowIso);\n // Same rationale as handoff-renderer. Track which\n // sessions already had `events_jsonl_unreadable` surfaced so non-running\n // sessions whose events.jsonl is unreadable still produce a stderr\n // warning instead of silently dropping their decisions.\n const unreadableEmitted = new Set<string>();\n const wrappedSkip: (sid: string, reason: SessionSkipReason) => void = (sid, reason) => {\n if (reason === \"events_jsonl_unreadable\") unreadableEmitted.add(sid);\n input.onSessionSkip?.(sid, reason);\n };\n const loadOpts: Parameters<typeof loadSessionEntries>[1] = { now, onSkip: wrappedSkip };\n if (input.onWarning !== undefined) loadOpts.onWarning = input.onWarning;\n const entries = await loadSessionEntries(input.paths, loadOpts);\n\n const decisions: DecisionRecord[] = [];\n // decision_id -> void record (last void wins). Collected in the same scan so\n // a void recorded in any session marks the target decision wherever it lives.\n const voids = new Map<\n string,\n { reason: string | null | undefined; supersededBy: string | undefined }\n >();\n // Workspace-wide event id index, populated during the same scan that\n // collects decisions, so `linked_events` membership can be resolved\n // without a second pass over events.jsonl.\n const knownEventIds = new Set<string>();\n for (const entry of entries) {\n const sessionDir = join(input.paths.sessions, entry.sessionId);\n try {\n for await (const ev of replayEvents(sessionDir, {\n onWarning: (w) => input.onWarning?.(w, entry.sessionId),\n })) {\n knownEventIds.add(ev.id);\n if (ev.type === \"decision_recorded\") {\n decisions.push({\n decisionId: ev.decision_id,\n title: ev.title,\n occurredAt: ev.occurred_at,\n sessionId: entry.sessionId,\n rationale: ev.rationale,\n alternatives: ev.alternatives,\n rejectedReason: ev.rejected_reason,\n linkedEvents: ev.linked_events,\n linkedFiles: ev.linked_files,\n kind: ev.kind,\n voided: undefined,\n });\n } else if (ev.type === \"decision_voided\") {\n voids.set(ev.decision_id, { reason: ev.reason, supersededBy: ev.superseded_by });\n }\n }\n } catch {\n if (!unreadableEmitted.has(entry.sessionId)) {\n wrappedSkip(entry.sessionId, \"events_jsonl_unreadable\");\n }\n }\n }\n for (const d of decisions) {\n const v = voids.get(d.decisionId);\n if (v !== undefined) d.voided = v;\n }\n decisions.sort((a, b) => {\n const c = Date.parse(a.occurredAt) - Date.parse(b.occurredAt);\n return c !== 0 ? c : a.decisionId.localeCompare(b.decisionId);\n });\n\n // Resolve linked_files relative to the repository root (= parent of\n // `.basou/`). Existence is checked with `lstat` so symlinks are treated\n // honestly — a dangling symlink is reported as `(missing)`. The check\n // runs once per unique path so repeated references share their lookup.\n const repoRoot = dirname(input.paths.root);\n const fileExistenceCache = new Map<string, boolean>();\n async function fileExists(relPath: string): Promise<boolean> {\n const cached = fileExistenceCache.get(relPath);\n if (cached !== undefined) return cached;\n const abs = resolve(repoRoot, relPath);\n let exists: boolean;\n try {\n await lstat(abs);\n exists = true;\n } catch {\n exists = false;\n }\n fileExistenceCache.set(relPath, exists);\n return exists;\n }\n\n const body = await formatDecisionsBody({\n nowIso: input.nowIso,\n decisions,\n knownEventIds,\n fileExists,\n });\n return { body, decisionCount: decisions.length };\n}\n\nasync function formatDecisionsBody(args: {\n nowIso: string;\n decisions: ReadonlyArray<DecisionRecord>;\n knownEventIds: ReadonlySet<string>;\n fileExists: (relPath: string) => Promise<boolean>;\n}): Promise<string> {\n const lines: string[] = [];\n lines.push(\"# Decisions\");\n lines.push(\"\");\n lines.push(`> Generated at ${args.nowIso}`);\n lines.push(\"\");\n if (args.decisions.length === 0) {\n lines.push(\"(no decisions recorded yet)\");\n return lines.join(\"\\n\");\n }\n for (const d of args.decisions) {\n // A track marker rides on the heading so the audit shows the decision was a\n // strategic direction; for an open track it precedes the `- 種別` line below.\n const trackMark = d.kind === \"track\" ? \" [TRACK]\" : \"\";\n if (d.voided !== undefined) {\n // Struck heading + a void line; the decision body is kept for the audit\n // trail but visibly marked no longer in force.\n lines.push(`## ~~${d.decisionId}: ${d.title}~~ [VOIDED]${trackMark}`);\n lines.push(\"\");\n const supersededBy =\n d.voided.supersededBy !== undefined ? `, superseded by ${d.voided.supersededBy}` : \"\";\n const reason =\n typeof d.voided.reason === \"string\" && d.voided.reason.length > 0\n ? `: ${d.voided.reason}`\n : \"\";\n lines.push(`- ⚠ VOIDED${reason}${supersededBy}`);\n } else {\n lines.push(`## ${d.decisionId}: ${d.title}${trackMark}`);\n lines.push(\"\");\n }\n const occurredDate = d.occurredAt.slice(0, 10); // YYYY-MM-DD\n lines.push(`- 決定日: ${occurredDate}`);\n // An OPEN track keeps resurfacing in orientation/handoff; note that here so\n // the full record explains why it is still surfaced (a voided track is closed\n // and carries the VOIDED line instead).\n if (d.kind === \"track\" && d.voided === undefined) {\n lines.push(\"- 種別: track (close まで orient/handoff に継続表示)\");\n }\n lines.push(`- session: ${shortDecisionSessionId(d.sessionId)}`);\n lines.push(`- 判断: ${d.title}`);\n if (typeof d.rationale === \"string\" && d.rationale.length > 0) {\n lines.push(`- rationale: ${d.rationale}`);\n }\n if (d.alternatives !== undefined && d.alternatives.length > 0) {\n lines.push(`- alternatives: ${d.alternatives.join(\", \")}`);\n }\n if (typeof d.rejectedReason === \"string\" && d.rejectedReason.length > 0) {\n lines.push(`- rejected_reason: ${d.rejectedReason}`);\n }\n if (d.linkedEvents !== undefined && d.linkedEvents.length > 0) {\n const parts = d.linkedEvents.map((eid) =>\n args.knownEventIds.has(eid) ? eid : `${eid} (missing)`,\n );\n lines.push(`- linked_events: ${parts.join(\", \")}`);\n }\n if (d.linkedFiles !== undefined && d.linkedFiles.length > 0) {\n const parts = await Promise.all(\n d.linkedFiles.map(async (path) =>\n (await args.fileExists(path)) ? path : `${path} (missing)`,\n ),\n );\n lines.push(`- linked_files: ${parts.join(\", \")}`);\n }\n lines.push(\"\");\n }\n return lines.join(\"\\n\");\n}\n\nfunction shortDecisionSessionId(sessionId: string): string {\n const SES = \"ses_\";\n if (sessionId.startsWith(SES)) return sessionId.slice(SES.length, SES.length + 10);\n return sessionId.slice(0, 10);\n}\n","import { createReadStream } from \"node:fs\";\nimport { stat } from \"node:fs/promises\";\nimport { join } from \"node:path\";\nimport { findErrorCode } from \"../lib/error-codes.js\";\nimport { type Event, EventSchema } from \"../schemas/event.schema.js\";\n\n/**\n * Recoverable warning surfaced via {@link ReplayOptions.onWarning}. The replay\n * generator never throws on these — it skips the offending line and continues.\n *\n * `partial_trailing_line` indicates the events.jsonl did not end with `\\n` and\n * the unterminated tail parsed as a complete event. The line is dropped\n * instead of yielded so consumers cannot accidentally observe a\n * partially-written record.\n */\nexport type ReplayWarning =\n | { kind: \"partial_trailing_line\"; line: number }\n | { kind: \"malformed_json\"; line: number; cause: unknown }\n | { kind: \"schema_violation\"; line: number; cause: unknown };\n\nexport type ReplayOptions = {\n /**\n * Hook to receive recoverable warnings (partial line / malformed JSON /\n * schema violation). When omitted, warnings are silently dropped — callers\n * that want to surface them (e.g. CLI orchestration) MUST provide this hook.\n */\n onWarning?: (warning: ReplayWarning) => void;\n};\n\n/**\n * Stream events from `<sessionDir>/events.jsonl` line by line.\n *\n * Behavior:\n * - ENOENT or empty file: yields nothing without warning.\n * - I/O error: throws `Error(\"Failed to read events.jsonl\")` with the native\n * error attached as `cause`. The thrown message never embeds an absolute\n * path (pathless contract).\n * - Trailing partial line that parses as a valid event: dropped silently when\n * {@link ReplayOptions.onWarning} is omitted; otherwise reported as\n * `partial_trailing_line`. A trailing partial line that fails JSON parsing\n * is reported as `malformed_json` instead.\n * - Malformed JSON / schema violation: skipped, with the corresponding\n * warning when a hook is provided.\n *\n * Single-writer-per-session is assumed (see `event-writer.ts` JSDoc on\n * {@link appendEvent}). Concurrent writers may interleave lines beyond\n * `PIPE_BUF` and are not recovered here in v0.1.\n */\n// NOTE: switched from plan A (Transform stream + readline) to plan B (manual\n// chunk-level split) because plan A's source-stream errors do not propagate\n// through `pipe()` to the readline iterator, so an EACCES on the events.jsonl\n// hangs the for-await loop instead of throwing. Plan B observes errors\n// directly via the for-await over `createReadStream` and reaches end-of-stream\n// deterministically with the trailing buffer in hand.\nexport async function* replayEvents(\n sessionDir: string,\n options: ReplayOptions = {},\n): AsyncGenerator<Event, void, void> {\n const filePath = join(sessionDir, \"events.jsonl\");\n\n // Probe existence first so ENOENT (= empty session) is silent while every\n // other I/O failure surfaces as a single fixed-message error.\n try {\n await stat(filePath);\n } catch (error: unknown) {\n if (findErrorCode(error, \"ENOENT\")) return;\n throw new Error(\"Failed to read events.jsonl\", { cause: error });\n }\n\n let stream: ReturnType<typeof createReadStream>;\n try {\n stream = createReadStream(filePath, { encoding: \"utf8\" });\n } catch (error: unknown) {\n throw new Error(\"Failed to read events.jsonl\", { cause: error });\n }\n\n let buffer = \"\";\n let lineNo = 0;\n\n try {\n for await (const chunk of stream as unknown as AsyncIterable<string>) {\n buffer += chunk;\n let newlineIdx = buffer.indexOf(\"\\n\");\n while (newlineIdx !== -1) {\n lineNo += 1;\n const rawLine = buffer.slice(0, newlineIdx);\n buffer = buffer.slice(newlineIdx + 1);\n const ev = processLine(rawLine, lineNo, options);\n if (ev !== null) yield ev;\n newlineIdx = buffer.indexOf(\"\\n\");\n }\n }\n } catch (error: unknown) {\n throw new Error(\"Failed to read events.jsonl\", { cause: error });\n }\n\n // Stream ended mid-line: anything left in `buffer` is the trailing partial\n // line. Empty / whitespace-only trailing content is treated as a normal end\n // of file (e.g. a final '\\n' was stripped by the loop above).\n const trimmed = buffer.replace(/[\\r\\n\\t ]+$/u, \"\");\n if (trimmed.length === 0) return;\n lineNo += 1;\n\n let parsed: unknown;\n try {\n parsed = JSON.parse(trimmed);\n } catch (cause) {\n // The trailing buffer was non-empty AND JSON-invalid. Either\n // partial_trailing_line or malformed_json captures the same observable\n // outcome; we surface malformed_json because the JSON layer rejected it\n // first and the line number is meaningful for the consumer.\n options.onWarning?.({ kind: \"malformed_json\", line: lineNo, cause });\n return;\n }\n\n const result = EventSchema.safeParse(parsed);\n if (!result.success) {\n options.onWarning?.({ kind: \"schema_violation\", line: lineNo, cause: result.error });\n return;\n }\n\n // Valid JSON + valid event schema BUT no terminating newline. Drop instead\n // of yielding so a half-flushed write cannot be consumed as a real event.\n options.onWarning?.({ kind: \"partial_trailing_line\", line: lineNo });\n}\n\nfunction processLine(rawLine: string, lineNo: number, options: ReplayOptions): Event | null {\n const trimmed = rawLine.trim();\n if (trimmed.length === 0) return null;\n let parsed: unknown;\n try {\n parsed = JSON.parse(trimmed);\n } catch (cause) {\n options.onWarning?.({ kind: \"malformed_json\", line: lineNo, cause });\n return null;\n }\n const result = EventSchema.safeParse(parsed);\n if (!result.success) {\n options.onWarning?.({ kind: \"schema_violation\", line: lineNo, cause: result.error });\n return null;\n }\n return result.data;\n}\n\n/**\n * Eager array helper: collect every event from {@link replayEvents} into\n * memory. Convenience for callers that need the full list in one structure\n * (e.g. `basou session show` rendering).\n */\nexport async function readAllEvents(\n sessionDir: string,\n options: ReplayOptions = {},\n): Promise<Event[]> {\n const out: Event[] = [];\n for await (const ev of replayEvents(sessionDir, options)) {\n out.push(ev);\n }\n return out;\n}\n","import { z } from \"zod\";\nimport {\n ApprovalIdSchema,\n DecisionIdSchema,\n EventIdSchema,\n EventSourceSchema,\n IsoTimestampSchema,\n RiskLevelSchema,\n SchemaVersionSchema,\n SessionIdSchema,\n TaskIdSchema,\n} from \"./shared.schema.js\";\n\n// Common base every event variant extends. Each variant declares its own\n// `type: z.literal(...)` and adds variant-specific fields.\nconst BaseEventSchema = z.object({\n schema_version: SchemaVersionSchema,\n id: EventIdSchema,\n session_id: SessionIdSchema,\n occurred_at: IsoTimestampSchema,\n source: EventSourceSchema,\n // Tamper-evidence back-pointer (hex sha-256 of the PREVIOUS event line's\n // written bytes; the first line carries the session-bound genesis hash).\n // Present only on sessions written with chaining enabled (import paths);\n // live/ad-hoc sessions omit it. Declared on the base so the `.strict()`\n // variants below treat it as a known key. Additive optional => no\n // schema_version bump.\n prev_hash: z.string().optional(),\n});\n\n// --- Session lifecycle events ---\n\nconst SessionStartedEventSchema = BaseEventSchema.extend({\n type: z.literal(\"session_started\"),\n});\n\nconst SessionEndedEventSchema = BaseEventSchema.extend({\n type: z.literal(\"session_ended\"),\n exit_code: z.number().int().optional(),\n});\n\n// `from`/`to` use `string` to keep this module independent of session.schema\n// and avoid a circular import. A later event-replay layer may narrow these to\n// SessionStatusSchema by relocating the enum into shared.schema.\nconst SessionStatusChangedEventSchema = BaseEventSchema.extend({\n type: z.literal(\"session_status_changed\"),\n from: z.string(),\n to: z.string(),\n});\n\n// --- Approval events ---\n\nconst ApprovalRequestedEventSchema = BaseEventSchema.extend({\n type: z.literal(\"approval_requested\"),\n approval_id: ApprovalIdSchema,\n expires_at: IsoTimestampSchema.nullable().default(null),\n risk_level: RiskLevelSchema,\n // `action.kind` is required; additional fields are allowed to support\n // future action shapes (shell_command, external_send, ...).\n action: z.object({ kind: z.string() }).passthrough(),\n reason: z.string(),\n status: z.literal(\"pending\"),\n});\n\nconst ApprovalApprovedEventSchema = BaseEventSchema.extend({\n type: z.literal(\"approval_approved\"),\n approval_id: ApprovalIdSchema,\n resolver: z.string().optional(),\n note: z.string().nullable().optional(),\n});\n\nconst ApprovalRejectedEventSchema = BaseEventSchema.extend({\n type: z.literal(\"approval_rejected\"),\n approval_id: ApprovalIdSchema,\n resolver: z.string().optional(),\n reason: z.string(),\n});\n\nconst ApprovalExpiredEventSchema = BaseEventSchema.extend({\n type: z.literal(\"approval_expired\"),\n approval_id: ApprovalIdSchema,\n});\n\n// --- Command / Git / File events ---\n\n// `command` is the spawned executable name only (e.g. \"npm\"); arguments are\n// kept in `args` to preserve quoting and avoid shell-injection round-trips.\n// `exit_code` is null when the child terminated by signal. `signal` records\n// the child's terminating signal; `received_signal` records what the parent\n// process received (SIGINT/SIGTERM) and forwarded as cancellation, so a\n// timeout (signal set, received_signal absent) can be distinguished from a\n// user interrupt (both set).\nconst CommandExecutedEventSchema = BaseEventSchema.extend({\n type: z.literal(\"command_executed\"),\n command: z.string(),\n args: z.array(z.string()),\n cwd: z.string(),\n exit_code: z.number().int().nullable(),\n signal: z.string().nullable().optional(),\n received_signal: z.string().nullable().optional(),\n duration_ms: z.number().int().nonnegative(),\n});\n\nconst GitSnapshotEventSchema = BaseEventSchema.extend({\n type: z.literal(\"git_snapshot\"),\n head: z.string(),\n branch: z.string(),\n dirty: z.boolean(),\n staged: z.array(z.string()),\n unstaged: z.array(z.string()),\n untracked: z.array(z.string()),\n ahead: z.number().int().nonnegative().optional(),\n behind: z.number().int().nonnegative().optional(),\n});\n\nconst FileChangedEventSchema = BaseEventSchema.extend({\n type: z.literal(\"file_changed\"),\n path: z.string(),\n change_type: z.enum([\"added\", \"modified\", \"deleted\", \"renamed\"]),\n // Renamed entries record the previous path here. Optional + nullable to\n // keep the wire format stable for added / modified / deleted events.\n old_path: z.string().nullable().optional(),\n});\n\n// --- Decision / Task / Note events ---\n\n// Decision rich fields are all optional so v0.1 payloads\n// (= core 4 fields only) round-trip unchanged. References are opaque — the\n// schema only validates the SHAPE (EventId format, non-empty / length-capped\n// strings); existence of the referenced event or file is the renderer's\n// concern and surfaces as `(missing)` rather than a parse failure, so\n// import/export round-trips across workspaces never reject on a stale id.\nconst DecisionRecordedEventSchema = BaseEventSchema.extend({\n type: z.literal(\"decision_recorded\"),\n decision_id: DecisionIdSchema,\n title: z.string(),\n rationale: z.string().nullable().optional(),\n alternatives: z.array(z.string().min(1)).optional(),\n rejected_reason: z.string().nullable().optional(),\n linked_events: z.array(EventIdSchema).optional(),\n linked_files: z.array(z.string().min(1).max(4096)).optional(),\n // `track` promotes a decision to a strategic, unfinished DIRECTION (\"the next\n // essential thing to build, and why\") that orientation/handoff resurface every\n // time until it is explicitly closed with `decision void` / supersede — as\n // opposed to a point-in-time `decision`, which is only ever surfaced as the\n // single latest one. This is the intent-continuity layer: a direction agreed\n // in conversation otherwise sinks into the flat decision list and never carries\n // to the next session. Absent (the default) is a plain `decision`, so all\n // pre-existing decision_recorded events round-trip unchanged (additive optional\n // => no schema_version bump; mirrors `note_added.kind`).\n kind: z.enum([\"decision\", \"track\"]).optional(),\n});\n\n// Voids (or supersedes) a previously recorded decision. Append-only: the\n// original `decision_recorded` line is never mutated; this event marks it no\n// longer in force so decisions.md / orientation can strike it and skip it as\n// the \"latest\" direction. `superseded_by` optionally points at the replacement\n// decision (a supersede); absent it is a plain void. Like `linked_events`, the\n// referenced ids are prefix-checked only — existence is a render-time concern,\n// so an import/export round-trip across workspaces never rejects on a stale id.\nconst DecisionVoidedEventSchema = BaseEventSchema.extend({\n type: z.literal(\"decision_voided\"),\n decision_id: DecisionIdSchema,\n reason: z.string().nullable().optional(),\n superseded_by: DecisionIdSchema.optional(),\n});\n\nconst TaskCreatedEventSchema = BaseEventSchema.extend({\n type: z.literal(\"task_created\"),\n task_id: TaskIdSchema,\n title: z.string(),\n});\n\nconst TaskStatusChangedEventSchema = BaseEventSchema.extend({\n type: z.literal(\"task_status_changed\"),\n task_id: TaskIdSchema,\n from: z.string(),\n to: z.string(),\n});\n\n// emitted by `basou task reconcile --write` after broken session\n// references in a task.md are cleaned up. `.strict()` so that any extra field\n// (likely a core-side miscoding of an audit value) is rejected at parse time\n// rather than silently stripped — the event is the audit trail.\nconst TaskReconciledEventSchema = BaseEventSchema.extend({\n type: z.literal(\"task_reconciled\"),\n task_id: TaskIdSchema,\n removed_created_in_session: SessionIdSchema.nullable().default(null),\n created_in_session_replacement: SessionIdSchema.nullable().default(null),\n removed_linked_sessions: z.array(SessionIdSchema).default([]),\n}).strict();\n\n// v0.2: emitted by `basou task refresh-linkage` after the task.md\n// `linked_sessions[]` snapshot is re-derived from `session.yaml.task_id`\n// matches across the workspace. Distinct from `task_reconciled` (= broken\n// ref cleanup) so each event carries a single, focused audit story.\n// `.strict()` for the same reason as TaskReconciledEvent — the event is the\n// authoritative record. The three count/array fields are all optional with\n// sensible defaults so the schema is backward-compatible (= a future caller\n// that omits them parses to an empty refresh, which is also the no-op\n// dry-run record shape).\nconst TaskLinkageRefreshedEventSchema = BaseEventSchema.extend({\n type: z.literal(\"task_linkage_refreshed\"),\n task_id: TaskIdSchema,\n added_linked_sessions: z.array(SessionIdSchema).default([]),\n removed_linked_sessions: z.array(SessionIdSchema).default([]),\n final_count: z.number().int().nonnegative().optional(),\n}).strict();\n\n// v0.2 lifecycle events for `basou task delete` / `basou task archive`.\n// Both are `.strict()` so the audit record is exactly what the orchestrator\n// emits, and both carry the task's last-known title so events.jsonl can\n// describe what was deleted / archived without requiring the (now gone or\n// relocated) task.md.\nconst TaskDeletedEventSchema = BaseEventSchema.extend({\n type: z.literal(\"task_deleted\"),\n task_id: TaskIdSchema,\n title: z.string().min(1),\n}).strict();\n\nconst TaskArchivedEventSchema = BaseEventSchema.extend({\n type: z.literal(\"task_archived\"),\n task_id: TaskIdSchema,\n title: z.string().min(1),\n}).strict();\n\nconst NoteAddedEventSchema = BaseEventSchema.extend({\n type: z.literal(\"note_added\"),\n body: z.string(),\n // `next_step` marks a note authored by `basou note` as the operator's resume\n // hint, which orientation surfaces as the next starting point. Absent (the\n // `basou session note` default) is a plain annotation orientation does not\n // surface. Optional so pre-existing note_added events remain valid.\n kind: z.enum([\"note\", \"next_step\"]).optional(),\n});\n\n// --- Adapter output (`.strict()` rejects raw bodies) ---\n//\n// The spec forbids embedding raw adapter output (`content`, `body`, `raw`,\n// ...) directly in events.jsonl (see\n// `docs/spec/schemas.md#74-adapter_output-constraint-important`). The\n// strict variant rejects any schema-unknown key so that contract is\n// enforced at parse time.\nconst AdapterOutputEventSchema = BaseEventSchema.extend({\n type: z.literal(\"adapter_output\"),\n stream: z.enum([\"stdout\", \"stderr\"]),\n summary: z.string(),\n raw_ref: z.string(),\n redacted: z.boolean().optional(),\n}).strict();\n\n/**\n * Discriminated union of every Basou v0.1 event type. The `type` literal\n * narrows TypeScript to the appropriate variant. The `adapter_output`\n * variant is uniquely strict to bar raw adapter bodies.\n */\nexport const EventSchema = z.discriminatedUnion(\"type\", [\n SessionStartedEventSchema,\n SessionEndedEventSchema,\n SessionStatusChangedEventSchema,\n ApprovalRequestedEventSchema,\n ApprovalApprovedEventSchema,\n ApprovalRejectedEventSchema,\n ApprovalExpiredEventSchema,\n CommandExecutedEventSchema,\n GitSnapshotEventSchema,\n FileChangedEventSchema,\n DecisionRecordedEventSchema,\n DecisionVoidedEventSchema,\n TaskCreatedEventSchema,\n TaskStatusChangedEventSchema,\n TaskReconciledEventSchema,\n TaskLinkageRefreshedEventSchema,\n TaskDeletedEventSchema,\n TaskArchivedEventSchema,\n NoteAddedEventSchema,\n AdapterOutputEventSchema,\n]);\n\n/** Inferred runtime type for any Basou event. */\nexport type Event = z.infer<typeof EventSchema>;\n\n/** Narrowed runtime type for the `session_started` event variant. */\nexport type SessionStartedEvent = z.infer<typeof SessionStartedEventSchema>;\n/** Narrowed runtime type for the `session_ended` event variant. */\nexport type SessionEndedEvent = z.infer<typeof SessionEndedEventSchema>;\n/** Narrowed runtime type for the `session_status_changed` event variant. */\nexport type SessionStatusChangedEvent = z.infer<typeof SessionStatusChangedEventSchema>;\n/** Narrowed runtime type for the `approval_requested` event variant. */\nexport type ApprovalRequestedEvent = z.infer<typeof ApprovalRequestedEventSchema>;\n/** Narrowed runtime type for the `approval_approved` event variant. */\nexport type ApprovalApprovedEvent = z.infer<typeof ApprovalApprovedEventSchema>;\n/** Narrowed runtime type for the `approval_rejected` event variant. */\nexport type ApprovalRejectedEvent = z.infer<typeof ApprovalRejectedEventSchema>;\n/** Narrowed runtime type for the `approval_expired` event variant. */\nexport type ApprovalExpiredEvent = z.infer<typeof ApprovalExpiredEventSchema>;\n/** Narrowed runtime type for the `command_executed` event variant. */\nexport type CommandExecutedEvent = z.infer<typeof CommandExecutedEventSchema>;\n/** Narrowed runtime type for the `git_snapshot` event variant. */\nexport type GitSnapshotEvent = z.infer<typeof GitSnapshotEventSchema>;\n/** Narrowed runtime type for the `file_changed` event variant. */\nexport type FileChangedEvent = z.infer<typeof FileChangedEventSchema>;\n/** Narrowed runtime type for the `decision_recorded` event variant. */\nexport type DecisionRecordedEvent = z.infer<typeof DecisionRecordedEventSchema>;\n/** Narrowed runtime type for the `decision_voided` event variant. */\nexport type DecisionVoidedEvent = z.infer<typeof DecisionVoidedEventSchema>;\n/** Narrowed runtime type for the `task_created` event variant. */\nexport type TaskCreatedEvent = z.infer<typeof TaskCreatedEventSchema>;\n/** Narrowed runtime type for the `task_status_changed` event variant. */\nexport type TaskStatusChangedEvent = z.infer<typeof TaskStatusChangedEventSchema>;\n/** Narrowed runtime type for the `task_reconciled` event variant (.strict()). */\nexport type TaskReconciledEvent = z.infer<typeof TaskReconciledEventSchema>;\n/** Narrowed runtime type for the `task_linkage_refreshed` event variant (.strict()). */\nexport type TaskLinkageRefreshedEvent = z.infer<typeof TaskLinkageRefreshedEventSchema>;\n/** Narrowed runtime type for the `task_deleted` event variant (.strict()). */\nexport type TaskDeletedEvent = z.infer<typeof TaskDeletedEventSchema>;\n/** Narrowed runtime type for the `task_archived` event variant (.strict()). */\nexport type TaskArchivedEvent = z.infer<typeof TaskArchivedEventSchema>;\n/** Narrowed runtime type for the `note_added` event variant. */\nexport type NoteAddedEvent = z.infer<typeof NoteAddedEventSchema>;\n/** Narrowed runtime type for the `adapter_output` event variant (.strict()). */\nexport type AdapterOutputEvent = z.infer<typeof AdapterOutputEventSchema>;\n","import { readdir } from \"node:fs/promises\";\nimport { join } from \"node:path\";\nimport { inspectChainTail } from \"../events/chained-append.js\";\nimport { type ReplayWarning, replayEvents } from \"../events/event-replay.js\";\nimport { findErrorCode } from \"../lib/error-codes.js\";\nimport { type Session, SessionSchema } from \"../schemas/session.schema.js\";\nimport type { BasouPaths } from \"./basou-dir.js\";\nimport { acquireLock } from \"./lockfile.js\";\nimport { overwriteYamlFile, readYamlFile } from \"./yaml-store.js\";\n\n/**\n * Threshold above which a still-`running` session with no `session_ended`\n * event is flagged suspect.\n *\n * 24h: long enough that an active long-running session will not be flagged,\n * short enough that an abandoned process is surfaced within a working day.\n * Tunable via CLI option in a later step (continuation backlog #23).\n */\nexport const STUCK_THRESHOLD_MS = 24 * 60 * 60 * 1000;\n\nexport type SuspectReason = \"events_say_ended_but_yaml_running\" | \"running_no_end_event\";\n\nexport type SessionEntry = {\n sessionId: string;\n session: Session;\n suspect: boolean;\n suspectReason: SuspectReason | null;\n /**\n * The trail store this entry was read from. Its `sessions` directory locates\n * the session's `events.jsonl`, so a federated caller can replay events from\n * the store the session actually lives in (not the local store). For a plain\n * local load this is the `paths` passed to {@link loadSessionEntries}.\n */\n sourceRoot: BasouPaths;\n /**\n * Federation host label from the registry (`~/.basou/hosts.yaml`), or `null`\n * for the local store. Surfaced by orientation so a merged, multi-host view\n * can attribute the latest session / decision / next-step to its host.\n */\n host: string | null;\n};\n\n/**\n * Per-session degradation reason emitted by {@link loadSessionEntries.onSkip}.\n *\n * - `session_yaml_missing` (ENOENT) and `session_yaml_invalid` (parse or schema\n * failure) both omit the entry from the result.\n * - `events_jsonl_unreadable` still pushes the entry with `suspect=false` so\n * the session row remains visible to the caller; only the suspect check is\n * degraded. Matches the existing CLI behaviour at\n * `packages/cli/src/commands/session.ts` (suspect-check stderr warning).\n */\nexport type SessionSkipReason =\n | \"session_yaml_missing\"\n | \"session_yaml_invalid\"\n | \"events_jsonl_unreadable\";\n\nexport type LoadSessionEntriesOptions = {\n /**\n * Single `now` shared across every {@link classifySuspect} call so that\n * sessions classified back-to-back observe the same instant. Avoids\n * boundary races where a session at age ≈ 24h would flip between calls.\n */\n now: Date;\n onWarning?: (warning: ReplayWarning, sessionId: string) => void;\n onSkip?: (sessionId: string, reason: SessionSkipReason) => void;\n};\n\n/**\n * A trail store to read in a federated load, tagged with its host label.\n * `host: null` denotes the local store; a non-null label comes from the host\n * registry (`~/.basou/hosts.yaml`). `paths` is where that store is reachable\n * as a local path on this machine (an SSHFS mount, an rsync mirror, etc.) —\n * basou itself never performs any network I/O to obtain it.\n */\nexport type FederatedRoot = { paths: BasouPaths; host: string | null };\n\nexport type LoadFederatedOptions = LoadSessionEntriesOptions & {\n /**\n * Called when a NON-local root cannot be enumerated (present-but-unreadable\n * mount, permission error). That root is skipped best-effort so the local\n * store and other roots still load. The local root (`host: null`) is never\n * degraded here — its errors propagate, preserving single-store behaviour.\n * (An absent root path is not an error: {@link enumerateSessionDirs} returns\n * `[]` on ENOENT, so a dropped mount is simply an empty host.)\n */\n onRootUnavailable?: (host: string, error: unknown) => void;\n};\n\n/**\n * List session directory names under `paths.sessions`, ULID ascending.\n *\n * - Returns `[]` when the sessions directory does not exist (empty workspace\n * or pre-init state).\n * - Throws `Error(\"Failed to enumerate sessions\", { cause })` on other I/O.\n * - Only directories are returned (`.gitkeep` and other files are filtered).\n *\n * Sort order is `Array.prototype.sort()` default (Unicode code-point\n * compare). ULIDs are Crockford base32 in uppercase, so the natural sort\n * is also chronological session-start order.\n */\nexport async function enumerateSessionDirs(paths: BasouPaths): Promise<string[]> {\n try {\n const dirents = await readdir(paths.sessions, { withFileTypes: true });\n return dirents\n .filter((d) => d.isDirectory())\n .map((d) => d.name)\n .sort();\n } catch (error: unknown) {\n if (findErrorCode(error, \"ENOENT\")) return [];\n throw new Error(\"Failed to enumerate sessions\", { cause: error });\n }\n}\n\n/**\n * Read and validate `<paths.sessions>/<sessionId>/session.yaml`.\n *\n * - Re-throws the yaml-store fixed-message `\"YAML file not found\"` for\n * ENOENT so the caller can branch on it.\n * - Throws `Error(\"Failed to read session.yaml\", { cause })` for parse\n * failures and schema violations (cause is either the YAML parser error\n * or the zod error).\n */\nexport async function readSessionYaml(paths: BasouPaths, sessionId: string): Promise<Session> {\n const filePath = join(paths.sessions, sessionId, \"session.yaml\");\n let raw: unknown;\n try {\n raw = await readYamlFile(filePath);\n } catch (error: unknown) {\n if (error instanceof Error && error.message === \"YAML file not found\") throw error;\n throw new Error(\"Failed to read session.yaml\", { cause: error });\n }\n const result = SessionSchema.safeParse(raw);\n if (!result.success) {\n throw new Error(\"Failed to read session.yaml\", { cause: result.error });\n }\n return result.data;\n}\n\n/**\n * Apply a terminal-status mutation to a live session's `session.yaml` AND, in\n * the same locked write, stamp the tamper-evidence head anchor derived from the\n * on-disk `events.jsonl` tail. Used by the `exec` / `run` orchestrators for\n * BOTH terminal writers (the normal end-of-run finalize and the spawn-failure\n * `failed` finalize).\n *\n * Why locked + anchor-from-tail: live appends chain the LOG only and leave the\n * anchor for finalize. Reading the final tail under the session lock means a\n * foreign line appended just before finalize (e.g. a `decision record` attached\n * to a still-running session) is included in the anchor, and a foreign attach\n * that arrives after the terminal status is set is rejected by the attach gate\n * — so the anchor can never disagree with the at-rest log. The whole-document\n * read-modify-write also preserves any field a foreign locked writer set (e.g.\n * a task attach's `task_id`).\n *\n * The anchor is written only when the log is actually chained with at least one\n * line; a legacy unchained session (and an empty log) is left with no\n * `integrity` anchor, matching the import writers. The mutator receives the\n * full {@link Session} document and typically sets\n * `session.session.status` / `ended_at` / `invocation.exit_code` /\n * `related_files`.\n *\n * Throws the {@link inspectChainTail} errors (torn / mixed log), the\n * {@link readSessionYaml} errors, a zod error if the mutation produces an\n * invalid document, or `Error(\"Failed to overwrite YAML file\")` on a disk\n * failure.\n */\nexport async function finalizeSessionYaml(\n paths: BasouPaths,\n sessionId: string,\n mutate: (session: Session) => void,\n): Promise<void> {\n const lock = await acquireLock(paths, \"session\", sessionId);\n try {\n const session = await readSessionYaml(paths, sessionId);\n mutate(session);\n const tail = await inspectChainTail(paths, sessionId);\n if (tail.chained && tail.count > 0) {\n session.session.integrity = { head_hash: tail.head, event_count: tail.count };\n }\n const validated = SessionSchema.parse(session);\n await overwriteYamlFile(join(paths.sessions, sessionId, \"session.yaml\"), validated);\n } finally {\n await lock.release();\n }\n}\n\n/**\n * Classify a `running` session as suspect using one of two rules:\n *\n * - Rule A (`events_say_ended_but_yaml_running`): events.jsonl contains a\n * `session_ended` event but the session.yaml is still `running`. The\n * session ended cleanly in the event log but the YAML write was lost or\n * never reached.\n * - Rule B (`running_no_end_event`): no `session_ended` event and the last\n * event is older than {@link STUCK_THRESHOLD_MS}. The process likely\n * crashed or was killed.\n *\n * Sessions that are not `running` are never suspect.\n *\n * I/O failure on events.jsonl is re-thrown unwrapped so the caller can\n * degrade with a warning instead of treating the session as healthy. The\n * caller is also responsible for surfacing replay warnings via `onWarning`.\n */\nexport async function classifySuspect(\n paths: BasouPaths,\n sessionId: string,\n session: Session,\n now: Date,\n onWarning?: (warning: ReplayWarning) => void,\n): Promise<{ suspect: boolean; suspectReason: SuspectReason | null }> {\n if (session.session.status !== \"running\") {\n return { suspect: false, suspectReason: null };\n }\n const sessionDir = join(paths.sessions, sessionId);\n let endedFound = false;\n let lastEventOccurredAt: string | null = null;\n // Forward onWarning only when supplied — `exactOptionalPropertyTypes`\n // rejects passing a literal `undefined` for an optional property.\n const replayOpts = onWarning !== undefined ? { onWarning } : {};\n for await (const ev of replayEvents(sessionDir, replayOpts)) {\n lastEventOccurredAt = ev.occurred_at;\n if (ev.type === \"session_ended\") endedFound = true;\n }\n if (endedFound) {\n return { suspect: true, suspectReason: \"events_say_ended_but_yaml_running\" };\n }\n if (lastEventOccurredAt !== null) {\n const ageMs = now.getTime() - Date.parse(lastEventOccurredAt);\n if (Number.isFinite(ageMs) && ageMs > STUCK_THRESHOLD_MS) {\n return { suspect: true, suspectReason: \"running_no_end_event\" };\n }\n }\n return { suspect: false, suspectReason: null };\n}\n\n/**\n * High-level helper that enumerates session dirs, reads each `session.yaml`,\n * and classifies suspect for `running` sessions in one pass.\n *\n * Per-session degradations are surfaced via `options.onSkip`:\n * - `session_yaml_missing` (ENOENT) and `session_yaml_invalid` (parse or\n * schema violation): the entry is omitted from the result.\n * - `events_jsonl_unreadable`: the entry is still pushed with `suspect=false`\n * so callers can render the session row plus a CLI-side warning.\n *\n * `options.now` is taken once and threaded into every {@link classifySuspect}\n * call so age comparisons are consistent across sessions.\n */\nasync function loadEntriesFromRoot(\n root: FederatedRoot,\n options: LoadSessionEntriesOptions,\n): Promise<SessionEntry[]> {\n const { paths } = root;\n const sessionIds = await enumerateSessionDirs(paths);\n const entries: SessionEntry[] = [];\n for (const sid of sessionIds) {\n let session: Session;\n try {\n session = await readSessionYaml(paths, sid);\n } catch (error: unknown) {\n if (error instanceof Error && error.message === \"YAML file not found\") {\n options.onSkip?.(sid, \"session_yaml_missing\");\n } else {\n options.onSkip?.(sid, \"session_yaml_invalid\");\n }\n continue;\n }\n let suspect = false;\n let suspectReason: SuspectReason | null = null;\n try {\n const r = await classifySuspect(paths, sid, session, options.now, (w) =>\n options.onWarning?.(w, sid),\n );\n suspect = r.suspect;\n suspectReason = r.suspectReason;\n } catch {\n // events.jsonl I/O failure (EACCES etc.) on the suspect check is\n // unrecoverable for the classification but should not drop the session\n // entry. Surface a dedicated reason so the caller can distinguish a\n // broken events.jsonl from a broken session.yaml.\n options.onSkip?.(sid, \"events_jsonl_unreadable\");\n }\n entries.push({\n sessionId: sid,\n session,\n suspect,\n suspectReason,\n sourceRoot: paths,\n host: root.host,\n });\n }\n return entries;\n}\n\nexport async function loadSessionEntries(\n paths: BasouPaths,\n options: LoadSessionEntriesOptions,\n): Promise<SessionEntry[]> {\n return loadEntriesFromRoot({ paths, host: null }, options);\n}\n\n/**\n * Federated load across multiple trail stores. Each root's sessions are tagged\n * with that root's host label and `sourceRoot`, so a caller replays events from\n * the store the session lives in. De-duped by `sessionId` (a per-host random\n * ULID), then by `source.external_id` when present — first occurrence wins, so\n * pass the local root FIRST to keep it authoritative (e.g. over a re-imported\n * copy of the same vendor session on another host). A non-local root that\n * cannot be enumerated is reported via `onRootUnavailable` and skipped; the\n * local root's errors propagate, matching {@link loadSessionEntries}.\n */\nexport async function loadFederatedSessionEntries(\n roots: ReadonlyArray<FederatedRoot>,\n options: LoadFederatedOptions,\n): Promise<SessionEntry[]> {\n const out: SessionEntry[] = [];\n const seenIds = new Set<string>();\n const seenExternal = new Set<string>();\n for (const root of roots) {\n let entries: SessionEntry[];\n if (root.host === null) {\n entries = await loadEntriesFromRoot(root, options);\n } else {\n try {\n entries = await loadEntriesFromRoot(root, options);\n } catch (error: unknown) {\n options.onRootUnavailable?.(root.host, error);\n continue;\n }\n }\n for (const entry of entries) {\n if (seenIds.has(entry.sessionId)) continue;\n const ext = entry.session.session.source.external_id;\n if (typeof ext === \"string\" && ext.length > 0) {\n // Namespace by source kind: external_id lives in the ORIGINATING tool's\n // own id space, so the same value under a different tool is a different\n // session and must not collapse.\n const extKey = `${entry.session.session.source.kind}:${ext}`;\n if (seenExternal.has(extKey)) continue;\n seenExternal.add(extKey);\n }\n seenIds.add(entry.sessionId);\n out.push(entry);\n }\n }\n return out;\n}\n","import { appendFile, readFile } from \"node:fs/promises\";\nimport { join } from \"node:path\";\nimport { findErrorCode } from \"../lib/error-codes.js\";\nimport { type Event, EventSchema } from \"../schemas/event.schema.js\";\nimport type { BasouPaths } from \"../storage/basou-dir.js\";\nimport { acquireLock } from \"../storage/lockfile.js\";\nimport { genesisHash, lineHash, serializeEventLine } from \"./chain.js\";\n\n/**\n * The chain state of an existing `events.jsonl`, as needed by the live append\n * and finalize paths.\n *\n * - `chained` — whether the NEXT line written to this log must carry a\n * `prev_hash`. True for an empty / not-yet-created log (a fresh session\n * chains from its genesis) and for a log whose FIRST complete line already\n * carries `prev_hash`. False for a legacy / pre-feature log whose first line\n * is unchained (so it stays unchained — we never half-chain a file).\n * - `head` — the `prev_hash` value the next line carries when `chained`:\n * `genesisHash(sessionId)` for an empty log, otherwise `lineHash` of the LAST\n * complete line's raw bytes. Meaningless (set to the genesis hash) when\n * `chained` is false.\n * - `count` — number of complete (newline-terminated) lines on disk; the\n * `event_count` an integrity anchor records.\n */\nexport type ChainTailState = {\n chained: boolean;\n head: string;\n count: number;\n};\n\n// Byte-level line split on 0x0A. The caller guarantees the buffer is\n// newline-terminated, so every returned entry is a complete line and there is\n// no trailing fragment. Subarray views, no copying.\nfunction splitLinesBytes(buf: Buffer): Buffer[] {\n const out: Buffer[] = [];\n let start = 0;\n for (let i = 0; i < buf.length; i++) {\n if (buf[i] === 0x0a) {\n out.push(buf.subarray(start, i));\n start = i + 1;\n }\n }\n if (start < buf.length) out.push(buf.subarray(start));\n return out;\n}\n\n// Does this raw line decode to a JSON object carrying a top-level `prev_hash`?\n// A line that fails to parse is treated as not carrying one (our writers never\n// emit such a line; verify is the detector for a corrupt chained log).\nfunction carriesPrevHash(line: Buffer): boolean {\n try {\n const obj: unknown = JSON.parse(line.toString(\"utf8\"));\n return typeof obj === \"object\" && obj !== null && \"prev_hash\" in obj;\n } catch {\n return false;\n }\n}\n\n/**\n * Inspect `<sessions>/<sessionId>/events.jsonl` to decide how the next append\n * (or the finalize anchor) must treat the chain. READ-ONLY; the caller MUST\n * already hold the session lock so the inspected tail cannot move underneath a\n * subsequent append.\n *\n * Chained-ness is decided from the FIRST complete line (does the log claim to\n * be chained), and the head pointer is taken from the LAST complete line. If\n * the first and last lines DISAGREE — a mixed / partially-tampered file — the\n * call THROWS rather than extending a broken chain; verify is the detector, the\n * writer must not deepen a break. An unterminated final line (a torn tail from\n * a crashed prior append) also THROWS so a new line is never glued onto a\n * fragment.\n *\n * Throws `Error(\"Failed to read events.jsonl\")` for non-ENOENT I/O,\n * `Error(\"Unterminated final line in events.jsonl\")` for a torn tail, and\n * `Error(\"events.jsonl is partially chained\")` for a mixed first/last line.\n */\nexport async function inspectChainTail(\n paths: BasouPaths,\n sessionId: string,\n): Promise<ChainTailState> {\n const filePath = join(paths.sessions, sessionId, \"events.jsonl\");\n let raw: Buffer;\n try {\n raw = await readFile(filePath);\n } catch (error: unknown) {\n if (findErrorCode(error, \"ENOENT\")) {\n // A not-yet-created log: the first append chains from the session genesis.\n return { chained: true, head: genesisHash(sessionId), count: 0 };\n }\n throw new Error(\"Failed to read events.jsonl\", { cause: error });\n }\n if (raw.length === 0) {\n return { chained: true, head: genesisHash(sessionId), count: 0 };\n }\n if (raw[raw.length - 1] !== 0x0a) {\n throw new Error(\"Unterminated final line in events.jsonl\");\n }\n const lines = splitLinesBytes(raw);\n const first = lines[0] as Buffer;\n const last = lines[lines.length - 1] as Buffer;\n const firstChained = carriesPrevHash(first);\n if (firstChained !== carriesPrevHash(last)) {\n throw new Error(\"events.jsonl is partially chained\");\n }\n return {\n chained: firstChained,\n head: firstChained ? lineHash(last) : genesisHash(sessionId),\n count: lines.length,\n };\n}\n\n/**\n * Append one event to `<sessions>/<sessionId>/events.jsonl`, threading the\n * tamper-evidence hash chain. The caller MUST already hold the session lock\n * (`acquireLock(paths, \"session\", sessionId)`); this function does NOT acquire\n * it, so it composes inside a larger caller-owned critical section (the\n * convention used by `decision record`, `session note`, task attach and\n * approval resolution) without re-entrant lock deadlock.\n *\n * The event is validated against {@link EventSchema}, then — if the existing\n * log is chained (or empty) — written with a `prev_hash` back-pointer derived\n * from the real on-disk tail (see {@link inspectChainTail}); a legacy unchained\n * log keeps receiving plain unchained lines. The single serializer\n * ({@link serializeEventLine}) is shared with the bulk writers so the bytes a\n * chain hashes can never diverge from another path's bytes.\n *\n * Does NOT touch `session.yaml.integrity`: the head anchor is written once, at\n * the terminal-status finalize, by {@link finalizeSessionYaml}. A still-live\n * session therefore has a chained log but no anchor yet, which `verify` reports\n * as the benign `in_progress`.\n *\n * Throws `\"Invalid Basou event payload\"` on validation failure, the\n * {@link inspectChainTail} errors on a torn / mixed log, or `\"Failed to append\n * event to events.jsonl\"` on a disk failure. The native error is attached as\n * `cause`.\n */\nexport async function appendChainedEventLocked(\n paths: BasouPaths,\n sessionId: string,\n event: unknown,\n): Promise<{ chained: boolean }> {\n let validated: Event;\n try {\n validated = EventSchema.parse(event);\n } catch (error: unknown) {\n throw new Error(\"Invalid Basou event payload\", { cause: error });\n }\n const tail = await inspectChainTail(paths, sessionId);\n const line = tail.chained\n ? serializeEventLine({ ...validated, prev_hash: tail.head })\n : serializeEventLine(validated);\n try {\n await appendFile(join(paths.sessions, sessionId, \"events.jsonl\"), `${line}\\n`, \"utf8\");\n } catch (error: unknown) {\n throw new Error(\"Failed to append event to events.jsonl\", { cause: error });\n }\n return { chained: tail.chained };\n}\n\n/**\n * Self-locking wrapper around {@link appendChainedEventLocked} for callers that\n * do NOT already hold the session lock (the `exec` / `run` orchestrators, which\n * append one event at a time to a session they own). Acquires the session lock,\n * appends, and releases. Each append is a short-lived lock hold — the lock is\n * NEVER held across a child process — so a foreign attach can interleave safely\n * and the next append chains onto the true tail.\n */\nexport async function appendChainedEvent(\n paths: BasouPaths,\n sessionId: string,\n event: unknown,\n): Promise<{ chained: boolean }> {\n const lock = await acquireLock(paths, \"session\", sessionId);\n try {\n return await appendChainedEventLocked(paths, sessionId, event);\n } finally {\n await lock.release();\n }\n}\n","import { mkdir, readFile, unlink } from \"node:fs/promises\";\nimport { dirname, join } from \"node:path\";\nimport { findErrorCode } from \"../lib/error-codes.js\";\nimport { atomicCreate } from \"./atomic.js\";\nimport type { BasouPaths } from \"./basou-dir.js\";\n\n/**\n * The two lock scopes basou uses. `task` guards the read-modify-write window\n * around a single `task.md`; `session` guards the events.jsonl append plus\n * surrounding `session.yaml` mutation for a single session. Two scopes use\n * different lockfile names so they never collide on disk.\n */\nexport type LockScope = \"task\" | \"session\";\n\n/**\n * Any lock older than this is treated as stale and force-released even if the\n * holding pid is still alive. basou CLI invocations hold a lock for ms to a\n * few seconds at most, so an hour is a 10000x safety margin; the upper bound\n * is also our defence against pid reuse (a different process happening to\n * receive a long-dead pid).\n */\nconst STALE_LOCK_MAX_AGE_MS = 60 * 60 * 1000;\n\ntype LockFileBody = {\n pid: number;\n acquired_at: string;\n};\n\nexport type LockHandle = {\n /**\n * Release the lock by unlinking the lockfile. Best-effort: any unlink error\n * is swallowed so a doubled release does not raise, and disk state never\n * holds a stranded lockfile after the caller's `finally` block.\n */\n release: () => Promise<void>;\n};\n\n/**\n * Acquire an advisory lock at `<paths.locks>/<scope>_<id>.lock` for the\n * lifetime of the returned handle. Lockfile body records the holder's pid\n * and acquire timestamp so a competitor can detect stale locks left by a\n * SIGINT'd CLI run and recover automatically.\n *\n * Acquisition strategy:\n * 1. {@link atomicCreate} the lockfile (POSIX link(2) + EEXIST).\n * On ENOENT (a workspace from before `.basou/locks/` existed), create\n * the directory and retry once; a retry failure throws the pathless\n * `\"Failed to acquire lock\"`.\n * 2. On EEXIST, probe the existing lockfile via {@link isStaleLock}.\n * - If stale (= holder pid is dead or lock is older than\n * {@link STALE_LOCK_MAX_AGE_MS}), `unlink` the stale file and retry\n * the atomic create once.\n * - If still EEXIST after the retry (= another competitor won the race),\n * throw `\"Lock is held by another process\"`.\n * - If the holder is alive, throw `\"Lock is held by another process\"`\n * without retrying.\n *\n * The caller MUST call `release()` (typically from a `finally` block); the\n * `process.exit()` path or a fatal crash relies on stale-lock detection on\n * the next acquire to recover.\n */\nexport async function acquireLock(\n paths: BasouPaths,\n scope: LockScope,\n resourceId: string,\n): Promise<LockHandle> {\n const lockPath = lockfilePath(paths, scope, resourceId);\n const body: LockFileBody = {\n pid: process.pid,\n acquired_at: new Date().toISOString(),\n };\n const serialised = JSON.stringify(body);\n\n try {\n await atomicCreate(lockPath, serialised);\n } catch (error: unknown) {\n // A workspace checked out (or created) before the locks directory\n // existed lacks `.basou/locks/`; create it and retry once rather than\n // failing every lock-taking command on such a workspace.\n if (findErrorCode(error, \"ENOENT\")) {\n try {\n await mkdir(dirname(lockPath), { recursive: true });\n await atomicCreate(lockPath, serialised);\n return {\n release: async () => {\n await unlink(lockPath).catch(() => undefined);\n },\n };\n } catch (retryError: unknown) {\n throw new Error(\"Failed to acquire lock\", { cause: retryError });\n }\n }\n if (!findErrorCode(error, \"EEXIST\")) {\n throw error;\n }\n const stale = await isStaleLock(lockPath);\n if (!stale) {\n throw new Error(\"Lock is held by another process\", { cause: error });\n }\n // Best-effort cleanup of the stale lockfile, then a single retry. A\n // second EEXIST means another competitor beat us to the cleared lock;\n // surface that as a normal \"held\" failure rather than looping.\n await unlink(lockPath).catch(() => undefined);\n try {\n await atomicCreate(lockPath, serialised);\n } catch (retryError: unknown) {\n throw new Error(\"Lock is held by another process\", { cause: retryError });\n }\n }\n\n return {\n release: async () => {\n await unlink(lockPath).catch(() => undefined);\n },\n };\n}\n\n/**\n * Read the lockfile at `lockPath` and decide whether the holder is dead or\n * the lock is too old to trust. Used by {@link acquireLock} on EEXIST to\n * recover from SIGINT'd CLI runs that left the lockfile behind.\n *\n * Stale predicates (any of these = stale):\n * - lockfile body unreadable or malformed\n * - `acquired_at` is older than {@link STALE_LOCK_MAX_AGE_MS}\n * - `process.kill(pid, 0)` throws ESRCH (holder pid is dead)\n *\n * EPERM from `process.kill` means the pid is alive but owned by a different\n * uid; we treat that as alive so cross-user lockfile takeover does not happen\n * by accident.\n */\nasync function isStaleLock(lockPath: string): Promise<boolean> {\n let body: LockFileBody;\n try {\n const raw = await readFile(lockPath, \"utf8\");\n const parsed = JSON.parse(raw) as unknown;\n if (typeof parsed !== \"object\" || parsed === null) return true;\n const candidate = parsed as Partial<LockFileBody>;\n if (typeof candidate.pid !== \"number\" || typeof candidate.acquired_at !== \"string\") {\n return true;\n }\n body = { pid: candidate.pid, acquired_at: candidate.acquired_at };\n } catch {\n // Unreadable lockfile (e.g. truncated mid-write) counts as stale so we\n // can recover instead of looping forever on EEXIST.\n return true;\n }\n const ageMs = Date.now() - Date.parse(body.acquired_at);\n if (!Number.isFinite(ageMs) || ageMs > STALE_LOCK_MAX_AGE_MS) {\n return true;\n }\n try {\n process.kill(body.pid, 0);\n return false;\n } catch (error: unknown) {\n if (findErrorCode(error, \"ESRCH\")) return true;\n // EPERM or any other surface — pid is alive (or unknown), keep the lock.\n return false;\n }\n}\n\nfunction lockfilePath(paths: BasouPaths, scope: LockScope, resourceId: string): string {\n // Strip the type prefix to keep the lockfile name compact (`task_01HX...` →\n // `01HX...`, `ses_01HX...` → `01HX...`). The scope literal at the start of\n // the filename keeps task/session lockfiles disjoint even when the ULID\n // tails happen to coincide.\n const sep = resourceId.indexOf(\"_\");\n const ulid = sep >= 0 ? resourceId.slice(sep + 1) : resourceId;\n return join(paths.locks, `${scope}_${ulid}.lock`);\n}\n","import { createHash } from \"node:crypto\";\nimport type { Event } from \"../schemas/event.schema.js\";\n\n// Domain-separation prefix for the chain's genesis hash. Versioned so a\n// future chain format can re-anchor without colliding with v1 hashes.\nconst GENESIS_PREFIX = \"basou:event-chain:v1:\";\n\n/**\n * Session-bound genesis hash: the `prev_hash` carried by the FIRST event line\n * of a chained `events.jsonl`. Binding the genesis to the session id means a\n * chain copied verbatim from another session fails verification at line 1\n * even though its internal back-pointers are intact.\n */\nexport function genesisHash(sessionId: string): string {\n return createHash(\"sha256\").update(`${GENESIS_PREFIX}${sessionId}`, \"utf8\").digest(\"hex\");\n}\n\n/**\n * Hex sha-256 of one event line's written bytes (EXCLUDING the trailing\n * `\\n`). The hash covers the literal serialized bytes — no canonical JSON\n * form. Writers pass the line string (always valid UTF-8, so its UTF-8\n * encoding IS the written bytes); the verifier passes the RAW BYTES it read,\n * so a byte-level mutation that decodes to the same string (e.g. an invalid\n * UTF-8 sequence collapsing to U+FFFD) still breaks the chain.\n */\nexport function lineHash(rawLine: string | Buffer): string {\n const hash = createHash(\"sha256\");\n if (typeof rawLine === \"string\") {\n hash.update(rawLine, \"utf8\");\n } else {\n hash.update(rawLine);\n }\n return hash.digest(\"hex\");\n}\n\n/**\n * The single serializer for event lines. Every writer (bulk and append) MUST\n * go through this function so the bytes a chain hashes can never diverge from\n * the bytes another code path would write.\n */\nexport function serializeEventLine(event: Event): string {\n return JSON.stringify(event);\n}\n\n/** Result of {@link chainEvents}: the serialized lines plus the head anchor inputs. */\nexport type ChainedEvents = {\n /** Serialized event lines (no trailing newline on the entries). */\n lines: string[];\n /**\n * Hex sha-256 of the LAST line — the value `session.yaml.integrity.head_hash`\n * anchors. For an empty batch this is the genesis hash and no anchor is\n * written.\n */\n headHash: string;\n /** Number of chained lines (= `integrity.event_count`). */\n count: number;\n};\n\n/**\n * Thread a `prev_hash` back-pointer through `events` and serialize them:\n * line 0 carries `genesisHash(sessionId)`, line N carries the hash of line\n * N-1's written bytes. Any `prev_hash` already present on an incoming event\n * (e.g. a round-trip import payload) is discarded and recomputed — chains are\n * never trusted from input, only derived at write time.\n */\nexport function chainEvents(events: ReadonlyArray<Event>, sessionId: string): ChainedEvents {\n let prev = genesisHash(sessionId);\n const lines: string[] = [];\n for (const event of events) {\n // Spread + override discards the incoming prev_hash VALUE; key order is\n // irrelevant because hashing covers the literal written bytes.\n const chained: Event = { ...event, prev_hash: prev };\n const line = serializeEventLine(chained);\n lines.push(line);\n prev = lineHash(line);\n }\n return { lines, headHash: prev, count: lines.length };\n}\n\n/**\n * Chain PRE-EXISTING serialized event lines WITHOUT re-serializing them\n * through the schema layer: each original line is JSON-parsed, given a\n * `prev_hash`, and stringified again. Because `JSON.parse`/`JSON.stringify`\n * round-trips key insertion order and every value verbatim, the output line\n * is the original line with only the `prev_hash` member appended — no zod\n * key-stripping or default materialization can occur. Used by the in-place\n * rechain migration of pre-chaining imported sessions.\n *\n * Callers MUST gate validity first (non-empty, JSON-parseable,\n * schema-valid, byte-identical JSON round-trip); this helper assumes\n * parseable lines and throws raw on a parse failure.\n */\nexport function chainRawJsonLines(\n rawLines: ReadonlyArray<string>,\n sessionId: string,\n): ChainedEvents {\n let prev = genesisHash(sessionId);\n const lines: string[] = [];\n for (const rawLine of rawLines) {\n const parsed = JSON.parse(rawLine) as Record<string, unknown>;\n const line = JSON.stringify({ ...parsed, prev_hash: prev });\n lines.push(line);\n prev = lineHash(line);\n }\n return { lines, headHash: prev, count: lines.length };\n}\n","import { z } from \"zod\";\nimport {\n IsoTimestampSchema,\n SchemaVersionSchema,\n SessionIdSchema,\n TaskIdSchema,\n WorkspaceIdSchema,\n} from \"./shared.schema.js\";\n\n/** Session lifecycle states. */\nexport const SessionStatusSchema = z.enum([\n \"initialized\",\n \"running\",\n \"waiting_approval\",\n \"completed\",\n \"failed\",\n \"interrupted\",\n \"imported\",\n \"archived\",\n]);\n/** Inferred runtime type for {@link SessionStatusSchema}. */\nexport type SessionStatus = z.infer<typeof SessionStatusSchema>;\n\n/**\n * Source kind that produced the session.\n *\n * - `claude-code-adapter` — a live `basou run claude-code` process wrap.\n * - `claude-code-import` — derived after the fact from a Claude Code native\n * transcript (`~/.claude/projects/*.jsonl`) by `basou import claude-code`.\n * - `codex-import` — derived after the fact from an OpenAI Codex native\n * rollout log (date-partitioned `~/.codex/sessions`) by `basou import codex`.\n * - `import` — a round-trip of a Basou-format export (`basou session import`).\n * - `human` / `terminal` — manually-authored / terminal-recorded sessions.\n */\nexport const SessionSourceKindSchema = z.enum([\n \"claude-code-adapter\",\n \"claude-code-import\",\n \"codex-import\",\n \"human\",\n \"import\",\n \"terminal\",\n]);\n/** Inferred runtime type for {@link SessionSourceKindSchema}. */\nexport type SessionSourceKind = z.infer<typeof SessionSourceKindSchema>;\n\nconst SessionSourceSchema = z.object({\n kind: SessionSourceKindSchema,\n version: z.literal(\"0.1.0\"),\n // Optional id of the originating session in the SOURCE tool's own\n // namespace (e.g. the Claude Code session UUID for a `claude-code-import`).\n // Lets re-imports of the same source be deduplicated; absent for live runs.\n external_id: z.string().optional(),\n // Byte size of the source native log at import time, recorded so a later\n // import can detect that an append-only transcript GREW and re-import it\n // (scoped, preserving the session id) instead of skipping it as already\n // imported. Additive optional => no schema_version bump (precedent:\n // external_id, metrics). Absent on sessions imported before this field\n // existed (treated as legacy: never auto-re-imported, populated on the next\n // fresh import or `--force`).\n source_size_bytes: z.number().int().nonnegative().optional(),\n});\n\nconst InvocationSchema = z.object({\n command: z.string().min(1),\n args: z.array(z.string()).default([]),\n // Nullable to record signal-terminated runs where the child has no exit\n // code; the same nullability is mirrored in CommandExecutedEventSchema.\n exit_code: z.number().int().nullable(),\n});\n\n/**\n * Optional per-session metrics, computed at import time from the source tool's\n * native log. Two groups, both optional because not every source records them:\n *\n * - Model-usage rollup (`*_tokens`): the transcript carries per-message token\n * usage; these are the session totals. `reasoning_output_tokens` is\n * Codex-only, and live `run`/`exec` sessions carry no token usage at all.\n * - Engaged-time metrics (`active_*`): the billing-oriented active time derived\n * from the session's genuine engagement timestamps (conversation turns plus\n * action events), with idle gaps capped. `active_intervals` are the merged\n * wall-clock ranges (so cross-session totals can de-duplicate overlapping\n * work by interval union); `active_time_ms` is their summed duration;\n * `active_gap_cap_ms` and `active_time_method` lock the methodology so the\n * stored numbers stay interpretable if the method changes later. When a\n * source records explicit per-turn intervals (Codex), `active_time_method` is\n * `turn-intervals` and the in-turn time is the log's real wall-clock span\n * rather than a gap-capped approximation; the active semantics are unchanged.\n * - `machine_active_time_ms`: model compute time — the summed duration of the\n * source's per-turn spans (Codex `task_complete.duration_ms`), a SUBSET of a\n * single session's engaged active time. Unlike `active_intervals` it is a\n * plain sum, NOT wall-clock-deduplicated, so two concurrent sessions can sum\n * past their billable (union) active wall-clock — that is intended (two models\n * working at once did two machine-hours in one wall-clock hour). Captured only\n * for sources that record per-turn duration (Codex); absent otherwise.\n *\n * Absent on sessions imported before a given field existed (re-import to\n * backfill). Live sessions carry no engaged-time metrics and fall back to\n * event-derived active time at stats time.\n */\nexport const SessionMetricsSchema = z.object({\n output_tokens: z.number().int().nonnegative().optional(),\n input_tokens: z.number().int().nonnegative().optional(),\n cached_input_tokens: z.number().int().nonnegative().optional(),\n reasoning_output_tokens: z.number().int().nonnegative().optional(),\n active_time_ms: z.number().int().nonnegative().optional(),\n active_intervals: z\n .array(z.object({ start: IsoTimestampSchema, end: IsoTimestampSchema }))\n .optional(),\n active_gap_cap_ms: z.number().int().nonnegative().optional(),\n active_time_method: z.string().optional(),\n machine_active_time_ms: z.number().int().nonnegative().optional(),\n});\n/** Inferred runtime type for {@link SessionMetricsSchema}. */\nexport type SessionMetrics = z.infer<typeof SessionMetricsSchema>;\n\n/**\n * Tamper-evidence head anchor for a session whose `events.jsonl` is hash\n * chained: `head_hash` is the hex sha-256 of the last written event line\n * (excluding the trailing newline), `event_count` the number of chained lines.\n * Written by the import / in-place re-import writers and, for a live session\n * (`exec` / `run` / ad-hoc), by the finalize once it reaches a terminal status.\n * Absent on a still-live session (the anchor is stamped at finalize) and on a\n * pre-feature unchained session. Additive optional => no schema_version bump.\n * `.strict()` because the writers fully own the shape.\n */\nexport const SessionIntegritySchema = z\n .object({\n head_hash: z.string(),\n event_count: z.number().int().nonnegative(),\n })\n .strict();\n/** Inferred runtime type for {@link SessionIntegritySchema}. */\nexport type SessionIntegrity = z.infer<typeof SessionIntegritySchema>;\n\nconst SessionInnerSchema = z.object({\n id: SessionIdSchema,\n label: z.string().optional(),\n task_id: TaskIdSchema.nullable().optional(),\n workspace_id: WorkspaceIdSchema,\n source: SessionSourceSchema,\n started_at: IsoTimestampSchema,\n // ended_at is optional because initialized / running sessions have no end time yet.\n ended_at: IsoTimestampSchema.optional(),\n status: SessionStatusSchema,\n working_directory: z.string().min(1),\n invocation: InvocationSchema,\n related_files: z.array(z.string()).default([]),\n events_log: z.string().default(\"events.jsonl\"),\n summary: z.string().nullable().optional(),\n metrics: SessionMetricsSchema.optional(),\n integrity: SessionIntegritySchema.optional(),\n});\n\n/**\n * Schema for `.basou/sessions/<session_id>/session.yaml`. The minimal\n * session document carries the actual fields nested under the outer\n * `session:` key.\n */\nexport const SessionSchema = z.object({\n schema_version: SchemaVersionSchema,\n session: SessionInnerSchema,\n});\n\n/** Inferred runtime type for {@link SessionSchema}. */\nexport type Session = z.infer<typeof SessionSchema>;\n","import { appendFile } from \"node:fs/promises\";\nimport { basename, join } from \"node:path\";\nimport { type Event, EventSchema } from \"../schemas/event.schema.js\";\nimport { atomicReplace } from \"../storage/atomic.js\";\nimport { chainEvents, serializeEventLine } from \"./chain.js\";\n\n/**\n * Append a single Basou event to `<sessionDir>/events.jsonl`.\n *\n * The event is validated against the discriminated union {@link EventSchema}\n * before being serialized as a single JSONL line (UTF-8, terminated by `\\n`).\n * Validation enforces the per-variant contract (required fields, source\n * vocabulary, strict variants such as `adapter_output`).\n *\n * This LOW-LEVEL writer does NOT hash-chain — it writes the validated event as\n * a plain line. Hash-chained appends go through `appendChainedEvent` /\n * `appendChainedEventLocked` (the live `exec` / `run` / attach / approval\n * paths), and the bulk import writers chain via {@link writeEventsBulk} with\n * `chain: true`. A direct caller of this raw export can still add an unchained\n * line to a chained log; that is DETECTED by `basou verify`\n * (`missing_prev_hash`), not prevented — a documented boundary.\n *\n * Atomicity: writes go through `appendFile` which uses `O_APPEND`. Lines up\n * to `PIPE_BUF` bytes (Linux 4096 / macOS 512) are written atomically by the\n * kernel; longer lines may interleave with concurrent writers and are not\n * recovered here. v0.1 assumes a single writer per session, so partial-line\n * recovery is delegated to the read side (event replay) when introduced.\n *\n * Throws if validation fails or the underlying append errors. The thrown\n * Error message is pathless; the original error is attached as `cause`.\n *\n * @param sessionDir absolute path to `.basou/sessions/<session_id>/`\n * @param event unknown payload to validate and append\n */\nexport async function appendEvent(sessionDir: string, event: unknown): Promise<void> {\n let validated: ReturnType<typeof EventSchema.parse>;\n try {\n validated = EventSchema.parse(event);\n } catch (error: unknown) {\n throw new Error(\"Invalid Basou event payload\", { cause: error });\n }\n const line = `${serializeEventLine(validated)}\\n`;\n try {\n await appendFile(join(sessionDir, \"events.jsonl\"), line, \"utf8\");\n } catch (error: unknown) {\n throw new Error(\"Failed to append event to events.jsonl\", { cause: error });\n }\n}\n\n/** Options for {@link writeEventsBulk}. */\nexport type WriteEventsBulkOptions = {\n /**\n * Thread a per-line `prev_hash` hash chain through the batch and return the\n * head anchor inputs. Used ONLY by the import writers (fresh import and\n * in-place re-import); defaults to false so the live / ad-hoc writers keep\n * producing plain unchained lines.\n */\n chain?: boolean;\n};\n\n/** Head anchor inputs returned by a chained {@link writeEventsBulk}. */\nexport type BulkChainResult = {\n /** Hex sha-256 of the last written line (excluding the trailing `\\n`). */\n headHash: string;\n /** Number of chained lines written. */\n count: number;\n};\n\n/**\n * Write `events.jsonl` in one atomic tmp+rename pass via {@link atomicReplace},\n * validating every event against {@link EventSchema} before any disk I/O so\n * a payload that fails validation never leaves a partial file behind.\n *\n * The helper is used by the round-trip importer (`session-import.ts`) and the\n * ad-hoc session orchestrator (`ad-hoc-session.ts`) where a small, fixed batch\n * of events must land together or not at all. Zero events produces a\n * zero-byte file so the session_yaml `events_log` pointer remains valid.\n *\n * With `options.chain` set, each line is written with a `prev_hash`\n * back-pointer (any incoming `prev_hash` is discarded and recomputed; the\n * chain's genesis is bound to `basename(sessionDir)` = the session id) and\n * the head anchor inputs are returned so the caller can persist\n * `session.yaml.integrity`. An empty chained batch writes a zero-byte file\n * and returns null — no anchor. Without `chain` the return value is null and\n * the written bytes are identical to the previous unchained format.\n *\n * Throws `\"Invalid Basou event payload\"` (same fixed message as\n * {@link appendEvent}) on validation failure, or `\"Failed to write\n * events.jsonl\"` on a disk I/O failure. The original native error is attached\n * as `cause`.\n */\nexport async function writeEventsBulk(\n sessionDir: string,\n events: Event[],\n options: WriteEventsBulkOptions = {},\n): Promise<BulkChainResult | null> {\n const validated: Event[] = [];\n try {\n for (const event of events) {\n validated.push(EventSchema.parse(event));\n }\n } catch (error: unknown) {\n throw new Error(\"Invalid Basou event payload\", { cause: error });\n }\n const filePath = join(sessionDir, \"events.jsonl\");\n\n let body: string;\n let result: BulkChainResult | null = null;\n if (options.chain === true) {\n const { lines, headHash, count } = chainEvents(validated, basename(sessionDir));\n body = lines.length > 0 ? `${lines.join(\"\\n\")}\\n` : \"\";\n result = count > 0 ? { headHash, count } : null;\n } else {\n body = validated.length > 0 ? `${validated.map(serializeEventLine).join(\"\\n\")}\\n` : \"\";\n }\n\n try {\n await atomicReplace(filePath, body);\n } catch (error: unknown) {\n throw new Error(\"Failed to write events.jsonl\", { cause: error });\n }\n return result;\n}\n","import { readFile } from \"node:fs/promises\";\nimport { join } from \"node:path\";\nimport { findErrorCode } from \"../lib/error-codes.js\";\nimport type { SessionIntegrity, SessionStatus } from \"../schemas/session.schema.js\";\nimport type { BasouPaths } from \"../storage/basou-dir.js\";\nimport { readSessionYaml } from \"../storage/sessions.js\";\nimport { genesisHash, lineHash } from \"./chain.js\";\n\n/**\n * Session statuses whose `events.jsonl` is at rest, so its tail and head anchor\n * are strictly checked (a torn tail / missing / mismatching anchor is\n * tampering). A live append session writes its anchor only at the terminal\n * finalize, so these are exactly the statuses a finalized log can carry.\n * `imported` and the reserved `archived` are likewise at rest.\n */\nconst STRICT_STATUSES: ReadonlySet<SessionStatus> = new Set<SessionStatus>([\n \"completed\",\n \"failed\",\n \"interrupted\",\n \"imported\",\n \"archived\",\n]);\n\n// A live session's events.jsonl tail is legitimately still growing (and its\n// anchor is not written until finalize), so the internal chain is verified but\n// the tail / anchor checks are forgiven => `in_progress`.\nfunction isLiveStatus(status: SessionStatus): boolean {\n return !STRICT_STATUSES.has(status);\n}\n\n/**\n * Verification outcome for one session's `events.jsonl`.\n *\n * - `unchained` — no event line carries `prev_hash` (live / ad-hoc / legacy\n * session) and `session.yaml` carries no integrity anchor. Informational.\n * - `empty` — zero events and no integrity anchor. Informational.\n * - `incomplete` — the log is chained but `session.yaml` is ENTIRELY absent\n * (an import crashed between the events write and the yaml write, or the\n * yaml was deleted out of band). Benign: a re-import / `--force` repairs it.\n * - `tampered` — a real integrity break (see {@link ChainBreakReason}).\n * - `in_progress` — a chained log whose session is still LIVE (a non-terminal\n * status: initialized / running / waiting_approval). The internal\n * back-pointer chain is fully verified, but the tail and head anchor are\n * forgiven because a live session's log is legitimately still growing and its\n * anchor is not written until the terminal finalize. Informational, exit 0.\n * - `verified` — every back-pointer, genesis, session-id and line-discipline\n * check passed AND the head anchor matches the on-disk log.\n */\nexport type ChainVerdictStatus =\n | \"verified\"\n | \"unchained\"\n | \"empty\"\n | \"incomplete\"\n | \"in_progress\"\n | \"tampered\";\n\n/** Machine-readable detail for a `tampered` (or `incomplete`) verdict. */\nexport type ChainBreakReason =\n /** The file does not end with `\\n`; chained writers always terminate the last line. */\n | \"torn_tail\"\n /** A blank line inside a chained log; chained writers never emit one. */\n | \"blank_line\"\n /** A line of a chained log failed JSON parsing; writers only emit valid JSON. */\n | \"malformed_line\"\n /** A chained log has a line without `prev_hash`; chained writers chain every line. */\n | \"missing_prev_hash\"\n /** Line 1's `prev_hash` is not this session's genesis hash (edit or cross-session copy). */\n | \"genesis_mismatch\"\n /** A line's `prev_hash` does not hash-match the previous line (edit / insert / delete / reorder). */\n | \"broken_link\"\n /** A line's `session_id` is not this session's id (cross-session copied line). */\n | \"session_id_mismatch\"\n /** `session.yaml` exists but its `integrity` anchor is missing (anchor stripped). */\n | \"anchor_missing\"\n /** The anchor's `head_hash` / `event_count` disagree with the on-disk log (edit or truncation). */\n | \"anchor_mismatch\"\n /** An integrity anchor exists but the log is unchained, empty, or missing (chain stripped). */\n | \"anchor_without_chain\"\n /** `session.yaml` exists but could not be parsed / validated, so the anchor is unreadable. */\n | \"yaml_unreadable\"\n /** `incomplete` only: `session.yaml` is entirely absent. */\n | \"yaml_missing\";\n\n/** Result of {@link verifyEventsChain}. */\nexport type ChainVerdict = {\n status: ChainVerdictStatus;\n /** Complete (newline-terminated) event lines found on disk. */\n eventCount: number;\n /** Detail for `tampered` / `incomplete`; absent otherwise. */\n reason?: ChainBreakReason;\n /** 1-based line number of the first break, when one specific line broke. */\n line?: number;\n};\n\n// Three-state view of `session.yaml` as seen by the verifier. The `present`\n// variant carries the session status so the verdict can forgive a live\n// session's still-growing tail / not-yet-written anchor (`in_progress`).\ntype AnchorState =\n | { kind: \"absent\" }\n | { kind: \"unreadable\" }\n | { kind: \"present\"; integrity: SessionIntegrity | undefined; status: SessionStatus };\n\n/**\n * Verify the tamper-evidence hash chain of `<sessions>/<sessionId>/events.jsonl`\n * against the head anchor in `session.yaml.integrity`. READ-ONLY.\n *\n * The verifier reads the RAW line BYTES (not the schema-filtering replay\n * reader, which silently drops bad lines; and not a decoded string, which\n * would collapse invalid UTF-8 sequences into U+FFFD and let a byte-level\n * substitution survive re-hashing) and hashes exactly the bytes it read.\n * The verdict is decided on the events first, then the anchor:\n *\n * - No line carries `prev_hash` (or there are zero lines / no file): the log\n * is unchained. If `session.yaml` nevertheless carries an integrity anchor,\n * the chain was stripped out of band => `tampered` (`anchor_without_chain`);\n * otherwise `unchained` / `empty`.\n * - At least one line carries `prev_hash`: the log claims to be chained, and\n * every check applies — line discipline (terminating `\\n`, no blank lines,\n * valid JSON), genesis binding, per-line back-pointers, per-line session id,\n * and finally the head anchor (`incomplete` when `session.yaml` is entirely\n * absent; `tampered` when it is present without a matching anchor).\n *\n * - When the chained log belongs to a LIVE session (a non-terminal status),\n * the internal chain is verified but a torn tail / absent / mismatching\n * anchor is FORGIVEN as `in_progress`: a live session's tail is legitimately\n * still growing and its anchor is written only at the terminal finalize.\n *\n * NON-CRYPTOGRAPHIC: the anchor lives in `session.yaml`, which is itself\n * editable; an attacker rewriting BOTH files consistently is not detected.\n * Signing is a follow-up.\n *\n * Throws `Error(\"Failed to read events.jsonl\")` only for non-ENOENT I/O\n * failures (EACCES etc.) — an unreadable file is an environment problem, not\n * a verdict.\n *\n * READ-ONLY and lock-free: a session being finalized concurrently can leave the\n * two files momentarily out of step (old events read before a finalize, new\n * anchor read after it). A strict `anchor_mismatch` is therefore re-snapshotted\n * ONCE before being returned — a genuine mismatch is deterministic across the\n * retry, while a finalize-in-flight resolves within it.\n */\nexport async function verifyEventsChain(\n paths: BasouPaths,\n sessionId: string,\n): Promise<ChainVerdict> {\n const first = await verifyOnce(paths, sessionId);\n if (first.status === \"tampered\" && first.reason === \"anchor_mismatch\") {\n return await verifyOnce(paths, sessionId);\n }\n return first;\n}\n\nasync function verifyOnce(paths: BasouPaths, sessionId: string): Promise<ChainVerdict> {\n const sessionDir = join(paths.sessions, sessionId);\n\n let raw: Buffer | null = null;\n try {\n raw = await readFile(join(sessionDir, \"events.jsonl\"));\n } catch (error: unknown) {\n if (!findErrorCode(error, \"ENOENT\")) {\n throw new Error(\"Failed to read events.jsonl\", { cause: error });\n }\n }\n\n let anchor: AnchorState;\n try {\n const session = await readSessionYaml(paths, sessionId);\n anchor = {\n kind: \"present\",\n integrity: session.session.integrity,\n status: session.session.status,\n };\n } catch (error: unknown) {\n if (error instanceof Error && error.message === \"YAML file not found\") {\n anchor = { kind: \"absent\" };\n } else {\n anchor = { kind: \"unreadable\" };\n }\n }\n\n // Split the raw BYTES into complete (newline-terminated) lines plus an\n // optional unterminated tail fragment. A missing or empty file has neither.\n // Splitting and hashing stay at the byte level; decoding to a string\n // happens only for JSON field inspection.\n const terminated = raw === null || raw.length === 0 || raw[raw.length - 1] === 0x0a;\n const segments = raw === null ? [] : splitLinesBytes(raw);\n const tailFragment = !terminated && segments.length > 0 ? (segments.pop() as Buffer) : null;\n const lines = segments;\n\n // Chained-ness: does ANY parseable line (or the tail fragment) carry prev_hash?\n const carriesPrevHash = (s: Buffer): boolean => {\n try {\n const obj: unknown = JSON.parse(s.toString(\"utf8\"));\n return typeof obj === \"object\" && obj !== null && \"prev_hash\" in obj;\n } catch {\n return false;\n }\n };\n const chained =\n lines.some((l) => l.length > 0 && carriesPrevHash(l)) ||\n (tailFragment !== null && carriesPrevHash(tailFragment));\n\n if (!chained) {\n // Unchained / empty logs are informational — UNLESS session.yaml anchors\n // a chain that is no longer there (one-file strip / truncate-to-zero /\n // log deletion). Legitimately unchained sessions never have an anchor:\n // only the import writers set one, and they always chain.\n if (anchor.kind === \"present\" && anchor.integrity !== undefined) {\n return {\n status: \"tampered\",\n eventCount: lines.length,\n reason: \"anchor_without_chain\",\n };\n }\n if (raw === null || raw.length === 0) {\n return { status: \"empty\", eventCount: 0 };\n }\n return { status: \"unchained\", eventCount: lines.length };\n }\n\n // The log claims to be chained: walk the back-pointer chain over the\n // complete lines, reporting the FIRST break.\n let expected = genesisHash(sessionId);\n for (let i = 0; i < lines.length; i++) {\n const line = lines[i] as Buffer;\n const lineNo = i + 1;\n if (line.length === 0) {\n return { status: \"tampered\", eventCount: lines.length, reason: \"blank_line\", line: lineNo };\n }\n let parsed: unknown;\n try {\n parsed = JSON.parse(line.toString(\"utf8\"));\n } catch {\n return {\n status: \"tampered\",\n eventCount: lines.length,\n reason: \"malformed_line\",\n line: lineNo,\n };\n }\n const record = parsed as Record<string, unknown>;\n if (typeof record.prev_hash !== \"string\") {\n return {\n status: \"tampered\",\n eventCount: lines.length,\n reason: \"missing_prev_hash\",\n line: lineNo,\n };\n }\n if (record.prev_hash !== expected) {\n return {\n status: \"tampered\",\n eventCount: lines.length,\n reason: i === 0 ? \"genesis_mismatch\" : \"broken_link\",\n line: lineNo,\n };\n }\n if (record.session_id !== sessionId) {\n return {\n status: \"tampered\",\n eventCount: lines.length,\n reason: \"session_id_mismatch\",\n line: lineNo,\n };\n }\n expected = lineHash(line);\n }\n\n // The internal back-pointer chain over the complete lines is consistent. A\n // LIVE session's tail and anchor are forgiven from here: the log is still\n // growing and the anchor is not written until the terminal finalize, so a\n // torn tail (crashed append) or absent / lagging anchor is benign.\n const live = anchor.kind === \"present\" && isLiveStatus(anchor.status);\n\n // A chained file must end in a terminating newline; for an at-rest (strict)\n // session an unterminated tail can only come from out-of-band editing (the\n // finalize wrote a terminated log). A live session may legitimately carry a\n // torn tail from a crashed in-flight append.\n if (tailFragment !== null || !terminated) {\n if (live) {\n return { status: \"in_progress\", eventCount: lines.length };\n }\n return {\n status: \"tampered\",\n eventCount: lines.length,\n reason: \"torn_tail\",\n line: lines.length + 1,\n };\n }\n\n // Events are internally consistent — now the head anchor.\n if (anchor.kind === \"absent\") {\n return { status: \"incomplete\", eventCount: lines.length, reason: \"yaml_missing\" };\n }\n if (anchor.kind === \"unreadable\") {\n return { status: \"tampered\", eventCount: lines.length, reason: \"yaml_unreadable\" };\n }\n if (live) {\n // The anchor is not authoritative until the session reaches a terminal\n // status, so it is neither required nor checked here.\n return { status: \"in_progress\", eventCount: lines.length };\n }\n if (anchor.integrity === undefined) {\n return { status: \"tampered\", eventCount: lines.length, reason: \"anchor_missing\" };\n }\n if (anchor.integrity.event_count !== lines.length || anchor.integrity.head_hash !== expected) {\n return { status: \"tampered\", eventCount: lines.length, reason: \"anchor_mismatch\" };\n }\n return { status: \"verified\", eventCount: lines.length };\n}\n\n// Byte-level line split on 0x0A. A trailing newline yields no final entry;\n// content after the last newline (an unterminated tail) is returned as the\n// final entry. Subarray views, no copying.\nfunction splitLinesBytes(buf: Buffer): Buffer[] {\n const out: Buffer[] = [];\n let start = 0;\n for (let i = 0; i < buf.length; i++) {\n if (buf[i] === 0x0a) {\n out.push(buf.subarray(start, i));\n start = i + 1;\n }\n }\n if (start < buf.length) out.push(buf.subarray(start));\n return out;\n}\n","import { readdir, stat } from \"node:fs/promises\";\nimport { join } from \"node:path\";\nimport { type SimpleGit, simpleGit } from \"simple-git\";\nimport type { GitSnapshotEvent } from \"../schemas/event.schema.js\";\nimport { findErrorCode } from \"../storage/status.js\";\n\n/**\n * Build a {@link SimpleGit} instance bound to `repoRoot`. Production callers\n * use this single helper so any future tightening (additional safety opts,\n * environment scrubbing, ...) lands in one place. Test fixtures that need\n * `unsafe.allowUnsafeConfigPaths` for isolated `GIT_CONFIG_*` paths build\n * their own SimpleGit locally and intentionally bypass this helper.\n */\nexport function safeSimpleGit(repoRoot: string): SimpleGit {\n return simpleGit({ baseDir: repoRoot });\n}\n\n/**\n * Detect \"git executable not found\" across error wrappers used by simple-git.\n * simple-git surfaces spawn errors as `GitError` instances which discard the\n * original errno `code` property — only the underlying `\"spawn git ENOENT\"`\n * text survives in the message string. We therefore check both the errno\n * `code` (via {@link findErrorCode}) and the message chain.\n */\nexport function isGitNotFound(error: unknown): boolean {\n if (findErrorCode(error, \"ENOENT\")) return true;\n let cur: unknown = error;\n for (let i = 0; i < 4 && cur instanceof Error; i++) {\n if (/\\bENOENT\\b/.test(cur.message)) return true;\n cur = (cur as Error).cause;\n }\n return false;\n}\n\n/**\n * Detect git's canonical \"not a git repository\" failure across simple-git's\n * error wrappers. This is the ONLY git failure that means `cwd` is genuinely\n * outside a repo — every other non-zero exit (a corrupt repo, a permission\n * error, a missing object store) is a real fault that must NOT be laundered\n * into \"Not a git repository\", because that string drives the workspace-view\n * symlink fallback in {@link resolveBasouRepositoryRoot}: misclassifying a\n * broken repo as \"no repo\" would fire the fallback (or print a misleading\n * \"run git init\") instead of surfacing the actual error.\n */\nfunction isNotAGitRepository(error: unknown): boolean {\n let cur: unknown = error;\n for (let i = 0; i < 4 && cur instanceof Error; i++) {\n if (/not a git repository/i.test(cur.message)) return true;\n cur = (cur as Error).cause;\n }\n return false;\n}\n\n/**\n * Payload subset of `git_snapshot` event, mechanically derived from the\n * zod-inferred event type. The wrapping event-shape fields\n * (schema_version, id, session_id, occurred_at, source, type) are added by\n * the caller (session lifecycle in later steps) when constructing the\n * event, so the schema remains the single source of truth.\n *\n * `ahead` / `behind` are omitted when there is no remote or no upstream\n * tracking; the schema declares both as optional non-negative integers.\n */\nexport type GitSnapshot = Omit<\n GitSnapshotEvent,\n \"schema_version\" | \"id\" | \"session_id\" | \"occurred_at\" | \"source\" | \"type\"\n>;\n\n/**\n * Resolve the absolute path of the Git repository root that contains `cwd`.\n * Equivalent to `git rev-parse --show-toplevel`.\n *\n * Throws `Error(\"Git executable not found in PATH. Install git first.\")`\n * with the spawn error attached as `cause` when git itself is missing.\n * Throws `Error(\"Not a git repository\")` (without command-specific suffix)\n * when `cwd` is not inside a repository — callers MAY wrap with their own\n * \"Run 'git init' first, then re-run 'basou XXX'.\" suffix.\n *\n * Pathless contract: the thrown message never embeds `cwd` or any absolute\n * path; native errors are kept on `error.cause` for verbose surfacing.\n */\nexport async function resolveRepositoryRoot(cwd: string): Promise<string> {\n const git = safeSimpleGit(cwd);\n try {\n const root = (await git.revparse([\"--show-toplevel\"])).trimEnd();\n if (root.length === 0) {\n throw new Error(\"Not a git repository\");\n }\n return root;\n } catch (error: unknown) {\n if (isGitNotFound(error)) {\n throw new Error(\"Git executable not found in PATH. Install git first.\", { cause: error });\n }\n if (error instanceof Error && error.message === \"Not a git repository\") {\n throw error; // the empty-root throw above, already in the fixed vocabulary\n }\n if (isNotAGitRepository(error)) {\n throw new Error(\"Not a git repository\", { cause: error });\n }\n // A genuine git failure (corrupt repo, permission denied, broken object\n // store, ...) is NOT \"no repo here\": surface it distinctly so callers do\n // not fall through to the workspace-view fallback or tell the operator to\n // `git init` over a repo that already exists.\n throw new Error(\"Git command failed\", { cause: error });\n }\n}\n\n/**\n * Resolve the repository root that owns the `.basou/` store for `cwd`, with a\n * fallback for agents-workspace \"view\" directories. A workspace view (e.g.\n * `~/projects/foo-workspace`) is intentionally OUTSIDE git and holds no `.basou/`\n * of its own; it aggregates sibling repos through symlinks (`foo-planning ->\n * ../foo-planning`). Running `basou orient` / `refresh` from there would\n * otherwise die with \"Not a git repository\" even though the view IS the\n * operator's daily cwd.\n *\n * Resolution:\n * 1. If `cwd` is inside a git repo, return its toplevel (unchanged behavior).\n * 2. Otherwise inspect `cwd`'s direct symlinks; if exactly one points at a\n * directory that has a `.basou/` store, redirect to that repo (firing\n * `onRedirect`). Zero candidates re-throws the original \"Not a git\n * repository\"; two or more throws an ambiguity error naming them so the\n * operator can `cd` into the right one.\n */\nexport async function resolveBasouRepositoryRoot(\n cwd: string,\n opts?: { onRedirect?: (info: { via: string; root: string }) => void },\n): Promise<string> {\n try {\n return await resolveRepositoryRoot(cwd);\n } catch (error: unknown) {\n if (!(error instanceof Error) || error.message !== \"Not a git repository\") throw error;\n const linked = await findLinkedBasouRepos(cwd);\n const only = linked[0];\n if (only !== undefined && linked.length === 1) {\n opts?.onRedirect?.({ via: only.name, root: only.root });\n return only.root;\n }\n if (linked.length > 1) {\n const names = linked.map((l) => l.name).join(\", \");\n throw new Error(\n `Ambiguous workspace view: ${linked.length} linked repos have a .basou store (${names}). cd into the one you want and re-run.`,\n );\n }\n throw error;\n }\n}\n\n/**\n * Direct children of `dir` that are symlinks to a git repository whose toplevel\n * holds a `.basou/` store — the planning repos a workspace view aggregates.\n * Detection keys off the git TOPLEVEL (where basou's store always lives), not\n * the raw link target, so a link into a subdirectory is not mistaken for a root.\n * Deduped by toplevel keeping the lexicographically-smallest link name (stable\n * `via` across runs), and sorted by name. Best-effort: an unreadable dir, a\n * broken link, or a non-git target yields no candidate.\n */\nasync function findLinkedBasouRepos(dir: string): Promise<{ name: string; root: string }[]> {\n const entries = await readdir(dir, { withFileTypes: true }).catch(() => null);\n if (entries === null) return [];\n const byRoot = new Map<string, string>(); // git toplevel -> chosen link name\n for (const entry of entries) {\n if (!entry.isSymbolicLink()) continue;\n let root: string;\n try {\n root = await resolveRepositoryRoot(join(dir, entry.name));\n } catch {\n continue; // broken link or not a git repo\n }\n try {\n if (!(await stat(join(root, \".basou\"))).isDirectory()) continue;\n } catch {\n continue; // no .basou store at the repo root\n }\n const existing = byRoot.get(root);\n if (existing === undefined || entry.name < existing) byRoot.set(root, entry.name);\n }\n return [...byRoot.entries()]\n .map(([root, name]) => ({ name, root }))\n .sort((a, b) => a.name.localeCompare(b.name));\n}\n\n/**\n * Read `remote.origin.url` from the local repository config. Returns\n * `undefined` if the remote is unset, the value is empty, or the lookup\n * fails for any reason (best-effort).\n *\n * The `--local` scope is critical: callers MUST NOT pick up the developer's\n * global remote.origin.url, which could leak the wrong repository URL into\n * `manifest.yaml`.\n */\nexport async function tryRemoteUrl(repositoryRoot: string): Promise<string | undefined> {\n const git = safeSimpleGit(repositoryRoot);\n try {\n const result = await git.getConfig(\"remote.origin.url\", \"local\");\n const url = (result.value ?? \"\").trimEnd();\n return url.length > 0 ? url : undefined;\n } catch {\n return undefined;\n }\n}\n\n/**\n * Build a {@link GitSnapshot} for the repository at `repositoryRoot`. The\n * caller is responsible for ensuring `repositoryRoot` is the canonical root\n * (typically obtained via {@link resolveRepositoryRoot}); this function\n * verifies repo membership via `git rev-parse --is-inside-work-tree` to\n * distinguish a non-git directory from an empty repository.\n *\n * Edge cases:\n * - **non-git directory**: throws `Error(\"Not a git repository\")`\n * - **empty repo (no commits)**: throws `Error(\"No commits in repository\")`\n * - **detached HEAD**: `branch = \"HEAD\"`, `head = commit hash`,\n * `ahead`/`behind` omitted\n * - **no remote / no upstream tracking**: `ahead`/`behind` omitted\n *\n * Pathless contract preserved on every throw path.\n */\nexport async function getSnapshot(repositoryRoot: string): Promise<GitSnapshot> {\n const git = safeSimpleGit(repositoryRoot);\n\n let inside: boolean;\n try {\n inside = await git.checkIsRepo();\n } catch (error: unknown) {\n if (isGitNotFound(error)) {\n throw new Error(\"Git executable not found in PATH. Install git first.\", { cause: error });\n }\n throw new Error(\"Failed to read git state\", { cause: error });\n }\n if (!inside) {\n throw new Error(\"Not a git repository\");\n }\n\n let head: string;\n try {\n head = (await git.revparse([\"HEAD\"])).trimEnd();\n } catch (error: unknown) {\n if (isGitNotFound(error)) {\n throw new Error(\"Git executable not found in PATH. Install git first.\", { cause: error });\n }\n throw new Error(\"No commits in repository\", { cause: error });\n }\n if (head.length === 0) {\n throw new Error(\"No commits in repository\");\n }\n\n let branch: string;\n try {\n const raw = (await git.raw([\"branch\", \"--show-current\"])).trimEnd();\n branch = raw.length > 0 ? raw : \"HEAD\";\n } catch (error: unknown) {\n throw new Error(\"Failed to read git state\", { cause: error });\n }\n\n let dirty: boolean;\n const staged: string[] = [];\n const unstaged: string[] = [];\n const untracked: string[] = [];\n try {\n const status = await git.status();\n dirty = !status.isClean();\n // Walk status.files so deleted / renamed / conflicted entries are\n // classified correctly (StatusResult's top-level `staged` / `modified`\n // / `not_added` arrays exclude D / R / U entries).\n for (const f of status.files) {\n if (f.index === \"?\" && f.working_dir === \"?\") {\n untracked.push(f.path);\n continue;\n }\n if (f.index !== \" \" && f.index !== \"?\") staged.push(f.path);\n if (f.working_dir !== \" \" && f.working_dir !== \"?\") unstaged.push(f.path);\n }\n } catch (error: unknown) {\n throw new Error(\"Failed to read git state\", { cause: error });\n }\n\n let ahead: number | undefined;\n let behind: number | undefined;\n if (branch !== \"HEAD\") {\n try {\n const upstream = `${branch}@{upstream}`;\n const counts = (\n await git.raw([\"rev-list\", \"--left-right\", \"--count\", `${upstream}...HEAD`])\n ).trim();\n const [behindStr, aheadStr] = counts.split(/\\s+/);\n const parsedBehind = Number.parseInt(behindStr ?? \"\", 10);\n const parsedAhead = Number.parseInt(aheadStr ?? \"\", 10);\n if (Number.isFinite(parsedBehind) && parsedBehind >= 0) behind = parsedBehind;\n if (Number.isFinite(parsedAhead) && parsedAhead >= 0) ahead = parsedAhead;\n } catch {\n // No upstream tracking: leave both undefined; the schema allows omission.\n }\n }\n\n const snapshot: GitSnapshot = {\n head,\n branch,\n dirty,\n staged,\n unstaged,\n untracked,\n ...(ahead !== undefined ? { ahead } : {}),\n ...(behind !== undefined ? { behind } : {}),\n };\n return snapshot;\n}\n","// Namespace import keeps lstat / readFile behind a single binding for the\n// read-side guards. The EACCES test exercises this module via real fs +\n// chmod on the parent directory rather than vi.spyOn, because vi.spyOn\n// cannot redefine ESM module exports under vitest 2.x.\nimport * as fsp from \"node:fs/promises\";\nimport type { Manifest } from \"../schemas/manifest.schema.js\";\nimport { StatusSchema, type StatusSnapshot } from \"../schemas/status.schema.js\";\nimport { atomicReplace } from \"./atomic.js\";\nimport type { BasouPaths } from \"./basou-dir.js\";\n\n/**\n * @internal Compile-time exhaustiveness via Record: every key of\n * `StatusSnapshot[\"directories_present\"]` MUST have a paths accessor here,\n * otherwise the file fails to typecheck. Run-time exhaustiveness is\n * verified by status.test.ts (key-set equality with\n * `StatusSchema.shape.directories_present.shape`). Exported only so the\n * test can perform that equality check; not part of the public API.\n */\nexport const DIRECTORY_CHECKS: Record<\n keyof StatusSnapshot[\"directories_present\"],\n (p: BasouPaths) => string\n> = {\n sessions: (p) => p.sessions,\n tasks: (p) => p.tasks,\n approvals_pending: (p) => p.approvals.pending,\n approvals_resolved: (p) => p.approvals.resolved,\n logs: (p) => p.logs,\n raw: (p) => p.raw,\n tmp: (p) => p.tmp,\n};\n\n/**\n * Refuse to operate on `.basou` if it is a symlink or not a directory. This\n * prevents `writeStatus` from being tricked into writing `status.json`\n * outside the repository root via a swapped `.basou` symlink. Mirrors\n * `ensureBasouDirectory`'s lstat-based guard.\n *\n * If `.basou` is absent the underlying ENOENT is propagated (wrapped) so\n * callers can map it to \"workspace not initialized\" via `findErrorCode`.\n *\n * Note: this is a baseline safety net, not a TOCTOU fix — the directory\n * could still be replaced between this check and the subsequent write. The\n * goal is to detect already-swapped symlinks, not to race-proof the\n * filesystem.\n */\nexport async function assertBasouRootSafe(rootPath: string): Promise<void> {\n let stat: Awaited<ReturnType<typeof fsp.lstat>>;\n try {\n stat = await fsp.lstat(rootPath);\n } catch (error: unknown) {\n if (hasErrorCode(error) && error.code === \"ENOENT\") {\n throw new Error(\"Basou workspace not found\", { cause: error });\n }\n throw new Error(\"Failed to inspect .basou root\", { cause: error });\n }\n if (stat.isSymbolicLink()) {\n throw new Error(\".basou root is a symlink; refusing to operate\");\n }\n if (!stat.isDirectory()) {\n throw new Error(\".basou root exists but is not a directory\");\n }\n}\n\n/**\n * Probe whether `path` is a directory using `lstat` (without following\n * symlinks, so a symlink-to-directory is reported as `false`).\n *\n * Only ENOENT and ENOTDIR are mapped to `false`; permission-style errors\n * (EACCES, EPERM, ...) are re-thrown so a misleading \"not present\" answer\n * is never written into status.json. This keeps the snapshot honest about\n * what was actually observed.\n */\nasync function dirPresent(path: string): Promise<boolean> {\n try {\n return (await fsp.lstat(path)).isDirectory();\n } catch (error: unknown) {\n if (hasErrorCode(error) && (error.code === \"ENOENT\" || error.code === \"ENOTDIR\")) {\n return false;\n }\n throw new Error(\"Failed to inspect .basou subdirectory\", { cause: error });\n }\n}\n\n/**\n * Build a StatusSnapshot from a manifest plus the path layout, observing\n * each subdirectory's presence via `lstat`. Read-only with respect to the\n * workspace state; writes nothing. The result is re-validated by\n * `StatusSchema.parse` before being returned.\n *\n * @param input.now Override for testing; defaults to `new Date()`.\n */\nexport async function buildStatusSnapshot(input: {\n manifest: Manifest;\n paths: BasouPaths;\n now?: Date;\n}): Promise<StatusSnapshot> {\n const { manifest, paths } = input;\n const generatedAt = (input.now ?? new Date()).toISOString();\n\n const entries = Object.entries(DIRECTORY_CHECKS) as Array<\n [keyof StatusSnapshot[\"directories_present\"], (p: BasouPaths) => string]\n >;\n const presence = await Promise.all(\n entries.map(async ([key, get]) => [key, await dirPresent(get(paths))] as const),\n );\n const directoriesEntries = Object.fromEntries(presence) as StatusSnapshot[\"directories_present\"];\n\n const snapshot: StatusSnapshot = {\n schema_version: \"0.1.0\",\n generated_at: generatedAt,\n workspace: {\n id: manifest.workspace.id,\n name: manifest.workspace.name,\n basou_version: manifest.basou_version,\n },\n directories_present: directoriesEntries,\n };\n return StatusSchema.parse(snapshot);\n}\n\n/**\n * Atomically write a StatusSnapshot to `paths.files.status`.\n *\n * Re-validates via `StatusSchema.parse` before any file I/O, so an invalid\n * snapshot throws synchronously and never overwrites the existing\n * `status.json`. Delegates the tmp-file + rename pass to {@link atomicReplace}.\n *\n * **Precondition**: callers MUST invoke {@link assertBasouRootSafe} on\n * `paths.root` first to ensure `.basou` is a real directory and not a\n * swapped symlink. `writeStatus` does not redo this guard — it trusts the\n * caller — so a direct invocation without the guard could write\n * `status.json` outside the repository root.\n */\nexport async function writeStatus(paths: BasouPaths, snapshot: StatusSnapshot): Promise<void> {\n const validated = StatusSchema.parse(snapshot);\n const body = `${JSON.stringify(validated, null, 2)}\\n`;\n try {\n await atomicReplace(paths.files.status, body);\n } catch (error: unknown) {\n throw new Error(\"Failed to write status file\", { cause: error });\n }\n}\n\n/**\n * Read `.basou/status.json` for the current schema_version (0.1.0). This\n * is a cache reader only; cross-version migration is not supported here.\n * Older or newer status.json shapes will fail `StatusSchema.parse` —\n * callers regenerate by calling `buildStatusSnapshot` + `writeStatus`.\n */\nexport async function readStatus(paths: BasouPaths): Promise<StatusSnapshot> {\n let body: string;\n try {\n body = await fsp.readFile(paths.files.status, \"utf8\");\n } catch (error: unknown) {\n if (hasErrorCode(error) && error.code === \"ENOENT\") {\n throw new Error(\"Status file not found\", { cause: error });\n }\n throw new Error(\"Failed to read status file\", { cause: error });\n }\n let parsed: unknown;\n try {\n parsed = JSON.parse(body);\n } catch (error: unknown) {\n throw new Error(\"Failed to parse status JSON\", { cause: error });\n }\n return StatusSchema.parse(parsed);\n}\n\n// Re-exported from lib so existing import paths (`./storage/status.js`,\n// `@basou/core/storage/index.js`, `@basou/core`) continue to resolve while\n// the canonical definition lives in `core/src/lib/error-codes.ts`.\nexport { findErrorCode } from \"../lib/error-codes.js\";\n\nfunction hasErrorCode(error: unknown): error is Error & { code: string } {\n if (!(error instanceof Error)) return false;\n return typeof (error as unknown as Record<string, unknown>).code === \"string\";\n}\n","import { z } from \"zod\";\nimport { IsoTimestampSchema, SchemaVersionSchema, WorkspaceIdSchema } from \"./shared.schema.js\";\n\n/**\n * Schema for `.basou/status.json` — a forward-incompat cache of the current\n * workspace state.\n *\n * Each level uses `.strict()` so unknown keys are rejected rather than\n * silently stripped. A v0.1 reader that encounters a future-shape\n * `status.json` therefore fails parsing instead of returning a partially\n * empty snapshot; callers regenerate by calling `buildStatusSnapshot` +\n * `writeStatus` rather than trying to migrate.\n */\nexport const StatusSchema = z\n .object({\n schema_version: SchemaVersionSchema,\n generated_at: IsoTimestampSchema,\n workspace: z\n .object({\n id: WorkspaceIdSchema,\n name: z.string().min(1),\n basou_version: z.literal(\"0.1.0\"),\n })\n .strict(),\n directories_present: z\n .object({\n sessions: z.boolean(),\n tasks: z.boolean(),\n approvals_pending: z.boolean(),\n approvals_resolved: z.boolean(),\n logs: z.boolean(),\n raw: z.boolean(),\n tmp: z.boolean(),\n })\n .strict(),\n })\n .strict();\n\n/** Inferred runtime type for {@link StatusSchema}. */\nexport type StatusSnapshot = z.infer<typeof StatusSchema>;\n","import type { SimpleGit } from \"simple-git\";\nimport { isGitNotFound, safeSimpleGit } from \"./snapshot.js\";\n\n/**\n * Status classification used by the `file_changed` event schema. Limited to\n * the four classes that simple-git's `git diff --name-status` reliably\n * surfaces; copy / unmerged / typechange entries are intentionally dropped\n * to keep the event payload shape narrow.\n */\nexport type FileChangeStatus = \"added\" | \"modified\" | \"deleted\" | \"renamed\";\n\n/**\n * Single file-level change observed between two refs. `old_path` is set\n * only for `renamed` entries (the previous path of the file).\n */\nexport type FileChange = {\n path: string;\n old_path?: string;\n status: FileChangeStatus;\n};\n\n/**\n * Result of {@link getDiff}. The `changed_files` array is in git's natural\n * `--name-status` order; callers requiring deterministic ordering should\n * sort by `path` themselves.\n */\nexport type DiffResult = {\n changed_files: FileChange[];\n};\n\n/**\n * Compute the file-level diff between two git refs.\n *\n * Returns a list of changed file paths classified by status (added /\n * modified / deleted / renamed). Diff content is intentionally NOT\n * returned — `file_changed` events record paths only, and raw diff bodies\n * are excluded so the trace cannot inadvertently leak source code that may\n * be sensitive. Use `git show <ref>` to obtain the underlying diff.\n *\n * Pathless contract: every thrown message is a fixed string from the set\n * {`Not a git repository`, `Git executable not found in PATH. Install git\n * first.`, `Invalid ref`, `Failed to compute git diff`}; native errors are\n * preserved on `Error.cause`.\n *\n * Special cases:\n * - `baseRef === headRef` short-circuits to an empty result\n * - copy / unmerged / typechange / unknown status codes are skipped\n *\n * @param repoRoot absolute path to the git repository root\n * @param baseRef base ref (e.g. session-start HEAD sha)\n * @param headRef head ref (e.g. session-end HEAD sha)\n */\nexport async function getDiff(\n repoRoot: string,\n baseRef: string,\n headRef: string,\n): Promise<DiffResult> {\n let git: SimpleGit;\n try {\n git = safeSimpleGit(repoRoot);\n } catch (error: unknown) {\n if (isGitNotFound(error)) {\n throw new Error(\"Git executable not found in PATH. Install git first.\", { cause: error });\n }\n throw new Error(\"Not a git repository\", { cause: error });\n }\n\n if (baseRef === headRef) return { changed_files: [] };\n\n let raw: string;\n try {\n raw = await git.raw([\"diff\", \"--name-status\", `${baseRef}..${headRef}`]);\n } catch (error: unknown) {\n if (isGitNotFound(error)) {\n throw new Error(\"Git executable not found in PATH. Install git first.\", { cause: error });\n }\n const message = error instanceof Error ? error.message : \"\";\n if (/not a git repository/i.test(message)) {\n throw new Error(\"Not a git repository\", { cause: error });\n }\n if (\n message.includes(\"bad revision\") ||\n message.includes(\"unknown revision\") ||\n message.includes(\"ambiguous argument\")\n ) {\n throw new Error(\"Invalid ref\", { cause: error });\n }\n throw new Error(\"Failed to compute git diff\", { cause: error });\n }\n\n return { changed_files: parseDiffNameStatus(raw) };\n}\n\nfunction parseDiffNameStatus(raw: string): FileChange[] {\n const lines = raw.split(\"\\n\").filter((l) => l.trim() !== \"\");\n const changes: FileChange[] = [];\n for (const line of lines) {\n const parts = line.split(\"\\t\");\n const code = parts[0];\n if (code === undefined || code.length === 0) continue;\n if (code.startsWith(\"R\") && parts.length >= 3) {\n const newPath = parts[2];\n const oldPath = parts[1];\n if (newPath === undefined) continue;\n changes.push({\n path: newPath,\n status: \"renamed\",\n ...(oldPath !== undefined ? { old_path: oldPath } : {}),\n });\n } else if (code === \"A\" && parts[1]) {\n changes.push({ path: parts[1], status: \"added\" });\n } else if (code === \"M\" && parts[1]) {\n changes.push({ path: parts[1], status: \"modified\" });\n } else if (code === \"D\" && parts[1]) {\n changes.push({ path: parts[1], status: \"deleted\" });\n }\n // C / U / T / X (copy / unmerged / typechange / unknown) are skipped:\n // the file_changed status enum does not cover them in v0.1.\n }\n return changes;\n}\n","import { join } from \"node:path\";\nimport { enumerateApprovals } from \"../approval/approval-store.js\";\nimport { type ReplayWarning, replayEvents } from \"../events/event-replay.js\";\nimport { isTrailingStale, pickLatestSubstantiveEntry } from \"../lib/recency.js\";\nimport type { BasouPaths } from \"../storage/basou-dir.js\";\nimport {\n loadSessionEntries,\n type SessionEntry,\n type SessionSkipReason,\n type SuspectReason,\n} from \"../storage/sessions.js\";\nimport { loadTaskEntries, type TaskDocument, type TaskSkipReason } from \"../storage/tasks.js\";\n\n/** Input contract for {@link renderHandoff}. */\nexport type HandoffRendererInput = {\n paths: BasouPaths;\n /** ISO timestamp embedded in the generated body header. Caller-provided for testability. */\n nowIso: string;\n /** Forwarded to {@link replayEvents} / {@link loadSessionEntries} per session. */\n onWarning?: (warning: ReplayWarning, sessionId: string) => void;\n /**\n * Per-session degradation reasons (missing/invalid session.yaml or\n * unreadable events.jsonl). The CLI maps `events_jsonl_unreadable` to the\n * existing suspect-check stderr wording to keep the user-facing surface\n * consistent with `basou session list`.\n */\n onSessionSkip?: (sessionId: string, reason: SessionSkipReason) => void;\n /**\n * Per-task degradation reasons (invalid front matter / unreadable file).\n * Surfaced so the CLI can warn the operator about a malformed task.md\n * without aborting the handoff render.\n */\n onTaskSkip?: (taskId: string, reason: TaskSkipReason) => void;\n /** Maximum related_files entries to display before `... +N more`. Default 20. */\n relatedFilesLimit?: number;\n};\n\nexport type HandoffRendererResult = {\n /** Generated body WITHOUT BASOU:GENERATED markers (markdown-store wraps them). */\n body: string;\n sessionCount: number;\n decisionCount: number;\n pendingApprovalsCount: number;\n suspectCount: number;\n /** Total number of task.md files successfully loaded. */\n taskCount: number;\n /** Tasks whose status is `planned` or `in_progress` (= shown in 次に実行すべき作業). */\n pendingTaskCount: number;\n};\n\ntype DecisionRecord = {\n decisionId: string;\n title: string;\n occurredAt: string;\n sessionId: string;\n};\n\n// An open (non-voided) `kind: \"track\"` decision — a strategic, unfinished\n// direction the handoff resurfaces until it is closed via `decision void`.\n// Mirrors the orientation renderer so the two outputs agree.\ntype TrackRecord = {\n decisionId: string;\n title: string;\n rationale: string | null;\n occurredAt: string;\n sessionId: string;\n};\n\ntype TaskCreatedRecord = {\n taskId: string;\n title: string;\n occurredAt: string;\n sessionId: string;\n};\n\ntype TaskStatusChangedRecord = {\n taskId: string;\n occurredAt: string;\n sessionId: string;\n};\n\n/**\n * Render the body of `handoff.md` from the current workspace state.\n *\n * The renderer is a pure function (no I/O beyond {@link replayEvents} /\n * {@link loadSessionEntries} / {@link enumerateApprovals}). It assembles the\n * the spec's `handoff.md` sections in order:\n *\n * 1. `現在の状態`: latest live session (status not archived, source not import).\n * 2. `直近の変更ファイル`: the most recent session's `related_files`, dedup +\n * sorted asc + truncated to `relatedFilesLimit` (default 20).\n * 3. `直近の判断`: latest `decision_recorded` event (chronological).\n * 4. `未決事項`: pending-approval count + suspect-session count.\n * 5. `次に読むべきファイル`: `.basou/decisions.md` + top-3 related files\n * (the same `displayedFiles` source is intentionally reused in two\n * sections — overview vs. resume context).\n * 6. `次に実行すべき作業`: placeholder until task events land.\n * 7. `セッション一覧`: all sessions newest first with inline suspect labels.\n *\n * Session enumeration goes through {@link loadSessionEntries} so the set of\n * sessions whose `decision_recorded` events we replay matches the\n * decisions renderer.\n */\nexport async function renderHandoff(input: HandoffRendererInput): Promise<HandoffRendererResult> {\n const limit = input.relatedFilesLimit ?? 20;\n const now = new Date(input.nowIso);\n // Wrap the caller's onSkip so we can detect whether loadSessionEntries'\n // suspect pass already emitted `events_jsonl_unreadable` for a session\n // For non-running sessions the suspect pass does not\n // touch events.jsonl, so the second replay below may be the first to\n // hit the unreadable file — without this bookkeeping that error would\n // be silently swallowed.\n const unreadableEmitted = new Set<string>();\n const wrappedSkip: (sid: string, reason: SessionSkipReason) => void = (sid, reason) => {\n if (reason === \"events_jsonl_unreadable\") unreadableEmitted.add(sid);\n input.onSessionSkip?.(sid, reason);\n };\n // `exactOptionalPropertyTypes` forbids passing literal `undefined` for an\n // optional property, so build the options object conditionally.\n const loadOpts: Parameters<typeof loadSessionEntries>[1] = { now, onSkip: wrappedSkip };\n if (input.onWarning !== undefined) loadOpts.onWarning = input.onWarning;\n const entries = await loadSessionEntries(input.paths, loadOpts);\n\n const decisions: DecisionRecord[] = [];\n // `kind: \"track\"` decisions (a strategic, unfinished direction); the open\n // subset (minus voided) is surfaced as 未完トラック and resurfaces until closed.\n const tracks: TrackRecord[] = [];\n // decision_ids marked no longer in force; the 直近の判断 pointer skips them so\n // a voided decision is never surfaced as current (mirrors the orientation\n // renderer, keeping the two outputs in agreement).\n const voidedDecisionIds = new Set<string>();\n const tasksCreated: TaskCreatedRecord[] = [];\n const tasksStatusChanged: TaskStatusChangedRecord[] = [];\n // Activity tail over NON-archived sessions = max of the session boundary\n // (ended_at ?? started_at) AND every event's occurred_at. Mirrors the\n // orientation renderer so the 直近の判断 staleness note fires identically (a\n // decision that real work continued past is not presented as current).\n let latestActivityAt: string | null = null;\n const noteActivity = (iso: string): void => {\n if (latestActivityAt === null || Date.parse(iso) > Date.parse(latestActivityAt)) {\n latestActivityAt = iso;\n }\n };\n for (const entry of entries) {\n const sessionDir = join(input.paths.sessions, entry.sessionId);\n const counted = entry.session.session.status !== \"archived\";\n if (counted) noteActivity(entry.session.session.ended_at ?? entry.session.session.started_at);\n try {\n for await (const ev of replayEvents(sessionDir, {\n onWarning: (w) => input.onWarning?.(w, entry.sessionId),\n })) {\n if (counted) noteActivity(ev.occurred_at);\n if (ev.type === \"decision_recorded\") {\n decisions.push({\n decisionId: ev.decision_id,\n title: ev.title,\n occurredAt: ev.occurred_at,\n sessionId: entry.sessionId,\n });\n if (ev.kind === \"track\") {\n tracks.push({\n decisionId: ev.decision_id,\n title: ev.title,\n rationale: ev.rationale ?? null,\n occurredAt: ev.occurred_at,\n sessionId: entry.sessionId,\n });\n }\n } else if (ev.type === \"decision_voided\") {\n voidedDecisionIds.add(ev.decision_id);\n } else if (ev.type === \"task_created\") {\n tasksCreated.push({\n taskId: ev.task_id,\n title: ev.title,\n occurredAt: ev.occurred_at,\n sessionId: entry.sessionId,\n });\n } else if (ev.type === \"task_status_changed\") {\n tasksStatusChanged.push({\n taskId: ev.task_id,\n occurredAt: ev.occurred_at,\n sessionId: entry.sessionId,\n });\n }\n }\n } catch {\n // events.jsonl unreadable on the decision-aggregation pass. If the\n // suspect pass has not already surfaced a warning for this session\n // (e.g. completed session, where classifySuspect short-circuits\n // before reading events.jsonl), emit the skip now so the operator\n // is not left wondering why a decision is missing.\n if (!unreadableEmitted.has(entry.sessionId)) {\n wrappedSkip(entry.sessionId, \"events_jsonl_unreadable\");\n }\n }\n }\n decisions.sort((a, b) => {\n const c = Date.parse(a.occurredAt) - Date.parse(b.occurredAt);\n return c !== 0 ? c : a.decisionId.localeCompare(b.decisionId);\n });\n // Newest decision NOT voided — the 直近の判断 pointer. A voided decision stays\n // in decisions.md (struck) but must not pose as the current direction.\n let latestDecision: DecisionRecord | undefined;\n for (let i = decisions.length - 1; i >= 0; i -= 1) {\n const d = decisions[i];\n if (d !== undefined && !voidedDecisionIds.has(d.decisionId)) {\n latestDecision = d;\n break;\n }\n }\n // Open tracks: non-voided `kind: \"track\"` decisions, newest first. Mirrors the\n // orientation renderer so handoff and orient surface the same strategic\n // continuation.\n const openTracks: TrackRecord[] = tracks\n .filter((t) => !voidedDecisionIds.has(t.decisionId))\n .sort((a, b) => {\n const c = Date.parse(b.occurredAt) - Date.parse(a.occurredAt);\n return c !== 0 ? c : b.decisionId.localeCompare(a.decisionId);\n });\n tasksCreated.sort((a, b) => {\n const c = Date.parse(a.occurredAt) - Date.parse(b.occurredAt);\n return c !== 0 ? c : a.taskId.localeCompare(b.taskId);\n });\n tasksStatusChanged.sort((a, b) => {\n const c = Date.parse(a.occurredAt) - Date.parse(b.occurredAt);\n return c !== 0 ? c : a.taskId.localeCompare(b.taskId);\n });\n\n const taskLoadOpts: Parameters<typeof loadTaskEntries>[1] = {};\n if (input.onTaskSkip !== undefined) taskLoadOpts.onSkip = input.onTaskSkip;\n const taskEntries = await loadTaskEntries(input.paths, taskLoadOpts);\n const taskById = new Map<string, TaskDocument>();\n for (const t of taskEntries) taskById.set(t.task.task.id, t);\n\n // Latest activity = most recent task_status_changed, falling back to the\n // most recent task_created when no status change has been recorded yet.\n // This surfaces \"the task whose status most recently changed (including\n // done)\" instead of \"the most recently created task\", so a task that just\n // transitioned to done is no longer hidden from the handoff.\n const latestStatusChange = tasksStatusChanged[tasksStatusChanged.length - 1];\n const latestCreatedRecord = tasksCreated[tasksCreated.length - 1];\n const latestActivityTaskId = latestStatusChange?.taskId ?? latestCreatedRecord?.taskId;\n const latestActivityTitle =\n latestActivityTaskId !== undefined\n ? (tasksCreated.find((t) => t.taskId === latestActivityTaskId)?.title ?? \"(title unknown)\")\n : undefined;\n const latestActivityRecord =\n latestActivityTaskId !== undefined && latestActivityTitle !== undefined\n ? { taskId: latestActivityTaskId, title: latestActivityTitle }\n : undefined;\n const latestTaskDoc =\n latestActivityRecord !== undefined ? taskById.get(latestActivityRecord.taskId) : undefined;\n const pendingTasks = taskEntries.filter(\n (t) => t.task.task.status === \"planned\" || t.task.task.status === \"in_progress\",\n );\n\n const approvals = await enumerateApprovals(input.paths);\n const pendingApprovalsCount = approvals.pending.length;\n\n const liveEntries = entries.filter(\n (e) => e.session.session.status !== \"archived\" && e.session.session.source.kind !== \"import\",\n );\n // Represent 最終 session with the most recent SUBSTANTIVE session, not a bare\n // resume/refresh session (e.g. 1 command, 0 files) that merely happens to be\n // newest — the latter hides the real-work session and disagrees with 直近の判断.\n const latestSession = pickLatestSubstantiveEntry(liveEntries);\n\n // 「直近の変更ファイル」 shows the files touched by the most recent SUBSTANTIVE\n // session — the same session surfaced as 最終 session above — so the section\n // reflects the latest real activity rather than the whole history. (A bare\n // resume session has no related_files anyway, so following 最終 session here\n // shows the substantive work's files instead of an empty list.) Unioning every\n // session's related_files turned this into a whole-history dump once transcript\n // imports became the primary source, since each import carries a full day of\n // file changes.\n const latestFiles = latestSession?.session.session.related_files ?? [];\n const sortedFiles = [...new Set(latestFiles)].sort();\n const displayedFiles = sortedFiles.slice(0, limit);\n const overflow = Math.max(0, sortedFiles.length - limit);\n\n const suspectCount = entries.filter((e) => e.suspect).length;\n\n const firstEntry = entries[0];\n const lastEntry = entries[entries.length - 1];\n const sessionRange =\n firstEntry !== undefined && lastEntry !== undefined\n ? `${shortIdWithPrefix(firstEntry.sessionId)}..${shortIdWithPrefix(lastEntry.sessionId)}`\n : \"\";\n\n const body = formatHandoffBody({\n nowIso: input.nowIso,\n sessionRange,\n sessionCount: entries.length,\n latestSession,\n latestActivityAt,\n decisions,\n latestDecision,\n openTracks,\n pendingApprovalsCount,\n suspectCount,\n displayedFiles,\n overflow,\n entries,\n latestActivityRecord,\n latestTaskDoc,\n pendingTasks,\n totalTaskCount: taskEntries.length,\n });\n\n return {\n body,\n sessionCount: entries.length,\n decisionCount: decisions.length,\n pendingApprovalsCount,\n suspectCount,\n taskCount: taskEntries.length,\n pendingTaskCount: pendingTasks.length,\n };\n}\n\nfunction formatHandoffBody(args: {\n nowIso: string;\n sessionRange: string;\n sessionCount: number;\n latestSession: SessionEntry | undefined;\n latestActivityAt: string | null;\n decisions: ReadonlyArray<DecisionRecord>;\n latestDecision: DecisionRecord | undefined;\n openTracks: ReadonlyArray<TrackRecord>;\n pendingApprovalsCount: number;\n suspectCount: number;\n displayedFiles: ReadonlyArray<string>;\n overflow: number;\n entries: ReadonlyArray<SessionEntry>;\n latestActivityRecord: { taskId: string; title: string } | undefined;\n latestTaskDoc: TaskDocument | undefined;\n pendingTasks: ReadonlyArray<TaskDocument>;\n totalTaskCount: number;\n}): string {\n const lines: string[] = [];\n lines.push(\"# Handoff\");\n lines.push(\"\");\n if (args.sessionRange !== \"\") {\n lines.push(`> Generated at ${args.nowIso} from ${args.sessionRange}`);\n } else {\n lines.push(`> Generated at ${args.nowIso}`);\n }\n lines.push(\"\");\n\n // 現在の状態\n lines.push(\"## 現在の状態\");\n lines.push(\"\");\n if (args.latestSession !== undefined) {\n const status = args.latestSession.session.session.status;\n const label = args.latestSession.session.session.label;\n const shortId = shortIdWithPrefix(args.latestSession.sessionId);\n // Lead with the human-readable label; the raw id is demoted to a trailing\n // [short id]. When the session has no label the short id is the only handle\n // available, so it becomes the primary text and the bracket is dropped to\n // avoid repeating it.\n if (label !== undefined && label !== \"\") {\n lines.push(`- 最終 session: ${label} (${status}) [${shortId}]`);\n } else {\n lines.push(`- 最終 session: ${shortId} (${status})`);\n }\n } else {\n lines.push(\"- 最終 session: (no live sessions)\");\n }\n if (args.latestActivityRecord !== undefined) {\n // Status comes from task.md when available. If the task_created event\n // exists but task.md is missing / invalid we MUST NOT fabricate\n // \"planned\" — events alone cannot restore the initial status and\n // operators would miss an unsafe-state reconcile.\n const statusLabel =\n args.latestTaskDoc !== undefined\n ? args.latestTaskDoc.task.task.status\n : \"status unknown — task.md missing or invalid\";\n // Surface linked_sessions cardinality inside the status parenthetical when\n // the latest task spans more than one session. Suppressed when the\n // task is single-session (the common case) or when task.md is\n // unavailable, keeping single-session output visually quiet.\n const linkedCount = args.latestTaskDoc?.task.task.linked_sessions?.length;\n const linkedSuffix =\n linkedCount !== undefined && linkedCount > 1 ? `, linked_sessions: ${linkedCount}` : \"\";\n // Lead with the task title; the raw id is demoted to a trailing [short id]\n // and linked_sessions rides alongside the status.\n lines.push(\n `- 最終 task: ${args.latestActivityRecord.title} (${statusLabel}${linkedSuffix}) [${shortIdWithPrefix(args.latestActivityRecord.taskId)}]`,\n );\n } else {\n lines.push(\"- 最終 task: (no tasks recorded yet)\");\n }\n lines.push(\"\");\n\n // 直近の変更ファイル\n lines.push(\"## 直近の変更ファイル\");\n lines.push(\"\");\n if (args.displayedFiles.length === 0) {\n lines.push(\"(no related files recorded)\");\n } else {\n for (const f of args.displayedFiles) lines.push(`- ${f}`);\n if (args.overflow > 0) lines.push(`- ... +${args.overflow} more`);\n }\n lines.push(\"\");\n\n // 直近の判断\n lines.push(\"## 直近の判断\");\n lines.push(\"\");\n if (args.latestDecision === undefined) {\n // Either no decisions, or every recorded decision has been voided — in\n // both cases there is no current recorded direction to surface.\n lines.push(\"(no decisions recorded yet)\");\n } else {\n const last = args.latestDecision;\n // Lead with the decision title; the raw id is demoted to a trailing\n // [short id].\n lines.push(`- ${last.title} [${shortIdWithPrefix(last.decisionId)}]`);\n // Staleness caveat (mirrors orientation): when real work continued well past\n // this decision, it may already be resolved/executed — do not let a resume\n // treat it as the current next step. handoff had no such note before, so a\n // stale recorded decision posed unguarded as \"直近の判断\".\n if (args.latestActivityAt !== null && isTrailingStale(args.latestActivityAt, last.occurredAt)) {\n lines.push(\n \" - 注: 最終活動はこの判断より後です。会話で既に解決済みの可能性があるため、再開前に継続点を確認してください(会話での意思決定は自動記録されません。`basou decision capture` で記録できます)。\",\n );\n }\n // When the latest decision is from a DIFFERENT session than 最終 session, the\n // two \"latest\" pointers disagree; surface it so the timeline is unambiguous.\n if (args.latestSession !== undefined && last.sessionId !== args.latestSession.sessionId) {\n lines.push(\n ` - 注: この判断は最終 session とは別の session [${shortIdWithPrefix(last.sessionId)}] のものです。`,\n );\n }\n lines.push(\"\");\n lines.push(`(${args.decisions.length} decisions total — see decisions.md)`);\n }\n lines.push(\"\");\n\n // 未完トラック — open strategic directions that resurface until closed. Placed\n // right after 直近の判断 (both decision-derived) and ahead of the mechanical\n // task list: an open track is the strongest \"where to resume\" signal, carrying\n // the next essential direction + why across the session boundary. Mirrors the\n // orientation renderer's forward section. Omitted entirely when none are open.\n if (args.openTracks.length > 0) {\n const TRACK_DISPLAY_LIMIT = 10;\n const shown = args.openTracks.slice(0, TRACK_DISPLAY_LIMIT);\n const overflow = args.openTracks.length - shown.length;\n lines.push(\"## 未完トラック (close まで継続表示)\");\n lines.push(\"\");\n for (const t of shown) {\n lines.push(`- ${t.title} [${shortIdWithPrefix(t.decisionId)}]`);\n if (t.rationale !== null && t.rationale.trim() !== \"\") {\n lines.push(` - 理由: ${handoffRationale(t.rationale)}`);\n }\n }\n if (overflow > 0) lines.push(`- ... +${overflow} more (see decisions.md)`);\n lines.push(\"\");\n lines.push(\"完了したら `basou decision void <decision_id>` で閉じてください。\");\n lines.push(\"\");\n }\n\n // 未決事項\n lines.push(\"## 未決事項\");\n lines.push(\"\");\n if (args.pendingApprovalsCount > 0) {\n lines.push(`- ${args.pendingApprovalsCount} pending approvals`);\n }\n if (args.suspectCount > 0) {\n lines.push(`- ${args.suspectCount} suspect sessions detected`);\n }\n if (args.pendingApprovalsCount === 0 && args.suspectCount === 0) {\n lines.push(\"(none)\");\n }\n lines.push(\"\");\n\n // 次に読むべきファイル\n // Drop self-reference to handoff.md, include `.basou/decisions.md` + the\n // top-3 of `displayedFiles` so the section points to concrete files. The\n // same `displayedFiles` source is reused intentionally (overview vs.\n // resume context).\n lines.push(\"## 次に読むべきファイル\");\n lines.push(\"\");\n lines.push(\"- .basou/decisions.md\");\n for (const f of args.displayedFiles.slice(0, 3)) lines.push(`- ${f}`);\n lines.push(\"\");\n\n // 次に実行すべき作業\n lines.push(\"## 次に実行すべき作業\");\n lines.push(\"\");\n if (args.pendingTasks.length === 0) {\n lines.push(\"(no pending tasks)\");\n } else {\n for (const t of args.pendingTasks) {\n // Lead with the task title; the raw id is demoted to a trailing [short id].\n lines.push(\n `- ${t.task.task.title} (${t.task.task.status}) [${shortIdWithPrefix(t.task.task.id)}]`,\n );\n }\n }\n lines.push(\"\");\n\n // セッション一覧 — the main table lists the operator's own sessions newest\n // first. This deliberately includes `claude-code-import` sessions: a\n // transcript captured after the fact via `basou import claude-code` is still\n // the operator's own work, so it belongs here rather than below. The separate\n // 「Imported sessions」 sub-section holds ONLY cross-workspace round-trips\n // brought in via `basou session import` (source.kind === \"import\"), so it is\n // absent whenever there are none. The \"(no sessions yet)\" placeholder fires\n // only when the workspace is completely empty; \"(no live sessions; …)\" fires\n // when every session is such a round-trip import.\n const liveTableEntries = args.entries.filter((e) => e.session.session.source.kind !== \"import\");\n const importedTableEntries = args.entries.filter(\n (e) => e.session.session.source.kind === \"import\",\n );\n lines.push(\"## セッション一覧\");\n lines.push(\"\");\n if (args.entries.length === 0) {\n lines.push(\"(no sessions yet)\");\n } else if (liveTableEntries.length === 0) {\n lines.push(\"(no live sessions; see Imported sessions below)\");\n } else {\n lines.push(\"| short_id | status | started_at | label |\");\n lines.push(\"|---|---|---|---|\");\n for (const e of [...liveTableEntries].reverse()) {\n const sid = shortHandoffId(e.sessionId);\n const status = e.session.session.status + suspectLabel(e.suspectReason);\n const startedAt = e.session.session.started_at;\n const label = e.session.session.label ?? \"\";\n lines.push(`| ${sid} | ${status} | ${startedAt} | ${label} |`);\n }\n }\n if (importedTableEntries.length > 0) {\n lines.push(\"\");\n lines.push(\"### Imported sessions\");\n lines.push(\"\");\n lines.push(\"| short_id | status | started_at | label |\");\n lines.push(\"|---|---|---|---|\");\n for (const e of [...importedTableEntries].reverse()) {\n const sid = shortHandoffId(e.sessionId);\n const status = e.session.session.status + suspectLabel(e.suspectReason);\n const startedAt = e.session.session.started_at;\n const label = e.session.session.label ?? \"\";\n lines.push(`| ${sid} | ${status} | ${startedAt} | ${label} |`);\n }\n }\n lines.push(\"\");\n // Session-status breakdown: surface completed / failed / running counts\n // alongside the total so an at-a-glance read distinguishes \"ten sessions,\n // all done\" from \"ten sessions, three still failing\". Order is fixed\n // (completed first since handoff is read after the work) and zero-count\n // statuses are omitted. When the workspace is empty the breakdown\n // parenthetical is suppressed entirely so the existing terse line stays.\n const statusCounts = new Map<string, number>();\n for (const e of args.entries) {\n const s = e.session.session.status;\n statusCounts.set(s, (statusCounts.get(s) ?? 0) + 1);\n }\n const orderedStatuses = [\n \"completed\",\n \"failed\",\n \"running\",\n \"interrupted\",\n \"waiting_approval\",\n \"initialized\",\n \"imported\",\n ] as const;\n const breakdown = orderedStatuses\n .filter((s) => (statusCounts.get(s) ?? 0) > 0)\n .map((s) => `${s} ${statusCounts.get(s)}`)\n .join(\", \");\n const sessionsLine =\n breakdown !== \"\"\n ? `Sessions: ${args.sessionCount} (${breakdown}). Tasks: ${args.totalTaskCount}.`\n : `Sessions: ${args.sessionCount}. Tasks: ${args.totalTaskCount}.`;\n lines.push(sessionsLine);\n\n return lines.join(\"\\n\");\n}\n\n// A track's rationale (the WHY) can be multi-line and long; collapse whitespace\n// to one line and cap it so the handoff stays scannable. The full text lives in\n// the decision_recorded event (see decisions.md).\nconst HANDOFF_TRACK_RATIONALE_MAX = 240;\nfunction handoffRationale(rationale: string): string {\n const oneLine = rationale.replace(/\\s+/g, \" \").trim();\n return oneLine.length > HANDOFF_TRACK_RATIONALE_MAX\n ? `${oneLine.slice(0, HANDOFF_TRACK_RATIONALE_MAX - 1)}…`\n : oneLine;\n}\n\nfunction suspectLabel(reason: SuspectReason | null): string {\n if (reason === \"events_say_ended_but_yaml_running\") return \" ⚠ ended (yaml stale)\";\n if (reason === \"running_no_end_event\") return \" ⚠ no end event\";\n return \"\";\n}\n\n// First 10 chars after the `ses_` prefix. Matches the truncation that\n// `basou session list` uses for its shortest display column.\nfunction shortHandoffId(sessionId: string): string {\n const SES = \"ses_\";\n if (sessionId.startsWith(SES)) return sessionId.slice(SES.length, SES.length + 10);\n return sessionId.slice(0, 10);\n}\n\n// Prose-line short id: keeps the type prefix (`ses_` / `task_` / `decision_`)\n// and truncates the ULID body to its first 10 chars, e.g.\n// `task_01KRNHYRS91F5GBX2VTN9ADJFV` -> `task_01KRNHYRS9`. Unlike the session\n// table — whose column header already marks the column as ids — body lines mix\n// session / task / decision ids inline, so the prefix is kept to keep each id\n// self-describing while still demoting it behind the human-readable text.\nfunction shortIdWithPrefix(id: string): string {\n const sep = id.indexOf(\"_\");\n if (sep === -1) return id.slice(0, 10);\n return id.slice(0, sep + 1) + id.slice(sep + 1, sep + 1 + 10);\n}\n","/**\n * Shared \"resume coherence\" helpers for the orientation and handoff renderers,\n * so both judge staleness and pick the representative session identically.\n *\n * These exist because a resume (\"basou refresh して続きを再開\") must not present\n * a stale recorded decision as the current direction, nor represent the latest\n * work with an essentially empty session — the two failure modes that let an\n * agent re-attempt already-completed work on resume.\n */\n\n/**\n * A recorded decision / next-step note is \"trailing\" when captured activity\n * continued for more than this gap after it. Decisions are recorded only from\n * AskUserQuestion tool calls, `basou decision record`, or `basou decision\n * capture` — free-form conversational decisions are not auto-captured — so a\n * long trailing gap means the operator's current direction may simply be\n * unrecorded. 1h is a deliberately conservative threshold so a decision made\n * near a session's end does not trigger the note.\n */\nexport const DECISION_TRAILING_ACTIVITY_GAP_MS = 60 * 60 * 1000;\n\n/**\n * True when captured activity continued more than\n * {@link DECISION_TRAILING_ACTIVITY_GAP_MS} after `recordedAt`. Used to decide\n * whether a recorded decision / note should carry a staleness caveat instead of\n * being presented as the current direction. `latestActivityAt === null` (no\n * activity tail) is never stale.\n */\nexport function isTrailingStale(latestActivityAt: string | null, recordedAt: string): boolean {\n if (latestActivityAt === null) return false;\n return Date.parse(latestActivityAt) - Date.parse(recordedAt) > DECISION_TRAILING_ACTIVITY_GAP_MS;\n}\n\n/** Minimal shape needed to rank a session for \"representative latest session\". */\ntype RankableSessionEntry = {\n session: { session: { started_at: string; related_files?: readonly string[] } };\n};\n\n/**\n * Pick the session that should represent \"最終 session\" / latest work.\n *\n * A bare resume/refresh session (e.g. 1 command, 0 files) is the most RECENT\n * session but the least informative; selecting it hides the real-work session\n * and makes the latest-session and latest-decision pointers disagree. So rank a\n * session that touched files ahead of one that did not, then break ties by\n * recency (started_at). The result is the most recent SUBSTANTIVE session,\n * falling back to the most recent session overall when none touched files.\n *\n * Returns `undefined` for an empty list. Does not mutate the input.\n */\nexport function pickLatestSubstantiveEntry<E extends RankableSessionEntry>(\n entries: readonly E[],\n): E | undefined {\n return [...entries].sort((a, b) => {\n const aSubstantive = (a.session.session.related_files?.length ?? 0) > 0 ? 1 : 0;\n const bSubstantive = (b.session.session.related_files?.length ?? 0) > 0 ? 1 : 0;\n if (aSubstantive !== bSubstantive) return bSubstantive - aSubstantive;\n return Date.parse(b.session.session.started_at) - Date.parse(a.session.session.started_at);\n })[0];\n}\n","import { createHash } from \"node:crypto\";\nimport { mkdir, readdir, readFile, rename, stat, unlink } from \"node:fs/promises\";\nimport { join } from \"node:path\";\nimport { parse as parseYaml, stringify as stringifyYaml } from \"yaml\";\nimport { z } from \"zod\";\nimport type { PrefixedId } from \"../ids/ulid.js\";\nimport { findErrorCode } from \"../lib/error-codes.js\";\nimport type { Event } from \"../schemas/event.schema.js\";\nimport type { Manifest } from \"../schemas/manifest.schema.js\";\nimport type { SessionStatus } from \"../schemas/session.schema.js\";\nimport { IsoTimestampSchema, SessionIdSchema, TaskIdSchema } from \"../schemas/shared.schema.js\";\nimport {\n type Task,\n TaskSchema,\n type TaskStatus,\n TaskStatusSchema,\n} from \"../schemas/task.schema.js\";\nimport type { TaskIndexEntry } from \"../schemas/task-index.schema.js\";\nimport {\n type AttachableStatus,\n appendEventToExistingSession,\n createAdHocSessionWithEvent,\n FailedToFinalizeError,\n} from \"./ad-hoc-session.js\";\nimport { atomicCreate, atomicReplace } from \"./atomic.js\";\nimport type { BasouPaths } from \"./basou-dir.js\";\nimport { acquireLock } from \"./lockfile.js\";\nimport { enumerateSessionDirs, readSessionYaml } from \"./sessions.js\";\nimport { readTaskIndex, rebuildTaskIndex, updateTaskIndex } from \"./task-index.js\";\nimport { overwriteYamlFile } from \"./yaml-store.js\";\n\n// ============================================================================\n// File format constants\n// ============================================================================\n\nconst FRONT_MATTER_DELIM = \"---\";\n// Raised from the original 40-char cap to 80 chars so long task /\n// reconcile titles retain their core information. The same cap applies\n// to `Ad-hoc task:`, `Ad-hoc task status:`, and `Ad-hoc task reconcile:`\n// labels so the three ad-hoc label generators stay consistent with the\n// decision-side cap (cli/src/commands/decision.ts).\nconst LABEL_TITLE_MAX = 80;\nconst LABEL_TRUNCATE_HEAD = LABEL_TITLE_MAX - 3;\n\nconst DEFAULT_ATTACHABLE_STATUSES: ReadonlySet<AttachableStatus> = new Set<AttachableStatus>([\n \"initialized\",\n \"running\",\n \"waiting_approval\",\n]);\n\n// Boundary parses for direct callers so a malformed task cannot smuggle\n// past the CLI-side parsers and commit a `task_created` event. The set\n// originally rejected `done` / `cancelled` as initial values, but the\n// orchestrator now emits a follow-up `task_status_changed` for terminal\n// initial statuses so retroactively-recorded completed tasks can be\n// entered in one CLI call; widening the schema lets that path through.\nconst InitialTaskStatusSchema = TaskStatusSchema;\nconst TaskTitleSchema = z.string().min(1);\nconst TaskLabelSchema = z.string().min(1);\n// `completedAt` is an optional ISO-8601 string. Validate it at the boundary\n// so a direct (non-CLI) caller cannot smuggle a garbage timestamp past the\n// orchestrator and leave durable `task_created` / `task_status_changed`\n// events with no valid task.md to back them up.\nconst CompletedAtSchema = IsoTimestampSchema;\n\nconst TERMINAL_TASK_STATUSES: ReadonlySet<TaskStatus> = new Set<TaskStatus>([\"done\", \"cancelled\"]);\n\nfunction isTerminalTaskStatus(status: TaskStatus): boolean {\n return TERMINAL_TASK_STATUSES.has(status);\n}\n\n// ============================================================================\n// File read / parse\n// ============================================================================\n\nexport type TaskDocument = {\n /** Parsed + zod-validated front matter. */\n task: Task;\n /** Raw markdown body after the closing front matter delimiter. */\n body: string;\n};\n\n/**\n * Split a task.md file body into the YAML front matter and the trailing\n * markdown body. The expected format is:\n *\n * ---\\n\n * <yaml>\\n\n * ---\\n\n * <body>\n *\n * Strict rules:\n * - A UTF-8 BOM at the head is rejected.\n * - CRLF inside the file is normalised to LF before delimiter scanning so\n * editors that auto-convert line endings stay compatible.\n * - The closing delimiter is the FIRST `---` line after the opening one,\n * so `---` lines inside the markdown body do not confuse the parser.\n */\nfunction splitFrontMatter(raw: string): { yamlText: string; body: string } {\n if (raw.length > 0 && raw.charCodeAt(0) === 0xfeff) {\n throw new Error(\"Invalid task file format\");\n }\n const normalised = raw.replace(/\\r\\n/g, \"\\n\");\n if (!normalised.startsWith(`${FRONT_MATTER_DELIM}\\n`)) {\n throw new Error(\"Invalid task file format\");\n }\n const remainder = normalised.slice(FRONT_MATTER_DELIM.length + 1);\n // Find the first line that is exactly `---`. Scan line-by-line so a `---`\n // appearing mid-line inside YAML text is not matched.\n const lines = remainder.split(\"\\n\");\n let closingIdx = -1;\n for (let i = 0; i < lines.length; i++) {\n if (lines[i] === FRONT_MATTER_DELIM) {\n closingIdx = i;\n break;\n }\n }\n if (closingIdx < 0) {\n throw new Error(\"Invalid task file format\");\n }\n const yamlText = lines.slice(0, closingIdx).join(\"\\n\");\n // The body is everything after the closing delimiter line, with one\n // separating newline consumed (if present) so the body does not start\n // with a stray blank line.\n const afterClosing = lines.slice(closingIdx + 1);\n let body = afterClosing.join(\"\\n\");\n if (body.startsWith(\"\\n\")) body = body.slice(1);\n return { yamlText, body };\n}\n\n/**\n * Read and validate `<paths.tasks>/<taskId>.md`. Returns the parsed front\n * matter (Task) plus the markdown body string. Error contract:\n *\n * - ENOENT → throw `\"Task file not found\"`.\n * - format violation → throw `\"Invalid task file format\"`.\n * - YAML parse / schema violation → throw `\"Failed to read task file\"`.\n * - any other I/O failure → throw `\"Failed to read task file\"` with cause.\n */\nexport async function readTaskFile(paths: BasouPaths, taskId: string): Promise<TaskDocument> {\n const filePath = join(paths.tasks, `${taskId}.md`);\n let raw: string;\n try {\n raw = await readFile(filePath, \"utf8\");\n } catch (error: unknown) {\n if (findErrorCode(error, \"ENOENT\")) {\n throw new Error(\"Task file not found\", { cause: error });\n }\n throw new Error(\"Failed to read task file\", { cause: error });\n }\n let split: { yamlText: string; body: string };\n try {\n split = splitFrontMatter(raw);\n } catch (error: unknown) {\n if (error instanceof Error && error.message === \"Invalid task file format\") {\n throw error;\n }\n throw new Error(\"Failed to read task file\", { cause: error });\n }\n let parsed: unknown;\n try {\n parsed = parseYaml(split.yamlText);\n } catch (error: unknown) {\n throw new Error(\"Failed to read task file\", { cause: error });\n }\n const result = TaskSchema.safeParse(parsed);\n if (!result.success) {\n throw new Error(\"Failed to read task file\", { cause: result.error });\n }\n return { task: result.data, body: split.body };\n}\n\n// ============================================================================\n// File write (atomic, mode-aware)\n// ============================================================================\n\nexport type WriteTaskFileMode = \"create\" | \"overwrite\";\n\n/**\n * Atomically write `<paths.tasks>/<taskId>.md`.\n *\n * `mode: \"create\"` delegates to {@link atomicCreate} so a pre-existing file\n * fails fast with EEXIST → `\"Task file already exists\"`.\n * `mode: \"overwrite\"` delegates to {@link atomicReplace} and silently\n * replaces any prior file.\n *\n * The serialised body is structured as:\n *\n * ---\\n\n * <yaml>\\n\n * ---\\n\n * \\n\n * <body>\\n (only when body is non-empty)\n */\nexport async function writeTaskFile(\n paths: BasouPaths,\n taskId: string,\n doc: TaskDocument,\n options: { mode: WriteTaskFileMode },\n): Promise<void> {\n // Runtime self-defense: even if a caller bypassed the TypeScript boundary,\n // a malformed task object cannot reach disk.\n const validated = TaskSchema.parse(doc.task);\n\n const filePath = join(paths.tasks, `${taskId}.md`);\n const yamlText = stringifyYaml(validated);\n const trimmedBody =\n doc.body.length === 0 ? \"\" : `\\n${doc.body.endsWith(\"\\n\") ? doc.body : `${doc.body}\\n`}`;\n const fileBody = `${FRONT_MATTER_DELIM}\\n${yamlText}${FRONT_MATTER_DELIM}\\n${trimmedBody}`;\n\n if (options.mode === \"create\") {\n try {\n await atomicCreate(filePath, fileBody);\n } catch (error: unknown) {\n if (findErrorCode(error, \"EEXIST\")) {\n throw new Error(\"Task file already exists\", { cause: error });\n }\n throw new Error(\"Failed to write task file\", { cause: error });\n }\n return;\n }\n\n // overwrite mode\n try {\n await atomicReplace(filePath, fileBody);\n } catch (error: unknown) {\n throw new Error(\"Failed to write task file\", { cause: error });\n }\n}\n\n// ============================================================================\n// Directory enumeration / loading\n// ============================================================================\n\nconst TASK_FILENAME_RE = /^(.+)\\.md$/;\n\n/**\n * Enumerate task ids by listing `<paths.tasks>/`. Filenames that do not\n * match the `<task_id>.md` shape, or that decode to a non-conforming task\n * id (per `TaskIdSchema`), are silently skipped — they are surfaced via\n * the caller's `options.onSkip` hook in {@link loadTaskEntries} so list\n * commands can show a warning row.\n *\n * Returns ids in ULID-ascending order (filename sort matches ULID order).\n * Empty directory or ENOENT → `[]`. Other I/O failures throw\n * `\"Failed to enumerate tasks\"`.\n */\nexport async function enumerateTaskIds(paths: BasouPaths): Promise<string[]> {\n // Fast path: read `tasks/index.json` if it exists and is valid. The index\n // is maintained write-through by every task mutation API so the cache\n // matches disk except across crashes / hand-edits / version bumps.\n try {\n const index = await readTaskIndex(paths);\n return index.tasks.map((t) => t.id);\n } catch {\n // Index missing / parse fail / schema mismatch — fall through to the\n // disk-scan rebuild path below. The discrete error classes from\n // readTaskIndex (Task index not found / Invalid task index / Failed\n // to read task index) are all equivalent here.\n }\n\n const ids = await enumerateTaskIdsFromDisk(paths);\n\n // Skip the lazy rebuild entirely when there is nothing to record: a\n // pre-init / empty workspace has no tasks/ dir, so a rebuild attempt\n // would fail with ENOENT and emit a misleading warning. The next write\n // will recreate the index from scratch via updateTaskIndex anyway.\n if (ids.length === 0) {\n return ids;\n }\n\n // Lazy rebuild: scan each task.md and write the resulting index. Best-\n // effort — a per-task read failure (= a malformed file we'd surface as\n // a skip elsewhere) is excluded from the rebuilt index but still\n // returned to the caller in `ids` so loadTaskEntries can surface its\n // own skip reason. Rebuild write failure (disk full, EACCES) is logged\n // and swallowed; the next enumerateTaskIds call will retry the rebuild.\n const entries: TaskIndexEntry[] = [];\n for (const id of ids) {\n try {\n const doc = await readTaskFile(paths, id);\n entries.push(buildTaskIndexEntry(doc.task.task));\n } catch {\n // Skip unreadable entry from the rebuild.\n }\n }\n await rebuildTaskIndex(paths, entries).catch(() => {\n console.warn(\"Failed to rebuild tasks/index.json; subsequent reads will retry\");\n });\n return ids;\n}\n\nasync function enumerateTaskIdsFromDisk(paths: BasouPaths): Promise<string[]> {\n let entries: string[];\n try {\n entries = (await readdir(paths.tasks, { withFileTypes: true }))\n .filter((d) => d.isFile())\n .map((d) => d.name);\n } catch (error: unknown) {\n if (findErrorCode(error, \"ENOENT\")) return [];\n throw new Error(\"Failed to enumerate tasks\", { cause: error });\n }\n const taskIds: string[] = [];\n for (const name of entries) {\n const match = TASK_FILENAME_RE.exec(name);\n if (match === null) continue;\n const candidate = match[1] as string;\n if (!TaskIdSchema.safeParse(candidate).success) continue;\n taskIds.push(candidate);\n }\n taskIds.sort();\n return taskIds;\n}\n\n/**\n * Convert the inner `task` portion of a task.md document into the\n * compact entry shape stored inside `tasks/index.json`. Omits `label`\n * when the task has no label set so the JSON does not store an\n * `undefined` literal.\n */\nfunction buildTaskIndexEntry(task: Task[\"task\"]): TaskIndexEntry {\n return {\n id: task.id,\n status: task.status,\n ...(task.label !== undefined ? { label: task.label } : {}),\n updated_at: task.updated_at,\n };\n}\n\n/**\n * Apply a single write-through update to `tasks/index.json` and swallow\n * any failure with a console.warn so the calling task-write API stays\n * successful (= task.md is the source of truth, index is a soft cache).\n */\nasync function safeUpdateTaskIndex(\n paths: BasouPaths,\n op: Parameters<typeof updateTaskIndex>[1],\n): Promise<void> {\n try {\n await updateTaskIndex(paths, op);\n } catch {\n console.warn(\"Index update failed; rebuild on next read\");\n }\n}\n\nconst ARCHIVE_DIR_NAME = \"archive\";\n\nfunction archiveTasksDir(paths: BasouPaths): string {\n return join(paths.tasks, ARCHIVE_DIR_NAME);\n}\n\n/**\n * Enumerate task ids inside `<paths.tasks>/archive/`. Returns `[]` when the\n * archive directory does not exist (= no task has ever been archived).\n * Filtering / ordering rules mirror {@link enumerateTaskIds}.\n */\nexport async function enumerateArchivedTaskIds(paths: BasouPaths): Promise<string[]> {\n let entries: string[];\n try {\n entries = (await readdir(archiveTasksDir(paths), { withFileTypes: true }))\n .filter((d) => d.isFile())\n .map((d) => d.name);\n } catch (error: unknown) {\n if (findErrorCode(error, \"ENOENT\")) return [];\n throw new Error(\"Failed to enumerate archived tasks\", { cause: error });\n }\n const taskIds: string[] = [];\n for (const name of entries) {\n const match = TASK_FILENAME_RE.exec(name);\n if (match === null) continue;\n const candidate = match[1] as string;\n if (!TaskIdSchema.safeParse(candidate).success) continue;\n taskIds.push(candidate);\n }\n taskIds.sort();\n return taskIds;\n}\n\n/**\n * Read a task.md file looking in the main tasks directory first and falling\n * back to `<paths.tasks>/archive/` if the file is missing there. Returns the\n * parsed document plus a flag indicating whether the hit came from the\n * archive dir. Useful for `basou task show` which surfaces archived tasks\n * read-only without requiring the operator to opt in.\n *\n * Error contract matches {@link readTaskFile} — only the lookup location\n * differs.\n */\nexport async function readTaskFileWithArchiveFallback(\n paths: BasouPaths,\n taskId: string,\n): Promise<{ doc: TaskDocument; archived: boolean }> {\n try {\n const doc = await readTaskFile(paths, taskId);\n return { doc, archived: false };\n } catch (error: unknown) {\n if (!(error instanceof Error && error.message === \"Task file not found\")) {\n throw error;\n }\n }\n const archiveFilePath = join(archiveTasksDir(paths), `${taskId}.md`);\n let raw: string;\n try {\n raw = await readFile(archiveFilePath, \"utf8\");\n } catch (error: unknown) {\n if (findErrorCode(error, \"ENOENT\")) {\n throw new Error(\"Task file not found\", { cause: error });\n }\n throw new Error(\"Failed to read task file\", { cause: error });\n }\n // Parsing mirrors readTaskFile; archived files share the schema.\n let split: { yamlText: string; body: string };\n try {\n split = splitFrontMatter(raw);\n } catch (error: unknown) {\n if (error instanceof Error && error.message === \"Invalid task file format\") {\n throw error;\n }\n throw new Error(\"Failed to read task file\", { cause: error });\n }\n let parsed: unknown;\n try {\n parsed = parseYaml(split.yamlText);\n } catch (error: unknown) {\n throw new Error(\"Failed to read task file\", { cause: error });\n }\n const result = TaskSchema.safeParse(parsed);\n if (!result.success) {\n throw new Error(\"Failed to read task file\", { cause: result.error });\n }\n return { doc: { task: result.data, body: split.body }, archived: true };\n}\n\nexport type TaskSkipReason = \"task_file_invalid\" | \"task_file_unreadable\";\n\nexport type LoadTaskEntriesOptions = {\n onSkip?: (taskId: string, reason: TaskSkipReason) => void;\n};\n\n/**\n * Read every task.md under `<paths.tasks>/` and return the valid documents,\n * skipping malformed / unreadable files with an `onSkip` callback for each.\n *\n * Returned entries are sorted ascending by `task.created_at` (internal asc;\n * the CLI layer reverses for newest-first display).\n */\nexport async function loadTaskEntries(\n paths: BasouPaths,\n options: LoadTaskEntriesOptions = {},\n): Promise<TaskDocument[]> {\n const ids = await enumerateTaskIds(paths);\n const entries: TaskDocument[] = [];\n for (const id of ids) {\n let doc: TaskDocument;\n try {\n doc = await readTaskFile(paths, id);\n } catch (error: unknown) {\n if (error instanceof Error && error.message === \"Invalid task file format\") {\n options.onSkip?.(id, \"task_file_invalid\");\n } else if (error instanceof Error && error.message === \"Failed to read task file\") {\n options.onSkip?.(id, \"task_file_invalid\");\n } else if (error instanceof Error && error.message === \"Task file not found\") {\n // Race: file was enumerated then deleted before read. Treat as unreadable.\n options.onSkip?.(id, \"task_file_unreadable\");\n } else {\n options.onSkip?.(id, \"task_file_unreadable\");\n }\n continue;\n }\n entries.push(doc);\n }\n entries.sort((a, b) => {\n const c = Date.parse(a.task.task.created_at) - Date.parse(b.task.task.created_at);\n return c !== 0 ? c : a.task.task.id.localeCompare(b.task.task.id);\n });\n return entries;\n}\n\n// ============================================================================\n// Status transition rules\n// ============================================================================\n\n// `planned -> done` and `planned -> cancelled` are direct shortcuts so a\n// task that was queued but completed (or abandoned) outside of an explicit\n// `in_progress` phase can be closed with a single CLI call. The 1\n// transition = 1 event invariant is preserved: each shortcut emits exactly\n// one `task_status_changed` event capturing the new from / to pair.\nconst ALLOWED_TRANSITIONS: Readonly<Record<TaskStatus, ReadonlySet<TaskStatus>>> = {\n planned: new Set<TaskStatus>([\"in_progress\", \"done\", \"cancelled\"]),\n in_progress: new Set<TaskStatus>([\"done\", \"cancelled\"]),\n done: new Set<TaskStatus>(),\n cancelled: new Set<TaskStatus>(),\n};\n\nfunction assertTransitionAllowed(from: TaskStatus, to: TaskStatus): void {\n const allowed = ALLOWED_TRANSITIONS[from];\n if (!allowed.has(to)) {\n throw new Error(`Invalid task status transition: ${from} -> ${to}`);\n }\n}\n\n// ============================================================================\n// Specialised error for task.md write failure after the event was persisted\n// ============================================================================\n\n/**\n * Thrown when the task event (`task_created` / `task_status_changed`) was\n * fully persisted to events.jsonl but the accompanying `task.md` write\n * failed. The caller is responsible for surfacing a \"do not rerun\"\n * warning — re-running the same CLI invocation would duplicate the event\n * in events.jsonl.\n *\n * Reconciliation (= regenerating the missing task.md from events) is a\n * v0.2 follow-up (= `task reconcile` family).\n */\n/**\n * `phase` identifies which staged write failed after the event commit:\n * - `create`: task.md create write (ad-hoc or attach path)\n * - `overwrite`: task.md overwrite during a status change\n * - `link-session`: session.yaml `task_id` update during the attach path\n * (split out so CLI warnings describe the actual unsafe artefact\n * instead of always saying \"task.md creation failed\")\n * - `reconcile`: task.md overwrite during `basou task reconcile --write`\n * after the `task_reconciled` event was persisted\n * - `reconcile-finalize`: ad-hoc reconcile session finalize failed (=\n * `FailedToFinalizeError` caught and re-classified)\n * - `reconcile-concurrent`: task.md was modified between the pre-write\n * snapshot and the post-event re-read; the operator is told to re-run\n * reconcile rather than overwrite a stale snapshot\n */\nexport type TaskWriteAfterEventPhase =\n | \"create\"\n | \"overwrite\"\n | \"link-session\"\n | \"reconcile\"\n | \"reconcile-finalize\"\n | \"reconcile-concurrent\"\n // Mirror the reconcile-failure phases for the `refreshTaskLinkedSessions`\n // path. Failure semantics are identical (= ad-hoc session committed, then\n // task.md write / concurrency check failed), but the operator-facing\n // recovery hint must point at `basou task refresh-linkage`, not reconcile.\n | \"linkage-refresh\"\n | \"linkage-refresh-finalize\"\n | \"linkage-refresh-concurrent\"\n // `task_deleted` / `task_archived` event was persisted in events.jsonl but\n // the subsequent file mutation (unlink / move to archive) failed. The\n // event remains the authoritative audit record; the operator must reconcile\n // the residual file state by hand.\n | \"delete\"\n | \"archive\";\n\nexport class TaskWriteAfterEventError extends Error {\n readonly taskId: PrefixedId<\"task\">;\n readonly eventId: PrefixedId<\"evt\">;\n readonly sessionId: PrefixedId<\"ses\">;\n readonly phase: TaskWriteAfterEventPhase;\n\n constructor(args: {\n taskId: PrefixedId<\"task\">;\n eventId: PrefixedId<\"evt\">;\n sessionId: PrefixedId<\"ses\">;\n phase: TaskWriteAfterEventPhase;\n cause: unknown;\n }) {\n super(\"Failed to write task file after event was persisted\", { cause: args.cause });\n this.name = \"TaskWriteAfterEventError\";\n this.taskId = args.taskId;\n this.eventId = args.eventId;\n this.sessionId = args.sessionId;\n this.phase = args.phase;\n }\n}\n\n// ============================================================================\n// Orchestrator: createTaskWithEvent\n// ============================================================================\n\nexport type CreateAdHocTaskInput = {\n mode: \"ad-hoc\";\n paths: BasouPaths;\n manifest: Manifest;\n occurredAt: string;\n taskId: PrefixedId<\"task\">;\n title: string;\n label?: string;\n initialStatus: TaskStatus;\n description: string;\n workingDirectory: string;\n /**\n * Optional override for `task.md.updated_at` when `initialStatus` is a\n * terminal value (done / cancelled). Lets the operator backdate a\n * retroactively-recorded completed task so `task.md` reflects the actual\n * completion moment while `events.jsonl` keeps recording time. Ignored\n * for non-terminal statuses.\n */\n completedAt?: string;\n};\n\nexport type AttachTaskInput = {\n mode: \"attach\";\n paths: BasouPaths;\n occurredAt: string;\n sessionId: PrefixedId<\"ses\">;\n taskId: PrefixedId<\"task\">;\n title: string;\n label?: string;\n initialStatus: TaskStatus;\n description: string;\n attachableStatuses?: ReadonlySet<AttachableStatus>;\n /** See {@link CreateAdHocTaskInput.completedAt}. */\n completedAt?: string;\n};\n\nexport type CreateTaskInput = CreateAdHocTaskInput | AttachTaskInput;\n\nexport type CreateTaskResult = {\n taskId: PrefixedId<\"task\">;\n eventId: PrefixedId<\"evt\">;\n sessionId: PrefixedId<\"ses\">;\n sessionStatus: SessionStatus;\n};\n\nfunction buildTaskCreatedEvent(input: {\n eventId: PrefixedId<\"evt\">;\n sessionId: PrefixedId<\"ses\">;\n taskId: PrefixedId<\"task\">;\n title: string;\n occurredAt: string;\n}): Event {\n return {\n schema_version: \"0.1.0\",\n id: input.eventId,\n session_id: input.sessionId,\n occurred_at: input.occurredAt,\n source: \"local-cli\",\n type: \"task_created\",\n task_id: input.taskId,\n title: input.title,\n };\n}\n\nfunction buildTaskStatusChangedEvent(input: {\n eventId: PrefixedId<\"evt\">;\n sessionId: PrefixedId<\"ses\">;\n taskId: PrefixedId<\"task\">;\n from: TaskStatus;\n to: TaskStatus;\n occurredAt: string;\n}): Event {\n return {\n schema_version: \"0.1.0\",\n id: input.eventId,\n session_id: input.sessionId,\n occurred_at: input.occurredAt,\n source: \"local-cli\",\n type: \"task_status_changed\",\n task_id: input.taskId,\n from: input.from,\n to: input.to,\n };\n}\n\nfunction buildAdHocTaskLabel(title: string, mode: \"new\" | \"status\"): string {\n const truncated =\n title.length > LABEL_TITLE_MAX ? `${title.slice(0, LABEL_TRUNCATE_HEAD)}...` : title;\n return mode === \"new\" ? `Ad-hoc task: ${truncated}` : `Ad-hoc task status: ${truncated}`;\n}\n\n// Kept distinct from buildAdHocTaskLabel rather than threading a third mode\n// through that helper — `basou task reconcile` is a management operation, not\n// a creation/status flow, and the label prefix should read that way.\nfunction buildAdHocReconcileLabel(title: string): string {\n const truncated =\n title.length > LABEL_TITLE_MAX ? `${title.slice(0, LABEL_TRUNCATE_HEAD)}...` : title;\n return `Ad-hoc task reconcile: ${truncated}`;\n}\n\n// Separate label generator for `basou task refresh-linkage` so the operator\n// can distinguish refresh runs from reconcile runs at a glance in session\n// listings — both flow through `createAdHocSessionWithEvent` but answer\n// different questions (broken-ref repair vs. snapshot-vs-events sync).\nfunction buildAdHocRefreshLinkageLabel(title: string): string {\n const truncated =\n title.length > LABEL_TITLE_MAX ? `${title.slice(0, LABEL_TRUNCATE_HEAD)}...` : title;\n return `Ad-hoc task refresh-linkage: ${truncated}`;\n}\n\nfunction buildAdHocDeleteLabel(title: string): string {\n const truncated =\n title.length > LABEL_TITLE_MAX ? `${title.slice(0, LABEL_TRUNCATE_HEAD)}...` : title;\n return `Ad-hoc task delete: ${truncated}`;\n}\n\nfunction buildAdHocArchiveLabel(title: string): string {\n const truncated =\n title.length > LABEL_TITLE_MAX ? `${title.slice(0, LABEL_TRUNCATE_HEAD)}...` : title;\n return `Ad-hoc task archive: ${truncated}`;\n}\n\nfunction buildTaskReconciledEvent(input: {\n eventId: PrefixedId<\"evt\">;\n sessionId: PrefixedId<\"ses\">;\n taskId: PrefixedId<\"task\">;\n removedCreatedInSession: PrefixedId<\"ses\"> | null;\n createdInSessionReplacement: PrefixedId<\"ses\"> | null;\n removedLinkedSessions: PrefixedId<\"ses\">[];\n occurredAt: string;\n}): Event {\n return {\n schema_version: \"0.1.0\",\n id: input.eventId,\n session_id: input.sessionId,\n occurred_at: input.occurredAt,\n source: \"local-cli\",\n type: \"task_reconciled\",\n task_id: input.taskId,\n removed_created_in_session: input.removedCreatedInSession,\n created_in_session_replacement: input.createdInSessionReplacement,\n removed_linked_sessions: input.removedLinkedSessions,\n };\n}\n\nfunction buildTaskDeletedEvent(input: {\n eventId: PrefixedId<\"evt\">;\n sessionId: PrefixedId<\"ses\">;\n taskId: PrefixedId<\"task\">;\n title: string;\n occurredAt: string;\n}): Event {\n return {\n schema_version: \"0.1.0\",\n id: input.eventId,\n session_id: input.sessionId,\n occurred_at: input.occurredAt,\n source: \"local-cli\",\n type: \"task_deleted\",\n task_id: input.taskId,\n title: input.title,\n };\n}\n\nfunction buildTaskArchivedEvent(input: {\n eventId: PrefixedId<\"evt\">;\n sessionId: PrefixedId<\"ses\">;\n taskId: PrefixedId<\"task\">;\n title: string;\n occurredAt: string;\n}): Event {\n return {\n schema_version: \"0.1.0\",\n id: input.eventId,\n session_id: input.sessionId,\n occurred_at: input.occurredAt,\n source: \"local-cli\",\n type: \"task_archived\",\n task_id: input.taskId,\n title: input.title,\n };\n}\n\nfunction buildTaskLinkageRefreshedEvent(input: {\n eventId: PrefixedId<\"evt\">;\n sessionId: PrefixedId<\"ses\">;\n taskId: PrefixedId<\"task\">;\n addedLinkedSessions: PrefixedId<\"ses\">[];\n removedLinkedSessions: PrefixedId<\"ses\">[];\n finalCount: number;\n occurredAt: string;\n}): Event {\n return {\n schema_version: \"0.1.0\",\n id: input.eventId,\n session_id: input.sessionId,\n occurred_at: input.occurredAt,\n source: \"local-cli\",\n type: \"task_linkage_refreshed\",\n task_id: input.taskId,\n added_linked_sessions: input.addedLinkedSessions,\n removed_linked_sessions: input.removedLinkedSessions,\n final_count: input.finalCount,\n };\n}\n\n/**\n * Create a new task: fires a single `task_created` event and writes\n * `.basou/tasks/<taskId>.md` with status = `initialStatus`.\n *\n * Ad-hoc path: a fresh ad-hoc session is minted (5-event bulk write,\n * `task_created` as the target event, session.yaml.task_id pinned to the\n * new task).\n *\n * Attach path: the target session's `task_id` is validated against the\n * session ⇆ task anchor invariant (see\n * `docs/spec/workspace.md#21-confirmed-invariants`; null → updated to the\n * new task; existing X → rejected since X is already owned). If validation\n * passes, the event is appended to events.jsonl and session.yaml's\n * `task_id` is updated to the new task.\n *\n * Race window (v0.1 accepts): stage 2 writes the event, stage 3 writes\n * task.md. A failure on stage 3 leaves events.jsonl ahead of task.md;\n * {@link TaskWriteAfterEventError} surfaces this with a \"do not rerun\"\n * warning so the operator can reconcile manually until the v0.2 reconcile\n * flow arrives.\n */\nexport async function createTaskWithEvent(input: CreateTaskInput): Promise<CreateTaskResult> {\n // Boundary parses so direct (non-CLI) callers can't smuggle in malformed\n // ids / statuses / titles past the CLI-side guards. All checks here run\n // BEFORE any persistent write, so a rejection leaves events.jsonl and\n // task.md untouched.\n TaskIdSchema.parse(input.taskId);\n InitialTaskStatusSchema.parse(input.initialStatus);\n TaskTitleSchema.parse(input.title);\n if (input.label !== undefined) {\n TaskLabelSchema.parse(input.label);\n }\n if (input.completedAt !== undefined) {\n CompletedAtSchema.parse(input.completedAt);\n }\n\n if (input.mode === \"ad-hoc\") {\n return createTaskAdHoc(input);\n }\n return createTaskAttach(input);\n}\n\nasync function createTaskAdHoc(input: CreateAdHocTaskInput): Promise<CreateTaskResult> {\n const adHoc = await createAdHocSessionWithEvent({\n paths: input.paths,\n manifest: input.manifest,\n label: buildAdHocTaskLabel(input.title, \"new\"),\n occurredAt: input.occurredAt,\n sessionSource: \"human\",\n workingDirectory: input.workingDirectory,\n invocation: {\n command: \"basou task new\",\n args: buildTaskNewInvocationArgs(input.title, input.initialStatus, input.completedAt),\n },\n taskId: input.taskId,\n targetEventBuilders: buildTaskNewTargetEventBuilders({\n taskId: input.taskId,\n title: input.title,\n initialStatus: input.initialStatus,\n occurredAt: input.occurredAt,\n }),\n });\n\n const task: Task = buildInitialTask({\n taskId: input.taskId,\n title: input.title,\n ...(input.label !== undefined ? { label: input.label } : {}),\n status: input.initialStatus,\n occurredAt: input.occurredAt,\n ...(input.completedAt !== undefined ? { completedAt: input.completedAt } : {}),\n workspaceId: input.manifest.workspace.id,\n createdInSession: adHoc.sessionId,\n });\n // `targetEventIds[0]` is the `task_created` anchor (= what the caller cares\n // about); a second `task_status_changed` event may also live in this\n // ad-hoc session when initialStatus is terminal, but it is not the\n // primary task-lifecycle anchor.\n const anchorEventId = adHoc.targetEventIds[0] as PrefixedId<\"evt\">;\n try {\n await writeTaskFile(\n input.paths,\n input.taskId,\n { task, body: input.description },\n { mode: \"create\" },\n );\n } catch (error: unknown) {\n throw new TaskWriteAfterEventError({\n taskId: input.taskId,\n eventId: anchorEventId,\n sessionId: adHoc.sessionId,\n phase: \"create\",\n cause: error,\n });\n }\n await safeUpdateTaskIndex(input.paths, { kind: \"add\", entry: buildTaskIndexEntry(task.task) });\n return {\n taskId: input.taskId,\n eventId: anchorEventId,\n sessionId: adHoc.sessionId,\n sessionStatus: \"completed\",\n };\n}\n\nasync function createTaskAttach(input: AttachTaskInput): Promise<CreateTaskResult> {\n SessionIdSchema.parse(input.sessionId);\n\n // Per-session lock spans the entire attach window (session.yaml read →\n // collision-matrix check → task_created append → session.yaml task_id\n // update → optional task_status_changed append). Without it, two\n // concurrent attaches on the same session.yaml could both observe a\n // null task_id, both append their own `task_created`, and race on the\n // final task_id overwrite.\n const sessionLock = await acquireLock(input.paths, \"session\", input.sessionId);\n try {\n return await createTaskAttachLocked(input);\n } finally {\n await sessionLock.release();\n }\n}\n\nasync function createTaskAttachLocked(input: AttachTaskInput): Promise<CreateTaskResult> {\n // 1. Read session.yaml + validate the §F.7.2 collision matrix BEFORE writing\n // anything. status / task_id checks share the same read.\n const sessionDoc = await readSessionYaml(input.paths, input.sessionId);\n const status = sessionDoc.session.status;\n if (status === \"imported\") {\n throw new Error(\"Cannot attach to imported session\");\n }\n const attachable = input.attachableStatuses ?? DEFAULT_ATTACHABLE_STATUSES;\n if (!attachable.has(status as AttachableStatus)) {\n throw new Error(`Session is not active: ${status}`);\n }\n const existingTaskId = sessionDoc.session.task_id ?? null;\n if (existingTaskId !== null && existingTaskId !== input.taskId) {\n throw new Error(`Session already linked to a different task: ${existingTaskId}`);\n }\n if (existingTaskId === input.taskId) {\n // Re-creating the same task on the same session would duplicate\n // `task_created` in events.jsonl. Reject up front.\n throw new Error(`Task already exists: ${input.taskId}`);\n }\n\n // 2. Append `task_created` to events.jsonl. We use appendEventToExistingSession\n // so the same status/imported-rejection logic is shared across attach-flavoured callers.\n const appendResult = await appendEventToExistingSession({\n paths: input.paths,\n sessionId: input.sessionId,\n ...(input.attachableStatuses !== undefined\n ? { attachableStatuses: input.attachableStatuses }\n : {}),\n eventBuilder: (eventId) =>\n buildTaskCreatedEvent({\n eventId,\n sessionId: input.sessionId,\n taskId: input.taskId,\n title: input.title,\n occurredAt: input.occurredAt,\n }),\n });\n\n // 3. Update session.yaml task_id (null → new) so the single-session ↔\n // single-task invariant holds. Failure here puts us into the same\n // \"event persisted, side-effect missing\" band as task.md. Use\n // phase: \"link-session\" so the operator warning identifies the failed\n // artefact correctly.\n try {\n const updated = {\n ...sessionDoc,\n session: { ...sessionDoc.session, task_id: input.taskId },\n };\n await overwriteYamlFile(join(input.paths.sessions, input.sessionId, \"session.yaml\"), updated);\n } catch (error: unknown) {\n throw new TaskWriteAfterEventError({\n taskId: input.taskId,\n eventId: appendResult.eventId,\n sessionId: input.sessionId,\n phase: \"link-session\",\n cause: error,\n });\n }\n\n // 4. For terminal initialStatus (done / cancelled) append a second target\n // event `task_status_changed (planned → terminal)` so the events.jsonl\n // audit trail records the implicit transition. The ALLOWED_TRANSITIONS\n // shortcut from `planned` to `done|cancelled` makes this a single\n // permitted edge. The session.yaml `task_id` link from step 3 covers\n // both events; no further session.yaml write is needed.\n if (isTerminalTaskStatus(input.initialStatus)) {\n await appendEventToExistingSession({\n paths: input.paths,\n sessionId: input.sessionId,\n ...(input.attachableStatuses !== undefined\n ? { attachableStatuses: input.attachableStatuses }\n : {}),\n eventBuilder: (eventId) =>\n buildTaskStatusChangedEvent({\n eventId,\n sessionId: input.sessionId,\n taskId: input.taskId,\n from: \"planned\",\n to: input.initialStatus,\n occurredAt: input.occurredAt,\n }),\n });\n }\n\n // 5. Write task.md (create mode, collision = rerun guard).\n const task: Task = buildInitialTask({\n taskId: input.taskId,\n title: input.title,\n ...(input.label !== undefined ? { label: input.label } : {}),\n status: input.initialStatus,\n occurredAt: input.occurredAt,\n ...(input.completedAt !== undefined ? { completedAt: input.completedAt } : {}),\n workspaceId: sessionDoc.session.workspace_id,\n createdInSession: input.sessionId,\n });\n try {\n await writeTaskFile(\n input.paths,\n input.taskId,\n { task, body: input.description },\n { mode: \"create\" },\n );\n } catch (error: unknown) {\n throw new TaskWriteAfterEventError({\n taskId: input.taskId,\n eventId: appendResult.eventId,\n sessionId: input.sessionId,\n phase: \"create\",\n cause: error,\n });\n }\n\n await safeUpdateTaskIndex(input.paths, { kind: \"add\", entry: buildTaskIndexEntry(task.task) });\n\n return {\n taskId: input.taskId,\n eventId: appendResult.eventId,\n sessionId: input.sessionId,\n sessionStatus: status,\n };\n}\n\nfunction buildInitialTask(input: {\n taskId: PrefixedId<\"task\">;\n title: string;\n label?: string;\n status: TaskStatus;\n occurredAt: string;\n /**\n * Override for `updated_at` when `status` is terminal. Ignored for\n * non-terminal statuses so backdating a non-completed task is not\n * possible by accident.\n */\n completedAt?: string;\n workspaceId: PrefixedId<\"ws\">;\n createdInSession: PrefixedId<\"ses\">;\n}): Task {\n const updatedAt =\n input.completedAt !== undefined && isTerminalTaskStatus(input.status)\n ? input.completedAt\n : input.occurredAt;\n return {\n schema_version: \"0.1.0\",\n task: {\n id: input.taskId,\n title: input.title,\n ...(input.label !== undefined ? { label: input.label } : {}),\n status: input.status,\n created_at: input.occurredAt,\n updated_at: updatedAt,\n workspace_id: input.workspaceId,\n created_in_session: input.createdInSession,\n linked_sessions: [input.createdInSession],\n },\n };\n}\n\n// Helpers for the ad-hoc `task new` path. The invocation args list mirrors\n// the operator's CLI input so the recorded `session.yaml.invocation.args`\n// stays accurate even when `--status` / `--completed-at` were supplied.\nfunction buildTaskNewInvocationArgs(\n title: string,\n initialStatus: TaskStatus,\n completedAt: string | undefined,\n): string[] {\n const args = [\"--title\", title];\n if (initialStatus !== \"planned\") {\n args.push(\"--status\", initialStatus);\n }\n if (completedAt !== undefined && isTerminalTaskStatus(initialStatus)) {\n args.push(\"--completed-at\", completedAt);\n }\n return args;\n}\n\nfunction buildTaskNewTargetEventBuilders(input: {\n taskId: PrefixedId<\"task\">;\n title: string;\n initialStatus: TaskStatus;\n occurredAt: string;\n}): Array<(sessionId: PrefixedId<\"ses\">, eventId: PrefixedId<\"evt\">) => Event> {\n const createdBuilder = (sessionId: PrefixedId<\"ses\">, eventId: PrefixedId<\"evt\">): Event =>\n buildTaskCreatedEvent({\n eventId,\n sessionId,\n taskId: input.taskId,\n title: input.title,\n occurredAt: input.occurredAt,\n });\n if (!isTerminalTaskStatus(input.initialStatus)) {\n return [createdBuilder];\n }\n // For terminal initialStatus, emit `task_status_changed (planned → terminal)`\n // right after `task_created` so replay reconstructs the implicit\n // transition. The shortcut edges `planned → done|cancelled` are already\n // allowed by ALLOWED_TRANSITIONS.\n const statusChangedBuilder = (sessionId: PrefixedId<\"ses\">, eventId: PrefixedId<\"evt\">): Event =>\n buildTaskStatusChangedEvent({\n eventId,\n sessionId,\n taskId: input.taskId,\n from: \"planned\",\n to: input.initialStatus,\n occurredAt: input.occurredAt,\n });\n return [createdBuilder, statusChangedBuilder];\n}\n\n// ============================================================================\n// Orchestrator: updateTaskStatusWithEvent\n// ============================================================================\n\nexport type UpdateAdHocTaskStatusInput = {\n mode: \"ad-hoc\";\n paths: BasouPaths;\n manifest: Manifest;\n occurredAt: string;\n taskId: PrefixedId<\"task\">;\n newStatus: TaskStatus;\n workingDirectory: string;\n};\n\nexport type AttachUpdateTaskStatusInput = {\n mode: \"attach\";\n paths: BasouPaths;\n occurredAt: string;\n sessionId: PrefixedId<\"ses\">;\n taskId: PrefixedId<\"task\">;\n newStatus: TaskStatus;\n attachableStatuses?: ReadonlySet<AttachableStatus>;\n};\n\nexport type UpdateTaskStatusInput = UpdateAdHocTaskStatusInput | AttachUpdateTaskStatusInput;\n\nexport type UpdateTaskStatusResult = {\n taskId: PrefixedId<\"task\">;\n eventId: PrefixedId<\"evt\">;\n sessionId: PrefixedId<\"ses\">;\n sessionStatus: SessionStatus;\n previousStatus: TaskStatus;\n newStatus: TaskStatus;\n};\n\n/**\n * Fire a `task_status_changed` event and overwrite the task.md front matter\n * with the new status / `updated_at` / appended-but-deduped `linked_sessions`.\n *\n * Validates the transition BEFORE any event write so a rejected transition\n * leaves events.jsonl untouched. The canonical edge set lives in\n * {@link ALLOWED_TRANSITIONS}; the current shape is:\n * planned → {in_progress, done, cancelled}\n * in_progress → {done, cancelled}\n * done / cancelled are terminal (= idempotent same-state is rejected too).\n */\nexport async function updateTaskStatusWithEvent(\n input: UpdateTaskStatusInput,\n): Promise<UpdateTaskStatusResult> {\n TaskIdSchema.parse(input.taskId);\n\n // Per-task lock guards the read-modify-write of task.md against concurrent\n // writers on the same task id (= another `task status` / `task edit` /\n // `task reconcile` on the same task). Released in a finally block so the\n // lock never lingers on either the success or the failure path.\n const handle = await acquireLock(input.paths, \"task\", input.taskId);\n try {\n // 1. Load current task.md (= source of truth for current status).\n const currentDoc = await readTaskFile(input.paths, input.taskId);\n const previousStatus = currentDoc.task.task.status;\n\n // 2. Validate transition before touching any persistent state.\n assertTransitionAllowed(previousStatus, input.newStatus);\n\n if (input.mode === \"ad-hoc\") {\n return await updateTaskStatusAdHoc(input, currentDoc, previousStatus);\n }\n return await updateTaskStatusAttach(input, currentDoc, previousStatus);\n } finally {\n await handle.release();\n }\n}\n\nasync function updateTaskStatusAdHoc(\n input: UpdateAdHocTaskStatusInput,\n currentDoc: TaskDocument,\n previousStatus: TaskStatus,\n): Promise<UpdateTaskStatusResult> {\n const title = currentDoc.task.task.title;\n const adHoc = await createAdHocSessionWithEvent({\n paths: input.paths,\n manifest: input.manifest,\n label: buildAdHocTaskLabel(title, \"status\"),\n occurredAt: input.occurredAt,\n sessionSource: \"human\",\n workingDirectory: input.workingDirectory,\n invocation: { command: \"basou task status\", args: [input.taskId, input.newStatus] },\n taskId: input.taskId,\n targetEventBuilders: [\n (sessionId, eventId) =>\n buildTaskStatusChangedEvent({\n eventId,\n sessionId,\n taskId: input.taskId,\n from: previousStatus,\n to: input.newStatus,\n occurredAt: input.occurredAt,\n }),\n ],\n });\n\n const anchorEventId = adHoc.targetEventIds[0] as PrefixedId<\"evt\">;\n // 3. Overwrite task.md (status + updated_at + linked_sessions append-dedup).\n const updatedDoc = buildUpdatedDoc({\n currentDoc,\n newStatus: input.newStatus,\n occurredAt: input.occurredAt,\n appendSessionId: adHoc.sessionId,\n });\n try {\n await writeTaskFile(input.paths, input.taskId, updatedDoc, { mode: \"overwrite\" });\n } catch (error: unknown) {\n throw new TaskWriteAfterEventError({\n taskId: input.taskId,\n eventId: anchorEventId,\n sessionId: adHoc.sessionId,\n phase: \"overwrite\",\n cause: error,\n });\n }\n await safeUpdateTaskIndex(input.paths, {\n kind: \"update\",\n entry: buildTaskIndexEntry(updatedDoc.task.task),\n });\n return {\n taskId: input.taskId,\n eventId: anchorEventId,\n sessionId: adHoc.sessionId,\n sessionStatus: \"completed\",\n previousStatus,\n newStatus: input.newStatus,\n };\n}\n\nasync function updateTaskStatusAttach(\n input: AttachUpdateTaskStatusInput,\n currentDoc: TaskDocument,\n previousStatus: TaskStatus,\n): Promise<UpdateTaskStatusResult> {\n SessionIdSchema.parse(input.sessionId);\n\n // Per-session lock guards the session.yaml read → events.jsonl append\n // window so a concurrent writer on the same session cannot append a\n // conflicting `task_status_changed` or flip session.yaml to a state\n // that invalidates the status check below. We are already inside the\n // per-task lock (updateTaskStatusWithEvent acquires it); the per-task\n // → per-session order is fixed across the codebase to keep cross-API\n // deadlocks impossible.\n const sessionLock = await acquireLock(input.paths, \"session\", input.sessionId);\n try {\n return await updateTaskStatusAttachLocked(input, currentDoc, previousStatus);\n } finally {\n await sessionLock.release();\n }\n}\n\nasync function updateTaskStatusAttachLocked(\n input: AttachUpdateTaskStatusInput,\n currentDoc: TaskDocument,\n previousStatus: TaskStatus,\n): Promise<UpdateTaskStatusResult> {\n const sessionDoc = await readSessionYaml(input.paths, input.sessionId);\n const status = sessionDoc.session.status;\n if (status === \"imported\") {\n throw new Error(\"Cannot attach to imported session\");\n }\n const attachable = input.attachableStatuses ?? DEFAULT_ATTACHABLE_STATUSES;\n if (!attachable.has(status as AttachableStatus)) {\n throw new Error(`Session is not active: ${status}`);\n }\n // task_id collision: the session MUST already be linked to the same task,\n // otherwise a status change on a task that the session does not own would\n // violate the session ⇆ task anchor invariant.\n const existingTaskId = sessionDoc.session.task_id ?? null;\n if (existingTaskId === null) {\n throw new Error(`Session is not linked to task: ${input.taskId}`);\n }\n if (existingTaskId !== input.taskId) {\n throw new Error(`Session already linked to a different task: ${existingTaskId}`);\n }\n\n const appendResult = await appendEventToExistingSession({\n paths: input.paths,\n sessionId: input.sessionId,\n ...(input.attachableStatuses !== undefined\n ? { attachableStatuses: input.attachableStatuses }\n : {}),\n eventBuilder: (eventId) =>\n buildTaskStatusChangedEvent({\n eventId,\n sessionId: input.sessionId,\n taskId: input.taskId,\n from: previousStatus,\n to: input.newStatus,\n occurredAt: input.occurredAt,\n }),\n });\n\n const updatedDoc = buildUpdatedDoc({\n currentDoc,\n newStatus: input.newStatus,\n occurredAt: input.occurredAt,\n appendSessionId: input.sessionId,\n });\n try {\n await writeTaskFile(input.paths, input.taskId, updatedDoc, { mode: \"overwrite\" });\n } catch (error: unknown) {\n throw new TaskWriteAfterEventError({\n taskId: input.taskId,\n eventId: appendResult.eventId,\n sessionId: input.sessionId,\n phase: \"overwrite\",\n cause: error,\n });\n }\n await safeUpdateTaskIndex(input.paths, {\n kind: \"update\",\n entry: buildTaskIndexEntry(updatedDoc.task.task),\n });\n return {\n taskId: input.taskId,\n eventId: appendResult.eventId,\n sessionId: input.sessionId,\n sessionStatus: status,\n previousStatus,\n newStatus: input.newStatus,\n };\n}\n\nfunction buildUpdatedDoc(input: {\n currentDoc: TaskDocument;\n newStatus: TaskStatus;\n occurredAt: string;\n appendSessionId: PrefixedId<\"ses\">;\n}): TaskDocument {\n const linked = input.currentDoc.task.task.linked_sessions;\n const merged = linked.includes(input.appendSessionId)\n ? linked\n : [...linked, input.appendSessionId];\n const next: Task = {\n ...input.currentDoc.task,\n task: {\n ...input.currentDoc.task.task,\n status: input.newStatus,\n updated_at: input.occurredAt,\n linked_sessions: merged,\n },\n };\n return { task: next, body: input.currentDoc.body };\n}\n\n// ============================================================================\n// Reconcile (basou task reconcile)\n// ============================================================================\n\n/**\n * Single-task audit result. Always returned by {@link reconcileTask} regardless\n * of mode: in dry-run the `clean` / `broken*` fields describe what would change\n * and `reconcileSession` is `null`; in write mode the same fields describe\n * what did change and `reconcileSession` carries the minted ad-hoc session +\n * `task_reconciled` event ids.\n *\n * Broken `linked_sessions[]` entries are deduplicated against the same session\n * id appearing more than once in the source task.md (hand-edit defence).\n */\nexport type ReconcileResult = {\n taskId: PrefixedId<\"task\">;\n clean: boolean;\n brokenCreatedInSession: PrefixedId<\"ses\"> | null;\n brokenLinkedSessions: PrefixedId<\"ses\">[];\n reconcileSession: {\n sessionId: PrefixedId<\"ses\">;\n eventId: PrefixedId<\"evt\">;\n } | null;\n};\n\n/**\n * Per-task failure record collected by {@link reconcileAllTasks}. The scan\n * keeps running on isolated failures so one bad task does not freeze the\n * batch; the CLI layer renders this list and exits 1 if any entry is present.\n *\n * `phase` is populated only for {@link TaskWriteAfterEventError}; for any\n * other error class it is `null` and the operator must use `--verbose` to\n * surface the cause chain.\n */\nexport type ReconcileFailure = {\n taskId: PrefixedId<\"task\">;\n errorClass: string;\n phase: TaskWriteAfterEventPhase | null;\n};\n\n/**\n * Batch audit result. Order follows `enumerateTaskIds(paths)` (ULID-ascending).\n * `scanned` is the number of readable task.md files processed (= excludes\n * malformed task.md from the count so an integrity-broken file does not\n * pad the total).\n */\nexport type ReconcileAllResult = {\n results: ReconcileResult[];\n failed: ReconcileFailure[];\n scanned: number;\n};\n\nexport type ReconcileTaskInput = {\n taskId: PrefixedId<\"task\">;\n occurredAt: string;\n workingDirectory: string;\n write: boolean;\n /**\n * Whether the caller invoked reconcile against a single task (`--task <id>`)\n * or as part of a full scan. The ad-hoc reconcile session records the form\n * on its `invocation.args` so audit trails distinguish targeted repairs\n * from sweeps:\n * - `\"single\"` -> `[\"--task\", <taskId>, \"--write\"]`\n * - `\"all\"` -> `[\"--write\"]` (= the operator typed no task id, so the\n * scan-wide intent is preserved instead of synthesising one per task)\n * Defaults to `\"single\"` so direct callers (tests, programmatic uses) keep\n * the targeted form without an explicit argument.\n */\n scope?: \"single\" | \"all\";\n /**\n * Test-only hook: the test runner uses this to mutate the task file\n * from outside the reconcile flow between the pre-write snapshot and\n * the post-event re-read, simulating a concurrent edit so the\n * `reconcile-concurrent` branch can be exercised deterministically.\n * Production callers leave it undefined.\n */\n _onPhaseCompleted?: (phase: \"phase-4-snapshot\" | \"phase-5-bulk-write\") => Promise<void>;\n};\n\nexport type ReconcileAllTasksInput = {\n /**\n * Per-task timestamp factory. Each reconciled task gets a fresh ISO string\n * so concurrent ad-hoc sessions do not collide on `occurred_at`. The CLI\n * layer wires this to `ctx.nowProvider().toISOString()`.\n */\n occurredAt: () => string;\n workingDirectory: string;\n write: boolean;\n};\n\nexport type ReconcileAllTasksOptions = {\n /**\n * When true the result includes clean tasks (= no broken refs). The CLI\n * layer leaves this false so the human output only mentions tasks that\n * actually changed.\n */\n includeClean?: boolean;\n};\n\ntype TaskMdSnapshot = {\n mtimeMs: number;\n hash: string;\n};\n\nasync function computeTaskMdSnapshot(paths: BasouPaths, taskId: string): Promise<TaskMdSnapshot> {\n const filePath = join(paths.tasks, `${taskId}.md`);\n const [stats, raw] = await Promise.all([stat(filePath), readFile(filePath)]);\n const hash = createHash(\"sha256\").update(raw).digest(\"hex\");\n return { mtimeMs: stats.mtimeMs, hash };\n}\n\n// Read task.md and derive its mtime/sha256 snapshot from the SAME raw bytes\n// the TaskDocument was parsed from. An earlier internal review flagged that\n// the previous \"readTaskFile, then computeTaskMdSnapshot\" sequence left a window\n// where a concurrent edit between those two reads could leave the caller\n// acting on stale content while the snapshot already reflected the new\n// content — and stage 7 would then clobber the new bytes with the stale\n// TaskDocument. Sharing the raw bytes here means stage 6's re-read is\n// compared against the EXACT bytes that produced this document, so any\n// drift since this read is caught.\nasync function readTaskFileWithSnapshot(\n paths: BasouPaths,\n taskId: string,\n): Promise<{ doc: TaskDocument; snapshot: TaskMdSnapshot }> {\n const filePath = join(paths.tasks, `${taskId}.md`);\n let rawBuffer: Buffer;\n let stats: Awaited<ReturnType<typeof stat>>;\n try {\n [rawBuffer, stats] = await Promise.all([readFile(filePath), stat(filePath)]);\n } catch (error: unknown) {\n if (findErrorCode(error, \"ENOENT\")) {\n throw new Error(\"Task file not found\", { cause: error });\n }\n throw new Error(\"Failed to read task file\", { cause: error });\n }\n const raw = rawBuffer.toString(\"utf8\");\n const hash = createHash(\"sha256\").update(rawBuffer).digest(\"hex\");\n // Parse logic mirrors readTaskFile so the error contract stays identical\n // (Invalid task file format / Failed to read task file). Duplicated here to\n // avoid a second readFile from the public helper.\n let split: { yamlText: string; body: string };\n try {\n split = splitFrontMatter(raw);\n } catch (error: unknown) {\n if (error instanceof Error && error.message === \"Invalid task file format\") {\n throw error;\n }\n throw new Error(\"Failed to read task file\", { cause: error });\n }\n let parsed: unknown;\n try {\n parsed = parseYaml(split.yamlText);\n } catch (error: unknown) {\n throw new Error(\"Failed to read task file\", { cause: error });\n }\n const result = TaskSchema.safeParse(parsed);\n if (!result.success) {\n throw new Error(\"Failed to read task file\", { cause: result.error });\n }\n return {\n doc: { task: result.data, body: split.body },\n snapshot: { mtimeMs: stats.mtimeMs, hash },\n };\n}\n\ntype DetectedBrokenRefs = {\n brokenCreatedInSession: PrefixedId<\"ses\"> | null;\n brokenLinkedSessions: PrefixedId<\"ses\">[];\n};\n\n// `enumerateSessionDirs` returns directory names only — it does NOT validate\n// the contents of each `session.yaml`. By treating directory existence alone\n// as \"reachable\", reconcile targets the dogfood failure mode where a session\n// directory is removed entirely and a dangling id remains in task.md, while\n// keeping the \"broken\" predicate cheap. A directory that exists but whose\n// session.yaml is missing or schema-invalid is intentionally classified as\n// reachable here; that flavour of corruption is the responsibility of session\n// integrity tooling and is out of scope for v0.2 reconcile.\nasync function detectBrokenRefs(\n paths: BasouPaths,\n task: Task[\"task\"],\n): Promise<DetectedBrokenRefs> {\n const sessionDirs = new Set(await enumerateSessionDirs(paths));\n const brokenCreatedInSession = sessionDirs.has(task.created_in_session)\n ? null\n : (task.created_in_session as PrefixedId<\"ses\">);\n // Deduplicate broken entries so duplicate broken ids in a hand-edited task.md\n // surface as a single entry on the event payload.\n const seen = new Set<string>();\n const brokenLinkedSessions: PrefixedId<\"ses\">[] = [];\n for (const sid of task.linked_sessions) {\n if (sessionDirs.has(sid)) continue;\n if (seen.has(sid)) continue;\n seen.add(sid);\n brokenLinkedSessions.push(sid as PrefixedId<\"ses\">);\n }\n return { brokenCreatedInSession, brokenLinkedSessions };\n}\n\nfunction buildReconciledDoc(input: {\n currentDoc: TaskDocument;\n brokenCreatedInSession: PrefixedId<\"ses\"> | null;\n brokenLinkedSessions: ReadonlyArray<PrefixedId<\"ses\">>;\n reconcileSessionId: PrefixedId<\"ses\">;\n occurredAt: string;\n}): TaskDocument {\n const brokenSet = new Set<string>(input.brokenLinkedSessions);\n const filtered = input.currentDoc.task.task.linked_sessions.filter((sid) => !brokenSet.has(sid));\n const merged: PrefixedId<\"ses\">[] = [...filtered] as PrefixedId<\"ses\">[];\n if (!merged.includes(input.reconcileSessionId)) {\n merged.push(input.reconcileSessionId);\n }\n const nextCreatedInSession =\n input.brokenCreatedInSession !== null\n ? input.reconcileSessionId\n : input.currentDoc.task.task.created_in_session;\n const next: Task = {\n ...input.currentDoc.task,\n task: {\n ...input.currentDoc.task.task,\n created_in_session: nextCreatedInSession,\n updated_at: input.occurredAt,\n linked_sessions: merged,\n },\n };\n return { task: next, body: input.currentDoc.body };\n}\n\n/**\n * Audit a single task's session references. In `write: false` mode this is a\n * pure read-only report (no events, no task.md change). In `write: true` mode,\n * if any broken reference is found, mint an ad-hoc reconcile session, fire\n * `task_reconciled`, and overwrite task.md with the repaired refs.\n *\n * The broken `created_in_session` field is REPLACED with the new reconcile\n * session id rather than nulled out — `TaskSchema.created_in_session` is\n * non-nullable, so dropping it would leave the file schema-invalid.\n * The old broken id is preserved on the event payload via\n * `removed_created_in_session` for audit.\n *\n * Stages — failures after stage 5 surface a phase-specific\n * {@link TaskWriteAfterEventError} so the CLI can render a tailored \"do not\n * rerun\" hint:\n * 1. Boundary parse\n * 2. Read task.md AND snapshot its mtime/hash from the same raw bytes,\n * then detect broken refs (sharing the raw bytes here closes the\n * readTaskFile-then-snapshot race window).\n * 3. Early return when clean (no event fired, no overwrite)\n * 4. (no separate stage anymore — snapshot is taken at stage 2)\n * 5. Mint ad-hoc session + `task_reconciled` event (catch\n * `FailedToFinalizeError` → `phase: \"reconcile-finalize\"`)\n * 6. Re-snapshot task.md; if changed since stage 2 →\n * `phase: \"reconcile-concurrent\"`\n * 7. Overwrite task.md; failure → `phase: \"reconcile\"`\n */\nexport async function reconcileTask(\n paths: BasouPaths,\n manifest: Manifest,\n input: ReconcileTaskInput,\n): Promise<ReconcileResult> {\n TaskIdSchema.parse(input.taskId);\n\n // Per-task lock spans the entire reconcile window (= snapshot read,\n // ad-hoc session + event write, post-write snapshot probe, task.md\n // overwrite) so the mtime/hash invariant cannot be defeated by a\n // concurrent writer the helper would otherwise not notice.\n const handle = await acquireLock(paths, \"task\", input.taskId);\n try {\n return await reconcileTaskLocked(paths, manifest, input);\n } finally {\n await handle.release();\n }\n}\n\nasync function reconcileTaskLocked(\n paths: BasouPaths,\n manifest: Manifest,\n input: ReconcileTaskInput,\n): Promise<ReconcileResult> {\n const { doc: currentDoc, snapshot: preSnapshot } = await readTaskFileWithSnapshot(\n paths,\n input.taskId,\n );\n const { brokenCreatedInSession, brokenLinkedSessions } = await detectBrokenRefs(\n paths,\n currentDoc.task.task,\n );\n\n if (brokenCreatedInSession === null && brokenLinkedSessions.length === 0) {\n return {\n taskId: input.taskId,\n clean: true,\n brokenCreatedInSession: null,\n brokenLinkedSessions: [],\n reconcileSession: null,\n };\n }\n\n if (!input.write) {\n return {\n taskId: input.taskId,\n clean: false,\n brokenCreatedInSession,\n brokenLinkedSessions,\n reconcileSession: null,\n };\n }\n\n if (input._onPhaseCompleted !== undefined) {\n await input._onPhaseCompleted(\"phase-4-snapshot\");\n }\n\n let adHoc: Awaited<ReturnType<typeof createAdHocSessionWithEvent>>;\n try {\n adHoc = await createAdHocSessionWithEvent({\n paths,\n manifest,\n label: buildAdHocReconcileLabel(currentDoc.task.task.title),\n occurredAt: input.occurredAt,\n sessionSource: \"human\",\n workingDirectory: input.workingDirectory,\n invocation: {\n command: \"basou task reconcile\",\n args:\n (input.scope ?? \"single\") === \"single\"\n ? [\"--task\", input.taskId, \"--write\"]\n : [\"--write\"],\n },\n taskId: input.taskId,\n targetEventBuilders: [\n (sessionId, eventId) =>\n buildTaskReconciledEvent({\n eventId,\n sessionId,\n taskId: input.taskId,\n removedCreatedInSession: brokenCreatedInSession,\n createdInSessionReplacement: brokenCreatedInSession !== null ? sessionId : null,\n removedLinkedSessions: brokenLinkedSessions,\n occurredAt: input.occurredAt,\n }),\n ],\n });\n } catch (error: unknown) {\n if (error instanceof FailedToFinalizeError) {\n throw new TaskWriteAfterEventError({\n taskId: input.taskId,\n eventId: error.targetEventIds[0] as PrefixedId<\"evt\">,\n sessionId: error.sessionId,\n phase: \"reconcile-finalize\",\n cause: error,\n });\n }\n throw error;\n }\n\n if (input._onPhaseCompleted !== undefined) {\n await input._onPhaseCompleted(\"phase-5-bulk-write\");\n }\n\n const anchorEventId = adHoc.targetEventIds[0] as PrefixedId<\"evt\">;\n\n const postSnapshot = await computeTaskMdSnapshot(paths, input.taskId);\n if (postSnapshot.mtimeMs !== preSnapshot.mtimeMs || postSnapshot.hash !== preSnapshot.hash) {\n throw new TaskWriteAfterEventError({\n taskId: input.taskId,\n eventId: anchorEventId,\n sessionId: adHoc.sessionId,\n phase: \"reconcile-concurrent\",\n cause: new Error(\"task.md changed during reconcile\"),\n });\n }\n\n const repaired = buildReconciledDoc({\n currentDoc,\n brokenCreatedInSession,\n brokenLinkedSessions,\n reconcileSessionId: adHoc.sessionId,\n occurredAt: input.occurredAt,\n });\n try {\n await writeTaskFile(paths, input.taskId, repaired, { mode: \"overwrite\" });\n } catch (error: unknown) {\n throw new TaskWriteAfterEventError({\n taskId: input.taskId,\n eventId: anchorEventId,\n sessionId: adHoc.sessionId,\n phase: \"reconcile\",\n cause: error,\n });\n }\n\n await safeUpdateTaskIndex(paths, {\n kind: \"update\",\n entry: buildTaskIndexEntry(repaired.task.task),\n });\n\n return {\n taskId: input.taskId,\n clean: false,\n brokenCreatedInSession,\n brokenLinkedSessions,\n reconcileSession: {\n sessionId: adHoc.sessionId,\n eventId: anchorEventId,\n },\n };\n}\n\n/**\n * Reconcile every task in `.basou/tasks/`. Continues on per-task failures so\n * an isolated {@link TaskWriteAfterEventError} does not stop the batch.\n * Malformed task.md files are skipped silently and excluded from `scanned`.\n */\nexport async function reconcileAllTasks(\n paths: BasouPaths,\n manifest: Manifest,\n input: ReconcileAllTasksInput,\n options: ReconcileAllTasksOptions = {},\n): Promise<ReconcileAllResult> {\n const taskIds = await enumerateTaskIds(paths);\n const results: ReconcileResult[] = [];\n const failed: ReconcileFailure[] = [];\n let scanned = 0;\n\n for (const id of taskIds) {\n // Probe readability first so malformed task.md does NOT inflate `scanned`\n // and never reaches the reconcile flow. The readTaskFile call is replayed\n // inside reconcileTask itself — re-reading is cheap and keeps reconcileTask's\n // contract single-purpose.\n try {\n await readTaskFile(paths, id);\n } catch {\n continue;\n }\n scanned += 1;\n\n try {\n const r = await reconcileTask(paths, manifest, {\n taskId: id as PrefixedId<\"task\">,\n occurredAt: input.occurredAt(),\n workingDirectory: input.workingDirectory,\n write: input.write,\n scope: \"all\",\n });\n if (options.includeClean === true || !r.clean) {\n results.push(r);\n }\n } catch (error: unknown) {\n const errorClass = error instanceof Error ? error.constructor.name : \"Error\";\n const phase = error instanceof TaskWriteAfterEventError ? error.phase : null;\n failed.push({\n taskId: id as PrefixedId<\"task\">,\n errorClass,\n phase,\n });\n }\n }\n\n return { results, failed, scanned };\n}\n\n// ============================================================================\n// Linkage refresh: events.jsonl → task.md `linked_sessions[]` forward sync\n// ============================================================================\n\n/**\n * Single-task linkage refresh result. In `write: false` mode this is a pure\n * dry-run report (no event, no task.md change); `addedLinkedSessions` and\n * `removedLinkedSessions` describe what would change. In `write: true` mode\n * the same fields describe what did change and `refreshSession` carries the\n * ad-hoc session + `task_linkage_refreshed` event ids that were minted.\n *\n * `clean === true` means the existing `task.md.linked_sessions[]` already\n * matches the union of `session.yaml.task_id` matches plus the anchor\n * (`created_in_session`) — no event fired, no overwrite.\n */\nexport type RefreshLinkageResult = {\n taskId: PrefixedId<\"task\">;\n clean: boolean;\n addedLinkedSessions: PrefixedId<\"ses\">[];\n removedLinkedSessions: PrefixedId<\"ses\">[];\n /** Number of entries in `linked_sessions[]` after the refresh would run. */\n finalCount: number;\n refreshSession: {\n sessionId: PrefixedId<\"ses\">;\n eventId: PrefixedId<\"evt\">;\n } | null;\n};\n\nexport type RefreshLinkageInput = {\n taskId: PrefixedId<\"task\">;\n occurredAt: string;\n workingDirectory: string;\n write: boolean;\n};\n\ntype DetectedLinkageDelta = {\n addedLinkedSessions: PrefixedId<\"ses\">[];\n removedLinkedSessions: PrefixedId<\"ses\">[];\n finalLinkedSessions: PrefixedId<\"ses\">[];\n};\n\n// Re-derive `linked_sessions[]` from the source of truth: every\n// `session.yaml` whose `task_id` points at this task, plus the\n// `created_in_session` anchor (which is preserved even if its session.yaml\n// no longer carries the task_id — that flavour of drift is the\n// `task reconcile` path's concern, not this one).\n//\n// `enumerateSessionDirs` already filters to dir-named-`ses_<ulid>` entries.\n// Sessions whose `session.yaml` is missing or schema-invalid are silently\n// skipped so a single broken session does not abort the workspace-wide\n// refresh; surfacing those is the responsibility of the session-integrity\n// tooling.\nasync function detectLinkageDelta(\n paths: BasouPaths,\n task: Task[\"task\"],\n): Promise<DetectedLinkageDelta> {\n const sessionIds = await enumerateSessionDirs(paths);\n const reachable = new Set<string>();\n for (const sid of sessionIds) {\n try {\n const doc = await readSessionYaml(paths, sid);\n if (doc.session.task_id === task.id) {\n reachable.add(sid);\n }\n } catch {\n // Missing / malformed session.yaml — skip. Surfacing those is the\n // responsibility of session-integrity tooling, not the linkage-refresh\n // path; a single corrupt session.yaml must not abort the workspace\n // scan.\n }\n }\n // The session ⇆ task anchor invariant\n // (`docs/spec/workspace.md#21-confirmed-invariants`) requires\n // `linked_sessions[]` to always contain `created_in_session`. Preserve it\n // here even if the session.yaml was hand-edited to clear task_id\n // (rare; handled by reconcile).\n const finalSet = new Set<string>(reachable);\n finalSet.add(task.created_in_session);\n\n const currentSet = new Set<string>(task.linked_sessions);\n const addedLinkedSessions: PrefixedId<\"ses\">[] = [];\n const removedLinkedSessions: PrefixedId<\"ses\">[] = [];\n for (const sid of finalSet) {\n if (!currentSet.has(sid)) addedLinkedSessions.push(sid as PrefixedId<\"ses\">);\n }\n for (const sid of currentSet) {\n if (!finalSet.has(sid)) removedLinkedSessions.push(sid as PrefixedId<\"ses\">);\n }\n // Stable ordering: ULID-ascending so two runs against the same workspace\n // produce identical event payloads (matters for replay determinism).\n addedLinkedSessions.sort();\n removedLinkedSessions.sort();\n const finalLinkedSessions = [...finalSet].sort() as PrefixedId<\"ses\">[];\n return { addedLinkedSessions, removedLinkedSessions, finalLinkedSessions };\n}\n\nfunction buildRefreshedDoc(input: {\n currentDoc: TaskDocument;\n finalLinkedSessions: ReadonlyArray<PrefixedId<\"ses\">>;\n refreshSessionId: PrefixedId<\"ses\">;\n occurredAt: string;\n}): TaskDocument {\n // Include the refresh session itself in `linked_sessions` (it is the\n // session that wrote the `task_linkage_refreshed` event, so it is by\n // definition linked). Deduplicate via a Set in case the ad-hoc session id\n // somehow already shows up in finalLinkedSessions (defensive).\n const merged = new Set<string>(input.finalLinkedSessions);\n merged.add(input.refreshSessionId);\n const linked = [...merged].sort() as PrefixedId<\"ses\">[];\n const next: Task = {\n ...input.currentDoc.task,\n task: {\n ...input.currentDoc.task.task,\n updated_at: input.occurredAt,\n linked_sessions: linked,\n },\n };\n return { task: next, body: input.currentDoc.body };\n}\n\n/**\n * Refresh `task.md.linked_sessions[]` so it matches the union of\n * `session.yaml.task_id` references in the workspace plus the\n * `created_in_session` anchor. In `write: false` this is a pure read-only\n * report; in `write: true` the diff is recorded as a\n * `task_linkage_refreshed` event inside a fresh ad-hoc session and the\n * task.md is overwritten with the new snapshot.\n *\n * Stages mirror `reconcileTask` so the operator gets the same\n * \"do-not-rerun\" hint shape on partial failure:\n * 1. Boundary parse\n * 2. Read task.md AND snapshot its mtime/hash from the same raw bytes\n * 3. Detect linkage delta (= scan workspace session.yaml)\n * 4. Early return when clean\n * 5. Mint ad-hoc session + `task_linkage_refreshed` event (catch\n * `FailedToFinalizeError` → `phase: \"linkage-refresh-finalize\"`)\n * 6. Re-snapshot task.md; if changed since stage 2 →\n * `phase: \"linkage-refresh-concurrent\"`\n * 7. Overwrite task.md; failure → `phase: \"linkage-refresh\"`\n *\n * The refresh event is distinct from `task_reconciled` (= broken-ref\n * cleanup, `.strict()` with broken-ref-specific fields) so each event\n * carries a single, focused audit story. Reusing `task_reconciled` here\n * would either redefine its semantics or require widening its strict\n * schema, both of which break replay determinism for older events.\n */\nexport async function refreshTaskLinkedSessions(\n paths: BasouPaths,\n manifest: Manifest,\n input: RefreshLinkageInput,\n): Promise<RefreshLinkageResult> {\n TaskIdSchema.parse(input.taskId);\n\n // Per-task lock spans the entire refresh window so the snapshot taken in\n // stage 2 cannot be invalidated by another writer the helper does not see;\n // the stage 6 mtime/hash probe still acts as a belt-and-braces guard\n // against drift from non-locked code paths (e.g. an external `vi` edit).\n const handle = await acquireLock(paths, \"task\", input.taskId);\n try {\n return await refreshTaskLinkedSessionsLocked(paths, manifest, input);\n } finally {\n await handle.release();\n }\n}\n\nasync function refreshTaskLinkedSessionsLocked(\n paths: BasouPaths,\n manifest: Manifest,\n input: RefreshLinkageInput,\n): Promise<RefreshLinkageResult> {\n const { doc: currentDoc, snapshot: preSnapshot } = await readTaskFileWithSnapshot(\n paths,\n input.taskId,\n );\n const { addedLinkedSessions, removedLinkedSessions, finalLinkedSessions } =\n await detectLinkageDelta(paths, currentDoc.task.task);\n\n if (addedLinkedSessions.length === 0 && removedLinkedSessions.length === 0) {\n return {\n taskId: input.taskId,\n clean: true,\n addedLinkedSessions: [],\n removedLinkedSessions: [],\n finalCount: finalLinkedSessions.length,\n refreshSession: null,\n };\n }\n\n if (!input.write) {\n return {\n taskId: input.taskId,\n clean: false,\n addedLinkedSessions,\n removedLinkedSessions,\n finalCount: finalLinkedSessions.length,\n refreshSession: null,\n };\n }\n\n // The refresh session is itself a new linked entry; account for it on\n // the event payload's `final_count` so the audit number matches the\n // post-write task.md. This is a +1 over the workspace-scan count.\n const finalCountWithRefreshSession = finalLinkedSessions.length + 1;\n\n let adHoc: Awaited<ReturnType<typeof createAdHocSessionWithEvent>>;\n try {\n adHoc = await createAdHocSessionWithEvent({\n paths,\n manifest,\n label: buildAdHocRefreshLinkageLabel(currentDoc.task.task.title),\n occurredAt: input.occurredAt,\n sessionSource: \"human\",\n workingDirectory: input.workingDirectory,\n invocation: {\n command: \"basou task refresh-linkage\",\n args: [input.taskId, \"--write\"],\n },\n taskId: input.taskId,\n targetEventBuilders: [\n (sessionId, eventId) =>\n buildTaskLinkageRefreshedEvent({\n eventId,\n sessionId,\n taskId: input.taskId,\n addedLinkedSessions,\n removedLinkedSessions,\n finalCount: finalCountWithRefreshSession,\n occurredAt: input.occurredAt,\n }),\n ],\n });\n } catch (error: unknown) {\n if (error instanceof FailedToFinalizeError) {\n throw new TaskWriteAfterEventError({\n taskId: input.taskId,\n eventId: error.targetEventIds[0] as PrefixedId<\"evt\">,\n sessionId: error.sessionId,\n phase: \"linkage-refresh-finalize\",\n cause: error,\n });\n }\n throw error;\n }\n\n const anchorEventId = adHoc.targetEventIds[0] as PrefixedId<\"evt\">;\n\n const postSnapshot = await computeTaskMdSnapshot(paths, input.taskId);\n if (postSnapshot.mtimeMs !== preSnapshot.mtimeMs || postSnapshot.hash !== preSnapshot.hash) {\n throw new TaskWriteAfterEventError({\n taskId: input.taskId,\n eventId: anchorEventId,\n sessionId: adHoc.sessionId,\n phase: \"linkage-refresh-concurrent\",\n cause: new Error(\"task.md changed during linkage refresh\"),\n });\n }\n\n const refreshed = buildRefreshedDoc({\n currentDoc,\n finalLinkedSessions,\n refreshSessionId: adHoc.sessionId,\n occurredAt: input.occurredAt,\n });\n try {\n await writeTaskFile(paths, input.taskId, refreshed, { mode: \"overwrite\" });\n } catch (error: unknown) {\n throw new TaskWriteAfterEventError({\n taskId: input.taskId,\n eventId: anchorEventId,\n sessionId: adHoc.sessionId,\n phase: \"linkage-refresh\",\n cause: error,\n });\n }\n\n await safeUpdateTaskIndex(paths, {\n kind: \"update\",\n entry: buildTaskIndexEntry(refreshed.task.task),\n });\n\n return {\n taskId: input.taskId,\n clean: false,\n addedLinkedSessions,\n removedLinkedSessions,\n finalCount: finalCountWithRefreshSession,\n refreshSession: {\n sessionId: adHoc.sessionId,\n eventId: anchorEventId,\n },\n };\n}\n\n// ============================================================================\n// editTask — field-level update (no event for pure title edits)\n// ============================================================================\n\nexport type EditTaskInput = {\n paths: BasouPaths;\n taskId: PrefixedId<\"task\">;\n /** New title; rejected when empty. Undefined leaves the field unchanged. */\n title?: string;\n /**\n * New status; routed through transition rules so the call rejects\n * invalid edges (e.g. `done -> planned`). Undefined leaves the field\n * unchanged.\n */\n newStatus?: TaskStatus;\n occurredAt: string;\n /**\n * Required when {@link newStatus} is provided — the status change fires\n * a `task_status_changed` event in a fresh ad-hoc session, which needs\n * a Manifest to seed the new session record. Title-only edits ignore\n * this field.\n */\n manifest?: Manifest;\n /** Working directory for the ad-hoc status-change session. */\n workingDirectory?: string;\n};\n\nexport type EditTaskResult = {\n taskId: PrefixedId<\"task\">;\n titleUpdated: boolean;\n statusUpdated: boolean;\n /** When {@link statusUpdated} is true, the previous status before the edit. */\n previousStatus: TaskStatus | null;\n /** When {@link statusUpdated} is true, the new status. */\n newStatus: TaskStatus | null;\n /** ad-hoc session minted when status was changed; null for title-only edits. */\n statusChangeSession: {\n sessionId: PrefixedId<\"ses\">;\n eventId: PrefixedId<\"evt\">;\n } | null;\n};\n\n/**\n * Update one or both of the user-editable fields on a task.md.\n *\n * - `title`: in-place overwrite of `task.md` only. v0.1 does not emit a\n * `task_title_changed` event — title changes are storage-level metadata\n * maintenance, not part of the audit trail.\n * - `newStatus`: routed through {@link updateTaskStatusWithEvent} so the\n * ALLOWED_TRANSITIONS gate is honored and a `task_status_changed` event is\n * appended to the audit trail.\n *\n * When both are supplied the status change runs first (= event committed)\n * and then the title overwrite runs against the freshly updated task.md\n * (= same `updated_at` from the status change). A failure of the\n * subsequent title overwrite leaves the status change committed; the\n * status-change side of an edit is the only side with an event, so the\n * audit trail is consistent regardless.\n */\nexport async function editTask(input: EditTaskInput): Promise<EditTaskResult> {\n TaskIdSchema.parse(input.taskId);\n if (input.title === undefined && input.newStatus === undefined) {\n throw new Error(\"Nothing to edit: provide --title or --status\");\n }\n if (input.title !== undefined) {\n TaskTitleSchema.parse(input.title);\n }\n\n let statusUpdated = false;\n let previousStatus: TaskStatus | null = null;\n let newStatus: TaskStatus | null = null;\n let statusChangeSession: EditTaskResult[\"statusChangeSession\"] = null;\n\n // Stage 1: status change (if any). Failure here exits with the existing\n // transition / not-found errors and leaves task.md untouched.\n if (input.newStatus !== undefined) {\n if (input.manifest === undefined || input.workingDirectory === undefined) {\n throw new Error(\"editTask requires manifest + workingDirectory when newStatus is supplied\");\n }\n const result = await updateTaskStatusWithEvent({\n mode: \"ad-hoc\",\n paths: input.paths,\n manifest: input.manifest,\n occurredAt: input.occurredAt,\n taskId: input.taskId,\n newStatus: input.newStatus,\n workingDirectory: input.workingDirectory,\n });\n statusUpdated = true;\n previousStatus = result.previousStatus;\n newStatus = result.newStatus;\n statusChangeSession = { sessionId: result.sessionId, eventId: result.eventId };\n }\n\n // Stage 2: title overwrite (if any). Re-read so the status change above\n // (which updated linked_sessions / updated_at) is preserved. The lock is\n // taken HERE (not around the whole function) because stage 1 calls\n // `updateTaskStatusWithEvent` which acquires the same per-task lock\n // internally — wrapping both stages in one outer lock would deadlock on\n // its own helper. Each stage being independently atomic is the operator-\n // visible invariant we care about, not stage-1-and-2 atomicity.\n let titleUpdated = false;\n if (input.title !== undefined) {\n const handle = await acquireLock(input.paths, \"task\", input.taskId);\n try {\n const doc = await readTaskFile(input.paths, input.taskId);\n if (doc.task.task.title !== input.title) {\n const next: Task = {\n ...doc.task,\n task: {\n ...doc.task.task,\n title: input.title,\n updated_at: input.occurredAt,\n },\n };\n await writeTaskFile(\n input.paths,\n input.taskId,\n { task: next, body: doc.body },\n { mode: \"overwrite\" },\n );\n await safeUpdateTaskIndex(input.paths, {\n kind: \"update\",\n entry: buildTaskIndexEntry(next.task),\n });\n titleUpdated = true;\n }\n } finally {\n await handle.release();\n }\n }\n\n return {\n taskId: input.taskId,\n titleUpdated,\n statusUpdated,\n previousStatus,\n newStatus,\n statusChangeSession,\n };\n}\n\n// ============================================================================\n// deleteTask — destructive removal with audit event\n// ============================================================================\n\nexport type DeleteTaskInput = {\n paths: BasouPaths;\n manifest: Manifest;\n taskId: PrefixedId<\"task\">;\n occurredAt: string;\n workingDirectory: string;\n};\n\nexport type DeleteTaskResult = {\n taskId: PrefixedId<\"task\">;\n title: string;\n sessionId: PrefixedId<\"ses\">;\n eventId: PrefixedId<\"evt\">;\n};\n\n/**\n * Hard-delete a task.md file with a `task_deleted` audit event.\n *\n * Sequence:\n * 1. Read task.md to capture the current title (which goes onto the\n * event payload so the audit record is self-describing even after\n * the file is gone).\n * 2. Mint an ad-hoc session, fire `task_deleted` as the target event.\n * The session's `task_id` is intentionally NOT pinned to the\n * to-be-deleted task — otherwise the audit session would carry a\n * broken reference the moment we unlink the file.\n * 3. Unlink `<paths.tasks>/<task_id>.md`.\n *\n * Failure of step 3 after the event is committed surfaces as a\n * {@link TaskWriteAfterEventError} with `phase: \"delete\"`; the operator\n * is told the event is durable but task.md still exists, and that a\n * manual `rm` (or a rerun) is required.\n *\n * v0.1 contract: no tombstone, no recovery. Restoring a deleted task is\n * not supported; the event payload (`task_id` + `title`) is the only\n * persistent record after the unlink succeeds.\n */\nexport async function deleteTask(input: DeleteTaskInput): Promise<DeleteTaskResult> {\n TaskIdSchema.parse(input.taskId);\n\n // Per-task lock keeps the read → audit event → unlink chain free of a\n // concurrent writer that could otherwise observe task.md after we read it\n // and before we delete it (e.g. another CLI running `task status`).\n const handle = await acquireLock(input.paths, \"task\", input.taskId);\n try {\n return await deleteTaskLocked(input);\n } finally {\n await handle.release();\n }\n}\n\nasync function deleteTaskLocked(input: DeleteTaskInput): Promise<DeleteTaskResult> {\n // Stage 1: capture the current title before mint.\n const doc = await readTaskFile(input.paths, input.taskId);\n const title = doc.task.task.title;\n\n // Stage 2: fire the audit event. NOTE we do NOT pass `taskId` to the\n // ad-hoc session — pinning the session to a task that is about to vanish\n // would create a guaranteed broken reference on session.yaml.task_id.\n const adHoc = await createAdHocSessionWithEvent({\n paths: input.paths,\n manifest: input.manifest,\n label: buildAdHocDeleteLabel(title),\n occurredAt: input.occurredAt,\n sessionSource: \"human\",\n workingDirectory: input.workingDirectory,\n invocation: {\n command: \"basou task delete\",\n args: [input.taskId, \"--yes\"],\n },\n targetEventBuilders: [\n (sessionId, eventId) =>\n buildTaskDeletedEvent({\n eventId,\n sessionId,\n taskId: input.taskId,\n title,\n occurredAt: input.occurredAt,\n }),\n ],\n });\n const eventId = adHoc.targetEventIds[0] as PrefixedId<\"evt\">;\n\n // Stage 3: unlink the file.\n try {\n await unlink(join(input.paths.tasks, `${input.taskId}.md`));\n } catch (error: unknown) {\n throw new TaskWriteAfterEventError({\n taskId: input.taskId,\n eventId,\n sessionId: adHoc.sessionId,\n phase: \"delete\",\n cause: error,\n });\n }\n\n await safeUpdateTaskIndex(input.paths, { kind: \"remove\", id: input.taskId });\n\n return {\n taskId: input.taskId,\n title,\n sessionId: adHoc.sessionId,\n eventId,\n };\n}\n\n// ============================================================================\n// archiveTask — move main/<id>.md to archive/<id>.md with audit event\n// ============================================================================\n\nexport type ArchiveTaskInput = {\n paths: BasouPaths;\n manifest: Manifest;\n taskId: PrefixedId<\"task\">;\n occurredAt: string;\n workingDirectory: string;\n};\n\nexport type ArchiveTaskResult = {\n taskId: PrefixedId<\"task\">;\n title: string;\n sessionId: PrefixedId<\"ses\">;\n eventId: PrefixedId<\"evt\">;\n};\n\n/**\n * Move a task.md file from `<paths.tasks>/<id>.md` to\n * `<paths.tasks>/archive/<id>.md` with a `task_archived` audit event.\n *\n * Sequence:\n * 1. Read task.md to capture the current title and existing content.\n * 2. Mint an ad-hoc session, fire `task_archived` as the target event.\n * The session's `task_id` IS pinned to the archived task — unlike\n * `task_deleted`, the task continues to exist (just at a new path),\n * so the session-task linkage stays a valid forward reference.\n * 3. Append the audit session to the task's `linked_sessions[]` and\n * overwrite the source task.md so the snapshot reflects the archive\n * session before the move.\n * 4. Ensure the archive directory exists.\n * 5. Rename main/<id>.md to archive/<id>.md (= atomic on the same fs).\n *\n * Failure modes after step 2 surface as\n * {@link TaskWriteAfterEventError} with `phase: \"archive\"`; the operator\n * is told the event is durable but the on-disk move is incomplete and\n * must be resolved manually (typically by rerunning `task archive`).\n */\nexport async function archiveTask(input: ArchiveTaskInput): Promise<ArchiveTaskResult> {\n TaskIdSchema.parse(input.taskId);\n\n // Per-task lock spans read → audit event → task.md overwrite → rename so\n // a concurrent writer cannot interleave between the linked_sessions\n // append and the move into archive/.\n const handle = await acquireLock(input.paths, \"task\", input.taskId);\n try {\n return await archiveTaskLocked(input);\n } finally {\n await handle.release();\n }\n}\n\nasync function archiveTaskLocked(input: ArchiveTaskInput): Promise<ArchiveTaskResult> {\n const doc = await readTaskFile(input.paths, input.taskId);\n const title = doc.task.task.title;\n\n const adHoc = await createAdHocSessionWithEvent({\n paths: input.paths,\n manifest: input.manifest,\n label: buildAdHocArchiveLabel(title),\n occurredAt: input.occurredAt,\n sessionSource: \"human\",\n workingDirectory: input.workingDirectory,\n invocation: {\n command: \"basou task archive\",\n args: [input.taskId, \"--yes\"],\n },\n taskId: input.taskId,\n targetEventBuilders: [\n (sessionId, eventId) =>\n buildTaskArchivedEvent({\n eventId,\n sessionId,\n taskId: input.taskId,\n title,\n occurredAt: input.occurredAt,\n }),\n ],\n });\n const eventId = adHoc.targetEventIds[0] as PrefixedId<\"evt\">;\n\n // Stage 3-5 share the same recovery contract: any failure surfaces as\n // phase \"archive\" so the operator gets a uniform \"rerun task archive\"\n // hint. Specific failure cases:\n // - 3: writeTaskFile (overwrite) — fs/yaml-serialize error\n // - 4: mkdir of archive dir — usually EACCES\n // - 5: rename across the same fs — EEXIST when archive/<id>.md is\n // already there, EACCES, or rare ENOSPC\n try {\n const linked = doc.task.task.linked_sessions;\n const merged = linked.includes(adHoc.sessionId) ? linked : [...linked, adHoc.sessionId];\n const next: Task = {\n ...doc.task,\n task: {\n ...doc.task.task,\n updated_at: input.occurredAt,\n linked_sessions: merged,\n },\n };\n await writeTaskFile(\n input.paths,\n input.taskId,\n { task: next, body: doc.body },\n { mode: \"overwrite\" },\n );\n\n await mkdir(archiveTasksDir(input.paths), { recursive: true });\n await rename(\n join(input.paths.tasks, `${input.taskId}.md`),\n join(archiveTasksDir(input.paths), `${input.taskId}.md`),\n );\n } catch (error: unknown) {\n throw new TaskWriteAfterEventError({\n taskId: input.taskId,\n eventId,\n sessionId: adHoc.sessionId,\n phase: \"archive\",\n cause: error,\n });\n }\n\n // Archived tasks live under tasks/archive/<id>.md, which enumerateTaskIds\n // ignores. Remove the entry from the active index so `task list` matches\n // disk reality.\n await safeUpdateTaskIndex(input.paths, { kind: \"remove\", id: input.taskId });\n\n return {\n taskId: input.taskId,\n title,\n sessionId: adHoc.sessionId,\n eventId,\n };\n}\n","import { z } from \"zod\";\nimport {\n IsoTimestampSchema,\n SchemaVersionSchema,\n SessionIdSchema,\n TaskIdSchema,\n WorkspaceIdSchema,\n} from \"./shared.schema.js\";\n\n/**\n * Task lifecycle states.\n *\n * The storage layer's `ALLOWED_TRANSITIONS` map (= source of truth in\n * `tasks.ts`) is the authoritative graph; the comment below is a snapshot.\n * `planned` reaches `done` / `cancelled` directly so tasks completed (or\n * abandoned) outside an explicit in-progress phase can close in a single\n * CLI call:\n *\n * planned → {in_progress | done | cancelled}\n * in_progress → {done | cancelled}\n * done / cancelled = terminal\n *\n * Self-edges are rejected so the audit trail stays monotonic.\n */\nexport const TaskStatusSchema = z.enum([\"planned\", \"in_progress\", \"done\", \"cancelled\"]);\n/** Inferred runtime type for {@link TaskStatusSchema}. */\nexport type TaskStatus = z.infer<typeof TaskStatusSchema>;\n\nconst TaskInnerSchema = z.object({\n id: TaskIdSchema,\n title: z.string().min(1),\n label: z.string().min(1).optional(),\n status: TaskStatusSchema,\n created_at: IsoTimestampSchema,\n updated_at: IsoTimestampSchema,\n workspace_id: WorkspaceIdSchema,\n /**\n * Session id that anchors this task. For freshly created tasks it is the\n * session that wrote the `task_created` event (= ad-hoc reconcile target\n * for ad-hoc paths, or the target session id for attach paths). After\n * `basou task reconcile --write` repairs a broken anchor the\n * value is replaced with the ad-hoc reconcile session id; the old broken\n * session_id is preserved on the `task_reconciled` event payload via\n * `removed_created_in_session` for audit. So this field always names a\n * reachable session, even after the original anchor is gone.\n */\n created_in_session: SessionIdSchema,\n /**\n * Snapshot of sessions linked to this task. The events.jsonl history is\n * the source of truth (see\n * `docs/spec/generated-markdown.md#105-decisionsmd-generation-principle`);\n * this field is maintained as a UX-only cache so editors can read the\n * task.md and immediately see related sessions. Defaults to `[]` for\n * backward compatibility.\n */\n linked_sessions: z.array(SessionIdSchema).default([]),\n});\n\n/**\n * Schema for the YAML front matter of `.basou/tasks/<task_id>.md`.\n *\n * The markdown body after the front matter is intentionally NOT modelled\n * here — it is free-form user-edited content. The storage layer splits\n * the file into `task` (this schema) and `body` (the trailing string).\n */\nexport const TaskSchema = z.object({\n schema_version: SchemaVersionSchema,\n task: TaskInnerSchema,\n});\n/** Inferred runtime type for {@link TaskSchema}. */\nexport type Task = z.infer<typeof TaskSchema>;\n","import { mkdir, rm } from \"node:fs/promises\";\nimport { homedir } from \"node:os\";\nimport { join } from \"node:path\";\nimport { appendChainedEventLocked } from \"../events/chained-append.js\";\nimport { writeEventsBulk } from \"../events/event-writer.js\";\nimport { type PrefixedId, prefixedUlid } from \"../ids/ulid.js\";\nimport { findErrorCode } from \"../lib/error-codes.js\";\nimport { sanitizeWorkingDirectory } from \"../lib/path-sanitizer.js\";\nimport type { Event } from \"../schemas/event.schema.js\";\nimport type { Manifest } from \"../schemas/manifest.schema.js\";\nimport {\n type Session,\n SessionSchema,\n type SessionSourceKind,\n SessionSourceKindSchema,\n type SessionStatus,\n} from \"../schemas/session.schema.js\";\nimport { SessionIdSchema } from \"../schemas/shared.schema.js\";\nimport type { BasouPaths } from \"./basou-dir.js\";\nimport { acquireLock } from \"./lockfile.js\";\nimport { readSessionYaml } from \"./sessions.js\";\nimport { linkYamlFile, overwriteYamlFile } from \"./yaml-store.js\";\n\n// ============================================================================\n// Finalization-failure error\n// ============================================================================\n\n/**\n * Thrown when the ad-hoc session was fully written to disk (4 lifecycle\n * events + N target events plus the initial `session.yaml`) but the final\n * `session.yaml` update to status `completed` failed. The caller can read\n * `sessionId` / `targetEventIds` to emit a retry-duplicate-prevention\n * warning, since the target events themselves are already persisted in\n * `events.jsonl`.\n *\n * `targetEventIds` is an array because a single ad-hoc session may carry\n * multiple target events (e.g. `task new --status done` fires both\n * `task_created` and `task_status_changed`). Callers that need a single\n * anchor id should use `targetEventIds[0]`, which by convention is the\n * primary event for the operation.\n */\nexport class FailedToFinalizeError extends Error {\n readonly sessionId: PrefixedId<\"ses\">;\n readonly targetEventIds: ReadonlyArray<PrefixedId<\"evt\">>;\n\n constructor(\n sessionId: PrefixedId<\"ses\">,\n targetEventIds: ReadonlyArray<PrefixedId<\"evt\">>,\n cause: unknown,\n ) {\n super(\"Failed to finalize ad-hoc session\", { cause });\n this.name = \"FailedToFinalizeError\";\n if (targetEventIds.length === 0) {\n // Defensive guard for direct (non-orchestrator) constructors. The\n // orchestrator already rejects an empty `targetEventBuilders` array\n // before any ID minting, but `FailedToFinalizeError` is a public\n // exported class and `error-render.ts` reads `targetEventIds[0]` as\n // the operator-facing anchor — an empty array there would surface as\n // `\"Recorded undefined ...\"`.\n throw new Error(\"FailedToFinalizeError requires at least one target event id\");\n }\n this.sessionId = sessionId;\n this.targetEventIds = targetEventIds;\n }\n}\n\n// ============================================================================\n// Ad-hoc session path\n// ============================================================================\n\nexport type CreateAdHocSessionInput = {\n paths: BasouPaths;\n manifest: Manifest;\n /** Pre-built session label (caller is responsible for truncation). */\n label: string;\n /** ISO timestamp shared across the 5 lifecycle/target events. */\n occurredAt: string;\n sessionSource: SessionSourceKind;\n workingDirectory: string;\n invocation: { command: string; args: string[] };\n /**\n * Optional task id to link this ad-hoc session to. When provided, both the\n * initial and the final `session.yaml` writes embed `task_id` so the\n * single-session-to-single-task invariant (see\n * `docs/spec/workspace.md#21-confirmed-invariants`) holds for task-flavoured\n * ad-hoc paths (`basou task new` / `task status` without `--session`).\n * Defaults to `null` so existing callers (decision / note) are unchanged.\n */\n taskId?: PrefixedId<\"task\">;\n /**\n * Builds the variant-specific target events. Each builder receives the\n * freshly minted session id and a freshly minted event id (one per\n * builder) so callers can fill in cross-reference fields (`decision_id`,\n * `body`, ...) without owning ID generation.\n *\n * The most common case is a single-element array (`[builder]`) for the\n * one-target-event flows (`basou decision record`, `basou session note`,\n * `basou task new --status planned`, `basou task status`,\n * `basou task reconcile`). Two-element arrays are used by\n * `basou task new --status done|cancelled` to emit `task_created` plus\n * an immediate `task_status_changed` in the same atomic bulk write.\n *\n * Must be non-empty; an empty array is rejected at the start of\n * {@link createAdHocSessionWithEvent}.\n */\n targetEventBuilders: ReadonlyArray<\n (sessionId: PrefixedId<\"ses\">, eventId: PrefixedId<\"evt\">) => Event\n >;\n};\n\nexport type CreateAdHocSessionResult = {\n sessionId: PrefixedId<\"ses\">;\n /**\n * Target event IDs in the order their builders were supplied. Length\n * equals `input.targetEventBuilders.length`. Callers that conceptually\n * have a single anchor event should use `targetEventIds[0]`.\n */\n targetEventIds: PrefixedId<\"evt\">[];\n /**\n * Lifecycle event IDs in chronological order:\n * `[started, status→running, status→completed, ended]`.\n * Target event IDs are reported separately in {@link targetEventIds}.\n */\n lifecycleEventIds: PrefixedId<\"evt\">[];\n};\n\n/**\n * Atomically create a fresh ad-hoc session that produces one or more target\n * events then immediately closes itself. The session lifecycle\n * (`initialized → running → completed`, see\n * `docs/spec/terminal-and-import.md#62-transition-diagram`) is honored:\n * `4 + N` events are\n * written in one bulk atomic pass (where N = number of target builders) and\n * `session.yaml` is written twice (`initialized` → `completed`).\n *\n * The single-target case (N = 1) covers `basou decision record`,\n * `basou session note`, `basou task new --status planned|in_progress`,\n * `basou task status`, and `basou task reconcile`. The two-target case\n * (N = 2) covers `basou task new --status done|cancelled` which fires\n * `task_created` followed immediately by `task_status_changed (planned → terminal)`\n * so the audit trail captures the implicit transition.\n *\n * Failures during `mkdir`, the initial `session.yaml` write, or the bulk\n * `events.jsonl` write trigger a best-effort `rm -rf` of the session\n * directory so partial ad-hoc sessions do not pollute the workspace.\n *\n * A failure on the final `session.yaml` status update is fatal but the\n * session directory is NOT cleaned up — `events.jsonl` is consistent and\n * carries the full lifecycle trail, so callers can reconcile manually. The\n * thrown {@link FailedToFinalizeError} carries the `sessionId` and\n * `targetEventIds` so the CLI layer can warn the user not to re-run the\n * command and duplicate the target events.\n *\n * Direct (non-CLI) callers are self-defended by zod boundary parses on\n * `sessionSource` and the initial session record.\n */\nexport async function createAdHocSessionWithEvent(\n input: CreateAdHocSessionInput,\n): Promise<CreateAdHocSessionResult> {\n // 1. core boundary parse — direct callers may pass arbitrary strings.\n SessionSourceKindSchema.parse(input.sessionSource);\n if (input.targetEventBuilders.length === 0) {\n throw new Error(\"Ad-hoc session requires at least one target event builder\");\n }\n\n // 2. ID minting. One target event id per builder; lifecycle ids are fixed.\n const sessionId = prefixedUlid(\"ses\");\n const startedEventId = prefixedUlid(\"evt\");\n const statusToRunningEventId = prefixedUlid(\"evt\");\n const targetEventIds = input.targetEventBuilders.map(() => prefixedUlid(\"evt\"));\n const statusToCompletedEventId = prefixedUlid(\"evt\");\n const endedEventId = prefixedUlid(\"evt\");\n\n // 3. Build the initial session record (status=initialized) and validate it\n // so a malformed input shape fails fast before any disk write.\n const initialSession: Session = SessionSchema.parse(\n buildInitialSession({\n sessionId,\n workspaceId: input.manifest.workspace.id,\n sourceKind: input.sessionSource,\n startedAt: input.occurredAt,\n label: input.label,\n workingDirectory: input.workingDirectory,\n invocation: input.invocation,\n taskId: input.taskId ?? null,\n }),\n );\n\n // Hold the session lock across the whole create sequence (initial yaml ->\n // bulk events -> final yaml). The session is briefly `initialized` and thus\n // attachable; without the lock a foreign attach could append a line into\n // that window which the atomic bulk write would then clobber, breaking the\n // chain. The session id is freshly minted, so no caller already holds it.\n const sessionDir = join(input.paths.sessions, sessionId);\n const sessionYamlPath = join(sessionDir, \"session.yaml\");\n const lock = await acquireLock(input.paths, \"session\", sessionId);\n let bulkResult: Awaited<ReturnType<typeof writeEventsBulk>> = null;\n try {\n // 4. Create the session directory (recursive=true so a stripped-down\n // workspace with `.basou/sessions` missing still recovers).\n try {\n await mkdir(sessionDir, { recursive: true });\n } catch (error: unknown) {\n throw new Error(\"Failed to create session directory\", { cause: error });\n }\n\n // 5. Initial session.yaml write (status=initialized).\n try {\n await linkYamlFile(sessionYamlPath, initialSession);\n } catch (error: unknown) {\n await rm(sessionDir, { recursive: true, force: true }).catch(() => undefined);\n if (findErrorCode(error, \"EEXIST\")) {\n throw new Error(\"Session directory collision (retry the command)\", {\n cause: error,\n });\n }\n throw error;\n }\n\n // 6. events.jsonl bulk write — the full lifecycle batch written atomically\n // in a single tmp+rename pass, hash-chained (chain:true) so the ad-hoc\n // log is tamper-evident like an imported one. A failure here removes the\n // session directory so no partial state survives (status=initialized +\n // no events is not visible in `basou session list`).\n try {\n const targetEvents: Event[] = input.targetEventBuilders.map((build, index) => {\n const targetEventId = targetEventIds[index] as PrefixedId<\"evt\">;\n return assertTargetEventIdentity(build(sessionId, targetEventId), sessionId, targetEventId);\n });\n const events: Event[] = [\n {\n schema_version: \"0.1.0\",\n id: startedEventId,\n session_id: sessionId,\n occurred_at: input.occurredAt,\n source: \"local-cli\",\n type: \"session_started\",\n },\n {\n schema_version: \"0.1.0\",\n id: statusToRunningEventId,\n session_id: sessionId,\n occurred_at: input.occurredAt,\n source: \"local-cli\",\n type: \"session_status_changed\",\n from: \"initialized\",\n to: \"running\",\n },\n ...targetEvents,\n {\n schema_version: \"0.1.0\",\n id: statusToCompletedEventId,\n session_id: sessionId,\n occurred_at: input.occurredAt,\n source: \"local-cli\",\n type: \"session_status_changed\",\n from: \"running\",\n to: \"completed\",\n },\n {\n schema_version: \"0.1.0\",\n id: endedEventId,\n session_id: sessionId,\n occurred_at: input.occurredAt,\n source: \"local-cli\",\n type: \"session_ended\",\n exit_code: 0,\n },\n ];\n bulkResult = await writeEventsBulk(sessionDir, events, { chain: true });\n } catch (error: unknown) {\n await rm(sessionDir, { recursive: true, force: true }).catch(() => undefined);\n throw error;\n }\n\n // 7. Finalize: overwrite session.yaml with status=completed + ended_at +\n // invocation.exit_code=0 + the integrity head anchor from the chained\n // bulk write. Failure is fatal but events.jsonl is already complete, so\n // the directory is intentionally NOT removed — the caller surfaces the\n // partial state via FailedToFinalizeError.\n try {\n const finalSession: Session = SessionSchema.parse({\n ...initialSession,\n session: {\n ...initialSession.session,\n status: \"completed\" satisfies SessionStatus,\n ended_at: input.occurredAt,\n invocation: { ...initialSession.session.invocation, exit_code: 0 },\n ...(bulkResult !== null\n ? { integrity: { head_hash: bulkResult.headHash, event_count: bulkResult.count } }\n : {}),\n },\n });\n await overwriteYamlFile(sessionYamlPath, finalSession);\n } catch (error: unknown) {\n throw new FailedToFinalizeError(sessionId, targetEventIds, error);\n }\n } finally {\n await lock.release();\n }\n\n return {\n sessionId,\n targetEventIds,\n lifecycleEventIds: [\n startedEventId,\n statusToRunningEventId,\n statusToCompletedEventId,\n endedEventId,\n ],\n };\n}\n\nfunction buildInitialSession(input: {\n sessionId: PrefixedId<\"ses\">;\n workspaceId: PrefixedId<\"ws\">;\n sourceKind: SessionSourceKind;\n startedAt: string;\n label: string;\n workingDirectory: string;\n invocation: { command: string; args: string[] };\n taskId: PrefixedId<\"task\"> | null;\n}): Session {\n return {\n schema_version: \"0.1.0\",\n session: {\n id: input.sessionId,\n label: input.label,\n task_id: input.taskId,\n workspace_id: input.workspaceId,\n source: { kind: input.sourceKind, version: \"0.1.0\" },\n started_at: input.startedAt,\n status: \"initialized\",\n working_directory: sanitizeWorkingDirectory(input.workingDirectory, { homedir: homedir() }),\n invocation: { ...input.invocation, exit_code: null },\n related_files: [],\n events_log: \"events.jsonl\",\n },\n };\n}\n\n// ============================================================================\n// Attach path\n// ============================================================================\n\nexport type AttachableStatus = \"initialized\" | \"running\" | \"waiting_approval\";\n\nconst DEFAULT_ATTACHABLE_STATUSES: ReadonlySet<AttachableStatus> = new Set<AttachableStatus>([\n \"initialized\",\n \"running\",\n \"waiting_approval\",\n]);\n\nexport type AppendEventToExistingInput = {\n paths: BasouPaths;\n /** Already resolved via `resolveSessionId`; parsed at boundary again. */\n sessionId: PrefixedId<\"ses\">;\n attachableStatuses?: ReadonlySet<AttachableStatus>;\n eventBuilder: (eventId: PrefixedId<\"evt\">) => Event;\n};\n\nexport type AppendEventToExistingResult = {\n eventId: PrefixedId<\"evt\">;\n sessionStatus: SessionStatus;\n};\n\n/**\n * Read `session.yaml`, verify the session is in an attachable state, and\n * append a single event to its `events.jsonl`. `session.yaml` is NOT modified\n * so the caller can safely append `decision_recorded` / `note_added` without\n * mutating `related_files`, `summary`, or the session status.\n *\n * Race note: the status check and the event append are not atomic.\n * Between them another writer (e.g. `basou run claude-code` ending its\n * session) can flip the YAML to `completed` and append `session_ended`.\n * v0.1 accepts this race; the `events_say_ended_but_yaml_running`-style\n * suspect rule surfaces the inconsistency. Per-session locking is\n * deferred to a v0.3+ follow-up.\n */\nexport async function appendEventToExistingSession(\n input: AppendEventToExistingInput,\n): Promise<AppendEventToExistingResult> {\n // 1. Boundary parse (direct caller self-defense).\n SessionIdSchema.parse(input.sessionId);\n\n // 2. Read session.yaml.\n const sessionDoc = await readSessionYaml(input.paths, input.sessionId);\n const status = sessionDoc.session.status;\n\n // 3. Status check.\n if (status === \"imported\") {\n throw new Error(\"Cannot attach to imported session\");\n }\n const attachable = input.attachableStatuses ?? DEFAULT_ATTACHABLE_STATUSES;\n if (!attachable.has(status as AttachableStatus)) {\n throw new Error(`Session is not active: ${status}`);\n }\n\n // 4. Mint event ID and build payload.\n const eventId = prefixedUlid(\"evt\");\n const event = assertTargetEventIdentity(input.eventBuilder(eventId), input.sessionId, eventId);\n\n // 5. Append, chaining onto the on-disk tail. The CALLER owns the session\n // lock (decision record / session note / task attach each acquire it\n // around this whole read-check-append window), so the lock-assumed\n // primitive is used here and must NOT re-acquire the lock.\n await appendChainedEventLocked(input.paths, input.sessionId, event);\n\n return { eventId, sessionStatus: status };\n}\n\n/**\n * Defensive check: a builder closure could in principle hand back\n * an event whose `id` or `session_id` differs from the orchestrator's\n * minted values. EventSchema only validates the shape, so this slip would\n * silently corrupt events.jsonl. Reject with a fixed pathless message so\n * direct-caller misuse never reaches disk.\n */\nfunction assertTargetEventIdentity(\n event: Event,\n expectedSessionId: PrefixedId<\"ses\">,\n expectedEventId: PrefixedId<\"evt\">,\n): Event {\n if (event.session_id !== expectedSessionId) {\n throw new Error(\"Target event session_id mismatch\");\n }\n if (event.id !== expectedEventId) {\n throw new Error(\"Target event id mismatch\");\n }\n return event;\n}\n","import { posix as path } from \"node:path\";\n\n/**\n * Options for {@link sanitizePath}. Both `workingDirectory` and `homedir`\n * are absolute POSIX paths the caller has already resolved (typically via\n * `process.cwd()` and `os.homedir()`). Callers are responsible for passing\n * fully normalised values; the sanitizer normalises them again internally\n * so a trailing slash or `.`-segment does not corrupt the prefix match.\n */\nexport type SanitizePathOptions = {\n /**\n * The session's working directory (= the `working_directory` field the\n * caller is about to write). Paths under this directory are rewritten\n * relative to it so the operator-private absolute prefix never leaks\n * into the workspace's persistent state.\n */\n workingDirectory: string;\n /**\n * The operator's home directory. Paths under this directory (but NOT\n * under `workingDirectory`) are rewritten with a `~/` prefix.\n */\n homedir: string;\n};\n\n/**\n * Rewrite an absolute path into a workspace-friendly form so the persisted\n * state of `.basou/` does not leak the operator's machine layout:\n *\n * 1. Paths under `opts.workingDirectory` become repository-relative\n * (e.g. `<wd>/src/x.ts` → `src/x.ts`, `<wd>` itself → `.`).\n * 2. Paths under `opts.homedir` (but not workingDirectory) become\n * tilde-prefixed (`/Users/u/notes/x.md` → `~/notes/x.md`,\n * `/Users/u` → `~`).\n * 3. Anything else — relative paths, system paths under `/etc/*`,\n * `..`-escapes from either base, paths that simply do not share a\n * prefix with either option — is returned verbatim (after `..`\n * normalisation). The sanitizer is intentionally non-redacting on\n * system paths so an operator who deliberately recorded a system\n * file (e.g. `/etc/hosts`) is not silently stripped of context.\n *\n * Hardening:\n * - A null byte in the input is rejected with `Invalid path: contains\n * null byte` (= POSIX path APIs treat \\0 as terminator and any path\n * containing one is malformed; we never accept it on the write side).\n * - `..` segments are resolved purely (no fs access) so the prefix\n * match cannot be defeated by `<wd>/../escape/x.ts` masquerading as\n * workingDirectory-internal.\n * - Backslashes are folded to forward slashes so a Windows-style input\n * can still be matched against POSIX bases. v0.3 targets macOS /\n * Linux only; full Windows support is a v0.4+ task.\n */\nexport function sanitizePath(rawPath: string, opts: SanitizePathOptions): string {\n if (rawPath.includes(\"\\0\")) {\n throw new Error(\"Invalid path: contains null byte\");\n }\n const normalized = path.normalize(rawPath.replace(/\\\\/g, \"/\"));\n const wd = path.normalize(opts.workingDirectory.replace(/\\\\/g, \"/\"));\n const home = path.normalize(opts.homedir.replace(/\\\\/g, \"/\"));\n\n // Only attempt prefix matching for absolute inputs; an already-relative\n // path stays as-is so write paths that pre-relativised do not get\n // mangled.\n if (!path.isAbsolute(normalized)) {\n return normalized;\n }\n\n // (1) workingDirectory-internal -> repo-relative.\n if (normalized === wd) return \".\";\n const wdRel = path.relative(wd, normalized);\n if (wdRel !== \"\" && !wdRel.startsWith(\"..\")) {\n return wdRel;\n }\n\n // (2) homedir-internal -> ~/...\n if (normalized === home) return \"~\";\n const homeRel = path.relative(home, normalized);\n if (homeRel !== \"\" && !homeRel.startsWith(\"..\")) {\n return `~/${homeRel}`;\n }\n\n // (3) preserve as-is.\n return normalized;\n}\n\n/**\n * Sanitize the `working_directory` field itself. This is a distinct entry\n * point because the field's own value is the workingDirectory of every\n * `related_files[]` entry written alongside it — running it through\n * {@link sanitizePath} with `opts.workingDirectory = rawPath` would\n * collapse the result to `\".\"` and lose the homedir-relative form the\n * spec requires.\n *\n * Strategy: bypass the workingDirectory rule entirely by passing a\n * sentinel that no real path can match. The homedir rule (rule 2) and\n * the preserve-as-is rule (rule 3) still apply, so:\n * - `/Users/u/projects/foo` → `~/projects/foo`\n * - `/Users/u` → `~`\n * - `/srv/work` → `/srv/work` (preserved, off-tree)\n *\n * Callers should still pass the live `homedir` so the rewrite uses the\n * real operator-private prefix.\n */\nexport function sanitizeWorkingDirectory(\n rawPath: string,\n opts: Pick<SanitizePathOptions, \"homedir\">,\n): string {\n // A sentinel that no real absolute path on disk can equal or be under.\n // `path.posix.normalize` collapses leading `/` so any sentinel must\n // remain non-prefixing post-normalisation; the sentinel below survives\n // normalisation as itself and never matches a real path.\n return sanitizePath(rawPath, {\n workingDirectory: \"/__basou_sentinel_never_match__\",\n homedir: opts.homedir,\n });\n}\n\n/** Result of {@link sanitizeRelatedFiles}. */\nexport type SanitizeRelatedFilesResult = {\n /** Sanitized path list (same length as the input). */\n sanitized: string[];\n /** Number of entries whose sanitized form differs from the input. */\n mutationCount: number;\n};\n\n/**\n * Apply {@link sanitizePath} to every entry of a `related_files[]` array\n * and report how many entries actually changed shape so callers (e.g. the\n * session-import CLI) can surface a single-line warning. The helper does\n * not deduplicate — callers already collect related_files into a Set\n * before serialising.\n */\nexport function sanitizeRelatedFiles(\n paths: ReadonlyArray<string>,\n opts: SanitizePathOptions,\n): SanitizeRelatedFilesResult {\n const sanitized: string[] = [];\n let mutationCount = 0;\n for (const p of paths) {\n const next = sanitizePath(p, opts);\n sanitized.push(next);\n if (next !== p) mutationCount += 1;\n }\n return { sanitized, mutationCount };\n}\n","import { readFile } from \"node:fs/promises\";\nimport { join } from \"node:path\";\nimport { findErrorCode } from \"../lib/error-codes.js\";\nimport {\n TASK_INDEX_SCHEMA_VERSION,\n type TaskIndex,\n type TaskIndexEntry,\n TaskIndexSchema,\n} from \"../schemas/task-index.schema.js\";\nimport { atomicReplace } from \"./atomic.js\";\nimport type { BasouPaths } from \"./basou-dir.js\";\n\n/**\n * Absolute path of the workspace's `tasks/index.json`. The index lives\n * INSIDE `<paths.tasks>` (not under `<paths.root>`) so a future\n * monorepo-style layout with multiple task families could carry its own\n * index without colliding at the basou root.\n */\nexport function taskIndexPath(paths: BasouPaths): string {\n return join(paths.tasks, \"index.json\");\n}\n\n/**\n * Read and validate `tasks/index.json`. Returns the parsed payload only\n * when the schema_version matches the current literal — a mismatch is\n * surfaced as a schema parse failure so the caller falls through to the\n * lazy-rebuild path.\n *\n * Error contract:\n * - ENOENT → throw `Error(\"Task index not found\", { cause })`\n * - JSON parse / schema fail / version mismatch → throw\n * `Error(\"Invalid task index\", { cause })`\n * - any other I/O failure → throw `Error(\"Failed to read task index\", { cause })`\n *\n * Callers should treat all three as \"rebuild from disk\"; the distinct\n * messages exist so debug output / dogfood notes can tell them apart.\n */\nexport async function readTaskIndex(paths: BasouPaths): Promise<TaskIndex> {\n const filePath = taskIndexPath(paths);\n let raw: string;\n try {\n raw = await readFile(filePath, \"utf8\");\n } catch (error: unknown) {\n if (findErrorCode(error, \"ENOENT\")) {\n throw new Error(\"Task index not found\", { cause: error });\n }\n throw new Error(\"Failed to read task index\", { cause: error });\n }\n let parsedJson: unknown;\n try {\n parsedJson = JSON.parse(raw);\n } catch (error: unknown) {\n throw new Error(\"Invalid task index\", { cause: error });\n }\n const result = TaskIndexSchema.safeParse(parsedJson);\n if (!result.success) {\n throw new Error(\"Invalid task index\", { cause: result.error });\n }\n if (result.data.schema_version !== TASK_INDEX_SCHEMA_VERSION) {\n // Reject older / newer schema versions so a future bump triggers a\n // forced rebuild rather than silent migration.\n throw new Error(\"Invalid task index\", {\n cause: new Error(`Unsupported task index schema_version: ${result.data.schema_version}`),\n });\n }\n return result.data;\n}\n\n/**\n * Atomically write `tasks/index.json` with the given entries. Entries\n * are sorted by id (= ULID-ascending) so two rebuilds on the same disk\n * state produce byte-identical output and `git diff` stays clean.\n *\n * Caller-controlled `now` lets tests assert on `last_rebuilt_at`\n * without faking `Date`. When omitted the current wall clock is used.\n */\nexport async function rebuildTaskIndex(\n paths: BasouPaths,\n entries: ReadonlyArray<TaskIndexEntry>,\n now?: () => Date,\n): Promise<TaskIndex> {\n const sorted = [...entries].sort((a, b) => a.id.localeCompare(b.id));\n const payload: TaskIndex = {\n schema_version: TASK_INDEX_SCHEMA_VERSION,\n tasks: sorted,\n last_rebuilt_at: (now ?? (() => new Date()))().toISOString(),\n };\n // Self-defense — boundary-parse so a buggy caller cannot smuggle in\n // an invalid entry shape past the read-side schema check.\n TaskIndexSchema.parse(payload);\n await atomicReplace(taskIndexPath(paths), `${JSON.stringify(payload, null, 2)}\\n`);\n return payload;\n}\n\n/**\n * Mutation kind for {@link updateTaskIndex}. `add` and `update` carry a\n * full entry payload; `remove` carries only the id (the entry is gone\n * from disk by the time we write the index).\n *\n * archiveTask uses `remove` too: the archived task no longer participates\n * in the active task index because `enumerateTaskIds` (= the index's\n * read consumer) scans only `tasks/<id>.md`, not `tasks/archive/<id>.md`.\n */\nexport type TaskIndexOp =\n | { kind: \"add\"; entry: TaskIndexEntry }\n | { kind: \"update\"; entry: TaskIndexEntry }\n | { kind: \"remove\"; id: string };\n\n/**\n * Apply a single mutation to `tasks/index.json` and atomically rewrite\n * it. Falls through to {@link rebuildTaskIndex} when the current index is\n * missing / invalid so the first write after a workspace migration\n * still produces a valid file.\n *\n * Write failure (atomic-rename ENOSPC / EACCES etc.) is re-thrown\n * unwrapped so the caller (= each task write API) can decide whether to\n * surface it as a warning or escalate. The recommended policy in\n * `tasks.ts` is `console.warn(...)` plus keep the task.md write\n * successful (= index is a soft cache, not source of truth).\n */\nexport async function updateTaskIndex(\n paths: BasouPaths,\n op: TaskIndexOp,\n options?: { now?: () => Date },\n): Promise<TaskIndex> {\n const nowFn = options?.now ?? (() => new Date());\n let current: TaskIndex;\n try {\n current = await readTaskIndex(paths);\n } catch {\n // Index missing or invalid — rebuild empty before applying op.\n current = {\n schema_version: TASK_INDEX_SCHEMA_VERSION,\n tasks: [],\n last_rebuilt_at: nowFn().toISOString(),\n };\n }\n\n let nextTasks: TaskIndexEntry[];\n switch (op.kind) {\n case \"add\":\n nextTasks = current.tasks.some((t) => t.id === op.entry.id)\n ? current.tasks.map((t) => (t.id === op.entry.id ? op.entry : t))\n : [...current.tasks, op.entry];\n break;\n case \"update\":\n nextTasks = current.tasks.some((t) => t.id === op.entry.id)\n ? current.tasks.map((t) => (t.id === op.entry.id ? op.entry : t))\n : [...current.tasks, op.entry];\n break;\n case \"remove\":\n nextTasks = current.tasks.filter((t) => t.id !== op.id);\n break;\n }\n\n return await rebuildTaskIndex(paths, nextTasks, nowFn);\n}\n","import { z } from \"zod\";\nimport { IsoTimestampSchema, SchemaVersionSchema, TaskIdSchema } from \"./shared.schema.js\";\nimport { TaskStatusSchema } from \"./task.schema.js\";\n\n/**\n * Single entry inside `.basou/tasks/index.json`.\n *\n * Source of truth remains `task.md`; this is a derived cache populated\n * write-through on every task mutation (`createTask`,\n * `updateTaskStatusWithEvent`, `editTask`, `deleteTask`, `archiveTask`,\n * `reconcileTask`, `refreshTaskLinkedSessions`). The minimum field set\n * lets `basou task list` filter / sort without re-parsing every front\n * matter, while keeping the index small enough that rebuilds stay cheap.\n *\n * `label` is omitted when the task has no explicit label so the JSON\n * round-trips without storing `undefined` literals.\n */\nexport const TaskIndexEntrySchema = z\n .object({\n id: TaskIdSchema,\n status: TaskStatusSchema,\n label: z.string().min(1).optional(),\n updated_at: IsoTimestampSchema,\n })\n .strict();\nexport type TaskIndexEntry = z.infer<typeof TaskIndexEntrySchema>;\n\n/**\n * Top-level schema for `.basou/tasks/index.json`. `tasks[]` is the\n * compact projection used for fast enumeration; `last_rebuilt_at`\n * records the wall-clock moment of the latest full readdir rebuild so\n * a future migration / debugging tool can spot stale caches without\n * comparing every entry against disk.\n *\n * `schema_version` lets a future bump trigger a forced rebuild instead\n * of attempting silent schema migration — readTaskIndex returns the\n * parsed payload only when the version matches the current literal, so\n * a mismatch falls through to the lazy-rebuild path.\n */\nexport const TaskIndexSchema = z\n .object({\n schema_version: SchemaVersionSchema,\n tasks: z.array(TaskIndexEntrySchema),\n last_rebuilt_at: IsoTimestampSchema,\n })\n .strict();\nexport type TaskIndex = z.infer<typeof TaskIndexSchema>;\n\n/** Current schema version. Bump triggers a forced rebuild on next read. */\nexport const TASK_INDEX_SCHEMA_VERSION = \"0.1.0\" as const;\n","// `[1-9]\\d*` rejects \"0\" and leading zeros so that callers cannot smuggle in\n// a non-positive duration (which the underlying spawn validators would later\n// reject anyway). The unit is fixed to `ms`/`s`/`m`/`h`; days and weeks are\n// out of scope for v0.1.\nconst DURATION_RE = /^([1-9]\\d*)(ms|s|m|h)$/;\n\n/**\n * Parse a unit-suffixed duration string (e.g. `30s`, `5m`, `1h`, `100ms`)\n * into milliseconds.\n *\n * Rejects formats that cannot represent a positive, finite millisecond\n * value: malformed inputs, zero, leading-zero values, and computations that\n * overflow to `Infinity`. The returned number is always a positive integer.\n *\n * Supported units: `ms` (milliseconds), `s` (seconds), `m` (minutes),\n * `h` (hours).\n *\n * @param input duration string with required unit suffix\n * @returns duration in milliseconds (positive, finite)\n * @throws Error with message\n * `Invalid duration: <input>. Expected format: <positive-integer><unit> where unit is ms/s/m/h`\n * for format errors, or `Duration overflow: <input>` for non-finite results.\n */\nexport function parseDuration(input: string): number {\n const trimmed = input.trim();\n const match = DURATION_RE.exec(trimmed);\n if (!match) {\n throw new Error(\n `Invalid duration: ${trimmed}. Expected format: <positive-integer><unit> where unit is ms/s/m/h`,\n );\n }\n const value = Number(match[1]);\n const unit = match[2];\n let ms: number;\n switch (unit) {\n case \"ms\":\n ms = value;\n break;\n case \"s\":\n ms = value * 1000;\n break;\n case \"m\":\n ms = value * 60_000;\n break;\n case \"h\":\n ms = value * 3_600_000;\n break;\n default:\n // Unreachable per the regex; satisfy exhaustiveness analysis.\n throw new Error(`Invalid duration unit: ${unit}`);\n }\n if (!Number.isFinite(ms)) {\n throw new Error(`Duration overflow: ${trimmed}`);\n }\n return ms;\n}\n","/**\n * Coarse human duration from milliseconds: \"3h 05m\" / \"12m 30s\" / \"8s\".\n * Shared by the work-stats surfaces (`basou stats`, `basou session show`) and\n * the report renderer so they format identically.\n */\nexport function formatDurationMs(ms: number): string {\n const totalSeconds = Math.round(ms / 1000);\n const hours = Math.floor(totalSeconds / 3600);\n const minutes = Math.floor((totalSeconds % 3600) / 60);\n const seconds = totalSeconds % 60;\n if (hours > 0) return `${hours}h ${String(minutes).padStart(2, \"0\")}m`;\n if (minutes > 0) return `${minutes}m ${String(seconds).padStart(2, \"0\")}s`;\n return `${seconds}s`;\n}\n","import type { BasouPaths } from \"../storage/basou-dir.js\";\nimport { enumerateSessionDirs } from \"../storage/sessions.js\";\nimport { enumerateArchivedTaskIds, enumerateTaskIds } from \"../storage/tasks.js\";\n\n/**\n * Resolve a possibly-truncated session id prefix to a full session id by\n * scanning `<paths.sessions>/`. Existing message contract (carried over\n * from `packages/cli/src/commands/session.ts`) is\n * preserved exactly so callers that grep stderr keep working:\n *\n * - `\"Session id is empty\"`\n * - `\"Session not found: <input>\"`\n * - `\"Ambiguous session id '<input>': matched <N> sessions. Disambiguate\n * with a longer prefix.\"`\n */\nexport async function resolveSessionId(paths: BasouPaths, input: string): Promise<string> {\n return resolveIdInternal(paths, input, \"session\");\n}\n\n/**\n * Resolve a possibly-truncated task id prefix to a full task id by scanning\n * `<paths.tasks>/`. Mirrors {@link resolveSessionId} with the noun changed\n * to `task` in every error message.\n *\n * `options.includeArchived` extends the scan to `<paths.tasks>/archive/` so\n * read-only commands (e.g. `basou task show`) can address tasks that were\n * archived by `basou task archive`. Defaults to `false` so destructive flows\n * (status change, edit, delete, archive itself) cannot operate on archived\n * tasks accidentally.\n */\nexport async function resolveTaskId(\n paths: BasouPaths,\n input: string,\n options: { includeArchived?: boolean } = {},\n): Promise<string> {\n return resolveIdInternal(paths, input, \"task\", options);\n}\n\ntype IdKind = \"session\" | \"task\";\n\ntype KindConfig = {\n prefix: string;\n noun: string;\n nounPlural: string;\n capNoun: string;\n enumerate: (paths: BasouPaths) => Promise<string[]>;\n};\n\nconst KIND_CONFIG: Record<IdKind, KindConfig> = {\n session: {\n prefix: \"ses_\",\n noun: \"session\",\n nounPlural: \"sessions\",\n capNoun: \"Session\",\n enumerate: enumerateSessionDirs,\n },\n task: {\n prefix: \"task_\",\n noun: \"task\",\n nounPlural: \"tasks\",\n capNoun: \"Task\",\n enumerate: enumerateTaskIds,\n },\n};\n\nasync function resolveIdInternal(\n paths: BasouPaths,\n input: string,\n kind: IdKind,\n options: { includeArchived?: boolean } = {},\n): Promise<string> {\n const cfg = KIND_CONFIG[kind];\n const trimmed = input.trim();\n if (trimmed.length === 0) {\n throw new Error(`${cfg.capNoun} id is empty`);\n }\n const normalized = trimmed.startsWith(cfg.prefix) ? trimmed : `${cfg.prefix}${trimmed}`;\n if (normalized.length <= cfg.prefix.length) {\n throw new Error(`${cfg.capNoun} not found: ${input}`);\n }\n const primary = await cfg.enumerate(paths);\n // Merge in archived task ids when the caller opts in. Dedupe via a Set so\n // a single id appearing in both surfaces (shouldn't happen but defend\n // anyway) does not falsely register as ambiguous.\n const merged = new Set<string>(primary);\n if (kind === \"task\" && options.includeArchived === true) {\n for (const id of await enumerateArchivedTaskIds(paths)) {\n merged.add(id);\n }\n }\n if (merged.size === 0) {\n throw new Error(`${cfg.capNoun} not found: ${input}`);\n }\n const matches = [...merged].filter((e) => e.startsWith(normalized));\n if (matches.length === 0) {\n throw new Error(`${cfg.capNoun} not found: ${input}`);\n }\n if (matches.length > 1) {\n throw new Error(\n `Ambiguous ${cfg.noun} id '${input}': matched ${matches.length} ${cfg.nounPlural}. Disambiguate with a longer prefix.`,\n );\n }\n return matches[0] as string;\n}\n","import { promises as fs } from \"node:fs\";\nimport { homedir as osHomedir } from \"node:os\";\nimport { basename, dirname, isAbsolute, join, normalize, relative, resolve } from \"node:path\";\n\n/**\n * Cross-project boundary classification: split a session's `related_files`\n * into those that resolve INSIDE the project's declared `source_roots` and\n * those that confidently resolve OUTSIDE all of them.\n *\n * Why this exists: the claude-code adapter records every file a transcript\n * edited, regardless of where the file lives. A session is attributed to a\n * project by its recorded cwd (the import-time cwd guard), but a session that\n * legitimately belongs to project A can still have edited files under an\n * unrelated repo B. Those B paths then surface in `basou orient`'s \"recent\n * files\" and can mislead a resuming agent into continuing the wrong project's\n * work. This helper is the read-only primitive both the import warning and the\n * orientation advisory use to flag that boundary crossing — it never mutates\n * the trail.\n *\n * Resolution is realpath-aware so a file recorded through a workspace-view\n * symlink (e.g. `~/projects/foo-workspace/foo -> ../foo`) is NOT mis-flagged as\n * out-of-root. The bias is deliberately toward NOT crying wolf: a path is only\n * reported out-of-root when it confidently resolves outside every source root.\n * Anything that cannot be resolved with confidence stays classified in-root.\n */\n\n/**\n * The agent's / basou's own tooling directories. Edits here (plans, memory,\n * the trail store itself) are routine infrastructure, not another project's\n * work, so callers pass these as `extraInRoot` to keep them out of the\n * cross-project out-of-root flag.\n */\nexport const AGENT_INFRA_DIRS: readonly string[] = [\"~/.claude\", \"~/.codex\", \"~/.basou\"];\n\n/** Result of {@link classifyFilesBySourceRoot}: a partition of the input. */\nexport type SourceRootScope = {\n /** Entries (verbatim, as passed in) that resolve under a source root, or that could not be resolved with confidence. */\n inRoot: string[];\n /** Entries (verbatim) that confidently resolve outside every source root. */\n outOfRoot: string[];\n};\n\n/**\n * Resolve a `realpath`, tolerating a non-existent tail: realpath the longest\n * existing ANCESTOR and re-append the missing segments. A file recorded in a\n * past session may have since moved or been deleted, but we still want to\n * classify the LOCATION it referred to (and resolve any symlink in its\n * existing ancestry). Falls back to the lexical input on any non-ENOENT error\n * or once the filesystem root is reached without an existing ancestor.\n */\nasync function realpathBestEffort(absPath: string): Promise<string> {\n let current = normalize(absPath);\n const tail: string[] = [];\n // Bound the walk by path depth so a pathological input cannot loop forever.\n for (let guard = 0; guard < 4096; guard += 1) {\n try {\n const real = await fs.realpath(current);\n return tail.length > 0 ? join(real, ...tail.reverse()) : real;\n } catch (error: unknown) {\n const code = (error as NodeJS.ErrnoException | undefined)?.code;\n if (code !== \"ENOENT\" && code !== \"ENOTDIR\") {\n // Permission error etc.: do not guess, fall back to the lexical path.\n return normalize(absPath);\n }\n const parent = dirname(current);\n if (parent === current) return normalize(absPath); // reached root, nothing existed\n tail.push(basename(current));\n current = parent;\n }\n }\n return normalize(absPath);\n}\n\n/** Expand a leading `~` / `~/` to the home directory; leave other forms as-is. */\nfunction expandTilde(p: string, homedir: string): string {\n if (p === \"~\") return homedir;\n if (p.startsWith(\"~/\")) return join(homedir, p.slice(2));\n return p;\n}\n\n/**\n * Resolve a stored (sanitized) path to an absolute path before realpath:\n * - `~` / `~/x` → under homedir\n * - absolute → as-is\n * - relative → resolved against the session working directory\n * `workingDirectory` is itself sanitized (typically `~/...`), so it is\n * tilde-expanded first.\n */\nfunction toAbsolute(p: string, workingDirAbs: string, homedir: string): string {\n const expanded = expandTilde(p, homedir);\n if (isAbsolute(expanded)) return normalize(expanded);\n return normalize(resolve(workingDirAbs, expanded));\n}\n\n/**\n * True when `child` is `parent` itself or lives underneath it. Uses\n * `path.relative` rather than raw `startsWith` so a trailing separator or a\n * `..`/`.` segment in either operand cannot defeat the prefix match (the\n * `startsWith` form is a known foot-gun this codebase moved away from in\n * realpath comparisons elsewhere).\n */\nfunction isUnder(child: string, parent: string): boolean {\n if (child === parent) return true;\n const rel = relative(parent, child);\n return rel !== \"\" && !rel.startsWith(\"..\") && !isAbsolute(rel);\n}\n\n/**\n * Partition `files` into in-root / out-of-root against the project's\n * `source_roots`.\n *\n * - `sourceRoots` are the manifest's `import.source_roots` (relative to\n * `masterRoot`). An absent/empty list means \"the whole repo root\" — matching\n * the effective-source-roots rule elsewhere — so a solo project never reports\n * anything out-of-root.\n * - `masterRoot` is the absolute repository root the source roots resolve\n * against (the parent of `.basou`).\n *\n * Returns `{ inRoot, outOfRoot }` preserving the original entry strings. Empty\n * input or zero resolvable roots yields everything in-root (no false alarms).\n */\nexport async function classifyFilesBySourceRoot(input: {\n files: readonly string[];\n workingDirectory: string;\n sourceRoots: readonly string[] | null | undefined;\n masterRoot: string;\n /**\n * Extra directories (absolute or `~`-prefixed) that also count as in-root.\n * Callers pass the agent's own tooling dirs (`~/.claude`, `~/.codex`,\n * `~/.basou`) so routine plan / memory / store edits are NOT flagged as\n * another project's work — they are infrastructure, not a cross-project\n * crossing. Resolved against the home directory, not `masterRoot`.\n */\n extraInRoot?: readonly string[];\n homedir?: string;\n}): Promise<SourceRootScope> {\n const inRoot: string[] = [];\n const outOfRoot: string[] = [];\n if (input.files.length === 0) return { inRoot, outOfRoot };\n\n const homedir = input.homedir ?? osHomedir();\n const workingDirAbs = toAbsolute(input.workingDirectory, homedir, homedir);\n\n // Effective roots: a declared list is used verbatim; absent/empty means the\n // whole repo root (mirrors `effectiveSourceRoots`). Resolve + realpath each;\n // drop any that fail to resolve so a single bad entry does not void the rest.\n const declared =\n input.sourceRoots && input.sourceRoots.length > 0 ? [...input.sourceRoots] : [\".\"];\n const rootsAbs: string[] = [];\n for (const r of declared) {\n const expanded = expandTilde(r, homedir);\n const abs = isAbsolute(expanded)\n ? normalize(expanded)\n : normalize(resolve(input.masterRoot, expanded));\n rootsAbs.push(await realpathBestEffort(abs));\n }\n // Extra in-root dirs (agent/tool infra) resolve against the home directory.\n for (const e of input.extraInRoot ?? []) {\n const expanded = expandTilde(e, homedir);\n const abs = isAbsolute(expanded) ? normalize(expanded) : normalize(resolve(homedir, expanded));\n rootsAbs.push(await realpathBestEffort(abs));\n }\n // No resolvable roots → cannot judge; keep everything in-root.\n if (rootsAbs.length === 0) {\n return { inRoot: [...input.files], outOfRoot };\n }\n\n for (const file of input.files) {\n try {\n const abs = toAbsolute(file, workingDirAbs, homedir);\n const real = await realpathBestEffort(abs);\n const within = rootsAbs.some((root) => isUnder(real, root));\n (within ? inRoot : outOfRoot).push(file);\n } catch {\n // Any unexpected resolution failure: bias to in-root (do not cry wolf).\n inRoot.push(file);\n }\n }\n\n return { inRoot, outOfRoot };\n}\n","import { dirname, join } from \"node:path\";\nimport { enumerateApprovals, isLazyExpired, loadApproval } from \"../approval/approval-store.js\";\nimport { type ReplayWarning, replayEvents } from \"../events/event-replay.js\";\nimport { formatDurationMs } from \"../lib/format-duration.js\";\nimport { isTrailingStale, pickLatestSubstantiveEntry } from \"../lib/recency.js\";\nimport { AGENT_INFRA_DIRS, classifyFilesBySourceRoot } from \"../lib/source-root-scope.js\";\nimport type { BasouPaths } from \"../storage/basou-dir.js\";\nimport { readManifest } from \"../storage/manifest.js\";\nimport {\n type FederatedRoot,\n loadFederatedSessionEntries,\n loadSessionEntries,\n type SessionSkipReason,\n type SuspectReason,\n} from \"../storage/sessions.js\";\nimport { loadTaskEntries, type TaskSkipReason } from \"../storage/tasks.js\";\n\n/** Input contract for {@link renderOrientation} and {@link summarizeOrientation}. */\nexport type OrientationRendererInput = {\n paths: BasouPaths;\n /** ISO timestamp embedded in the header AND used as \"now\" for freshness + suspect classification. */\n nowIso: string;\n onWarning?: (warning: ReplayWarning, sessionId: string) => void;\n onSessionSkip?: (sessionId: string, reason: SessionSkipReason) => void;\n onTaskSkip?: (taskId: string, reason: TaskSkipReason) => void;\n /** Maximum related_files entries to display before `... +N more`. Default 10. */\n relatedFilesLimit?: number;\n /**\n * Result of a read-only dry-run staleness probe (sessions a `basou refresh`\n * would add or update), computed by the CLI which holds the import context.\n * Drives the plain \"これは最新か\" verdict. `null` / omitted = not probed, so\n * the verdict says it cannot confirm freshness rather than claiming current.\n */\n staleness?: {\n newSessions: number;\n updatedSessions: number;\n unverifiableSessions?: number;\n } | null;\n /**\n * Append the raw freshness telemetry (ISO timestamp, per-source counts, source\n * roots, suspect count) under the plain verdict. Off by default so the section\n * reads as a verdict for a supervisor, not developer diagnostics.\n */\n verbose?: boolean;\n /**\n * Additional trail stores to MERGE into this orientation, each a local path\n * (an SSHFS mount / rsync mirror of another host's `.basou`) tagged with a\n * host label. Absent / empty = local-only (byte-identical to before). basou\n * performs no network I/O; the operator's existing tooling places these paths.\n */\n federatedRoots?: FederatedRoot[];\n /**\n * Called when a federated (non-local) host root is present but cannot be\n * enumerated (e.g. an unreadable mount). That host is skipped; the local\n * store and other hosts still render. An absent root path is silently empty.\n */\n onHostUnavailable?: (host: string, error: unknown) => void;\n};\n\nexport type OrientationRendererResult = {\n /** Generated body. orientation.md is overwritten whole (no markers, gitignored). */\n body: string;\n sessionCount: number;\n pendingApprovalsCount: number;\n suspectCount: number;\n /** Tasks whose status is `planned` or `in_progress`. */\n inFlightTaskCount: number;\n decisionCount: number;\n /** Open (non-voided) `kind: \"track\"` decisions surfaced as strategic continuation. */\n openTrackCount: number;\n};\n\ntype DecisionRecord = {\n decisionId: string;\n title: string;\n occurredAt: string;\n sessionId: string;\n host: string | null;\n};\n\n/**\n * An open (non-voided) decision recorded with `kind: \"track\"` — a strategic,\n * unfinished direction the forward section resurfaces every session until it is\n * closed via `decision void` / supersede. Carries the rationale (the WHY) so the\n * surfaced track answers not just \"what to build next\" but \"and why\", which is\n * exactly the intent that otherwise lives only in the conversation.\n */\ntype TrackRecord = {\n decisionId: string;\n title: string;\n rationale: string | null;\n occurredAt: string;\n sessionId: string;\n host: string | null;\n};\n\ntype NoteRecord = { body: string; sessionId: string; occurredAt: string; host: string | null };\n\ntype PendingApproval = {\n id: string;\n risk: string;\n kind: string;\n reason: string;\n sessionId: string;\n createdAt: string;\n expired: boolean;\n};\n\ntype InFlightTask = { id: string; title: string; status: string; linkedSessions: number };\ntype PlannedTask = { id: string; title: string };\ntype SuspectSession = {\n sessionId: string;\n status: string;\n reason: SuspectReason | null;\n host: string | null;\n};\ntype LatestSession = {\n sessionId: string;\n label: string | null;\n status: string;\n host: string | null;\n};\ntype SourceCount = { kind: string; count: number };\n\n/**\n * The vendor-neutral, serializable structured summary behind orientation. This\n * is the single source of the four orientation questions (where am I now / what\n * is in flight / where am I heading / is this current). {@link renderOrientation}\n * formats it into markdown; programmatic consumers (e.g. a multi-workspace\n * portfolio view) read it directly without parsing prose.\n *\n * It carries STRUCTURED FACTS only — the pending-approval list with risk/reason,\n * suspect sessions, in-flight task linkage, capture freshness/coverage, the\n * latest decision. It deliberately holds NO work-stats (volume / active time /\n * tokens) and NO per-agent scorecards, productivity, or utilization metrics:\n * orientation shows product state, not surveillance of the fleet.\n */\nexport type OrientationSummary = {\n /** ISO \"now\"; the header timestamp and the basis for freshness/suspect classification. */\n generatedAt: string;\n /** All captured sessions (archived included), matching the count line. */\n sessionCount: number;\n /** Newest non-archived, non-import session (\"where am I now\"); null when none. */\n latestSession: LatestSession | null;\n /** Most recent `decision_recorded` across all sessions; null when none. */\n latestDecision: DecisionRecord | null;\n decisionCount: number;\n /**\n * Open (non-voided) `kind: \"track\"` decisions — strategic, unfinished\n * directions that the forward section (\"どこへ向かう\") resurfaces every session\n * until they are closed with `decision void` / supersede. Newest first. This\n * is the intent-continuity layer: distinct from the single latest decision\n * (point-in-time) and the recorded next step (`note`), an open track keeps\n * carrying \"the next essential thing to build, and why\" across sessions so it\n * does not sink into the flat decision list. Empty when none are open.\n */\n openTracks: TrackRecord[];\n /**\n * Most recent `note_added` over non-archived sessions — the recorded next\n * step / handoff (\"次の起点\") surfaced in the forward section; null when none.\n */\n latestNote: NoteRecord | null;\n /**\n * related_files of the latest session, deduped + sorted + capped at the\n * display limit. `outOfRoot` lists the entries (over the FULL deduped set,\n * not just `displayed`) that resolve OUTSIDE the project's `source_roots` — a\n * cross-project boundary crossing worth flagging so a resuming agent does not\n * mistake another repo's edits for this project's work. Empty unless the\n * latest session is local (a federated host's source_roots are not loaded\n * here) and confidently has out-of-root edits.\n */\n relatedFiles: { displayed: string[]; overflow: number; outOfRoot: string[] };\n /** Tasks whose status is `planned` or `in_progress`. */\n inFlightTasks: InFlightTask[];\n /** Tasks whose status is `planned` (\"where am I heading\"). */\n plannedTasks: PlannedTask[];\n pendingApprovals: PendingApproval[];\n suspects: SuspectSession[];\n /**\n * Distinct non-local host labels present in the merged set (sorted). Empty\n * for a local-only orientation. Lets a consumer render the multi-host banner\n * and the local-only-freshness caveat without re-deriving from sessions.\n */\n hosts: string[];\n freshness: {\n /** started_at of the newest non-archived session, or null when none captured. */\n newestStartedAt: string | null;\n /** source.kind of the newest non-archived session, or null when none captured. */\n newestSource: string | null;\n /**\n * Tail of captured activity over non-archived sessions = max of each\n * session's boundary (`ended_at` ?? `started_at`) and every captured event's\n * `occurred_at`. Folding event times covers a live session whose `ended_at`\n * is not yet written. Used to flag a latest-recorded decision that trails\n * real activity; null when no non-archived sessions exist.\n */\n latestActivityAt: string | null;\n /** Session counts per source kind, sorted by kind. Counts only — never volume/time. */\n bySource: SourceCount[];\n /** manifest `import.source_roots`, or null when single-root / unreadable. */\n sourceRoots: string[] | null;\n };\n};\n\n/**\n * Gather the structured orientation facts for a workspace. Read-only and runs\n * NO imports: freshness reflects already-captured state, so a stale capture is\n * visible rather than silently refreshed (run `basou refresh` to re-import).\n *\n * Returns a fully serializable {@link OrientationSummary}. See its docstring for\n * the positioning constraint (no work-stats, no surveillance metrics).\n */\nexport async function summarizeOrientation(\n input: OrientationRendererInput,\n): Promise<OrientationSummary> {\n const limit = input.relatedFilesLimit ?? 10;\n const now = new Date(input.nowIso);\n\n // `exactOptionalPropertyTypes` forbids passing literal `undefined`, so build\n // the options object conditionally (mirrors the handoff renderer).\n const loadOpts: Parameters<typeof loadSessionEntries>[1] = { now };\n if (input.onSessionSkip !== undefined) loadOpts.onSkip = input.onSessionSkip;\n if (input.onWarning !== undefined) loadOpts.onWarning = input.onWarning;\n const entries =\n input.federatedRoots !== undefined && input.federatedRoots.length > 0\n ? await loadFederatedSessionEntries(\n [{ paths: input.paths, host: null }, ...input.federatedRoots],\n {\n ...loadOpts,\n ...(input.onHostUnavailable !== undefined\n ? { onRootUnavailable: input.onHostUnavailable }\n : {}),\n },\n )\n : await loadSessionEntries(input.paths, loadOpts);\n\n // One replay pass per session yields three facts:\n // - `decisions`: chronological `decision_recorded` across ALL sessions.\n // - `latestNote`: the most recent `note_added` over NON-archived sessions —\n // the operator's recorded next step / handoff (\"次の起点\"), surfaced in the\n // forward section so a free-text resume hint survives into the next session.\n // - `latestActivityAt`: the tail of captured activity over NON-archived\n // sessions = max of the session boundary (ended_at ?? started_at) AND every\n // event's occurred_at. Folding event times (not just ended_at) is what makes\n // the trailing-decision note fire for a LIVE session: a running session has\n // no ended_at yet, but its post-decision events (more commands, notes, task\n // attaches via `decision record --session`) are already captured. Without\n // this, a mid-session decision in an ongoing long session — the exact case\n // the note targets — would be silently treated as current (a false-clear).\n // The population is intentionally asymmetric: decisions span archived\n // sessions (a past decision still answers \"what did I last decide\"), while\n // the activity tail and latest note are non-archived only (they answer \"is\n // there newer work\" / \"where do I resume\").\n const decisions: DecisionRecord[] = [];\n // Decisions recorded with `kind: \"track\"` (a strategic, unfinished direction).\n // Collected across the same pass; the open subset (minus voided) is surfaced\n // in the forward section and resurfaces until closed.\n const tracks: TrackRecord[] = [];\n // decision_ids marked no longer in force by a `decision_voided` event; the\n // \"latest decision\" pointer skips them so a voided decision is never\n // surfaced as the current direction.\n const voidedDecisionIds = new Set<string>();\n let latestActivityAt: string | null = null;\n let latestNote: NoteRecord | null = null;\n const noteActivity = (iso: string): void => {\n if (latestActivityAt === null || Date.parse(iso) > Date.parse(latestActivityAt)) {\n latestActivityAt = iso;\n }\n };\n for (const entry of entries) {\n const sessionDir = join(entry.sourceRoot.sessions, entry.sessionId);\n const counted = entry.session.session.status !== \"archived\";\n // Seed with the session boundary so a session whose events are empty or\n // unreadable still contributes its known activity window.\n if (counted) noteActivity(entry.session.session.ended_at ?? entry.session.session.started_at);\n try {\n for await (const ev of replayEvents(sessionDir, {\n onWarning: (w) => input.onWarning?.(w, entry.sessionId),\n })) {\n if (ev.type === \"decision_recorded\") {\n decisions.push({\n decisionId: ev.decision_id,\n title: ev.title,\n occurredAt: ev.occurred_at,\n sessionId: entry.sessionId,\n host: entry.host,\n });\n // Tracks (kind === \"track\") are an unfinished direction; collect them\n // separately with their rationale so the forward section can resurface\n // them until closed. A void recorded later removes the id from the open\n // set (resolved below, after the full scan, so a void seen before its\n // target decision still applies).\n if (ev.kind === \"track\") {\n tracks.push({\n decisionId: ev.decision_id,\n title: ev.title,\n rationale: ev.rationale ?? null,\n occurredAt: ev.occurred_at,\n sessionId: entry.sessionId,\n host: entry.host,\n });\n }\n } else if (ev.type === \"decision_voided\") {\n voidedDecisionIds.add(ev.decision_id);\n }\n // Only `next_step`-kind notes (from `basou note`) are resume hints; a\n // plain `basou session note` annotation (kind absent) is not surfaced.\n if (counted && ev.type === \"note_added\" && ev.kind === \"next_step\") {\n if (\n latestNote === null ||\n Date.parse(ev.occurred_at) > Date.parse(latestNote.occurredAt)\n ) {\n latestNote = {\n body: ev.body,\n sessionId: entry.sessionId,\n occurredAt: ev.occurred_at,\n host: entry.host,\n };\n }\n }\n if (counted) noteActivity(ev.occurred_at);\n }\n } catch {\n input.onSessionSkip?.(entry.sessionId, \"events_jsonl_unreadable\");\n }\n }\n decisions.sort((a, b) => {\n const c = Date.parse(a.occurredAt) - Date.parse(b.occurredAt);\n return c !== 0 ? c : a.decisionId.localeCompare(b.decisionId);\n });\n // The latest-decision pointer is the newest decision NOT voided — a voided\n // decision must not be presented as the current direction. decisions.md still\n // lists it (struck) for the audit trail.\n let latestDecision: DecisionRecord | undefined;\n for (let i = decisions.length - 1; i >= 0; i -= 1) {\n const d = decisions[i];\n if (d !== undefined && !voidedDecisionIds.has(d.decisionId)) {\n latestDecision = d;\n break;\n }\n }\n\n // Open tracks: every `kind: \"track\"` decision not yet voided/superseded, newest\n // first (most recent strategic direction leads). These resurface in the forward\n // section every session until explicitly closed — the durable intent layer.\n const openTracks: TrackRecord[] = tracks\n .filter((t) => !voidedDecisionIds.has(t.decisionId))\n .sort((a, b) => {\n const c = Date.parse(b.occurredAt) - Date.parse(a.occurredAt);\n return c !== 0 ? c : b.decisionId.localeCompare(a.decisionId);\n });\n\n // Tasks: in-flight (planned / in_progress) carry the cross-session linkage\n // that a flat transcript scan cannot reconstruct.\n const taskLoadOpts: Parameters<typeof loadTaskEntries>[1] = {};\n if (input.onTaskSkip !== undefined) taskLoadOpts.onSkip = input.onTaskSkip;\n const taskEntries = await loadTaskEntries(input.paths, taskLoadOpts);\n const inFlightTasks: InFlightTask[] = taskEntries\n .filter((t) => t.task.task.status === \"in_progress\" || t.task.task.status === \"planned\")\n .map((t) => ({\n id: t.task.task.id,\n title: t.task.task.title,\n status: t.task.task.status,\n linkedSessions: t.task.task.linked_sessions?.length ?? 0,\n }));\n const plannedTasks: PlannedTask[] = taskEntries\n .filter((t) => t.task.task.status === \"planned\")\n .map((t) => ({ id: t.task.task.id, title: t.task.task.title }));\n\n // Pending approvals: enumerateApprovals returns IDs only, so each pending id\n // is read via loadApproval to surface risk / action / reason (handoff shows\n // only a count). A null load (race / removed mid-read) is skipped.\n const { pending: pendingIds } = await enumerateApprovals(input.paths);\n const pendingApprovals: PendingApproval[] = [];\n for (const id of [...pendingIds].sort()) {\n const loaded = await loadApproval(input.paths, id);\n if (loaded === null) continue;\n const a = loaded.approval;\n pendingApprovals.push({\n id,\n risk: a.risk_level,\n kind: a.action.kind,\n reason: a.reason,\n sessionId: a.session_id,\n createdAt: a.created_at,\n expired: isLazyExpired(a, now),\n });\n }\n\n const suspects: SuspectSession[] = entries\n .filter((e) => e.suspect)\n .map((e) => ({\n sessionId: e.sessionId,\n status: e.session.session.status,\n reason: e.suspectReason,\n host: e.host,\n }));\n\n // \"where am I now\" latest session: exclude archived + cross-workspace round-trip\n // imports (`source.kind === \"import\"`), matching the handoff renderer.\n // claude-code-import / codex-import sessions ARE the operator's own captured\n // work, so they remain in scope.\n const liveEntries = entries.filter(\n (e) => e.session.session.status !== \"archived\" && e.session.session.source.kind !== \"import\",\n );\n // Represent \"最終 session\" with the most recent SUBSTANTIVE session, not a bare\n // resume/refresh session (e.g. 1 command, 0 files) that merely happens to be\n // newest — the latter hides the real-work session and makes 最終 session and\n // 直近の判断 disagree. Freshness (\"newest captured session\", below) still uses\n // pure recency, so the staleness signal stays honest.\n const latestEntry = pickLatestSubstantiveEntry(liveEntries);\n // `label` is `z.string().optional()` in the session schema — a parsed session\n // is `string | undefined`, never `null`. So `?? null` only maps `undefined`,\n // and the formatter's `label !== null && label !== \"\"` is byte-identical to\n // the original `label !== undefined && label !== \"\"` predicate.\n const latestSession: LatestSession | null =\n latestEntry !== undefined\n ? {\n sessionId: latestEntry.sessionId,\n label: latestEntry.session.session.label ?? null,\n status: latestEntry.session.session.status,\n host: latestEntry.host,\n }\n : null;\n\n // Freshness: newest started_at over all non-archived sessions (= most recent\n // captured activity). This is an honest staleness signal, NOT a completeness\n // claim — orientation runs no import, so what is not yet captured is not\n // counted here.\n const activityEntries = entries.filter((e) => e.session.session.status !== \"archived\");\n const newest = [...activityEntries].sort(\n (a, b) => Date.parse(b.session.session.started_at) - Date.parse(a.session.session.started_at),\n )[0];\n\n const bySourceMap = new Map<string, number>();\n for (const e of entries) {\n const k = e.session.session.source.kind;\n bySourceMap.set(k, (bySourceMap.get(k) ?? 0) + 1);\n }\n const bySource: SourceCount[] = [...bySourceMap.entries()]\n .sort((a, b) => a[0].localeCompare(b[0]))\n .map(([kind, count]) => ({ kind, count }));\n\n let sourceRoots: string[] | null = null;\n try {\n const manifest = await readManifest(input.paths);\n sourceRoots = manifest.import?.source_roots ?? null;\n } catch {\n // A missing / unreadable manifest leaves the source-roots line absent; the\n // CLI asserts the workspace is initialized before calling, so this is rare.\n sourceRoots = null;\n }\n\n const latestFiles = latestEntry?.session.session.related_files ?? [];\n const uniqueFiles = new Set(latestFiles);\n const sortedFiles = [...uniqueFiles].sort();\n const displayed = sortedFiles.slice(0, limit);\n const overflow = Math.max(0, uniqueFiles.size - limit);\n\n // Flag the files that resolve OUTSIDE this project's source_roots (a\n // cross-project boundary crossing). Classify the FULL file set, not just the\n // displayed slice, so an out-of-root file past the display cap is still\n // counted. Gated to projects that DECLARE source_roots (a multi-repo\n // workspace): a solo project's effective root is the whole repo, so there is\n // no declared boundary to cross and flagging would be noise. Scoped to a\n // LOCAL latest session — a federated host's source_roots are not loaded here,\n // so classifying its files against the local roots would cry wolf. Agent/tool\n // infra dirs count as in-root so routine plan / memory edits are not mistaken\n // for another project. dirname(.basou) is the repo root the source_roots\n // resolve against.\n let outOfRoot: string[] = [];\n if (\n latestEntry !== undefined &&\n latestEntry.host === null &&\n sortedFiles.length > 0 &&\n sourceRoots !== null &&\n sourceRoots.length > 0\n ) {\n try {\n const scope = await classifyFilesBySourceRoot({\n files: sortedFiles,\n workingDirectory: latestEntry.session.session.working_directory,\n sourceRoots,\n masterRoot: dirname(input.paths.root),\n extraInRoot: AGENT_INFRA_DIRS,\n });\n outOfRoot = scope.outOfRoot;\n } catch {\n // Classification is advisory only; never let it break orientation.\n outOfRoot = [];\n }\n }\n\n const hosts = [\n ...new Set(entries.map((e) => e.host).filter((h): h is string => h !== null)),\n ].sort();\n\n return {\n generatedAt: input.nowIso,\n sessionCount: entries.length,\n latestSession,\n latestDecision: latestDecision ?? null,\n decisionCount: decisions.length,\n openTracks,\n latestNote,\n relatedFiles: { displayed, overflow, outOfRoot },\n inFlightTasks,\n plannedTasks,\n pendingApprovals,\n suspects,\n hosts,\n freshness: {\n newestStartedAt: newest?.session.session.started_at ?? null,\n newestSource: newest?.session.session.source.kind ?? null,\n latestActivityAt,\n bySource,\n sourceRoots,\n },\n };\n}\n\n/**\n * Render `.basou/orientation.md`: a point-in-time \"current position\" view for a\n * supervisor who delegated execution to AI agents. Unlike `handoff.md` (a\n * session-resume narrative) this answers four orientation questions —\n * where am I now / what is in flight / where am I heading / is this current —\n * and deliberately leads with STRUCTURED FACTS an LLM cannot reliably derive\n * from raw transcripts (the\n * pending-approval list with risk/reason, suspect sessions, in-flight task\n * linkage, capture freshness/coverage) rather than prose synthesis.\n *\n * The renderer is read-only and runs NO imports: the freshness section reflects\n * already-captured state, so a stale capture is visible rather than silently\n * refreshed (use `basou refresh` to re-import). It must never emit per-agent\n * scorecards, productivity, or utilization metrics — orientation shows product\n * state, not surveillance of the fleet.\n *\n * Formatting only: the facts come from {@link summarizeOrientation}.\n */\nexport async function renderOrientation(\n input: OrientationRendererInput,\n): Promise<OrientationRendererResult> {\n const summary = await summarizeOrientation(input);\n return {\n body: formatOrientationBody(summary, {\n staleness: input.staleness ?? null,\n verbose: input.verbose === true,\n }),\n sessionCount: summary.sessionCount,\n pendingApprovalsCount: summary.pendingApprovals.length,\n suspectCount: summary.suspects.length,\n inFlightTaskCount: summary.inFlightTasks.length,\n decisionCount: summary.decisionCount,\n openTrackCount: summary.openTracks.length,\n };\n}\n\nfunction formatOrientationBody(\n summary: OrientationSummary,\n opts: {\n staleness: {\n newSessions: number;\n updatedSessions: number;\n unverifiableSessions?: number;\n } | null;\n verbose: boolean;\n },\n): string {\n const lines: string[] = [];\n const now = new Date(summary.generatedAt);\n const newestRel = relativeAge(summary.freshness.newestStartedAt ?? undefined, now);\n // Multi-host attribution suffix: only non-local rows carry it, so a\n // single-host (local-only) orientation is byte-identical to before.\n const hostSuffix = (h: string | null): string => (h !== null ? ` @${h}` : \"\");\n\n lines.push(\"# Orientation\");\n lines.push(\"\");\n lines.push(\n `> Generated at ${summary.generatedAt} · sessions ${summary.sessionCount} · newest ${newestRel} · pending ${summary.pendingApprovals.length} · suspect ${summary.suspects.length}`,\n );\n if (summary.hosts.length > 0) {\n lines.push(`> hosts: local, ${summary.hosts.join(\", \")}`);\n }\n lines.push(\"\");\n\n // \"where am I now\"\n lines.push(\"## 今どこにいる\");\n lines.push(\"\");\n if (summary.latestSession !== null) {\n const s = summary.latestSession;\n const sid = shortId(s.sessionId);\n if (s.label !== null && s.label !== \"\") {\n lines.push(`- 最終 session: ${s.label} (${s.status}) [${sid}]${hostSuffix(s.host)}`);\n } else {\n lines.push(`- 最終 session: ${sid} (${s.status})${hostSuffix(s.host)}`);\n }\n } else {\n lines.push(\"- 最終 session: (no live sessions)\");\n }\n if (summary.latestDecision !== null) {\n const dec = summary.latestDecision;\n const decAge = relativeAgeJa(dec.occurredAt, now);\n lines.push(\n `- 直近の判断: ${dec.title} [${shortId(dec.decisionId)}] (${decAge})${hostSuffix(dec.host)}`,\n );\n // Honesty over recency theater: this is the latest *recorded* decision, not\n // necessarily the latest decision. When captured activity continued well\n // past it, the operator's current direction may simply be unrecorded\n // (conversational decisions are not auto-captured), so note the gap rather\n // than presenting a stale decision as the current direction. The wording\n // states only what is certain — the decision predates the latest activity —\n // and does not assert that decisions were made in between, so it stays\n // honest whether the later activity is in the same session or another.\n const activityAt = summary.freshness.latestActivityAt;\n if (activityAt !== null && isTrailingStale(activityAt, dec.occurredAt)) {\n lines.push(\n ` - 注: これは最後に「記録された」判断です。最終活動 (${relativeAgeJa(activityAt, now)}) はこれより後のため、現在の方針が反映されていない可能性があります(会話での意思決定は自動記録されません。\\`basou decision capture\\` でこの session の判断を記録できます)。`,\n );\n }\n // When the latest recorded decision comes from a DIFFERENT session than the\n // representative latest session, the two \"latest\" pointers disagree. Say so,\n // so a resume reader does not treat an older thread's decision as this\n // session's direction (a linear-timeline cue, not a stale claim).\n if (summary.latestSession !== null && dec.sessionId !== summary.latestSession.sessionId) {\n lines.push(\n ` - 注: この判断は最終 session とは別の session [${shortId(dec.sessionId)}] のものです。`,\n );\n }\n if (summary.decisionCount > 1) {\n lines.push(` - ${summary.decisionCount} decisions total — see decisions.md`);\n }\n } else {\n lines.push(\"- 直近の判断: (no decisions recorded yet; capture with `basou decision capture`)\");\n }\n if (summary.relatedFiles.displayed.length > 0) {\n const shown = summary.relatedFiles.displayed.join(\", \");\n const more =\n summary.relatedFiles.overflow > 0 ? ` (... +${summary.relatedFiles.overflow} more)` : \"\";\n lines.push(`- 直近の変更ファイル: ${shown}${more}`);\n if (summary.relatedFiles.outOfRoot.length > 0) {\n // Cross-project boundary crossing: the latest session edited files\n // outside this project's source_roots. Flag it so a resuming agent does\n // not adopt another repo's work as this project's continuation. The count\n // reflects ALL out-of-root files; the listed paths are capped like the\n // line above.\n const OUT_OF_ROOT_DISPLAY = 10;\n const out = summary.relatedFiles.outOfRoot;\n const shownOut = out.slice(0, OUT_OF_ROOT_DISPLAY).join(\", \");\n const outMore =\n out.length > OUT_OF_ROOT_DISPLAY ? ` (... +${out.length - OUT_OF_ROOT_DISPLAY} more)` : \"\";\n lines.push(\n ` - ⚠ source_roots 外 ${out.length} 件 (別プロジェクトの可能性): ${shownOut}${outMore}`,\n );\n }\n } else {\n lines.push(\"- 直近の変更ファイル: (none recorded)\");\n }\n lines.push(\"\");\n\n // \"what is in flight\" — structured facts\n lines.push(\"## 何が動く\");\n lines.push(\"\");\n lines.push(`### 進行中 task (${summary.inFlightTasks.length})`);\n if (summary.inFlightTasks.length === 0) {\n lines.push(\"- (none)\");\n } else {\n for (const t of summary.inFlightTasks) {\n const linkedSuffix = t.linkedSessions > 1 ? ` — linked_sessions: ${t.linkedSessions}` : \"\";\n lines.push(`- ${t.title} (${t.status}) [${shortId(t.id)}]${linkedSuffix}`);\n }\n }\n lines.push(\"\");\n lines.push(`### 承認待ち (${summary.pendingApprovals.length})`);\n if (summary.pendingApprovals.length === 0) {\n lines.push(\"- (none)\");\n } else {\n for (const a of summary.pendingApprovals) {\n const expired = a.expired ? \" (expired)\" : \"\";\n lines.push(\n `- [${a.risk}] ${a.kind}: ${a.reason} — session ${shortId(a.sessionId)}, since ${a.createdAt}${expired}`,\n );\n }\n }\n lines.push(\"\");\n lines.push(`### 要注意 session (${summary.suspects.length})`);\n if (summary.suspects.length === 0) {\n lines.push(\"- (none)\");\n } else {\n for (const e of summary.suspects) {\n lines.push(\n `- ${shortId(e.sessionId)} (${e.status}) — ${suspectText(e.reason)}${hostSuffix(e.host)}`,\n );\n }\n }\n lines.push(\"\");\n\n // \"where am I heading\"\n lines.push(\"## どこへ向かう\");\n lines.push(\"\");\n // Open tracks lead the forward section: a strategic, unfinished direction\n // (\"the next essential thing to build, and why\") is the most important thing to\n // carry across a session boundary, and it resurfaces here every time until\n // explicitly closed. Distinct from the recorded next step (a terminal `note`)\n // and from in-flight tasks (mechanical). This is the intent-continuity layer —\n // without it an agreed direction sinks into the flat decision list and the next\n // session never sees it (the failure this section exists to prevent).\n if (summary.openTracks.length > 0) {\n const TRACK_DISPLAY_LIMIT = 10;\n const shownTracks = summary.openTracks.slice(0, TRACK_DISPLAY_LIMIT);\n const trackOverflow = summary.openTracks.length - shownTracks.length;\n lines.push(`### 未完トラック (close まで継続表示) (${summary.openTracks.length})`);\n for (const t of shownTracks) {\n const trackAge = relativeAgeJa(t.occurredAt, now);\n lines.push(`- ${t.title} [${shortId(t.decisionId)}] (${trackAge})${hostSuffix(t.host)}`);\n if (t.rationale !== null && t.rationale.trim() !== \"\") {\n lines.push(` - 理由: ${trackRationale(t.rationale)}`);\n }\n }\n if (trackOverflow > 0) {\n lines.push(`- ... +${trackOverflow} more (see decisions.md)`);\n }\n // Section-scoped close instruction: a top-level line (not an indented sub-\n // bullet) so it reads as guidance for the whole list, mirroring handoff.\n lines.push(\n \"完了したら `basou decision void <decision_id>` で閉じてください。閉じるまで毎回ここに表示されます。\",\n );\n lines.push(\"\");\n }\n // The recorded next step (a `basou note`) is the operator's explicit resume\n // hint; surface it first so a free-text handoff survives into the next session\n // rather than living only in a decision title or an external memory file.\n if (summary.latestNote !== null) {\n const noteAge = relativeAgeJa(summary.latestNote.occurredAt, now);\n lines.push(\n `- 次の起点 (記録済み, ${noteAge}): ${noteSummary(summary.latestNote.body)} [session ${shortId(summary.latestNote.sessionId)}]${hostSuffix(summary.latestNote.host)}`,\n );\n // Same honesty guard as the latest decision: if captured activity continued\n // well past when this resume hint was recorded, the work may have moved on,\n // so flag it rather than presenting a stale starting point as current.\n const activityAt = summary.freshness.latestActivityAt;\n if (activityAt !== null && isTrailingStale(activityAt, summary.latestNote.occurredAt)) {\n lines.push(\n ` - 注: この起点の記録後 (最終活動 ${relativeAgeJa(activityAt, now)}) も作業が続いています。再開点が古い可能性があります。`,\n );\n }\n }\n for (const t of summary.plannedTasks) {\n lines.push(`- ${t.title} [${shortId(t.id)}]`);\n }\n // Fall back to the decision hint only when there is no open track, no recorded\n // next step, and no planned task — otherwise the section already says where to\n // go (an open track is the strongest such signal).\n if (\n summary.openTracks.length === 0 &&\n summary.latestNote === null &&\n summary.plannedTasks.length === 0\n ) {\n const dec = summary.latestDecision;\n if (dec === null) {\n lines.push(\"- (no planned tasks or recorded next step yet)\");\n } else if (isTrailingStale(summary.freshness.latestActivityAt, dec.occurredAt)) {\n // The misfire guard: do NOT present a STALE decision as direction. Activity\n // continued well after it, so it may already be resolved/executed; an agent\n // that treats it as the next task can re-attempt completed work. Ask for the\n // continuation point instead, and demote the decision to a labelled\n // reference rather than an instruction (aligns the forward section with the\n // staleness warning already shown on the 直近の判断 line above).\n lines.push(\n \"- (no planned tasks or recorded next step — 最終活動は直近の判断より後です。継続点をユーザに確認してください)\",\n );\n lines.push(` - 参考 (古い可能性・方針ではない): ${dec.title}`);\n } else {\n lines.push(\"- (no planned tasks — direction is inferred from recent decisions)\");\n lines.push(` - 直近の判断: ${dec.title}`);\n }\n // Discoverability nudge: fires when there ARE recorded decisions but none give\n // a durable forward direction (latest is stale, or just point-in-time) — the\n // moment a strategic direction is most likely sitting only in conversation.\n // Point the agent at tracks so the next agreed direction is captured durably\n // instead of leaking again. Suppressed for a pristine workspace (no decisions\n // yet) so it is a hint at the right time, not noise, and never shown when an\n // open track / note / planned task already gives direction.\n if (dec !== null) {\n lines.push(\n ' - 次に作るべき本質的な方向性が定まったら `basou decision capture` (`\"kind\":\"track\"`) / `basou decision record --track` で track 化すると、close まで毎 session ここに継続表示されます。',\n );\n }\n }\n lines.push(\"\");\n\n // \"is this current\" — a plain verdict for a supervisor, not telemetry: is what\n // I am looking at the latest and complete, and if not, what should I do? Raw\n // ISO / per-source counts / source roots / a zero suspect count are diagnostics\n // and move under `--verbose`.\n lines.push(\"## これは最新か\");\n lines.push(\"\");\n for (const line of freshnessVerdict(summary, opts.staleness, now)) lines.push(line);\n // The verdict above reflects the LOCAL store only (the dry-run probe reads\n // this machine's native logs). With federated hosts merged in, do not let it\n // imply the whole multi-host view is current — the other hosts' freshness is\n // unknowable here (their native logs are not on this machine).\n if (summary.hosts.length > 0) {\n lines.push(\"\");\n lines.push(\n \"注: 鮮度判定はこのマシンのローカルストアのみが対象です。他ホストの取りこぼしは判定できません(各ホストで basou refresh を実行し同期してください)。\",\n );\n }\n\n if (opts.verbose) {\n lines.push(\"\");\n lines.push(\"<!-- verbose: raw freshness telemetry -->\");\n if (summary.freshness.newestStartedAt !== null) {\n lines.push(`- newest captured session: ${summary.freshness.newestStartedAt} (${newestRel})`);\n } else {\n lines.push(\"- newest captured session: (no sessions captured yet)\");\n }\n if (summary.freshness.latestActivityAt !== null) {\n lines.push(\n `- latest activity: ${summary.freshness.latestActivityAt} (${relativeAge(summary.freshness.latestActivityAt, now)})`,\n );\n }\n const sourceBreakdown = summary.freshness.bySource\n .map(({ kind, count }) => `${kind} ${count}`)\n .join(\", \");\n lines.push(\n `- sessions: ${summary.sessionCount}${sourceBreakdown !== \"\" ? ` (${sourceBreakdown})` : \"\"}`,\n );\n if (summary.freshness.sourceRoots !== null && summary.freshness.sourceRoots.length > 0) {\n lines.push(`- source roots: ${summary.freshness.sourceRoots.join(\", \")}`);\n } else {\n lines.push(\"- source roots: (single root)\");\n }\n lines.push(`- suspect sessions: ${summary.suspects.length}`);\n const probe =\n opts.staleness === null\n ? \"not run\"\n : `new ${opts.staleness.newSessions}, updated ${opts.staleness.updatedSessions}, unverifiable ${opts.staleness.unverifiableSessions ?? 0}`;\n lines.push(`- staleness probe: ${probe}`);\n }\n\n return lines.join(\"\\n\");\n}\n\n/**\n * Translate an internal source kind into the tool name a supervisor recognizes.\n * Unknown kinds pass through verbatim so a new adapter is never silently mislabeled.\n */\nfunction toolDisplayName(kind: string | null): string {\n switch (kind) {\n case \"claude-code-import\":\n case \"claude-code-adapter\":\n return \"Claude Code\";\n case \"codex-import\":\n return \"Codex\";\n case \"terminal\":\n return \"ターミナル\";\n case \"human\":\n return \"手動メモ\";\n case \"import\":\n return \"他ワークスペース\";\n default:\n return kind ?? \"不明\";\n }\n}\n\n/**\n * The plain \"これは最新か\" verdict: a status line plus one human sentence that\n * answers \"is this current, and if not what do I do?\". Freshness comes from the\n * dry-run `staleness` probe (uncaptured/grown native work); when it was not run\n * the verdict says so instead of claiming current. A non-zero suspect count is\n * surfaced as a caution even when the capture is fresh.\n */\nfunction freshnessVerdict(\n summary: OrientationSummary,\n staleness: { newSessions: number; updatedSessions: number; unverifiableSessions?: number } | null,\n now: Date,\n): string[] {\n // Unverifiable wins absolutely first: a source that GREW but could not be\n // re-imported safely (broken chain / unreadable / non-append) means the\n // capture is provably behind AND a plain `basou refresh` would skip it again.\n // Claiming \"current\" here is the false-clear this verdict exists to prevent,\n // so it is surfaced ahead of every other state, including \"no records\".\n if (staleness !== null && (staleness.unverifiableSessions ?? 0) > 0) {\n return [\n `⚠️ 最新か確認できません。変化したが安全に取り込めないセッションが ${staleness.unverifiableSessions} 件あります(ハッシュチェーン破損・非追記変更など)。`,\n \"`basou verify` で確認し、`basou refresh --force` で再取り込みしてください。\",\n ];\n }\n\n // Stale wins next: uncaptured/grown native work means there IS work to pull\n // in, even when the store itself is still empty — so this must be checked\n // before the \"no records\" branch.\n if (staleness !== null && (staleness.newSessions > 0 || staleness.updatedSessions > 0)) {\n const parts: string[] = [];\n if (staleness.newSessions > 0) parts.push(`新規 ${staleness.newSessions} 件`);\n if (staleness.updatedSessions > 0) parts.push(`更新 ${staleness.updatedSessions} 件`);\n return [\n `⚠️ 古いかもしれません。最後の取り込み以降に未取り込みの作業があります(${parts.join(\"・\")})。`,\n \"`basou refresh` で更新してください。\",\n ];\n }\n\n if (summary.freshness.newestStartedAt === null) {\n return [\n \"ℹ️ まだ記録がありません。\",\n \"このワークスペースで作業すると、ここに現在地が表示されます。\",\n ];\n }\n\n const rel = relativeAgeJa(summary.freshness.newestStartedAt, now);\n const tool = toolDisplayName(summary.freshness.newestSource);\n const suspectCount = summary.suspects.length;\n\n if (staleness === null) {\n return [\n `ℹ️ 取り込み済みの状態を表示しています。最後の作業は ${rel}(${tool})。`,\n \"最新か確認するには `basou refresh` を実行してください。\",\n ];\n }\n\n // The probe ran and found no uncaptured/grown native sessions, so the IMPORT is\n // current. Scope the claim to exactly that — the old \"取りこぼし・要注意なし\"\n // (no omissions / nothing to worry about) overclaimed: this verdict only checks\n // that captured native sessions are imported and none are suspect. It does NOT\n // (and from telemetry alone cannot) detect planning/implementation drift or\n // unrecorded decisions, so it must not imply provenance is comprehensive.\n // Federated views merge other hosts' sessions, but this verdict is driven by\n // a LOCAL dry-run probe (the remote hosts' native logs are not on this\n // machine). Scope the green claim to THIS host so it never reads as \"the whole\n // multi-host view is current\" — the local-only-freshness caveat below adds the\n // per-host sync guidance. Local-only views keep the original wording.\n const localScope = summary.hosts.length > 0 ? \"このホスト(ローカル)の\" : \"\";\n const lines = [\n `✅ ${localScope}取り込みは最新です。最後の作業は ${rel}(${tool})。未取り込みの native セッションはありません。`,\n ];\n if (suspectCount > 0) {\n lines.push(`ただし要注意セッションが ${suspectCount} 件あります(上記「要注意 session」参照)。`);\n }\n lines.push(\n \"注: この判定は取り込み済み native セッションの鮮度と suspect の有無だけを見ます。計画↔実装のドリフトや未記録の意思決定までは検知しません。\",\n );\n return lines;\n}\n\n/** Japanese relative age, e.g. \"7時間26分前\" / \"3日前\" / \"たった今\", for the verdict line. */\nfunction relativeAgeJa(startedAt: string | null, now: Date): string {\n if (startedAt === null) return \"(不明)\";\n const ms = now.getTime() - Date.parse(startedAt);\n if (!Number.isFinite(ms) || ms < 0) return \"たった今\";\n if (ms < 60_000) return \"たった今\";\n const totalMin = Math.floor(ms / 60_000);\n const days = Math.floor(totalMin / 1440);\n const hours = Math.floor((totalMin % 1440) / 60);\n const mins = totalMin % 60;\n if (days > 0) return hours > 0 ? `${days}日${hours}時間前` : `${days}日前`;\n if (hours > 0) return mins > 0 ? `${hours}時間${mins}分前` : `${hours}時間前`;\n return `${mins}分前`;\n}\n\n/** \"3h 05m ago\" / \"just now\" / \"(unknown)\" for a session's age relative to `now`. */\nfunction relativeAge(startedAt: string | undefined, now: Date): string {\n if (startedAt === undefined) return \"(unknown)\";\n const ms = now.getTime() - Date.parse(startedAt);\n if (!Number.isFinite(ms)) return \"(unknown)\";\n if (ms < 0) return \"just now\";\n if (ms < 1000) return \"just now\";\n return `${formatDurationMs(ms)} ago`;\n}\n\n// A recorded note can be multi-line and arbitrarily long; collapse whitespace\n// to keep it on one orientation bullet and cap it so a verbose handoff does not\n// dominate the view. The full body is preserved in the event (see session show).\nconst NOTE_SUMMARY_MAX = 200;\nfunction noteSummary(body: string): string {\n const oneLine = body.replace(/\\s+/g, \" \").trim();\n return oneLine.length > NOTE_SUMMARY_MAX ? `${oneLine.slice(0, NOTE_SUMMARY_MAX - 1)}…` : oneLine;\n}\n\n// A track's rationale is the WHY behind the direction; like a note it can be\n// multi-line and long, so collapse whitespace to one line and cap it. The full\n// text is preserved in the decision_recorded event (see decisions.md).\nconst TRACK_RATIONALE_MAX = 240;\nfunction trackRationale(rationale: string): string {\n const oneLine = rationale.replace(/\\s+/g, \" \").trim();\n return oneLine.length > TRACK_RATIONALE_MAX\n ? `${oneLine.slice(0, TRACK_RATIONALE_MAX - 1)}…`\n : oneLine;\n}\n\nfunction suspectText(reason: SuspectReason | null): string {\n if (reason === \"events_say_ended_but_yaml_running\") return \"ended (yaml stale)\";\n if (reason === \"running_no_end_event\") return \"no end event\";\n return \"suspect\";\n}\n\n// Prose-line short id: keep the type prefix and truncate the ULID body to its\n// first 10 chars, e.g. `task_01KRNHYRS91F5GBX...` -> `task_01KRNHYRS9`.\nfunction shortId(id: string): string {\n const sep = id.indexOf(\"_\");\n if (sep === -1) return id.slice(0, 10);\n return id.slice(0, sep + 1) + id.slice(sep + 1, sep + 1 + 10);\n}\n","import { lstat } from \"node:fs/promises\";\nimport { type PrefixedId, prefixedUlid } from \"../ids/ulid.js\";\nimport { type Manifest, ManifestSchema } from \"../schemas/manifest.schema.js\";\nimport type { BasouPaths } from \"./basou-dir.js\";\nimport { readYamlFile, writeYamlFile } from \"./yaml-store.js\";\n\n/**\n * Inputs for {@link createManifest}. Optional fields drop out of the\n * resulting Manifest entirely (they are not emitted as `null`/`undefined`\n * in YAML); pass `null` for `repositoryUrl` to keep an explicit `null`.\n */\nexport type CreateManifestInput = {\n workspaceName: string;\n projectName?: string;\n projectDescription?: string;\n repositoryUrl?: string | null;\n /** Override for tests; defaults to `new Date()`. */\n now?: Date;\n /** Override for tests; defaults to a freshly generated `ws_<ULID>`. */\n workspaceId?: PrefixedId<\"ws\">;\n /**\n * Import source roots, each RELATIVE to the repository root (e.g. `\".\"`,\n * `\"../basou-workspace\"`). Persisted under `import.source_roots` so\n * `basou refresh` / `basou import` aggregate several sibling repos into one\n * `.basou/`. Validated by `ManifestSchema` (absolute paths are rejected).\n * Omitted from the manifest entirely when absent or empty.\n */\n sourceRoots?: string[];\n};\n\n/**\n * Build a fresh Manifest object that satisfies the manifest schema's\n * minimum shape. Performs no I/O. Returned object is parse-validated by\n * `ManifestSchema`.\n */\nexport function createManifest(input: CreateManifestInput): Manifest {\n if (input.workspaceName.length === 0) {\n throw new Error(\"Workspace name is empty. Pass --name explicitly.\");\n }\n const now = (input.now ?? new Date()).toISOString();\n const workspaceId = input.workspaceId ?? prefixedUlid(\"ws\");\n\n const project: Manifest[\"project\"] = {\n ...(input.projectName !== undefined ? { name: input.projectName } : {}),\n ...(input.projectDescription !== undefined ? { description: input.projectDescription } : {}),\n ...(input.repositoryUrl !== undefined ? { repository_url: input.repositoryUrl } : {}),\n };\n\n const manifest: Manifest = {\n schema_version: \"0.1.0\",\n basou_version: \"0.1.0\",\n workspace: {\n id: workspaceId,\n name: input.workspaceName,\n created_at: now,\n updated_at: now,\n },\n project,\n capabilities: {\n enabled: [\"core\", \"claude-code-adapter\", \"terminal-recording\", \"git-capability\", \"approval\"],\n },\n approval: {\n required_for: [\"destructive_command\", \"external_send\"],\n default_risk_level: \"medium\",\n },\n adapters: {\n \"claude-code\": { enabled: true },\n },\n git: { events_log: \"ignore\" },\n ...(input.sourceRoots !== undefined && input.sourceRoots.length > 0\n ? { import: { source_roots: input.sourceRoots } }\n : {}),\n };\n return ManifestSchema.parse(manifest);\n}\n\n/**\n * Write a Manifest to `paths.files.manifest`. Re-validates via\n * `ManifestSchema` before serialization.\n *\n * Refuses to overwrite an existing manifest unless `force: true`.\n */\nexport async function writeManifest(\n paths: BasouPaths,\n manifest: Manifest,\n options?: { force?: boolean },\n): Promise<void> {\n const force = options?.force === true;\n const validated = ManifestSchema.parse(manifest);\n\n if (!force) {\n let existed = false;\n try {\n await lstat(paths.files.manifest);\n existed = true;\n } catch (error: unknown) {\n if (!hasErrorCode(error) || error.code !== \"ENOENT\") {\n throw new Error(\"Failed to inspect existing manifest\", { cause: error });\n }\n }\n if (existed) {\n throw new Error(\"Already initialized. Use --force to overwrite.\");\n }\n }\n\n await writeYamlFile(paths.files.manifest, validated);\n}\n\n/**\n * Read and parse a Manifest from `paths.files.manifest`. Throws if the file\n * is missing or contents fail `ManifestSchema` validation.\n */\nexport async function readManifest(paths: BasouPaths): Promise<Manifest> {\n const raw = await readYamlFile(paths.files.manifest);\n return ManifestSchema.parse(raw);\n}\n\nfunction hasErrorCode(error: unknown): error is Error & { code: string } {\n if (!(error instanceof Error)) return false;\n return typeof (error as unknown as Record<string, unknown>).code === \"string\";\n}\n","import { z } from \"zod\";\nimport { IsoTimestampSchema, SchemaVersionSchema, WorkspaceIdSchema } from \"./shared.schema.js\";\n\nconst ProjectSchema = z.looseObject({\n name: z.string().optional(),\n description: z.string().optional(),\n repository_url: z.string().nullable().optional(),\n});\n\nconst CapabilitiesSchema = z.looseObject({\n enabled: z.array(z.string()),\n});\n\nconst ApprovalConfigSchema = z.looseObject({\n required_for: z.array(z.string()).optional(),\n default_risk_level: z.enum([\"low\", \"medium\", \"high\", \"critical\"]),\n});\n\nconst ClaudeCodeAdapterConfigSchema = z.looseObject({\n enabled: z.boolean(),\n config_path: z.string().optional(),\n});\n\nconst AdaptersSchema = z.looseObject({\n \"claude-code\": ClaudeCodeAdapterConfigSchema,\n});\n\nconst GitConfigSchema = z.looseObject({\n events_log: z.enum([\"ignore\", \"commit\"]).default(\"ignore\"),\n});\n\n/**\n * A source root is RELATIVE to the manifest's repository root (it is resolved\n * to an absolute path at import time). manifest.yaml is a commit candidate, so\n * absolute machine paths (`/Users/...`), home-expansion (`~`), and stray\n * backslashes are rejected to keep committed manifests path-clean and\n * machine-portable. A `..`-prefixed sibling (e.g. `../basou-workspace`) is\n * allowed.\n *\n * Encoded as a regex (not a Zod refinement) so the constraint is also emitted\n * into the published JSON Schema's `pattern`, letting cross-language validators\n * enforce the same rule. It rejects: a leading `~` (home), a leading `/` (POSIX\n * absolute), any backslash anywhere (UNC / Windows / stray), a `<drive>:`\n * prefix, and null bytes; `min(1)` rejects the empty string. It also rejects\n * leading/trailing whitespace: without the `(?!\\s)` and trailing `[^\\0\\\\\\s]`\n * guards a leading space would \"shield\" a forbidden first char (\" ~/x\" would\n * pass), and `basou project sync` normalizes (`.trim()`) before persisting, so\n * a padded path that passed at read time would fail re-validation on write —\n * and a padded `source_roots` entry resolves (path.resolve) to a missed repo\n * while sync wrongly reports it covered. Interior whitespace stays allowed\n * (`../my dir` is a legitimate directory name).\n */\nconst SOURCE_ROOT_PATTERN = /^(?![~/\\\\])(?![A-Za-z]:)(?!\\s)[^\\0\\\\]*[^\\0\\\\\\s]$/;\n\nconst SourceRootSchema = z.string().min(1).regex(SOURCE_ROOT_PATTERN, {\n message:\n \"source_roots entries must be relative paths (no absolute path, '~', '\\\\', or null byte)\",\n});\n\n/**\n * Optional import config. `source_roots` lets one `.basou/` aggregate the\n * native logs of several sibling repositories (each a path relative to the\n * repo root, e.g. `[\".\"`, `\"../basou\"]`). `basou refresh` / `basou import`\n * scan every listed root; the list is the complete set, so include `\".\"` to\n * keep the host repository itself. Absent => the host repository root only.\n */\nconst ImportConfigSchema = z.looseObject({\n source_roots: z.array(SourceRootSchema).min(1).optional(),\n});\n\n/**\n * A project's declared repo roster (the \"saddle\" model): the single source of\n * truth for which repos make up this project. The capture config\n * (`import.source_roots`) is reconciled against this list, and\n * `basou project check` reports drift between the two (e.g. a companion repo\n * wired into the workspace but never added to `source_roots`). Each `path` is\n * relative to the manifest repo root, reusing the machine-portable source-root\n * constraint. `visibility` is the repo's git visibility, `language` its source\n * (commit/comment/code) language, and `publishes` the surfaces it deploys, each\n * independent of the others. `visibility`, `language`, and `publishes` are all\n * optional so a roster can be adopted first and enriched incrementally.\n */\nconst RepoVisibilitySchema = z.enum([\"public\", \"private\", \"future-public\"]);\n\n/**\n * The audience-driven language axis, independent of visibility:\n * `en` / `ja` for a single audience, `en+ja` when both are served.\n */\nconst RepoLanguageSchema = z.enum([\"en\", \"ja\", \"en+ja\"]);\n\n/** A published surface kind: a deployed website or a package registry. */\nconst PublishKindSchema = z.enum([\"web\", \"npm\"]);\n\n/**\n * One published surface. Its `visibility` and `language` are independent of the\n * source repo's (a private repo commonly publishes a public site) and both are\n * optional so a surface can be declared before those facts are pinned down.\n * `kind` is required: a surface with no kind is meaningless.\n */\nconst PublishTargetSchema = z.looseObject({\n kind: PublishKindSchema,\n visibility: RepoVisibilitySchema.optional(),\n language: RepoLanguageSchema.optional(),\n});\n\nconst RepoEntrySchema = z.looseObject({\n path: SourceRootSchema,\n visibility: RepoVisibilitySchema.optional(),\n language: RepoLanguageSchema.optional(),\n publishes: z.array(PublishTargetSchema).optional(),\n});\n\nconst WorkspaceMetaSchema = z.looseObject({\n id: WorkspaceIdSchema,\n name: z.string().min(1),\n created_at: IsoTimestampSchema,\n updated_at: IsoTimestampSchema,\n /**\n * The generated workspace view: a throwaway directory that aggregates the\n * roster repos via symlinks (one `<repo-basename>` symlink per repo). A path\n * relative to the manifest root, reusing the machine-portable source-root\n * constraint. Absent for a solo project (no view needed); `basou project\n * workspace` reconciles the view's symlinks to the declared roster.\n */\n view: SourceRootSchema.optional(),\n});\n\n/**\n * Schema for `.basou/manifest.yaml`. The minimal manifest carries\n * schema_version, basou_version, workspace metadata, project info, enabled\n * capabilities, approval policy, adapter config, and git policy. The\n * `adapters.\"claude-code\"` key uses a hyphen; downstream code accesses it\n * via bracket notation.\n *\n * Every object here is `looseObject` (NOT the default strip), so unknown keys\n * at every level survive parse. The manifest is the declarative source of truth\n * and is git-tracked and read-modify-written by `basou project` commands; with\n * the default strip, a field this basou does not recognize — a newer version's\n * additive field, a future adapter under `adapters`, a hand-added key — would be\n * silently dropped on the next write. Preserving them keeps basou from destroying\n * config it does not understand (forward-compatible), while known fields are still\n * fully type-checked and validated. {@link unknownManifestKeys} surfaces the\n * unrecognized top-level keys so preservation is not silent.\n */\nexport const ManifestSchema = z.looseObject({\n schema_version: SchemaVersionSchema,\n basou_version: z.literal(\"0.1.0\"),\n workspace: WorkspaceMetaSchema,\n project: ProjectSchema,\n capabilities: CapabilitiesSchema,\n approval: ApprovalConfigSchema,\n adapters: AdaptersSchema,\n git: GitConfigSchema,\n import: ImportConfigSchema.optional(),\n repos: z.array(RepoEntrySchema).min(1).optional(),\n});\n\n/** Inferred runtime type for {@link ManifestSchema}. */\nexport type Manifest = z.infer<typeof ManifestSchema>;\n\n/** The declared top-level manifest keys, derived from the schema (no hardcoded drift). */\nconst KNOWN_TOP_LEVEL_KEYS: ReadonlySet<string> = new Set(Object.keys(ManifestSchema.shape));\n\n/**\n * The unrecognized TOP-LEVEL keys a parsed manifest carries — fields preserved by\n * the loose schema that this basou does not know. Returned sorted, for surfacing as\n * an advisory by the read-modify-write commands so preservation is not silent (a\n * newer version's section, or a hand-added/typo'd key, is flagged rather than\n * dropped). Nested unknown keys are preserved too but not enumerated here; this is\n * the high-signal top-level case. Read-only — never mutates.\n */\nexport function unknownManifestKeys(manifest: Manifest): string[] {\n return Object.keys(manifest)\n .filter((k) => !KNOWN_TOP_LEVEL_KEYS.has(k))\n .sort();\n}\n","/**\n * The single lexical relative-path normalizer shared by every `basou project`\n * command (roster drift, source-root reconcile, archive/rename matching, view +\n * symlink + preset dedup). It produces a canonical COMPARISON key — it is the\n * answer to \"do these two declared paths denote the same location?\", not a\n * validator (see `SOURCE_ROOT_PATTERN` in the manifest schema) and not an identity\n * resolver (see `realpathSync` for on-disk identity).\n *\n * It is string-pure (NO filesystem access): two paths must compare equal from\n * their spelling alone, so the manifest can be reasoned about without touching\n * disk. It:\n * - trims surrounding whitespace (declared paths carry no leading/trailing space);\n * - drops empty segments (collapsing `//` and a trailing `/`) and `.` segments;\n * - resolves a `..` against the preceding NORMAL segment, and otherwise keeps it\n * (a relative path may ascend: `../b`, `../../b` are preserved);\n * - preserves whitespace INSIDE a segment (a directory may legitimately be named\n * with spaces — `../my repo` stays `../my repo`), never collapsing it; and\n * - yields `.` for an empty / all-dot result.\n *\n * So `../b`, `../b/`, `../b/.`, `./../b`, and `a/../../b` all canonicalize to\n * `../b`, while `x/..` and `a/b/../..` canonicalize to `.`. Absolute input (which\n * declared paths never are) is normalized defensively, `..` above the root being\n * dropped.\n */\nexport function normalizeRelativePath(p: string): string {\n const trimmed = p.trim();\n // Absolute detection is on the trimmed string, so a malformed leading-\n // whitespace-then-slash input (` /a`) canonicalizes as absolute rather than\n // mis-resolving. This is defensive only: a declared/validated path is always\n // relative (SOURCE_ROOT_PATTERN forbids both leading whitespace and a leading\n // slash), so this branch is unreachable from any manifest value.\n const absolute = trimmed.startsWith(\"/\");\n const out: string[] = [];\n for (const seg of trimmed.split(\"/\")) {\n if (seg === \"\" || seg === \".\") continue;\n if (seg === \"..\") {\n const top = out[out.length - 1];\n if (top !== undefined && top !== \"..\") {\n out.pop(); // resolve against a preceding normal segment\n } else if (!absolute) {\n out.push(\"..\"); // a relative path may ascend; an absolute one cannot pass root\n }\n continue;\n }\n out.push(seg);\n }\n const joined = out.join(\"/\");\n if (absolute) return `/${joined}`;\n return joined.length === 0 ? \".\" : joined;\n}\n","/**\n * Archive (fold) a repo out of a project's declared roster — the inverse of\n * `adopt` + `sync`, and the first PRUNING step in the saddle model (every prior\n * slice was additive and deliberately deferred removal). When a repo has served\n * its purpose, archiving removes it from the declared `repos` roster and prunes\n * its capture entry from `source_roots`, so it is no longer part of the project\n * or scanned by `refresh`.\n *\n * Pure: it computes the manifest mutation from the DECLARED lists alone — no\n * filesystem or git I/O — so it works even when the repo is already gone from\n * disk (the common \"I deleted the repo, now clean basou\" case). Historical\n * captured data in the anchor is NOT touched (archiving stops future capture,\n * it does not erase the past). The repo-side wiring teardown (view symlink,\n * instruction symlinks, .gitignore, canonical) is the caller's separate,\n * higher-blast-radius concern; this only mutates the manifest's declaration.\n */\n\nimport { normalizeRelativePath as normalize } from \"./relative-path.js\";\nimport type { RepoEntry } from \"./roster.js\";\n\nexport type ArchivePlan = {\n /** The normalized target path being archived. */\n target: string;\n /** True when the target is declared in the roster. */\n found: boolean;\n /**\n * True when the target resolves to the anchor/host (`.`). Archiving the\n * project's own root is refused: it is the home of the manifest, not a member\n * repo to fold. The caller writes nothing in this case.\n */\n isAnchor: boolean;\n /** The roster entry that would be removed (echoed for the report); set only when found & not anchor. */\n rosterEntry?: RepoEntry | undefined;\n /** The roster after removal. An empty array means the project closes (the `repos` key is dropped). */\n nextRepos: RepoEntry[];\n /** True when removal leaves the roster empty (the unified-instruction project is fully closed). */\n reposEmptied: boolean;\n /** The `source_roots` entry (normalized) that would be pruned; set only when the target was captured. */\n sourceRootRemoval?: string | undefined;\n /** The `source_roots` after pruning; set only when a prune actually happens. */\n nextSourceRoots?: string[] | undefined;\n /** Declared repos remaining after removal. */\n remainingCount: number;\n /** True when exactly one repo remains: the project becomes solo and the workspace view is no longer needed. */\n becomesSolo: boolean;\n};\n\n/**\n * Compute the {@link ArchivePlan} for folding `target` out of the project. Pure:\n * it partitions the declared `repos` and `source_roots` by normalized path.\n *\n * - Archiving the anchor (`.`, or a path the caller resolved to the manifest\n * root) is refused — the plan reports `isAnchor` and removes nothing.\n * - A target not in the roster yields `found: false` and no change (the caller\n * reports the declared paths so the operator sees what to type).\n * - Otherwise EVERY roster entry matching the normalized target is removed (so a\n * path declared twice does not survive), and the matching `source_roots` entry\n * (commonly the same path) is pruned. Only the EXACT normalized target is\n * pruned from `source_roots`; entries for every other path — the host `.`, a\n * generated workspace-view source root — survive.\n * - When removal empties the roster, `nextRepos` is `[]` and `reposEmptied` is\n * true (the caller drops the `repos` key — `repos: []` is not a valid roster).\n */\nexport function planArchive(input: {\n repos?: RepoEntry[];\n sourceRoots?: string[];\n target: string;\n targetIsAnchor?: boolean;\n}): ArchivePlan {\n const target = normalize(input.target);\n const repos = input.repos ?? [];\n const isAnchor = input.targetIsAnchor === true || target === \".\";\n const matched = repos.filter((r) => normalize(r.path) === target);\n const found = matched.length > 0;\n\n // Refusals / no-ops: archiving the anchor, or a target not in the roster,\n // changes nothing. Report the situation and leave the lists untouched.\n if (isAnchor || !found) {\n return {\n target,\n found,\n isAnchor,\n nextRepos: repos,\n reposEmptied: false,\n remainingCount: repos.length,\n becomesSolo: false,\n };\n }\n\n const nextRepos = repos.filter((r) => normalize(r.path) !== target);\n const remainingCount = nextRepos.length;\n\n let sourceRootRemoval: string | undefined;\n let nextSourceRoots: string[] | undefined;\n if (input.sourceRoots !== undefined) {\n const pruned = input.sourceRoots.filter((s) => normalize(s) !== target);\n if (pruned.length !== input.sourceRoots.length) {\n sourceRootRemoval = target;\n nextSourceRoots = pruned;\n }\n }\n\n return {\n target,\n found: true,\n isAnchor: false,\n rosterEntry: matched[matched.length - 1],\n nextRepos,\n reposEmptied: remainingCount === 0,\n ...(sourceRootRemoval !== undefined ? { sourceRootRemoval } : {}),\n ...(nextSourceRoots !== undefined ? { nextSourceRoots } : {}),\n remainingCount,\n becomesSolo: remainingCount === 1,\n };\n}\n","/**\n * Plan the agent instruction-file `.gitignore` entries a declared repo needs\n * (the first generation step of the \"saddle\" model). For a public-facing repo,\n * the agent instruction files (AGENTS.md, CLAUDE.md, …) must be GITIGNORED so the\n * gitignored symlinks to the private canonical never enter public git history.\n * `basou project gitignore` reconciles each repo's `.gitignore` to that; this is\n * the pure planner behind it.\n *\n * Pure: it diffs the REQUIRED patterns against the repo's CURRENT `.gitignore`\n * lines (both gathered by the caller) and reports only what is MISSING — it never\n * proposes removing a line. The realpath / file reading / writing is the caller's\n * job. The privacy decision is visibility-aware: only public / future-public\n * repos require the patterns (a private anchor may legitimately track its\n * canonical), and a repo with unset visibility is skipped (reported), never\n * acted on by guesswork.\n */\n\nimport type { RepoVisibility } from \"./roster.js\";\n\n/** A declared repo's current `.gitignore` state, gathered by the caller. */\nexport type RepoGitignoreFacts = {\n /** Roster repo path (relative to the manifest root). */\n path: string;\n /** Declared visibility; undefined when the operator has not set it yet. */\n visibility?: RepoVisibility | undefined;\n /** False when the repo path could not be resolved / is not a usable git repo. */\n reachable: boolean;\n /** Existing `.gitignore` lines, trimmed; an empty array when there is no `.gitignore`. */\n currentLines: string[];\n};\n\n/** The patterns to ADD to one repo's `.gitignore` (never any to remove). */\nexport type RepoGitignorePlan = {\n path: string;\n toAdd: string[];\n};\n\nexport type GitignorePlanSummary = {\n /** Repos that need patterns added (those with an empty `toAdd` are omitted). */\n plans: RepoGitignorePlan[];\n /** Repo paths skipped because visibility is unset (cannot decide safely). */\n unknown: string[];\n /** Repo paths that could not be resolved / are not usable git repos. */\n unreachable: string[];\n /**\n * True only when nothing needs adding AND every repo was judgeable and\n * reachable — so a clean verdict is never claimed while some repos were\n * skipped (unset visibility) or could not be inspected (unreachable).\n */\n ok: boolean;\n};\n\n/** Whether a visibility exposes git history to the public (so instruction files must be ignored). */\nfunction isPublicFacing(v: RepoVisibility | undefined): v is \"public\" | \"future-public\" {\n return v === \"public\" || v === \"future-public\";\n}\n\n/**\n * Compute the {@link GitignorePlanSummary}: for each public-facing, reachable\n * repo, the `required` patterns that are not already present in its `.gitignore`\n * (compared by trimmed exact line). Private repos require nothing; unset\n * visibility is reported as `unknown` and unreachable repos as `unreachable`.\n * `ok` is true when no repo needs any addition.\n */\nexport function planGitignore(input: {\n repos: RepoGitignoreFacts[];\n required: string[];\n}): GitignorePlanSummary {\n const plans: RepoGitignorePlan[] = [];\n const unknown: string[] = [];\n const unreachable: string[] = [];\n\n for (const repo of input.repos) {\n if (!repo.reachable) {\n unreachable.push(repo.path);\n continue;\n }\n if (repo.visibility === undefined) {\n unknown.push(repo.path);\n continue;\n }\n if (!isPublicFacing(repo.visibility)) continue;\n\n // A line already present suppresses re-adding. Treat an anchored-root form\n // (`/AGENTS.md`) as covering the plain pattern (`AGENTS.md`) so we do not add\n // a redundant equivalent rule. A directory-only `AGENTS.md/` or a comment is\n // intentionally NOT treated as equivalent.\n const present = new Set<string>();\n for (const line of repo.currentLines) {\n const trimmed = line.trim();\n present.add(trimmed);\n if (trimmed.startsWith(\"/\")) present.add(trimmed.slice(1));\n }\n const toAdd = input.required.filter((p) => !present.has(p));\n if (toAdd.length > 0) plans.push({ path: repo.path, toAdd });\n }\n\n return {\n plans,\n unknown,\n unreachable,\n ok: plans.length === 0 && unknown.length === 0 && unreachable.length === 0,\n };\n}\n","/**\n * Agent instruction-file \"A preset\" generation. A repo's\n * canonical instruction file splits into a STABLE PRESET (its source\n * visibility, source language, and published surfaces — facts derived from the\n * manifest) and a HAND-AUTHORED POLICY (tech choices, coding rules). This\n * renders the stable preset from the declaration so the operator stops\n * hand-typing it into every prompt, and plans how that generated region\n * reconciles against each repo's canonical — so `basou project preset` keeps it\n * in sync without ever touching the hand-authored content around it.\n *\n * Pure: it renders deterministic markdown from declared fields and judges\n * already-gathered facts (does the canonical exist? what does its generated\n * region currently hold?). The filesystem / marker reading / writing is the\n * caller's job.\n */\n\nimport { normalizeRelativePath as normalizePath } from \"./relative-path.js\";\nimport type { PublishTarget, RepoLanguage, RepoVisibility } from \"./roster.js\";\n\n/** The declared fields the preset block is rendered from. */\nexport type PresetRepo = {\n visibility?: RepoVisibility | undefined;\n language?: RepoLanguage | undefined;\n publishes?: PublishTarget[] | undefined;\n};\n\n/** Source git-visibility, rendered with the consequence the agent must respect. */\nfunction visibilityLabel(v: RepoVisibility | undefined): string {\n switch (v) {\n case \"public\":\n return \"public(git 履歴は公開)\";\n case \"private\":\n return \"private(git 履歴は非公開)\";\n case \"future-public\":\n return \"future-public(現在は非公開・将来公開予定)\";\n default:\n return \"未設定\";\n }\n}\n\n/** Source language (commits/comments/code), rendered with the audience it serves. */\nfunction sourceLanguageLabel(l: RepoLanguage | undefined): string {\n switch (l) {\n case \"en\":\n return \"en(commit・コメント・コードは英語)\";\n case \"ja\":\n return \"ja(commit・コメント・コードは日本語)\";\n case \"en+ja\":\n return \"en+ja(commit・コメント・コードは日英)\";\n default:\n return \"未設定\";\n }\n}\n\n/** Published-surface kind. */\nfunction publishKindLabel(k: PublishTarget[\"kind\"]): string {\n return k === \"web\" ? \"web(デプロイ)\" : \"npm(パッケージ)\";\n}\n\n/** A published surface's visibility (independent of the source repo's). */\nfunction publishVisibilityLabel(v: RepoVisibility | undefined): string {\n switch (v) {\n case \"public\":\n return \"公開\";\n case \"private\":\n return \"非公開\";\n case \"future-public\":\n return \"将来公開\";\n default:\n return \"可視性未設定\";\n }\n}\n\n/** A published surface's content language (read by end users; may differ from source). */\nfunction contentLanguageLabel(l: RepoLanguage | undefined): string {\n return l ?? \"言語未設定\";\n}\n\n/**\n * Whether a repo has anything to render. A repo with no visibility, no language,\n * and no published surface yields an all-\"未設定\" block that helps no one, so it\n * is reported as `undeclared` rather than generated.\n */\nexport function isRenderable(repo: PresetRepo): boolean {\n return (\n repo.visibility !== undefined ||\n repo.language !== undefined ||\n (repo.publishes !== undefined && repo.publishes.length > 0)\n );\n}\n\n/**\n * Render the stable-preset markdown block (the content that lives BETWEEN the\n * BASOU:GENERATED markers in a canonical). Deterministic and OSS-generic: it\n * derives entirely from the declared fields, embedding no operator-specific\n * names, so re-running on an unchanged manifest produces byte-identical output\n * (the basis for drift detection). The published surfaces are listed in their\n * declared order. Returns the block WITHOUT a trailing newline; the marker\n * writer adds the surrounding structure.\n */\nexport function renderPresetBlock(repo: PresetRepo): string {\n const lines: string[] = [];\n lines.push(\"## プロジェクト構成(basou が生成 — manifest が正本)\");\n lines.push(\"\");\n lines.push(\n \"このセクションは `.basou/manifest.yaml` の宣言から `basou project preset` が生成します。編集は manifest 側で行ってください(マーカー外の記述は保持されます)。\",\n );\n lines.push(\"\");\n lines.push(`- ソース可視性: ${visibilityLabel(repo.visibility)}`);\n lines.push(`- ソース言語: ${sourceLanguageLabel(repo.language)}`);\n const publishes = repo.publishes ?? [];\n if (publishes.length === 0) {\n lines.push(\"- 配信物: なし\");\n } else {\n lines.push(\"- 配信物:\");\n for (const p of publishes) {\n lines.push(\n ` - ${publishKindLabel(p.kind)} — ${publishVisibilityLabel(p.visibility)} / ${contentLanguageLabel(p.language)}`,\n );\n }\n }\n return lines.join(\"\\n\");\n}\n\n/**\n * The canonical's marker state as parsed by the caller (mirrors\n * `markdown-store`'s `MarkerSection.kind`). `ok` means exactly one well-ordered\n * marker pair; any other value means the generated region cannot be located\n * safely, so the preset is NOT injected (we never clobber a hand-authored file).\n */\nexport type PresetMarkerKind =\n | \"ok\"\n | \"no_markers\"\n | \"missing_start\"\n | \"missing_end\"\n | \"multiple_pairs\"\n | \"wrong_order\";\n\n/** The gathered facts for one declared repo. */\nexport type RepoPresetFacts = {\n /** Roster repo path (relative to the manifest root). */\n path: string;\n /** True when this repo IS the project anchor (its own AGENTS.md is hand-maintained; skipped). */\n isAnchor: boolean;\n /** False when the repo path could not be resolved / is not a usable git repo. */\n reachable: boolean;\n /** Declared fields (the render input). */\n visibility?: RepoVisibility | undefined;\n language?: RepoLanguage | undefined;\n publishes?: PublishTarget[] | undefined;\n /**\n * The canonical's repo name (`<name>` in `agents/<name>/AGENTS.md`). Two\n * DISTINCT repos sharing it would write to one canonical, so it is used to\n * detect that. Set by the caller for reachable, non-anchor repos.\n */\n canonicalName?: string | undefined;\n /** Whether the canonical file exists on disk. */\n canonicalPresent: boolean;\n /**\n * Whether the present canonical could be read. Defaults to readable; the\n * caller sets it `false` when the file exists but a non-ENOENT read failed\n * (e.g. it is a directory, or permission denied), so one bad canonical\n * degrades only that repo instead of crashing the whole report.\n */\n canonicalReadable?: boolean | undefined;\n /** Marker parse result of the canonical (only meaningful when present and readable). */\n markerKind?: PresetMarkerKind | undefined;\n /** Current generated-region content (only when `markerKind === \"ok\"`). */\n currentBlock?: string | undefined;\n};\n\n/** `create` seeds an absent canonical; `update` replaces the region of an existing one. */\nexport type PresetAction = \"create\" | \"update\";\n\n/** A repo whose canonical's generated region will be created or updated. */\nexport type RepoPresetPlan = {\n path: string;\n canonicalName: string;\n action: PresetAction;\n /** The block that will be written (the marker-delimited content). */\n desiredBlock: string;\n};\n\n/**\n * A canonical that exists but whose markers cannot be located safely (absent or\n * malformed). The region is NOT injected — surfaced so the operator can add the\n * markers (or remove the malformed ones) by hand.\n */\nexport type PresetMarkerConflict = {\n repo: string;\n reason: Exclude<PresetMarkerKind, \"ok\">;\n};\n\n/**\n * Two or more DISTINCT declared repos whose canonical resolves to the same\n * `agents/<canonicalName>/AGENTS.md`. They would write over one canonical, so\n * neither is generated; the operator must disambiguate.\n */\nexport type PresetCollision = {\n canonicalName: string;\n repos: string[];\n};\n\nexport type PresetPlanSummary = {\n /** Repos whose canonical's generated region will be created/updated (only those with work). */\n plans: RepoPresetPlan[];\n /** Repos already in sync (canonical present, ok markers, block matches). */\n inSync: string[];\n /** Repos with nothing declared to render (no visibility, language, or published surface). */\n undeclared: string[];\n /** Canonicals that exist but whose markers are absent/malformed — not overwritten. */\n markerConflicts: PresetMarkerConflict[];\n /** Repos whose canonical exists but could not be read (degraded, not generated). */\n unreadable: string[];\n /** Groups of distinct repos that resolve to the same canonical (ambiguous; not generated). */\n collisions: PresetCollision[];\n /** Repos that resolve to the anchor (their own AGENTS.md is hand-maintained; skipped). */\n anchors: string[];\n /** Repo paths that could not be resolved / are not usable git repos. */\n unreachable: string[];\n /**\n * True only when nothing needs writing AND there are no marker conflicts, no\n * unreadable canonicals, no collisions, no unreachable repos, and no\n * undeclared repos — so a clean \"all in sync\" verdict is never claimed while\n * some repo was skipped or unjudgeable. Anchors do not block it (they are\n * intentionally not generated).\n */\n ok: boolean;\n};\n\n/** Normalize a block for in-sync comparison: LF line endings, no trailing blank lines. */\nfunction normalizeBlock(s: string): string {\n return s.replace(/\\r\\n/g, \"\\n\").replace(/\\n+$/, \"\");\n}\n\n/**\n * Compute the {@link PresetPlanSummary} from per-repo facts. For each declared,\n * non-anchor, reachable, renderable repo: an absent canonical is a `create`, an\n * existing canonical with an `ok` marker region is an `update` (or `inSync` when\n * the region already matches), and a canonical with absent/malformed markers is\n * a {@link PresetMarkerConflict} (never overwritten). The anchor is skipped\n * (`anchors`), an unrenderable repo is `undeclared`, and an unresolvable repo is\n * `unreachable`.\n *\n * Robustness:\n * - Facts are deduped by normalized path (first wins), so a repo listed twice\n * never yields duplicate plans / report entries.\n * - Two DISTINCT repos resolving to the same canonical name are a\n * {@link PresetCollision} and neither is generated (silent clobbering of one\n * canonical is surfaced, not actioned).\n */\nexport function summarizePresetPlan(facts: RepoPresetFacts[]): PresetPlanSummary {\n // Dedup by normalized path (first declaration wins).\n const deduped: RepoPresetFacts[] = [];\n const seenPath = new Set<string>();\n for (const f of facts) {\n const key = normalizePath(f.path);\n if (seenPath.has(key)) continue;\n seenPath.add(key);\n deduped.push(f);\n }\n\n // Detect canonical-name collisions among repos that would actually generate\n // (non-anchor, reachable, renderable, canonical name known).\n const byCanonical = new Map<string, string[]>();\n for (const f of deduped) {\n if (f.isAnchor || !f.reachable || f.canonicalName === undefined || !isRenderable(f)) continue;\n const repos = byCanonical.get(f.canonicalName) ?? [];\n repos.push(f.path);\n byCanonical.set(f.canonicalName, repos);\n }\n const collisions: PresetCollision[] = [];\n const collidingPaths = new Set<string>();\n for (const [canonicalName, repos] of byCanonical) {\n if (repos.length > 1) {\n collisions.push({ canonicalName, repos });\n for (const r of repos) collidingPaths.add(r);\n }\n }\n\n const plans: RepoPresetPlan[] = [];\n const inSync: string[] = [];\n const undeclared: string[] = [];\n const markerConflicts: PresetMarkerConflict[] = [];\n const unreadable: string[] = [];\n const anchors: string[] = [];\n const unreachable: string[] = [];\n\n for (const f of deduped) {\n if (f.isAnchor) {\n anchors.push(f.path);\n continue;\n }\n if (!f.reachable) {\n unreachable.push(f.path);\n continue;\n }\n if (!isRenderable(f)) {\n undeclared.push(f.path);\n continue;\n }\n // A repo sharing a canonical name with another is surfaced as a collision\n // and never generated.\n if (collidingPaths.has(f.path)) continue;\n // Reachable + renderable + non-colliding repos always have a canonical name\n // (the caller sets it from the resolved basename); guard for type-safety.\n if (f.canonicalName === undefined) {\n unreachable.push(f.path);\n continue;\n }\n\n const desiredBlock = renderPresetBlock(f);\n if (!f.canonicalPresent) {\n plans.push({ path: f.path, canonicalName: f.canonicalName, action: \"create\", desiredBlock });\n continue;\n }\n // Canonical exists but could not be read — degrade this repo, do not generate.\n if (f.canonicalReadable === false) {\n unreadable.push(f.path);\n continue;\n }\n if (f.markerKind === \"ok\") {\n if (normalizeBlock(f.currentBlock ?? \"\") === normalizeBlock(desiredBlock)) {\n inSync.push(f.path);\n } else {\n plans.push({\n path: f.path,\n canonicalName: f.canonicalName,\n action: \"update\",\n desiredBlock,\n });\n }\n continue;\n }\n // Canonical present but markers absent/malformed — do not clobber.\n markerConflicts.push({ repo: f.path, reason: f.markerKind ?? \"no_markers\" });\n }\n\n return {\n plans,\n inSync,\n undeclared,\n markerConflicts,\n unreadable,\n collisions,\n anchors,\n unreachable,\n ok:\n plans.length === 0 &&\n markerConflicts.length === 0 &&\n unreadable.length === 0 &&\n collisions.length === 0 &&\n unreachable.length === 0 &&\n undeclared.length === 0,\n };\n}\n","/**\n * Rename (re-path) a repo in a project's declared roster. When a repo's\n * directory is moved or renamed on disk, its declared `path` (and the matching\n * `source_roots` capture entry) must follow, or the roster drifts from reality.\n * This is the saddle model's maintenance counterpart to `archive` — it mutates\n * the manifest's path references rather than removing them.\n *\n * Pure: it computes the mutation from the DECLARED lists alone — no filesystem\n * or git I/O — so it works regardless of whether the move has happened on disk\n * yet. The repo-side wiring that embeds the old basename (the anchor canonical\n * `agents/<basename>/AGENTS.md`, the workspace-view symlink, the relative\n * targets of the repo's own instruction symlinks) is the caller's separate\n * concern; this only re-paths the declaration.\n */\n\nimport { normalizeRelativePath as normalize } from \"./relative-path.js\";\nimport type { RepoEntry } from \"./roster.js\";\n\nexport type RenamePlan = {\n /** The normalized source path being renamed. */\n oldTarget: string;\n /** The normalized destination path. */\n newTarget: string;\n /** True when old and new normalize to the same path (nothing to do). */\n noop: boolean;\n /**\n * True when the source resolves to the anchor/host (`.`). Renaming the\n * project's own root is refused; the caller writes nothing.\n */\n isAnchor: boolean;\n /** True when the source path is declared in the roster. */\n found: boolean;\n /** True when the destination path is ALREADY declared (a distinct entry) — refused to avoid a duplicate. */\n collision: boolean;\n /** The roster entry being renamed (echoed for the report); set only in the actionable case. */\n rosterEntry?: RepoEntry | undefined;\n /** The roster after re-pathing the entry (other fields preserved). */\n nextRepos: RepoEntry[];\n /** True when the roster changed. */\n reposChanged: boolean;\n /** The old normalized path that was re-pathed in `source_roots`; set only when it was captured. */\n sourceRootRenamed?: string | undefined;\n /** The `source_roots` after re-pathing; set only when a rename happened. */\n nextSourceRoots?: string[] | undefined;\n /** True when the basename changes (old/new last segment differ) — the repo-side canonical/view names need renaming too. */\n basenameChanged: boolean;\n};\n\n/** The last path segment of a normalized relative path (e.g. \"../a/x\" => \"x\", \".\" => \".\"). */\nexport function pathBasename(p: string): string {\n const parts = normalize(p).split(\"/\");\n return parts[parts.length - 1] as string;\n}\n\n/** Dedup roster entries by normalized path (first wins). */\nfunction dedupRepos(entries: RepoEntry[]): RepoEntry[] {\n const seen = new Set<string>();\n const out: RepoEntry[] = [];\n for (const e of entries) {\n const k = normalize(e.path);\n if (seen.has(k)) continue;\n seen.add(k);\n out.push(e);\n }\n return out;\n}\n\n/** Dedup source-root strings by normalized form (first occurrence wins, original form kept). */\nfunction dedupNorm(items: string[]): string[] {\n const seen = new Set<string>();\n const out: string[] = [];\n for (const s of items) {\n const k = normalize(s);\n if (seen.has(k)) continue;\n seen.add(k);\n out.push(s);\n }\n return out;\n}\n\n/**\n * Compute the {@link RenamePlan} for re-pathing `oldPath` to `newPath`. Pure: it\n * re-maps the declared `repos` and `source_roots` by normalized path.\n *\n * - A no-op (old === new), renaming the anchor, a source not in the roster, or a\n * destination that already exists as a distinct entry (collision) all change\n * nothing — the caller writes nothing and the report explains.\n * - Otherwise EVERY roster entry matching the old path is re-pathed to the new\n * path (preserving its visibility/language/publishes), and the result is\n * deduped (so an old path declared twice collapses to one new entry). The\n * matching `source_roots` entry (commonly the same path) is re-pathed and\n * deduped likewise; all other entries (the host `.`, a view source root) keep\n * their position and form.\n */\nexport function planRename(input: {\n repos?: RepoEntry[];\n sourceRoots?: string[];\n oldPath: string;\n newPath: string;\n oldIsAnchor?: boolean;\n}): RenamePlan {\n const oldTarget = normalize(input.oldPath);\n const newTarget = normalize(input.newPath);\n const repos = input.repos ?? [];\n const basenameChanged = pathBasename(oldTarget) !== pathBasename(newTarget);\n const noop = oldTarget === newTarget;\n const isAnchor = input.oldIsAnchor === true || oldTarget === \".\";\n const found = repos.some((r) => normalize(r.path) === oldTarget);\n const collision = !noop && repos.some((r) => normalize(r.path) === newTarget);\n\n if (noop || isAnchor || !found || collision) {\n return {\n oldTarget,\n newTarget,\n noop,\n isAnchor,\n found,\n collision,\n nextRepos: repos,\n reposChanged: false,\n basenameChanged,\n };\n }\n\n // FIRST match wins, consistently: dedupRepos below also keeps the first, so the\n // echoed entry and the written entry are the same object when a path is\n // (malformed-ly) declared twice with differing metadata.\n const rosterEntry = repos.find((r) => normalize(r.path) === oldTarget);\n const nextRepos = dedupRepos(\n repos.map((r) => (normalize(r.path) === oldTarget ? { ...r, path: newTarget } : r)),\n );\n\n let sourceRootRenamed: string | undefined;\n let nextSourceRoots: string[] | undefined;\n if (\n input.sourceRoots !== undefined &&\n input.sourceRoots.some((s) => normalize(s) === oldTarget)\n ) {\n nextSourceRoots = dedupNorm(\n input.sourceRoots.map((s) => (normalize(s) === oldTarget ? newTarget : s)),\n );\n sourceRootRenamed = oldTarget;\n }\n\n return {\n oldTarget,\n newTarget,\n noop: false,\n isAnchor: false,\n found: true,\n collision: false,\n rosterEntry,\n nextRepos,\n reposChanged: true,\n ...(sourceRootRenamed !== undefined ? { sourceRootRenamed } : {}),\n ...(nextSourceRoots !== undefined ? { nextSourceRoots } : {}),\n basenameChanged,\n };\n}\n","/**\n * Project roster drift (the \"saddle\" model). A project's repos are DECLARED\n * once in the manifest's `repos` list; the capture config (`source_roots`) must\n * cover every declared repo. This computes the drift between the two so\n * `basou project check` can surface a declared repo that is NOT being captured\n * — the class of bug where a companion repo was wired into the workspace but\n * never added to `source_roots`, so its work silently fell out of capture.\n *\n * Pure: it compares declared relative paths against captured relative paths and\n * performs no filesystem or git I/O. Paths are compared as declared (both lists\n * use the same machine-portable relative-path form), not resolved on disk.\n */\n\nimport { normalizeRelativePath as normalize } from \"./relative-path.js\";\n\nexport type RepoVisibility = \"public\" | \"private\" | \"future-public\";\n\n/**\n * The audience-driven language axis. Independent of visibility: a private repo\n * can publish English content, a public repo can carry bilingual docs. `en` /\n * `ja` for a single audience, `en+ja` when both are served.\n */\nexport type RepoLanguage = \"en\" | \"ja\" | \"en+ja\";\n\n/** A published surface a repo emits: a deployed website or a package registry. */\nexport type PublishKind = \"web\" | \"npm\";\n\n/**\n * One published surface. Its visibility and language are INDEPENDENT of the\n * source repo's: a private repo commonly publishes a public website. Both are\n * optional so a surface can be declared\n * before those facts are pinned down (mirroring how `adopt` leaves repo\n * visibility unset for the operator to fill in).\n */\nexport type PublishTarget = {\n kind: PublishKind;\n visibility?: RepoVisibility | undefined;\n language?: RepoLanguage | undefined;\n};\n\nexport type RepoEntry = {\n /** Path relative to the manifest repo root (e.g. \".\", \"../takuhon\"). */\n path: string;\n // `| undefined` so a zod-inferred manifest entry (visibility?: X | undefined,\n // under exactOptionalPropertyTypes) is assignable without remapping.\n visibility?: RepoVisibility | undefined;\n /** Source language (commits/comments/code, read by contributors). Independent of visibility. */\n language?: RepoLanguage | undefined;\n /** Published surfaces this repo emits (opt-in; absent for a repo that publishes nothing). */\n publishes?: PublishTarget[] | undefined;\n};\n\nexport type RosterDriftSummary = {\n declaredCount: number;\n capturedCount: number;\n /** Declared in `repos` but absent from `source_roots`: a capture gap. */\n gaps: RepoEntry[];\n /** In `source_roots` but not declared in `repos` (e.g. a workspace view, or a stray). */\n extra: string[];\n /** Declared paths that are also captured. */\n matched: string[];\n /** True when there is no capture gap (every declared repo is covered). */\n ok: boolean;\n};\n\n/**\n * Compute the {@link RosterDriftSummary} for a project. A declared repo missing\n * from the captured set is a `gap` (the surfaced suspicion); a captured path not\n * in the declared set is `extra` (commonly the workspace view, which is a\n * capture source but not itself a project repo). With no declared roster, there\n * are no gaps (nothing to check against) and every captured path is `extra`.\n */\nexport function summarizeRosterDrift(input: {\n repos?: RepoEntry[];\n sourceRoots?: string[];\n}): RosterDriftSummary {\n const captured = new Set((input.sourceRoots ?? []).map(normalize));\n // Last declaration of a given normalized path wins, but carry it as one entry.\n const declared = new Map<string, RepoEntry>();\n for (const r of input.repos ?? []) declared.set(normalize(r.path), r);\n\n const gaps: RepoEntry[] = [];\n const matched: string[] = [];\n for (const [norm, entry] of declared) {\n if (captured.has(norm)) matched.push(norm);\n else gaps.push(entry);\n }\n const extra = [...captured].filter((c) => !declared.has(c)).sort();\n\n return {\n declaredCount: declared.size,\n capturedCount: captured.size,\n gaps,\n extra,\n matched: matched.sort(),\n ok: gaps.length === 0,\n };\n}\n\nexport type SourceRootsReconcile = {\n /**\n * The reconciled `source_roots`: the existing entries verbatim, then every\n * declared repo path that was missing (normalized, in roster order). Existing\n * order and form are preserved so the manifest diff is minimal and reversible.\n */\n next: string[];\n /** Declared repo paths (normalized) that were appended because `source_roots` did not cover them. */\n added: string[];\n /** True when `source_roots` already covers every declared repo (`next` equals the current list). */\n unchanged: boolean;\n};\n\n/**\n * Derive the `source_roots` a project's declared repo roster requires. The\n * roster (`repos`) is the single source of truth for which repos belong to the\n * project; this is the actuator behind `basou project sync`, computing the\n * additive reconciliation so every declared repo is captured.\n *\n * ADDITIVE ONLY: it appends declared paths that are missing and never removes\n * an existing entry. A captured-but-undeclared path (commonly the generated\n * workspace view — a legitimate capture source that is not itself a project\n * repo) is preserved; pruning strays is deferred to the slice that generates\n * the view (so basou knows which extras it owns). Existing entries are kept\n * byte-identical; only appended paths are normalized.\n *\n * Pure: no filesystem or git I/O. Paths are compared in the same normalized\n * form as {@link summarizeRosterDrift}, so a trailing-slash variant of an\n * already-captured repo is not re-appended.\n */\nexport function reconcileSourceRoots(input: {\n repos?: RepoEntry[];\n sourceRoots?: string[];\n}): SourceRootsReconcile {\n const current = input.sourceRoots ?? [];\n const seen = new Set(current.map(normalize));\n const added: string[] = [];\n for (const r of input.repos ?? []) {\n const norm = normalize(r.path);\n if (seen.has(norm)) continue;\n seen.add(norm);\n added.push(norm);\n }\n return {\n next: [...current, ...added],\n added,\n unchanged: added.length === 0,\n };\n}\n\n/**\n * On-disk classification of a source-root candidate during adoption: a git repo\n * root (→ becomes a roster entry), a resolved-but-non-repo directory (the\n * generated workspace view, `/tmp`, a scratch dir → excluded), or a path that\n * could not be resolved on disk (→ excluded).\n */\nexport type AdoptCandidateKind = \"repo\" | \"non-repo\" | \"unresolved\";\n\nexport type AdoptCandidate = {\n /** Source-root path as declared (relative to the manifest root). */\n path: string;\n /** On-disk classification; the filesystem probing that produces it is the caller's job. */\n kind: AdoptCandidateKind;\n};\n\nexport type RosterAdoptionPlan = {\n /** Proposed `repos` entries: the candidates that are git repos (visibility left unset for the operator). */\n repos: RepoEntry[];\n /** Candidates excluded from the roster, with why (a non-repo directory, or an unresolvable path). */\n excluded: { path: string; kind: Exclude<AdoptCandidateKind, \"repo\"> }[];\n};\n\n/**\n * Plan a `repos` roster from classified source-root candidates (the actuator\n * behind `basou project adopt`). Pure: it partitions already-classified\n * candidates — the realpath / `.git` filesystem probing that produces each\n * `kind` is the caller's job, so this stays testable without disk I/O.\n *\n * A git repo becomes a roster entry (path only; visibility is left unset because\n * it is a human judgment, kept independent of the other axes). A non-repo\n * (commonly the generated workspace view) or an unresolvable path is excluded and\n * reported, so the operator sees what was dropped and why before editing. Repo\n * paths are deduped by normalized form, preserving the first declared form and\n * order.\n */\nexport function planRosterAdoption(candidates: AdoptCandidate[]): RosterAdoptionPlan {\n const repos: RepoEntry[] = [];\n const excluded: { path: string; kind: Exclude<AdoptCandidateKind, \"repo\"> }[] = [];\n const seen = new Set<string>();\n for (const c of candidates) {\n // Dedup by normalized path across ALL kinds (a trailing-slash variant of a\n // path already seen — repo or excluded — is not listed twice).\n const norm = normalize(c.path);\n if (seen.has(norm)) continue;\n seen.add(norm);\n if (c.kind === \"repo\") repos.push({ path: c.path });\n else excluded.push({ path: c.path, kind: c.kind });\n }\n return { repos, excluded };\n}\n","/**\n * Plan the agent instruction-file symlinks a declared repo needs (the\n * generation step that follows `basou project gitignore` in the \"saddle\"\n * model). Each repo's agent instruction files are GITIGNORED symlinks that\n * resolve to a single canonical source kept in the project's private anchor —\n * so the canonical (which may carry private planning content) is edited once\n * and every CLI reads it through a symlink, never committed to a public repo's\n * history.\n *\n * The on-disk topology (verified against the operator's live environment) is a\n * hub-and-spoke:\n *\n * <repo>/AGENTS.md -> <anchor>/agents/<repo>/AGENTS.md (the hub → canonical)\n * <repo>/CLAUDE.md -> AGENTS.md (a spoke → the hub)\n * <repo>/.github/copilot-instructions.md -> ../AGENTS.md (a spoke → the hub)\n *\n * Only AGENTS.md points at the anchor's canonical; CLAUDE.md and Copilot point\n * back at the repo's own AGENTS.md, so there is exactly one link per repo that\n * depends on the anchor path. GEMINI.md is intentionally not generated (the\n * Gemini CLI was discontinued for personal use).\n *\n * Pure: it judges already-gathered, per-file facts (does the link exist? does\n * it point where it should?) and reports only what is MISSING. It never\n * proposes overwriting an existing file or repointing a link that points\n * elsewhere — those surface as conflicts for the operator to resolve by hand\n * (non-destructive, like the additive `.gitignore` planner). The realpath /\n * symlink reading / writing is the caller's job.\n */\n\nimport { normalizeRelativePath as normalize } from \"./relative-path.js\";\n\n/**\n * The on-disk state of one instruction file relative to the symlink it should\n * be. `correct` = the expected link already exists (idempotent skip); `missing`\n * = nothing there (ENOENT), so it can be created; `mismatch` = a symlink\n * pointing somewhere else; `occupied` = a real file or directory; `blocked` =\n * the path could not be inspected (e.g. a parent component is a file → ENOTDIR,\n * or permission denied). Only `missing` is actionable; the rest are left\n * untouched. `blocked` is distinct from `missing` so a non-ENOENT lstat error\n * is never mistaken for a creatable gap (which would crash `--apply`).\n */\nexport type InstructionSymlinkState = \"correct\" | \"missing\" | \"mismatch\" | \"occupied\" | \"blocked\";\n\n/** The gathered facts for one instruction file in one declared repo. */\nexport type InstructionSymlinkFact = {\n /** Repo-relative file name, e.g. \"AGENTS.md\", \".github/copilot-instructions.md\". */\n name: string;\n /** The relative symlink target this file should have (computed by the caller). */\n expectedTarget: string;\n /** On-disk state of the path. */\n state: InstructionSymlinkState;\n /** The link's current target, present only when `state` is `mismatch`. */\n actualTarget?: string;\n};\n\n/** The gathered symlink facts for one declared repo. */\nexport type RepoSymlinkFacts = {\n /** Roster repo path (relative to the manifest root). */\n path: string;\n /**\n * True when this repo IS the project anchor (it owns the canonical sources, so\n * it never links to itself). An anchor entry is skipped entirely.\n */\n isAnchor: boolean;\n /** False when the repo path could not be resolved / is not a usable git repo. */\n reachable: boolean;\n /**\n * Whether the anchor's canonical source for this repo\n * (`<anchor>/agents/<repo>/AGENTS.md`) exists. Without it the hub link would\n * dangle, so no links are planned (reported as a missing canonical instead).\n */\n canonicalPresent: boolean;\n /**\n * The canonical's repo name (the `<repo>` in `agents/<repo>/AGENTS.md`) this\n * repo wires to. Two DISTINCT repos sharing one canonical name would silently\n * collide on a single canonical, so it is used to detect that. Set by the\n * caller for reachable, canonical-present repos; undefined otherwise.\n */\n canonicalName?: string;\n /** Per instruction-file facts (empty when anchor / unreachable / canonical absent). */\n files: InstructionSymlinkFact[];\n};\n\n/** The instruction-file symlinks to CREATE in one repo (only the `missing` ones). */\nexport type RepoSymlinkPlan = {\n path: string;\n toCreate: { name: string; target: string }[];\n};\n\n/**\n * A symlink that already exists but is not what we would generate: a symlink\n * pointing elsewhere (`mismatch`), a real file/directory (`occupied`), or a path\n * that could not be inspected (`blocked`, e.g. a parent is a file). Surfaced,\n * never overwritten.\n */\nexport type SymlinkConflict = {\n repo: string;\n file: string;\n reason: \"mismatch\" | \"occupied\" | \"blocked\";\n /** The conflicting link's current target, present only when `reason` is `mismatch`. */\n actualTarget?: string;\n};\n\n/**\n * Two or more DISTINCT declared repos whose canonical resolves to the same\n * `agents/<canonicalName>/AGENTS.md`. They would silently share one canonical,\n * so neither is auto-wired; the operator must disambiguate.\n */\nexport type SymlinkCollision = {\n canonicalName: string;\n repos: string[];\n};\n\nexport type SymlinkPlanSummary = {\n /** Repos with at least one link to create (those with nothing to create are omitted). */\n plans: RepoSymlinkPlan[];\n /** Existing files/links that block generation and are left untouched for the operator. */\n conflicts: SymlinkConflict[];\n /** Repo paths whose anchor canonical (`agents/<repo>/AGENTS.md`) is absent, so nothing can be wired. */\n missingCanonical: string[];\n /** Repo paths that could not be resolved / are not usable git repos. */\n unreachable: string[];\n /** Groups of distinct repos that resolve to the same canonical (ambiguous; not auto-wired). */\n collisions: SymlinkCollision[];\n /**\n * True only when nothing needs creating AND there are no conflicts, no missing\n * canonicals, no unreachable repos, and no collisions — so a clean \"all wired\"\n * verdict is never claimed while some repo was blocked, ambiguous, or could not\n * be inspected.\n */\n ok: boolean;\n};\n\n/**\n * Compute the {@link SymlinkPlanSummary} from per-repo facts. For each declared,\n * non-anchor, reachable repo whose canonical exists: a `missing` link becomes a\n * create, a `mismatch`/`occupied`/`blocked` link becomes a {@link SymlinkConflict}\n * (never a create — we do not overwrite), and a `correct` link is a no-op. The\n * anchor is skipped (it owns the canonical), an absent canonical is reported as\n * `missingCanonical` (no links planned, since the hub would dangle), and an\n * unresolvable repo as `unreachable`.\n *\n * Robustness:\n * - Facts are deduped by normalized path (first wins), so a repo listed twice in\n * the manifest never yields duplicate plans (which would make `--apply` create\n * the same link twice → EEXIST) or duplicate report entries.\n * - Two DISTINCT repos resolving to the same canonical name are reported as a\n * {@link SymlinkCollision} and neither is auto-wired (silent sharing of one\n * canonical is surfaced, not actioned).\n *\n * `ok` is true only when there is genuinely nothing to do and every repo was\n * judgeable, reachable, and unambiguous.\n */\nexport function summarizeSymlinkPlan(facts: RepoSymlinkFacts[]): SymlinkPlanSummary {\n // Dedup by normalized path (first declaration wins).\n const deduped: RepoSymlinkFacts[] = [];\n const seenPath = new Set<string>();\n for (const f of facts) {\n const key = normalize(f.path);\n if (seenPath.has(key)) continue;\n seenPath.add(key);\n deduped.push(f);\n }\n\n // Detect canonical-name collisions among the repos that would actually wire\n // (reachable, canonical present). Distinct repo paths sharing a canonical name\n // are ambiguous: surface them and wire neither.\n const byCanonical = new Map<string, string[]>();\n for (const f of deduped) {\n if (f.isAnchor || !f.reachable || !f.canonicalPresent || f.canonicalName === undefined) {\n continue;\n }\n const repos = byCanonical.get(f.canonicalName) ?? [];\n repos.push(f.path);\n byCanonical.set(f.canonicalName, repos);\n }\n const collisions: SymlinkCollision[] = [];\n const collidingPaths = new Set<string>();\n for (const [canonicalName, repos] of byCanonical) {\n if (repos.length > 1) {\n collisions.push({ canonicalName, repos });\n for (const r of repos) collidingPaths.add(r);\n }\n }\n\n const plans: RepoSymlinkPlan[] = [];\n const conflicts: SymlinkConflict[] = [];\n const missingCanonical: string[] = [];\n const unreachable: string[] = [];\n\n for (const f of deduped) {\n if (f.isAnchor) continue;\n if (!f.reachable) {\n unreachable.push(f.path);\n continue;\n }\n if (!f.canonicalPresent) {\n missingCanonical.push(f.path);\n continue;\n }\n // A repo sharing a canonical name with another is surfaced as a collision and\n // never auto-wired (avoids silently pointing two repos at one canonical).\n if (collidingPaths.has(f.path)) continue;\n\n const toCreate: { name: string; target: string }[] = [];\n for (const file of f.files) {\n if (file.state === \"missing\") {\n toCreate.push({ name: file.name, target: file.expectedTarget });\n } else if (file.state === \"mismatch\") {\n conflicts.push({\n repo: f.path,\n file: file.name,\n reason: \"mismatch\",\n ...(file.actualTarget !== undefined ? { actualTarget: file.actualTarget } : {}),\n });\n } else if (file.state === \"occupied\") {\n conflicts.push({ repo: f.path, file: file.name, reason: \"occupied\" });\n } else if (file.state === \"blocked\") {\n conflicts.push({ repo: f.path, file: file.name, reason: \"blocked\" });\n }\n // \"correct\" → already wired, nothing to do.\n }\n if (toCreate.length > 0) plans.push({ path: f.path, toCreate });\n }\n\n return {\n plans,\n conflicts,\n missingCanonical,\n unreachable,\n collisions,\n ok:\n plans.length === 0 &&\n conflicts.length === 0 &&\n missingCanonical.length === 0 &&\n unreachable.length === 0 &&\n collisions.length === 0,\n };\n}\n","/**\n * Agent instruction-file wiring (the read-only first step of the \"saddle\"\n * model's view/instruction/gitignore generation). For each declared repo, the\n * agent instruction files (AGENTS.md, CLAUDE.md, .github/copilot-instructions.md)\n * should be present as GITIGNORED symlinks to a canonical source, never tracked\n * in a public repo's git history (where they would expose the private canonical\n * content they point at). This summarizes the on-disk + git facts the CLI\n * gathers and surfaces the privacy-relevant drift; it generates nothing.\n *\n * Pure: it judges already-gathered facts (presence + git-tracked status). The\n * filesystem / git probing that produces those facts is the caller's job.\n */\n\nimport type { RepoVisibility } from \"./roster.js\";\n\n/** On-disk + git state of one instruction file in one repo. */\nexport type InstructionFileFact = {\n /** Repo-relative file name, e.g. \"AGENTS.md\", \".github/copilot-instructions.md\". */\n name: string;\n /** Exists on disk (a regular file or a symlink, including a broken one). */\n present: boolean;\n /** Tracked by the repo's git (committed / staged). */\n tracked: boolean;\n};\n\n/** The gathered wiring facts for one declared repo. */\nexport type RepoWiringFacts = {\n /** Roster repo path (relative to the manifest root). */\n path: string;\n /** Declared visibility; undefined when the operator has not set it yet. */\n visibility?: RepoVisibility | undefined;\n /** False when the repo path could not be resolved / is not a usable git repo. */\n reachable: boolean;\n /** Per instruction-file facts (omitted/empty when unreachable). */\n instructionFiles: InstructionFileFact[];\n};\n\n/**\n * A privacy risk: a public-facing repo tracks an instruction file in git, which\n * can expose the private canonical content the file points at.\n */\nexport type WiringRisk = {\n repo: string;\n visibility: Extract<RepoVisibility, \"public\" | \"future-public\">;\n file: string;\n};\n\nexport type WiringSummary = {\n /** Echo of the per-repo facts (for `--json` and the detailed view). */\n repos: RepoWiringFacts[];\n /** Public / future-public repos with a tracked instruction file. */\n risks: WiringRisk[];\n /** Repo paths whose visibility is unset, so the privacy verdict cannot be judged. */\n unknown: string[];\n /** Repos missing one or more instruction files (a wiring gap a later generate slice fills). */\n incomplete: { repo: string; missing: string[] }[];\n /** Repo paths that could not be resolved / are not usable git repos. */\n unreachable: string[];\n /** True when there are no risks, no unknown visibility, and no unreachable repos. */\n ok: boolean;\n};\n\n/** Whether a visibility exposes git history to the public (so tracked instruction files leak). */\nfunction isPublicFacing(v: RepoVisibility | undefined): v is \"public\" | \"future-public\" {\n return v === \"public\" || v === \"future-public\";\n}\n\n/**\n * Summarize {@link RepoWiringFacts} into the privacy-relevant verdict. A\n * public-facing repo that TRACKS an instruction file is a {@link WiringRisk}\n * (its git history can expose the private canonical it points at); a repo with\n * unset visibility cannot be judged (`unknown`); a repo missing instruction\n * files is `incomplete` (a wiring gap, not a privacy problem). `ok` is true only\n * when nothing is at risk, every repo is judgeable, and every repo is reachable.\n */\nexport function summarizeWiring(facts: RepoWiringFacts[]): WiringSummary {\n const risks: WiringRisk[] = [];\n const unknown: string[] = [];\n const incomplete: { repo: string; missing: string[] }[] = [];\n const unreachable: string[] = [];\n\n for (const f of facts) {\n if (!f.reachable) {\n unreachable.push(f.path);\n continue;\n }\n if (isPublicFacing(f.visibility)) {\n for (const file of f.instructionFiles) {\n if (file.tracked) risks.push({ repo: f.path, visibility: f.visibility, file: file.name });\n }\n } else if (f.visibility === undefined) {\n unknown.push(f.path);\n }\n const missing = f.instructionFiles.filter((file) => !file.present).map((file) => file.name);\n if (missing.length > 0) incomplete.push({ repo: f.path, missing });\n }\n\n return {\n repos: facts,\n risks,\n unknown,\n incomplete,\n unreachable,\n ok: risks.length === 0 && unknown.length === 0 && unreachable.length === 0,\n };\n}\n","/**\n * Plan the symlinks a project's throwaway \"view\" needs (the generation step\n * after the instruction-file symlinks in the \"saddle\" model). When a project\n * has 2+ repos, basou generates a view directory that aggregates every roster\n * repo via one symlink each, named by the repo's basename and pointing at the\n * repo (relative to the view):\n *\n * <view>/app -> ../app\n * <view>/anchor -> ../anchor (the anchor's \".\" entry is aggregated here too,\n * <view>/app-site -> ../app-site unlike the instruction symlinks)\n *\n * The view is git-unmanaged and regenerable; its location is declared once\n * (`workspace.view` in the manifest) and its contents are derived from the\n * roster. This is the pure planner: it judges already-gathered, per-repo facts\n * (does the view link exist? does it point at the repo?) and reports only what\n * is MISSING. It never overwrites an existing file or repoints a link that\n * points elsewhere — those surface as conflicts for the operator to resolve by\n * hand (non-destructive). The realpath / readlink / symlink I/O is the caller's\n * job.\n *\n * Pruning stray entries already in the view IS now in scope (see `toPrune` /\n * `strayUnknown` and {@link ExistingViewLink}): the ownership model that tells an\n * orphaned repo link from the view's own instruction files / local state lives\n * here. Still NOT in scope: generating the view's own instruction files.\n */\n\nimport { normalizeRelativePath as normalize } from \"./relative-path.js\";\n\n/**\n * The on-disk state of one repo's view symlink. `correct` = the expected link\n * exists (idempotent skip); `missing` = nothing there (ENOENT) so it can be\n * created; `mismatch` = a symlink pointing elsewhere; `occupied` = a real file\n * or directory; `blocked` = the path could not be inspected (a non-ENOENT lstat\n * error, e.g. a parent component is a file). Only `missing` is actionable.\n */\nexport type ViewLinkState = \"correct\" | \"missing\" | \"mismatch\" | \"occupied\" | \"blocked\";\n\n/** The gathered facts for one roster repo's place in the view. */\nexport type ViewRepoFact = {\n /** Roster repo path (relative to the manifest root), e.g. \".\", \"../basou\". */\n path: string;\n /**\n * False when the repo cannot be aggregated into the view: its path did not\n * resolve on disk, or it resolves to the view directory itself (a self-link).\n */\n reachable: boolean;\n /** The view symlink name (the repo's basename), e.g. \"basou\". Set when reachable. */\n linkName?: string;\n /** The relative target the view link should have, e.g. \"../basou\". Set when reachable. */\n expectedTarget?: string;\n /** On-disk state of `<view>/<linkName>`. Set when reachable. */\n state?: ViewLinkState;\n /** The link's current target, present only when `state` is `mismatch`. */\n actualTarget?: string;\n};\n\n/**\n * An existing view entry that is not what we would generate: a symlink pointing\n * elsewhere (`mismatch`), a real file/directory (`occupied`), or an\n * uninspectable path (`blocked`). Surfaced, never overwritten.\n */\nexport type ViewConflict = {\n name: string;\n reason: \"mismatch\" | \"occupied\" | \"blocked\";\n /** The conflicting link's current target, present only when `reason` is `mismatch`. */\n actualTarget?: string;\n};\n\n/**\n * Two or more DISTINCT roster repos whose basename is the same, so they would\n * collide on a single `<view>/<basename>` link. Neither is auto-wired; the\n * operator must disambiguate.\n */\nexport type ViewCollision = {\n linkName: string;\n repos: string[];\n};\n\n/**\n * An entry actually present in the view directory, pre-classified by the caller's\n * filesystem probe, for stray detection (the inverse of generation). Only\n * symlinks are candidates — a real file or directory is never the caller's to\n * remove and is not gathered here. `kind` tells the planner whether a symlink not\n * tied to any roster repo is a basou-generated repo link safe to prune:\n *\n * - `repo`: a relative target that follows to an existing git repository — a\n * directory containing a `.git` entry, whether a directory OR a gitdir-pointer\n * FILE (git worktrees and submodules), matching the project family's repo test\n * (`existsSync(<dir>/.git)`, as `adopt`/`wiring` use). Exactly what\n * {@link planWorkspaceView}'s `toCreate` produces. A stray of this kind is prunable.\n * - `broken`: a relative target that does not resolve on disk (e.g. the repo was\n * moved/deleted). Reported, never auto-pruned (we cannot confirm it was ours).\n * - `non-repo`: a relative target that resolves to a non-repository path (a file,\n * or a directory without `.git`). Reported, never auto-pruned.\n * - `absolute`: an absolute target. basou never writes absolute view links, so it\n * is not ours. Reported, never auto-pruned.\n *\n * The caller filters out the view's OWN instruction-file symlinks (e.g. a top-level\n * `AGENTS.md`/`CLAUDE.md`) before classifying, so they never surface as strays.\n */\nexport type ExistingViewLink = {\n name: string;\n target: string;\n kind: \"repo\" | \"broken\" | \"non-repo\" | \"absolute\";\n};\n\n/**\n * A view symlink not tied to any current roster repo that was NOT auto-pruned\n * because we could not confirm it is a basou-generated repo link. Surfaced for\n * the operator to resolve by hand; never removed.\n */\nexport type ViewStrayUnknown = {\n name: string;\n target: string;\n reason: \"broken\" | \"non-repo\" | \"absolute\";\n};\n\nexport type WorkspaceViewPlan = {\n /** The view symlinks to create (the `missing` ones), as name + relative target. */\n toCreate: { name: string; target: string }[];\n /** Existing entries that block generation and are left untouched. */\n conflicts: ViewConflict[];\n /** Distinct repos colliding on one view link name (not auto-wired). */\n collisions: ViewCollision[];\n /** Roster repo paths that cannot be aggregated: unresolved on disk, or resolving to the view itself. */\n unreachable: string[];\n /**\n * Stray basou-generated repo links to remove: a view symlink whose name is not\n * a current roster repo and whose relative target follows to a git repository.\n * Removed only under `--prune` (its own opt-in, separate from `--apply`).\n */\n toPrune: { name: string; target: string }[];\n /**\n * Stray view symlinks not tied to any roster repo that we did NOT recognize as\n * a basou-generated repo link (broken, non-repo, or absolute target). Reported,\n * never auto-pruned.\n */\n strayUnknown: ViewStrayUnknown[];\n /** Count of repos whose view link is already correct (for the report). */\n correctCount: number;\n /**\n * True only when nothing needs creating, there are no conflicts, no collisions,\n * no unreachable repos, AND no strays (prunable or unknown) — so a clean \"view\n * in sync\" verdict is never claimed while a repo was blocked, ambiguous, could\n * not be resolved, or the view still carries an entry the roster no longer backs.\n */\n ok: boolean;\n};\n\n/**\n * Compute the {@link WorkspaceViewPlan} from per-repo facts. For each declared,\n * reachable, non-colliding repo: a `missing` view link becomes a create, a\n * `mismatch`/`occupied`/`blocked` link becomes a {@link ViewConflict} (never a\n * create — we do not overwrite), and a `correct` link is counted. The view\n * aggregates exactly the DECLARED roster — the anchor is aggregated when present\n * as its `.` entry (which `adopt` always adds) and, unlike the instruction\n * symlinks, is NOT skipped; it is not implicitly injected when absent (the roster\n * is the single source of truth). An unresolvable repo is `unreachable`, and two\n * distinct repos sharing a basename are a {@link ViewCollision} (neither\n * auto-wired).\n *\n * Stray detection (the inverse of generation): given the entries actually present\n * in the view (`existing`), any symlink whose name is NOT owned by a declared\n * roster repo is a stray. A stray classified `repo` (a relative target following to\n * a git repository — basou's own generation shape) goes to `toPrune` (removed only\n * under the separate `--prune` opt-in); a stray we cannot confirm is ours (broken,\n * non-repo, or absolute target) goes to `strayUnknown` (reported, never removed).\n *\n * Ownership (what is NEVER a stray): a name is owned if it is the link name of a\n * reachable roster repo OR appears in `rosterNames` — the basenames of EVERY\n * declared roster entry, supplied independent of reachability. The reachability-\n * independent set is load-bearing: a roster repo whose path transiently fails to\n * resolve (an unmounted volume, a mid-edit symlinked parent, an uncloned sibling)\n * still owns its view link name, so its live link is never mislabeled a stray and\n * pruned. (A roster repo's link reached under a DIFFERENT name — e.g. an aliased\n * roster path — is excluded by the caller before it reaches `existing`, by matching\n * the link's resolved target against the roster's resolved repos.) `existing` and\n * `rosterNames` default to empty, so a caller that does not scan the view gets the\n * original create-only plan.\n *\n * Robustness (mirroring the instruction-symlink planner):\n * - Facts are deduped by normalized path (a repo declared twice yields one link,\n * never a duplicate `symlinkSync` → EEXIST or a duplicate report entry).\n * - `ok` is true only when there is genuinely nothing to do, every repo was\n * resolvable and unambiguous, and the view carries no stray.\n */\nexport function planWorkspaceView(\n facts: ViewRepoFact[],\n existing: ExistingViewLink[] = [],\n rosterNames: string[] = [],\n): WorkspaceViewPlan {\n // Dedup by normalized path (first declaration wins).\n const deduped: ViewRepoFact[] = [];\n const seenPath = new Set<string>();\n for (const f of facts) {\n const key = normalize(f.path);\n if (seenPath.has(key)) continue;\n seenPath.add(key);\n deduped.push(f);\n }\n\n // Detect basename collisions among the reachable repos (distinct paths sharing\n // one link name would clobber a single `<view>/<basename>`).\n const byLinkName = new Map<string, string[]>();\n for (const f of deduped) {\n if (!f.reachable || f.linkName === undefined) continue;\n const repos = byLinkName.get(f.linkName) ?? [];\n repos.push(f.path);\n byLinkName.set(f.linkName, repos);\n }\n const collisions: ViewCollision[] = [];\n const collidingPaths = new Set<string>();\n for (const [linkName, repos] of byLinkName) {\n if (repos.length > 1) {\n collisions.push({ linkName, repos });\n for (const r of repos) collidingPaths.add(r);\n }\n }\n\n const toCreate: { name: string; target: string }[] = [];\n const conflicts: ViewConflict[] = [];\n const unreachable: string[] = [];\n let correctCount = 0;\n\n for (const f of deduped) {\n if (!f.reachable) {\n unreachable.push(f.path);\n continue;\n }\n if (collidingPaths.has(f.path)) continue; // surfaced as a collision; not auto-wired\n if (f.linkName === undefined || f.expectedTarget === undefined || f.state === undefined) {\n continue;\n }\n\n if (f.state === \"missing\") {\n toCreate.push({ name: f.linkName, target: f.expectedTarget });\n } else if (f.state === \"mismatch\") {\n conflicts.push({\n name: f.linkName,\n reason: \"mismatch\",\n ...(f.actualTarget !== undefined ? { actualTarget: f.actualTarget } : {}),\n });\n } else if (f.state === \"occupied\") {\n conflicts.push({ name: f.linkName, reason: \"occupied\" });\n } else if (f.state === \"blocked\") {\n conflicts.push({ name: f.linkName, reason: \"blocked\" });\n } else {\n correctCount += 1; // \"correct\"\n }\n }\n\n // Stray detection: every declared roster repo \"owns\" its link name and must\n // never have its link pruned — even a colliding one (the repo still wants it)\n // and even an unreachable one (a transient resolution failure must not expose a\n // live link to deletion). Ownership is the union of reachable repos' resolved\n // link names and EVERY declared entry's basename (`rosterNames`, reachability-\n // independent). Any existing view symlink whose name is outside that set is a\n // stray candidate.\n const ownedNames = new Set<string>(rosterNames);\n for (const f of deduped) {\n if (f.reachable && f.linkName !== undefined) ownedNames.add(f.linkName);\n }\n\n const toPrune: { name: string; target: string }[] = [];\n const strayUnknown: ViewStrayUnknown[] = [];\n const seenExisting = new Set<string>();\n for (const e of existing) {\n if (ownedNames.has(e.name)) continue; // a declared repo's own link, not a stray\n if (seenExisting.has(e.name)) continue; // a name can appear once on disk; guard anyway\n seenExisting.add(e.name);\n if (e.kind === \"repo\") {\n toPrune.push({ name: e.name, target: e.target });\n } else {\n strayUnknown.push({ name: e.name, target: e.target, reason: e.kind });\n }\n }\n\n return {\n toCreate,\n conflicts,\n collisions,\n unreachable,\n toPrune,\n strayUnknown,\n correctCount,\n ok:\n toCreate.length === 0 &&\n conflicts.length === 0 &&\n collisions.length === 0 &&\n unreachable.length === 0 &&\n toPrune.length === 0 &&\n strayUnknown.length === 0,\n };\n}\n","import { join } from \"node:path\";\nimport {\n enumerateApprovals,\n type LoadedApproval,\n loadApproval,\n} from \"../approval/approval-store.js\";\nimport { type ReplayWarning, replayEvents } from \"../events/event-replay.js\";\nimport { type ChainVerdictStatus, verifyEventsChain } from \"../events/verify.js\";\nimport { formatDurationMs } from \"../lib/format-duration.js\";\nimport type {\n ApprovalStatus,\n RiskLevel,\n SessionSourceKind,\n SessionStatus,\n TaskStatus,\n} from \"../schemas/index.js\";\nimport { computeWorkStats, type StatusCount } from \"../stats/work-stats.js\";\nimport type { BasouPaths } from \"../storage/basou-dir.js\";\nimport {\n loadSessionEntries,\n type SessionEntry,\n type SessionSkipReason,\n} from \"../storage/sessions.js\";\nimport { loadTaskEntries, type TaskSkipReason } from \"../storage/tasks.js\";\n\n/**\n * Caps on how many items each section lists in the rendered markdown. The\n * structured `ReportData` always keeps the FULL set (machine consumers get\n * everything); only the human-facing markdown truncates (with a `... +N more`\n * line) so a report over a large workspace stays readable and \"簡易\".\n */\nconst CHANGED_FILES_MARKDOWN_LIMIT = 50;\nconst DECISIONS_MARKDOWN_LIMIT = 20;\nconst SESSIONS_MARKDOWN_LIMIT = 30;\nconst TASKS_MARKDOWN_LIMIT = 30;\nconst APPROVALS_MARKDOWN_LIMIT = 30;\n\n/** Render order for the session-status breakdown (most-relevant-first). */\nconst SESSION_STATUS_ORDER: readonly SessionStatus[] = [\n \"completed\",\n \"failed\",\n \"running\",\n \"waiting_approval\",\n \"interrupted\",\n \"initialized\",\n \"imported\",\n \"archived\",\n];\n\n/** Render order for the task-status breakdown. */\nconst TASK_STATUS_ORDER: readonly TaskStatus[] = [\"planned\", \"in_progress\", \"done\", \"cancelled\"];\n\nexport type ReportRendererInput = {\n paths: BasouPaths;\n /** ISO timestamp stamped into the report header and used as the clock. */\n nowIso: string;\n /** Optional subject line surfaced in the report title. */\n title?: string;\n /**\n * IANA timezone passed through to {@link computeWorkStats} (it labels the\n * time figures with the zone). The CLI omits this (host default); tests and\n * the SDK pass a fixed value for deterministic output. [Codex #5]\n */\n timeZone?: string;\n onWarning?: (warning: ReplayWarning, sessionId: string) => void;\n onSessionSkip?: (sessionId: string, reason: SessionSkipReason) => void;\n onTaskSkip?: (taskId: string, reason: TaskSkipReason) => void;\n};\n\nexport type ReportSessionItem = {\n id: string;\n label: string | null;\n status: SessionStatus;\n source: SessionSourceKind;\n startedAt: string;\n activeMs: number;\n outputTokens: number;\n};\n\nexport type ReportDecisionItem = {\n id: string;\n title: string;\n occurredAt: string;\n /** True when a later `decision_voided` event retracted this decision. */\n voided?: boolean;\n /** True when the decision was recorded as a strategic track (`kind: \"track\"`). */\n track?: boolean;\n};\nexport type ReportTaskItem = { id: string; title: string; status: TaskStatus };\nexport type ReportApprovalItem = {\n id: string;\n reason: string;\n status: ApprovalStatus;\n riskLevel: RiskLevel;\n};\nexport type TaskStatusCount = { status: TaskStatus; count: number };\n\n/**\n * Curated, purpose-built structured shape behind `basou report generate\n * --json`. Deliberately NOT the full {@link WorkStatsResult} — report's JSON\n * stays a stable contract decoupled from the stats schema. Field names avoid\n * the word \"billable\": a report is a neutral work-explanation export, not a\n * billing artifact. [Codex #2]\n */\nexport type ReportData = {\n generatedAt: string;\n title?: string;\n /** Earliest session start .. latest session end (or `now` for open sessions). */\n period: { from: string | null; to: string | null };\n sessions: { total: number; byStatus: StatusCount[]; items: ReportSessionItem[] };\n volume: {\n outputTokens: number;\n reasoningTokens: number;\n commandCount: number;\n fileChangedCount: number;\n decisionCount: number;\n tokensAvailable: boolean;\n };\n time: {\n activeMs: number;\n machineActiveMs: number;\n machineAvailable: boolean;\n spanMs: number;\n commandTimeMs: number;\n timeZone: string;\n };\n decisions: { count: number; items: ReportDecisionItem[] };\n approvals: {\n pending: number;\n approved: number;\n rejected: number;\n expired: number;\n items: ReportApprovalItem[];\n };\n tasks: { total: number; byStatus: TaskStatusCount[]; items: ReportTaskItem[] };\n /** Union of related files across non-`import` sessions (full; markdown truncates). */\n changedFiles: string[];\n integrity: {\n total: number;\n verified: number;\n unchained: number;\n empty: number;\n incomplete: number;\n in_progress: number;\n tampered: number;\n /** Session ids whose chain is `tampered`, surfaced for follow-up. */\n tamperedSessions: string[];\n };\n};\n\nexport type ReportRendererResult = { body: string; data: ReportData };\n\n/**\n * Render a neutral \"work report\" — a point-in-time export that explains the\n * work captured in a workspace: how much, what was decided / approved /\n * undertaken, which files changed, and whether the local provenance is\n * internally consistent. It composes existing read primitives only and writes\n * nothing; the caller chooses where `body` goes (stdout / a file) and whether\n * to emit the structured `data` as JSON.\n *\n * Warning surfaces mirror the sibling renderers: `loadSessionEntries` (suspect\n * classification) and the decision-aggregation replay (with the same\n * unreadable-skip wrapper as `decisions-renderer.ts`) report through the\n * callbacks. {@link computeWorkStats} runs SILENTLY here — it re-reads the same\n * sessions/events, so surfacing its warnings too would double-emit. [Codex #6]\n */\nexport async function renderReport(input: ReportRendererInput): Promise<ReportRendererResult> {\n const now = new Date(input.nowIso);\n\n // Track which sessions already surfaced `events_jsonl_unreadable` so a\n // non-running session whose log is unreadable still warns once (not twice,\n // not zero times) across the suspect pass and the decision replay.\n const unreadableEmitted = new Set<string>();\n const wrappedSkip: (sid: string, reason: SessionSkipReason) => void = (sid, reason) => {\n if (reason === \"events_jsonl_unreadable\") unreadableEmitted.add(sid);\n input.onSessionSkip?.(sid, reason);\n };\n\n const loadOpts: Parameters<typeof loadSessionEntries>[1] = { now, onSkip: wrappedSkip };\n if (input.onWarning !== undefined) loadOpts.onWarning = input.onWarning;\n const entries = await loadSessionEntries(input.paths, loadOpts);\n\n // Volume / time / per-session active+tokens. Silent: the warning surface is\n // the two passes above (this re-reads the same data).\n const statsInput: Parameters<typeof computeWorkStats>[0] = { paths: input.paths, now };\n if (input.timeZone !== undefined) statsInput.timeZone = input.timeZone;\n const stats = await computeWorkStats(statsInput);\n const statsBySession = new Map(stats.sessions.map((s) => [s.sessionId, s]));\n\n // Decisions: replicate decisions-renderer's full collection — the\n // unreadable-skip wrapper AND the (occurred_at, id) sort, not just the loop.\n const decisions: ReportDecisionItem[] = [];\n // decision_ids retracted by a later `decision_voided` event; marked on the\n // items below so the report annotates them instead of presenting a retracted\n // decision as if it still stands (mirrors decisions.md / orient / handoff).\n const voidedDecisionIds = new Set<string>();\n for (const entry of entries) {\n const sessionDir = join(input.paths.sessions, entry.sessionId);\n try {\n for await (const ev of replayEvents(sessionDir, {\n onWarning: (w) => input.onWarning?.(w, entry.sessionId),\n })) {\n if (ev.type === \"decision_recorded\") {\n decisions.push({\n id: ev.decision_id,\n title: ev.title,\n occurredAt: ev.occurred_at,\n ...(ev.kind === \"track\" ? { track: true } : {}),\n });\n } else if (ev.type === \"decision_voided\") {\n voidedDecisionIds.add(ev.decision_id);\n }\n }\n } catch {\n if (!unreadableEmitted.has(entry.sessionId)) {\n wrappedSkip(entry.sessionId, \"events_jsonl_unreadable\");\n }\n }\n }\n for (const d of decisions) {\n if (voidedDecisionIds.has(d.id)) d.voided = true;\n }\n decisions.sort((a, b) => {\n const c = Date.parse(a.occurredAt) - Date.parse(b.occurredAt);\n return c !== 0 ? c : a.id.localeCompare(b.id);\n });\n\n // Tasks.\n const taskLoadOpts: Parameters<typeof loadTaskEntries>[1] = {};\n if (input.onTaskSkip !== undefined) taskLoadOpts.onSkip = input.onTaskSkip;\n const taskEntries = await loadTaskEntries(input.paths, taskLoadOpts);\n const taskItems: ReportTaskItem[] = taskEntries.map((t) => ({\n id: t.task.task.id,\n title: t.task.task.title,\n status: t.task.task.status,\n }));\n const tasksByStatus = tallyTaskStatus(taskItems);\n\n // Approvals: dedupe a stale pending id that is also resolved (resolved wins),\n // then tally by status (mirrors the SDK's listApprovals dedupe).\n const approvalIds = await enumerateApprovals(input.paths);\n const resolvedSet = new Set(approvalIds.resolved);\n const pendingIds = approvalIds.pending.filter((id) => !resolvedSet.has(id));\n const loadedApprovals = (\n await Promise.all(\n [...pendingIds, ...approvalIds.resolved].map((id) => loadApproval(input.paths, id)),\n )\n ).filter((a): a is LoadedApproval => a !== null);\n const approvalItems: ReportApprovalItem[] = loadedApprovals.map((a) => ({\n id: a.approval.id,\n reason: a.approval.reason,\n status: a.approval.status,\n riskLevel: a.approval.risk_level,\n }));\n const approvalCounts = { pending: 0, approved: 0, rejected: 0, expired: 0 };\n for (const a of approvalItems) approvalCounts[a.status] += 1;\n\n // Changed files: union over NON-import sessions only, so cross-workspace\n // round-trip imports don't dominate (matches handoff's precedent). [Codex #4]\n const changedSet = new Set<string>();\n for (const entry of entries) {\n if (entry.session.session.source.kind === \"import\") continue;\n for (const f of entry.session.session.related_files) changedSet.add(f);\n }\n const changedFiles = [...changedSet].sort();\n\n // Integrity: verify each session's chain and tally by verdict. A session\n // whose events.jsonl is unreadable (a non-ENOENT I/O error) makes\n // verifyEventsChain throw; surface it as a skip and leave it out of the tally\n // so a single bad file never fails the whole report (a successful render must\n // exit 0). `total` therefore counts only the sessions that could be verified.\n const integrity = {\n total: 0,\n verified: 0,\n unchained: 0,\n empty: 0,\n incomplete: 0,\n in_progress: 0,\n tampered: 0,\n tamperedSessions: [] as string[],\n };\n for (const entry of entries) {\n const verdict = await verifyEventsChain(input.paths, entry.sessionId).catch(() => null);\n if (verdict === null) {\n if (!unreadableEmitted.has(entry.sessionId)) {\n wrappedSkip(entry.sessionId, \"events_jsonl_unreadable\");\n }\n continue;\n }\n integrity.total += 1;\n integrity[verdict.status] += 1;\n if (verdict.status === \"tampered\") integrity.tamperedSessions.push(entry.sessionId);\n }\n\n // Session table rows + period, from the per-session stats joined onto the\n // canonical session list (newest-first).\n const sessionItems: ReportSessionItem[] = [...entries]\n .sort(\n (a, b) => Date.parse(b.session.session.started_at) - Date.parse(a.session.session.started_at),\n )\n .map((e) => {\n const w = statsBySession.get(e.sessionId);\n return {\n id: e.sessionId,\n label: e.session.session.label ?? null,\n status: e.session.session.status,\n source: e.session.session.source.kind,\n startedAt: e.session.session.started_at,\n activeMs: w?.activeTimeMs ?? 0,\n outputTokens: w?.tokens.output ?? 0,\n };\n });\n const period = computePeriod(entries, input.nowIso);\n\n const t = stats.totals;\n const data: ReportData = {\n generatedAt: input.nowIso,\n ...(input.title !== undefined ? { title: input.title } : {}),\n period,\n sessions: { total: entries.length, byStatus: stats.byStatus, items: sessionItems },\n volume: {\n outputTokens: t.tokens.output,\n reasoningTokens: t.tokens.reasoning,\n commandCount: t.commandCount,\n fileChangedCount: t.fileChangedCount,\n decisionCount: t.decisionCount,\n tokensAvailable: t.tokensAvailable,\n },\n time: {\n activeMs: t.billableActiveTimeMs,\n machineActiveMs: t.machineActiveTimeMs,\n machineAvailable: t.machineActiveAvailable,\n spanMs: t.sessionSpanMs,\n commandTimeMs: t.commandTimeMs,\n timeZone: stats.timeZone,\n },\n decisions: { count: decisions.length, items: decisions },\n approvals: { ...approvalCounts, items: approvalItems },\n tasks: { total: taskEntries.length, byStatus: tasksByStatus, items: taskItems },\n changedFiles,\n integrity,\n };\n\n return { body: formatReportBody(data), data };\n}\n\nfunction computePeriod(\n entries: ReadonlyArray<SessionEntry>,\n nowIso: string,\n): { from: string | null; to: string | null } {\n if (entries.length === 0) return { from: null, to: null };\n let from = entries[0]?.session.session.started_at ?? nowIso;\n let to = nowIso;\n let sawEnd = false;\n for (const e of entries) {\n const s = e.session.session.started_at;\n if (Date.parse(s) < Date.parse(from)) from = s;\n const end = e.session.session.ended_at ?? nowIso;\n if (!sawEnd || Date.parse(end) > Date.parse(to)) {\n to = end;\n sawEnd = true;\n }\n }\n // Guard a clock-skewed session (ended_at < started_at) from producing a\n // reversed window where `to` precedes `from`.\n if (Date.parse(to) < Date.parse(from)) to = from;\n return { from, to };\n}\n\nfunction tallyTaskStatus(items: ReadonlyArray<ReportTaskItem>): TaskStatusCount[] {\n const counts = new Map<TaskStatus, number>();\n for (const i of items) counts.set(i.status, (counts.get(i.status) ?? 0) + 1);\n return TASK_STATUS_ORDER.filter((s) => (counts.get(s) ?? 0) > 0).map((status) => ({\n status,\n count: counts.get(status) as number,\n }));\n}\n\nfunction formatReportBody(data: ReportData): string {\n const lines: string[] = [];\n const titleSuffix = data.title !== undefined ? ` — ${data.title}` : \"\";\n lines.push(`# Report${titleSuffix}`);\n lines.push(\"\");\n const periodSuffix =\n data.period.from !== null && data.period.to !== null\n ? ` (${data.period.from.slice(0, 10)}..${data.period.to.slice(0, 10)})`\n : \"\";\n lines.push(`> Generated at ${data.generatedAt}${periodSuffix}`);\n lines.push(\"\");\n\n // Summary\n lines.push(\"## 概要\");\n lines.push(\"\");\n lines.push(`- ${formatSessionsLine(data)}`);\n lines.push(\n `- Active time ${formatDurationMs(data.time.activeMs)}, ${formatInt(data.volume.outputTokens)} output tokens`,\n );\n lines.push(\"\");\n\n // Volume + time\n lines.push(\"## 作業量\");\n lines.push(\"\");\n const tokenCaveat = data.volume.tokensAvailable ? \"\" : \" (no token data captured)\";\n lines.push(`- Output tokens: ${formatInt(data.volume.outputTokens)}${tokenCaveat}`);\n if (data.volume.reasoningTokens > 0) {\n lines.push(`- Reasoning tokens: ${formatInt(data.volume.reasoningTokens)} (Codex)`);\n }\n lines.push(\n `- Actions: ${data.volume.commandCount} commands, ${data.volume.fileChangedCount} files, ${data.volume.decisionCount} decisions`,\n );\n lines.push(\n `- Active time: ${formatDurationMs(data.time.activeMs)} (union; idle gaps > 5m excluded; tz ${data.time.timeZone})`,\n );\n if (data.time.machineAvailable) {\n lines.push(\n `- Model working: ${formatDurationMs(data.time.machineActiveMs)} (model compute, subset of active)`,\n );\n }\n lines.push(`- Span: ${formatDurationMs(data.time.spanMs)} (total elapsed)`);\n lines.push(\"\");\n\n // Decisions — the most recent ones (the report explains what was decided;\n // ids live in --json, omitted here to keep the human narrative clean and\n // because batch-imported ids share a ULID timestamp prefix).\n lines.push(\"## 判断\");\n lines.push(\"\");\n if (data.decisions.items.length === 0) {\n lines.push(\"(no decisions recorded yet)\");\n } else {\n const total = data.decisions.items.length;\n const shown =\n total > DECISIONS_MARKDOWN_LIMIT\n ? data.decisions.items.slice(-DECISIONS_MARKDOWN_LIMIT)\n : data.decisions.items;\n if (total > DECISIONS_MARKDOWN_LIMIT) {\n lines.push(`(showing the ${DECISIONS_MARKDOWN_LIMIT} most recent of ${total})`);\n lines.push(\"\");\n }\n for (const d of shown) {\n const trackTag = d.track === true ? \" [track]\" : \"\";\n const voidedTag = d.voided === true ? \" (voided)\" : \"\";\n lines.push(`- ${d.occurredAt.slice(0, 10)} · ${d.title}${trackTag}${voidedTag}`);\n }\n }\n lines.push(\"\");\n\n // Approvals\n lines.push(\"## 承認\");\n lines.push(\"\");\n if (data.approvals.items.length === 0) {\n lines.push(\"(none)\");\n } else {\n const a = data.approvals;\n lines.push(\n `Pending ${a.pending} · Approved ${a.approved} · Rejected ${a.rejected} · Expired ${a.expired}`,\n );\n lines.push(\"\");\n for (const item of data.approvals.items.slice(0, APPROVALS_MARKDOWN_LIMIT)) {\n lines.push(`- ${item.reason} (${item.status}, ${item.riskLevel})`);\n }\n const overflow = data.approvals.items.length - APPROVALS_MARKDOWN_LIMIT;\n if (overflow > 0) lines.push(`- ... +${overflow} more`);\n }\n lines.push(\"\");\n\n // Tasks\n lines.push(\"## タスク\");\n lines.push(\"\");\n if (data.tasks.items.length === 0) {\n lines.push(\"(no tasks recorded yet)\");\n } else {\n const breakdown = data.tasks.byStatus.map((s) => `${s.status} ${s.count}`).join(\", \");\n lines.push(`Tasks: ${data.tasks.total} (${breakdown})`);\n lines.push(\"\");\n for (const item of data.tasks.items.slice(0, TASKS_MARKDOWN_LIMIT)) {\n lines.push(`- ${item.title} (${item.status})`);\n }\n const overflow = data.tasks.items.length - TASKS_MARKDOWN_LIMIT;\n if (overflow > 0) lines.push(`- ... +${overflow} more`);\n }\n lines.push(\"\");\n\n // Changed files\n lines.push(\"## 変更ファイル\");\n lines.push(\"\");\n if (data.changedFiles.length === 0) {\n lines.push(\"(no related files recorded)\");\n } else {\n for (const f of data.changedFiles.slice(0, CHANGED_FILES_MARKDOWN_LIMIT)) lines.push(`- ${f}`);\n const overflow = data.changedFiles.length - CHANGED_FILES_MARKDOWN_LIMIT;\n if (overflow > 0) lines.push(`- ... +${overflow} more`);\n }\n lines.push(\"\");\n\n // Sessions — newest first. The started_at is the human row key; full ids are\n // in --json (and would collide as short ids for batch imports).\n lines.push(\"## セッション一覧\");\n lines.push(\"\");\n if (data.sessions.items.length === 0) {\n lines.push(\"(no sessions yet)\");\n } else {\n lines.push(\"| started_at | source | status | active | out tok |\");\n lines.push(\"|---|---|---|---|---|\");\n for (const s of data.sessions.items.slice(0, SESSIONS_MARKDOWN_LIMIT)) {\n lines.push(\n `| ${s.startedAt} | ${s.source} | ${s.status} | ${formatDurationMs(s.activeMs)} | ${formatInt(s.outputTokens)} |`,\n );\n }\n const overflow = data.sessions.items.length - SESSIONS_MARKDOWN_LIMIT;\n if (overflow > 0) {\n lines.push(\"\");\n lines.push(`... +${overflow} more sessions`);\n }\n }\n lines.push(\"\");\n\n // Integrity\n lines.push(\"## 整合性\");\n lines.push(\"\");\n const i = data.integrity;\n lines.push(\n `Provenance internally tamper-checked: ${i.verified} verified, ${i.unchained} unchained, ${i.empty} empty, ${i.incomplete} incomplete, ${i.in_progress} in_progress, ${i.tampered} tampered (of ${i.total} sessions).`,\n );\n lines.push(\"\");\n lines.push(\n \"This reflects internal consistency of the local event-log hash chain — not a third-party cryptographic proof.\",\n );\n if (i.tampered > 0) {\n lines.push(\"\");\n // Full ids here: a tampered verdict is actionable, so surface the exact\n // session to investigate with `basou verify --session <id>`.\n for (const id of i.tamperedSessions) lines.push(`- Tampered: ${id}`);\n }\n\n return lines.join(\"\\n\");\n}\n\nfunction formatSessionsLine(data: ReportData): string {\n const counts = new Map<SessionStatus, number>();\n for (const s of data.sessions.byStatus) counts.set(s.status, s.count);\n const breakdown = SESSION_STATUS_ORDER.filter((s) => (counts.get(s) ?? 0) > 0)\n .map((s) => `${s} ${counts.get(s)}`)\n .join(\", \");\n return breakdown !== \"\"\n ? `Sessions: ${data.sessions.total} (${breakdown})`\n : `Sessions: ${data.sessions.total}`;\n}\n\n/** \"1,234,567\" — thousands-separated, fixed en-US so output is deterministic. */\nfunction formatInt(n: number): string {\n return n.toLocaleString(\"en-US\");\n}\n\n// Re-export the verdict-status type so callers (and tests) can name it without\n// reaching into the events module.\nexport type { ChainVerdictStatus };\n","import { join } from \"node:path\";\nimport { type ReplayWarning, replayEvents } from \"../events/event-replay.js\";\nimport type { Event } from \"../schemas/event.schema.js\";\nimport type {\n Session,\n SessionMetrics,\n SessionSourceKind,\n SessionStatus,\n} from \"../schemas/session.schema.js\";\nimport type { BasouPaths } from \"../storage/basou-dir.js\";\nimport { loadSessionEntries, type SessionSkipReason } from \"../storage/sessions.js\";\nimport {\n ACTIVE_GAP_CAP_MS,\n activeTimeFromTimestamps,\n type IntervalMs,\n type IsoInterval,\n intervalsIsoToMs,\n intervalsMsToIso,\n unionDurationMs,\n} from \"./active-time.js\";\n\n// Re-exported for callers that imported the cap from this module historically.\nexport { ACTIVE_GAP_CAP_MS };\n\n/**\n * Resolve the timezone used to bucket per-day stats. Native logs are UTC, so a\n * billing day needs an explicit timezone; default to the host's local zone.\n */\nfunction resolveTimeZone(timeZone: string | undefined): string {\n if (timeZone !== undefined && timeZone.length > 0) return timeZone;\n return Intl.DateTimeFormat().resolvedOptions().timeZone;\n}\n\nexport type WorkStatsInput = {\n paths: BasouPaths;\n /** Shared clock; running sessions are measured up to this instant. */\n now: Date;\n /**\n * IANA timezone used to bucket the per-day breakdown (logs are UTC, so a\n * billing day needs an explicit zone). Defaults to the host's local zone;\n * injectable for deterministic tests.\n */\n timeZone?: string;\n onWarning?: (warning: ReplayWarning, sessionId: string) => void;\n onSessionSkip?: (sessionId: string, reason: SessionSkipReason) => void;\n};\n\n/** Which measures are meaningful for a given session / source. */\nexport type MeasureAvailability = {\n /** Always true (started_at + now bound the span). */\n span: boolean;\n /**\n * `commandTimeMs` reflects real shell time. False for `claude-code-import`,\n * whose transcript carries no per-command duration (recorded as 0).\n */\n commandTime: boolean;\n /** At least one active interval could be measured (stored or event-derived). */\n activeTime: boolean;\n /** Token totals were captured (model-usage metrics present). */\n tokens: boolean;\n /** Model compute time was captured (`machine_active_time_ms`; Codex only). */\n machineActive: boolean;\n};\n\n/** Token rollup. Zero when not captured; `reasoning` is Codex-only. */\nexport type TokenTotals = {\n output: number;\n input: number;\n cached: number;\n reasoning: number;\n};\n\n/** How a session's active time was derived. */\nexport type ActiveTimeBasis = \"engaged-turns\" | \"events\";\n\nexport type SessionWorkStats = {\n sessionId: string;\n label: string | undefined;\n status: SessionStatus;\n sourceKind: SessionSourceKind;\n startedAt: string;\n endedAt: string | undefined;\n /** ended_at absent: span is measured to `now`. */\n open: boolean;\n sessionSpanMs: number;\n commandTimeMs: number;\n activeTimeMs: number;\n /**\n * How `activeTimeMs` / `activeIntervals` were derived: `engaged-turns` from\n * the engagement timestamps stored at import (captures conversation), or\n * `events` from the action-event stream (live sessions and pre-v2 imports).\n */\n activeTimeBasis: ActiveTimeBasis;\n /**\n * Merged active wall-clock ranges. Their summed duration equals\n * `activeTimeMs`; the aggregator unions them across sessions so overlapping\n * (concurrent) work is not double-counted in billable totals.\n */\n activeIntervals: IsoInterval[];\n /**\n * Model compute time: the source's summed per-turn duration\n * (`metrics.machine_active_time_ms`). A subset of `activeTimeMs`; 0 when the\n * source records no per-turn duration (everything but Codex today).\n */\n machineActiveTimeMs: number;\n /**\n * Methodology lock copied from `metrics.active_time_method` (e.g.\n * `turn-intervals` / `engaged-turns`); undefined when active time was derived\n * from the event stream rather than stored metrics.\n */\n activeTimeMethod: string | undefined;\n commandCount: number;\n fileChangedCount: number;\n decisionCount: number;\n eventCount: number;\n tokens: TokenTotals;\n availability: MeasureAvailability;\n /** ended_at < started_at (clock skew): span was clamped to 0. */\n spanClamped: boolean;\n /** events.jsonl could not be read: action / time counts are 0 + untrustworthy. */\n eventsUnreadable: boolean;\n};\n\nexport type SourceWorkStats = {\n sourceKind: SessionSourceKind;\n sessionCount: number;\n sessionSpanMs: number;\n commandTimeMs: number;\n activeTimeMs: number;\n machineActiveTimeMs: number;\n commandCount: number;\n fileChangedCount: number;\n decisionCount: number;\n eventCount: number;\n tokens: TokenTotals;\n /** Every session of this kind reports real command time. */\n commandTimeReliable: boolean;\n /** At least one session of this kind captured token totals. */\n tokensAvailable: boolean;\n /** At least one session of this kind captured model compute time. */\n machineActiveAvailable: boolean;\n};\n\nexport type StatusCount = { status: SessionStatus; count: number };\n\n/**\n * One calendar day of the time x volume billing view. `billableActiveTimeMs` is\n * the union of active intervals starting on this date (so per-day sums to the\n * de-duplicated workspace total); volume is attributed to each session's\n * `started_at` date.\n */\nexport type DayWorkStats = {\n /** Calendar date `YYYY-MM-DD` in the report timezone. */\n date: string;\n billableActiveTimeMs: number;\n /**\n * Model compute time for sessions started on this date (summed\n * `machine_active_time_ms`). Not wall-clock-deduplicated, so — unlike\n * `billableActiveTimeMs` — concurrent sessions sum freely.\n */\n machineActiveTimeMs: number;\n sessionCount: number;\n commandCount: number;\n fileChangedCount: number;\n decisionCount: number;\n tokens: TokenTotals;\n};\n\nexport type WorkStatsTotals = {\n sessionCount: number;\n openSessionCount: number;\n sessionSpanMs: number;\n commandTimeMs: number;\n /** Naive sum of per-session active time; double-counts overlapping sessions. */\n activeTimeMs: number;\n /**\n * Billable active time: the UNION of every session's active intervals, so\n * concurrent sessions do not double-count human wall-clock. Equals\n * `activeTimeMs` when no sessions overlap, and is smaller when they do.\n */\n billableActiveTimeMs: number;\n /**\n * Workspace-wide model compute time: summed `machine_active_time_ms`. A plain\n * sum (not interval union), so it can exceed `billableActiveTimeMs` when\n * sessions ran concurrently — two models working at once is two machine-hours\n * in one wall-clock hour.\n */\n machineActiveTimeMs: number;\n commandCount: number;\n fileChangedCount: number;\n decisionCount: number;\n eventCount: number;\n tokens: TokenTotals;\n /** No `claude-code-import` sessions present, so command time is workspace-wide real. */\n commandTimeReliable: boolean;\n tokensAvailable: boolean;\n /** At least one session captured model compute time (`machine_active_time_ms`). */\n machineActiveAvailable: boolean;\n};\n\nexport type WorkStatsResult = {\n generatedAt: string;\n /** Idle-gap cap applied to active time (methodology lock). */\n activeGapCapMs: number;\n /** IANA timezone used to bucket {@link WorkStatsResult.byDay}. */\n timeZone: string;\n totals: WorkStatsTotals;\n /** Per session, started_at ascending (loadSessionEntries order). */\n sessions: SessionWorkStats[];\n bySource: SourceWorkStats[];\n byStatus: StatusCount[];\n /** Per-day time x volume billing view, date ascending. */\n byDay: DayWorkStats[];\n};\n\n// Fixed display order, mirroring the handoff renderer (+ archived appended).\nconst STATUS_ORDER: readonly SessionStatus[] = [\n \"completed\",\n \"failed\",\n \"running\",\n \"interrupted\",\n \"waiting_approval\",\n \"initialized\",\n \"imported\",\n \"archived\",\n];\n\n/**\n * Aggregate work + engaged-time across the workspace's sessions.\n *\n * Honesty note: this returns a LABELED SET of measures, not one number. Token\n * volume (when captured) is the most direct \"how much the AI produced\" signal.\n * The time measures are proxies, ordered from most to least billing-relevant:\n *\n * - `billableActiveTimeMs` (totals) is the headline for billing human harness\n * labor: the UNION of every session's active intervals, so two sessions run\n * concurrently do not bill the same wall-clock twice. `activeTimeMs` is the\n * naive sum, kept only to expose the overlap delta.\n * - Per-session active time is derived from the session's ENGAGED series. For\n * imported sessions this is the genuine engagement timestamps captured at\n * import (conversation turns plus action events), so design discussion that\n * produced few tool calls is still counted; idle gaps over `ACTIVE_GAP_CAP_MS`\n * (5 min) are not credited. Live sessions and pre-v2 imports lack that signal\n * and fall back to the action-event stream (`activeTimeBasis: \"events\"`).\n * - `sessionSpanMs` overcounts (includes idle) and `commandTimeMs` is\n * shell-execution only (0 for `claude-code-import`); both are kept as context.\n *\n * The per-day view buckets the union intervals by `timeZone` (logs are UTC, so\n * a billing day needs an explicit zone). A union interval crossing local\n * midnight is attributed to its start day; per-day time still sums to the\n * billable total. Availability flags let callers caveat each measure.\n *\n * Session enumeration goes through {@link loadSessionEntries} (the handoff /\n * decisions path), so `session.yaml`-broken sessions are skipped consistently.\n */\nexport async function computeWorkStats(input: WorkStatsInput): Promise<WorkStatsResult> {\n const { now } = input;\n const timeZone = resolveTimeZone(input.timeZone);\n // Surface events_jsonl_unreadable exactly once per session even when the\n // throw happens in our own replay loop below (verbatim from the renderers).\n const unreadableEmitted = new Set<string>();\n const wrappedSkip: (sid: string, reason: SessionSkipReason) => void = (sid, reason) => {\n if (reason === \"events_jsonl_unreadable\") unreadableEmitted.add(sid);\n input.onSessionSkip?.(sid, reason);\n };\n const loadOpts: Parameters<typeof loadSessionEntries>[1] = { now, onSkip: wrappedSkip };\n if (input.onWarning !== undefined) loadOpts.onWarning = input.onWarning;\n const entries = await loadSessionEntries(input.paths, loadOpts);\n\n const sessions: SessionWorkStats[] = [];\n for (const entry of entries) {\n const events: Event[] = [];\n let eventsUnreadable = false;\n try {\n for await (const ev of replayEvents(join(input.paths.sessions, entry.sessionId), {\n onWarning: (w) => input.onWarning?.(w, entry.sessionId),\n })) {\n events.push(ev);\n }\n } catch {\n eventsUnreadable = true;\n if (!unreadableEmitted.has(entry.sessionId)) {\n wrappedSkip(entry.sessionId, \"events_jsonl_unreadable\");\n }\n }\n sessions.push(\n sessionWorkStatsFromEvents(\n entry.sessionId,\n entry.session.session,\n events,\n now,\n eventsUnreadable,\n ),\n );\n }\n\n // Union every session's active intervals once; both the billable total and\n // the per-day view are attributed from the same merged ranges so they agree.\n const allIntervals: IntervalMs[] = [];\n for (const s of sessions) allIntervals.push(...intervalsIsoToMs(s.activeIntervals));\n const union = unionDurationMs(allIntervals);\n\n return {\n generatedAt: now.toISOString(),\n activeGapCapMs: ACTIVE_GAP_CAP_MS,\n timeZone,\n totals: computeTotals(sessions, union.ms),\n sessions,\n bySource: computeBySource(sessions),\n byStatus: computeByStatus(sessions),\n byDay: computeByDay(sessions, union.merged, timeZone),\n };\n}\n\n/**\n * Compute one session's work stats from its inner record + event list. Pure\n * and exported so a single-session surface (e.g. `basou session show`) can\n * reuse the exact same measures the workspace aggregator produces.\n */\nexport function sessionWorkStatsFromEvents(\n sessionId: string,\n inner: Session[\"session\"],\n events: ReadonlyArray<Event>,\n now: Date,\n eventsUnreadable = false,\n): SessionWorkStats {\n let commandCount = 0;\n let fileChangedCount = 0;\n let decisionCount = 0;\n let commandTimeMs = 0;\n const timestamps: number[] = [];\n for (const ev of events) {\n const t = Date.parse(ev.occurred_at);\n if (Number.isFinite(t)) timestamps.push(t);\n if (ev.type === \"command_executed\") {\n commandCount++;\n commandTimeMs += ev.duration_ms;\n } else if (ev.type === \"file_changed\") {\n fileChangedCount++;\n } else if (ev.type === \"decision_recorded\") {\n decisionCount++;\n }\n }\n const span = computeSpan(inner.started_at, inner.ended_at, now);\n const tokens = readTokens(inner.metrics);\n const active = resolveActiveTime(inner.metrics, timestamps);\n const machineActiveTimeMs = inner.metrics?.machine_active_time_ms ?? 0;\n return {\n sessionId,\n label: inner.label,\n status: inner.status,\n sourceKind: inner.source.kind,\n startedAt: inner.started_at,\n endedAt: inner.ended_at,\n open: inner.ended_at === undefined,\n sessionSpanMs: span.ms,\n commandTimeMs,\n activeTimeMs: active.ms,\n activeTimeBasis: active.basis,\n activeIntervals: intervalsMsToIso(active.intervals),\n machineActiveTimeMs,\n activeTimeMethod: inner.metrics?.active_time_method,\n commandCount,\n fileChangedCount,\n decisionCount,\n eventCount: events.length,\n tokens,\n availability: {\n span: true,\n commandTime: inner.source.kind !== \"claude-code-import\",\n activeTime: active.intervals.length > 0,\n tokens: hasTokens(tokens),\n machineActive: machineActiveTimeMs > 0,\n },\n spanClamped: span.clamped,\n eventsUnreadable,\n };\n}\n\n/**\n * Resolve a session's active time + intervals. Prefer the engaged-time\n * intervals stored at import (they capture conversation turns the event stream\n * misses); otherwise derive from the action-event timestamps. Either way\n * `ms` equals the summed interval duration.\n */\nfunction resolveActiveTime(\n metrics: SessionMetrics | undefined,\n eventTimestamps: number[],\n): { ms: number; intervals: IntervalMs[]; basis: ActiveTimeBasis } {\n const stored = metrics?.active_intervals;\n if (stored !== undefined && stored.length > 0) {\n const intervals = intervalsIsoToMs(stored);\n const ms = intervals.reduce((n, [start, end]) => n + (end - start), 0);\n return { ms, intervals, basis: \"engaged-turns\" };\n }\n const derived = activeTimeFromTimestamps(eventTimestamps, ACTIVE_GAP_CAP_MS);\n return { ms: derived.ms, intervals: derived.intervals, basis: \"events\" };\n}\n\nfunction computeSpan(\n startedAt: string,\n endedAt: string | undefined,\n now: Date,\n): { ms: number; clamped: boolean } {\n const start = Date.parse(startedAt);\n const end = endedAt !== undefined ? Date.parse(endedAt) : now.getTime();\n if (!Number.isFinite(start) || !Number.isFinite(end)) return { ms: 0, clamped: true };\n const raw = end - start;\n return raw < 0 ? { ms: 0, clamped: true } : { ms: raw, clamped: false };\n}\n\nfunction readTokens(metrics: SessionMetrics | undefined): TokenTotals {\n return {\n output: metrics?.output_tokens ?? 0,\n input: metrics?.input_tokens ?? 0,\n cached: metrics?.cached_input_tokens ?? 0,\n reasoning: metrics?.reasoning_output_tokens ?? 0,\n };\n}\n\nfunction hasTokens(t: TokenTotals): boolean {\n return t.output > 0 || t.input > 0 || t.cached > 0 || t.reasoning > 0;\n}\n\nfunction emptyTokens(): TokenTotals {\n return { output: 0, input: 0, cached: 0, reasoning: 0 };\n}\n\nfunction addTokens(a: TokenTotals, b: TokenTotals): void {\n a.output += b.output;\n a.input += b.input;\n a.cached += b.cached;\n a.reasoning += b.reasoning;\n}\n\nfunction computeTotals(\n sessions: readonly SessionWorkStats[],\n billableActiveTimeMs: number,\n): WorkStatsTotals {\n const tokens = emptyTokens();\n const totals: WorkStatsTotals = {\n sessionCount: sessions.length,\n openSessionCount: 0,\n sessionSpanMs: 0,\n commandTimeMs: 0,\n activeTimeMs: 0,\n billableActiveTimeMs,\n machineActiveTimeMs: 0,\n commandCount: 0,\n fileChangedCount: 0,\n decisionCount: 0,\n eventCount: 0,\n tokens,\n commandTimeReliable: true,\n tokensAvailable: false,\n machineActiveAvailable: false,\n };\n for (const s of sessions) {\n if (s.open) totals.openSessionCount++;\n totals.sessionSpanMs += s.sessionSpanMs;\n totals.commandTimeMs += s.commandTimeMs;\n totals.activeTimeMs += s.activeTimeMs;\n totals.machineActiveTimeMs += s.machineActiveTimeMs;\n totals.commandCount += s.commandCount;\n totals.fileChangedCount += s.fileChangedCount;\n totals.decisionCount += s.decisionCount;\n totals.eventCount += s.eventCount;\n addTokens(tokens, s.tokens);\n if (!s.availability.commandTime) totals.commandTimeReliable = false;\n if (s.availability.tokens) totals.tokensAvailable = true;\n if (s.availability.machineActive) totals.machineActiveAvailable = true;\n }\n return totals;\n}\n\nfunction computeBySource(sessions: readonly SessionWorkStats[]): SourceWorkStats[] {\n const map = new Map<SessionSourceKind, SourceWorkStats>();\n for (const s of sessions) {\n let row = map.get(s.sourceKind);\n if (row === undefined) {\n row = {\n sourceKind: s.sourceKind,\n sessionCount: 0,\n sessionSpanMs: 0,\n commandTimeMs: 0,\n activeTimeMs: 0,\n machineActiveTimeMs: 0,\n commandCount: 0,\n fileChangedCount: 0,\n decisionCount: 0,\n eventCount: 0,\n tokens: emptyTokens(),\n commandTimeReliable: true,\n tokensAvailable: false,\n machineActiveAvailable: false,\n };\n map.set(s.sourceKind, row);\n }\n row.sessionCount++;\n row.sessionSpanMs += s.sessionSpanMs;\n row.commandTimeMs += s.commandTimeMs;\n row.activeTimeMs += s.activeTimeMs;\n row.machineActiveTimeMs += s.machineActiveTimeMs;\n row.commandCount += s.commandCount;\n row.fileChangedCount += s.fileChangedCount;\n row.decisionCount += s.decisionCount;\n row.eventCount += s.eventCount;\n addTokens(row.tokens, s.tokens);\n if (!s.availability.commandTime) row.commandTimeReliable = false;\n if (s.availability.tokens) row.tokensAvailable = true;\n if (s.availability.machineActive) row.machineActiveAvailable = true;\n }\n return [...map.values()].sort((a, b) => a.sourceKind.localeCompare(b.sourceKind));\n}\n\nfunction computeByStatus(sessions: readonly SessionWorkStats[]): StatusCount[] {\n const counts = new Map<SessionStatus, number>();\n for (const s of sessions) counts.set(s.status, (counts.get(s.status) ?? 0) + 1);\n const ordered: StatusCount[] = [];\n for (const status of STATUS_ORDER) {\n const count = counts.get(status);\n if (count !== undefined && count > 0) ordered.push({ status, count });\n }\n return ordered;\n}\n\n/**\n * Build the per-day billing view. Time comes from the pre-merged union\n * intervals, attributed to each interval's start date so the per-day totals sum\n * exactly to `totals.billableActiveTimeMs`. Volume (tokens, action counts) is\n * attributed to each session's `started_at` date.\n */\nfunction computeByDay(\n sessions: readonly SessionWorkStats[],\n unionMerged: readonly IntervalMs[],\n timeZone: string,\n): DayWorkStats[] {\n const days = new Map<string, DayWorkStats>();\n const ensure = (date: string): DayWorkStats => {\n let day = days.get(date);\n if (day === undefined) {\n day = {\n date,\n billableActiveTimeMs: 0,\n machineActiveTimeMs: 0,\n sessionCount: 0,\n commandCount: 0,\n fileChangedCount: 0,\n decisionCount: 0,\n tokens: emptyTokens(),\n };\n days.set(date, day);\n }\n return day;\n };\n for (const [start, end] of unionMerged) {\n ensure(tzDate(start, timeZone)).billableActiveTimeMs += end - start;\n }\n for (const s of sessions) {\n const startedMs = Date.parse(s.startedAt);\n if (!Number.isFinite(startedMs)) continue;\n const day = ensure(tzDate(startedMs, timeZone));\n day.sessionCount++;\n day.machineActiveTimeMs += s.machineActiveTimeMs;\n day.commandCount += s.commandCount;\n day.fileChangedCount += s.fileChangedCount;\n day.decisionCount += s.decisionCount;\n addTokens(day.tokens, s.tokens);\n }\n return [...days.values()].sort((a, b) => a.date.localeCompare(b.date));\n}\n\n/** Calendar date (`YYYY-MM-DD`) of an instant in the given IANA timezone. */\nfunction tzDate(ms: number, timeZone: string): string {\n return new Intl.DateTimeFormat(\"en-CA\", {\n timeZone,\n year: \"numeric\",\n month: \"2-digit\",\n day: \"2-digit\",\n }).format(new Date(ms));\n}\n","import { existsSync, realpathSync } from \"node:fs\";\nimport { homedir } from \"node:os\";\nimport { basename, isAbsolute, join } from \"node:path\";\nimport { type ReplayWarning, replayEvents } from \"../events/event-replay.js\";\nimport type { BasouPaths } from \"../storage/basou-dir.js\";\nimport { loadSessionEntries, type SessionSkipReason } from \"../storage/sessions.js\";\n\n/**\n * Review-gap surfacer: a read-only, advisory check for the \"external\n * adversarial review before commit\" protocol. For each unit of work that landed\n * commits, it asks whether a CROSS-MODEL review session (a different vendor than\n * the one that wrote the code — here: Codex) actually examined that repo's diff\n * before the commit.\n *\n * Hard design rule, learned from killing the naive time-window v1 (which\n * false-cleared the very omission that motivated this): it NEVER emits a\n * confident \"reviewed / clear\" verdict. Temporal proximity is not binding. The\n * worst failure mode is falsely reassuring the operator that a protocol was\n * followed when it was not, so this surfaces SUSPICION and leaves the final\n * binding to a human:\n *\n * - `omission` no cross-model review of this repo in the preceding window.\n * - `near_unbound` a review session was nearby but did not examine this repo's\n * diff or any changed file (the exact class naive v1 cleared).\n * - `candidate` a review session examined this repo's diff / overlapping\n * files — listed for the human to confirm it covered THIS\n * change. NOT an automatic pass.\n * - `unknown` the repo or time could not be derived; abstain rather than\n * guess (an abstention is never counted as a clear).\n *\n * It reads only captured provenance and writes nothing.\n */\n\nexport type ReviewGapVerdict = \"omission\" | \"near_unbound\" | \"candidate\" | \"unknown\";\n\n/** A cross-model review session cited as (possibly) covering a unit of work. */\nexport type CitedReview = {\n sessionId: string;\n /** The session ran `git diff` / `git show` in the repo (examined the diff). */\n examinedDiff: boolean;\n /** Basenames of files the session read/inspected in the repo (capped). */\n files: string[];\n endedAt: string | null;\n};\n\n/** One unit of work (a committing session's commits in one repo) and its verdict. */\nexport type ReviewGapUnit = {\n repo: string;\n /** The session whose commits form this unit. */\n sessionId: string;\n commitCount: number;\n firstCommitAt: string | null;\n lastCommitAt: string | null;\n verdict: ReviewGapVerdict;\n /** For `candidate` / `near_unbound`: the review sessions considered. */\n reviews: CitedReview[];\n};\n\nexport type ReviewGapRepoSummary = {\n repo: string;\n units: number;\n omissionUnits: number;\n nearUnboundUnits: number;\n candidateUnits: number;\n unknownUnits: number;\n};\n\nexport type ReviewGapsSummary = {\n generatedAt: string;\n windowHours: number;\n /** Repos the scope was restricted to, or null when every repo was considered. */\n scope: string[] | null;\n repos: ReviewGapRepoSummary[];\n /** Units WITHOUT a binding review trail (omission + near_unbound), recent-first. */\n gaps: ReviewGapUnit[];\n /** Units WITH a review candidate, recent-first (surfaced for confirmation). */\n candidates: ReviewGapUnit[];\n /** Units whose repo/time could not be derived from the captured command; abstained, not cleared. */\n unknowns: ReviewGapUnit[];\n /** Newest captured commit considered; commits not yet imported are invisible. */\n newestCommitAt: string | null;\n};\n\n/** Strip one layer of matching surrounding quotes (e.g. `cd \"…/repo\"`). */\nfunction stripQuotes(s: string): string {\n if (s.length >= 2 && ((s[0] === '\"' && s.at(-1) === '\"') || (s[0] === \"'\" && s.at(-1) === \"'\"))) {\n return s.slice(1, -1);\n }\n return s;\n}\n\n/**\n * Per-process cache of realpath resolutions. A stored `null` records that\n * realpath FAILED for that input (the path is absent), so a repeat lookup of the\n * same absent path neither re-issues the syscall nor is mistaken for a cache\n * miss. The filesystem is assumed stable for the duration of a single command\n * run. Bounded in practice: one entry per distinct repo path seen (O(10–100)).\n */\nconst realpathCache = new Map<string, string | null>();\n\n/** realpath an absolute path, caching both success and failure; null when unresolvable. */\nfunction resolveRealpath(absPath: string): string | null {\n // Stored values are `string | null`; only an ABSENT key reads back as\n // `undefined`, so a cached failure (null) returns without re-issuing realpath.\n const cached = realpathCache.get(absPath);\n if (cached !== undefined) return cached;\n let resolved: string | null;\n try {\n resolved = realpathSync(absPath);\n } catch {\n resolved = null;\n }\n realpathCache.set(absPath, resolved);\n return resolved;\n}\n\n/** Per-process cache of git-repo-root checks, keyed by resolved (realpath) path. */\nconst repoRootCache = new Map<string, boolean>();\n\n/**\n * Whether a resolved path is a git repo root, i.e. contains a `.git` (a directory\n * for a normal clone, a file for a worktree/submodule). Used to reject a real but\n * non-repo directory (a workspace view root, `/tmp`, a scratch dir) so it never\n * becomes a binding key. A bare repo (no working tree, no `.git` child) is not\n * recognized — review-gaps tracks working-tree commits, which bare repos lack.\n */\nfunction isRepoRoot(realPath: string): boolean {\n const cached = repoRootCache.get(realPath);\n if (cached !== undefined) return cached;\n const result = existsSync(join(realPath, \".git\"));\n repoRootCache.set(realPath, result);\n return result;\n}\n\n/**\n * Normalize a path to a stable BINDING key: the canonical full path (NOT just a\n * basename), so a commit in `/u/projects/basou` and a review in\n * `/u/projects/basou` bind, while a same-named checkout elsewhere\n * (`/tmp/x/basou`) does not.\n *\n * A workspace \"view\" reaches sibling repos through symlinks\n * (`<view>/<repo> -> ../<repo>`), and commits are often run with\n * `cd <view>/<repo>`. To collapse the view-routed path and the direct path to\n * one key REGARDLESS of the view directory's name, the path is resolved with\n * realpath (which also unifies platform aliases such as macOS `/tmp` ->\n * `/private/tmp`). Only absolute paths are resolved; a relative `cd ../x` target\n * would realpath against the wrong base, so it is left to the fallback.\n *\n * A resolved path is accepted as a key only when it is an actual git repo root\n * (contains `.git`); a real but non-repo directory (a view root, `/tmp`, a\n * scratch dir) returns null so the caller abstains (`unknown`) rather than\n * mislabeling it a repo. When realpath cannot resolve the path (e.g. a historical\n * capture whose repo has since moved), it FALLS BACK to a string heuristic that\n * collapses a `*-workspace`-named view and rejects the view root itself. Returns\n * null for a non-repo / view root, an unexpanded shell var, or empty input.\n *\n * The realpath / `.git` probes are the only filesystem I/O this otherwise\n * string-pure key function performs, and their results are cached for the\n * process lifetime.\n */\nexport function normalizeRepoPath(p: string | null | undefined): string | null {\n if (!p) return null;\n let s = stripQuotes(p.trim()).replace(/\\/+$/, \"\");\n if (s.length === 0 || s === \"~\") return null;\n // expand a leading ~ so the same repo recorded as `~/projects/x` and\n // `/Users/u/projects/x` collapses to one binding key (the events capture both).\n if (s.startsWith(\"~/\")) s = homedir() + s.slice(1);\n\n // Prefer the on-disk truth: realpath follows the view's symlink so ANY view\n // name (not only `*-workspace`) collapses to the real repo path. Only absolute\n // paths are resolved; a relative target would resolve against the wrong base.\n if (isAbsolute(s)) {\n const real = resolveRealpath(s);\n if (real !== null) {\n // Resolved on disk: bind only when it is an actual git repo root. A real\n // but non-repo directory must not become a key, and must NOT fall through\n // to the string heuristic (which would mislabel `/tmp`, scratch dirs, a\n // view root) — abstain (null -> `unknown`) instead.\n return isRepoRoot(real) ? real : null;\n }\n // real === null: path absent (e.g. a moved/historical capture) -> fall\n // through to the legacy *-workspace string heuristic below.\n }\n\n // Fallback for paths not present on disk (historical/imported captures): the\n // legacy string heuristic, name-bound to `*-workspace` views.\n // a path THROUGH a *-workspace view: .../foo-workspace/foo-planning -> .../foo-planning\n s = s.replace(/\\/[^/]*-workspace\\/([^/]+)/, \"/$1\");\n const seg = s\n .split(\"/\")\n .filter((x) => x.length > 0)\n .pop();\n if (seg === undefined) return null;\n // the view dir itself is not a repo; an unexpanded shell var is not a repo\n if (/-workspace$/.test(seg) || seg.includes(\"$\")) return null;\n return s;\n}\n\n/**\n * Short repo key (the final path segment) for DISPLAY and `--scope` matching.\n * Binding uses {@link normalizeRepoPath} to avoid basename collisions; this is\n * only the human-facing label.\n */\nexport function normalizeRepoKey(p: string | null | undefined): string | null {\n const full = normalizeRepoPath(p);\n return full === null ? null : basename(full);\n}\n\n/** Files a single command read/inspected, and whether it inspected the git diff. */\nfunction inspectCommand(args: string[]): { files: string[]; examinedDiff: boolean } {\n const a = args.join(\" \");\n const files = new Set<string>();\n const examinedDiff = /\\bgit\\s+(?:diff|show|log\\s+-p|add\\s+-p)\\b/.test(a);\n for (const re of [\n /\\b(?:cat|less|bat|head|tail)\\s+([^\\s|&;<>]+)/g,\n /\\bsed\\s+-n\\s+'[^']*'\\s+([^\\s|&;<>]+)/g,\n /\\b(?:rg|grep)\\b[^|&;]*?\\s([^\\s|&;<>]+\\.[A-Za-z0-9]+)(?:\\s|$)/g,\n ]) {\n let m: RegExpExecArray | null;\n // biome-ignore lint/suspicious/noAssignInExpressions: standard regex exec loop\n while ((m = re.exec(a)) !== null) {\n const f = m[1];\n if (f !== undefined) files.add(basename(f));\n }\n }\n return { files: [...files], examinedDiff };\n}\n\n/** Repo a command effectively ran in: an explicit `cd <repo> &&` wins over cwd. */\nfunction commandRepo(args: string[], cwd: string): string | null {\n // An explicit `cd <target> &&` wins over cwd — and wins EVEN WHEN the target\n // resolves to null (a non-repo dir): the command ran there, so it must not be\n // silently re-credited to the session's cwd (which could falsely bind an\n // unrelated repo and clear a real gap). Fall back to cwd only when there was\n // no explicit `cd`.\n const cd = args.join(\" \").match(/\\bcd\\s+(\"[^\"]+\"|'[^']+'|[^\\s&]+)\\s*&&/);\n if (cd) return normalizeRepoPath(cd[1]);\n return normalizeRepoPath(cwd);\n}\n\n/** True when a captured command exited non-zero (a failure is not evidence / not landed work). */\nfunction commandFailed(exitCode: number | null): boolean {\n return exitCode !== null && exitCode !== 0;\n}\n\n/** Changed files named inline on the commit's command (`git add A B`); heuristic. */\nfunction commitFiles(args: string[]): string[] {\n const a = args.join(\" \");\n const add = a.match(/git add\\s+([^&|;]+)/);\n if (!add?.[1]) return [];\n return add[1]\n .split(/\\s+/)\n .filter((t) => /\\.[A-Za-z]/.test(t) && !t.startsWith(\"-\"))\n .map((t) => basename(t));\n}\n\ntype CommitRec = { repo: string; at: number; files: string[] };\ntype ReviewRec = {\n sessionId: string;\n endedAt: number | null;\n /** repo key -> what the review touched in it. */\n repos: Map<string, { examinedDiff: boolean; files: Set<string> }>;\n};\n\nconst REVIEW_SOURCE = \"codex-import\"; // the cross-model reviewer vendor (v1)\nconst DEFAULT_WINDOW_HOURS = 24;\n\nexport type ReviewGapsInput = {\n paths: BasouPaths;\n /** ISO \"now\"; basis for `generatedAt`. */\n nowIso: string;\n /** Restrict to these repo keys (e.g. [\"basou\"]); omit/empty = every repo seen. */\n scope?: string[];\n /** Coarse pre-filter window before a commit to look for a review; default 24h. */\n windowHours?: number;\n onWarning?: (warning: ReplayWarning, sessionId: string) => void;\n onSessionSkip?: (sessionId: string, reason: SessionSkipReason) => void;\n};\n\n/**\n * Compute the {@link ReviewGapsSummary} for a workspace. Read-only: reads\n * captured sessions / events and writes nothing.\n */\nexport async function findReviewGaps(input: ReviewGapsInput): Promise<ReviewGapsSummary> {\n const now = new Date(input.nowIso);\n const windowHours = input.windowHours ?? DEFAULT_WINDOW_HOURS;\n const scope = input.scope && input.scope.length > 0 ? input.scope : null;\n\n const loadOpts: Parameters<typeof loadSessionEntries>[1] = { now };\n if (input.onSessionSkip !== undefined) loadOpts.onSkip = input.onSessionSkip;\n if (input.onWarning !== undefined) loadOpts.onWarning = input.onWarning;\n const entries = await loadSessionEntries(input.paths, loadOpts);\n\n const reviews: ReviewRec[] = [];\n // committing session -> repo path -> commits\n const workUnits = new Map<string, Map<string, CommitRec[]>>();\n // committing session -> commit times whose repo/time could not be derived\n const unknownCommits = new Map<string, (number | null)[]>();\n\n for (const entry of entries) {\n const sessionDir = join(input.paths.sessions, entry.sessionId);\n const isReview = entry.session.session.source.kind === REVIEW_SOURCE;\n const reviewRepos = new Map<string, { examinedDiff: boolean; files: Set<string> }>();\n let reviewEnd: number | null = null;\n\n try {\n for await (const ev of replayEvents(sessionDir, {\n onWarning: (w) => input.onWarning?.(w, entry.sessionId),\n })) {\n if (ev.type !== \"command_executed\") continue;\n // A failed command is neither review evidence nor landed work.\n if (commandFailed(ev.exit_code)) continue;\n const at = Date.parse(ev.occurred_at);\n\n if (isReview) {\n // Bind to the repo the command actually ran in (an explicit `cd <repo>`\n // wins over cwd), symmetric with commit derivation, so `cd other &&\n // git diff` is not credited to the session's starting cwd.\n const repo = commandRepo(ev.args, ev.cwd);\n if (repo === null) continue;\n const ins = inspectCommand(ev.args);\n const slot = reviewRepos.get(repo) ?? { examinedDiff: false, files: new Set() };\n if (ins.examinedDiff) slot.examinedDiff = true;\n for (const f of ins.files) slot.files.add(f);\n reviewRepos.set(repo, slot);\n if (!Number.isNaN(at)) reviewEnd = reviewEnd === null ? at : Math.max(reviewEnd, at);\n continue;\n }\n\n // committing (code-author) session: collect git-commit events\n if (!ev.args.join(\" \").includes(\"git commit\")) continue;\n const repo = commandRepo(ev.args, ev.cwd);\n if (repo === null || Number.isNaN(at)) {\n // Surface as unknown rather than silently dropping an observed commit.\n const list = unknownCommits.get(entry.sessionId) ?? [];\n list.push(Number.isNaN(at) ? null : at);\n unknownCommits.set(entry.sessionId, list);\n continue;\n }\n const byRepo = workUnits.get(entry.sessionId) ?? new Map<string, CommitRec[]>();\n const list = byRepo.get(repo) ?? [];\n list.push({ repo, at, files: commitFiles(ev.args) });\n byRepo.set(repo, list);\n workUnits.set(entry.sessionId, byRepo);\n }\n } catch {\n input.onSessionSkip?.(entry.sessionId, \"events_jsonl_unreadable\");\n continue;\n }\n\n if (isReview && reviewRepos.size > 0) {\n reviews.push({ sessionId: entry.sessionId, endedAt: reviewEnd, repos: reviewRepos });\n }\n }\n\n const windowMs = windowHours * 3600 * 1000;\n const units: ReviewGapUnit[] = [];\n let newestCommit: number | null = null;\n\n for (const [sessionId, byRepo] of workUnits) {\n for (const [repoPath, commits] of byRepo) {\n const label = basename(repoPath);\n if (scope !== null && !scope.includes(label)) continue;\n const times = commits.map((c) => c.at).sort((a, b) => a - b);\n const first = times[0] ?? null;\n const last = times[times.length - 1] ?? null;\n if (last !== null) newestCommit = newestCommit === null ? last : Math.max(newestCommit, last);\n const changedFiles = new Set(commits.flatMap((c) => c.files));\n\n // candidate reviews: the SAME repo path (collision-safe), ended before this\n // unit's first commit, within the coarse window. The window is only a\n // pre-filter — binding is by examined diff / overlapping files, never by\n // temporal proximity alone.\n const before = first ?? last ?? 0;\n const nearby = reviews.filter((r) => {\n if (!r.repos.has(repoPath) || r.endedAt === null) return false;\n return r.endedAt <= before && r.endedAt >= before - windowMs;\n });\n const bound = nearby.filter((r) => {\n const touched = r.repos.get(repoPath);\n if (touched === undefined) return false;\n if (touched.examinedDiff) return true;\n for (const f of changedFiles) if (touched.files.has(f)) return true;\n return false;\n });\n\n const verdict: ReviewGapVerdict =\n bound.length > 0 ? \"candidate\" : nearby.length > 0 ? \"near_unbound\" : \"omission\";\n const cited = verdict === \"candidate\" ? bound : verdict === \"near_unbound\" ? nearby : [];\n\n units.push({\n repo: label,\n sessionId,\n commitCount: commits.length,\n firstCommitAt: first === null ? null : new Date(first).toISOString(),\n lastCommitAt: last === null ? null : new Date(last).toISOString(),\n verdict,\n reviews: cited.map((r) => ({\n sessionId: r.sessionId,\n examinedDiff: r.repos.get(repoPath)?.examinedDiff ?? false,\n files: [...(r.repos.get(repoPath)?.files ?? [])].slice(0, 8),\n endedAt: r.endedAt === null ? null : new Date(r.endedAt).toISOString(),\n })),\n });\n }\n }\n\n // Observed commits whose repo/time could not be derived become explicit\n // `unknown` units (an abstention, never a clear). They cannot be attributed to\n // a scoped repo, so they are reported only when no `--repo` scope is applied.\n if (scope === null) {\n for (const [sessionId, times] of unknownCommits) {\n const valid = times.filter((t): t is number => t !== null).sort((a, b) => a - b);\n const first = valid[0] ?? null;\n const last = valid[valid.length - 1] ?? null;\n if (last !== null) newestCommit = newestCommit === null ? last : Math.max(newestCommit, last);\n units.push({\n repo: \"(unknown)\",\n sessionId,\n commitCount: times.length,\n firstCommitAt: first === null ? null : new Date(first).toISOString(),\n lastCommitAt: last === null ? null : new Date(last).toISOString(),\n verdict: \"unknown\",\n reviews: [],\n });\n }\n }\n\n const recentFirst = (a: ReviewGapUnit, b: ReviewGapUnit): number =>\n (Date.parse(b.lastCommitAt ?? \"\") || 0) - (Date.parse(a.lastCommitAt ?? \"\") || 0);\n\n const repoKeys = [...new Set(units.map((u) => u.repo))].sort();\n const repos: ReviewGapRepoSummary[] = repoKeys.map((repo) => {\n const us = units.filter((u) => u.repo === repo);\n return {\n repo,\n units: us.length,\n omissionUnits: us.filter((u) => u.verdict === \"omission\").length,\n nearUnboundUnits: us.filter((u) => u.verdict === \"near_unbound\").length,\n candidateUnits: us.filter((u) => u.verdict === \"candidate\").length,\n unknownUnits: us.filter((u) => u.verdict === \"unknown\").length,\n };\n });\n\n return {\n generatedAt: input.nowIso,\n windowHours,\n scope,\n repos,\n gaps: units\n .filter((u) => u.verdict === \"omission\" || u.verdict === \"near_unbound\")\n .sort(recentFirst),\n candidates: units.filter((u) => u.verdict === \"candidate\").sort(recentFirst),\n unknowns: units.filter((u) => u.verdict === \"unknown\").sort(recentFirst),\n newestCommitAt: newestCommit === null ? null : new Date(newestCommit).toISOString(),\n };\n}\n","import { type ChildProcess, spawn } from \"node:child_process\";\n\nimport { findErrorCode } from \"../storage/status.js\";\n\nimport type { ProcessRunner, RunOptions, RunResult } from \"./process-runner.js\";\n\nconst DEFAULT_KILL_GRACE_MS = 5_000;\n\n/**\n * Spawn-based ProcessRunner implementation.\n *\n * Behavior:\n * - `shell: false` and `detached: false`. The process group is not\n * detached, but the OS does not guarantee the child is reaped when\n * the parent terminates abruptly; callers handle SIGINT/SIGTERM/exit\n * hooks themselves.\n * - `capture: \"buffer\"` (default): `stdio: ['pipe', 'pipe', 'pipe']`,\n * stdout / stderr are decoded as UTF-8 and accumulated as full\n * strings (no streaming callbacks).\n * - `capture: \"none\"`: `stdio: ['inherit', 'inherit', 'inherit']`, the\n * child writes directly to the parent terminal in real time and\n * `RunResult.stdout` / `stderr` are empty strings. `stdin` is\n * incompatible with this mode (the child has no writable stdin pipe)\n * and the combination is rejected before spawn.\n * - `timeout_ms` and `AbortSignal` both trigger a two-stage kill:\n * `SIGTERM`, then `SIGKILL` after `DEFAULT_KILL_GRACE_MS` (5_000 ms).\n * - A non-zero `exit_code` does not throw; it is returned via\n * `RunResult`. Spawn-time errors throw with a pathless message and\n * the original error attached as `cause`.\n *\n * Error message contract: messages never include `cwd` or absolute\n * command paths. The original errno (and any nested wrapping) is\n * preserved on `Error.cause`, allowing callers to classify with\n * `findErrorCode` when needed.\n */\nexport class ChildProcessRunner implements ProcessRunner {\n async run(command: string, args: readonly string[], options: RunOptions): Promise<RunResult> {\n validateOptions(options);\n\n if (options.signal?.aborted) {\n throw new Error(\"Process aborted before spawn\", {\n cause: options.signal.reason,\n });\n }\n\n // Freeze the invocation snapshot at spawn time so the eventual RunResult\n // reflects the call as it was issued, even if the caller mutates `args`\n // or `options` afterward.\n const snapshotCommand = command;\n const snapshotArgs: readonly string[] = [...args];\n const snapshotCwd = options.cwd;\n const captureMode = options.capture ?? \"buffer\";\n\n const started_at = new Date();\n\n let child: ChildProcess;\n try {\n child = spawn(snapshotCommand, [...snapshotArgs], {\n cwd: snapshotCwd,\n env: options.env ?? process.env,\n stdio:\n captureMode === \"none\" ? [\"inherit\", \"inherit\", \"inherit\"] : [\"pipe\", \"pipe\", \"pipe\"],\n shell: false,\n detached: false,\n });\n } catch (error: unknown) {\n throw classifySpawnError(error);\n }\n\n // Notify caller that the child exists so they can wire parent-side\n // cleanup (e.g. an `exit` hook). The runner ignores any throw from\n // the callback; the caller is responsible for keeping it side-effect\n // safe.\n if (options.onSpawn) {\n try {\n options.onSpawn(child);\n } catch {\n // intentional: do not let onSpawn failures abort the run.\n }\n }\n\n let timeoutTimer: NodeJS.Timeout | null = null;\n let killTimer: NodeJS.Timeout | null = null;\n let killed = false;\n let settled = false;\n\n const triggerKill = (): void => {\n if (killed || child.exitCode !== null) return;\n killed = true;\n child.kill(\"SIGTERM\");\n killTimer = setTimeout(() => {\n if (child.exitCode === null) {\n child.kill(\"SIGKILL\");\n }\n }, DEFAULT_KILL_GRACE_MS);\n };\n\n // Attach the abort listener immediately, then re-check `aborted` to\n // close the window between spawn() returning and addEventListener.\n const onAbort = (): void => {\n triggerKill();\n };\n options.signal?.addEventListener(\"abort\", onAbort);\n if (options.signal?.aborted) {\n triggerKill();\n }\n\n let stdout = \"\";\n let stderr = \"\";\n if (captureMode === \"buffer\") {\n // stdio is ['pipe', 'pipe', 'pipe'] so stdout/stderr/stdin are non-null.\n child.stdout?.setEncoding(\"utf8\");\n child.stderr?.setEncoding(\"utf8\");\n child.stdout?.on(\"data\", (chunk: string) => {\n stdout += chunk;\n });\n child.stderr?.on(\"data\", (chunk: string) => {\n stderr += chunk;\n });\n\n if (options.stdin !== undefined) {\n child.stdin?.end(options.stdin);\n } else {\n child.stdin?.end();\n }\n }\n // capture: \"none\" leaves stdio inherited; stdout/stderr remain \"\".\n\n if (options.timeout_ms !== undefined) {\n timeoutTimer = setTimeout(triggerKill, options.timeout_ms);\n }\n\n const cleanup = (): void => {\n if (timeoutTimer !== null) clearTimeout(timeoutTimer);\n if (killTimer !== null) clearTimeout(killTimer);\n options.signal?.removeEventListener(\"abort\", onAbort);\n };\n\n return new Promise<RunResult>((resolve, reject) => {\n child.once(\"error\", (error: Error) => {\n if (settled) return;\n settled = true;\n cleanup();\n reject(classifySpawnError(error));\n });\n child.once(\"close\", (code: number | null, signal: NodeJS.Signals | null) => {\n if (settled) return;\n settled = true;\n cleanup();\n const ended_at = new Date();\n resolve({\n command: snapshotCommand,\n args: snapshotArgs,\n cwd: snapshotCwd,\n exit_code: code,\n signal,\n stdout,\n stderr,\n started_at: started_at.toISOString(),\n ended_at: ended_at.toISOString(),\n duration_ms: ended_at.getTime() - started_at.getTime(),\n pid: child.pid ?? null,\n });\n });\n });\n }\n}\n\nfunction validateOptions(options: RunOptions): void {\n if (\n options.timeout_ms !== undefined &&\n (!Number.isFinite(options.timeout_ms) || options.timeout_ms <= 0)\n ) {\n throw new Error(\"Invalid timeout_ms\");\n }\n if (options.capture === \"none\" && options.stdin !== undefined) {\n throw new Error('Combination of capture: \"none\" and stdin is not supported');\n }\n}\n\nfunction classifySpawnError(error: unknown): Error {\n if (findErrorCode(error, \"ENOENT\")) {\n return new Error(\"Command not found\", { cause: error });\n }\n return new Error(\"Failed to spawn child process\", { cause: error });\n}\n","import { z } from \"zod\";\nimport { ApprovalSchema } from \"./approval.schema.js\";\nimport { EventSchema } from \"./event.schema.js\";\nimport { ManifestSchema } from \"./manifest.schema.js\";\nimport { SessionSchema } from \"./session.schema.js\";\nimport { SessionImportPayloadSchema } from \"./session-import.schema.js\";\nimport { StatusSchema } from \"./status.schema.js\";\nimport { TaskSchema } from \"./task.schema.js\";\nimport { TaskIndexSchema } from \"./task-index.schema.js\";\n\n/**\n * Schema version of the on-disk Basou v0.1 formats these JSON Schemas describe.\n * It tracks {@link SchemaVersionSchema} (the `schema_version` field), NOT the\n * npm package version, so the `$id` URLs stay stable while the package moves.\n */\nexport const JSON_SCHEMA_VERSION = \"0.1.0\";\n\n/** Base of every emitted schema's `$id`. The URL is a stable identifier; it\n * need not resolve (serving the schemas on basou.dev is a separate concern). */\nconst ID_BASE = `https://basou.dev/schemas/${JSON_SCHEMA_VERSION}`;\n\n/** JSON Schema draft the artifacts target (what `z.toJSONSchema` emits). */\nconst JSON_SCHEMA_DIALECT = \"https://json-schema.org/draft/2020-12/schema\";\n\n/**\n * The on-disk Basou documents that get a published JSON Schema, keyed by the\n * artifact basename (`<name>.schema.json`). Each entry maps a `.basou/` file\n * format to the Zod schema that is its single source of truth.\n */\nconst DOCUMENTS: ReadonlyArray<{\n name: string;\n schema: z.ZodType;\n title: string;\n description: string;\n}> = [\n {\n name: \"manifest\",\n schema: ManifestSchema,\n title: \"Basou Manifest\",\n description: \"The `.basou/manifest.yaml` workspace manifest.\",\n },\n {\n name: \"session\",\n schema: SessionSchema,\n title: \"Basou Session\",\n description: \"A `.basou/sessions/<id>/session.yaml` session record.\",\n },\n {\n name: \"event\",\n schema: EventSchema,\n title: \"Basou Event\",\n description:\n \"One line of a `.basou/sessions/<id>/events.jsonl` stream (a discriminated union over the event `type`).\",\n },\n {\n name: \"task\",\n schema: TaskSchema,\n title: \"Basou Task\",\n description: \"The YAML front matter of a `.basou/tasks/<id>.md` task document.\",\n },\n {\n name: \"approval\",\n schema: ApprovalSchema,\n title: \"Basou Approval\",\n description: \"A `.basou/approvals/{pending,resolved}/<id>.yaml` approval record.\",\n },\n {\n name: \"status\",\n schema: StatusSchema,\n title: \"Basou Status\",\n description: \"The `.basou/status.json` workspace status snapshot.\",\n },\n {\n name: \"task-index\",\n schema: TaskIndexSchema,\n title: \"Basou Task Index\",\n description: \"The `.basou/tasks/index.json` task lookup index.\",\n },\n {\n name: \"session-import\",\n schema: SessionImportPayloadSchema,\n title: \"Basou Session Import Payload\",\n description: \"The portable session payload consumed by `basou session import`.\",\n },\n];\n\n/** One emitted JSON Schema artifact. */\nexport type JsonSchemaArtifact = {\n /** Artifact basename without extension (e.g. `session`). */\n name: string;\n /** The JSON Schema document (draft 2020-12). */\n schema: Record<string, unknown>;\n};\n\n/**\n * Build the published JSON Schema artifacts from the canonical Zod schemas.\n *\n * Pure: no disk or environment access. Each artifact is `z.toJSONSchema` of the\n * document schema, re-headed with a stable `$id` / `title` / `description` (the\n * draft `$schema` from zod is preserved). This is the single generator used by\n * both the `gen:schemas` script (which writes the committed files) and the\n * drift-guard test (which asserts the committed files still match), so the two\n * can never disagree.\n *\n * Generated in `io: \"input\"` mode so the artifacts describe what a consumer\n * AUTHORS on disk, not zod's parsed output: a field with a `.default()` (e.g.\n * `events_log`) stays optional rather than `required`, and a non-strict object\n * omits `additionalProperties: false` so additive fields are allowed. Only the\n * `.strict()` event variants (e.g. `adapter_output`) keep\n * `additionalProperties: false`, preserving their reject-unknown contract.\n *\n * Note: prefixed-id fields carry a representable `pattern` (see\n * `createPrefixedIdSchema`); other refinement-only constraints are not\n * expressible in JSON Schema and are intentionally omitted.\n */\nexport function buildJsonSchemas(): JsonSchemaArtifact[] {\n return DOCUMENTS.map((doc) => {\n const generated = z.toJSONSchema(doc.schema, { io: \"input\" }) as Record<string, unknown>;\n const { $schema, ...rest } = generated;\n const schema: Record<string, unknown> = {\n $schema: typeof $schema === \"string\" ? $schema : JSON_SCHEMA_DIALECT,\n $id: `${ID_BASE}/${doc.name}.schema.json`,\n title: doc.title,\n description: doc.description,\n ...rest,\n };\n return { name: doc.name, schema };\n });\n}\n\n/** Serialize an artifact's schema exactly as the committed file stores it\n * (2-space indent, trailing newline) so the generator and the drift-guard test\n * compare byte-for-byte. */\nexport function serializeJsonSchema(schema: Record<string, unknown>): string {\n return `${JSON.stringify(schema, null, 2)}\\n`;\n}\n","import { z } from \"zod\";\nimport { EventSchema } from \"./event.schema.js\";\nimport {\n SessionIntegritySchema,\n SessionMetricsSchema,\n SessionSourceKindSchema,\n SessionStatusSchema,\n} from \"./session.schema.js\";\nimport {\n IsoTimestampSchema,\n SessionIdSchema,\n TaskIdSchema,\n WorkspaceIdSchema,\n} from \"./shared.schema.js\";\n\n// Independent copy of SessionInnerSchema for import payloads. The differences\n// from session.schema.ts are deliberate:\n// - `id` is `SessionIdSchema.optional()` so format is validated when present\n// but the orchestrator discards it and assigns a fresh ULID.\n// - `status` / `source.kind` are validated against the canonical enums but\n// overwritten by the orchestrator (status -> \"imported\", source.kind\n// retained from input).\n// - `events_log` is plain `z.string().optional()`; the orchestrator forces\n// \"events.jsonl\" to block path traversal.\n// - `.strict()` rejects unknown session-level keys at parse time.\n//\n// Events strictness follows EventSchema as authored: `adapter_output` is\n// `.strict()`, the other 14 variants are permissive, and\n// `approval_requested.action` is `.passthrough()`. This keeps the spec's\n// additive-event-fields rule and round-trip imports of post-v0.1 events\n// compatible. A blanket strict wrap for every variant is deferred.\n//\n// `schema_version` at the top level is `z.string()` rather than the\n// `SchemaVersionSchema = z.literal(\"0.1.0\")` literal. The strict reject for\n// unsupported versions emits a dedicated `Unsupported import schema_version`\n// message from the orchestrator; a literal here would short-circuit the\n// branch and turn every mismatched version into the generic\n// `Invalid import payload`.\nexport const SessionInnerImportSchema = z\n .object({\n id: SessionIdSchema.optional(),\n label: z.string().optional(),\n task_id: TaskIdSchema.nullable().optional(),\n workspace_id: WorkspaceIdSchema,\n source: z.object({\n kind: SessionSourceKindSchema,\n version: z.literal(\"0.1.0\"),\n // Source-tool-native id (e.g. Claude Code session UUID), retained so\n // re-imports of the same source can be deduplicated.\n external_id: z.string().optional(),\n // Byte size of the source native log at import time. Declared here too\n // (not only in session.schema.ts) because this inner `source` object is\n // a plain z.object: zod strips keys it does not declare, so a field\n // absent here would be dropped from the parsed payload before persist\n // and the size could never be stored.\n source_size_bytes: z.number().int().nonnegative().optional(),\n }),\n started_at: IsoTimestampSchema,\n ended_at: IsoTimestampSchema.optional(),\n status: SessionStatusSchema,\n working_directory: z.string().min(1),\n invocation: z.object({\n command: z.string().min(1),\n args: z.array(z.string()),\n exit_code: z.number().int().nullable(),\n }),\n related_files: z.array(z.string()).default([]),\n events_log: z.string().optional(),\n summary: z.string().nullable().optional(),\n metrics: SessionMetricsSchema.optional(),\n // Accepted so a payload assembled from an on-disk chained session.yaml\n // round-trips, and DISCARDED by the importer (buildSessionRecord never\n // copies it): the integrity anchor is computed at write time, never\n // imported. Mirrors the accept-and-discard of `prev_hash` on events.\n integrity: SessionIntegritySchema.optional(),\n })\n .strict();\n\n/**\n * Schema for the round-trip JSON payload accepted by `basou session import\n * --format json`. The top level is `.strict()`; unknown keys at the outer\n * envelope are rejected.\n */\nexport const SessionImportPayloadSchema = z\n .object({\n schema_version: z.string(),\n session: SessionInnerImportSchema,\n events: z.array(EventSchema),\n })\n .strict();\n\n/** Inferred runtime type for {@link SessionImportPayloadSchema}. */\nexport type SessionImportPayload = z.infer<typeof SessionImportPayloadSchema>;\n/** Inferred runtime type for {@link SessionInnerImportSchema}. */\nexport type SessionInnerImportInput = z.infer<typeof SessionInnerImportSchema>;\n","import { lstat, mkdir } from \"node:fs/promises\";\nimport { join } from \"node:path\";\n\n/**\n * Absolute paths to the standard `.basou/` directory layout, derived from a\n * given repository root. The shape mirrors the canonical `.basou/` tree\n * (see `docs/spec/workspace.md`). `root` is the `.basou/` directory itself\n * (i.e. `repositoryRoot/.basou`).\n *\n * `files` exposes the well-known top-level files inside `.basou/`. Each path\n * is computed but not created — they are written by their respective\n * subsystems (e.g. `writeManifest` for `manifest.yaml`).\n *\n * All fields are deeply readonly; consumers must not mutate the returned\n * object.\n */\nexport type BasouPaths = {\n readonly root: string;\n readonly sessions: string;\n readonly tasks: string;\n readonly approvals: {\n readonly pending: string;\n readonly resolved: string;\n };\n readonly locks: string;\n readonly logs: string;\n readonly raw: string;\n readonly tmp: string;\n readonly files: {\n readonly manifest: string;\n readonly status: string;\n readonly handoff: string;\n readonly decisions: string;\n readonly orientation: string;\n };\n};\n\n/**\n * Compute absolute paths to the standard `.basou/` directory layout under\n * `repositoryRoot`. Pure: performs no I/O and is safe to call before the\n * directory exists.\n *\n * @param repositoryRoot Absolute path to the git repository root (the\n * parent directory of `.basou/`). Caller is responsible for resolving\n * `process.cwd()` or running `git rev-parse --show-toplevel` upstream;\n * this function does not validate that the path exists or is a git\n * repository.\n */\nexport function basouPaths(repositoryRoot: string): BasouPaths {\n const root = join(repositoryRoot, \".basou\");\n const approvalsBase = join(root, \"approvals\");\n return {\n root,\n sessions: join(root, \"sessions\"),\n tasks: join(root, \"tasks\"),\n approvals: {\n pending: join(approvalsBase, \"pending\"),\n resolved: join(approvalsBase, \"resolved\"),\n },\n locks: join(root, \"locks\"),\n logs: join(root, \"logs\"),\n raw: join(root, \"raw\"),\n tmp: join(root, \"tmp\"),\n files: {\n manifest: join(root, \"manifest.yaml\"),\n status: join(root, \"status.json\"),\n handoff: join(root, \"handoff.md\"),\n decisions: join(root, \"decisions.md\"),\n orientation: join(root, \"orientation.md\"),\n },\n };\n}\n\n// Labels for sub-paths inside `.basou/`. Used in pathless error messages so\n// the surface area for absolute-path leakage is bounded by this map.\nconst PATH_LABELS = {\n sessions: \".basou/sessions\",\n tasks: \".basou/tasks\",\n approvalsPending: \".basou/approvals/pending\",\n approvalsResolved: \".basou/approvals/resolved\",\n locks: \".basou/locks\",\n logs: \".basou/logs\",\n raw: \".basou/raw\",\n tmp: \".basou/tmp\",\n} as const;\n\n/**\n * Create the standard `.basou/` directory layout under `repositoryRoot`.\n *\n * Idempotent: a no-op on an already-initialized layout. Returns the resolved\n * {@link BasouPaths} so callers can immediately use them.\n *\n * Throws if `repositoryRoot/.basou` (or any required subdirectory) exists\n * but is not a directory, or if filesystem permissions prevent creation.\n * All thrown error messages are pathless; the original native error is\n * attached as `cause` for diagnostics.\n *\n * @param repositoryRoot Absolute path to the git repository root. See\n * {@link basouPaths} for the contract on this parameter.\n */\nexport async function ensureBasouDirectory(repositoryRoot: string): Promise<BasouPaths> {\n const paths = basouPaths(repositoryRoot);\n\n // lstat (not stat) so that a symlink at `.basou` is detected as a symlink\n // and rejected; following the link could place Basou state outside the\n // git repository root, violating the workspace-root invariant.\n let existing: Awaited<ReturnType<typeof lstat>> | undefined;\n try {\n existing = await lstat(paths.root);\n } catch (error: unknown) {\n if (!hasErrorCode(error) || error.code !== \"ENOENT\") {\n throw new Error(\"Failed to inspect .basou directory\", { cause: error });\n }\n }\n if (existing !== undefined && !existing.isDirectory()) {\n throw new Error(\"Basou root .basou exists but is not a directory\");\n }\n\n await Promise.all([\n mkdirLabeled(paths.sessions, PATH_LABELS.sessions),\n mkdirLabeled(paths.tasks, PATH_LABELS.tasks),\n mkdirLabeled(paths.approvals.pending, PATH_LABELS.approvalsPending),\n mkdirLabeled(paths.approvals.resolved, PATH_LABELS.approvalsResolved),\n mkdirLabeled(paths.locks, PATH_LABELS.locks),\n mkdirLabeled(paths.logs, PATH_LABELS.logs),\n mkdirLabeled(paths.raw, PATH_LABELS.raw),\n mkdirLabeled(paths.tmp, PATH_LABELS.tmp),\n ]);\n\n return paths;\n}\n\nasync function mkdirLabeled(target: string, label: string): Promise<void> {\n try {\n await mkdir(target, { recursive: true });\n } catch (error: unknown) {\n if (hasErrorCode(error) && (error.code === \"ENOTDIR\" || error.code === \"EEXIST\")) {\n throw new Error(`${label} exists but is not a directory`, { cause: error });\n }\n throw new Error(`Failed to create ${label}`, { cause: error });\n }\n}\n\nfunction hasErrorCode(error: unknown): error is Error & { code: string } {\n if (!(error instanceof Error)) return false;\n const codeProp = (error as unknown as Record<string, unknown>).code;\n return typeof codeProp === \"string\";\n}\n","import { readFile, writeFile } from \"node:fs/promises\";\nimport { join } from \"node:path\";\n\nconst MARKER = \"# Basou - default ignore\";\n\n// Recommended .gitignore block (ignore + commit). The test asserts an\n// exact match against this spec string literal to detect spec drift.\nconst BASOU_GITIGNORE_BLOCK =\n \"# Basou - default ignore\\n\" +\n \".basou/logs/\\n\" +\n \".basou/raw/\\n\" +\n \".basou/tmp/\\n\" +\n \".basou/locks/\\n\" +\n \".basou/status.json\\n\" +\n \".basou/orientation.md\\n\" +\n \".basou/sessions/*/events.jsonl\\n\" +\n \".basou/sessions/*/artifacts/\\n\" +\n \".basou/approvals/pending/\\n\" +\n \".basou/approvals/resolved/\\n\" +\n \"\\n\" +\n \"# Basou - default commit\\n\" +\n \"# .basou/manifest.yaml\\n\" +\n \"# .basou/handoff.md\\n\" +\n \"# .basou/decisions.md\\n\" +\n \"# .basou/tasks/\\n\" +\n \"# .basou/sessions/*/session.yaml\\n\" +\n \"# .basou/sessions/*/transcript.md\\n\" +\n \"# .basou/sessions/*/changed-files.json\\n\";\n\n// Local-only `.basou/` exclude (opt-in via `basou init --local-only`). The trail\n// is never committed — it is personal/local state, regenerable by re-importing\n// from the agents' own logs. Shares the marker line so re-running init is still\n// idempotent. The test asserts an exact match to detect spec drift.\nconst BASOU_GITIGNORE_BLOCK_LOCAL_ONLY =\n \"# Basou - default ignore\\n\" +\n \"# Local-only: basou's trail is never committed (personal/local state,\\n\" +\n \"# regenerable by re-importing from the agents' own logs). Recommended for\\n\" +\n \"# monitored repos and any workspace kept out of version control.\\n\" +\n \".basou/\\n\";\n\nexport type AppendBasouGitignoreResult = {\n /** True if the block was appended (or the file was newly created). */\n readonly appended: boolean;\n};\n\n/** Options for {@link appendBasouGitignore}. */\nexport type AppendBasouGitignoreOptions = {\n /** Write a `.basou/` full-exclude block instead of the default ignore+commit block. */\n readonly localOnly?: boolean;\n};\n\n/**\n * Append Basou's default `.gitignore` block to `repositoryRoot/.gitignore`.\n *\n * The block contents are derived from the Basou v0.1 specification (the\n * standard ignore + commit recommendations). Callers must pass an absolute\n * path to a Git repository root.\n *\n * With `options.localOnly`, a `.basou/` full-exclude block is written instead\n * of the default ignore+commit block (the trail is kept out of version\n * control). The default (no options) is unchanged.\n *\n * Behavior:\n * - If `.gitignore` does not exist, it is created with the chosen Basou block.\n * - If a `# Basou - default ignore` marker OR a standalone `.basou/` exclude\n * line is already present, the file is left untouched and `appended: false`\n * is returned (idempotent across both modes).\n * - If `.gitignore` is a symlink, the link is followed and the target file\n * is updated. Symlinks are not rejected.\n *\n * On I/O failure throws Error with a pathless message\n * (`Failed to read .gitignore` / `Failed to write .gitignore`) and the\n * original native error attached as `cause`.\n */\nexport async function appendBasouGitignore(\n repositoryRoot: string,\n options: AppendBasouGitignoreOptions = {},\n): Promise<AppendBasouGitignoreResult> {\n const gitignorePath = join(repositoryRoot, \".gitignore\");\n\n let body: string;\n let existed: boolean;\n try {\n body = await readFile(gitignorePath, \"utf8\");\n existed = true;\n } catch (error: unknown) {\n if (hasErrorCode(error) && error.code === \"ENOENT\") {\n body = \"\";\n existed = false;\n } else {\n throw new Error(\"Failed to read .gitignore\", { cause: error });\n }\n }\n\n if (existed && hasBasouGitignore(body)) {\n return { appended: false };\n }\n\n const block =\n options.localOnly === true ? BASOU_GITIGNORE_BLOCK_LOCAL_ONLY : BASOU_GITIGNORE_BLOCK;\n const next = composeNextBody(body, block);\n try {\n await writeFile(gitignorePath, next, { encoding: \"utf8\" });\n } catch (error: unknown) {\n throw new Error(\"Failed to write .gitignore\", { cause: error });\n }\n return { appended: true };\n}\n\n/** True if the file already carries a Basou block (marker) or a `.basou/` full-exclude line. */\nfunction hasBasouGitignore(body: string): boolean {\n for (const rawLine of body.split(\"\\n\")) {\n const line = rawLine.trimEnd();\n if (line.startsWith(MARKER)) return true;\n if (line === \".basou/\" || line === \"/.basou/\") return true;\n }\n return false;\n}\n\nfunction composeNextBody(existing: string, block: string): string {\n if (existing.length === 0) return block;\n const normalized = existing.endsWith(\"\\n\") ? existing : `${existing}\\n`;\n return `${normalized}\\n${block}`;\n}\n\nfunction hasErrorCode(error: unknown): error is Error & { code: string } {\n if (!(error instanceof Error)) return false;\n return typeof (error as unknown as Record<string, unknown>).code === \"string\";\n}\n","import { readFile } from \"node:fs/promises\";\nimport { atomicReplace } from \"./atomic.js\";\n\n/** Marker line that begins the auto-generated region. */\nexport const GENERATED_START = \"<!-- BASOU:GENERATED:START -->\";\n/** Marker line that ends the auto-generated region. */\nexport const GENERATED_END = \"<!-- BASOU:GENERATED:END -->\";\n\n/** Marker line that begins a managed protocol block in a foreign instruction file. */\nexport const PROTOCOL_START = \"<!-- BASOU:PROTOCOLS:START -->\";\n/** Marker line that ends a managed protocol block. */\nexport const PROTOCOL_END = \"<!-- BASOU:PROTOCOLS:END -->\";\n\n/** A start/end marker pair. Both lines are matched whole-line, exact. */\nexport type Markers = { start: string; end: string };\n\n/** Default marker pair: the BASOU:GENERATED region used by handoff/decisions/orient. */\nconst DEFAULT_MARKERS: Markers = { start: GENERATED_START, end: GENERATED_END };\n\n/**\n * Result of parsing a markdown body for the BASOU:GENERATED marker region.\n *\n * The spec mandates strict line-level matching (see\n * `docs/spec/generated-markdown.md#102-marker-convention`): a marker is\n * only recognized when an entire line is exactly the marker string.\n * Leading/trailing whitespace and comment compression are treated as legacy\n * formats (`no_markers`) so that re-generation refuses to silently overwrite a\n * mismatched manual edit. A leading UTF-8 BOM is the one exception: it is\n * tolerated (stripped for matching, re-prepended on render) so a BOM-prefixed\n * file round-trips instead of duplicating the block.\n */\nexport type MarkerSection =\n | { kind: \"ok\"; before: string; generated: string; after: string }\n | { kind: \"no_markers\" }\n | { kind: \"missing_start\" }\n | { kind: \"missing_end\" }\n | { kind: \"multiple_pairs\" }\n | { kind: \"wrong_order\" };\n\n/**\n * Read a markdown file as UTF-8 text. Returns `null` when the file does not\n * exist; throws `Error(\"Failed to read markdown file\", { cause })` for other\n * I/O failures (pathless contract — never embed the absolute path in the\n * thrown `message`).\n */\nexport async function readMarkdownFile(filePath: string): Promise<string | null> {\n try {\n return await readFile(filePath, \"utf8\");\n } catch (error: unknown) {\n if (hasErrorCode(error) && error.code === \"ENOENT\") return null;\n throw new Error(\"Failed to read markdown file\", { cause: error });\n }\n}\n\n/**\n * Atomically write a markdown body via {@link atomicReplace}. The shared\n * helper handles the tmp-file + rename sequence, `wx` collision guard, and\n * best-effort tmp cleanup on failure.\n *\n * On any failure the original error is re-thrown as\n * `Error(\"Failed to write markdown file\", { cause })` (pathless contract).\n */\nexport async function writeMarkdownFile(filePath: string, body: string): Promise<void> {\n try {\n await atomicReplace(filePath, body);\n } catch (error: unknown) {\n throw new Error(\"Failed to write markdown file\", { cause: error });\n }\n}\n\n/**\n * Parse a markdown body and identify the BASOU:GENERATED marker region.\n *\n * Returns one of six `kind` discriminants:\n * - `ok`: exactly one START line followed by exactly one END line in the\n * correct order. `before` / `generated` / `after` slice the original\n * text by character offsets so CRLF / LF are preserved verbatim outside\n * the marker region.\n * - `no_markers`: both START and END absent (legacy file / fresh write).\n * - `missing_start` / `missing_end`: exactly one of the pair is present.\n * - `multiple_pairs`: more than one START or END line.\n * - `wrong_order`: END appears before START.\n *\n * Matching is strict: leading/trailing whitespace and comment compression\n * (`<!--BASOU:...-->`) bypass the marker and are treated as legacy content. A\n * leading UTF-8 BOM is the exception: it is stripped before matching and\n * re-prepended to `before`, so a marker on the first line of a BOM-prefixed\n * file still matches and the file round-trips.\n */\nexport function parseMarkers(content: string, markers: Markers = DEFAULT_MARKERS): MarkerSection {\n // Tolerate a leading UTF-8 BOM: strip it for line matching and offset math,\n // then re-prepend it to `before` so a BOM-prefixed file round-trips. Without\n // this, a marker on line 0 of a BOM file would fail the exact-line compare\n // and the block would be duplicated on the next render.\n const bom = content.charCodeAt(0) === 0xfeff ? \"\\uFEFF\" : \"\";\n const body = bom === \"\" ? content : content.slice(1);\n\n // Split on either CRLF or LF so the line count is consistent regardless of\n // the file's line ending. The reconstruction step below slices the original\n // string by character offsets to preserve the actual line endings outside\n // the generated region.\n const lines = body.split(/\\r?\\n/);\n const startLines: number[] = [];\n const endLines: number[] = [];\n for (let i = 0; i < lines.length; i++) {\n if (lines[i] === markers.start) startLines.push(i);\n else if (lines[i] === markers.end) endLines.push(i);\n }\n if (startLines.length === 0 && endLines.length === 0) return { kind: \"no_markers\" };\n if (startLines.length === 0) return { kind: \"missing_start\" };\n if (endLines.length === 0) return { kind: \"missing_end\" };\n if (startLines.length >= 2 || endLines.length >= 2) return { kind: \"multiple_pairs\" };\n const startLineIdx = startLines[0] as number;\n const endLineIdx = endLines[0] as number;\n if (endLineIdx < startLineIdx) return { kind: \"wrong_order\" };\n\n // Walk the (BOM-stripped) string to find byte offsets of the marker lines.\n // This preserves CRLF vs LF in the surrounding text — splitting and\n // re-joining would normalize the line endings.\n const startOffset = lineStartOffset(body, startLineIdx);\n const endLineStart = lineStartOffset(body, endLineIdx);\n const startLineEnd = startOffset + markers.start.length;\n const endLineEnd = endLineStart + markers.end.length;\n\n const before = bom + body.slice(0, startOffset);\n // The generated region is everything between the two marker lines,\n // exclusive of the marker lines themselves but including the newline after\n // START and excluding the newline before END (so re-render can plug in\n // its own body without doubling separators).\n const afterStartNewline = skipOneNewline(body, startLineEnd);\n const beforeEndNewline = trimOneNewline(body, endLineStart);\n const generated = body.slice(afterStartNewline, beforeEndNewline);\n const after = body.slice(endLineEnd);\n return { kind: \"ok\", before, generated, after };\n}\n\n/**\n * Build the final markdown body by replacing the BASOU:GENERATED region.\n *\n * - `existing === null` (no file yet): return `<START>\\n<generated>\\n<END>\\n`.\n * - existing parses to `ok`: replace the marked region and keep everything\n * before START and after END untouched (preserving manual additions).\n * - any other parse result: throw a pathless error referencing `fileLabel`.\n *\n * The caller passes `fileLabel` (e.g. `\"handoff.md\"` or `\"decisions.md\"`)\n * so the error message is informative without leaking an absolute path.\n */\nexport function renderWithMarkers(\n existing: string | null,\n generated: string,\n fileLabel: string,\n markers: Markers = DEFAULT_MARKERS,\n): string {\n const normalized = generated.endsWith(\"\\n\") ? generated : `${generated}\\n`;\n if (existing === null) {\n return `${markers.start}\\n${normalized}${markers.end}\\n`;\n }\n const section = parseMarkers(existing, markers);\n switch (section.kind) {\n case \"ok\":\n return `${section.before}${markers.start}\\n${normalized}${markers.end}${section.after}`;\n case \"no_markers\":\n throw new Error(`Markers missing in ${fileLabel}`);\n case \"missing_start\":\n case \"missing_end\":\n case \"multiple_pairs\":\n case \"wrong_order\":\n throw new Error(`Markers mismatched in ${fileLabel}`);\n }\n}\n\n/**\n * Remove a marker region from `existing`, returning the body without the block.\n *\n * - `no_markers`: returns `existing` unchanged (nothing to remove).\n * - `ok`: drops both marker lines and the generated region, collapsing the\n * single newline that terminated the END marker line so no stray blank line\n * is left behind.\n * - any other parse result: throws a pathless error referencing `fileLabel`\n * (mismatched markers must not be silently rewritten).\n */\nexport function removeMarkerSection(\n existing: string,\n fileLabel: string,\n markers: Markers = DEFAULT_MARKERS,\n): string {\n const section = parseMarkers(existing, markers);\n switch (section.kind) {\n case \"no_markers\":\n return existing;\n case \"ok\": {\n const after = section.after.replace(/^\\r?\\n/, \"\");\n return section.before + after;\n }\n case \"missing_start\":\n case \"missing_end\":\n case \"multiple_pairs\":\n case \"wrong_order\":\n throw new Error(`Markers mismatched in ${fileLabel}`);\n }\n}\n\n/** Character offset of the first character of `lineIdx` (0-based). */\nfunction lineStartOffset(content: string, lineIdx: number): number {\n if (lineIdx === 0) return 0;\n let offset = 0;\n let line = 0;\n while (offset < content.length && line < lineIdx) {\n const ch = content[offset];\n if (ch === \"\\n\") {\n line += 1;\n offset += 1;\n } else if (ch === \"\\r\") {\n // CR or CRLF both count as a line terminator.\n offset += 1;\n if (content[offset] === \"\\n\") offset += 1;\n line += 1;\n } else {\n offset += 1;\n }\n }\n return offset;\n}\n\n/** Advance past one trailing `\\n` or `\\r\\n` if present. */\nfunction skipOneNewline(content: string, offset: number): number {\n if (content[offset] === \"\\r\" && content[offset + 1] === \"\\n\") return offset + 2;\n if (content[offset] === \"\\n\") return offset + 1;\n return offset;\n}\n\n/** Walk back past one leading `\\n` or `\\r\\n` if present. */\nfunction trimOneNewline(content: string, offset: number): number {\n if (offset >= 2 && content[offset - 2] === \"\\r\" && content[offset - 1] === \"\\n\")\n return offset - 2;\n if (offset >= 1 && content[offset - 1] === \"\\n\") return offset - 1;\n return offset;\n}\n\nfunction hasErrorCode(error: unknown): error is Error & { code: string } {\n if (!(error instanceof Error)) return false;\n const codeProp = (error as unknown as Record<string, unknown>).code;\n return typeof codeProp === \"string\";\n}\n","import { mkdir, readFile, rm } from \"node:fs/promises\";\nimport { homedir } from \"node:os\";\nimport { join } from \"node:path\";\nimport { chainRawJsonLines } from \"../events/chain.js\";\nimport { readAllEvents } from \"../events/event-replay.js\";\nimport { type BulkChainResult, writeEventsBulk } from \"../events/event-writer.js\";\nimport { verifyEventsChain } from \"../events/verify.js\";\nimport { type PrefixedId, prefixedUlid } from \"../ids/ulid.js\";\nimport { findErrorCode } from \"../lib/error-codes.js\";\nimport { sanitizeRelatedFiles, sanitizeWorkingDirectory } from \"../lib/path-sanitizer.js\";\nimport { type Event, EventSchema } from \"../schemas/event.schema.js\";\nimport type { Manifest } from \"../schemas/manifest.schema.js\";\nimport type { Session, SessionSourceKind, SessionStatus } from \"../schemas/session.schema.js\";\nimport type {\n SessionImportPayload,\n SessionInnerImportInput,\n} from \"../schemas/session-import.schema.js\";\nimport { TaskIdSchema } from \"../schemas/shared.schema.js\";\nimport { atomicReplace } from \"./atomic.js\";\nimport type { BasouPaths } from \"./basou-dir.js\";\nimport { acquireLock } from \"./lockfile.js\";\nimport { readSessionYaml } from \"./sessions.js\";\nimport { enumerateTaskIds } from \"./tasks.js\";\nimport { linkYamlFile, overwriteYamlFile } from \"./yaml-store.js\";\n\n/**\n * Options for {@link importSessionFromJson}. All fields are optional.\n *\n * - `labelOverride` / `taskIdOverride` come from the CLI `--label` / `--task`\n * flags and win over the corresponding fields on the input payload.\n * - `dryRun` skips disk writes entirely and returns a preview result.\n */\nexport type ImportSessionOptions = {\n labelOverride?: string;\n taskIdOverride?: string;\n dryRun?: boolean;\n};\n\n/**\n * Result of a successful import. `finalStatus` is always the literal\n * `\"imported\"` (per the import-session lifecycle policy); `finalSourceKind`\n * mirrors the input's `session.source.kind` so round-trip imports preserve\n * provenance.\n *\n * `pathSanitizeReport` summarises how many path-shaped fields the importer\n * rewrote on the way in: `related_files[]` entries plus a single boolean\n * for `working_directory`. The CLI wrapper surfaces this as a one-line\n * stderr warning when the total is non-zero so the operator sees that\n * machine-private prefixes were stripped.\n */\nexport type ImportSessionResult = {\n sessionId: PrefixedId<\"ses\">;\n eventCount: number;\n finalStatus: SessionStatus;\n finalSourceKind: SessionSourceKind;\n pathSanitizeReport: {\n relatedFiles: number;\n workingDirectoryRewritten: boolean;\n };\n};\n\n/**\n * Import a round-trip JSON payload into `.basou/sessions/<new>/`. The caller\n * MUST validate the payload against {@link SessionImportPayloadSchema} first\n * and gate the `schema_version === \"0.1.0\"` literal check externally; this\n * function trusts both invariants.\n *\n * On success a fresh session ID is minted and a complete\n * `session.yaml` + `events.jsonl` pair is written atomically. On any post-\n * mkdir failure the session directory is removed best-effort so partial\n * imports do not leave `session_yaml_missing` half-states behind.\n *\n * Throws `Error` with one of the fixed messages enumerated by the import contract\n * §\"Error messages\" table; the original native error is attached as `cause`\n * for `--verbose` rendering.\n */\nexport async function importSessionFromJson(\n paths: BasouPaths,\n manifest: Manifest,\n payload: SessionImportPayload,\n options: ImportSessionOptions,\n): Promise<ImportSessionResult> {\n // Defense in depth: the CLI converter (parseTaskIdOverride) already gates\n // this, but a direct core API caller could still pass an arbitrary string.\n if (\n options.taskIdOverride !== undefined &&\n !TaskIdSchema.safeParse(options.taskIdOverride).success\n ) {\n throw new Error(`Invalid task_id: ${options.taskIdOverride}`);\n }\n\n // Reachability guard: rewriteEvents\n // preserves variant-specific task_id fields, so importing a session that\n // references a task absent from the local workspace would silently\n // install a dangling reference. Validate every task_id carrier:\n // task_created / task_status_changed / task_reconciled events plus the\n // effective session task_id (override-wins, matches buildSessionRecord\n // below).\n const effectiveSessionTaskId = options.taskIdOverride ?? payload.session.task_id ?? null;\n await assertImportedTaskReferencesAreReachable(paths, payload.events, effectiveSessionTaskId);\n\n const newSessionId = prefixedUlid(\"ses\");\n\n const rewrittenEvents = rewriteEvents(payload.events, newSessionId);\n assertChronologicalOrder(rewrittenEvents);\n\n const { record: sessionRecord, pathSanitizeReport } = buildSessionRecord(\n payload.session,\n manifest,\n newSessionId,\n options,\n );\n\n if (options.dryRun === true) {\n return {\n sessionId: newSessionId,\n eventCount: rewrittenEvents.length,\n finalStatus: \"imported\",\n finalSourceKind: sessionRecord.session.source.kind,\n pathSanitizeReport,\n };\n }\n\n // recursive: true lets a stripped-down workspace (manifest present but\n // `.basou/sessions` missing) recover instead of failing with ENOENT; ULID\n // collision on the new session dir itself is statistically impossible, so\n // the silent EEXIST on an existing directory is acceptable here. Concurrent\n // attempts to write the same session.yaml are caught by linkYamlFile below.\n const sessionDir = join(paths.sessions, newSessionId);\n try {\n await mkdir(sessionDir, { recursive: true });\n } catch (error: unknown) {\n throw new Error(\"Failed to create session directory\", { cause: error });\n }\n\n // Chained write: imported sessions are the tamper-evident corpus, so the\n // bulk write threads the per-line hash chain and returns the head anchor.\n let chainResult: BulkChainResult | null;\n try {\n chainResult = await writeEventsBulk(sessionDir, rewrittenEvents, { chain: true });\n } catch (error: unknown) {\n await rm(sessionDir, { recursive: true, force: true }).catch(() => undefined);\n throw error;\n }\n\n try {\n const sessionYamlPath = join(sessionDir, \"session.yaml\");\n await linkYamlFile(sessionYamlPath, withIntegrity(sessionRecord, chainResult));\n } catch (error: unknown) {\n await rm(sessionDir, { recursive: true, force: true }).catch(() => undefined);\n if (findErrorCode(error, \"EEXIST\")) {\n throw new Error(\"Session directory collision (retry the command)\", {\n cause: error,\n });\n }\n throw error;\n }\n\n return {\n sessionId: newSessionId,\n eventCount: rewrittenEvents.length,\n finalStatus: \"imported\",\n finalSourceKind: sessionRecord.session.source.kind,\n pathSanitizeReport,\n };\n}\n\n// Reachability guard: refuse any payload that\n// references task ids absent from the local workspace, across every carrier:\n// task_created / task_status_changed / task_reconciled events plus the\n// effective session task_id (= the override if supplied, otherwise the\n// imported session.yaml.task_id; matches buildSessionRecord's override-wins\n// semantics so we never reject on an id the final record will discard). The\n// fixed message is pathless-contract compliant; broken ids are not echoed\n// back so an adversarial payload cannot probe the local task namespace.\nasync function assertImportedTaskReferencesAreReachable(\n paths: BasouPaths,\n events: ReadonlyArray<Event>,\n effectiveSessionTaskId: string | null,\n): Promise<void> {\n const taskIdsToCheck = new Set<string>();\n for (const ev of events) {\n if (\n ev.type === \"task_created\" ||\n ev.type === \"task_status_changed\" ||\n ev.type === \"task_reconciled\" ||\n ev.type === \"task_linkage_refreshed\" ||\n ev.type === \"task_deleted\" ||\n ev.type === \"task_archived\"\n ) {\n taskIdsToCheck.add(ev.task_id);\n }\n }\n if (effectiveSessionTaskId !== null) {\n taskIdsToCheck.add(effectiveSessionTaskId);\n }\n if (taskIdsToCheck.size === 0) {\n // skip the tasks-dir scan when nothing references a task,\n // so imports that carry no task_id at all keep the original perf.\n return;\n }\n const knownTaskIds = new Set(await enumerateTaskIds(paths));\n for (const id of taskIdsToCheck) {\n if (!knownTaskIds.has(id)) {\n throw new Error(\"Imported session references unknown task_id\");\n }\n }\n}\n\n// Rewrite each event's `id` and `session_id` to brand-new values while\n// retaining every other field — including variant-specific cross-reference\n// IDs (approval_id, decision_id, task_id, file paths, raw_ref) — so that\n// chains like `approval_requested` -> `approval_approved` remain joinable on\n// the imported side. The events were already validated against EventSchema,\n// and prefixedUlid output satisfies EventIdSchema by construction, so the\n// rewritten events do not need to be re-parsed.\nfunction rewriteEvents(events: Event[], newSessionId: PrefixedId<\"ses\">): Event[] {\n return events.map((event) => ({\n ...event,\n id: prefixedUlid(\"evt\"),\n session_id: newSessionId,\n }));\n}\n\n// Enforce strict chronological order with same-ms duplicates allowed (`>=`).\n// Out-of-order events indicate an exporter bug or\n// hand-edited payload — refuse to silently sort.\nfunction assertChronologicalOrder(events: Event[]): void {\n for (let i = 1; i < events.length; i++) {\n const prevEvent = events[i - 1];\n const currEvent = events[i];\n if (prevEvent === undefined || currEvent === undefined) continue;\n const prev = Date.parse(prevEvent.occurred_at);\n const curr = Date.parse(currEvent.occurred_at);\n if (!Number.isFinite(prev) || !Number.isFinite(curr) || curr < prev) {\n throw new Error(\"Events are not in chronological order\");\n }\n }\n}\n\n// Attach the head anchor returned by a chained writeEventsBulk to a session\n// record about to be persisted. A null chain result (empty event batch) leaves\n// the record anchor-less, matching the zero-byte events.jsonl on disk.\nfunction withIntegrity(record: Session, chainResult: BulkChainResult | null): Session {\n if (chainResult === null) return record;\n return {\n ...record,\n session: {\n ...record.session,\n integrity: { head_hash: chainResult.headHash, event_count: chainResult.count },\n },\n };\n}\n\nfunction buildSessionRecord(\n input: SessionInnerImportInput,\n manifest: Manifest,\n newSessionId: PrefixedId<\"ses\">,\n options: ImportSessionOptions,\n): {\n record: Session;\n pathSanitizeReport: ImportSessionResult[\"pathSanitizeReport\"];\n} {\n // Sanitize before constructing the record so the operator-private\n // absolute prefix never reaches disk (= same write-time policy as\n // run.ts / exec.ts / ad-hoc-session.ts). We use the imported\n // working_directory itself as the base for related_files so paths\n // recorded relative to the original repo continue to read as\n // repo-internal; the working_directory field itself is sanitized via\n // the sentinel-based helper so the same value yields \"~/projects/foo\"\n // instead of collapsing to \".\".\n const home = homedir();\n const workingDirectoryRaw = input.working_directory;\n const workingDirectorySanitized = sanitizeWorkingDirectory(workingDirectoryRaw, {\n homedir: home,\n });\n const relatedSanitized = sanitizeRelatedFiles(input.related_files, {\n workingDirectory: workingDirectoryRaw,\n homedir: home,\n });\n\n const inner: Session[\"session\"] = {\n id: newSessionId,\n ...(options.labelOverride !== undefined || input.label !== undefined\n ? { label: options.labelOverride ?? input.label }\n : {}),\n task_id:\n options.taskIdOverride !== undefined\n ? (options.taskIdOverride as Session[\"session\"][\"task_id\"])\n : (input.task_id ?? null),\n workspace_id: manifest.workspace.id,\n source: input.source,\n started_at: input.started_at,\n ...(input.ended_at !== undefined ? { ended_at: input.ended_at } : {}),\n status: \"imported\",\n working_directory: workingDirectorySanitized,\n invocation: input.invocation,\n related_files: relatedSanitized.sanitized,\n events_log: \"events.jsonl\",\n summary: input.summary ?? null,\n ...(input.metrics !== undefined ? { metrics: input.metrics } : {}),\n };\n return {\n record: { schema_version: \"0.1.0\", session: inner },\n pathSanitizeReport: {\n relatedFiles: relatedSanitized.mutationCount,\n workingDirectoryRewritten: workingDirectorySanitized !== workingDirectoryRaw,\n },\n };\n}\n\n/**\n * The closed allowlist of session source kinds that are import-DERIVED (an\n * adapter mechanically derived the events from a native log). Used to\n * discriminate the events a scoped re-import re-derives from the ones it\n * preserves. `EventSourceSchema` is open vocab, so this is a deliberate\n * allowlist: any source NOT in it is treated as non-derived and preserved.\n */\nconst IMPORT_DERIVED_SOURCES: ReadonlySet<string> = new Set<SessionSourceKind>([\n \"claude-code-import\",\n \"codex-import\",\n]);\n\n/** Whether `source` is one of the known import-derived event sources. */\nexport function isImportDerivedSource(source: string): boolean {\n return IMPORT_DERIVED_SOURCES.has(source);\n}\n\n/** Options for {@link reimportPreservingId}. */\nexport type ReimportOptions = {\n /** Compute the re-import and return its preview without writing to disk. */\n dryRun?: boolean;\n};\n\n/** Result of {@link reimportPreservingId}. */\nexport type ReimportResult =\n | {\n status: \"reimported\";\n sessionId: PrefixedId<\"ses\">;\n /** Total events written to the merged `events.jsonl`. */\n eventCount: number;\n /** Non-derived events (human / unknown source) carried over unchanged. */\n preservedCount: number;\n /** Derived events whose prior id (and decision_id) was reused. */\n reusedIdCount: number;\n }\n | {\n status: \"skipped\";\n // `prior_events_unreadable`: the prior events.jsonl had a line that could\n // not be preserved, so the re-import was aborted to avoid dropping data\n // on the atomic rewrite.\n // `prior_derived_dropped`: re-deriving the (grown) source would NOT\n // reproduce some prior derived event, so its id would vanish — only\n // possible on a non-append-only source change, which is out of scope.\n // Aborted so an id a cross-session `linked_events` may reference is\n // never silently dropped; `--force` rebuilds from scratch.\n // `prior_chain_broken`: the prior events.jsonl failed hash-chain\n // verification (tampered). Aborted so a re-import cannot launder a\n // broken chain into a freshly-valid one; the operator inspects with\n // `basou verify` and decides (`--force` rebuilds from scratch).\n reason: \"prior_events_unreadable\" | \"prior_derived_dropped\" | \"prior_chain_broken\";\n };\n\n/**\n * A stable content key for a DERIVED event, used to match a freshly-derived\n * event to its counterpart in the prior import so the prior event's id (and any\n * id-bearing field such as `decision_id`) can be reused — keeping cross-session\n * `decision_recorded.linked_events` references valid across a re-import. The key\n * is `type + occurred_at + the variant's salient derived fields` (the fields the\n * importers populate deterministically from the source records), so matching is\n * robust to record reordering between imports (a positional match is not).\n * `session_started` / `session_ended` are matched by role instead (a session has\n * exactly one of each, and `session_ended`'s occurred_at moves as the log grows).\n */\nfunction derivedEventContentKey(event: Event): string {\n const base = `${event.type}\u0000${event.occurred_at}`;\n switch (event.type) {\n case \"command_executed\":\n return `${base}\u0000${event.command}\u0000${event.args.join(\"\u0001\")}\u0000${event.cwd}`;\n case \"file_changed\":\n return `${base}\u0000${event.path}\u0000${event.change_type}`;\n case \"decision_recorded\":\n return `${base}\u0000${event.title}`;\n default:\n return base;\n }\n}\n\n/**\n * Re-key the freshly-derived events (which carry placeholder ids) onto the\n * session being re-imported, reusing prior derived events' IDS wherever the\n * derivation is unchanged so their ids stay stable across the re-import:\n *\n * - `session_started` / `session_ended`: matched by role (a session has exactly\n * one of each). The prior id is reused with the FRESH content, so\n * `session_ended`'s occurred_at advances to the log's new end while the id is\n * stable.\n * - every other derived event: matched to a prior derived event by content key\n * (FIFO per key for same-key duplicates). A match reuses the prior `id` (and,\n * for `decision_recorded`, the prior `decision_id`) but keeps the FRESH\n * content, so a re-derived field that changed between imports (e.g. a Codex\n * command's `exit_code` / `duration_ms` that filled in once it completed)\n * updates; a miss is a genuinely new event and gets a fresh ULID.\n *\n * `droppedPriorDerived` is true when a prior derived event was NOT reproduced by\n * the fresh derivation — its id would be dropped. That cannot happen for an\n * append-only growth (the prior derivations all recur); it signals a non-append\n * source change, which the caller treats as out of scope and skips.\n */\nfunction reuseDerivedIds(\n priorDerived: ReadonlyArray<Event>,\n freshDerived: ReadonlyArray<Event>,\n sessionId: PrefixedId<\"ses\">,\n): { events: Event[]; reusedIdCount: number; droppedPriorDerived: boolean } {\n const priorStarted = priorDerived.find((e) => e.type === \"session_started\");\n const priorEnded = priorDerived.find((e) => e.type === \"session_ended\");\n let startedUsed = false;\n let endedUsed = false;\n // FIFO queue of prior MIDDLE events per content key.\n const middleByKey = new Map<string, Event[]>();\n for (const e of priorDerived) {\n if (e.type === \"session_started\" || e.type === \"session_ended\") continue;\n const key = derivedEventContentKey(e);\n const list = middleByKey.get(key);\n if (list === undefined) middleByKey.set(key, [e]);\n else list.push(e);\n }\n let reusedIdCount = 0;\n // Reuse the prior id (so a cross-session `linked_events` target survives) but\n // keep the FRESH content; carry the prior `decision_id` so a decision's\n // identity is stable too.\n const withReusedId = (fresh: Event, prior: Event): Event => {\n reusedIdCount++;\n if (fresh.type === \"decision_recorded\" && prior.type === \"decision_recorded\") {\n return { ...fresh, id: prior.id, session_id: sessionId, decision_id: prior.decision_id };\n }\n return { ...fresh, id: prior.id, session_id: sessionId };\n };\n const events = freshDerived.map((fresh): Event => {\n if (fresh.type === \"session_started\") {\n if (priorStarted !== undefined) {\n startedUsed = true;\n return withReusedId(fresh, priorStarted);\n }\n return { ...fresh, id: prefixedUlid(\"evt\"), session_id: sessionId };\n }\n if (fresh.type === \"session_ended\") {\n if (priorEnded !== undefined) {\n endedUsed = true;\n return withReusedId(fresh, priorEnded);\n }\n return { ...fresh, id: prefixedUlid(\"evt\"), session_id: sessionId };\n }\n const match = middleByKey.get(derivedEventContentKey(fresh))?.shift();\n if (match !== undefined) return withReusedId(fresh, match);\n return { ...fresh, id: prefixedUlid(\"evt\"), session_id: sessionId };\n });\n // A prior derived event not consumed above would be DROPPED on the rewrite.\n const droppedPriorDerived =\n (priorStarted !== undefined && !startedUsed) ||\n (priorEnded !== undefined && !endedUsed) ||\n [...middleByKey.values()].some((q) => q.length > 0);\n return { events, reusedIdCount, droppedPriorDerived };\n}\n\n/**\n * Re-import a source whose native log GREW into the SAME Basou session,\n * preserving its id and any non-derived events, instead of skipping it (default\n * dedup) or deleting + recreating it (`--force`). The caller has already\n * validated `freshPayload` and confirmed (by source byte size) that the source\n * changed; this function re-derives the adapter's events, reuses prior derived\n * event ids for unchanged derivations (so `linked_events` references survive),\n * preserves human / unknown-source events, and rewrites `events.jsonl` +\n * `session.yaml` atomically under the session lock.\n *\n * The whole read-modify-write runs under {@link acquireLock} so a concurrent\n * writer cannot interleave; `dryRun` computes the result and writes nothing\n * (and takes no lock). If the prior `events.jsonl` has any line that cannot be\n * preserved (malformed / schema-invalid / half-written), the re-import is\n * ABORTED (`status: \"skipped\"`) rather than risk dropping data on the rewrite.\n */\nexport async function reimportPreservingId(\n paths: BasouPaths,\n manifest: Manifest,\n priorSessionId: string,\n freshPayload: SessionImportPayload,\n options: ReimportOptions = {},\n): Promise<ReimportResult> {\n // The id originates from an on-disk session directory, so it is already a\n // valid `ses_<ULID>`; the cast threads it into the typed record builders.\n const sessionId = priorSessionId as PrefixedId<\"ses\">;\n const importSource = freshPayload.session.source.kind;\n const sessionDir = join(paths.sessions, priorSessionId);\n\n const lock = options.dryRun === true ? null : await acquireLock(paths, \"session\", priorSessionId);\n try {\n // Pre-verify the prior chain BEFORE deriving anything from it: a re-import\n // that reuses event ids out of a hash-broken log would launder the break\n // into a freshly-valid chain. An `unchained` prior (imported before\n // chaining existed) passes — there is no chain to break, and the rewrite\n // below chains it. `incomplete` (yaml lost) also passes: the events are\n // internally consistent and the rewrite repairs the missing anchor.\n const priorVerdict = await verifyEventsChain(paths, priorSessionId);\n if (priorVerdict.status === \"tampered\") {\n return { status: \"skipped\", reason: \"prior_chain_broken\" };\n }\n\n // Strict read of the prior events: abort on ANY unpreservable line so the\n // atomic rewrite below never silently drops a human / unknown-source event.\n let priorUnreadable = false;\n const priorEvents = await readAllEvents(sessionDir, {\n onWarning: () => {\n priorUnreadable = true;\n },\n });\n if (priorUnreadable) {\n return { status: \"skipped\", reason: \"prior_events_unreadable\" };\n }\n\n // Partition by event source: re-derive exactly the events THIS adapter\n // produced (source === importSource); preserve everything else (human\n // local-cli notes / decisions AND any unknown-source event, since\n // EventSourceSchema is open vocab).\n const priorDerived = priorEvents.filter((e) => e.source === importSource);\n const preserved = priorEvents.filter((e) => e.source !== importSource);\n\n const {\n events: rederived,\n reusedIdCount,\n droppedPriorDerived,\n } = reuseDerivedIds(priorDerived, freshPayload.events, sessionId);\n if (droppedPriorDerived) {\n // The grown source no longer reproduces some prior derived event, so its\n // id would vanish (only happens on a non-append-only change). Abort rather\n // than silently drop an id a cross-session linked_events may reference.\n return { status: \"skipped\", reason: \"prior_derived_dropped\" };\n }\n\n // Merge derived + preserved into one stream ordered by occurred_at (stable\n // sort => derived before preserved at an equal timestamp) and enforce the\n // same monotonic invariant a fresh import guarantees, so a re-imported\n // session is indistinguishable in shape from a freshly imported one.\n const mergedEvents = [...rederived, ...preserved].sort(\n (a, b) => Date.parse(a.occurred_at) - Date.parse(b.occurred_at),\n );\n assertChronologicalOrder(mergedEvents);\n\n // Rebuild session.yaml from the fresh derivation (label, metrics, source +\n // size, related_files, timestamps, sanitized paths), preserving the id and\n // the human-owned fields a re-derivation must not clobber.\n const prior = await readSessionYaml(paths, priorSessionId);\n const { record } = buildSessionRecord(freshPayload.session, manifest, sessionId, {});\n const preservedInner: Session[\"session\"] = {\n ...record.session,\n // Defensive: keep any task_id already present on the prior yaml so a\n // re-derive never drops a link, whatever wrote it.\n task_id: prior.session.task_id ?? null,\n // Re-derivation always yields a null summary; keep a prior non-null one.\n summary: prior.session.summary ?? record.session.summary ?? null,\n };\n const updatedRecord: Session = { schema_version: \"0.1.0\", session: preservedInner };\n\n if (options.dryRun !== true) {\n // Capture the prior events.jsonl RAW BYTES before the rewrite so a\n // session.yaml failure can restore them VERBATIM. Re-serializing\n // `priorEvents` here instead would write an UNCHAINED file (the parsed\n // events lost their byte-exact form), contradicting the prior anchor.\n const eventsPath = join(sessionDir, \"events.jsonl\");\n let priorEventsRaw: Buffer | null = null;\n try {\n priorEventsRaw = await readFile(eventsPath);\n } catch (error: unknown) {\n if (!findErrorCode(error, \"ENOENT\")) {\n throw new Error(\"Failed to read events.jsonl\", { cause: error });\n }\n }\n\n const chainResult = await writeEventsBulk(sessionDir, mergedEvents, { chain: true });\n try {\n await overwriteYamlFile(\n join(sessionDir, \"session.yaml\"),\n withIntegrity(updatedRecord, chainResult),\n );\n } catch (error: unknown) {\n // events.jsonl and session.yaml are two separate atomic writes. If the\n // yaml write fails after the events were rewritten, restore the prior\n // events bytes so the session is never left with fresh events paired\n // with stale metadata (size / label / metrics / integrity anchor). The\n // yaml itself needs no restore: an atomic replace that threw never\n // renamed over the prior file.\n if (priorEventsRaw !== null) {\n await atomicReplace(eventsPath, priorEventsRaw).catch(() => undefined);\n } else {\n await rm(eventsPath, { force: true }).catch(() => undefined);\n }\n throw error;\n }\n }\n\n return {\n status: \"reimported\",\n sessionId,\n eventCount: mergedEvents.length,\n preservedCount: preserved.length,\n reusedIdCount,\n };\n } finally {\n await lock?.release();\n }\n}\n\n/** Options for {@link rechainSessionInPlace}. */\nexport type RechainOptions = {\n /**\n * Compute the outcome and write nothing. The session lock is STILL taken:\n * an unlocked read could observe a concurrent in-place re-import's\n * two-file write window (events rewritten, yaml not yet) and report a\n * state a locked run would never see.\n */\n dryRun?: boolean;\n};\n\n/** Result of {@link rechainSessionInPlace}. */\nexport type RechainResult =\n | { status: \"rechained\"; eventCount: number }\n | {\n status: \"skipped\";\n // `already_chained`: the session is `verified` — idempotent no-op.\n // `empty`: zero events; nothing to chain and no anchor is written\n // (same rule as the import writers).\n // `not_imported`: only the closed imported corpus may be chained — a\n // live/ad-hoc session would be appended to afterwards and turn\n // `tampered`.\n // `tampered`: the log already fails verification; rechaining it would\n // launder the break into a fresh valid chain. Inspect with\n // `basou verify`; `--force` re-import is the explicit override.\n // `events_unreadable`: a line cannot be preserved EXACTLY (blank or\n // whitespace-only line, invalid UTF-8, JSON parse failure, schema\n // gate failure, or a non-byte-identical JSON round-trip) — rechain\n // never normalizes or drops content.\n // `session_id_mismatch`: a line's session_id is not this session's id;\n // chaining it would manufacture an instantly-tampered session.\n // `yaml_missing` / `yaml_unreadable`: session.yaml absent / unparseable.\n reason:\n | \"already_chained\"\n | \"empty\"\n | \"not_imported\"\n | \"tampered\"\n | \"events_unreadable\"\n | \"session_id_mismatch\"\n | \"yaml_missing\"\n | \"yaml_unreadable\";\n };\n\n/**\n * Add the tamper-evidence hash chain, IN PLACE, to an imported session that\n * was written before chaining existed (or whose chain was legitimately never\n * computed). Event ids, order, field sets, values and key order are all\n * preserved exactly — each original line is re-emitted with only `prev_hash`\n * appended (see {@link chainRawJsonLines}); `session.yaml` is rewritten as\n * read with only `integrity` added. Nothing else changes, so cross-session\n * references (`linked_events`) survive, unlike a `--force` re-import.\n *\n * Rechaining asserts tamper-evidence FROM NOW ON; it does not retroactively\n * prove the pre-existing content was never modified before the migration.\n *\n * Refuses anything it cannot preserve exactly or that is not the closed\n * imported corpus — see {@link RechainResult} reasons. Throws (rather than\n * returning a skip) on environment-level I/O failures, mirroring\n * `verifyEventsChain`.\n */\nexport async function rechainSessionInPlace(\n paths: BasouPaths,\n sessionId: string,\n options: RechainOptions = {},\n): Promise<RechainResult> {\n const sessionDir = join(paths.sessions, sessionId);\n // Wrap lock-acquisition failures in the fixed pathless vocabulary: the CLI\n // surfaces per-session error messages verbatim, so a raw fs error here\n // would leak an absolute lockfile path.\n let lock: Awaited<ReturnType<typeof acquireLock>>;\n try {\n lock = await acquireLock(paths, \"session\", sessionId);\n } catch (error: unknown) {\n if (error instanceof Error && error.message === \"Lock is held by another process\") {\n throw error;\n }\n throw new Error(\"Failed to acquire lock\", { cause: error });\n }\n try {\n // 1. Status gate: only the imported corpus is closed against appends.\n let record: Session;\n try {\n record = await readSessionYaml(paths, sessionId);\n } catch (error: unknown) {\n if (error instanceof Error && error.message === \"YAML file not found\") {\n return { status: \"skipped\", reason: \"yaml_missing\" };\n }\n return { status: \"skipped\", reason: \"yaml_unreadable\" };\n }\n if (record.session.status !== \"imported\") {\n return { status: \"skipped\", reason: \"not_imported\" };\n }\n\n // 2. Verdict gate. Only a clean `unchained` log proceeds; `incomplete`\n // is unreachable here because the yaml-missing case returned above.\n const verdict = await verifyEventsChain(paths, sessionId);\n if (verdict.status === \"verified\") {\n return { status: \"skipped\", reason: \"already_chained\" };\n }\n if (verdict.status === \"empty\") {\n return { status: \"skipped\", reason: \"empty\" };\n }\n if (verdict.status !== \"unchained\") {\n return { status: \"skipped\", reason: \"tampered\" };\n }\n\n // 3. Raw-line gate: every line must be preservable EXACTLY. The decoded\n // text must re-encode to the same bytes (invalid UTF-8 would silently\n // normalize to U+FFFD), and every line must be byte-identical to its own\n // JSON round-trip (whitespace padding, duplicate keys or non-canonical\n // escapes would otherwise be silently rewritten). Every line a basou\n // writer produced satisfies both.\n const eventsPath = join(sessionDir, \"events.jsonl\");\n let priorRaw: Buffer;\n try {\n priorRaw = await readFile(eventsPath);\n } catch (error: unknown) {\n throw new Error(\"Failed to read events.jsonl\", { cause: error });\n }\n if (priorRaw.length === 0 || priorRaw[priorRaw.length - 1] !== 0x0a) {\n return { status: \"skipped\", reason: \"events_unreadable\" };\n }\n const text = priorRaw.toString(\"utf8\");\n if (!priorRaw.equals(Buffer.from(text, \"utf8\"))) {\n return { status: \"skipped\", reason: \"events_unreadable\" };\n }\n const rawLines = text.slice(0, -1).split(\"\\n\");\n for (const line of rawLines) {\n if (line.trim().length === 0) {\n return { status: \"skipped\", reason: \"events_unreadable\" };\n }\n let parsed: unknown;\n try {\n parsed = JSON.parse(line);\n } catch {\n return { status: \"skipped\", reason: \"events_unreadable\" };\n }\n if (JSON.stringify(parsed) !== line) {\n return { status: \"skipped\", reason: \"events_unreadable\" };\n }\n // Gate ONLY: the parsed/validated output is never written.\n if (!EventSchema.safeParse(parsed).success) {\n return { status: \"skipped\", reason: \"events_unreadable\" };\n }\n if ((parsed as Record<string, unknown>).session_id !== sessionId) {\n return { status: \"skipped\", reason: \"session_id_mismatch\" };\n }\n }\n\n if (options.dryRun === true) {\n return { status: \"rechained\", eventCount: rawLines.length };\n }\n\n // 4-6. Chain the ORIGINAL lines, write atomically, anchor the yaml read\n // in step 1 (all other fields preserved as-is). On a yaml failure,\n // restore the prior events bytes verbatim — same rollback as the\n // in-place re-import.\n const chainResult = chainRawJsonLines(rawLines, sessionId);\n const body = `${chainResult.lines.join(\"\\n\")}\\n`;\n try {\n await atomicReplace(eventsPath, body);\n } catch (error: unknown) {\n throw new Error(\"Failed to write events.jsonl\", { cause: error });\n }\n try {\n await overwriteYamlFile(\n join(sessionDir, \"session.yaml\"),\n withIntegrity(record, { headHash: chainResult.headHash, count: chainResult.count }),\n );\n } catch (error: unknown) {\n await atomicReplace(eventsPath, priorRaw).catch(() => undefined);\n throw error;\n }\n\n return { status: \"rechained\", eventCount: chainResult.count };\n } finally {\n await lock.release();\n }\n}\n","/**\n * Version of the `@basou/core` package, aligned with `manifest.yaml`'s\n * `basou_version` field as defined in the Basou v0.1 specification.\n */\nexport const BASOU_CORE_VERSION = \"0.1.0\";\n\nexport type {\n ClaudeTranscriptRecord,\n ClaudeTranscriptToPayloadOptions,\n CommandLookup,\n} from \"./adapters/claude-code/index.js\";\nexport {\n CLAUDE_IMPORT_SOURCE,\n claudeCodeAdapterMetadata,\n claudeTranscriptToImportPayload,\n resolveClaudeCodeCommand,\n summarizeAdapterOutput,\n} from \"./adapters/claude-code/index.js\";\nexport type {\n CodexRolloutRecord,\n CodexRolloutToPayloadOptions,\n} from \"./adapters/codex/index.js\";\nexport { CODEX_IMPORT_SOURCE, codexRolloutToImportPayload } from \"./adapters/codex/index.js\";\nexport type { ApprovalLocation, LoadedApproval } from \"./approval/index.js\";\nexport { enumerateApprovals, isLazyExpired, loadApproval } from \"./approval/index.js\";\nexport type { DecisionsRendererInput, DecisionsRendererResult } from \"./decisions/index.js\";\nexport { renderDecisions } from \"./decisions/index.js\";\nexport type {\n BulkChainResult,\n ChainBreakReason,\n ChainedEvents,\n ChainTailState,\n ChainVerdict,\n ChainVerdictStatus,\n ReplayOptions,\n ReplayWarning,\n WriteEventsBulkOptions,\n} from \"./events/index.js\";\nexport {\n appendChainedEvent,\n appendChainedEventLocked,\n appendEvent,\n chainEvents,\n chainRawJsonLines,\n genesisHash,\n inspectChainTail,\n lineHash,\n readAllEvents,\n replayEvents,\n serializeEventLine,\n verifyEventsChain,\n writeEventsBulk,\n} from \"./events/index.js\";\nexport type { DiffResult, FileChange, FileChangeStatus } from \"./git/diff.js\";\nexport { getDiff } from \"./git/diff.js\";\nexport type { GitSnapshot } from \"./git/snapshot.js\";\nexport {\n getSnapshot,\n isGitNotFound,\n resolveBasouRepositoryRoot,\n resolveRepositoryRoot,\n safeSimpleGit,\n tryRemoteUrl,\n} from \"./git/snapshot.js\";\nexport type { HandoffRendererInput, HandoffRendererResult } from \"./handoff/index.js\";\nexport { renderHandoff } from \"./handoff/index.js\";\nexport type { IdPrefix, PrefixedId } from \"./ids/ulid.js\";\nexport { ID_PREFIXES, isValidPrefixedId, prefixedUlid, ulid } from \"./ids/ulid.js\";\nexport { parseDuration } from \"./lib/duration.js\";\nexport { formatDurationMs } from \"./lib/format-duration.js\";\nexport { resolveSessionId, resolveTaskId } from \"./lib/id-resolver.js\";\nexport type { SanitizePathOptions, SanitizeRelatedFilesResult } from \"./lib/path-sanitizer.js\";\nexport {\n sanitizePath,\n sanitizeRelatedFiles,\n sanitizeWorkingDirectory,\n} from \"./lib/path-sanitizer.js\";\nexport type { SourceRootScope } from \"./lib/source-root-scope.js\";\nexport { AGENT_INFRA_DIRS, classifyFilesBySourceRoot } from \"./lib/source-root-scope.js\";\nexport type {\n OrientationRendererInput,\n OrientationRendererResult,\n OrientationSummary,\n} from \"./orientation/index.js\";\nexport { renderOrientation, summarizeOrientation } from \"./orientation/index.js\";\nexport type { ArchivePlan } from \"./project/archive.js\";\nexport { planArchive } from \"./project/archive.js\";\nexport type {\n GitignorePlanSummary,\n RepoGitignoreFacts,\n RepoGitignorePlan,\n} from \"./project/gitignore-plan.js\";\nexport { planGitignore } from \"./project/gitignore-plan.js\";\nexport type {\n PresetAction,\n PresetCollision,\n PresetMarkerConflict,\n PresetMarkerKind,\n PresetPlanSummary,\n PresetRepo,\n RepoPresetFacts,\n RepoPresetPlan,\n} from \"./project/preset.js\";\nexport { isRenderable, renderPresetBlock, summarizePresetPlan } from \"./project/preset.js\";\nexport type { RenamePlan } from \"./project/rename.js\";\nexport { pathBasename, planRename } from \"./project/rename.js\";\nexport type {\n AdoptCandidate,\n AdoptCandidateKind,\n PublishKind,\n PublishTarget,\n RepoEntry,\n RepoLanguage,\n RepoVisibility,\n RosterAdoptionPlan,\n RosterDriftSummary,\n SourceRootsReconcile,\n} from \"./project/roster.js\";\nexport {\n planRosterAdoption,\n reconcileSourceRoots,\n summarizeRosterDrift,\n} from \"./project/roster.js\";\nexport type {\n InstructionSymlinkFact,\n InstructionSymlinkState,\n RepoSymlinkFacts,\n RepoSymlinkPlan,\n SymlinkCollision,\n SymlinkConflict,\n SymlinkPlanSummary,\n} from \"./project/symlinks.js\";\nexport { summarizeSymlinkPlan } from \"./project/symlinks.js\";\nexport type {\n InstructionFileFact,\n RepoWiringFacts,\n WiringRisk,\n WiringSummary,\n} from \"./project/wiring.js\";\nexport { summarizeWiring } from \"./project/wiring.js\";\nexport type {\n ExistingViewLink,\n ViewCollision,\n ViewConflict,\n ViewLinkState,\n ViewRepoFact,\n ViewStrayUnknown,\n WorkspaceViewPlan,\n} from \"./project/workspace-view.js\";\nexport { planWorkspaceView } from \"./project/workspace-view.js\";\nexport type {\n ReportApprovalItem,\n ReportData,\n ReportDecisionItem,\n ReportRendererInput,\n ReportRendererResult,\n ReportSessionItem,\n ReportTaskItem,\n TaskStatusCount,\n} from \"./report/index.js\";\nexport { renderReport } from \"./report/index.js\";\nexport type {\n CitedReview,\n ReviewGapRepoSummary,\n ReviewGapsInput,\n ReviewGapsSummary,\n ReviewGapUnit,\n ReviewGapVerdict,\n} from \"./review/index.js\";\nexport { findReviewGaps, normalizeRepoKey, normalizeRepoPath } from \"./review/index.js\";\nexport { ChildProcessRunner } from \"./runtime/child-process-runner.js\";\nexport type {\n CaptureMode,\n ProcessRunner,\n RunOptions,\n RunResult,\n} from \"./runtime/process-runner.js\";\nexport type {\n AdapterOutputEvent,\n Approval,\n ApprovalApprovedEvent,\n ApprovalExpiredEvent,\n ApprovalRejectedEvent,\n ApprovalRequestedEvent,\n ApprovalStatus,\n CommandExecutedEvent,\n DecisionRecordedEvent,\n Event,\n FileChangedEvent,\n GitSnapshotEvent,\n JsonSchemaArtifact,\n Manifest,\n NoteAddedEvent,\n RiskLevel,\n Session,\n SessionEndedEvent,\n SessionImportPayload,\n SessionInnerImportInput,\n SessionIntegrity,\n SessionMetrics,\n SessionSourceKind,\n SessionStartedEvent,\n SessionStatus,\n SessionStatusChangedEvent,\n StatusSnapshot,\n Task,\n TaskArchivedEvent,\n TaskCreatedEvent,\n TaskDeletedEvent,\n TaskLinkageRefreshedEvent,\n TaskReconciledEvent,\n TaskStatus,\n TaskStatusChangedEvent,\n} from \"./schemas/index.js\";\nexport {\n ApprovalIdSchema,\n ApprovalSchema,\n ApprovalStatusSchema,\n buildJsonSchemas,\n DecisionIdSchema,\n EventIdSchema,\n EventSchema,\n EventSourceSchema,\n IsoTimestampSchema,\n JSON_SCHEMA_VERSION,\n ManifestSchema,\n RiskLevelSchema,\n SchemaVersionSchema,\n SessionIdSchema,\n SessionImportPayloadSchema,\n SessionInnerImportSchema,\n SessionIntegritySchema,\n SessionMetricsSchema,\n SessionSchema,\n SessionSourceKindSchema,\n SessionStatusSchema,\n StatusSchema,\n serializeJsonSchema,\n TaskIdSchema,\n TaskSchema,\n TaskStatusSchema,\n unknownManifestKeys,\n WorkspaceIdSchema,\n} from \"./schemas/index.js\";\nexport type {\n ActiveTimeBasis,\n DayWorkStats,\n MeasureAvailability,\n SessionWorkStats,\n SourceWorkStats,\n StatusCount,\n TokenTotals,\n WorkStatsInput,\n WorkStatsResult,\n WorkStatsTotals,\n} from \"./stats/index.js\";\nexport { ACTIVE_GAP_CAP_MS, computeWorkStats, sessionWorkStatsFromEvents } from \"./stats/index.js\";\nexport type {\n AppendBasouGitignoreOptions,\n AppendBasouGitignoreResult,\n AppendEventToExistingInput,\n AppendEventToExistingResult,\n ArchiveTaskInput,\n ArchiveTaskResult,\n AttachableStatus,\n AttachTaskInput,\n AttachUpdateTaskStatusInput,\n BasouPaths,\n CreateAdHocSessionInput,\n CreateAdHocSessionResult,\n CreateAdHocTaskInput,\n CreateManifestInput,\n CreateTaskInput,\n CreateTaskResult,\n DeleteTaskInput,\n DeleteTaskResult,\n EditTaskInput,\n EditTaskResult,\n FederatedRoot,\n ImportSessionOptions,\n ImportSessionResult,\n LoadFederatedOptions,\n LoadSessionEntriesOptions,\n LoadTaskEntriesOptions,\n LockHandle,\n LockScope,\n MarkerSection,\n Markers,\n RechainOptions,\n RechainResult,\n ReconcileAllResult,\n ReconcileAllTasksInput,\n ReconcileAllTasksOptions,\n ReconcileFailure,\n ReconcileResult,\n ReconcileTaskInput,\n RefreshLinkageInput,\n RefreshLinkageResult,\n ReimportOptions,\n ReimportResult,\n SessionEntry,\n SessionSkipReason,\n SuspectReason,\n TaskDocument,\n TaskSkipReason,\n TaskWriteAfterEventPhase,\n UpdateAdHocTaskStatusInput,\n UpdateTaskStatusInput,\n UpdateTaskStatusResult,\n WriteTaskFileMode,\n} from \"./storage/index.js\";\nexport {\n acquireLock,\n appendBasouGitignore,\n appendEventToExistingSession,\n archiveTask,\n assertBasouRootSafe,\n basouPaths,\n buildStatusSnapshot,\n classifySuspect,\n createAdHocSessionWithEvent,\n createManifest,\n createTaskWithEvent,\n deleteTask,\n editTask,\n ensureBasouDirectory,\n enumerateArchivedTaskIds,\n enumerateSessionDirs,\n enumerateTaskIds,\n FailedToFinalizeError,\n finalizeSessionYaml,\n findErrorCode,\n GENERATED_END,\n GENERATED_START,\n importSessionFromJson,\n isImportDerivedSource,\n linkYamlFile,\n loadFederatedSessionEntries,\n loadSessionEntries,\n loadTaskEntries,\n overwriteYamlFile,\n PROTOCOL_END,\n PROTOCOL_START,\n parseMarkers,\n readManifest,\n readMarkdownFile,\n readSessionYaml,\n readStatus,\n readTaskFile,\n readTaskFileWithArchiveFallback,\n readYamlFile,\n rechainSessionInPlace,\n reconcileAllTasks,\n reconcileTask,\n refreshTaskLinkedSessions,\n reimportPreservingId,\n removeMarkerSection,\n renderWithMarkers,\n STUCK_THRESHOLD_MS,\n TaskWriteAfterEventError,\n updateTaskStatusWithEvent,\n writeManifest,\n writeMarkdownFile,\n writeStatus,\n writeTaskFile,\n writeYamlFile,\n} from \"./storage/index.js\";\n"],"mappings":"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","names":["resolve","path","readString","payload","isObject","readNonNegInt","commandExecutedEvent","sessionStartedEvent","sessionEndedEvent","baseEvent","z","z","dirname","join","join","z","z","join","readdir","join","readFile","join","readFile","unlink","join","unlink","readFile","ulid","join","join","readFile","z","z","readdir","join","join","dirname","path","appendFile","join","appendFile","join","readFile","join","join","readFile","splitLinesBytes","carriesPrevHash","readdir","stat","join","z","z","stat","hasErrorCode","path","readdir","join","stat","join","createHash","mkdir","readdir","readFile","rename","stat","unlink","join","z","z","z","mkdir","join","join","mkdir","readFile","join","z","z","join","readFile","DEFAULT_ATTACHABLE_STATUSES","z","join","readFile","readdir","stat","createHash","eventId","unlink","mkdir","rename","join","shortId","basename","dirname","join","resolve","homedir","dirname","join","lstat","z","z","lstat","hasErrorCode","join","dirname","isPublicFacing","join","join","join","join","t","homedir","basename","isAbsolute","join","join","homedir","isAbsolute","basename","repo","list","spawn","spawn","resolve","z","z","z","z","lstat","mkdir","join","hasErrorCode","readFile","writeFile","join","hasErrorCode","readFile","readFile","hasErrorCode","mkdir","readFile","rm","homedir","join","join","mkdir","rm","homedir","readFile"]}
1
+ {"version":3,"sources":["../src/adapters/claude-code/claude-code-adapter.ts","../src/adapters/claude-code/stop-hook.ts","../src/ids/ulid.ts","../src/stats/active-time.ts","../src/adapters/session-label.ts","../src/adapters/claude-code/transcript-importer.ts","../src/adapters/codex/rollout-importer.ts","../src/approval/approval-store.ts","../src/lib/error-codes.ts","../src/schemas/approval.schema.ts","../src/schemas/shared.schema.ts","../src/storage/yaml-store.ts","../src/storage/atomic.ts","../src/decisions/decisions-renderer.ts","../src/events/event-replay.ts","../src/schemas/event.schema.ts","../src/storage/sessions.ts","../src/events/chained-append.ts","../src/storage/lockfile.ts","../src/events/chain.ts","../src/schemas/session.schema.ts","../src/events/event-writer.ts","../src/events/verify.ts","../src/git/snapshot.ts","../src/storage/status.ts","../src/schemas/status.schema.ts","../src/git/diff.ts","../src/handoff/handoff-renderer.ts","../src/lib/recency.ts","../src/storage/tasks.ts","../src/schemas/task.schema.ts","../src/storage/ad-hoc-session.ts","../src/lib/path-sanitizer.ts","../src/storage/task-index.ts","../src/schemas/task-index.schema.ts","../src/lib/duration.ts","../src/lib/format-duration.ts","../src/lib/id-resolver.ts","../src/lib/source-root-scope.ts","../src/orientation/orientation-renderer.ts","../src/storage/manifest.ts","../src/schemas/manifest.schema.ts","../src/project/relative-path.ts","../src/project/archive.ts","../src/project/gitignore-plan.ts","../src/project/preset.ts","../src/project/rename.ts","../src/project/roster.ts","../src/project/symlinks.ts","../src/project/wiring.ts","../src/project/workspace-view.ts","../src/report/report-renderer.ts","../src/stats/work-stats.ts","../src/review/review-gaps.ts","../src/runtime/child-process-runner.ts","../src/schemas/json-schema.ts","../src/schemas/session-import.schema.ts","../src/storage/basou-dir.ts","../src/storage/gitignore.ts","../src/storage/markdown-store.ts","../src/storage/session-import.ts","../src/index.ts"],"sourcesContent":["import { spawn } from \"node:child_process\";\n\n/**\n * Static metadata identifying the claude-code adapter as the session source.\n * Consumed by the CLI orchestration when populating `session.yaml.source`\n * and event `source` fields. The literal `kind` is part of the wire format\n * defined by the session schema; do not change without coordinated schema\n * migration.\n */\nexport const claudeCodeAdapterMetadata = {\n kind: \"claude-code-adapter\",\n version: \"0.1.0\",\n} as const;\n\n/**\n * Lookup predicate used by {@link resolveClaudeCodeCommand} to decide\n * whether a candidate executable is reachable on PATH. Exposed as a\n * parameter so tests can substitute a deterministic mock; production\n * callers should omit it and rely on the default `which`-based lookup.\n */\nexport type CommandLookup = (command: string) => Promise<boolean>;\n\n/**\n * Resolve the Claude Code CLI executable name. Tries `claude-code` first\n * and falls back to `claude`; the first candidate found on PATH wins.\n *\n * Throws a fixed-message Error when neither candidate is reachable, so\n * callers can present a single user-facing prompt to install the CLI.\n *\n * @throws Error(\"Claude Code CLI not found in PATH. Install claude-code (or claude) first.\")\n */\nexport async function resolveClaudeCodeCommand(\n lookup: CommandLookup = isOnPath,\n): Promise<{ command: string }> {\n for (const candidate of [\"claude-code\", \"claude\"]) {\n if (await lookup(candidate)) return { command: candidate };\n }\n throw new Error(\"Claude Code CLI not found in PATH. Install claude-code (or claude) first.\");\n}\n\n/**\n * Default {@link CommandLookup} backed by `which` (POSIX) — the spawn\n * succeeds with exit code 0 iff the candidate is on PATH. Windows fallback\n * is intentionally not implemented in v0.1; call sites that target Windows\n * supply their own lookup.\n */\nasync function isOnPath(command: string): Promise<boolean> {\n return new Promise((resolve) => {\n const child = spawn(\"which\", [command], { stdio: \"ignore\" });\n child.on(\"error\", () => resolve(false));\n child.on(\"exit\", (code) => resolve(code === 0));\n });\n}\n\n/**\n * Stub for the future `adapter_output` summary generator.\n *\n * The current release keeps `capture: \"none\"` and intentionally does\n * not emit `adapter_output` events, so this hook has no production\n * callers yet. The signature is committed so a later release can\n * implement raw_ref generation without retrofitting the adapter\n * scaffold.\n *\n * @throws Error - always; not implemented in this release.\n */\nexport function summarizeAdapterOutput(_stream: \"stdout\" | \"stderr\", _raw: string): string {\n throw new Error(\"adapter_output summary is not implemented in this release\");\n}\n","import type { ClaudeTranscriptRecord } from \"./transcript-importer.js\";\n\n/**\n * Default minimum number of \"action\" tool uses (Bash commands + file edits) a\n * session must contain before the Stop-hook nudge treats it as substantive\n * enough to be worth a session-end capture. Below this the session reads as a\n * trivial check / quick question and the hook stays silent rather than nagging.\n */\nexport const DEFAULT_STOP_HOOK_MIN_ACTIONS = 5;\n\n/**\n * Commands that constitute capturing the session's intent: the agent ran one of\n * basou's capture verbs, so the why/next-step is recorded and no nudge is owed.\n * Matched against each Bash tool-use command string. The verb must START a\n * command segment — at the beginning of the line or after a `;` / `&` / `|` /\n * `(` / newline separator (so `cd x && basou note \"...\"` matches) — so a capture\n * verb merely MENTIONED inside another command's quoted argument (e.g.\n * `rg \"basou note\"`, `echo \"basou decision capture\"`) does NOT falsely count as\n * a capture and permanently silence the nudge.\n */\nconst CAPTURE_COMMAND_PATTERN = /(?:^|[\\n;&|(])\\s*basou\\s+(?:decision\\s+(?:capture|record)|note)\\b/;\n\n/** Tool-use names that mutate a file; each counts as one substantive action. */\nconst FILE_EDIT_TOOLS = new Set([\"Edit\", \"Write\", \"NotebookEdit\"]);\n\nexport type StopHookEvaluationInput = {\n /**\n * Parsed transcript records, one per JSONL line of the current session's\n * transcript. The caller drops malformed lines; this function reads every\n * field defensively, like the importer, since the format is undocumented.\n */\n records: ReadonlyArray<ClaudeTranscriptRecord>;\n /**\n * The Stop hook's `stop_hook_active` stdin flag: true when Claude is already\n * continuing because of a previous Stop-hook response. When true the nudge\n * stays silent so it can never form a continuation loop.\n */\n stopHookActive: boolean;\n /** Override the substantive-work threshold (defaults to {@link DEFAULT_STOP_HOOK_MIN_ACTIONS}). */\n minActions?: number;\n};\n\n/** Why the hook stayed silent (useful for tests and `--json` introspection). */\nexport type StopHookSilentReason = \"stop_hook_active\" | \"not_substantive\" | \"already_captured\";\n\nexport type StopHookEvaluation =\n | { kind: \"silent\"; reason: StopHookSilentReason; commandCount: number; fileCount: number }\n | { kind: \"nudge\"; additionalContext: string; commandCount: number; fileCount: number };\n\n/**\n * Decide whether a finished turn warrants a non-blocking capture nudge.\n *\n * Pure: no disk or environment access. The CLI handler reads the Stop hook's\n * stdin payload and the transcript file, parses the JSONL into `records`, and\n * passes them here. A `nudge` result is rendered as\n * `hookSpecificOutput.additionalContext` (non-blocking — Claude may act on it\n * or stop); a `silent` result emits nothing.\n *\n * The nudge fires only when ALL hold:\n * - not already continuing from a prior nudge (`stopHookActive` is false), so\n * the hook never loops;\n * - the session did substantive work (>= `minActions` Bash commands + edits),\n * so trivial check sessions are left alone;\n * - no capture verb (`basou decision capture` / `decision record` / `note`)\n * was run this session, so a session that already recorded its intent is\n * left alone.\n */\nexport function evaluateStopHook(input: StopHookEvaluationInput): StopHookEvaluation {\n const minActions = input.minActions ?? DEFAULT_STOP_HOOK_MIN_ACTIONS;\n\n // Loop guard first: a turn that is already a continuation from a prior nudge\n // can never nudge again, so short-circuit before scanning the transcript at\n // all (the counts are unused on this path).\n if (input.stopHookActive) {\n return { kind: \"silent\", reason: \"stop_hook_active\", commandCount: 0, fileCount: 0 };\n }\n\n let commandCount = 0;\n let fileCount = 0;\n let captured = false;\n\n for (const record of input.records) {\n if (readString(record.type) !== \"assistant\") continue;\n for (const tool of toolUsesOf(record)) {\n const name = readString(tool.name);\n if (name === undefined) continue;\n if (name === \"Bash\") {\n commandCount += 1;\n const input2 = isObject(tool.input) ? tool.input : undefined;\n const command = input2 !== undefined ? readString(input2.command) : undefined;\n if (command !== undefined && CAPTURE_COMMAND_PATTERN.test(command)) captured = true;\n } else if (FILE_EDIT_TOOLS.has(name)) {\n fileCount += 1;\n }\n }\n }\n\n if (captured) {\n return { kind: \"silent\", reason: \"already_captured\", commandCount, fileCount };\n }\n if (commandCount + fileCount < minActions) {\n return { kind: \"silent\", reason: \"not_substantive\", commandCount, fileCount };\n }\n\n return {\n kind: \"nudge\",\n additionalContext: renderNudge(commandCount, fileCount),\n commandCount,\n fileCount,\n };\n}\n\n/** The advisory text fed back to the model (addressed to the agent, not the user). */\nfunction renderNudge(commandCount: number, fileCount: number): string {\n const ran = `${commandCount} ${commandCount === 1 ? \"command\" : \"commands\"}`;\n const edited = `${fileCount} ${fileCount === 1 ? \"file\" : \"files\"}`;\n return [\n `This session ran ${ran} and edited ${edited} but recorded no decisions or next step.`,\n \"If meaningful decisions were made (the chosen approach, rejected alternatives, and why) or there is a clear next step, capture them now so the next session can resume correctly:\",\n ' - Decisions: run `basou decision capture` and pipe a JSON array (one object per decision; \"title\" required, plus optional rationale/alternatives/rejected_reason/linked_files; set \"kind\":\"track\" for an unfinished strategic direction).',\n ' - Next step: run `basou note \"<what you would do next>\"`.',\n \"If nothing is worth capturing, just stop — do not invent decisions.\",\n ].join(\"\\n\");\n}\n\n// --- defensive readers (mirror transcript-importer's undocumented-format style) ---\n\nfunction readString(value: unknown): string | undefined {\n return typeof value === \"string\" && value.length > 0 ? value : undefined;\n}\n\nfunction isObject(value: unknown): value is Record<string, unknown> {\n return typeof value === \"object\" && value !== null && !Array.isArray(value);\n}\n\n/**\n * Extract the `tool_use` items from an assistant record's `message.content[]`.\n * Returns an empty array for any record that does not match the expected\n * nesting, so callers can iterate unconditionally.\n */\nfunction toolUsesOf(record: ClaudeTranscriptRecord): Array<Record<string, unknown>> {\n const message = isObject(record.message) ? record.message : undefined;\n const content = message !== undefined && Array.isArray(message.content) ? message.content : [];\n const result: Array<Record<string, unknown>> = [];\n for (const item of content) {\n if (isObject(item) && readString(item.type) === \"tool_use\") result.push(item);\n }\n return result;\n}\n","import { isValid as isValidUlid, monotonicFactory } from \"ulid\";\n\n/**\n * Allowed ID type prefixes for Basou entities.\n *\n * Frozen at runtime so that mutating the exported array cannot diverge from\n * the validation set used internally. The single source of truth for both\n * the `IdPrefix` type and runtime prefix checks.\n */\nexport const ID_PREFIXES = Object.freeze([\"ws\", \"task\", \"ses\", \"evt\", \"appr\", \"decision\"] as const);\n\n/**\n * Type prefix used for Basou entity IDs.\n * Format: `<prefix>_<26-char ULID>`, e.g. `ws_01HXABCDEF1234567890ABCDEF`.\n */\nexport type IdPrefix = (typeof ID_PREFIXES)[number];\n\n/**\n * A Basou entity ID as a template literal type.\n *\n * `PrefixedId<\"ses\">` narrows to ``ses_${string}`` so a session schema can\n * preserve the prefix in its inferred type beyond runtime validation.\n */\nexport type PrefixedId<P extends IdPrefix = IdPrefix> = `${P}_${string}`;\n\nconst PREFIX_SET = new Set<string>(ID_PREFIXES);\n\n// ULID body shape: 26 chars, first char 0-7 (48-bit timestamp / 5-bit Crockford\n// symbols), remaining 25 chars use Crockford alphabet excluding I, L, O, U.\n// Enforced locally because npm `ulid`'s `isValid` does not reject leading 8 or 9.\nconst ULID_BODY_REGEX = /^[0-7][0-9A-HJKMNP-TV-Z]{25}$/;\n\n// Module-scope monotonic factory. Created once at module load. Pure function\n// so it does not violate `sideEffects: false` of the surrounding package.\nconst monotonic = monotonicFactory();\n\n/**\n * Generate a Crockford Base32 ULID.\n *\n * The result is a 26-character, lexicographically time-sortable identifier.\n * Multiple calls within the same millisecond are strictly increasing for the\n * lifetime of the current process.\n *\n * NOTE: `seedTime` is forwarded to the underlying monotonic factory and is\n * NOT a deterministic seed: repeated calls with the same `seedTime` still\n * return strictly increasing values, because the factory increments its\n * internal counter on each call.\n *\n * @param seedTime Optional millisecond timestamp passed to the monotonic\n * factory. Useful for ordered generation in tests; not deterministic.\n */\nexport function ulid(seedTime?: number): string {\n return monotonic(seedTime);\n}\n\n/**\n * Generate a prefixed Basou ID, e.g. `ses_01HXABCDEF1234567890ABCDEF`.\n *\n * The return type preserves the prefix as a template literal type so that\n * downstream zod schemas can narrow an `IdPrefix` parameter through the API.\n *\n * Throws if `prefix` is not one of {@link ID_PREFIXES}. The runtime guard\n * defends against JavaScript callers and casted TypeScript that bypass the\n * compile-time `IdPrefix` constraint.\n */\nexport function prefixedUlid<P extends IdPrefix>(prefix: P): PrefixedId<P> {\n if (!PREFIX_SET.has(prefix)) {\n throw new Error(`Unknown ID prefix: ${prefix}`);\n }\n return `${prefix}_${ulid()}` as PrefixedId<P>;\n}\n\n/**\n * Check whether the given string is a valid prefixed Basou ID.\n *\n * Returns true only if the string has shape `<prefix>_<ULID>` where prefix is\n * one of {@link ID_PREFIXES} and the trailing 26 characters form a valid\n * Crockford Base32 ULID. Validation combines a strict shape regex (to enforce\n * the 0-7 leading char and the I/L/O/U exclusion) with the npm `ulid`\n * library's `isValid` for forward compatibility.\n *\n * NOTE: This validates the prefix is known. Schemas that require a specific\n * prefix (e.g. only `ses_*` for a session ID) must add their own narrowing.\n */\nexport function isValidPrefixedId(value: string): boolean {\n const idx = value.indexOf(\"_\");\n if (idx <= 0) return false;\n const prefix = value.slice(0, idx);\n const ulidPart = value.slice(idx + 1);\n if (!PREFIX_SET.has(prefix)) return false;\n if (!ULID_BODY_REGEX.test(ulidPart)) return false;\n return isValidUlid(ulidPart);\n}\n","/**\n * Gap longer than this between two consecutive engagement timestamps is treated\n * as idle and not credited as active time. A deliberately coarse heuristic: a\n * focus / billable-attention proxy, NOT a measure of model compute. 5 minutes.\n */\nexport const ACTIVE_GAP_CAP_MS = 5 * 60 * 1000;\n\n/** A wall-clock range, in epoch milliseconds, expressed as `[start, end]`. */\nexport type IntervalMs = [start: number, end: number];\n\n/** A wall-clock range expressed as ISO-8601 strings (for persistence). */\nexport type IsoInterval = { start: string; end: string };\n\n/**\n * Identifier stored in `metrics.active_time_method` for active time derived\n * from genuine engagement timestamps (conversation turns plus action events).\n * Bump this string if the derivation method changes, so stored numbers remain\n * interpretable.\n */\nexport const ENGAGED_TURNS_METHOD = \"engaged-turns\";\n\n/**\n * Identifier stored in `metrics.active_time_method` for active time that uses a\n * source's explicit per-turn intervals (e.g. Codex `task_started` /\n * `task_complete`) for the in-turn duration, with the gap-capped engagement\n * series only bridging between turns. More faithful than {@link\n * ENGAGED_TURNS_METHOD}: in-turn time is the log's real wall-clock span, not a\n * gap-capped approximation, and a session's final turn is credited. The active\n * semantics are unchanged — still human-engaged time with idle gaps excluded —\n * only the in-turn precision improves. Bump this string if the derivation\n * method changes, so stored numbers stay interpretable.\n */\nexport const TURN_INTERVALS_METHOD = \"turn-intervals\";\n\n/**\n * Build active intervals from a list of engagement timestamps (epoch ms).\n *\n * Each consecutive pair credits the range `[t_prev, t_prev + min(gap, capMs)]`:\n * activity at a timestamp is assumed to continue until the next timestamp, or\n * for at most `capMs` if the next is further away (the remainder is idle).\n * Adjacent or overlapping ranges are merged into runs. The summed duration of\n * the merged intervals equals `sum(min(gap, capMs))`, so this both reproduces a\n * gap-capped active-time scalar and yields the real ranges needed for\n * cross-session union.\n *\n * Non-finite timestamps are skipped (the single invalid-timestamp policy) and\n * the input is sorted internally, so callers may pass timestamps in any order.\n */\nexport function activeTimeFromTimestamps(\n timestampsMs: ReadonlyArray<number>,\n capMs: number,\n): { ms: number; intervals: IntervalMs[] } {\n const sorted = timestampsMs.filter((t) => Number.isFinite(t)).sort((a, b) => a - b);\n const raw: IntervalMs[] = [];\n for (let i = 1; i < sorted.length; i++) {\n const prev = sorted[i - 1];\n const curr = sorted[i];\n if (prev === undefined || curr === undefined) continue;\n const gap = curr - prev;\n if (gap <= 0) continue;\n raw.push([prev, prev + Math.min(gap, capMs)]);\n }\n const intervals = mergeIntervals(raw);\n return { ms: sumDurations(intervals), intervals };\n}\n\n/** Merge a set of (possibly unsorted / overlapping) intervals into disjoint runs. */\nexport function mergeIntervals(intervals: ReadonlyArray<IntervalMs>): IntervalMs[] {\n const sorted = [...intervals].sort((a, b) => a[0] - b[0]);\n const merged: IntervalMs[] = [];\n for (const [start, end] of sorted) {\n const last = merged[merged.length - 1];\n // `start <= last end` joins adjacent runs (an interval ending exactly where\n // the next begins) as well as genuine overlaps.\n if (last !== undefined && start <= last[1]) {\n if (end > last[1]) last[1] = end;\n } else {\n merged.push([start, end]);\n }\n }\n return merged;\n}\n\n/**\n * De-duplicate active time across many sessions: merge all their intervals and\n * return the union duration (so concurrent sessions do not double-count human\n * wall-clock) together with the merged ranges.\n */\nexport function unionDurationMs(intervals: ReadonlyArray<IntervalMs>): {\n ms: number;\n merged: IntervalMs[];\n} {\n const merged = mergeIntervals(intervals);\n return { ms: sumDurations(merged), merged };\n}\n\n/** Convert epoch-ms intervals to ISO ranges for persistence. */\nexport function intervalsMsToIso(intervals: ReadonlyArray<IntervalMs>): IsoInterval[] {\n return intervals.map(([start, end]) => ({\n start: new Date(start).toISOString(),\n end: new Date(end).toISOString(),\n }));\n}\n\n/** Parse stored ISO ranges back to epoch-ms intervals, skipping unparseable ones. */\nexport function intervalsIsoToMs(intervals: ReadonlyArray<IsoInterval>): IntervalMs[] {\n const out: IntervalMs[] = [];\n for (const { start, end } of intervals) {\n const s = Date.parse(start);\n const e = Date.parse(end);\n if (Number.isFinite(s) && Number.isFinite(e) && e >= s) out.push([s, e]);\n }\n return out;\n}\n\nfunction sumDurations(intervals: ReadonlyArray<IntervalMs>): number {\n let total = 0;\n for (const [start, end] of intervals) total += end - start;\n return total;\n}\n","/**\n * The date portion of an imported session's human-readable label.\n *\n * A same-day session reads as a single date (`2026-06-22`). A session that\n * spans a day boundary — e.g. a long evening-into-morning run — reads as a\n * range (`2026-06-21..2026-06-22`) so the most recent day stays visible instead\n * of the work being buried under the (older) start date when scanning\n * `basou session list`. The label is sorted/listed by `started_at` elsewhere;\n * this only governs the displayed text.\n *\n * Dates are the raw ISO date prefix with no timezone normalization, matching\n * how `started_at` / `ended_at` are stored. `ended_at` is never earlier than\n * `started_at` by instant, but with mixed UTC offsets the earlier instant can\n * still carry the later calendar date, so the two dates are ordered\n * lexicographically (= chronologically for `YYYY-MM-DD`) and the range always\n * reads earliest..latest.\n */\nexport function sessionLabelDateSpan(startIso: string, endIso: string): string {\n const a = startIso.slice(0, 10);\n const b = endIso.slice(0, 10);\n if (a === b) return a;\n return a < b ? `${a}..${b}` : `${b}..${a}`;\n}\n","import { type PrefixedId, prefixedUlid } from \"../../ids/ulid.js\";\nimport type { Event } from \"../../schemas/event.schema.js\";\nimport type { Manifest } from \"../../schemas/manifest.schema.js\";\nimport type { SessionImportPayload } from \"../../schemas/session-import.schema.js\";\nimport {\n ACTIVE_GAP_CAP_MS,\n activeTimeFromTimestamps,\n ENGAGED_TURNS_METHOD,\n intervalsMsToIso,\n} from \"../../stats/active-time.js\";\nimport { sessionLabelDateSpan } from \"../session-label.js\";\n\n/**\n * The `source` string stamped on every event derived from a Claude Code\n * native transcript, and the matching session `source.kind`.\n */\nexport const CLAUDE_IMPORT_SOURCE = \"claude-code-import\";\n\n/**\n * One parsed line of a Claude Code native transcript\n * (`~/.claude/projects/<encoded-cwd>/<uuid>.jsonl`). The shape is the\n * vendor's internal message log, not Basou's event schema, so every field\n * is read defensively — unknown record types and missing fields are skipped\n * rather than rejected (transcripts are an undocumented format that may gain\n * fields between Claude Code releases).\n */\nexport type ClaudeTranscriptRecord = Record<string, unknown>;\n\n/** Options for {@link claudeTranscriptToImportPayload}. */\nexport type ClaudeTranscriptToPayloadOptions = {\n /** Workspace id of the target Basou workspace (from its manifest). */\n workspaceId: Manifest[\"workspace\"][\"id\"];\n /**\n * Claude Code session id (transcript filename / `sessionId`). Stored as\n * `session.source.external_id` so re-imports can be deduplicated. Falls\n * back to the `sessionId` read from the records when omitted.\n */\n externalId?: string;\n /**\n * Byte size of the source transcript that produced `records`, stored as\n * `session.source.source_size_bytes` so a later import can detect growth and\n * re-import the session. The caller passes the size of the buffer it actually\n * read (an immutable snapshot of the parsed bytes), so the stored size always\n * matches the imported content. Omitted => the field is not recorded.\n */\n sourceSizeBytes?: number;\n};\n\n/**\n * Transform a Claude Code native transcript into a Basou\n * {@link SessionImportPayload}, ready to hand to `importSessionFromJson`.\n *\n * This is a pure function: no disk or environment access. It DERIVES Basou's\n * provenance-level events from the transcript's message-level records, rather\n * than mapping one-to-one:\n *\n * - `session_started` / `session_ended` from the first / last timestamped record.\n * - `command_executed` from each `Bash` tool use, recorded as `bash -c \"<cmd>\"`\n * (the transcript carries the shell line, not a parsed argv).\n * - `file_changed` from each `Edit` / `Write` / `NotebookEdit` tool use.\n * - `decision_recorded` from each `AskUserQuestion` tool use: one decision per\n * question, titled `<question> -> <chosen answer>`. The chosen answer is read\n * from the paired result record's structured `toolUseResult.answers` map; a\n * question with no recorded string answer is skipped.\n *\n * Exit codes and per-command durations are not present in the transcript, so\n * `command_executed.exit_code` is `null` and `duration_ms` is `0`.\n *\n * Returns `null` when the transcript has no timestamped records, or no\n * observable command / file / decision action — such sessions carry no\n * provenance worth importing and are skipped by the caller.\n *\n * Event `id` / `session_id` are placeholders; `importSessionFromJson` mints\n * fresh ids on the way in. They are valid-by-construction so the payload\n * still passes `SessionImportPayloadSchema` validation upstream.\n */\nexport function claudeTranscriptToImportPayload(\n records: ReadonlyArray<ClaudeTranscriptRecord>,\n options: ClaudeTranscriptToPayloadOptions,\n): SessionImportPayload | null {\n const placeholderSessionId = prefixedUlid(\"ses\");\n // AskUserQuestion answers live on the *result* record, which arrives after\n // the originating tool_use; pre-index them so decisions can be derived at the\n // tool_use site in the single forward pass below.\n const askAnswers = indexAskAnswers(records);\n const derived: Event[] = [];\n const relatedFiles = new Set<string>();\n // Real transcripts are NOT strictly ordered by timestamp on disk\n // (sidechains and async writes interleave), so file order cannot be\n // trusted. Track the earliest / latest timestamp explicitly, and order the\n // derived events by occurred_at below.\n let minTs: string | undefined;\n let maxTs: string | undefined;\n let workingDir: string | undefined;\n let claudeSessionId: string | undefined;\n // Model-usage rollup: the transcript carries per-assistant-message token\n // usage; sum it into a session total (see metrics on the payload below).\n let outputTokens = 0;\n let inputTokens = 0;\n let cachedInputTokens = 0;\n // A single assistant message is split across multiple records (thinking /\n // text / tool_use), each carrying the SAME message.id and the SAME usage.\n // Count usage once per message.id to avoid multiplying the token totals.\n const seenMessageIds = new Set<string>();\n // Genuine engagement timestamps for the billing-oriented active-time metric:\n // real human prompts and assistant messages (action tool uses live inside\n // assistant messages, so they are subsumed). Sub-agent sidechains and\n // tool-result / meta records are excluded so autonomous loops and noise do\n // not inflate billable time. Assistant messages are counted once per\n // message.id, matching the token dedup above.\n const engagementTsMs: number[] = [];\n const seenEngagementMessageIds = new Set<string>();\n\n for (const record of records) {\n const ts = readString(record.timestamp);\n if (ts === undefined) continue;\n if (minTs === undefined || Date.parse(ts) < Date.parse(minTs)) minTs = ts;\n if (maxTs === undefined || Date.parse(ts) > Date.parse(maxTs)) maxTs = ts;\n if (workingDir === undefined) workingDir = readString(record.cwd);\n if (claudeSessionId === undefined) claudeSessionId = readString(record.sessionId);\n\n if (record.isSidechain !== true) {\n const tsMs = Date.parse(ts);\n if (Number.isFinite(tsMs)) {\n const recType = readString(record.type);\n if (recType === \"user\") {\n if (isHumanUserMessage(record)) engagementTsMs.push(tsMs);\n } else if (recType === \"assistant\") {\n const msg = isObject(record.message) ? record.message : undefined;\n const mid = msg !== undefined ? readString(msg.id) : undefined;\n if (mid === undefined || !seenEngagementMessageIds.has(mid)) {\n if (mid !== undefined) seenEngagementMessageIds.add(mid);\n engagementTsMs.push(tsMs);\n }\n }\n }\n }\n\n if (readString(record.type) !== \"assistant\") continue;\n\n const message = isObject(record.message) ? record.message : undefined;\n const usage = message !== undefined && isObject(message.usage) ? message.usage : undefined;\n if (usage !== undefined) {\n // Dedup by message.id; records without an id are counted individually.\n const messageId = message !== undefined ? readString(message.id) : undefined;\n const alreadyCounted = messageId !== undefined && seenMessageIds.has(messageId);\n if (!alreadyCounted) {\n if (messageId !== undefined) seenMessageIds.add(messageId);\n outputTokens += readNonNegInt(usage.output_tokens);\n inputTokens += readNonNegInt(usage.input_tokens);\n cachedInputTokens += readNonNegInt(usage.cache_read_input_tokens);\n }\n }\n\n const cwd = readString(record.cwd) ?? workingDir ?? \".\";\n for (const item of toolUses(record)) {\n const name = readString(item.name);\n const input = isObject(item.input) ? item.input : undefined;\n if (input === undefined) continue;\n\n if (name === \"Bash\") {\n const command = readString(input.command);\n if (command !== undefined) {\n derived.push(commandExecutedEvent(ts, placeholderSessionId, command, cwd));\n }\n continue;\n }\n\n if (name === \"AskUserQuestion\") {\n const useId = readString(item.id);\n const answers = useId !== undefined ? askAnswers.get(useId) : undefined;\n if (answers !== undefined) {\n // One decision per question; preserve the order the questions were\n // asked (Object.entries keeps insertion order for string keys, and\n // the stable sort below keeps it among the same-timestamp events).\n for (const [question, answer] of Object.entries(answers)) {\n if (question.length === 0) continue;\n const answerStr = typeof answer === \"string\" && answer.length > 0 ? answer : undefined;\n const title = answerStr !== undefined ? `${question} -> ${answerStr}` : question;\n derived.push(decisionRecordedEvent(ts, placeholderSessionId, title));\n }\n }\n continue;\n }\n\n if (name === \"Edit\" || name === \"Write\" || name === \"NotebookEdit\") {\n const path = readString(input.file_path) ?? readString(input.notebook_path);\n if (path !== undefined) {\n // Edit / NotebookEdit mutate an existing file; Write is treated as a\n // creation. The transcript does not reliably distinguish a Write\n // that overwrites, so \"added\" is the conservative default.\n const changeType = name === \"Write\" ? \"added\" : \"modified\";\n relatedFiles.add(path);\n derived.push(fileChangedEvent(ts, placeholderSessionId, path, changeType));\n }\n }\n }\n }\n\n if (minTs === undefined || maxTs === undefined) return null;\n if (derived.length === 0) return null;\n\n // Order derived events by occurred_at so the assembled stream is\n // non-decreasing — importSessionFromJson rejects out-of-order events.\n // Array.prototype.sort is stable, so same-timestamp events keep their\n // transcript order.\n derived.sort((a, b) => Date.parse(a.occurred_at) - Date.parse(b.occurred_at));\n\n const events: Event[] = [\n sessionStartedEvent(minTs, placeholderSessionId),\n ...derived,\n sessionEndedEvent(maxTs, placeholderSessionId),\n ];\n\n const externalId = options.externalId ?? claudeSessionId;\n // Human-readable label: when + how much, so the session reads as content in\n // `basou session list` / handoff rather than an opaque id. The source id is\n // kept structurally in `source.external_id` (not the label), and paths are\n // deliberately excluded — the label is NOT path-sanitized downstream, so a\n // raw file path here would leak an operator-private prefix.\n const commandCount = derived.reduce((n, e) => (e.type === \"command_executed\" ? n + 1 : n), 0);\n const fileCount = relatedFiles.size;\n const label = `claude-code ${sessionLabelDateSpan(minTs, maxTs)}: ${commandCount} ${commandCount === 1 ? \"command\" : \"commands\"}, ${fileCount} ${fileCount === 1 ? \"file\" : \"files\"}`;\n\n // Engaged-active time from the genuine engagement series (needs >= 2 points\n // to bound any gap); omitted when too sparse so stats falls back to the\n // event-derived measure.\n const active =\n engagementTsMs.length >= 2\n ? activeTimeFromTimestamps(engagementTsMs, ACTIVE_GAP_CAP_MS)\n : undefined;\n\n // Only include fields actually present; omit metrics entirely for a\n // transcript that carried neither token usage nor an engaged-time signal.\n const metricsFields = {\n ...(outputTokens > 0 ? { output_tokens: outputTokens } : {}),\n ...(inputTokens > 0 ? { input_tokens: inputTokens } : {}),\n ...(cachedInputTokens > 0 ? { cached_input_tokens: cachedInputTokens } : {}),\n ...(active !== undefined && active.ms > 0\n ? {\n active_time_ms: active.ms,\n active_intervals: intervalsMsToIso(active.intervals),\n active_gap_cap_ms: ACTIVE_GAP_CAP_MS,\n active_time_method: ENGAGED_TURNS_METHOD,\n }\n : {}),\n };\n const metrics = Object.keys(metricsFields).length > 0 ? metricsFields : undefined;\n\n const payload: SessionImportPayload = {\n schema_version: \"0.1.0\",\n session: {\n label,\n workspace_id: options.workspaceId,\n source: {\n kind: CLAUDE_IMPORT_SOURCE,\n version: \"0.1.0\",\n ...(externalId !== undefined ? { external_id: externalId } : {}),\n ...(options.sourceSizeBytes !== undefined\n ? { source_size_bytes: options.sourceSizeBytes }\n : {}),\n },\n started_at: minTs,\n ended_at: maxTs,\n // Validated against the canonical enum here; importSessionFromJson\n // overwrites it with the literal \"imported\" regardless.\n status: \"imported\",\n working_directory: workingDir ?? \".\",\n invocation: { command: \"claude\", args: [], exit_code: null },\n related_files: [...relatedFiles].sort(),\n summary: null,\n ...(metrics !== undefined ? { metrics } : {}),\n },\n events,\n };\n return payload;\n}\n\n// --- event builders -------------------------------------------------------\n\nfunction baseEvent(\n occurredAt: string,\n sessionId: PrefixedId<\"ses\">,\n): {\n schema_version: \"0.1.0\";\n id: PrefixedId<\"evt\">;\n session_id: PrefixedId<\"ses\">;\n occurred_at: string;\n source: string;\n} {\n return {\n schema_version: \"0.1.0\",\n id: prefixedUlid(\"evt\"),\n session_id: sessionId,\n occurred_at: occurredAt,\n source: CLAUDE_IMPORT_SOURCE,\n };\n}\n\nfunction sessionStartedEvent(occurredAt: string, sessionId: PrefixedId<\"ses\">): Event {\n return { ...baseEvent(occurredAt, sessionId), type: \"session_started\" };\n}\n\nfunction sessionEndedEvent(occurredAt: string, sessionId: PrefixedId<\"ses\">): Event {\n return { ...baseEvent(occurredAt, sessionId), type: \"session_ended\" };\n}\n\nfunction commandExecutedEvent(\n occurredAt: string,\n sessionId: PrefixedId<\"ses\">,\n command: string,\n cwd: string,\n): Event {\n return {\n ...baseEvent(occurredAt, sessionId),\n type: \"command_executed\",\n command: \"bash\",\n args: [\"-c\", command],\n cwd,\n exit_code: null,\n duration_ms: 0,\n };\n}\n\nfunction fileChangedEvent(\n occurredAt: string,\n sessionId: PrefixedId<\"ses\">,\n path: string,\n changeType: \"added\" | \"modified\",\n): Event {\n return {\n ...baseEvent(occurredAt, sessionId),\n type: \"file_changed\",\n path,\n change_type: changeType,\n };\n}\n\nfunction decisionRecordedEvent(\n occurredAt: string,\n sessionId: PrefixedId<\"ses\">,\n title: string,\n): Event {\n return {\n ...baseEvent(occurredAt, sessionId),\n type: \"decision_recorded\",\n decision_id: prefixedUlid(\"decision\"),\n title,\n };\n}\n\n// --- defensive readers ----------------------------------------------------\n\nfunction readString(value: unknown): string | undefined {\n return typeof value === \"string\" && value.length > 0 ? value : undefined;\n}\n\n/** Read a non-negative integer token count, treating anything else as 0. */\nfunction readNonNegInt(value: unknown): number {\n return typeof value === \"number\" && Number.isInteger(value) && value >= 0 ? value : 0;\n}\n\nfunction isObject(value: unknown): value is Record<string, unknown> {\n return typeof value === \"object\" && value !== null && !Array.isArray(value);\n}\n\n/**\n * A `user` record is a genuine human prompt when its message content is a\n * non-empty string, or an array containing at least one non-`tool_result`\n * block (e.g. text / image). A record whose content is only `tool_result`\n * blocks is the assistant's tool-feedback loop, not human input, and is\n * excluded from the engagement series.\n */\nfunction isHumanUserMessage(record: ClaudeTranscriptRecord): boolean {\n const message = isObject(record.message) ? record.message : undefined;\n if (message === undefined) return false;\n const content = message.content;\n if (typeof content === \"string\") return content.length > 0;\n if (Array.isArray(content)) {\n return content.some((block) => {\n if (!isObject(block)) return false;\n const type = readString(block.type);\n return type !== undefined && type !== \"tool_result\";\n });\n }\n return false;\n}\n\n/**\n * Extract the `tool_use` items from an assistant record's\n * `message.content[]`. Returns an empty array for any record that does not\n * match the expected nesting, so callers can iterate unconditionally.\n */\nfunction toolUses(record: ClaudeTranscriptRecord): Array<Record<string, unknown>> {\n const message = isObject(record.message) ? record.message : undefined;\n const content = message !== undefined && Array.isArray(message.content) ? message.content : [];\n const result: Array<Record<string, unknown>> = [];\n for (const item of content) {\n if (isObject(item) && readString(item.type) === \"tool_use\") {\n result.push(item);\n }\n }\n return result;\n}\n\n/**\n * Index the structured answers of every `AskUserQuestion` tool use by its\n * tool_use id. The chosen answers live on the *result* record's\n * `toolUseResult.answers` — a `{ \"<question>\": \"<chosen answer>\" }` map — which\n * is only present on AskUserQuestion results, so its presence is the\n * discriminator. The result record carries the originating tool_use id inside\n * its `message.content[].tool_use_id`.\n */\nfunction indexAskAnswers(\n records: ReadonlyArray<ClaudeTranscriptRecord>,\n): Map<string, Record<string, unknown>> {\n const byId = new Map<string, Record<string, unknown>>();\n for (const record of records) {\n const result = record.toolUseResult;\n if (!isObject(result)) continue;\n const answers = result.answers;\n if (!isObject(answers)) continue;\n const message = isObject(record.message) ? record.message : undefined;\n const content = message !== undefined && Array.isArray(message.content) ? message.content : [];\n for (const item of content) {\n if (isObject(item) && readString(item.type) === \"tool_result\") {\n const id = readString(item.tool_use_id);\n if (id !== undefined) byId.set(id, answers);\n }\n }\n }\n return byId;\n}\n","import { type PrefixedId, prefixedUlid } from \"../../ids/ulid.js\";\nimport type { Event } from \"../../schemas/event.schema.js\";\nimport type { Manifest } from \"../../schemas/manifest.schema.js\";\nimport type { SessionImportPayload } from \"../../schemas/session-import.schema.js\";\nimport {\n ACTIVE_GAP_CAP_MS,\n activeTimeFromTimestamps,\n ENGAGED_TURNS_METHOD,\n type IntervalMs,\n intervalsMsToIso,\n TURN_INTERVALS_METHOD,\n unionDurationMs,\n} from \"../../stats/active-time.js\";\nimport { sessionLabelDateSpan } from \"../session-label.js\";\n\n/**\n * The `source` string stamped on every event derived from an OpenAI Codex\n * native rollout log, and the matching session `source.kind`.\n */\nexport const CODEX_IMPORT_SOURCE = \"codex-import\";\n\n/**\n * One parsed line of a Codex rollout log\n * (`~/.codex/sessions/<YYYY>/<MM>/<DD>/rollout-*.jsonl`). Each line is an\n * envelope `{ type, timestamp, payload }` where `payload` shape depends on\n * `type`. As with the Claude importer the format is the vendor's internal\n * log, not Basou's schema, so every field is read defensively — unknown\n * record / payload types and missing fields are skipped rather than rejected.\n */\nexport type CodexRolloutRecord = Record<string, unknown>;\n\n/** Options for {@link codexRolloutToImportPayload}. */\nexport type CodexRolloutToPayloadOptions = {\n /** Workspace id of the target Basou workspace (from its manifest). */\n workspaceId: Manifest[\"workspace\"][\"id\"];\n /**\n * Codex session id (`session_meta.payload.id`). Stored as\n * `session.source.external_id` so re-imports can be deduplicated. Falls back\n * to the id read from the rollout's `session_meta` record when omitted.\n */\n externalId?: string;\n /**\n * Byte size of the source rollout that produced `records`, stored as\n * `session.source.source_size_bytes` so a later import can detect growth and\n * re-import the session. The caller passes the size of the buffer it actually\n * read (an immutable snapshot of the parsed bytes), so the stored size always\n * matches the imported content. Omitted => the field is not recorded.\n */\n sourceSizeBytes?: number;\n};\n\n/**\n * Transform a Codex native rollout log into a Basou {@link SessionImportPayload},\n * ready to hand to `importSessionFromJson`.\n *\n * This is a pure function: no disk or environment access. It DERIVES Basou's\n * provenance-level events from the rollout's message-level records:\n *\n * - `session_started` / `session_ended` from the first / last timestamped record.\n * - `command_executed` from each `exec_command` function call, recorded as\n * `bash -c \"<cmd>\"`. The shell line and working directory come from the\n * call's JSON `arguments` (`{ cmd, workdir }`); the exit code and duration\n * are parsed from the paired `function_call_output` (matched by `call_id`),\n * whose text carries `Process exited with code N` and `Wall time: X seconds`.\n *\n * Per-session `metrics` are also derived: token totals from the cumulative\n * `token_count` events; active time from the real `task_started` ->\n * `task_complete` turn spans (in-turn, uncapped) unioned with the gap-capped\n * engagement series (between-turn bridging), labeled `turn-intervals`; and\n * `machine_active_time_ms` from the summed `task_complete.duration_ms` (model\n * compute time, a subset of active time).\n *\n * Unlike the Claude importer this derives no `file_changed`: Codex has no\n * dedicated edit tool and applies edits inside `exec_command` (e.g.\n * `apply_patch`), so there is no clean file-change signal to map. Decisions\n * and approvals are likewise not derivable — Codex records an `approval_policy`\n * (a policy, not a per-action approval) and has no structured question/answer\n * record. Both are deferred.\n *\n * Returns `null` when the rollout has no timestamped records or no observable\n * `exec_command` — such sessions carry no provenance worth importing and are\n * skipped by the caller.\n *\n * Event `id` / `session_id` are placeholders; `importSessionFromJson` mints\n * fresh ids on the way in. They are valid-by-construction so the payload still\n * passes `SessionImportPayloadSchema` validation upstream.\n */\nexport function codexRolloutToImportPayload(\n records: ReadonlyArray<CodexRolloutRecord>,\n options: CodexRolloutToPayloadOptions,\n): SessionImportPayload | null {\n const placeholderSessionId = prefixedUlid(\"ses\");\n // A command's exit code and duration live on its `function_call_output`,\n // which arrives after the originating `function_call`; pre-index outputs by\n // call_id so commands can be completed in the single forward pass below.\n const outputsByCallId = indexOutputs(records);\n // A turn's start lives on its `task_started`; pair it with the matching\n // `task_complete` by turn_id so each turn yields a real wall-clock interval.\n // Pre-indexed (rather than matched inline) so it is robust to record order.\n const turnStartMsByTurnId = indexTaskStarts(records);\n const derived: Event[] = [];\n // Real rollouts are written in arrival order, but track the earliest /\n // latest timestamp explicitly (rather than trusting first / last line) and\n // order the derived events by occurred_at below, mirroring the Claude path.\n let minTs: string | undefined;\n let maxTs: string | undefined;\n let workingDir: string | undefined;\n let codexSessionId: string | undefined;\n // Codex emits cumulative token_count events; the last one's\n // total_token_usage is the session total (see metrics on the payload below).\n let lastTokenTotals: Record<string, unknown> | undefined;\n // Genuine engagement timestamps for the billing-oriented active-time metric:\n // conversation turns (user / agent messages and task boundaries) plus the\n // exec_command actions. Token-count heartbeats, reasoning, web-search and\n // tool-output records are excluded so they cannot inflate billable time.\n // These bridge BETWEEN turns (gap-capped); within a turn the explicit\n // task interval below supersedes them on merge.\n const engagementTsMs: number[] = [];\n // Real per-turn wall-clock spans (`task_started` -> `task_complete`). Used\n // for the in-turn portion of active time (uncapped, and crediting the final\n // turn), unioned with the gap-capped engagement series above.\n // One per (deduped) `task_complete`: the turn's wall-clock interval and its\n // reported model-compute duration. Resolved into active time + machine time\n // after the loop, once `minTs` is known (so a reconstructed start can be\n // clamped to the session floor). `durationMs` is 0 when the rollout records\n // none (older format).\n const completions: Array<{ interval: IntervalMs | undefined; durationMs: number }> = [];\n // De-dup completions by turn_id: a duplicate `task_complete` for the same\n // turn would double-count machine time (breaking machine <= active) while the\n // union-merged interval counts the turn once. First completion per turn wins.\n const completedTurnIds = new Set<string>();\n\n for (const record of records) {\n const ts = readString(record.timestamp);\n if (ts === undefined) continue;\n if (minTs === undefined || Date.parse(ts) < Date.parse(minTs)) minTs = ts;\n if (maxTs === undefined || Date.parse(ts) > Date.parse(maxTs)) maxTs = ts;\n\n const payload = isObject(record.payload) ? record.payload : undefined;\n if (payload === undefined) continue;\n\n if (readString(record.type) === \"session_meta\") {\n // The session-level cwd and id are the most reliable working directory\n // and dedup key; take the first occurrence and keep it.\n if (workingDir === undefined) workingDir = readString(payload.cwd);\n if (codexSessionId === undefined) codexSessionId = readString(payload.id);\n continue;\n }\n\n if (readString(record.type) === \"event_msg\" && readString(payload.type) === \"token_count\") {\n const info = isObject(payload.info) ? payload.info : undefined;\n const totals =\n info !== undefined && isObject(info.total_token_usage) ? info.total_token_usage : undefined;\n // Cumulative; keep the latest so the final value is the session total.\n if (totals !== undefined) lastTokenTotals = totals;\n continue;\n }\n\n if (readString(record.type) === \"event_msg\") {\n const pt = readString(payload.type);\n if (\n pt === \"user_message\" ||\n pt === \"agent_message\" ||\n pt === \"task_started\" ||\n pt === \"task_complete\"\n ) {\n const tsMs = Date.parse(ts);\n if (Number.isFinite(tsMs)) engagementTsMs.push(tsMs);\n }\n if (pt === \"task_complete\") {\n const turnId = readString(payload.turn_id);\n // Skip a duplicate completion for an already-counted turn (F1).\n if (turnId === undefined || !completedTurnIds.has(turnId)) {\n if (turnId !== undefined) completedTurnIds.add(turnId);\n completions.push({\n interval: turnIntervalFromComplete(ts, payload, turnStartMsByTurnId),\n durationMs: readNonNegInt(payload.duration_ms),\n });\n }\n }\n // event_msg records are never response_items; skip the rest.\n continue;\n }\n\n if (readString(record.type) !== \"response_item\") continue;\n if (readString(payload.type) !== \"function_call\") continue;\n if (readString(payload.name) !== \"exec_command\") continue;\n\n const command = readExecCommand(payload.arguments);\n if (command === undefined) continue;\n const cwd = command.workdir ?? workingDir ?? \".\";\n const output = readCallId(payload.call_id, outputsByCallId);\n const execTsMs = Date.parse(ts);\n if (Number.isFinite(execTsMs)) engagementTsMs.push(execTsMs);\n derived.push(\n commandExecutedEvent(ts, placeholderSessionId, command.cmd, cwd, {\n exitCode: parseExitCode(output),\n durationMs: parseWallTimeMs(output),\n }),\n );\n }\n\n if (minTs === undefined || maxTs === undefined) return null;\n if (derived.length === 0) return null;\n\n // Order derived events by occurred_at so the assembled stream is\n // non-decreasing — importSessionFromJson rejects out-of-order events.\n // Array.prototype.sort is stable, so same-timestamp events keep their\n // rollout order.\n derived.sort((a, b) => Date.parse(a.occurred_at) - Date.parse(b.occurred_at));\n\n const events: Event[] = [\n sessionStartedEvent(minTs, placeholderSessionId),\n ...derived,\n sessionEndedEvent(maxTs, placeholderSessionId),\n ];\n\n const externalId = options.externalId ?? codexSessionId;\n // Human-readable label: when + how much, so the session reads as content in\n // `basou session list` / handoff rather than an opaque id. The source id is\n // kept structurally in `source.external_id` (not the label), and paths are\n // deliberately excluded — the label is NOT path-sanitized downstream, so a\n // raw file path here would leak an operator-private prefix.\n const commandCount = derived.length;\n const label = `codex ${sessionLabelDateSpan(minTs, maxTs)}: ${commandCount} ${commandCount === 1 ? \"command\" : \"commands\"}`;\n\n // Resolve per-turn intervals + machine compute now that `minTs` is known.\n // A turn whose `task_started` is absent has its start reconstructed as\n // `end - duration_ms`, which can precede the earliest record (and thus\n // `started_at`); clamp to `minTs` so no active interval predates the session\n // (F3). Machine compute for each turn is bounded by its clamped in-session\n // span, so a clamped turn can never push the session's machine time above its\n // active time (the documented `machine <= active` subset; F1). For ordinary\n // fully-logged turns the span is >= the reported duration, so this is exactly\n // `duration_ms`. `machine` is honest only when EVERY completed turn carried a\n // duration: a mix of duration-bearing and duration-less completions would\n // report a partial compute total as if complete, so it is omitted then (F2).\n const minTsMs = Date.parse(minTs);\n const turnIntervals: IntervalMs[] = [];\n let machineActiveMs = 0;\n let allCompletedTurnsHaveDuration = true;\n for (const { interval, durationMs } of completions) {\n if (durationMs <= 0) allCompletedTurnsHaveDuration = false;\n if (interval === undefined) continue;\n const start = Number.isFinite(minTsMs) ? Math.max(interval[0], minTsMs) : interval[0];\n const end = interval[1];\n if (!(start < end)) continue;\n turnIntervals.push([start, end]);\n machineActiveMs += Math.min(durationMs, end - start);\n }\n\n // Active time = union of the real per-turn intervals (in-turn, uncapped) and\n // the gap-capped engagement series (between-turn bridging). The merge dedups\n // their overlap, so the in-turn portion is the turn span while between-turn\n // human time is still credited up to the cap. A single explicit turn is\n // enough to bound active time, so the >= 2-point fallback only matters when\n // no turn intervals exist. Omitted when neither yields a span, so stats falls\n // back to the event-derived measure. Method label reflects which was used.\n const pointResult = activeTimeFromTimestamps(engagementTsMs, ACTIVE_GAP_CAP_MS);\n const active =\n turnIntervals.length > 0 || pointResult.intervals.length > 0\n ? unionDurationMs([...turnIntervals, ...pointResult.intervals])\n : undefined;\n const activeMethod = turnIntervals.length > 0 ? TURN_INTERVALS_METHOD : ENGAGED_TURNS_METHOD;\n const machineActive = allCompletedTurnsHaveDuration ? machineActiveMs : 0;\n\n // Token totals from the last cumulative token_count event; include only the\n // fields actually present (> 0). Metrics is emitted if either token usage or\n // an engaged-time signal is present.\n const tokenFields =\n lastTokenTotals === undefined\n ? {}\n : {\n ...(readNonNegInt(lastTokenTotals.output_tokens) > 0\n ? { output_tokens: readNonNegInt(lastTokenTotals.output_tokens) }\n : {}),\n ...(readNonNegInt(lastTokenTotals.input_tokens) > 0\n ? { input_tokens: readNonNegInt(lastTokenTotals.input_tokens) }\n : {}),\n ...(readNonNegInt(lastTokenTotals.cached_input_tokens) > 0\n ? { cached_input_tokens: readNonNegInt(lastTokenTotals.cached_input_tokens) }\n : {}),\n ...(readNonNegInt(lastTokenTotals.reasoning_output_tokens) > 0\n ? { reasoning_output_tokens: readNonNegInt(lastTokenTotals.reasoning_output_tokens) }\n : {}),\n };\n const metricsFields = {\n ...tokenFields,\n ...(active !== undefined && active.ms > 0\n ? {\n active_time_ms: active.ms,\n active_intervals: intervalsMsToIso(active.merged),\n active_gap_cap_ms: ACTIVE_GAP_CAP_MS,\n active_time_method: activeMethod,\n }\n : {}),\n ...(machineActive > 0 ? { machine_active_time_ms: machineActive } : {}),\n };\n const metrics = Object.keys(metricsFields).length > 0 ? metricsFields : undefined;\n\n const payload: SessionImportPayload = {\n schema_version: \"0.1.0\",\n session: {\n label,\n workspace_id: options.workspaceId,\n source: {\n kind: CODEX_IMPORT_SOURCE,\n version: \"0.1.0\",\n ...(externalId !== undefined ? { external_id: externalId } : {}),\n ...(options.sourceSizeBytes !== undefined\n ? { source_size_bytes: options.sourceSizeBytes }\n : {}),\n },\n started_at: minTs,\n ended_at: maxTs,\n // Validated against the canonical enum here; importSessionFromJson\n // overwrites it with the literal \"imported\" regardless.\n status: \"imported\",\n working_directory: workingDir ?? \".\",\n invocation: { command: \"codex\", args: [], exit_code: null },\n related_files: [],\n summary: null,\n ...(metrics !== undefined ? { metrics } : {}),\n },\n events,\n };\n return payload;\n}\n\n// --- event builders -------------------------------------------------------\n\nfunction baseEvent(\n occurredAt: string,\n sessionId: PrefixedId<\"ses\">,\n): {\n schema_version: \"0.1.0\";\n id: PrefixedId<\"evt\">;\n session_id: PrefixedId<\"ses\">;\n occurred_at: string;\n source: string;\n} {\n return {\n schema_version: \"0.1.0\",\n id: prefixedUlid(\"evt\"),\n session_id: sessionId,\n occurred_at: occurredAt,\n source: CODEX_IMPORT_SOURCE,\n };\n}\n\nfunction sessionStartedEvent(occurredAt: string, sessionId: PrefixedId<\"ses\">): Event {\n return { ...baseEvent(occurredAt, sessionId), type: \"session_started\" };\n}\n\nfunction sessionEndedEvent(occurredAt: string, sessionId: PrefixedId<\"ses\">): Event {\n return { ...baseEvent(occurredAt, sessionId), type: \"session_ended\" };\n}\n\nfunction commandExecutedEvent(\n occurredAt: string,\n sessionId: PrefixedId<\"ses\">,\n command: string,\n cwd: string,\n outcome: { exitCode: number | null; durationMs: number },\n): Event {\n return {\n ...baseEvent(occurredAt, sessionId),\n type: \"command_executed\",\n command: \"bash\",\n args: [\"-c\", command],\n cwd,\n exit_code: outcome.exitCode,\n duration_ms: outcome.durationMs,\n };\n}\n\n// --- defensive readers ----------------------------------------------------\n\nfunction readString(value: unknown): string | undefined {\n return typeof value === \"string\" && value.length > 0 ? value : undefined;\n}\n\n/** Read a non-negative integer token count, treating anything else as 0. */\nfunction readNonNegInt(value: unknown): number {\n return typeof value === \"number\" && Number.isInteger(value) && value >= 0 ? value : 0;\n}\n\nfunction isObject(value: unknown): value is Record<string, unknown> {\n return typeof value === \"object\" && value !== null && !Array.isArray(value);\n}\n\n/**\n * Parse an `exec_command` call's JSON `arguments` string into its shell line\n * and optional working directory. Returns `undefined` when the arguments are\n * not parseable or carry no `cmd`, so the caller can skip the call.\n */\nfunction readExecCommand(value: unknown): { cmd: string; workdir: string | undefined } | undefined {\n const raw = readString(value);\n if (raw === undefined) return undefined;\n let parsed: unknown;\n try {\n parsed = JSON.parse(raw);\n } catch {\n return undefined;\n }\n if (!isObject(parsed)) return undefined;\n const cmd = readString(parsed.cmd);\n if (cmd === undefined) return undefined;\n return { cmd, workdir: readString(parsed.workdir) };\n}\n\nfunction readCallId(value: unknown, outputs: ReadonlyMap<string, string>): string | undefined {\n const callId = readString(value);\n return callId !== undefined ? outputs.get(callId) : undefined;\n}\n\n/**\n * Build a turn's `[start, end]` wall-clock interval from its `task_complete`.\n * `end` is the completion record's own timestamp (ISO, ms precision); `start`\n * is the matching `task_started`'s timestamp, or — when that record is absent\n * (a session whose first turn was already in progress at import) —\n * reconstructed as `end - duration_ms`. Returns `undefined` when no start can\n * be resolved or the span is non-positive, so the caller falls back to the\n * gap-capped engagement series for that turn.\n */\nfunction turnIntervalFromComplete(\n endTs: string,\n payload: Record<string, unknown>,\n startMsByTurnId: ReadonlyMap<string, number>,\n): IntervalMs | undefined {\n const endMs = Date.parse(endTs);\n if (!Number.isFinite(endMs)) return undefined;\n const turnId = readString(payload.turn_id);\n const indexedStart = turnId !== undefined ? startMsByTurnId.get(turnId) : undefined;\n const durationMs = readNonNegInt(payload.duration_ms);\n const startMs =\n indexedStart !== undefined ? indexedStart : durationMs > 0 ? endMs - durationMs : undefined;\n if (startMs === undefined || !(startMs < endMs)) return undefined;\n return [startMs, endMs];\n}\n\n/**\n * Index each turn's start time (epoch ms) by its `turn_id` from the\n * `task_started` records. First occurrence wins. Lets a `task_complete` recover\n * the real turn start regardless of record order.\n */\nfunction indexTaskStarts(records: ReadonlyArray<CodexRolloutRecord>): Map<string, number> {\n const byTurnId = new Map<string, number>();\n for (const record of records) {\n if (readString(record.type) !== \"event_msg\") continue;\n const payload = isObject(record.payload) ? record.payload : undefined;\n if (payload === undefined || readString(payload.type) !== \"task_started\") continue;\n const turnId = readString(payload.turn_id);\n const startMs = Date.parse(readString(record.timestamp) ?? \"\");\n if (turnId !== undefined && Number.isFinite(startMs) && !byTurnId.has(turnId)) {\n byTurnId.set(turnId, startMs);\n }\n }\n return byTurnId;\n}\n\n/**\n * Codex's `exec_command` output text reports the child's exit code as\n * `Process exited with code N` (N may be negative for signal termination).\n * Returns `null` when the line is absent — the command may have yielded before\n * completing or the session was cut off mid-command.\n */\nfunction parseExitCode(output: string | undefined): number | null {\n if (output === undefined) return null;\n const match = output.match(/Process exited with code (-?\\d+)/);\n return match?.[1] !== undefined ? Number.parseInt(match[1], 10) : null;\n}\n\n/**\n * Codex's `exec_command` output text reports wall-clock duration as\n * `Wall time: X seconds`. Returns `0` (the schema floor) when absent or\n * non-finite, matching the Claude importer's missing-duration default.\n */\nfunction parseWallTimeMs(output: string | undefined): number {\n if (output === undefined) return 0;\n const match = output.match(/Wall time:\\s*([\\d.]+)\\s*seconds/);\n if (match?.[1] === undefined) return 0;\n const seconds = Number.parseFloat(match[1]);\n return Number.isFinite(seconds) ? Math.round(seconds * 1000) : 0;\n}\n\n/**\n * Index every `function_call_output`'s text by its `call_id`, so a command's\n * exit code and duration can be looked up at the originating `function_call`.\n * Only string outputs are kept — image / structured tool results are arrays\n * and carry no command outcome.\n */\nfunction indexOutputs(records: ReadonlyArray<CodexRolloutRecord>): Map<string, string> {\n const byId = new Map<string, string>();\n for (const record of records) {\n if (readString(record.type) !== \"response_item\") continue;\n const payload = isObject(record.payload) ? record.payload : undefined;\n if (payload === undefined) continue;\n if (readString(payload.type) !== \"function_call_output\") continue;\n const callId = readString(payload.call_id);\n const output = readString(payload.output);\n if (callId !== undefined && output !== undefined) byId.set(callId, output);\n }\n return byId;\n}\n","import { readdir } from \"node:fs/promises\";\nimport { join } from \"node:path\";\nimport { findErrorCode } from \"../lib/error-codes.js\";\nimport { type Approval, ApprovalSchema } from \"../schemas/approval.schema.js\";\nimport type { BasouPaths } from \"../storage/basou-dir.js\";\nimport { readYamlFile } from \"../storage/yaml-store.js\";\n\n/** Which side of `.basou/approvals/` an approval YAML lives on. */\nexport type ApprovalLocation = \"pending\" | \"resolved\";\n\n/** Result returned by {@link loadApproval}: the parsed approval and where it was found. */\nexport type LoadedApproval = {\n approval: Approval;\n location: ApprovalLocation;\n};\n\n/**\n * Locate and load the approval YAML for `approvalId`. Searches resolved\n * first so that a duplicated YAML (the crash-window scenario where both\n * pending and resolved exist for the same id) returns the resolved-side\n * record — matching the dedupe rule used by `approval list` and\n * `resolveApprovalId`. Returns null if neither directory contains the\n * YAML. Throws with a pathless message on read or schema-validation\n * failure.\n */\nexport async function loadApproval(\n paths: BasouPaths,\n approvalId: string,\n): Promise<LoadedApproval | null> {\n for (const location of [\"resolved\", \"pending\"] as const) {\n const filePath = join(paths.approvals[location], `${approvalId}.yaml`);\n let raw: unknown;\n try {\n raw = await readYamlFile(filePath);\n } catch (error: unknown) {\n // ENOENT (i.e. \"YAML file not found\") → continue to the other directory.\n if (error instanceof Error && error.message === \"YAML file not found\") continue;\n throw new Error(\"Failed to read approval\", { cause: error });\n }\n const result = ApprovalSchema.safeParse(raw);\n if (!result.success) {\n throw new Error(\"Failed to read approval\", { cause: result.error });\n }\n // Defensive id check: a hand-edited YAML whose `id` field disagrees\n // with the filename-derived id would otherwise let the CLI render or\n // mutate one approval while citing another. Treat the mismatch as a\n // read failure rather than silently picking one side.\n if (result.data.id !== approvalId) {\n throw new Error(\"Failed to read approval\", {\n cause: new Error(\n `Approval id mismatch: filename id ${approvalId} vs YAML body id ${result.data.id}`,\n ),\n });\n }\n return { approval: result.data, location };\n }\n return null;\n}\n\n/**\n * Enumerate approval IDs by inspecting `<id>.yaml` filenames in pending\n * and resolved. ENOENT on either directory is treated as empty (e.g. a\n * workspace that has no resolved approvals yet). YAML parse and schema\n * validation are NOT performed; callers that need the parsed approval\n * should use {@link loadApproval} per ID.\n */\nexport async function enumerateApprovals(paths: BasouPaths): Promise<{\n pending: string[];\n resolved: string[];\n}> {\n const [pending, resolved] = await Promise.all([\n enumerateIds(paths.approvals.pending),\n enumerateIds(paths.approvals.resolved),\n ]);\n return { pending, resolved };\n}\n\nasync function enumerateIds(dir: string): Promise<string[]> {\n let entries: string[];\n try {\n const dirents = await readdir(dir, { withFileTypes: true });\n entries = dirents\n .filter((e) => e.isFile() && e.name.endsWith(\".yaml\"))\n .map((e) => e.name.slice(0, -\".yaml\".length));\n } catch (error: unknown) {\n if (findErrorCode(error, \"ENOENT\")) return [];\n throw new Error(\"Failed to enumerate approvals\", { cause: error });\n }\n return entries;\n}\n\n/**\n * Return true when an approval is in `pending` state and its `expires_at`\n * timestamp has elapsed. Used by `basou approval list` / `show` to surface\n * a `(expired)` label without mutating the YAML file. Approval expiry uses\n * lazy-evaluation semantics; actual `approval_expired` event firing is\n * deferred to a later step.\n *\n * `now` is taken as a parameter so a single CLI invocation can share one\n * \"now\" across every record it inspects (avoids boundary races where two\n * reads of `Date.now()` straddle an expiry instant).\n */\nexport function isLazyExpired(approval: Approval, now: Date): boolean {\n if (approval.status !== \"pending\") return false;\n if (approval.expires_at === null) return false;\n const expiresMs = Date.parse(approval.expires_at);\n if (!Number.isFinite(expiresMs)) return false;\n return expiresMs < now.getTime();\n}\n","/**\n * Walk the cause chain (up to `depth` levels) looking for an Error whose\n * errno-style `code` matches `code`. Returns true on the first match.\n * Resilient to wrapper depth changes so that ENOENT detection survives\n * future error-wrapping refactors.\n */\nexport function findErrorCode(error: unknown, code: string, depth = 4): boolean {\n let cur: unknown = error;\n for (let i = 0; i < depth && cur instanceof Error; i++) {\n const c = (cur as { code?: unknown }).code;\n if (typeof c === \"string\" && c === code) return true;\n cur = (cur as Error).cause;\n }\n return false;\n}\n","import { z } from \"zod\";\nimport {\n ApprovalIdSchema,\n IsoTimestampSchema,\n RiskLevelSchema,\n SchemaVersionSchema,\n SessionIdSchema,\n} from \"./shared.schema.js\";\n\n/**\n * Lifecycle states of a Basou approval. The status is stored directly on\n * the approval YAML (flat shape) so that pending → resolved transitions\n * are atomic-move + in-place rewrites rather than schema-variant swaps.\n */\nexport const ApprovalStatusSchema = z.enum([\"pending\", \"approved\", \"rejected\", \"expired\"]);\n/** Inferred runtime type for {@link ApprovalStatusSchema}. */\nexport type ApprovalStatus = z.infer<typeof ApprovalStatusSchema>;\n\n/**\n * Schema for `.basou/approvals/{pending,resolved}/<approval_id>.yaml`.\n *\n * The schema is intentionally flat (one shape regardless of `status`) so\n * that pending and resolved YAMLs share the same parser. Required vs.\n * optional semantics by status (e.g. `rejection_reason` MUST be set when\n * `status === \"rejected\"`) are enforced at the CLI orchestration layer\n * rather than here, mirroring the approval event variants in\n * `event.schema.ts`.\n *\n * The `action` field is `{ kind: string }` with `passthrough()` so that\n * adapter-defined keys (e.g. `command`, `path`, `target_url`) survive the\n * round-trip without being stripped — matching the approval_requested\n * event variant.\n */\nexport const ApprovalSchema = z.object({\n schema_version: SchemaVersionSchema,\n id: ApprovalIdSchema,\n session_id: SessionIdSchema,\n created_at: IsoTimestampSchema,\n status: ApprovalStatusSchema,\n risk_level: RiskLevelSchema,\n action: z.object({ kind: z.string() }).passthrough(),\n reason: z.string(),\n expires_at: IsoTimestampSchema.nullable().default(null),\n // The four fields below are null while `status === \"pending\"` and set\n // once a resolver records a decision. Defaulting to null keeps the\n // pending YAML free of explicit nulls if a producer omits them.\n resolver: z.string().nullable().default(null),\n resolved_at: IsoTimestampSchema.nullable().default(null),\n note: z.string().nullable().default(null),\n rejection_reason: z.string().nullable().default(null),\n});\n\n/** Inferred runtime type for {@link ApprovalSchema}. */\nexport type Approval = z.infer<typeof ApprovalSchema>;\n","import { z } from \"zod\";\nimport { type IdPrefix, isValidPrefixedId, type PrefixedId } from \"../ids/ulid.js\";\n\n/**\n * Schema version literal pinned to \"0.1.0\" for Basou v0.1.\n * Reused across every entity schema so inferred types narrow to the literal.\n */\nexport const SchemaVersionSchema = z.literal(\"0.1.0\");\n\n/**\n * ISO 8601 timestamp with explicit timezone offset (e.g. `+09:00`).\n *\n * The spec samples include offsets, so the default zod `.datetime()` (which\n * rejects offsets) is insufficient; `{ offset: true }` is required.\n */\nexport const IsoTimestampSchema = z.string().datetime({ offset: true });\n\n// Internal factory shared by every prefixed-ID schema. Not exported because\n// the public API surface should only expose the six fully-typed ID schemas.\n//\n// The `.refine` carries the real (ULID-aware) validation but is opaque to JSON\n// Schema generation, so the `.meta` mirrors the prefix + ULID-body shape as a\n// representable `pattern` (and a description). This is METADATA ONLY: it does\n// not affect parsing — `isValidPrefixedId` still gates acceptance — it just\n// lets `z.toJSONSchema` emit a faithful pattern for the published artifact. The\n// pattern mirrors `ULID_BODY_REGEX` (leading 0-7, then 25 Crockford symbols\n// excluding I/L/O/U); it is intentionally slightly looser than the library\n// `isValid` check, matching the documented id shape.\nconst createPrefixedIdSchema = <P extends IdPrefix>(prefix: P) => {\n const refiner = (value: string): value is PrefixedId<P> =>\n isValidPrefixedId(value) && value.startsWith(`${prefix}_`);\n return z\n .string()\n .refine(refiner, { message: `Expected ${prefix}_<ULID>` })\n .meta({\n pattern: `^${prefix}_[0-7][0-9A-HJKMNP-TV-Z]{25}$`,\n description: `Basou ${prefix} id: \\`${prefix}_\\` followed by a 26-character Crockford Base32 ULID.`,\n });\n};\n\n/** Workspace ID schema: validates `ws_<26-char ULID>`. */\nexport const WorkspaceIdSchema = createPrefixedIdSchema(\"ws\");\n/** Task ID schema: validates `task_<26-char ULID>`. */\nexport const TaskIdSchema = createPrefixedIdSchema(\"task\");\n/** Session ID schema: validates `ses_<26-char ULID>`. */\nexport const SessionIdSchema = createPrefixedIdSchema(\"ses\");\n/** Event ID schema: validates `evt_<26-char ULID>`. */\nexport const EventIdSchema = createPrefixedIdSchema(\"evt\");\n/** Approval ID schema: validates `appr_<26-char ULID>`. */\nexport const ApprovalIdSchema = createPrefixedIdSchema(\"appr\");\n/** Decision ID schema: validates `decision_<26-char ULID>`. */\nexport const DecisionIdSchema = createPrefixedIdSchema(\"decision\");\n\n/**\n * Risk level vocabulary fixed by the spec. Adapters MUST emit one of these\n * four values; arbitrary strings are rejected at schema parse time.\n */\nexport const RiskLevelSchema = z.enum([\"low\", \"medium\", \"high\", \"critical\"]);\n/** Inferred runtime type for {@link RiskLevelSchema}. */\nexport type RiskLevel = z.infer<typeof RiskLevelSchema>;\n\n/**\n * Source attribution for events (e.g. \"claude-code-adapter\",\n * \"git-capability\", \"terminal-recording\", \"local-cli\", \"human\"). Free-form\n * non-empty string in v0.1; a stricter enum may be introduced post-v0.1.\n */\nexport const EventSourceSchema = z.string().min(1);\n","import { readFile } from \"node:fs/promises\";\nimport { parse, stringify } from \"yaml\";\nimport { atomicCreate, atomicReplace } from \"./atomic.js\";\n\n/**\n * Read a YAML file as `unknown`. Caller MUST validate via a zod schema.\n *\n * Throws Error with pathless message and the original native error attached\n * as `cause` for I/O failures and YAML parse errors. All fs and parse exits\n * go through fixed messages so absolute paths cannot leak via `error.message`.\n */\nexport async function readYamlFile(filePath: string): Promise<unknown> {\n let body: string;\n try {\n body = await readFile(filePath, \"utf8\");\n } catch (error: unknown) {\n if (hasErrorCode(error) && error.code === \"ENOENT\") {\n throw new Error(\"YAML file not found\", { cause: error });\n }\n throw new Error(\"Failed to read YAML file\", { cause: error });\n }\n try {\n return parse(body);\n } catch (error: unknown) {\n throw new Error(\"Failed to parse YAML content\", { cause: error });\n }\n}\n\n/**\n * Write a value as YAML using {@link atomicReplace} for crash-resistant\n * atomicity. The shared helper handles the tmp-file + rename sequence,\n * `wx` collision guard, and best-effort tmp cleanup on failure. This\n * wrapper adds the YAML serialisation and the pathless error vocabulary.\n */\nexport async function writeYamlFile(filePath: string, value: unknown): Promise<void> {\n const body = stringify(value);\n try {\n await atomicReplace(filePath, body);\n } catch (error: unknown) {\n throw new Error(\"Failed to write YAML file\", { cause: error });\n }\n}\n\n/**\n * Atomically create a new YAML file. Like {@link writeYamlFile} but\n * delegates to {@link atomicCreate} so a pre-existing target fails with\n * EEXIST instead of being silently overwritten.\n *\n * Used by `basou approval approve` / `reject` to write the resolved-side\n * YAML, so a concurrent resolver cannot overwrite an already-resolved\n * approval.\n *\n * Throws `Error(\"Failed to write YAML file\", { cause })` on failure; if\n * `cause.code === \"EEXIST\"` the caller can detect a target-exists race.\n */\nexport async function linkYamlFile(filePath: string, value: unknown): Promise<void> {\n const body = stringify(value);\n try {\n await atomicCreate(filePath, body);\n } catch (error: unknown) {\n throw new Error(\"Failed to write YAML file\", { cause: error });\n }\n}\n\n/**\n * Overwrite an existing YAML file atomically. Like {@link writeYamlFile}\n * but with a distinct pathless message label, used for files that\n * legitimately need in-place mutation (e.g. session.yaml's status /\n * ended_at lifecycle updates).\n */\nexport async function overwriteYamlFile(filePath: string, value: unknown): Promise<void> {\n const body = stringify(value);\n try {\n await atomicReplace(filePath, body);\n } catch (error: unknown) {\n throw new Error(\"Failed to overwrite YAML file\", { cause: error });\n }\n}\n\nfunction hasErrorCode(error: unknown): error is Error & { code: string } {\n if (!(error instanceof Error)) return false;\n return typeof (error as unknown as Record<string, unknown>).code === \"string\";\n}\n","import { randomUUID } from \"node:crypto\";\nimport { link, rename, unlink, writeFile } from \"node:fs/promises\";\n\n/**\n * Atomically create a new file at `targetPath` via tmp + link.\n *\n * Strategy: write the body to a sibling tmp file (`${targetPath}.tmp.<uuid>`)\n * with the `wx` flag, then `link()` the tmp inode into place at `targetPath`.\n * If the target already exists, `link` fails with EEXIST — callers detect this\n * via `findErrorCode(error, \"EEXIST\")` to surface a domain-specific\n * \"already exists\" message.\n *\n * The tmp file lives in the SAME directory as the target so `link` cannot\n * fail with EXDEV. On every code path (success and failure) the tmp inode\n * is best-effort unlinked, so after a successful call the tmp side of the\n * hard-link pair is removed and only `targetPath` remains.\n *\n * The native fs error is re-thrown WITHOUT wrapping so callers can attach\n * their own pathless message via `new Error(\"<fixed msg>\", { cause })`. The\n * caller is responsible for the final error vocabulary (pathless contract).\n */\nexport async function atomicCreate(targetPath: string, content: string | Buffer): Promise<void> {\n const tmpPath = `${targetPath}.tmp.${randomUUID()}`;\n try {\n await writeFile(tmpPath, content, { encoding: \"utf8\", flag: \"wx\" });\n await link(tmpPath, targetPath);\n } catch (error: unknown) {\n await unlink(tmpPath).catch(() => undefined);\n throw error;\n }\n // tmp inode is now linked twice (tmp + target); unlink the tmp side so\n // disk does not carry a spurious sibling after a successful create.\n await unlink(tmpPath).catch(() => undefined);\n}\n\n/**\n * Atomically replace the file at `targetPath` via tmp + rename.\n *\n * Strategy: write the body to a sibling tmp file (`${targetPath}.tmp.<uuid>`)\n * with the `wx` flag, then `rename()` the tmp over `targetPath`. Silently\n * overwrites any existing file at `targetPath`. The tmp file lives in the\n * SAME directory as the target so `rename` cannot fail with EXDEV. `rename`\n * consumes the tmp file, so no post-success cleanup is needed.\n *\n * On failure the tmp file is best-effort unlinked so disk never carries a\n * half-written rename source. The native fs error is re-thrown WITHOUT\n * wrapping so callers can attach their own pathless message.\n */\nexport async function atomicReplace(targetPath: string, content: string | Buffer): Promise<void> {\n const tmpPath = `${targetPath}.tmp.${randomUUID()}`;\n try {\n await writeFile(tmpPath, content, { encoding: \"utf8\", flag: \"wx\" });\n await rename(tmpPath, targetPath);\n } catch (error: unknown) {\n await unlink(tmpPath).catch(() => undefined);\n throw error;\n }\n}\n","import { lstat } from \"node:fs/promises\";\nimport { dirname, join, resolve } from \"node:path\";\nimport { type ReplayWarning, replayEvents } from \"../events/event-replay.js\";\nimport type { BasouPaths } from \"../storage/basou-dir.js\";\nimport { loadSessionEntries, type SessionSkipReason } from \"../storage/sessions.js\";\n\nexport type DecisionsRendererInput = {\n paths: BasouPaths;\n nowIso: string;\n onWarning?: (warning: ReplayWarning, sessionId: string) => void;\n onSessionSkip?: (sessionId: string, reason: SessionSkipReason) => void;\n};\n\nexport type DecisionsRendererResult = {\n /** Generated body WITHOUT BASOU:GENERATED markers. */\n body: string;\n decisionCount: number;\n};\n\ntype DecisionRecord = {\n decisionId: string;\n title: string;\n occurredAt: string;\n sessionId: string;\n // Rich fields. All optional; populated only when the decision_recorded\n // event carried the field.\n rationale: string | null | undefined;\n alternatives: readonly string[] | undefined;\n rejectedReason: string | null | undefined;\n linkedEvents: readonly string[] | undefined;\n linkedFiles: readonly string[] | undefined;\n // \"track\" when the decision was recorded as a strategic, unfinished direction\n // (resurfaced by orientation/handoff until voided); undefined / \"decision\" is\n // a plain point-in-time decision.\n kind: \"decision\" | \"track\" | undefined;\n // Set when a later `decision_voided` event targets this decision. The\n // decision is kept (append-only) but rendered struck-through; orientation\n // skips it as the \"latest\" direction.\n voided: { reason: string | null | undefined; supersededBy: string | undefined } | undefined;\n};\n\n/**\n * Render the body of `decisions.md` from `decision_recorded` events across\n * every healthy session in the workspace.\n *\n * Session enumeration goes through {@link loadSessionEntries} (the same path\n * the handoff renderer uses) so that `session.yaml`-broken sessions are\n * skipped in BOTH outputs and the handoff's `decisionCount` summary stays\n * consistent with the number of sections rendered here.\n *\n * Order: `occurred_at` ascending with `decisionId` (= ULID) as tie-breaker.\n * Both fields are monotonic, so the result is a stable cross-session\n * timeline.\n *\n * The decision rich fields (rationale / alternatives / rejected_reason /\n * linked_events / linked_files) are rendered when the event carries them.\n * `linked_events` and `linked_files` are OPAQUE references: the schema only\n * validates the SHAPE, not existence — references that cannot be resolved\n * to a known event id or an existing file on disk are surfaced inline as\n * `(missing)` so cross-workspace round-trips never reject parse-time.\n */\nexport async function renderDecisions(\n input: DecisionsRendererInput,\n): Promise<DecisionsRendererResult> {\n const now = new Date(input.nowIso);\n // Same rationale as handoff-renderer. Track which\n // sessions already had `events_jsonl_unreadable` surfaced so non-running\n // sessions whose events.jsonl is unreadable still produce a stderr\n // warning instead of silently dropping their decisions.\n const unreadableEmitted = new Set<string>();\n const wrappedSkip: (sid: string, reason: SessionSkipReason) => void = (sid, reason) => {\n if (reason === \"events_jsonl_unreadable\") unreadableEmitted.add(sid);\n input.onSessionSkip?.(sid, reason);\n };\n const loadOpts: Parameters<typeof loadSessionEntries>[1] = { now, onSkip: wrappedSkip };\n if (input.onWarning !== undefined) loadOpts.onWarning = input.onWarning;\n const entries = await loadSessionEntries(input.paths, loadOpts);\n\n const decisions: DecisionRecord[] = [];\n // decision_id -> void record (last void wins). Collected in the same scan so\n // a void recorded in any session marks the target decision wherever it lives.\n const voids = new Map<\n string,\n { reason: string | null | undefined; supersededBy: string | undefined }\n >();\n // Workspace-wide event id index, populated during the same scan that\n // collects decisions, so `linked_events` membership can be resolved\n // without a second pass over events.jsonl.\n const knownEventIds = new Set<string>();\n for (const entry of entries) {\n const sessionDir = join(input.paths.sessions, entry.sessionId);\n try {\n for await (const ev of replayEvents(sessionDir, {\n onWarning: (w) => input.onWarning?.(w, entry.sessionId),\n })) {\n knownEventIds.add(ev.id);\n if (ev.type === \"decision_recorded\") {\n decisions.push({\n decisionId: ev.decision_id,\n title: ev.title,\n occurredAt: ev.occurred_at,\n sessionId: entry.sessionId,\n rationale: ev.rationale,\n alternatives: ev.alternatives,\n rejectedReason: ev.rejected_reason,\n linkedEvents: ev.linked_events,\n linkedFiles: ev.linked_files,\n kind: ev.kind,\n voided: undefined,\n });\n } else if (ev.type === \"decision_voided\") {\n voids.set(ev.decision_id, { reason: ev.reason, supersededBy: ev.superseded_by });\n }\n }\n } catch {\n if (!unreadableEmitted.has(entry.sessionId)) {\n wrappedSkip(entry.sessionId, \"events_jsonl_unreadable\");\n }\n }\n }\n for (const d of decisions) {\n const v = voids.get(d.decisionId);\n if (v !== undefined) d.voided = v;\n }\n decisions.sort((a, b) => {\n const c = Date.parse(a.occurredAt) - Date.parse(b.occurredAt);\n return c !== 0 ? c : a.decisionId.localeCompare(b.decisionId);\n });\n\n // Resolve linked_files relative to the repository root (= parent of\n // `.basou/`). Existence is checked with `lstat` so symlinks are treated\n // honestly — a dangling symlink is reported as `(missing)`. The check\n // runs once per unique path so repeated references share their lookup.\n const repoRoot = dirname(input.paths.root);\n const fileExistenceCache = new Map<string, boolean>();\n async function fileExists(relPath: string): Promise<boolean> {\n const cached = fileExistenceCache.get(relPath);\n if (cached !== undefined) return cached;\n const abs = resolve(repoRoot, relPath);\n let exists: boolean;\n try {\n await lstat(abs);\n exists = true;\n } catch {\n exists = false;\n }\n fileExistenceCache.set(relPath, exists);\n return exists;\n }\n\n const body = await formatDecisionsBody({\n nowIso: input.nowIso,\n decisions,\n knownEventIds,\n fileExists,\n });\n return { body, decisionCount: decisions.length };\n}\n\nasync function formatDecisionsBody(args: {\n nowIso: string;\n decisions: ReadonlyArray<DecisionRecord>;\n knownEventIds: ReadonlySet<string>;\n fileExists: (relPath: string) => Promise<boolean>;\n}): Promise<string> {\n const lines: string[] = [];\n lines.push(\"# Decisions\");\n lines.push(\"\");\n lines.push(`> Generated at ${args.nowIso}`);\n lines.push(\"\");\n if (args.decisions.length === 0) {\n lines.push(\"(no decisions recorded yet)\");\n return lines.join(\"\\n\");\n }\n for (const d of args.decisions) {\n // A track marker rides on the heading so the audit shows the decision was a\n // strategic direction; for an open track it precedes the `- 種別` line below.\n const trackMark = d.kind === \"track\" ? \" [TRACK]\" : \"\";\n if (d.voided !== undefined) {\n // Struck heading + a void line; the decision body is kept for the audit\n // trail but visibly marked no longer in force.\n lines.push(`## ~~${d.decisionId}: ${d.title}~~ [VOIDED]${trackMark}`);\n lines.push(\"\");\n const supersededBy =\n d.voided.supersededBy !== undefined ? `, superseded by ${d.voided.supersededBy}` : \"\";\n const reason =\n typeof d.voided.reason === \"string\" && d.voided.reason.length > 0\n ? `: ${d.voided.reason}`\n : \"\";\n lines.push(`- ⚠ VOIDED${reason}${supersededBy}`);\n } else {\n lines.push(`## ${d.decisionId}: ${d.title}${trackMark}`);\n lines.push(\"\");\n }\n const occurredDate = d.occurredAt.slice(0, 10); // YYYY-MM-DD\n lines.push(`- 決定日: ${occurredDate}`);\n // An OPEN track keeps resurfacing in orientation/handoff; note that here so\n // the full record explains why it is still surfaced (a voided track is closed\n // and carries the VOIDED line instead).\n if (d.kind === \"track\" && d.voided === undefined) {\n lines.push(\"- 種別: track (close まで orient/handoff に継続表示)\");\n }\n lines.push(`- session: ${shortDecisionSessionId(d.sessionId)}`);\n lines.push(`- 判断: ${d.title}`);\n if (typeof d.rationale === \"string\" && d.rationale.length > 0) {\n lines.push(`- rationale: ${d.rationale}`);\n }\n if (d.alternatives !== undefined && d.alternatives.length > 0) {\n lines.push(`- alternatives: ${d.alternatives.join(\", \")}`);\n }\n if (typeof d.rejectedReason === \"string\" && d.rejectedReason.length > 0) {\n lines.push(`- rejected_reason: ${d.rejectedReason}`);\n }\n if (d.linkedEvents !== undefined && d.linkedEvents.length > 0) {\n const parts = d.linkedEvents.map((eid) =>\n args.knownEventIds.has(eid) ? eid : `${eid} (missing)`,\n );\n lines.push(`- linked_events: ${parts.join(\", \")}`);\n }\n if (d.linkedFiles !== undefined && d.linkedFiles.length > 0) {\n const parts = await Promise.all(\n d.linkedFiles.map(async (path) =>\n (await args.fileExists(path)) ? path : `${path} (missing)`,\n ),\n );\n lines.push(`- linked_files: ${parts.join(\", \")}`);\n }\n lines.push(\"\");\n }\n return lines.join(\"\\n\");\n}\n\nfunction shortDecisionSessionId(sessionId: string): string {\n const SES = \"ses_\";\n if (sessionId.startsWith(SES)) return sessionId.slice(SES.length, SES.length + 10);\n return sessionId.slice(0, 10);\n}\n","import { createReadStream } from \"node:fs\";\nimport { stat } from \"node:fs/promises\";\nimport { join } from \"node:path\";\nimport { findErrorCode } from \"../lib/error-codes.js\";\nimport { type Event, EventSchema } from \"../schemas/event.schema.js\";\n\n/**\n * Recoverable warning surfaced via {@link ReplayOptions.onWarning}. The replay\n * generator never throws on these — it skips the offending line and continues.\n *\n * `partial_trailing_line` indicates the events.jsonl did not end with `\\n` and\n * the unterminated tail parsed as a complete event. The line is dropped\n * instead of yielded so consumers cannot accidentally observe a\n * partially-written record.\n */\nexport type ReplayWarning =\n | { kind: \"partial_trailing_line\"; line: number }\n | { kind: \"malformed_json\"; line: number; cause: unknown }\n | { kind: \"schema_violation\"; line: number; cause: unknown };\n\nexport type ReplayOptions = {\n /**\n * Hook to receive recoverable warnings (partial line / malformed JSON /\n * schema violation). When omitted, warnings are silently dropped — callers\n * that want to surface them (e.g. CLI orchestration) MUST provide this hook.\n */\n onWarning?: (warning: ReplayWarning) => void;\n};\n\n/**\n * Stream events from `<sessionDir>/events.jsonl` line by line.\n *\n * Behavior:\n * - ENOENT or empty file: yields nothing without warning.\n * - I/O error: throws `Error(\"Failed to read events.jsonl\")` with the native\n * error attached as `cause`. The thrown message never embeds an absolute\n * path (pathless contract).\n * - Trailing partial line that parses as a valid event: dropped silently when\n * {@link ReplayOptions.onWarning} is omitted; otherwise reported as\n * `partial_trailing_line`. A trailing partial line that fails JSON parsing\n * is reported as `malformed_json` instead.\n * - Malformed JSON / schema violation: skipped, with the corresponding\n * warning when a hook is provided.\n *\n * Single-writer-per-session is assumed (see `event-writer.ts` JSDoc on\n * {@link appendEvent}). Concurrent writers may interleave lines beyond\n * `PIPE_BUF` and are not recovered here in v0.1.\n */\n// NOTE: switched from plan A (Transform stream + readline) to plan B (manual\n// chunk-level split) because plan A's source-stream errors do not propagate\n// through `pipe()` to the readline iterator, so an EACCES on the events.jsonl\n// hangs the for-await loop instead of throwing. Plan B observes errors\n// directly via the for-await over `createReadStream` and reaches end-of-stream\n// deterministically with the trailing buffer in hand.\nexport async function* replayEvents(\n sessionDir: string,\n options: ReplayOptions = {},\n): AsyncGenerator<Event, void, void> {\n const filePath = join(sessionDir, \"events.jsonl\");\n\n // Probe existence first so ENOENT (= empty session) is silent while every\n // other I/O failure surfaces as a single fixed-message error.\n try {\n await stat(filePath);\n } catch (error: unknown) {\n if (findErrorCode(error, \"ENOENT\")) return;\n throw new Error(\"Failed to read events.jsonl\", { cause: error });\n }\n\n let stream: ReturnType<typeof createReadStream>;\n try {\n stream = createReadStream(filePath, { encoding: \"utf8\" });\n } catch (error: unknown) {\n throw new Error(\"Failed to read events.jsonl\", { cause: error });\n }\n\n let buffer = \"\";\n let lineNo = 0;\n\n try {\n for await (const chunk of stream as unknown as AsyncIterable<string>) {\n buffer += chunk;\n let newlineIdx = buffer.indexOf(\"\\n\");\n while (newlineIdx !== -1) {\n lineNo += 1;\n const rawLine = buffer.slice(0, newlineIdx);\n buffer = buffer.slice(newlineIdx + 1);\n const ev = processLine(rawLine, lineNo, options);\n if (ev !== null) yield ev;\n newlineIdx = buffer.indexOf(\"\\n\");\n }\n }\n } catch (error: unknown) {\n throw new Error(\"Failed to read events.jsonl\", { cause: error });\n }\n\n // Stream ended mid-line: anything left in `buffer` is the trailing partial\n // line. Empty / whitespace-only trailing content is treated as a normal end\n // of file (e.g. a final '\\n' was stripped by the loop above).\n const trimmed = buffer.replace(/[\\r\\n\\t ]+$/u, \"\");\n if (trimmed.length === 0) return;\n lineNo += 1;\n\n let parsed: unknown;\n try {\n parsed = JSON.parse(trimmed);\n } catch (cause) {\n // The trailing buffer was non-empty AND JSON-invalid. Either\n // partial_trailing_line or malformed_json captures the same observable\n // outcome; we surface malformed_json because the JSON layer rejected it\n // first and the line number is meaningful for the consumer.\n options.onWarning?.({ kind: \"malformed_json\", line: lineNo, cause });\n return;\n }\n\n const result = EventSchema.safeParse(parsed);\n if (!result.success) {\n options.onWarning?.({ kind: \"schema_violation\", line: lineNo, cause: result.error });\n return;\n }\n\n // Valid JSON + valid event schema BUT no terminating newline. Drop instead\n // of yielding so a half-flushed write cannot be consumed as a real event.\n options.onWarning?.({ kind: \"partial_trailing_line\", line: lineNo });\n}\n\nfunction processLine(rawLine: string, lineNo: number, options: ReplayOptions): Event | null {\n const trimmed = rawLine.trim();\n if (trimmed.length === 0) return null;\n let parsed: unknown;\n try {\n parsed = JSON.parse(trimmed);\n } catch (cause) {\n options.onWarning?.({ kind: \"malformed_json\", line: lineNo, cause });\n return null;\n }\n const result = EventSchema.safeParse(parsed);\n if (!result.success) {\n options.onWarning?.({ kind: \"schema_violation\", line: lineNo, cause: result.error });\n return null;\n }\n return result.data;\n}\n\n/**\n * Eager array helper: collect every event from {@link replayEvents} into\n * memory. Convenience for callers that need the full list in one structure\n * (e.g. `basou session show` rendering).\n */\nexport async function readAllEvents(\n sessionDir: string,\n options: ReplayOptions = {},\n): Promise<Event[]> {\n const out: Event[] = [];\n for await (const ev of replayEvents(sessionDir, options)) {\n out.push(ev);\n }\n return out;\n}\n","import { z } from \"zod\";\nimport {\n ApprovalIdSchema,\n DecisionIdSchema,\n EventIdSchema,\n EventSourceSchema,\n IsoTimestampSchema,\n RiskLevelSchema,\n SchemaVersionSchema,\n SessionIdSchema,\n TaskIdSchema,\n} from \"./shared.schema.js\";\n\n// Common base every event variant extends. Each variant declares its own\n// `type: z.literal(...)` and adds variant-specific fields.\nconst BaseEventSchema = z.object({\n schema_version: SchemaVersionSchema,\n id: EventIdSchema,\n session_id: SessionIdSchema,\n occurred_at: IsoTimestampSchema,\n source: EventSourceSchema,\n // Tamper-evidence back-pointer (hex sha-256 of the PREVIOUS event line's\n // written bytes; the first line carries the session-bound genesis hash).\n // Present only on sessions written with chaining enabled (import paths);\n // live/ad-hoc sessions omit it. Declared on the base so the `.strict()`\n // variants below treat it as a known key. Additive optional => no\n // schema_version bump.\n prev_hash: z.string().optional(),\n});\n\n// --- Session lifecycle events ---\n\nconst SessionStartedEventSchema = BaseEventSchema.extend({\n type: z.literal(\"session_started\"),\n});\n\nconst SessionEndedEventSchema = BaseEventSchema.extend({\n type: z.literal(\"session_ended\"),\n exit_code: z.number().int().optional(),\n});\n\n// `from`/`to` use `string` to keep this module independent of session.schema\n// and avoid a circular import. A later event-replay layer may narrow these to\n// SessionStatusSchema by relocating the enum into shared.schema.\nconst SessionStatusChangedEventSchema = BaseEventSchema.extend({\n type: z.literal(\"session_status_changed\"),\n from: z.string(),\n to: z.string(),\n});\n\n// --- Approval events ---\n\nconst ApprovalRequestedEventSchema = BaseEventSchema.extend({\n type: z.literal(\"approval_requested\"),\n approval_id: ApprovalIdSchema,\n expires_at: IsoTimestampSchema.nullable().default(null),\n risk_level: RiskLevelSchema,\n // `action.kind` is required; additional fields are allowed to support\n // future action shapes (shell_command, external_send, ...).\n action: z.object({ kind: z.string() }).passthrough(),\n reason: z.string(),\n status: z.literal(\"pending\"),\n});\n\nconst ApprovalApprovedEventSchema = BaseEventSchema.extend({\n type: z.literal(\"approval_approved\"),\n approval_id: ApprovalIdSchema,\n resolver: z.string().optional(),\n note: z.string().nullable().optional(),\n});\n\nconst ApprovalRejectedEventSchema = BaseEventSchema.extend({\n type: z.literal(\"approval_rejected\"),\n approval_id: ApprovalIdSchema,\n resolver: z.string().optional(),\n reason: z.string(),\n});\n\nconst ApprovalExpiredEventSchema = BaseEventSchema.extend({\n type: z.literal(\"approval_expired\"),\n approval_id: ApprovalIdSchema,\n});\n\n// --- Command / Git / File events ---\n\n// `command` is the spawned executable name only (e.g. \"npm\"); arguments are\n// kept in `args` to preserve quoting and avoid shell-injection round-trips.\n// `exit_code` is null when the child terminated by signal. `signal` records\n// the child's terminating signal; `received_signal` records what the parent\n// process received (SIGINT/SIGTERM) and forwarded as cancellation, so a\n// timeout (signal set, received_signal absent) can be distinguished from a\n// user interrupt (both set).\nconst CommandExecutedEventSchema = BaseEventSchema.extend({\n type: z.literal(\"command_executed\"),\n command: z.string(),\n args: z.array(z.string()),\n cwd: z.string(),\n exit_code: z.number().int().nullable(),\n signal: z.string().nullable().optional(),\n received_signal: z.string().nullable().optional(),\n duration_ms: z.number().int().nonnegative(),\n});\n\nconst GitSnapshotEventSchema = BaseEventSchema.extend({\n type: z.literal(\"git_snapshot\"),\n head: z.string(),\n branch: z.string(),\n dirty: z.boolean(),\n staged: z.array(z.string()),\n unstaged: z.array(z.string()),\n untracked: z.array(z.string()),\n ahead: z.number().int().nonnegative().optional(),\n behind: z.number().int().nonnegative().optional(),\n});\n\nconst FileChangedEventSchema = BaseEventSchema.extend({\n type: z.literal(\"file_changed\"),\n path: z.string(),\n change_type: z.enum([\"added\", \"modified\", \"deleted\", \"renamed\"]),\n // Renamed entries record the previous path here. Optional + nullable to\n // keep the wire format stable for added / modified / deleted events.\n old_path: z.string().nullable().optional(),\n});\n\n// --- Decision / Task / Note events ---\n\n// Decision rich fields are all optional so v0.1 payloads\n// (= core 4 fields only) round-trip unchanged. References are opaque — the\n// schema only validates the SHAPE (EventId format, non-empty / length-capped\n// strings); existence of the referenced event or file is the renderer's\n// concern and surfaces as `(missing)` rather than a parse failure, so\n// import/export round-trips across workspaces never reject on a stale id.\nconst DecisionRecordedEventSchema = BaseEventSchema.extend({\n type: z.literal(\"decision_recorded\"),\n decision_id: DecisionIdSchema,\n title: z.string(),\n rationale: z.string().nullable().optional(),\n alternatives: z.array(z.string().min(1)).optional(),\n rejected_reason: z.string().nullable().optional(),\n linked_events: z.array(EventIdSchema).optional(),\n linked_files: z.array(z.string().min(1).max(4096)).optional(),\n // `track` promotes a decision to a strategic, unfinished DIRECTION (\"the next\n // essential thing to build, and why\") that orientation/handoff resurface every\n // time until it is explicitly closed with `decision void` / supersede — as\n // opposed to a point-in-time `decision`, which is only ever surfaced as the\n // single latest one. This is the intent-continuity layer: a direction agreed\n // in conversation otherwise sinks into the flat decision list and never carries\n // to the next session. Absent (the default) is a plain `decision`, so all\n // pre-existing decision_recorded events round-trip unchanged (additive optional\n // => no schema_version bump; mirrors `note_added.kind`).\n kind: z.enum([\"decision\", \"track\"]).optional(),\n});\n\n// Voids (or supersedes) a previously recorded decision. Append-only: the\n// original `decision_recorded` line is never mutated; this event marks it no\n// longer in force so decisions.md / orientation can strike it and skip it as\n// the \"latest\" direction. `superseded_by` optionally points at the replacement\n// decision (a supersede); absent it is a plain void. Like `linked_events`, the\n// referenced ids are prefix-checked only — existence is a render-time concern,\n// so an import/export round-trip across workspaces never rejects on a stale id.\nconst DecisionVoidedEventSchema = BaseEventSchema.extend({\n type: z.literal(\"decision_voided\"),\n decision_id: DecisionIdSchema,\n reason: z.string().nullable().optional(),\n superseded_by: DecisionIdSchema.optional(),\n});\n\nconst TaskCreatedEventSchema = BaseEventSchema.extend({\n type: z.literal(\"task_created\"),\n task_id: TaskIdSchema,\n title: z.string(),\n});\n\nconst TaskStatusChangedEventSchema = BaseEventSchema.extend({\n type: z.literal(\"task_status_changed\"),\n task_id: TaskIdSchema,\n from: z.string(),\n to: z.string(),\n});\n\n// emitted by `basou task reconcile --write` after broken session\n// references in a task.md are cleaned up. `.strict()` so that any extra field\n// (likely a core-side miscoding of an audit value) is rejected at parse time\n// rather than silently stripped — the event is the audit trail.\nconst TaskReconciledEventSchema = BaseEventSchema.extend({\n type: z.literal(\"task_reconciled\"),\n task_id: TaskIdSchema,\n removed_created_in_session: SessionIdSchema.nullable().default(null),\n created_in_session_replacement: SessionIdSchema.nullable().default(null),\n removed_linked_sessions: z.array(SessionIdSchema).default([]),\n}).strict();\n\n// v0.2: emitted by `basou task refresh-linkage` after the task.md\n// `linked_sessions[]` snapshot is re-derived from `session.yaml.task_id`\n// matches across the workspace. Distinct from `task_reconciled` (= broken\n// ref cleanup) so each event carries a single, focused audit story.\n// `.strict()` for the same reason as TaskReconciledEvent — the event is the\n// authoritative record. The three count/array fields are all optional with\n// sensible defaults so the schema is backward-compatible (= a future caller\n// that omits them parses to an empty refresh, which is also the no-op\n// dry-run record shape).\nconst TaskLinkageRefreshedEventSchema = BaseEventSchema.extend({\n type: z.literal(\"task_linkage_refreshed\"),\n task_id: TaskIdSchema,\n added_linked_sessions: z.array(SessionIdSchema).default([]),\n removed_linked_sessions: z.array(SessionIdSchema).default([]),\n final_count: z.number().int().nonnegative().optional(),\n}).strict();\n\n// v0.2 lifecycle events for `basou task delete` / `basou task archive`.\n// Both are `.strict()` so the audit record is exactly what the orchestrator\n// emits, and both carry the task's last-known title so events.jsonl can\n// describe what was deleted / archived without requiring the (now gone or\n// relocated) task.md.\nconst TaskDeletedEventSchema = BaseEventSchema.extend({\n type: z.literal(\"task_deleted\"),\n task_id: TaskIdSchema,\n title: z.string().min(1),\n}).strict();\n\nconst TaskArchivedEventSchema = BaseEventSchema.extend({\n type: z.literal(\"task_archived\"),\n task_id: TaskIdSchema,\n title: z.string().min(1),\n}).strict();\n\nconst NoteAddedEventSchema = BaseEventSchema.extend({\n type: z.literal(\"note_added\"),\n body: z.string(),\n // `next_step` marks a note authored by `basou note` as the operator's resume\n // hint, which orientation surfaces as the next starting point. Absent (the\n // `basou session note` default) is a plain annotation orientation does not\n // surface. Optional so pre-existing note_added events remain valid.\n kind: z.enum([\"note\", \"next_step\"]).optional(),\n});\n\n// --- Adapter output (`.strict()` rejects raw bodies) ---\n//\n// The spec forbids embedding raw adapter output (`content`, `body`, `raw`,\n// ...) directly in events.jsonl (see\n// `docs/spec/schemas.md#74-adapter_output-constraint-important`). The\n// strict variant rejects any schema-unknown key so that contract is\n// enforced at parse time.\nconst AdapterOutputEventSchema = BaseEventSchema.extend({\n type: z.literal(\"adapter_output\"),\n stream: z.enum([\"stdout\", \"stderr\"]),\n summary: z.string(),\n raw_ref: z.string(),\n redacted: z.boolean().optional(),\n}).strict();\n\n/**\n * Discriminated union of every Basou v0.1 event type. The `type` literal\n * narrows TypeScript to the appropriate variant. The `adapter_output`\n * variant is uniquely strict to bar raw adapter bodies.\n */\nexport const EventSchema = z.discriminatedUnion(\"type\", [\n SessionStartedEventSchema,\n SessionEndedEventSchema,\n SessionStatusChangedEventSchema,\n ApprovalRequestedEventSchema,\n ApprovalApprovedEventSchema,\n ApprovalRejectedEventSchema,\n ApprovalExpiredEventSchema,\n CommandExecutedEventSchema,\n GitSnapshotEventSchema,\n FileChangedEventSchema,\n DecisionRecordedEventSchema,\n DecisionVoidedEventSchema,\n TaskCreatedEventSchema,\n TaskStatusChangedEventSchema,\n TaskReconciledEventSchema,\n TaskLinkageRefreshedEventSchema,\n TaskDeletedEventSchema,\n TaskArchivedEventSchema,\n NoteAddedEventSchema,\n AdapterOutputEventSchema,\n]);\n\n/** Inferred runtime type for any Basou event. */\nexport type Event = z.infer<typeof EventSchema>;\n\n/** Narrowed runtime type for the `session_started` event variant. */\nexport type SessionStartedEvent = z.infer<typeof SessionStartedEventSchema>;\n/** Narrowed runtime type for the `session_ended` event variant. */\nexport type SessionEndedEvent = z.infer<typeof SessionEndedEventSchema>;\n/** Narrowed runtime type for the `session_status_changed` event variant. */\nexport type SessionStatusChangedEvent = z.infer<typeof SessionStatusChangedEventSchema>;\n/** Narrowed runtime type for the `approval_requested` event variant. */\nexport type ApprovalRequestedEvent = z.infer<typeof ApprovalRequestedEventSchema>;\n/** Narrowed runtime type for the `approval_approved` event variant. */\nexport type ApprovalApprovedEvent = z.infer<typeof ApprovalApprovedEventSchema>;\n/** Narrowed runtime type for the `approval_rejected` event variant. */\nexport type ApprovalRejectedEvent = z.infer<typeof ApprovalRejectedEventSchema>;\n/** Narrowed runtime type for the `approval_expired` event variant. */\nexport type ApprovalExpiredEvent = z.infer<typeof ApprovalExpiredEventSchema>;\n/** Narrowed runtime type for the `command_executed` event variant. */\nexport type CommandExecutedEvent = z.infer<typeof CommandExecutedEventSchema>;\n/** Narrowed runtime type for the `git_snapshot` event variant. */\nexport type GitSnapshotEvent = z.infer<typeof GitSnapshotEventSchema>;\n/** Narrowed runtime type for the `file_changed` event variant. */\nexport type FileChangedEvent = z.infer<typeof FileChangedEventSchema>;\n/** Narrowed runtime type for the `decision_recorded` event variant. */\nexport type DecisionRecordedEvent = z.infer<typeof DecisionRecordedEventSchema>;\n/** Narrowed runtime type for the `decision_voided` event variant. */\nexport type DecisionVoidedEvent = z.infer<typeof DecisionVoidedEventSchema>;\n/** Narrowed runtime type for the `task_created` event variant. */\nexport type TaskCreatedEvent = z.infer<typeof TaskCreatedEventSchema>;\n/** Narrowed runtime type for the `task_status_changed` event variant. */\nexport type TaskStatusChangedEvent = z.infer<typeof TaskStatusChangedEventSchema>;\n/** Narrowed runtime type for the `task_reconciled` event variant (.strict()). */\nexport type TaskReconciledEvent = z.infer<typeof TaskReconciledEventSchema>;\n/** Narrowed runtime type for the `task_linkage_refreshed` event variant (.strict()). */\nexport type TaskLinkageRefreshedEvent = z.infer<typeof TaskLinkageRefreshedEventSchema>;\n/** Narrowed runtime type for the `task_deleted` event variant (.strict()). */\nexport type TaskDeletedEvent = z.infer<typeof TaskDeletedEventSchema>;\n/** Narrowed runtime type for the `task_archived` event variant (.strict()). */\nexport type TaskArchivedEvent = z.infer<typeof TaskArchivedEventSchema>;\n/** Narrowed runtime type for the `note_added` event variant. */\nexport type NoteAddedEvent = z.infer<typeof NoteAddedEventSchema>;\n/** Narrowed runtime type for the `adapter_output` event variant (.strict()). */\nexport type AdapterOutputEvent = z.infer<typeof AdapterOutputEventSchema>;\n","import { readdir } from \"node:fs/promises\";\nimport { join } from \"node:path\";\nimport { inspectChainTail } from \"../events/chained-append.js\";\nimport { type ReplayWarning, replayEvents } from \"../events/event-replay.js\";\nimport { findErrorCode } from \"../lib/error-codes.js\";\nimport { type Session, SessionSchema } from \"../schemas/session.schema.js\";\nimport type { BasouPaths } from \"./basou-dir.js\";\nimport { acquireLock } from \"./lockfile.js\";\nimport { overwriteYamlFile, readYamlFile } from \"./yaml-store.js\";\n\n/**\n * Threshold above which a still-`running` session with no `session_ended`\n * event is flagged suspect.\n *\n * 24h: long enough that an active long-running session will not be flagged,\n * short enough that an abandoned process is surfaced within a working day.\n * Tunable via CLI option in a later step (continuation backlog #23).\n */\nexport const STUCK_THRESHOLD_MS = 24 * 60 * 60 * 1000;\n\nexport type SuspectReason = \"events_say_ended_but_yaml_running\" | \"running_no_end_event\";\n\nexport type SessionEntry = {\n sessionId: string;\n session: Session;\n suspect: boolean;\n suspectReason: SuspectReason | null;\n /**\n * The trail store this entry was read from. Its `sessions` directory locates\n * the session's `events.jsonl`, so a federated caller can replay events from\n * the store the session actually lives in (not the local store). For a plain\n * local load this is the `paths` passed to {@link loadSessionEntries}.\n */\n sourceRoot: BasouPaths;\n /**\n * Federation host label from the registry (`~/.basou/hosts.yaml`), or `null`\n * for the local store. Surfaced by orientation so a merged, multi-host view\n * can attribute the latest session / decision / next-step to its host.\n */\n host: string | null;\n};\n\n/**\n * Per-session degradation reason emitted by {@link loadSessionEntries.onSkip}.\n *\n * - `session_yaml_missing` (ENOENT) and `session_yaml_invalid` (parse or schema\n * failure) both omit the entry from the result.\n * - `events_jsonl_unreadable` still pushes the entry with `suspect=false` so\n * the session row remains visible to the caller; only the suspect check is\n * degraded. Matches the existing CLI behaviour at\n * `packages/cli/src/commands/session.ts` (suspect-check stderr warning).\n */\nexport type SessionSkipReason =\n | \"session_yaml_missing\"\n | \"session_yaml_invalid\"\n | \"events_jsonl_unreadable\";\n\nexport type LoadSessionEntriesOptions = {\n /**\n * Single `now` shared across every {@link classifySuspect} call so that\n * sessions classified back-to-back observe the same instant. Avoids\n * boundary races where a session at age ≈ 24h would flip between calls.\n */\n now: Date;\n onWarning?: (warning: ReplayWarning, sessionId: string) => void;\n onSkip?: (sessionId: string, reason: SessionSkipReason) => void;\n};\n\n/**\n * A trail store to read in a federated load, tagged with its host label.\n * `host: null` denotes the local store; a non-null label comes from the host\n * registry (`~/.basou/hosts.yaml`). `paths` is where that store is reachable\n * as a local path on this machine (an SSHFS mount, an rsync mirror, etc.) —\n * basou itself never performs any network I/O to obtain it.\n */\nexport type FederatedRoot = { paths: BasouPaths; host: string | null };\n\nexport type LoadFederatedOptions = LoadSessionEntriesOptions & {\n /**\n * Called when a NON-local root cannot be enumerated (present-but-unreadable\n * mount, permission error). That root is skipped best-effort so the local\n * store and other roots still load. The local root (`host: null`) is never\n * degraded here — its errors propagate, preserving single-store behaviour.\n * (An absent root path is not an error: {@link enumerateSessionDirs} returns\n * `[]` on ENOENT, so a dropped mount is simply an empty host.)\n */\n onRootUnavailable?: (host: string, error: unknown) => void;\n};\n\n/**\n * List session directory names under `paths.sessions`, ULID ascending.\n *\n * - Returns `[]` when the sessions directory does not exist (empty workspace\n * or pre-init state).\n * - Throws `Error(\"Failed to enumerate sessions\", { cause })` on other I/O.\n * - Only directories are returned (`.gitkeep` and other files are filtered).\n *\n * Sort order is `Array.prototype.sort()` default (Unicode code-point\n * compare). ULIDs are Crockford base32 in uppercase, so the natural sort\n * is also chronological session-start order.\n */\nexport async function enumerateSessionDirs(paths: BasouPaths): Promise<string[]> {\n try {\n const dirents = await readdir(paths.sessions, { withFileTypes: true });\n return dirents\n .filter((d) => d.isDirectory())\n .map((d) => d.name)\n .sort();\n } catch (error: unknown) {\n if (findErrorCode(error, \"ENOENT\")) return [];\n throw new Error(\"Failed to enumerate sessions\", { cause: error });\n }\n}\n\n/**\n * Read and validate `<paths.sessions>/<sessionId>/session.yaml`.\n *\n * - Re-throws the yaml-store fixed-message `\"YAML file not found\"` for\n * ENOENT so the caller can branch on it.\n * - Throws `Error(\"Failed to read session.yaml\", { cause })` for parse\n * failures and schema violations (cause is either the YAML parser error\n * or the zod error).\n */\nexport async function readSessionYaml(paths: BasouPaths, sessionId: string): Promise<Session> {\n const filePath = join(paths.sessions, sessionId, \"session.yaml\");\n let raw: unknown;\n try {\n raw = await readYamlFile(filePath);\n } catch (error: unknown) {\n if (error instanceof Error && error.message === \"YAML file not found\") throw error;\n throw new Error(\"Failed to read session.yaml\", { cause: error });\n }\n const result = SessionSchema.safeParse(raw);\n if (!result.success) {\n throw new Error(\"Failed to read session.yaml\", { cause: result.error });\n }\n return result.data;\n}\n\n/**\n * Apply a terminal-status mutation to a live session's `session.yaml` AND, in\n * the same locked write, stamp the tamper-evidence head anchor derived from the\n * on-disk `events.jsonl` tail. Used by the `exec` / `run` orchestrators for\n * BOTH terminal writers (the normal end-of-run finalize and the spawn-failure\n * `failed` finalize).\n *\n * Why locked + anchor-from-tail: live appends chain the LOG only and leave the\n * anchor for finalize. Reading the final tail under the session lock means a\n * foreign line appended just before finalize (e.g. a `decision record` attached\n * to a still-running session) is included in the anchor, and a foreign attach\n * that arrives after the terminal status is set is rejected by the attach gate\n * — so the anchor can never disagree with the at-rest log. The whole-document\n * read-modify-write also preserves any field a foreign locked writer set (e.g.\n * a task attach's `task_id`).\n *\n * The anchor is written only when the log is actually chained with at least one\n * line; a legacy unchained session (and an empty log) is left with no\n * `integrity` anchor, matching the import writers. The mutator receives the\n * full {@link Session} document and typically sets\n * `session.session.status` / `ended_at` / `invocation.exit_code` /\n * `related_files`.\n *\n * Throws the {@link inspectChainTail} errors (torn / mixed log), the\n * {@link readSessionYaml} errors, a zod error if the mutation produces an\n * invalid document, or `Error(\"Failed to overwrite YAML file\")` on a disk\n * failure.\n */\nexport async function finalizeSessionYaml(\n paths: BasouPaths,\n sessionId: string,\n mutate: (session: Session) => void,\n): Promise<void> {\n const lock = await acquireLock(paths, \"session\", sessionId);\n try {\n const session = await readSessionYaml(paths, sessionId);\n mutate(session);\n const tail = await inspectChainTail(paths, sessionId);\n if (tail.chained && tail.count > 0) {\n session.session.integrity = { head_hash: tail.head, event_count: tail.count };\n }\n const validated = SessionSchema.parse(session);\n await overwriteYamlFile(join(paths.sessions, sessionId, \"session.yaml\"), validated);\n } finally {\n await lock.release();\n }\n}\n\n/**\n * Classify a `running` session as suspect using one of two rules:\n *\n * - Rule A (`events_say_ended_but_yaml_running`): events.jsonl contains a\n * `session_ended` event but the session.yaml is still `running`. The\n * session ended cleanly in the event log but the YAML write was lost or\n * never reached.\n * - Rule B (`running_no_end_event`): no `session_ended` event and the last\n * event is older than {@link STUCK_THRESHOLD_MS}. The process likely\n * crashed or was killed.\n *\n * Sessions that are not `running` are never suspect.\n *\n * I/O failure on events.jsonl is re-thrown unwrapped so the caller can\n * degrade with a warning instead of treating the session as healthy. The\n * caller is also responsible for surfacing replay warnings via `onWarning`.\n */\nexport async function classifySuspect(\n paths: BasouPaths,\n sessionId: string,\n session: Session,\n now: Date,\n onWarning?: (warning: ReplayWarning) => void,\n): Promise<{ suspect: boolean; suspectReason: SuspectReason | null }> {\n if (session.session.status !== \"running\") {\n return { suspect: false, suspectReason: null };\n }\n const sessionDir = join(paths.sessions, sessionId);\n let endedFound = false;\n let lastEventOccurredAt: string | null = null;\n // Forward onWarning only when supplied — `exactOptionalPropertyTypes`\n // rejects passing a literal `undefined` for an optional property.\n const replayOpts = onWarning !== undefined ? { onWarning } : {};\n for await (const ev of replayEvents(sessionDir, replayOpts)) {\n lastEventOccurredAt = ev.occurred_at;\n if (ev.type === \"session_ended\") endedFound = true;\n }\n if (endedFound) {\n return { suspect: true, suspectReason: \"events_say_ended_but_yaml_running\" };\n }\n if (lastEventOccurredAt !== null) {\n const ageMs = now.getTime() - Date.parse(lastEventOccurredAt);\n if (Number.isFinite(ageMs) && ageMs > STUCK_THRESHOLD_MS) {\n return { suspect: true, suspectReason: \"running_no_end_event\" };\n }\n }\n return { suspect: false, suspectReason: null };\n}\n\n/**\n * High-level helper that enumerates session dirs, reads each `session.yaml`,\n * and classifies suspect for `running` sessions in one pass.\n *\n * Per-session degradations are surfaced via `options.onSkip`:\n * - `session_yaml_missing` (ENOENT) and `session_yaml_invalid` (parse or\n * schema violation): the entry is omitted from the result.\n * - `events_jsonl_unreadable`: the entry is still pushed with `suspect=false`\n * so callers can render the session row plus a CLI-side warning.\n *\n * `options.now` is taken once and threaded into every {@link classifySuspect}\n * call so age comparisons are consistent across sessions.\n */\nasync function loadEntriesFromRoot(\n root: FederatedRoot,\n options: LoadSessionEntriesOptions,\n): Promise<SessionEntry[]> {\n const { paths } = root;\n const sessionIds = await enumerateSessionDirs(paths);\n const entries: SessionEntry[] = [];\n for (const sid of sessionIds) {\n let session: Session;\n try {\n session = await readSessionYaml(paths, sid);\n } catch (error: unknown) {\n if (error instanceof Error && error.message === \"YAML file not found\") {\n options.onSkip?.(sid, \"session_yaml_missing\");\n } else {\n options.onSkip?.(sid, \"session_yaml_invalid\");\n }\n continue;\n }\n let suspect = false;\n let suspectReason: SuspectReason | null = null;\n try {\n const r = await classifySuspect(paths, sid, session, options.now, (w) =>\n options.onWarning?.(w, sid),\n );\n suspect = r.suspect;\n suspectReason = r.suspectReason;\n } catch {\n // events.jsonl I/O failure (EACCES etc.) on the suspect check is\n // unrecoverable for the classification but should not drop the session\n // entry. Surface a dedicated reason so the caller can distinguish a\n // broken events.jsonl from a broken session.yaml.\n options.onSkip?.(sid, \"events_jsonl_unreadable\");\n }\n entries.push({\n sessionId: sid,\n session,\n suspect,\n suspectReason,\n sourceRoot: paths,\n host: root.host,\n });\n }\n return entries;\n}\n\nexport async function loadSessionEntries(\n paths: BasouPaths,\n options: LoadSessionEntriesOptions,\n): Promise<SessionEntry[]> {\n return loadEntriesFromRoot({ paths, host: null }, options);\n}\n\n/**\n * Federated load across multiple trail stores. Each root's sessions are tagged\n * with that root's host label and `sourceRoot`, so a caller replays events from\n * the store the session lives in. De-duped by `sessionId` (a per-host random\n * ULID), then by `source.external_id` when present — first occurrence wins, so\n * pass the local root FIRST to keep it authoritative (e.g. over a re-imported\n * copy of the same vendor session on another host). A non-local root that\n * cannot be enumerated is reported via `onRootUnavailable` and skipped; the\n * local root's errors propagate, matching {@link loadSessionEntries}.\n */\nexport async function loadFederatedSessionEntries(\n roots: ReadonlyArray<FederatedRoot>,\n options: LoadFederatedOptions,\n): Promise<SessionEntry[]> {\n const out: SessionEntry[] = [];\n const seenIds = new Set<string>();\n const seenExternal = new Set<string>();\n for (const root of roots) {\n let entries: SessionEntry[];\n if (root.host === null) {\n entries = await loadEntriesFromRoot(root, options);\n } else {\n try {\n entries = await loadEntriesFromRoot(root, options);\n } catch (error: unknown) {\n options.onRootUnavailable?.(root.host, error);\n continue;\n }\n }\n for (const entry of entries) {\n if (seenIds.has(entry.sessionId)) continue;\n const ext = entry.session.session.source.external_id;\n if (typeof ext === \"string\" && ext.length > 0) {\n // Namespace by source kind: external_id lives in the ORIGINATING tool's\n // own id space, so the same value under a different tool is a different\n // session and must not collapse.\n const extKey = `${entry.session.session.source.kind}:${ext}`;\n if (seenExternal.has(extKey)) continue;\n seenExternal.add(extKey);\n }\n seenIds.add(entry.sessionId);\n out.push(entry);\n }\n }\n return out;\n}\n","import { appendFile, readFile } from \"node:fs/promises\";\nimport { join } from \"node:path\";\nimport { findErrorCode } from \"../lib/error-codes.js\";\nimport { type Event, EventSchema } from \"../schemas/event.schema.js\";\nimport type { BasouPaths } from \"../storage/basou-dir.js\";\nimport { acquireLock } from \"../storage/lockfile.js\";\nimport { genesisHash, lineHash, serializeEventLine } from \"./chain.js\";\n\n/**\n * The chain state of an existing `events.jsonl`, as needed by the live append\n * and finalize paths.\n *\n * - `chained` — whether the NEXT line written to this log must carry a\n * `prev_hash`. True for an empty / not-yet-created log (a fresh session\n * chains from its genesis) and for a log whose FIRST complete line already\n * carries `prev_hash`. False for a legacy / pre-feature log whose first line\n * is unchained (so it stays unchained — we never half-chain a file).\n * - `head` — the `prev_hash` value the next line carries when `chained`:\n * `genesisHash(sessionId)` for an empty log, otherwise `lineHash` of the LAST\n * complete line's raw bytes. Meaningless (set to the genesis hash) when\n * `chained` is false.\n * - `count` — number of complete (newline-terminated) lines on disk; the\n * `event_count` an integrity anchor records.\n */\nexport type ChainTailState = {\n chained: boolean;\n head: string;\n count: number;\n};\n\n// Byte-level line split on 0x0A. The caller guarantees the buffer is\n// newline-terminated, so every returned entry is a complete line and there is\n// no trailing fragment. Subarray views, no copying.\nfunction splitLinesBytes(buf: Buffer): Buffer[] {\n const out: Buffer[] = [];\n let start = 0;\n for (let i = 0; i < buf.length; i++) {\n if (buf[i] === 0x0a) {\n out.push(buf.subarray(start, i));\n start = i + 1;\n }\n }\n if (start < buf.length) out.push(buf.subarray(start));\n return out;\n}\n\n// Does this raw line decode to a JSON object carrying a top-level `prev_hash`?\n// A line that fails to parse is treated as not carrying one (our writers never\n// emit such a line; verify is the detector for a corrupt chained log).\nfunction carriesPrevHash(line: Buffer): boolean {\n try {\n const obj: unknown = JSON.parse(line.toString(\"utf8\"));\n return typeof obj === \"object\" && obj !== null && \"prev_hash\" in obj;\n } catch {\n return false;\n }\n}\n\n/**\n * Inspect `<sessions>/<sessionId>/events.jsonl` to decide how the next append\n * (or the finalize anchor) must treat the chain. READ-ONLY; the caller MUST\n * already hold the session lock so the inspected tail cannot move underneath a\n * subsequent append.\n *\n * Chained-ness is decided from the FIRST complete line (does the log claim to\n * be chained), and the head pointer is taken from the LAST complete line. If\n * the first and last lines DISAGREE — a mixed / partially-tampered file — the\n * call THROWS rather than extending a broken chain; verify is the detector, the\n * writer must not deepen a break. An unterminated final line (a torn tail from\n * a crashed prior append) also THROWS so a new line is never glued onto a\n * fragment.\n *\n * Throws `Error(\"Failed to read events.jsonl\")` for non-ENOENT I/O,\n * `Error(\"Unterminated final line in events.jsonl\")` for a torn tail, and\n * `Error(\"events.jsonl is partially chained\")` for a mixed first/last line.\n */\nexport async function inspectChainTail(\n paths: BasouPaths,\n sessionId: string,\n): Promise<ChainTailState> {\n const filePath = join(paths.sessions, sessionId, \"events.jsonl\");\n let raw: Buffer;\n try {\n raw = await readFile(filePath);\n } catch (error: unknown) {\n if (findErrorCode(error, \"ENOENT\")) {\n // A not-yet-created log: the first append chains from the session genesis.\n return { chained: true, head: genesisHash(sessionId), count: 0 };\n }\n throw new Error(\"Failed to read events.jsonl\", { cause: error });\n }\n if (raw.length === 0) {\n return { chained: true, head: genesisHash(sessionId), count: 0 };\n }\n if (raw[raw.length - 1] !== 0x0a) {\n throw new Error(\"Unterminated final line in events.jsonl\");\n }\n const lines = splitLinesBytes(raw);\n const first = lines[0] as Buffer;\n const last = lines[lines.length - 1] as Buffer;\n const firstChained = carriesPrevHash(first);\n if (firstChained !== carriesPrevHash(last)) {\n throw new Error(\"events.jsonl is partially chained\");\n }\n return {\n chained: firstChained,\n head: firstChained ? lineHash(last) : genesisHash(sessionId),\n count: lines.length,\n };\n}\n\n/**\n * Append one event to `<sessions>/<sessionId>/events.jsonl`, threading the\n * tamper-evidence hash chain. The caller MUST already hold the session lock\n * (`acquireLock(paths, \"session\", sessionId)`); this function does NOT acquire\n * it, so it composes inside a larger caller-owned critical section (the\n * convention used by `decision record`, `session note`, task attach and\n * approval resolution) without re-entrant lock deadlock.\n *\n * The event is validated against {@link EventSchema}, then — if the existing\n * log is chained (or empty) — written with a `prev_hash` back-pointer derived\n * from the real on-disk tail (see {@link inspectChainTail}); a legacy unchained\n * log keeps receiving plain unchained lines. The single serializer\n * ({@link serializeEventLine}) is shared with the bulk writers so the bytes a\n * chain hashes can never diverge from another path's bytes.\n *\n * Does NOT touch `session.yaml.integrity`: the head anchor is written once, at\n * the terminal-status finalize, by {@link finalizeSessionYaml}. A still-live\n * session therefore has a chained log but no anchor yet, which `verify` reports\n * as the benign `in_progress`.\n *\n * Throws `\"Invalid Basou event payload\"` on validation failure, the\n * {@link inspectChainTail} errors on a torn / mixed log, or `\"Failed to append\n * event to events.jsonl\"` on a disk failure. The native error is attached as\n * `cause`.\n */\nexport async function appendChainedEventLocked(\n paths: BasouPaths,\n sessionId: string,\n event: unknown,\n): Promise<{ chained: boolean }> {\n let validated: Event;\n try {\n validated = EventSchema.parse(event);\n } catch (error: unknown) {\n throw new Error(\"Invalid Basou event payload\", { cause: error });\n }\n const tail = await inspectChainTail(paths, sessionId);\n const line = tail.chained\n ? serializeEventLine({ ...validated, prev_hash: tail.head })\n : serializeEventLine(validated);\n try {\n await appendFile(join(paths.sessions, sessionId, \"events.jsonl\"), `${line}\\n`, \"utf8\");\n } catch (error: unknown) {\n throw new Error(\"Failed to append event to events.jsonl\", { cause: error });\n }\n return { chained: tail.chained };\n}\n\n/**\n * Self-locking wrapper around {@link appendChainedEventLocked} for callers that\n * do NOT already hold the session lock (the `exec` / `run` orchestrators, which\n * append one event at a time to a session they own). Acquires the session lock,\n * appends, and releases. Each append is a short-lived lock hold — the lock is\n * NEVER held across a child process — so a foreign attach can interleave safely\n * and the next append chains onto the true tail.\n */\nexport async function appendChainedEvent(\n paths: BasouPaths,\n sessionId: string,\n event: unknown,\n): Promise<{ chained: boolean }> {\n const lock = await acquireLock(paths, \"session\", sessionId);\n try {\n return await appendChainedEventLocked(paths, sessionId, event);\n } finally {\n await lock.release();\n }\n}\n","import { mkdir, readFile, unlink } from \"node:fs/promises\";\nimport { dirname, join } from \"node:path\";\nimport { findErrorCode } from \"../lib/error-codes.js\";\nimport { atomicCreate } from \"./atomic.js\";\nimport type { BasouPaths } from \"./basou-dir.js\";\n\n/**\n * The two lock scopes basou uses. `task` guards the read-modify-write window\n * around a single `task.md`; `session` guards the events.jsonl append plus\n * surrounding `session.yaml` mutation for a single session. Two scopes use\n * different lockfile names so they never collide on disk.\n */\nexport type LockScope = \"task\" | \"session\";\n\n/**\n * Any lock older than this is treated as stale and force-released even if the\n * holding pid is still alive. basou CLI invocations hold a lock for ms to a\n * few seconds at most, so an hour is a 10000x safety margin; the upper bound\n * is also our defence against pid reuse (a different process happening to\n * receive a long-dead pid).\n */\nconst STALE_LOCK_MAX_AGE_MS = 60 * 60 * 1000;\n\ntype LockFileBody = {\n pid: number;\n acquired_at: string;\n};\n\nexport type LockHandle = {\n /**\n * Release the lock by unlinking the lockfile. Best-effort: any unlink error\n * is swallowed so a doubled release does not raise, and disk state never\n * holds a stranded lockfile after the caller's `finally` block.\n */\n release: () => Promise<void>;\n};\n\n/**\n * Acquire an advisory lock at `<paths.locks>/<scope>_<id>.lock` for the\n * lifetime of the returned handle. Lockfile body records the holder's pid\n * and acquire timestamp so a competitor can detect stale locks left by a\n * SIGINT'd CLI run and recover automatically.\n *\n * Acquisition strategy:\n * 1. {@link atomicCreate} the lockfile (POSIX link(2) + EEXIST).\n * On ENOENT (a workspace from before `.basou/locks/` existed), create\n * the directory and retry once; a retry failure throws the pathless\n * `\"Failed to acquire lock\"`.\n * 2. On EEXIST, probe the existing lockfile via {@link isStaleLock}.\n * - If stale (= holder pid is dead or lock is older than\n * {@link STALE_LOCK_MAX_AGE_MS}), `unlink` the stale file and retry\n * the atomic create once.\n * - If still EEXIST after the retry (= another competitor won the race),\n * throw `\"Lock is held by another process\"`.\n * - If the holder is alive, throw `\"Lock is held by another process\"`\n * without retrying.\n *\n * The caller MUST call `release()` (typically from a `finally` block); the\n * `process.exit()` path or a fatal crash relies on stale-lock detection on\n * the next acquire to recover.\n */\nexport async function acquireLock(\n paths: BasouPaths,\n scope: LockScope,\n resourceId: string,\n): Promise<LockHandle> {\n const lockPath = lockfilePath(paths, scope, resourceId);\n const body: LockFileBody = {\n pid: process.pid,\n acquired_at: new Date().toISOString(),\n };\n const serialised = JSON.stringify(body);\n\n try {\n await atomicCreate(lockPath, serialised);\n } catch (error: unknown) {\n // A workspace checked out (or created) before the locks directory\n // existed lacks `.basou/locks/`; create it and retry once rather than\n // failing every lock-taking command on such a workspace.\n if (findErrorCode(error, \"ENOENT\")) {\n try {\n await mkdir(dirname(lockPath), { recursive: true });\n await atomicCreate(lockPath, serialised);\n return {\n release: async () => {\n await unlink(lockPath).catch(() => undefined);\n },\n };\n } catch (retryError: unknown) {\n throw new Error(\"Failed to acquire lock\", { cause: retryError });\n }\n }\n if (!findErrorCode(error, \"EEXIST\")) {\n throw error;\n }\n const stale = await isStaleLock(lockPath);\n if (!stale) {\n throw new Error(\"Lock is held by another process\", { cause: error });\n }\n // Best-effort cleanup of the stale lockfile, then a single retry. A\n // second EEXIST means another competitor beat us to the cleared lock;\n // surface that as a normal \"held\" failure rather than looping.\n await unlink(lockPath).catch(() => undefined);\n try {\n await atomicCreate(lockPath, serialised);\n } catch (retryError: unknown) {\n throw new Error(\"Lock is held by another process\", { cause: retryError });\n }\n }\n\n return {\n release: async () => {\n await unlink(lockPath).catch(() => undefined);\n },\n };\n}\n\n/**\n * Read the lockfile at `lockPath` and decide whether the holder is dead or\n * the lock is too old to trust. Used by {@link acquireLock} on EEXIST to\n * recover from SIGINT'd CLI runs that left the lockfile behind.\n *\n * Stale predicates (any of these = stale):\n * - lockfile body unreadable or malformed\n * - `acquired_at` is older than {@link STALE_LOCK_MAX_AGE_MS}\n * - `process.kill(pid, 0)` throws ESRCH (holder pid is dead)\n *\n * EPERM from `process.kill` means the pid is alive but owned by a different\n * uid; we treat that as alive so cross-user lockfile takeover does not happen\n * by accident.\n */\nasync function isStaleLock(lockPath: string): Promise<boolean> {\n let body: LockFileBody;\n try {\n const raw = await readFile(lockPath, \"utf8\");\n const parsed = JSON.parse(raw) as unknown;\n if (typeof parsed !== \"object\" || parsed === null) return true;\n const candidate = parsed as Partial<LockFileBody>;\n if (typeof candidate.pid !== \"number\" || typeof candidate.acquired_at !== \"string\") {\n return true;\n }\n body = { pid: candidate.pid, acquired_at: candidate.acquired_at };\n } catch {\n // Unreadable lockfile (e.g. truncated mid-write) counts as stale so we\n // can recover instead of looping forever on EEXIST.\n return true;\n }\n const ageMs = Date.now() - Date.parse(body.acquired_at);\n if (!Number.isFinite(ageMs) || ageMs > STALE_LOCK_MAX_AGE_MS) {\n return true;\n }\n try {\n process.kill(body.pid, 0);\n return false;\n } catch (error: unknown) {\n if (findErrorCode(error, \"ESRCH\")) return true;\n // EPERM or any other surface — pid is alive (or unknown), keep the lock.\n return false;\n }\n}\n\nfunction lockfilePath(paths: BasouPaths, scope: LockScope, resourceId: string): string {\n // Strip the type prefix to keep the lockfile name compact (`task_01HX...` →\n // `01HX...`, `ses_01HX...` → `01HX...`). The scope literal at the start of\n // the filename keeps task/session lockfiles disjoint even when the ULID\n // tails happen to coincide.\n const sep = resourceId.indexOf(\"_\");\n const ulid = sep >= 0 ? resourceId.slice(sep + 1) : resourceId;\n return join(paths.locks, `${scope}_${ulid}.lock`);\n}\n","import { createHash } from \"node:crypto\";\nimport type { Event } from \"../schemas/event.schema.js\";\n\n// Domain-separation prefix for the chain's genesis hash. Versioned so a\n// future chain format can re-anchor without colliding with v1 hashes.\nconst GENESIS_PREFIX = \"basou:event-chain:v1:\";\n\n/**\n * Session-bound genesis hash: the `prev_hash` carried by the FIRST event line\n * of a chained `events.jsonl`. Binding the genesis to the session id means a\n * chain copied verbatim from another session fails verification at line 1\n * even though its internal back-pointers are intact.\n */\nexport function genesisHash(sessionId: string): string {\n return createHash(\"sha256\").update(`${GENESIS_PREFIX}${sessionId}`, \"utf8\").digest(\"hex\");\n}\n\n/**\n * Hex sha-256 of one event line's written bytes (EXCLUDING the trailing\n * `\\n`). The hash covers the literal serialized bytes — no canonical JSON\n * form. Writers pass the line string (always valid UTF-8, so its UTF-8\n * encoding IS the written bytes); the verifier passes the RAW BYTES it read,\n * so a byte-level mutation that decodes to the same string (e.g. an invalid\n * UTF-8 sequence collapsing to U+FFFD) still breaks the chain.\n */\nexport function lineHash(rawLine: string | Buffer): string {\n const hash = createHash(\"sha256\");\n if (typeof rawLine === \"string\") {\n hash.update(rawLine, \"utf8\");\n } else {\n hash.update(rawLine);\n }\n return hash.digest(\"hex\");\n}\n\n/**\n * The single serializer for event lines. Every writer (bulk and append) MUST\n * go through this function so the bytes a chain hashes can never diverge from\n * the bytes another code path would write.\n */\nexport function serializeEventLine(event: Event): string {\n return JSON.stringify(event);\n}\n\n/** Result of {@link chainEvents}: the serialized lines plus the head anchor inputs. */\nexport type ChainedEvents = {\n /** Serialized event lines (no trailing newline on the entries). */\n lines: string[];\n /**\n * Hex sha-256 of the LAST line — the value `session.yaml.integrity.head_hash`\n * anchors. For an empty batch this is the genesis hash and no anchor is\n * written.\n */\n headHash: string;\n /** Number of chained lines (= `integrity.event_count`). */\n count: number;\n};\n\n/**\n * Thread a `prev_hash` back-pointer through `events` and serialize them:\n * line 0 carries `genesisHash(sessionId)`, line N carries the hash of line\n * N-1's written bytes. Any `prev_hash` already present on an incoming event\n * (e.g. a round-trip import payload) is discarded and recomputed — chains are\n * never trusted from input, only derived at write time.\n */\nexport function chainEvents(events: ReadonlyArray<Event>, sessionId: string): ChainedEvents {\n let prev = genesisHash(sessionId);\n const lines: string[] = [];\n for (const event of events) {\n // Spread + override discards the incoming prev_hash VALUE; key order is\n // irrelevant because hashing covers the literal written bytes.\n const chained: Event = { ...event, prev_hash: prev };\n const line = serializeEventLine(chained);\n lines.push(line);\n prev = lineHash(line);\n }\n return { lines, headHash: prev, count: lines.length };\n}\n\n/**\n * Chain PRE-EXISTING serialized event lines WITHOUT re-serializing them\n * through the schema layer: each original line is JSON-parsed, given a\n * `prev_hash`, and stringified again. Because `JSON.parse`/`JSON.stringify`\n * round-trips key insertion order and every value verbatim, the output line\n * is the original line with only the `prev_hash` member appended — no zod\n * key-stripping or default materialization can occur. Used by the in-place\n * rechain migration of pre-chaining imported sessions.\n *\n * Callers MUST gate validity first (non-empty, JSON-parseable,\n * schema-valid, byte-identical JSON round-trip); this helper assumes\n * parseable lines and throws raw on a parse failure.\n */\nexport function chainRawJsonLines(\n rawLines: ReadonlyArray<string>,\n sessionId: string,\n): ChainedEvents {\n let prev = genesisHash(sessionId);\n const lines: string[] = [];\n for (const rawLine of rawLines) {\n const parsed = JSON.parse(rawLine) as Record<string, unknown>;\n const line = JSON.stringify({ ...parsed, prev_hash: prev });\n lines.push(line);\n prev = lineHash(line);\n }\n return { lines, headHash: prev, count: lines.length };\n}\n","import { z } from \"zod\";\nimport {\n IsoTimestampSchema,\n SchemaVersionSchema,\n SessionIdSchema,\n TaskIdSchema,\n WorkspaceIdSchema,\n} from \"./shared.schema.js\";\n\n/** Session lifecycle states. */\nexport const SessionStatusSchema = z.enum([\n \"initialized\",\n \"running\",\n \"waiting_approval\",\n \"completed\",\n \"failed\",\n \"interrupted\",\n \"imported\",\n \"archived\",\n]);\n/** Inferred runtime type for {@link SessionStatusSchema}. */\nexport type SessionStatus = z.infer<typeof SessionStatusSchema>;\n\n/**\n * Source kind that produced the session.\n *\n * - `claude-code-adapter` — a live `basou run claude-code` process wrap.\n * - `claude-code-import` — derived after the fact from a Claude Code native\n * transcript (`~/.claude/projects/*.jsonl`) by `basou import claude-code`.\n * - `codex-import` — derived after the fact from an OpenAI Codex native\n * rollout log (date-partitioned `~/.codex/sessions`) by `basou import codex`.\n * - `import` — a round-trip of a Basou-format export (`basou session import`).\n * - `human` / `terminal` — manually-authored / terminal-recorded sessions.\n */\nexport const SessionSourceKindSchema = z.enum([\n \"claude-code-adapter\",\n \"claude-code-import\",\n \"codex-import\",\n \"human\",\n \"import\",\n \"terminal\",\n]);\n/** Inferred runtime type for {@link SessionSourceKindSchema}. */\nexport type SessionSourceKind = z.infer<typeof SessionSourceKindSchema>;\n\nconst SessionSourceSchema = z.object({\n kind: SessionSourceKindSchema,\n version: z.literal(\"0.1.0\"),\n // Optional id of the originating session in the SOURCE tool's own\n // namespace (e.g. the Claude Code session UUID for a `claude-code-import`).\n // Lets re-imports of the same source be deduplicated; absent for live runs.\n external_id: z.string().optional(),\n // Byte size of the source native log at import time, recorded so a later\n // import can detect that an append-only transcript GREW and re-import it\n // (scoped, preserving the session id) instead of skipping it as already\n // imported. Additive optional => no schema_version bump (precedent:\n // external_id, metrics). Absent on sessions imported before this field\n // existed (treated as legacy: never auto-re-imported, populated on the next\n // fresh import or `--force`).\n source_size_bytes: z.number().int().nonnegative().optional(),\n});\n\nconst InvocationSchema = z.object({\n command: z.string().min(1),\n args: z.array(z.string()).default([]),\n // Nullable to record signal-terminated runs where the child has no exit\n // code; the same nullability is mirrored in CommandExecutedEventSchema.\n exit_code: z.number().int().nullable(),\n});\n\n/**\n * Optional per-session metrics, computed at import time from the source tool's\n * native log. Two groups, both optional because not every source records them:\n *\n * - Model-usage rollup (`*_tokens`): the transcript carries per-message token\n * usage; these are the session totals. `reasoning_output_tokens` is\n * Codex-only, and live `run`/`exec` sessions carry no token usage at all.\n * - Engaged-time metrics (`active_*`): the billing-oriented active time derived\n * from the session's genuine engagement timestamps (conversation turns plus\n * action events), with idle gaps capped. `active_intervals` are the merged\n * wall-clock ranges (so cross-session totals can de-duplicate overlapping\n * work by interval union); `active_time_ms` is their summed duration;\n * `active_gap_cap_ms` and `active_time_method` lock the methodology so the\n * stored numbers stay interpretable if the method changes later. When a\n * source records explicit per-turn intervals (Codex), `active_time_method` is\n * `turn-intervals` and the in-turn time is the log's real wall-clock span\n * rather than a gap-capped approximation; the active semantics are unchanged.\n * - `machine_active_time_ms`: model compute time — the summed duration of the\n * source's per-turn spans (Codex `task_complete.duration_ms`), a SUBSET of a\n * single session's engaged active time. Unlike `active_intervals` it is a\n * plain sum, NOT wall-clock-deduplicated, so two concurrent sessions can sum\n * past their billable (union) active wall-clock — that is intended (two models\n * working at once did two machine-hours in one wall-clock hour). Captured only\n * for sources that record per-turn duration (Codex); absent otherwise.\n *\n * Absent on sessions imported before a given field existed (re-import to\n * backfill). Live sessions carry no engaged-time metrics and fall back to\n * event-derived active time at stats time.\n */\nexport const SessionMetricsSchema = z.object({\n output_tokens: z.number().int().nonnegative().optional(),\n input_tokens: z.number().int().nonnegative().optional(),\n cached_input_tokens: z.number().int().nonnegative().optional(),\n reasoning_output_tokens: z.number().int().nonnegative().optional(),\n active_time_ms: z.number().int().nonnegative().optional(),\n active_intervals: z\n .array(z.object({ start: IsoTimestampSchema, end: IsoTimestampSchema }))\n .optional(),\n active_gap_cap_ms: z.number().int().nonnegative().optional(),\n active_time_method: z.string().optional(),\n machine_active_time_ms: z.number().int().nonnegative().optional(),\n});\n/** Inferred runtime type for {@link SessionMetricsSchema}. */\nexport type SessionMetrics = z.infer<typeof SessionMetricsSchema>;\n\n/**\n * Tamper-evidence head anchor for a session whose `events.jsonl` is hash\n * chained: `head_hash` is the hex sha-256 of the last written event line\n * (excluding the trailing newline), `event_count` the number of chained lines.\n * Written by the import / in-place re-import writers and, for a live session\n * (`exec` / `run` / ad-hoc), by the finalize once it reaches a terminal status.\n * Absent on a still-live session (the anchor is stamped at finalize) and on a\n * pre-feature unchained session. Additive optional => no schema_version bump.\n * `.strict()` because the writers fully own the shape.\n */\nexport const SessionIntegritySchema = z\n .object({\n head_hash: z.string(),\n event_count: z.number().int().nonnegative(),\n })\n .strict();\n/** Inferred runtime type for {@link SessionIntegritySchema}. */\nexport type SessionIntegrity = z.infer<typeof SessionIntegritySchema>;\n\nconst SessionInnerSchema = z.object({\n id: SessionIdSchema,\n label: z.string().optional(),\n task_id: TaskIdSchema.nullable().optional(),\n workspace_id: WorkspaceIdSchema,\n source: SessionSourceSchema,\n started_at: IsoTimestampSchema,\n // ended_at is optional because initialized / running sessions have no end time yet.\n ended_at: IsoTimestampSchema.optional(),\n status: SessionStatusSchema,\n working_directory: z.string().min(1),\n invocation: InvocationSchema,\n related_files: z.array(z.string()).default([]),\n events_log: z.string().default(\"events.jsonl\"),\n summary: z.string().nullable().optional(),\n metrics: SessionMetricsSchema.optional(),\n integrity: SessionIntegritySchema.optional(),\n});\n\n/**\n * Schema for `.basou/sessions/<session_id>/session.yaml`. The minimal\n * session document carries the actual fields nested under the outer\n * `session:` key.\n */\nexport const SessionSchema = z.object({\n schema_version: SchemaVersionSchema,\n session: SessionInnerSchema,\n});\n\n/** Inferred runtime type for {@link SessionSchema}. */\nexport type Session = z.infer<typeof SessionSchema>;\n","import { appendFile } from \"node:fs/promises\";\nimport { basename, join } from \"node:path\";\nimport { type Event, EventSchema } from \"../schemas/event.schema.js\";\nimport { atomicReplace } from \"../storage/atomic.js\";\nimport { chainEvents, serializeEventLine } from \"./chain.js\";\n\n/**\n * Append a single Basou event to `<sessionDir>/events.jsonl`.\n *\n * The event is validated against the discriminated union {@link EventSchema}\n * before being serialized as a single JSONL line (UTF-8, terminated by `\\n`).\n * Validation enforces the per-variant contract (required fields, source\n * vocabulary, strict variants such as `adapter_output`).\n *\n * This LOW-LEVEL writer does NOT hash-chain — it writes the validated event as\n * a plain line. Hash-chained appends go through `appendChainedEvent` /\n * `appendChainedEventLocked` (the live `exec` / `run` / attach / approval\n * paths), and the bulk import writers chain via {@link writeEventsBulk} with\n * `chain: true`. A direct caller of this raw export can still add an unchained\n * line to a chained log; that is DETECTED by `basou verify`\n * (`missing_prev_hash`), not prevented — a documented boundary.\n *\n * Atomicity: writes go through `appendFile` which uses `O_APPEND`. Lines up\n * to `PIPE_BUF` bytes (Linux 4096 / macOS 512) are written atomically by the\n * kernel; longer lines may interleave with concurrent writers and are not\n * recovered here. v0.1 assumes a single writer per session, so partial-line\n * recovery is delegated to the read side (event replay) when introduced.\n *\n * Throws if validation fails or the underlying append errors. The thrown\n * Error message is pathless; the original error is attached as `cause`.\n *\n * @param sessionDir absolute path to `.basou/sessions/<session_id>/`\n * @param event unknown payload to validate and append\n */\nexport async function appendEvent(sessionDir: string, event: unknown): Promise<void> {\n let validated: ReturnType<typeof EventSchema.parse>;\n try {\n validated = EventSchema.parse(event);\n } catch (error: unknown) {\n throw new Error(\"Invalid Basou event payload\", { cause: error });\n }\n const line = `${serializeEventLine(validated)}\\n`;\n try {\n await appendFile(join(sessionDir, \"events.jsonl\"), line, \"utf8\");\n } catch (error: unknown) {\n throw new Error(\"Failed to append event to events.jsonl\", { cause: error });\n }\n}\n\n/** Options for {@link writeEventsBulk}. */\nexport type WriteEventsBulkOptions = {\n /**\n * Thread a per-line `prev_hash` hash chain through the batch and return the\n * head anchor inputs. Used ONLY by the import writers (fresh import and\n * in-place re-import); defaults to false so the live / ad-hoc writers keep\n * producing plain unchained lines.\n */\n chain?: boolean;\n};\n\n/** Head anchor inputs returned by a chained {@link writeEventsBulk}. */\nexport type BulkChainResult = {\n /** Hex sha-256 of the last written line (excluding the trailing `\\n`). */\n headHash: string;\n /** Number of chained lines written. */\n count: number;\n};\n\n/**\n * Write `events.jsonl` in one atomic tmp+rename pass via {@link atomicReplace},\n * validating every event against {@link EventSchema} before any disk I/O so\n * a payload that fails validation never leaves a partial file behind.\n *\n * The helper is used by the round-trip importer (`session-import.ts`) and the\n * ad-hoc session orchestrator (`ad-hoc-session.ts`) where a small, fixed batch\n * of events must land together or not at all. Zero events produces a\n * zero-byte file so the session_yaml `events_log` pointer remains valid.\n *\n * With `options.chain` set, each line is written with a `prev_hash`\n * back-pointer (any incoming `prev_hash` is discarded and recomputed; the\n * chain's genesis is bound to `basename(sessionDir)` = the session id) and\n * the head anchor inputs are returned so the caller can persist\n * `session.yaml.integrity`. An empty chained batch writes a zero-byte file\n * and returns null — no anchor. Without `chain` the return value is null and\n * the written bytes are identical to the previous unchained format.\n *\n * Throws `\"Invalid Basou event payload\"` (same fixed message as\n * {@link appendEvent}) on validation failure, or `\"Failed to write\n * events.jsonl\"` on a disk I/O failure. The original native error is attached\n * as `cause`.\n */\nexport async function writeEventsBulk(\n sessionDir: string,\n events: Event[],\n options: WriteEventsBulkOptions = {},\n): Promise<BulkChainResult | null> {\n const validated: Event[] = [];\n try {\n for (const event of events) {\n validated.push(EventSchema.parse(event));\n }\n } catch (error: unknown) {\n throw new Error(\"Invalid Basou event payload\", { cause: error });\n }\n const filePath = join(sessionDir, \"events.jsonl\");\n\n let body: string;\n let result: BulkChainResult | null = null;\n if (options.chain === true) {\n const { lines, headHash, count } = chainEvents(validated, basename(sessionDir));\n body = lines.length > 0 ? `${lines.join(\"\\n\")}\\n` : \"\";\n result = count > 0 ? { headHash, count } : null;\n } else {\n body = validated.length > 0 ? `${validated.map(serializeEventLine).join(\"\\n\")}\\n` : \"\";\n }\n\n try {\n await atomicReplace(filePath, body);\n } catch (error: unknown) {\n throw new Error(\"Failed to write events.jsonl\", { cause: error });\n }\n return result;\n}\n","import { readFile } from \"node:fs/promises\";\nimport { join } from \"node:path\";\nimport { findErrorCode } from \"../lib/error-codes.js\";\nimport type { SessionIntegrity, SessionStatus } from \"../schemas/session.schema.js\";\nimport type { BasouPaths } from \"../storage/basou-dir.js\";\nimport { readSessionYaml } from \"../storage/sessions.js\";\nimport { genesisHash, lineHash } from \"./chain.js\";\n\n/**\n * Session statuses whose `events.jsonl` is at rest, so its tail and head anchor\n * are strictly checked (a torn tail / missing / mismatching anchor is\n * tampering). A live append session writes its anchor only at the terminal\n * finalize, so these are exactly the statuses a finalized log can carry.\n * `imported` and the reserved `archived` are likewise at rest.\n */\nconst STRICT_STATUSES: ReadonlySet<SessionStatus> = new Set<SessionStatus>([\n \"completed\",\n \"failed\",\n \"interrupted\",\n \"imported\",\n \"archived\",\n]);\n\n// A live session's events.jsonl tail is legitimately still growing (and its\n// anchor is not written until finalize), so the internal chain is verified but\n// the tail / anchor checks are forgiven => `in_progress`.\nfunction isLiveStatus(status: SessionStatus): boolean {\n return !STRICT_STATUSES.has(status);\n}\n\n/**\n * Verification outcome for one session's `events.jsonl`.\n *\n * - `unchained` — no event line carries `prev_hash` (live / ad-hoc / legacy\n * session) and `session.yaml` carries no integrity anchor. Informational.\n * - `empty` — zero events and no integrity anchor. Informational.\n * - `incomplete` — the log is chained but `session.yaml` is ENTIRELY absent\n * (an import crashed between the events write and the yaml write, or the\n * yaml was deleted out of band). Benign: a re-import / `--force` repairs it.\n * - `tampered` — a real integrity break (see {@link ChainBreakReason}).\n * - `in_progress` — a chained log whose session is still LIVE (a non-terminal\n * status: initialized / running / waiting_approval). The internal\n * back-pointer chain is fully verified, but the tail and head anchor are\n * forgiven because a live session's log is legitimately still growing and its\n * anchor is not written until the terminal finalize. Informational, exit 0.\n * - `verified` — every back-pointer, genesis, session-id and line-discipline\n * check passed AND the head anchor matches the on-disk log.\n */\nexport type ChainVerdictStatus =\n | \"verified\"\n | \"unchained\"\n | \"empty\"\n | \"incomplete\"\n | \"in_progress\"\n | \"tampered\";\n\n/** Machine-readable detail for a `tampered` (or `incomplete`) verdict. */\nexport type ChainBreakReason =\n /** The file does not end with `\\n`; chained writers always terminate the last line. */\n | \"torn_tail\"\n /** A blank line inside a chained log; chained writers never emit one. */\n | \"blank_line\"\n /** A line of a chained log failed JSON parsing; writers only emit valid JSON. */\n | \"malformed_line\"\n /** A chained log has a line without `prev_hash`; chained writers chain every line. */\n | \"missing_prev_hash\"\n /** Line 1's `prev_hash` is not this session's genesis hash (edit or cross-session copy). */\n | \"genesis_mismatch\"\n /** A line's `prev_hash` does not hash-match the previous line (edit / insert / delete / reorder). */\n | \"broken_link\"\n /** A line's `session_id` is not this session's id (cross-session copied line). */\n | \"session_id_mismatch\"\n /** `session.yaml` exists but its `integrity` anchor is missing (anchor stripped). */\n | \"anchor_missing\"\n /** The anchor's `head_hash` / `event_count` disagree with the on-disk log (edit or truncation). */\n | \"anchor_mismatch\"\n /** An integrity anchor exists but the log is unchained, empty, or missing (chain stripped). */\n | \"anchor_without_chain\"\n /** `session.yaml` exists but could not be parsed / validated, so the anchor is unreadable. */\n | \"yaml_unreadable\"\n /** `incomplete` only: `session.yaml` is entirely absent. */\n | \"yaml_missing\";\n\n/** Result of {@link verifyEventsChain}. */\nexport type ChainVerdict = {\n status: ChainVerdictStatus;\n /** Complete (newline-terminated) event lines found on disk. */\n eventCount: number;\n /** Detail for `tampered` / `incomplete`; absent otherwise. */\n reason?: ChainBreakReason;\n /** 1-based line number of the first break, when one specific line broke. */\n line?: number;\n};\n\n// Three-state view of `session.yaml` as seen by the verifier. The `present`\n// variant carries the session status so the verdict can forgive a live\n// session's still-growing tail / not-yet-written anchor (`in_progress`).\ntype AnchorState =\n | { kind: \"absent\" }\n | { kind: \"unreadable\" }\n | { kind: \"present\"; integrity: SessionIntegrity | undefined; status: SessionStatus };\n\n/**\n * Verify the tamper-evidence hash chain of `<sessions>/<sessionId>/events.jsonl`\n * against the head anchor in `session.yaml.integrity`. READ-ONLY.\n *\n * The verifier reads the RAW line BYTES (not the schema-filtering replay\n * reader, which silently drops bad lines; and not a decoded string, which\n * would collapse invalid UTF-8 sequences into U+FFFD and let a byte-level\n * substitution survive re-hashing) and hashes exactly the bytes it read.\n * The verdict is decided on the events first, then the anchor:\n *\n * - No line carries `prev_hash` (or there are zero lines / no file): the log\n * is unchained. If `session.yaml` nevertheless carries an integrity anchor,\n * the chain was stripped out of band => `tampered` (`anchor_without_chain`);\n * otherwise `unchained` / `empty`.\n * - At least one line carries `prev_hash`: the log claims to be chained, and\n * every check applies — line discipline (terminating `\\n`, no blank lines,\n * valid JSON), genesis binding, per-line back-pointers, per-line session id,\n * and finally the head anchor (`incomplete` when `session.yaml` is entirely\n * absent; `tampered` when it is present without a matching anchor).\n *\n * - When the chained log belongs to a LIVE session (a non-terminal status),\n * the internal chain is verified but a torn tail / absent / mismatching\n * anchor is FORGIVEN as `in_progress`: a live session's tail is legitimately\n * still growing and its anchor is written only at the terminal finalize.\n *\n * NON-CRYPTOGRAPHIC: the anchor lives in `session.yaml`, which is itself\n * editable; an attacker rewriting BOTH files consistently is not detected.\n * Signing is a follow-up.\n *\n * Throws `Error(\"Failed to read events.jsonl\")` only for non-ENOENT I/O\n * failures (EACCES etc.) — an unreadable file is an environment problem, not\n * a verdict.\n *\n * READ-ONLY and lock-free: a session being finalized concurrently can leave the\n * two files momentarily out of step (old events read before a finalize, new\n * anchor read after it). A strict `anchor_mismatch` is therefore re-snapshotted\n * ONCE before being returned — a genuine mismatch is deterministic across the\n * retry, while a finalize-in-flight resolves within it.\n */\nexport async function verifyEventsChain(\n paths: BasouPaths,\n sessionId: string,\n): Promise<ChainVerdict> {\n const first = await verifyOnce(paths, sessionId);\n if (first.status === \"tampered\" && first.reason === \"anchor_mismatch\") {\n return await verifyOnce(paths, sessionId);\n }\n return first;\n}\n\nasync function verifyOnce(paths: BasouPaths, sessionId: string): Promise<ChainVerdict> {\n const sessionDir = join(paths.sessions, sessionId);\n\n let raw: Buffer | null = null;\n try {\n raw = await readFile(join(sessionDir, \"events.jsonl\"));\n } catch (error: unknown) {\n if (!findErrorCode(error, \"ENOENT\")) {\n throw new Error(\"Failed to read events.jsonl\", { cause: error });\n }\n }\n\n let anchor: AnchorState;\n try {\n const session = await readSessionYaml(paths, sessionId);\n anchor = {\n kind: \"present\",\n integrity: session.session.integrity,\n status: session.session.status,\n };\n } catch (error: unknown) {\n if (error instanceof Error && error.message === \"YAML file not found\") {\n anchor = { kind: \"absent\" };\n } else {\n anchor = { kind: \"unreadable\" };\n }\n }\n\n // Split the raw BYTES into complete (newline-terminated) lines plus an\n // optional unterminated tail fragment. A missing or empty file has neither.\n // Splitting and hashing stay at the byte level; decoding to a string\n // happens only for JSON field inspection.\n const terminated = raw === null || raw.length === 0 || raw[raw.length - 1] === 0x0a;\n const segments = raw === null ? [] : splitLinesBytes(raw);\n const tailFragment = !terminated && segments.length > 0 ? (segments.pop() as Buffer) : null;\n const lines = segments;\n\n // Chained-ness: does ANY parseable line (or the tail fragment) carry prev_hash?\n const carriesPrevHash = (s: Buffer): boolean => {\n try {\n const obj: unknown = JSON.parse(s.toString(\"utf8\"));\n return typeof obj === \"object\" && obj !== null && \"prev_hash\" in obj;\n } catch {\n return false;\n }\n };\n const chained =\n lines.some((l) => l.length > 0 && carriesPrevHash(l)) ||\n (tailFragment !== null && carriesPrevHash(tailFragment));\n\n if (!chained) {\n // Unchained / empty logs are informational — UNLESS session.yaml anchors\n // a chain that is no longer there (one-file strip / truncate-to-zero /\n // log deletion). Legitimately unchained sessions never have an anchor:\n // only the import writers set one, and they always chain.\n if (anchor.kind === \"present\" && anchor.integrity !== undefined) {\n return {\n status: \"tampered\",\n eventCount: lines.length,\n reason: \"anchor_without_chain\",\n };\n }\n if (raw === null || raw.length === 0) {\n return { status: \"empty\", eventCount: 0 };\n }\n return { status: \"unchained\", eventCount: lines.length };\n }\n\n // The log claims to be chained: walk the back-pointer chain over the\n // complete lines, reporting the FIRST break.\n let expected = genesisHash(sessionId);\n for (let i = 0; i < lines.length; i++) {\n const line = lines[i] as Buffer;\n const lineNo = i + 1;\n if (line.length === 0) {\n return { status: \"tampered\", eventCount: lines.length, reason: \"blank_line\", line: lineNo };\n }\n let parsed: unknown;\n try {\n parsed = JSON.parse(line.toString(\"utf8\"));\n } catch {\n return {\n status: \"tampered\",\n eventCount: lines.length,\n reason: \"malformed_line\",\n line: lineNo,\n };\n }\n const record = parsed as Record<string, unknown>;\n if (typeof record.prev_hash !== \"string\") {\n return {\n status: \"tampered\",\n eventCount: lines.length,\n reason: \"missing_prev_hash\",\n line: lineNo,\n };\n }\n if (record.prev_hash !== expected) {\n return {\n status: \"tampered\",\n eventCount: lines.length,\n reason: i === 0 ? \"genesis_mismatch\" : \"broken_link\",\n line: lineNo,\n };\n }\n if (record.session_id !== sessionId) {\n return {\n status: \"tampered\",\n eventCount: lines.length,\n reason: \"session_id_mismatch\",\n line: lineNo,\n };\n }\n expected = lineHash(line);\n }\n\n // The internal back-pointer chain over the complete lines is consistent. A\n // LIVE session's tail and anchor are forgiven from here: the log is still\n // growing and the anchor is not written until the terminal finalize, so a\n // torn tail (crashed append) or absent / lagging anchor is benign.\n const live = anchor.kind === \"present\" && isLiveStatus(anchor.status);\n\n // A chained file must end in a terminating newline; for an at-rest (strict)\n // session an unterminated tail can only come from out-of-band editing (the\n // finalize wrote a terminated log). A live session may legitimately carry a\n // torn tail from a crashed in-flight append.\n if (tailFragment !== null || !terminated) {\n if (live) {\n return { status: \"in_progress\", eventCount: lines.length };\n }\n return {\n status: \"tampered\",\n eventCount: lines.length,\n reason: \"torn_tail\",\n line: lines.length + 1,\n };\n }\n\n // Events are internally consistent — now the head anchor.\n if (anchor.kind === \"absent\") {\n return { status: \"incomplete\", eventCount: lines.length, reason: \"yaml_missing\" };\n }\n if (anchor.kind === \"unreadable\") {\n return { status: \"tampered\", eventCount: lines.length, reason: \"yaml_unreadable\" };\n }\n if (live) {\n // The anchor is not authoritative until the session reaches a terminal\n // status, so it is neither required nor checked here.\n return { status: \"in_progress\", eventCount: lines.length };\n }\n if (anchor.integrity === undefined) {\n return { status: \"tampered\", eventCount: lines.length, reason: \"anchor_missing\" };\n }\n if (anchor.integrity.event_count !== lines.length || anchor.integrity.head_hash !== expected) {\n return { status: \"tampered\", eventCount: lines.length, reason: \"anchor_mismatch\" };\n }\n return { status: \"verified\", eventCount: lines.length };\n}\n\n// Byte-level line split on 0x0A. A trailing newline yields no final entry;\n// content after the last newline (an unterminated tail) is returned as the\n// final entry. Subarray views, no copying.\nfunction splitLinesBytes(buf: Buffer): Buffer[] {\n const out: Buffer[] = [];\n let start = 0;\n for (let i = 0; i < buf.length; i++) {\n if (buf[i] === 0x0a) {\n out.push(buf.subarray(start, i));\n start = i + 1;\n }\n }\n if (start < buf.length) out.push(buf.subarray(start));\n return out;\n}\n","import { readdir, stat } from \"node:fs/promises\";\nimport { join } from \"node:path\";\nimport { type SimpleGit, simpleGit } from \"simple-git\";\nimport type { GitSnapshotEvent } from \"../schemas/event.schema.js\";\nimport { findErrorCode } from \"../storage/status.js\";\n\n/**\n * Build a {@link SimpleGit} instance bound to `repoRoot`. Production callers\n * use this single helper so any future tightening (additional safety opts,\n * environment scrubbing, ...) lands in one place. Test fixtures that need\n * `unsafe.allowUnsafeConfigPaths` for isolated `GIT_CONFIG_*` paths build\n * their own SimpleGit locally and intentionally bypass this helper.\n */\nexport function safeSimpleGit(repoRoot: string): SimpleGit {\n return simpleGit({ baseDir: repoRoot });\n}\n\n/**\n * Detect \"git executable not found\" across error wrappers used by simple-git.\n * simple-git surfaces spawn errors as `GitError` instances which discard the\n * original errno `code` property — only the underlying `\"spawn git ENOENT\"`\n * text survives in the message string. We therefore check both the errno\n * `code` (via {@link findErrorCode}) and the message chain.\n */\nexport function isGitNotFound(error: unknown): boolean {\n if (findErrorCode(error, \"ENOENT\")) return true;\n let cur: unknown = error;\n for (let i = 0; i < 4 && cur instanceof Error; i++) {\n if (/\\bENOENT\\b/.test(cur.message)) return true;\n cur = (cur as Error).cause;\n }\n return false;\n}\n\n/**\n * Detect git's canonical \"not a git repository\" failure across simple-git's\n * error wrappers. This is the ONLY git failure that means `cwd` is genuinely\n * outside a repo — every other non-zero exit (a corrupt repo, a permission\n * error, a missing object store) is a real fault that must NOT be laundered\n * into \"Not a git repository\", because that string drives the workspace-view\n * symlink fallback in {@link resolveBasouRepositoryRoot}: misclassifying a\n * broken repo as \"no repo\" would fire the fallback (or print a misleading\n * \"run git init\") instead of surfacing the actual error.\n */\nfunction isNotAGitRepository(error: unknown): boolean {\n let cur: unknown = error;\n for (let i = 0; i < 4 && cur instanceof Error; i++) {\n if (/not a git repository/i.test(cur.message)) return true;\n cur = (cur as Error).cause;\n }\n return false;\n}\n\n/**\n * Payload subset of `git_snapshot` event, mechanically derived from the\n * zod-inferred event type. The wrapping event-shape fields\n * (schema_version, id, session_id, occurred_at, source, type) are added by\n * the caller (session lifecycle in later steps) when constructing the\n * event, so the schema remains the single source of truth.\n *\n * `ahead` / `behind` are omitted when there is no remote or no upstream\n * tracking; the schema declares both as optional non-negative integers.\n */\nexport type GitSnapshot = Omit<\n GitSnapshotEvent,\n \"schema_version\" | \"id\" | \"session_id\" | \"occurred_at\" | \"source\" | \"type\"\n>;\n\n/**\n * Resolve the absolute path of the Git repository root that contains `cwd`.\n * Equivalent to `git rev-parse --show-toplevel`.\n *\n * Throws `Error(\"Git executable not found in PATH. Install git first.\")`\n * with the spawn error attached as `cause` when git itself is missing.\n * Throws `Error(\"Not a git repository\")` (without command-specific suffix)\n * when `cwd` is not inside a repository — callers MAY wrap with their own\n * \"Run 'git init' first, then re-run 'basou XXX'.\" suffix.\n *\n * Pathless contract: the thrown message never embeds `cwd` or any absolute\n * path; native errors are kept on `error.cause` for verbose surfacing.\n */\nexport async function resolveRepositoryRoot(cwd: string): Promise<string> {\n const git = safeSimpleGit(cwd);\n try {\n const root = (await git.revparse([\"--show-toplevel\"])).trimEnd();\n if (root.length === 0) {\n throw new Error(\"Not a git repository\");\n }\n return root;\n } catch (error: unknown) {\n if (isGitNotFound(error)) {\n throw new Error(\"Git executable not found in PATH. Install git first.\", { cause: error });\n }\n if (error instanceof Error && error.message === \"Not a git repository\") {\n throw error; // the empty-root throw above, already in the fixed vocabulary\n }\n if (isNotAGitRepository(error)) {\n throw new Error(\"Not a git repository\", { cause: error });\n }\n // A genuine git failure (corrupt repo, permission denied, broken object\n // store, ...) is NOT \"no repo here\": surface it distinctly so callers do\n // not fall through to the workspace-view fallback or tell the operator to\n // `git init` over a repo that already exists.\n throw new Error(\"Git command failed\", { cause: error });\n }\n}\n\n/**\n * Resolve the repository root that owns the `.basou/` store for `cwd`, with a\n * fallback for agents-workspace \"view\" directories. A workspace view (e.g.\n * `~/projects/foo-workspace`) is intentionally OUTSIDE git and holds no `.basou/`\n * of its own; it aggregates sibling repos through symlinks (`foo-planning ->\n * ../foo-planning`). Running `basou orient` / `refresh` from there would\n * otherwise die with \"Not a git repository\" even though the view IS the\n * operator's daily cwd.\n *\n * Resolution:\n * 1. If `cwd` is inside a git repo, return its toplevel (unchanged behavior).\n * 2. Otherwise inspect `cwd`'s direct symlinks; if exactly one points at a\n * directory that has a `.basou/` store, redirect to that repo (firing\n * `onRedirect`). Zero candidates re-throws the original \"Not a git\n * repository\"; two or more throws an ambiguity error naming them so the\n * operator can `cd` into the right one.\n */\nexport async function resolveBasouRepositoryRoot(\n cwd: string,\n opts?: { onRedirect?: (info: { via: string; root: string }) => void },\n): Promise<string> {\n try {\n return await resolveRepositoryRoot(cwd);\n } catch (error: unknown) {\n if (!(error instanceof Error) || error.message !== \"Not a git repository\") throw error;\n const linked = await findLinkedBasouRepos(cwd);\n const only = linked[0];\n if (only !== undefined && linked.length === 1) {\n opts?.onRedirect?.({ via: only.name, root: only.root });\n return only.root;\n }\n if (linked.length > 1) {\n const names = linked.map((l) => l.name).join(\", \");\n throw new Error(\n `Ambiguous workspace view: ${linked.length} linked repos have a .basou store (${names}). cd into the one you want and re-run.`,\n );\n }\n throw error;\n }\n}\n\n/**\n * Direct children of `dir` that are symlinks to a git repository whose toplevel\n * holds a `.basou/` store — the planning repos a workspace view aggregates.\n * Detection keys off the git TOPLEVEL (where basou's store always lives), not\n * the raw link target, so a link into a subdirectory is not mistaken for a root.\n * Deduped by toplevel keeping the lexicographically-smallest link name (stable\n * `via` across runs), and sorted by name. Best-effort: an unreadable dir, a\n * broken link, or a non-git target yields no candidate.\n */\nasync function findLinkedBasouRepos(dir: string): Promise<{ name: string; root: string }[]> {\n const entries = await readdir(dir, { withFileTypes: true }).catch(() => null);\n if (entries === null) return [];\n const byRoot = new Map<string, string>(); // git toplevel -> chosen link name\n for (const entry of entries) {\n if (!entry.isSymbolicLink()) continue;\n let root: string;\n try {\n root = await resolveRepositoryRoot(join(dir, entry.name));\n } catch {\n continue; // broken link or not a git repo\n }\n try {\n if (!(await stat(join(root, \".basou\"))).isDirectory()) continue;\n } catch {\n continue; // no .basou store at the repo root\n }\n const existing = byRoot.get(root);\n if (existing === undefined || entry.name < existing) byRoot.set(root, entry.name);\n }\n return [...byRoot.entries()]\n .map(([root, name]) => ({ name, root }))\n .sort((a, b) => a.name.localeCompare(b.name));\n}\n\n/**\n * Read `remote.origin.url` from the local repository config. Returns\n * `undefined` if the remote is unset, the value is empty, or the lookup\n * fails for any reason (best-effort).\n *\n * The `--local` scope is critical: callers MUST NOT pick up the developer's\n * global remote.origin.url, which could leak the wrong repository URL into\n * `manifest.yaml`.\n */\nexport async function tryRemoteUrl(repositoryRoot: string): Promise<string | undefined> {\n const git = safeSimpleGit(repositoryRoot);\n try {\n const result = await git.getConfig(\"remote.origin.url\", \"local\");\n const url = (result.value ?? \"\").trimEnd();\n return url.length > 0 ? url : undefined;\n } catch {\n return undefined;\n }\n}\n\n/**\n * Build a {@link GitSnapshot} for the repository at `repositoryRoot`. The\n * caller is responsible for ensuring `repositoryRoot` is the canonical root\n * (typically obtained via {@link resolveRepositoryRoot}); this function\n * verifies repo membership via `git rev-parse --is-inside-work-tree` to\n * distinguish a non-git directory from an empty repository.\n *\n * Edge cases:\n * - **non-git directory**: throws `Error(\"Not a git repository\")`\n * - **empty repo (no commits)**: throws `Error(\"No commits in repository\")`\n * - **detached HEAD**: `branch = \"HEAD\"`, `head = commit hash`,\n * `ahead`/`behind` omitted\n * - **no remote / no upstream tracking**: `ahead`/`behind` omitted\n *\n * Pathless contract preserved on every throw path.\n */\nexport async function getSnapshot(repositoryRoot: string): Promise<GitSnapshot> {\n const git = safeSimpleGit(repositoryRoot);\n\n let inside: boolean;\n try {\n inside = await git.checkIsRepo();\n } catch (error: unknown) {\n if (isGitNotFound(error)) {\n throw new Error(\"Git executable not found in PATH. Install git first.\", { cause: error });\n }\n throw new Error(\"Failed to read git state\", { cause: error });\n }\n if (!inside) {\n throw new Error(\"Not a git repository\");\n }\n\n let head: string;\n try {\n head = (await git.revparse([\"HEAD\"])).trimEnd();\n } catch (error: unknown) {\n if (isGitNotFound(error)) {\n throw new Error(\"Git executable not found in PATH. Install git first.\", { cause: error });\n }\n throw new Error(\"No commits in repository\", { cause: error });\n }\n if (head.length === 0) {\n throw new Error(\"No commits in repository\");\n }\n\n let branch: string;\n try {\n const raw = (await git.raw([\"branch\", \"--show-current\"])).trimEnd();\n branch = raw.length > 0 ? raw : \"HEAD\";\n } catch (error: unknown) {\n throw new Error(\"Failed to read git state\", { cause: error });\n }\n\n let dirty: boolean;\n const staged: string[] = [];\n const unstaged: string[] = [];\n const untracked: string[] = [];\n try {\n const status = await git.status();\n dirty = !status.isClean();\n // Walk status.files so deleted / renamed / conflicted entries are\n // classified correctly (StatusResult's top-level `staged` / `modified`\n // / `not_added` arrays exclude D / R / U entries).\n for (const f of status.files) {\n if (f.index === \"?\" && f.working_dir === \"?\") {\n untracked.push(f.path);\n continue;\n }\n if (f.index !== \" \" && f.index !== \"?\") staged.push(f.path);\n if (f.working_dir !== \" \" && f.working_dir !== \"?\") unstaged.push(f.path);\n }\n } catch (error: unknown) {\n throw new Error(\"Failed to read git state\", { cause: error });\n }\n\n let ahead: number | undefined;\n let behind: number | undefined;\n if (branch !== \"HEAD\") {\n try {\n const upstream = `${branch}@{upstream}`;\n const counts = (\n await git.raw([\"rev-list\", \"--left-right\", \"--count\", `${upstream}...HEAD`])\n ).trim();\n const [behindStr, aheadStr] = counts.split(/\\s+/);\n const parsedBehind = Number.parseInt(behindStr ?? \"\", 10);\n const parsedAhead = Number.parseInt(aheadStr ?? \"\", 10);\n if (Number.isFinite(parsedBehind) && parsedBehind >= 0) behind = parsedBehind;\n if (Number.isFinite(parsedAhead) && parsedAhead >= 0) ahead = parsedAhead;\n } catch {\n // No upstream tracking: leave both undefined; the schema allows omission.\n }\n }\n\n const snapshot: GitSnapshot = {\n head,\n branch,\n dirty,\n staged,\n unstaged,\n untracked,\n ...(ahead !== undefined ? { ahead } : {}),\n ...(behind !== undefined ? { behind } : {}),\n };\n return snapshot;\n}\n","// Namespace import keeps lstat / readFile behind a single binding for the\n// read-side guards. The EACCES test exercises this module via real fs +\n// chmod on the parent directory rather than vi.spyOn, because vi.spyOn\n// cannot redefine ESM module exports under vitest 2.x.\nimport * as fsp from \"node:fs/promises\";\nimport type { Manifest } from \"../schemas/manifest.schema.js\";\nimport { StatusSchema, type StatusSnapshot } from \"../schemas/status.schema.js\";\nimport { atomicReplace } from \"./atomic.js\";\nimport type { BasouPaths } from \"./basou-dir.js\";\n\n/**\n * @internal Compile-time exhaustiveness via Record: every key of\n * `StatusSnapshot[\"directories_present\"]` MUST have a paths accessor here,\n * otherwise the file fails to typecheck. Run-time exhaustiveness is\n * verified by status.test.ts (key-set equality with\n * `StatusSchema.shape.directories_present.shape`). Exported only so the\n * test can perform that equality check; not part of the public API.\n */\nexport const DIRECTORY_CHECKS: Record<\n keyof StatusSnapshot[\"directories_present\"],\n (p: BasouPaths) => string\n> = {\n sessions: (p) => p.sessions,\n tasks: (p) => p.tasks,\n approvals_pending: (p) => p.approvals.pending,\n approvals_resolved: (p) => p.approvals.resolved,\n logs: (p) => p.logs,\n raw: (p) => p.raw,\n tmp: (p) => p.tmp,\n};\n\n/**\n * Refuse to operate on `.basou` if it is a symlink or not a directory. This\n * prevents `writeStatus` from being tricked into writing `status.json`\n * outside the repository root via a swapped `.basou` symlink. Mirrors\n * `ensureBasouDirectory`'s lstat-based guard.\n *\n * If `.basou` is absent the underlying ENOENT is propagated (wrapped) so\n * callers can map it to \"workspace not initialized\" via `findErrorCode`.\n *\n * Note: this is a baseline safety net, not a TOCTOU fix — the directory\n * could still be replaced between this check and the subsequent write. The\n * goal is to detect already-swapped symlinks, not to race-proof the\n * filesystem.\n */\nexport async function assertBasouRootSafe(rootPath: string): Promise<void> {\n let stat: Awaited<ReturnType<typeof fsp.lstat>>;\n try {\n stat = await fsp.lstat(rootPath);\n } catch (error: unknown) {\n if (hasErrorCode(error) && error.code === \"ENOENT\") {\n throw new Error(\"Basou workspace not found\", { cause: error });\n }\n throw new Error(\"Failed to inspect .basou root\", { cause: error });\n }\n if (stat.isSymbolicLink()) {\n throw new Error(\".basou root is a symlink; refusing to operate\");\n }\n if (!stat.isDirectory()) {\n throw new Error(\".basou root exists but is not a directory\");\n }\n}\n\n/**\n * Probe whether `path` is a directory using `lstat` (without following\n * symlinks, so a symlink-to-directory is reported as `false`).\n *\n * Only ENOENT and ENOTDIR are mapped to `false`; permission-style errors\n * (EACCES, EPERM, ...) are re-thrown so a misleading \"not present\" answer\n * is never written into status.json. This keeps the snapshot honest about\n * what was actually observed.\n */\nasync function dirPresent(path: string): Promise<boolean> {\n try {\n return (await fsp.lstat(path)).isDirectory();\n } catch (error: unknown) {\n if (hasErrorCode(error) && (error.code === \"ENOENT\" || error.code === \"ENOTDIR\")) {\n return false;\n }\n throw new Error(\"Failed to inspect .basou subdirectory\", { cause: error });\n }\n}\n\n/**\n * Build a StatusSnapshot from a manifest plus the path layout, observing\n * each subdirectory's presence via `lstat`. Read-only with respect to the\n * workspace state; writes nothing. The result is re-validated by\n * `StatusSchema.parse` before being returned.\n *\n * @param input.now Override for testing; defaults to `new Date()`.\n */\nexport async function buildStatusSnapshot(input: {\n manifest: Manifest;\n paths: BasouPaths;\n now?: Date;\n}): Promise<StatusSnapshot> {\n const { manifest, paths } = input;\n const generatedAt = (input.now ?? new Date()).toISOString();\n\n const entries = Object.entries(DIRECTORY_CHECKS) as Array<\n [keyof StatusSnapshot[\"directories_present\"], (p: BasouPaths) => string]\n >;\n const presence = await Promise.all(\n entries.map(async ([key, get]) => [key, await dirPresent(get(paths))] as const),\n );\n const directoriesEntries = Object.fromEntries(presence) as StatusSnapshot[\"directories_present\"];\n\n const snapshot: StatusSnapshot = {\n schema_version: \"0.1.0\",\n generated_at: generatedAt,\n workspace: {\n id: manifest.workspace.id,\n name: manifest.workspace.name,\n basou_version: manifest.basou_version,\n },\n directories_present: directoriesEntries,\n };\n return StatusSchema.parse(snapshot);\n}\n\n/**\n * Atomically write a StatusSnapshot to `paths.files.status`.\n *\n * Re-validates via `StatusSchema.parse` before any file I/O, so an invalid\n * snapshot throws synchronously and never overwrites the existing\n * `status.json`. Delegates the tmp-file + rename pass to {@link atomicReplace}.\n *\n * **Precondition**: callers MUST invoke {@link assertBasouRootSafe} on\n * `paths.root` first to ensure `.basou` is a real directory and not a\n * swapped symlink. `writeStatus` does not redo this guard — it trusts the\n * caller — so a direct invocation without the guard could write\n * `status.json` outside the repository root.\n */\nexport async function writeStatus(paths: BasouPaths, snapshot: StatusSnapshot): Promise<void> {\n const validated = StatusSchema.parse(snapshot);\n const body = `${JSON.stringify(validated, null, 2)}\\n`;\n try {\n await atomicReplace(paths.files.status, body);\n } catch (error: unknown) {\n throw new Error(\"Failed to write status file\", { cause: error });\n }\n}\n\n/**\n * Read `.basou/status.json` for the current schema_version (0.1.0). This\n * is a cache reader only; cross-version migration is not supported here.\n * Older or newer status.json shapes will fail `StatusSchema.parse` —\n * callers regenerate by calling `buildStatusSnapshot` + `writeStatus`.\n */\nexport async function readStatus(paths: BasouPaths): Promise<StatusSnapshot> {\n let body: string;\n try {\n body = await fsp.readFile(paths.files.status, \"utf8\");\n } catch (error: unknown) {\n if (hasErrorCode(error) && error.code === \"ENOENT\") {\n throw new Error(\"Status file not found\", { cause: error });\n }\n throw new Error(\"Failed to read status file\", { cause: error });\n }\n let parsed: unknown;\n try {\n parsed = JSON.parse(body);\n } catch (error: unknown) {\n throw new Error(\"Failed to parse status JSON\", { cause: error });\n }\n return StatusSchema.parse(parsed);\n}\n\n// Re-exported from lib so existing import paths (`./storage/status.js`,\n// `@basou/core/storage/index.js`, `@basou/core`) continue to resolve while\n// the canonical definition lives in `core/src/lib/error-codes.ts`.\nexport { findErrorCode } from \"../lib/error-codes.js\";\n\nfunction hasErrorCode(error: unknown): error is Error & { code: string } {\n if (!(error instanceof Error)) return false;\n return typeof (error as unknown as Record<string, unknown>).code === \"string\";\n}\n","import { z } from \"zod\";\nimport { IsoTimestampSchema, SchemaVersionSchema, WorkspaceIdSchema } from \"./shared.schema.js\";\n\n/**\n * Schema for `.basou/status.json` — a forward-incompat cache of the current\n * workspace state.\n *\n * Each level uses `.strict()` so unknown keys are rejected rather than\n * silently stripped. A v0.1 reader that encounters a future-shape\n * `status.json` therefore fails parsing instead of returning a partially\n * empty snapshot; callers regenerate by calling `buildStatusSnapshot` +\n * `writeStatus` rather than trying to migrate.\n */\nexport const StatusSchema = z\n .object({\n schema_version: SchemaVersionSchema,\n generated_at: IsoTimestampSchema,\n workspace: z\n .object({\n id: WorkspaceIdSchema,\n name: z.string().min(1),\n basou_version: z.literal(\"0.1.0\"),\n })\n .strict(),\n directories_present: z\n .object({\n sessions: z.boolean(),\n tasks: z.boolean(),\n approvals_pending: z.boolean(),\n approvals_resolved: z.boolean(),\n logs: z.boolean(),\n raw: z.boolean(),\n tmp: z.boolean(),\n })\n .strict(),\n })\n .strict();\n\n/** Inferred runtime type for {@link StatusSchema}. */\nexport type StatusSnapshot = z.infer<typeof StatusSchema>;\n","import type { SimpleGit } from \"simple-git\";\nimport { isGitNotFound, safeSimpleGit } from \"./snapshot.js\";\n\n/**\n * Status classification used by the `file_changed` event schema. Limited to\n * the four classes that simple-git's `git diff --name-status` reliably\n * surfaces; copy / unmerged / typechange entries are intentionally dropped\n * to keep the event payload shape narrow.\n */\nexport type FileChangeStatus = \"added\" | \"modified\" | \"deleted\" | \"renamed\";\n\n/**\n * Single file-level change observed between two refs. `old_path` is set\n * only for `renamed` entries (the previous path of the file).\n */\nexport type FileChange = {\n path: string;\n old_path?: string;\n status: FileChangeStatus;\n};\n\n/**\n * Result of {@link getDiff}. The `changed_files` array is in git's natural\n * `--name-status` order; callers requiring deterministic ordering should\n * sort by `path` themselves.\n */\nexport type DiffResult = {\n changed_files: FileChange[];\n};\n\n/**\n * Compute the file-level diff between two git refs.\n *\n * Returns a list of changed file paths classified by status (added /\n * modified / deleted / renamed). Diff content is intentionally NOT\n * returned — `file_changed` events record paths only, and raw diff bodies\n * are excluded so the trace cannot inadvertently leak source code that may\n * be sensitive. Use `git show <ref>` to obtain the underlying diff.\n *\n * Pathless contract: every thrown message is a fixed string from the set\n * {`Not a git repository`, `Git executable not found in PATH. Install git\n * first.`, `Invalid ref`, `Failed to compute git diff`}; native errors are\n * preserved on `Error.cause`.\n *\n * Special cases:\n * - `baseRef === headRef` short-circuits to an empty result\n * - copy / unmerged / typechange / unknown status codes are skipped\n *\n * @param repoRoot absolute path to the git repository root\n * @param baseRef base ref (e.g. session-start HEAD sha)\n * @param headRef head ref (e.g. session-end HEAD sha)\n */\nexport async function getDiff(\n repoRoot: string,\n baseRef: string,\n headRef: string,\n): Promise<DiffResult> {\n let git: SimpleGit;\n try {\n git = safeSimpleGit(repoRoot);\n } catch (error: unknown) {\n if (isGitNotFound(error)) {\n throw new Error(\"Git executable not found in PATH. Install git first.\", { cause: error });\n }\n throw new Error(\"Not a git repository\", { cause: error });\n }\n\n if (baseRef === headRef) return { changed_files: [] };\n\n let raw: string;\n try {\n raw = await git.raw([\"diff\", \"--name-status\", `${baseRef}..${headRef}`]);\n } catch (error: unknown) {\n if (isGitNotFound(error)) {\n throw new Error(\"Git executable not found in PATH. Install git first.\", { cause: error });\n }\n const message = error instanceof Error ? error.message : \"\";\n if (/not a git repository/i.test(message)) {\n throw new Error(\"Not a git repository\", { cause: error });\n }\n if (\n message.includes(\"bad revision\") ||\n message.includes(\"unknown revision\") ||\n message.includes(\"ambiguous argument\")\n ) {\n throw new Error(\"Invalid ref\", { cause: error });\n }\n throw new Error(\"Failed to compute git diff\", { cause: error });\n }\n\n return { changed_files: parseDiffNameStatus(raw) };\n}\n\nfunction parseDiffNameStatus(raw: string): FileChange[] {\n const lines = raw.split(\"\\n\").filter((l) => l.trim() !== \"\");\n const changes: FileChange[] = [];\n for (const line of lines) {\n const parts = line.split(\"\\t\");\n const code = parts[0];\n if (code === undefined || code.length === 0) continue;\n if (code.startsWith(\"R\") && parts.length >= 3) {\n const newPath = parts[2];\n const oldPath = parts[1];\n if (newPath === undefined) continue;\n changes.push({\n path: newPath,\n status: \"renamed\",\n ...(oldPath !== undefined ? { old_path: oldPath } : {}),\n });\n } else if (code === \"A\" && parts[1]) {\n changes.push({ path: parts[1], status: \"added\" });\n } else if (code === \"M\" && parts[1]) {\n changes.push({ path: parts[1], status: \"modified\" });\n } else if (code === \"D\" && parts[1]) {\n changes.push({ path: parts[1], status: \"deleted\" });\n }\n // C / U / T / X (copy / unmerged / typechange / unknown) are skipped:\n // the file_changed status enum does not cover them in v0.1.\n }\n return changes;\n}\n","import { join } from \"node:path\";\nimport { enumerateApprovals } from \"../approval/approval-store.js\";\nimport { type ReplayWarning, replayEvents } from \"../events/event-replay.js\";\nimport { isTrailingStale, pickLatestSubstantiveEntry } from \"../lib/recency.js\";\nimport type { BasouPaths } from \"../storage/basou-dir.js\";\nimport {\n loadSessionEntries,\n type SessionEntry,\n type SessionSkipReason,\n type SuspectReason,\n} from \"../storage/sessions.js\";\nimport { loadTaskEntries, type TaskDocument, type TaskSkipReason } from \"../storage/tasks.js\";\n\n/** Input contract for {@link renderHandoff}. */\nexport type HandoffRendererInput = {\n paths: BasouPaths;\n /** ISO timestamp embedded in the generated body header. Caller-provided for testability. */\n nowIso: string;\n /** Forwarded to {@link replayEvents} / {@link loadSessionEntries} per session. */\n onWarning?: (warning: ReplayWarning, sessionId: string) => void;\n /**\n * Per-session degradation reasons (missing/invalid session.yaml or\n * unreadable events.jsonl). The CLI maps `events_jsonl_unreadable` to the\n * existing suspect-check stderr wording to keep the user-facing surface\n * consistent with `basou session list`.\n */\n onSessionSkip?: (sessionId: string, reason: SessionSkipReason) => void;\n /**\n * Per-task degradation reasons (invalid front matter / unreadable file).\n * Surfaced so the CLI can warn the operator about a malformed task.md\n * without aborting the handoff render.\n */\n onTaskSkip?: (taskId: string, reason: TaskSkipReason) => void;\n /** Maximum related_files entries to display before `... +N more`. Default 20. */\n relatedFilesLimit?: number;\n};\n\nexport type HandoffRendererResult = {\n /** Generated body WITHOUT BASOU:GENERATED markers (markdown-store wraps them). */\n body: string;\n sessionCount: number;\n decisionCount: number;\n pendingApprovalsCount: number;\n suspectCount: number;\n /** Total number of task.md files successfully loaded. */\n taskCount: number;\n /** Tasks whose status is `planned` or `in_progress` (= shown in 次に実行すべき作業). */\n pendingTaskCount: number;\n};\n\ntype DecisionRecord = {\n decisionId: string;\n title: string;\n occurredAt: string;\n sessionId: string;\n};\n\n// An open (non-voided) `kind: \"track\"` decision — a strategic, unfinished\n// direction the handoff resurfaces until it is closed via `decision void`.\n// Mirrors the orientation renderer so the two outputs agree.\ntype TrackRecord = {\n decisionId: string;\n title: string;\n rationale: string | null;\n occurredAt: string;\n sessionId: string;\n};\n\ntype TaskCreatedRecord = {\n taskId: string;\n title: string;\n occurredAt: string;\n sessionId: string;\n};\n\ntype TaskStatusChangedRecord = {\n taskId: string;\n occurredAt: string;\n sessionId: string;\n};\n\n/**\n * Render the body of `handoff.md` from the current workspace state.\n *\n * The renderer is a pure function (no I/O beyond {@link replayEvents} /\n * {@link loadSessionEntries} / {@link enumerateApprovals}). It assembles the\n * the spec's `handoff.md` sections in order:\n *\n * 1. `現在の状態`: latest live session (status not archived, source not import).\n * 2. `直近の変更ファイル`: the most recent session's `related_files`, dedup +\n * sorted asc + truncated to `relatedFilesLimit` (default 20).\n * 3. `直近の判断`: latest `decision_recorded` event (chronological).\n * 4. `未決事項`: pending-approval count + suspect-session count.\n * 5. `次に読むべきファイル`: `.basou/decisions.md` + top-3 related files\n * (the same `displayedFiles` source is intentionally reused in two\n * sections — overview vs. resume context).\n * 6. `次に実行すべき作業`: placeholder until task events land.\n * 7. `セッション一覧`: all sessions newest first with inline suspect labels.\n *\n * Session enumeration goes through {@link loadSessionEntries} so the set of\n * sessions whose `decision_recorded` events we replay matches the\n * decisions renderer.\n */\nexport async function renderHandoff(input: HandoffRendererInput): Promise<HandoffRendererResult> {\n const limit = input.relatedFilesLimit ?? 20;\n const now = new Date(input.nowIso);\n // Wrap the caller's onSkip so we can detect whether loadSessionEntries'\n // suspect pass already emitted `events_jsonl_unreadable` for a session\n // For non-running sessions the suspect pass does not\n // touch events.jsonl, so the second replay below may be the first to\n // hit the unreadable file — without this bookkeeping that error would\n // be silently swallowed.\n const unreadableEmitted = new Set<string>();\n const wrappedSkip: (sid: string, reason: SessionSkipReason) => void = (sid, reason) => {\n if (reason === \"events_jsonl_unreadable\") unreadableEmitted.add(sid);\n input.onSessionSkip?.(sid, reason);\n };\n // `exactOptionalPropertyTypes` forbids passing literal `undefined` for an\n // optional property, so build the options object conditionally.\n const loadOpts: Parameters<typeof loadSessionEntries>[1] = { now, onSkip: wrappedSkip };\n if (input.onWarning !== undefined) loadOpts.onWarning = input.onWarning;\n const entries = await loadSessionEntries(input.paths, loadOpts);\n\n const decisions: DecisionRecord[] = [];\n // `kind: \"track\"` decisions (a strategic, unfinished direction); the open\n // subset (minus voided) is surfaced as 未完トラック and resurfaces until closed.\n const tracks: TrackRecord[] = [];\n // decision_ids marked no longer in force; the 直近の判断 pointer skips them so\n // a voided decision is never surfaced as current (mirrors the orientation\n // renderer, keeping the two outputs in agreement).\n const voidedDecisionIds = new Set<string>();\n const tasksCreated: TaskCreatedRecord[] = [];\n const tasksStatusChanged: TaskStatusChangedRecord[] = [];\n // Activity tail over NON-archived sessions = max of the session boundary\n // (ended_at ?? started_at) AND every event's occurred_at. Mirrors the\n // orientation renderer so the 直近の判断 staleness note fires identically (a\n // decision that real work continued past is not presented as current).\n let latestActivityAt: string | null = null;\n const noteActivity = (iso: string): void => {\n if (latestActivityAt === null || Date.parse(iso) > Date.parse(latestActivityAt)) {\n latestActivityAt = iso;\n }\n };\n for (const entry of entries) {\n const sessionDir = join(input.paths.sessions, entry.sessionId);\n const counted = entry.session.session.status !== \"archived\";\n if (counted) noteActivity(entry.session.session.ended_at ?? entry.session.session.started_at);\n try {\n for await (const ev of replayEvents(sessionDir, {\n onWarning: (w) => input.onWarning?.(w, entry.sessionId),\n })) {\n if (counted) noteActivity(ev.occurred_at);\n if (ev.type === \"decision_recorded\") {\n decisions.push({\n decisionId: ev.decision_id,\n title: ev.title,\n occurredAt: ev.occurred_at,\n sessionId: entry.sessionId,\n });\n if (ev.kind === \"track\") {\n tracks.push({\n decisionId: ev.decision_id,\n title: ev.title,\n rationale: ev.rationale ?? null,\n occurredAt: ev.occurred_at,\n sessionId: entry.sessionId,\n });\n }\n } else if (ev.type === \"decision_voided\") {\n voidedDecisionIds.add(ev.decision_id);\n } else if (ev.type === \"task_created\") {\n tasksCreated.push({\n taskId: ev.task_id,\n title: ev.title,\n occurredAt: ev.occurred_at,\n sessionId: entry.sessionId,\n });\n } else if (ev.type === \"task_status_changed\") {\n tasksStatusChanged.push({\n taskId: ev.task_id,\n occurredAt: ev.occurred_at,\n sessionId: entry.sessionId,\n });\n }\n }\n } catch {\n // events.jsonl unreadable on the decision-aggregation pass. If the\n // suspect pass has not already surfaced a warning for this session\n // (e.g. completed session, where classifySuspect short-circuits\n // before reading events.jsonl), emit the skip now so the operator\n // is not left wondering why a decision is missing.\n if (!unreadableEmitted.has(entry.sessionId)) {\n wrappedSkip(entry.sessionId, \"events_jsonl_unreadable\");\n }\n }\n }\n decisions.sort((a, b) => {\n const c = Date.parse(a.occurredAt) - Date.parse(b.occurredAt);\n return c !== 0 ? c : a.decisionId.localeCompare(b.decisionId);\n });\n // Newest decision NOT voided — the 直近の判断 pointer. A voided decision stays\n // in decisions.md (struck) but must not pose as the current direction.\n let latestDecision: DecisionRecord | undefined;\n for (let i = decisions.length - 1; i >= 0; i -= 1) {\n const d = decisions[i];\n if (d !== undefined && !voidedDecisionIds.has(d.decisionId)) {\n latestDecision = d;\n break;\n }\n }\n // Open tracks: non-voided `kind: \"track\"` decisions, newest first. Mirrors the\n // orientation renderer so handoff and orient surface the same strategic\n // continuation.\n const openTracks: TrackRecord[] = tracks\n .filter((t) => !voidedDecisionIds.has(t.decisionId))\n .sort((a, b) => {\n const c = Date.parse(b.occurredAt) - Date.parse(a.occurredAt);\n return c !== 0 ? c : b.decisionId.localeCompare(a.decisionId);\n });\n tasksCreated.sort((a, b) => {\n const c = Date.parse(a.occurredAt) - Date.parse(b.occurredAt);\n return c !== 0 ? c : a.taskId.localeCompare(b.taskId);\n });\n tasksStatusChanged.sort((a, b) => {\n const c = Date.parse(a.occurredAt) - Date.parse(b.occurredAt);\n return c !== 0 ? c : a.taskId.localeCompare(b.taskId);\n });\n\n const taskLoadOpts: Parameters<typeof loadTaskEntries>[1] = {};\n if (input.onTaskSkip !== undefined) taskLoadOpts.onSkip = input.onTaskSkip;\n const taskEntries = await loadTaskEntries(input.paths, taskLoadOpts);\n const taskById = new Map<string, TaskDocument>();\n for (const t of taskEntries) taskById.set(t.task.task.id, t);\n\n // Latest activity = most recent task_status_changed, falling back to the\n // most recent task_created when no status change has been recorded yet.\n // This surfaces \"the task whose status most recently changed (including\n // done)\" instead of \"the most recently created task\", so a task that just\n // transitioned to done is no longer hidden from the handoff.\n const latestStatusChange = tasksStatusChanged[tasksStatusChanged.length - 1];\n const latestCreatedRecord = tasksCreated[tasksCreated.length - 1];\n const latestActivityTaskId = latestStatusChange?.taskId ?? latestCreatedRecord?.taskId;\n const latestActivityTitle =\n latestActivityTaskId !== undefined\n ? (tasksCreated.find((t) => t.taskId === latestActivityTaskId)?.title ?? \"(title unknown)\")\n : undefined;\n const latestActivityRecord =\n latestActivityTaskId !== undefined && latestActivityTitle !== undefined\n ? { taskId: latestActivityTaskId, title: latestActivityTitle }\n : undefined;\n const latestTaskDoc =\n latestActivityRecord !== undefined ? taskById.get(latestActivityRecord.taskId) : undefined;\n const pendingTasks = taskEntries.filter(\n (t) => t.task.task.status === \"planned\" || t.task.task.status === \"in_progress\",\n );\n\n const approvals = await enumerateApprovals(input.paths);\n const pendingApprovalsCount = approvals.pending.length;\n\n const liveEntries = entries.filter(\n (e) => e.session.session.status !== \"archived\" && e.session.session.source.kind !== \"import\",\n );\n // Represent 最終 session with the most recent SUBSTANTIVE session, not a bare\n // resume/refresh session (e.g. 1 command, 0 files) that merely happens to be\n // newest — the latter hides the real-work session and disagrees with 直近の判断.\n const latestSession = pickLatestSubstantiveEntry(liveEntries);\n\n // 「直近の変更ファイル」 shows the files touched by the most recent SUBSTANTIVE\n // session — the same session surfaced as 最終 session above — so the section\n // reflects the latest real activity rather than the whole history. (A bare\n // resume session has no related_files anyway, so following 最終 session here\n // shows the substantive work's files instead of an empty list.) Unioning every\n // session's related_files turned this into a whole-history dump once transcript\n // imports became the primary source, since each import carries a full day of\n // file changes.\n const latestFiles = latestSession?.session.session.related_files ?? [];\n const sortedFiles = [...new Set(latestFiles)].sort();\n const displayedFiles = sortedFiles.slice(0, limit);\n const overflow = Math.max(0, sortedFiles.length - limit);\n\n const suspectCount = entries.filter((e) => e.suspect).length;\n\n const firstEntry = entries[0];\n const lastEntry = entries[entries.length - 1];\n const sessionRange =\n firstEntry !== undefined && lastEntry !== undefined\n ? `${shortIdWithPrefix(firstEntry.sessionId)}..${shortIdWithPrefix(lastEntry.sessionId)}`\n : \"\";\n\n const body = formatHandoffBody({\n nowIso: input.nowIso,\n sessionRange,\n sessionCount: entries.length,\n latestSession,\n latestActivityAt,\n decisions,\n latestDecision,\n openTracks,\n pendingApprovalsCount,\n suspectCount,\n displayedFiles,\n overflow,\n entries,\n latestActivityRecord,\n latestTaskDoc,\n pendingTasks,\n totalTaskCount: taskEntries.length,\n });\n\n return {\n body,\n sessionCount: entries.length,\n decisionCount: decisions.length,\n pendingApprovalsCount,\n suspectCount,\n taskCount: taskEntries.length,\n pendingTaskCount: pendingTasks.length,\n };\n}\n\nfunction formatHandoffBody(args: {\n nowIso: string;\n sessionRange: string;\n sessionCount: number;\n latestSession: SessionEntry | undefined;\n latestActivityAt: string | null;\n decisions: ReadonlyArray<DecisionRecord>;\n latestDecision: DecisionRecord | undefined;\n openTracks: ReadonlyArray<TrackRecord>;\n pendingApprovalsCount: number;\n suspectCount: number;\n displayedFiles: ReadonlyArray<string>;\n overflow: number;\n entries: ReadonlyArray<SessionEntry>;\n latestActivityRecord: { taskId: string; title: string } | undefined;\n latestTaskDoc: TaskDocument | undefined;\n pendingTasks: ReadonlyArray<TaskDocument>;\n totalTaskCount: number;\n}): string {\n const lines: string[] = [];\n lines.push(\"# Handoff\");\n lines.push(\"\");\n if (args.sessionRange !== \"\") {\n lines.push(`> Generated at ${args.nowIso} from ${args.sessionRange}`);\n } else {\n lines.push(`> Generated at ${args.nowIso}`);\n }\n lines.push(\"\");\n\n // 現在の状態\n lines.push(\"## 現在の状態\");\n lines.push(\"\");\n if (args.latestSession !== undefined) {\n const status = args.latestSession.session.session.status;\n const label = args.latestSession.session.session.label;\n const shortId = shortIdWithPrefix(args.latestSession.sessionId);\n // Lead with the human-readable label; the raw id is demoted to a trailing\n // [short id]. When the session has no label the short id is the only handle\n // available, so it becomes the primary text and the bracket is dropped to\n // avoid repeating it.\n if (label !== undefined && label !== \"\") {\n lines.push(`- 最終 session: ${label} (${status}) [${shortId}]`);\n } else {\n lines.push(`- 最終 session: ${shortId} (${status})`);\n }\n } else {\n lines.push(\"- 最終 session: (no live sessions)\");\n }\n if (args.latestActivityRecord !== undefined) {\n // Status comes from task.md when available. If the task_created event\n // exists but task.md is missing / invalid we MUST NOT fabricate\n // \"planned\" — events alone cannot restore the initial status and\n // operators would miss an unsafe-state reconcile.\n const statusLabel =\n args.latestTaskDoc !== undefined\n ? args.latestTaskDoc.task.task.status\n : \"status unknown — task.md missing or invalid\";\n // Surface linked_sessions cardinality inside the status parenthetical when\n // the latest task spans more than one session. Suppressed when the\n // task is single-session (the common case) or when task.md is\n // unavailable, keeping single-session output visually quiet.\n const linkedCount = args.latestTaskDoc?.task.task.linked_sessions?.length;\n const linkedSuffix =\n linkedCount !== undefined && linkedCount > 1 ? `, linked_sessions: ${linkedCount}` : \"\";\n // Lead with the task title; the raw id is demoted to a trailing [short id]\n // and linked_sessions rides alongside the status.\n lines.push(\n `- 最終 task: ${args.latestActivityRecord.title} (${statusLabel}${linkedSuffix}) [${shortIdWithPrefix(args.latestActivityRecord.taskId)}]`,\n );\n } else {\n lines.push(\"- 最終 task: (no tasks recorded yet)\");\n }\n lines.push(\"\");\n\n // 直近の変更ファイル\n lines.push(\"## 直近の変更ファイル\");\n lines.push(\"\");\n if (args.displayedFiles.length === 0) {\n lines.push(\"(no related files recorded)\");\n } else {\n for (const f of args.displayedFiles) lines.push(`- ${f}`);\n if (args.overflow > 0) lines.push(`- ... +${args.overflow} more`);\n }\n lines.push(\"\");\n\n // 直近の判断\n lines.push(\"## 直近の判断\");\n lines.push(\"\");\n if (args.latestDecision === undefined) {\n // Either no decisions, or every recorded decision has been voided — in\n // both cases there is no current recorded direction to surface.\n lines.push(\"(no decisions recorded yet)\");\n } else {\n const last = args.latestDecision;\n // Lead with the decision title; the raw id is demoted to a trailing\n // [short id].\n lines.push(`- ${last.title} [${shortIdWithPrefix(last.decisionId)}]`);\n // Staleness caveat (mirrors orientation): when real work continued well past\n // this decision, it may already be resolved/executed — do not let a resume\n // treat it as the current next step. handoff had no such note before, so a\n // stale recorded decision posed unguarded as \"直近の判断\".\n if (args.latestActivityAt !== null && isTrailingStale(args.latestActivityAt, last.occurredAt)) {\n lines.push(\n \" - 注: 最終活動はこの判断より後です。会話で既に解決済みの可能性があるため、再開前に継続点を確認してください(会話での意思決定は自動記録されません。`basou decision capture` で記録できます)。\",\n );\n }\n // When the latest decision is from a DIFFERENT session than 最終 session, the\n // two \"latest\" pointers disagree; surface it so the timeline is unambiguous.\n if (args.latestSession !== undefined && last.sessionId !== args.latestSession.sessionId) {\n lines.push(\n ` - 注: この判断は最終 session とは別の session [${shortIdWithPrefix(last.sessionId)}] のものです。`,\n );\n }\n lines.push(\"\");\n lines.push(`(${args.decisions.length} decisions total — see decisions.md)`);\n }\n lines.push(\"\");\n\n // 未完トラック — open strategic directions that resurface until closed. Placed\n // right after 直近の判断 (both decision-derived) and ahead of the mechanical\n // task list: an open track is the strongest \"where to resume\" signal, carrying\n // the next essential direction + why across the session boundary. Mirrors the\n // orientation renderer's forward section. Omitted entirely when none are open.\n if (args.openTracks.length > 0) {\n const TRACK_DISPLAY_LIMIT = 10;\n const shown = args.openTracks.slice(0, TRACK_DISPLAY_LIMIT);\n const overflow = args.openTracks.length - shown.length;\n lines.push(\"## 未完トラック (close まで継続表示)\");\n lines.push(\"\");\n for (const t of shown) {\n lines.push(`- ${t.title} [${shortIdWithPrefix(t.decisionId)}]`);\n if (t.rationale !== null && t.rationale.trim() !== \"\") {\n lines.push(` - 理由: ${handoffRationale(t.rationale)}`);\n }\n }\n if (overflow > 0) lines.push(`- ... +${overflow} more (see decisions.md)`);\n lines.push(\"\");\n lines.push(\"完了したら `basou decision void <decision_id>` で閉じてください。\");\n lines.push(\"\");\n }\n\n // 未決事項\n lines.push(\"## 未決事項\");\n lines.push(\"\");\n if (args.pendingApprovalsCount > 0) {\n lines.push(`- ${args.pendingApprovalsCount} pending approvals`);\n }\n if (args.suspectCount > 0) {\n lines.push(`- ${args.suspectCount} suspect sessions detected`);\n }\n if (args.pendingApprovalsCount === 0 && args.suspectCount === 0) {\n lines.push(\"(none)\");\n }\n lines.push(\"\");\n\n // 次に読むべきファイル\n // Drop self-reference to handoff.md, include `.basou/decisions.md` + the\n // top-3 of `displayedFiles` so the section points to concrete files. The\n // same `displayedFiles` source is reused intentionally (overview vs.\n // resume context).\n lines.push(\"## 次に読むべきファイル\");\n lines.push(\"\");\n lines.push(\"- .basou/decisions.md\");\n for (const f of args.displayedFiles.slice(0, 3)) lines.push(`- ${f}`);\n lines.push(\"\");\n\n // 次に実行すべき作業\n lines.push(\"## 次に実行すべき作業\");\n lines.push(\"\");\n if (args.pendingTasks.length === 0) {\n lines.push(\"(no pending tasks)\");\n } else {\n for (const t of args.pendingTasks) {\n // Lead with the task title; the raw id is demoted to a trailing [short id].\n lines.push(\n `- ${t.task.task.title} (${t.task.task.status}) [${shortIdWithPrefix(t.task.task.id)}]`,\n );\n }\n }\n lines.push(\"\");\n\n // セッション一覧 — the main table lists the operator's own sessions newest\n // first. This deliberately includes `claude-code-import` sessions: a\n // transcript captured after the fact via `basou import claude-code` is still\n // the operator's own work, so it belongs here rather than below. The separate\n // 「Imported sessions」 sub-section holds ONLY cross-workspace round-trips\n // brought in via `basou session import` (source.kind === \"import\"), so it is\n // absent whenever there are none. The \"(no sessions yet)\" placeholder fires\n // only when the workspace is completely empty; \"(no live sessions; …)\" fires\n // when every session is such a round-trip import.\n const liveTableEntries = args.entries.filter((e) => e.session.session.source.kind !== \"import\");\n const importedTableEntries = args.entries.filter(\n (e) => e.session.session.source.kind === \"import\",\n );\n lines.push(\"## セッション一覧\");\n lines.push(\"\");\n if (args.entries.length === 0) {\n lines.push(\"(no sessions yet)\");\n } else if (liveTableEntries.length === 0) {\n lines.push(\"(no live sessions; see Imported sessions below)\");\n } else {\n lines.push(\"| short_id | status | started_at | label |\");\n lines.push(\"|---|---|---|---|\");\n for (const e of [...liveTableEntries].reverse()) {\n const sid = shortHandoffId(e.sessionId);\n const status = e.session.session.status + suspectLabel(e.suspectReason);\n const startedAt = e.session.session.started_at;\n const label = e.session.session.label ?? \"\";\n lines.push(`| ${sid} | ${status} | ${startedAt} | ${label} |`);\n }\n }\n if (importedTableEntries.length > 0) {\n lines.push(\"\");\n lines.push(\"### Imported sessions\");\n lines.push(\"\");\n lines.push(\"| short_id | status | started_at | label |\");\n lines.push(\"|---|---|---|---|\");\n for (const e of [...importedTableEntries].reverse()) {\n const sid = shortHandoffId(e.sessionId);\n const status = e.session.session.status + suspectLabel(e.suspectReason);\n const startedAt = e.session.session.started_at;\n const label = e.session.session.label ?? \"\";\n lines.push(`| ${sid} | ${status} | ${startedAt} | ${label} |`);\n }\n }\n lines.push(\"\");\n // Session-status breakdown: surface completed / failed / running counts\n // alongside the total so an at-a-glance read distinguishes \"ten sessions,\n // all done\" from \"ten sessions, three still failing\". Order is fixed\n // (completed first since handoff is read after the work) and zero-count\n // statuses are omitted. When the workspace is empty the breakdown\n // parenthetical is suppressed entirely so the existing terse line stays.\n const statusCounts = new Map<string, number>();\n for (const e of args.entries) {\n const s = e.session.session.status;\n statusCounts.set(s, (statusCounts.get(s) ?? 0) + 1);\n }\n const orderedStatuses = [\n \"completed\",\n \"failed\",\n \"running\",\n \"interrupted\",\n \"waiting_approval\",\n \"initialized\",\n \"imported\",\n ] as const;\n const breakdown = orderedStatuses\n .filter((s) => (statusCounts.get(s) ?? 0) > 0)\n .map((s) => `${s} ${statusCounts.get(s)}`)\n .join(\", \");\n const sessionsLine =\n breakdown !== \"\"\n ? `Sessions: ${args.sessionCount} (${breakdown}). Tasks: ${args.totalTaskCount}.`\n : `Sessions: ${args.sessionCount}. Tasks: ${args.totalTaskCount}.`;\n lines.push(sessionsLine);\n\n return lines.join(\"\\n\");\n}\n\n// A track's rationale (the WHY) can be multi-line and long; collapse whitespace\n// to one line and cap it so the handoff stays scannable. The full text lives in\n// the decision_recorded event (see decisions.md).\nconst HANDOFF_TRACK_RATIONALE_MAX = 240;\nfunction handoffRationale(rationale: string): string {\n const oneLine = rationale.replace(/\\s+/g, \" \").trim();\n return oneLine.length > HANDOFF_TRACK_RATIONALE_MAX\n ? `${oneLine.slice(0, HANDOFF_TRACK_RATIONALE_MAX - 1)}…`\n : oneLine;\n}\n\nfunction suspectLabel(reason: SuspectReason | null): string {\n if (reason === \"events_say_ended_but_yaml_running\") return \" ⚠ ended (yaml stale)\";\n if (reason === \"running_no_end_event\") return \" ⚠ no end event\";\n return \"\";\n}\n\n// First 10 chars after the `ses_` prefix. Matches the truncation that\n// `basou session list` uses for its shortest display column.\nfunction shortHandoffId(sessionId: string): string {\n const SES = \"ses_\";\n if (sessionId.startsWith(SES)) return sessionId.slice(SES.length, SES.length + 10);\n return sessionId.slice(0, 10);\n}\n\n// Prose-line short id: keeps the type prefix (`ses_` / `task_` / `decision_`)\n// and truncates the ULID body to its first 10 chars, e.g.\n// `task_01KRNHYRS91F5GBX2VTN9ADJFV` -> `task_01KRNHYRS9`. Unlike the session\n// table — whose column header already marks the column as ids — body lines mix\n// session / task / decision ids inline, so the prefix is kept to keep each id\n// self-describing while still demoting it behind the human-readable text.\nfunction shortIdWithPrefix(id: string): string {\n const sep = id.indexOf(\"_\");\n if (sep === -1) return id.slice(0, 10);\n return id.slice(0, sep + 1) + id.slice(sep + 1, sep + 1 + 10);\n}\n","/**\n * Shared \"resume coherence\" helpers for the orientation and handoff renderers,\n * so both judge staleness and pick the representative session identically.\n *\n * These exist because a resume (\"basou refresh して続きを再開\") must not present\n * a stale recorded decision as the current direction, nor represent the latest\n * work with an essentially empty session — the two failure modes that let an\n * agent re-attempt already-completed work on resume.\n */\n\n/**\n * A recorded decision / next-step note is \"trailing\" when captured activity\n * continued for more than this gap after it. Decisions are recorded only from\n * AskUserQuestion tool calls, `basou decision record`, or `basou decision\n * capture` — free-form conversational decisions are not auto-captured — so a\n * long trailing gap means the operator's current direction may simply be\n * unrecorded. 1h is a deliberately conservative threshold so a decision made\n * near a session's end does not trigger the note.\n */\nexport const DECISION_TRAILING_ACTIVITY_GAP_MS = 60 * 60 * 1000;\n\n/**\n * True when captured activity continued more than\n * {@link DECISION_TRAILING_ACTIVITY_GAP_MS} after `recordedAt`. Used to decide\n * whether a recorded decision / note should carry a staleness caveat instead of\n * being presented as the current direction. `latestActivityAt === null` (no\n * activity tail) is never stale.\n */\nexport function isTrailingStale(latestActivityAt: string | null, recordedAt: string): boolean {\n if (latestActivityAt === null) return false;\n return Date.parse(latestActivityAt) - Date.parse(recordedAt) > DECISION_TRAILING_ACTIVITY_GAP_MS;\n}\n\n/** Minimal shape needed to rank a session for \"representative latest session\". */\ntype RankableSessionEntry = {\n session: { session: { started_at: string; related_files?: readonly string[] } };\n};\n\n/**\n * Pick the session that should represent \"最終 session\" / latest work.\n *\n * A bare resume/refresh session (e.g. 1 command, 0 files) is the most RECENT\n * session but the least informative; selecting it hides the real-work session\n * and makes the latest-session and latest-decision pointers disagree. So rank a\n * session that touched files ahead of one that did not, then break ties by\n * recency (started_at). The result is the most recent SUBSTANTIVE session,\n * falling back to the most recent session overall when none touched files.\n *\n * Returns `undefined` for an empty list. Does not mutate the input.\n */\nexport function pickLatestSubstantiveEntry<E extends RankableSessionEntry>(\n entries: readonly E[],\n): E | undefined {\n return [...entries].sort((a, b) => {\n const aSubstantive = (a.session.session.related_files?.length ?? 0) > 0 ? 1 : 0;\n const bSubstantive = (b.session.session.related_files?.length ?? 0) > 0 ? 1 : 0;\n if (aSubstantive !== bSubstantive) return bSubstantive - aSubstantive;\n return Date.parse(b.session.session.started_at) - Date.parse(a.session.session.started_at);\n })[0];\n}\n","import { createHash } from \"node:crypto\";\nimport { mkdir, readdir, readFile, rename, stat, unlink } from \"node:fs/promises\";\nimport { join } from \"node:path\";\nimport { parse as parseYaml, stringify as stringifyYaml } from \"yaml\";\nimport { z } from \"zod\";\nimport type { PrefixedId } from \"../ids/ulid.js\";\nimport { findErrorCode } from \"../lib/error-codes.js\";\nimport type { Event } from \"../schemas/event.schema.js\";\nimport type { Manifest } from \"../schemas/manifest.schema.js\";\nimport type { SessionStatus } from \"../schemas/session.schema.js\";\nimport { IsoTimestampSchema, SessionIdSchema, TaskIdSchema } from \"../schemas/shared.schema.js\";\nimport {\n type Task,\n TaskSchema,\n type TaskStatus,\n TaskStatusSchema,\n} from \"../schemas/task.schema.js\";\nimport type { TaskIndexEntry } from \"../schemas/task-index.schema.js\";\nimport {\n type AttachableStatus,\n appendEventToExistingSession,\n createAdHocSessionWithEvent,\n FailedToFinalizeError,\n} from \"./ad-hoc-session.js\";\nimport { atomicCreate, atomicReplace } from \"./atomic.js\";\nimport type { BasouPaths } from \"./basou-dir.js\";\nimport { acquireLock } from \"./lockfile.js\";\nimport { enumerateSessionDirs, readSessionYaml } from \"./sessions.js\";\nimport { readTaskIndex, rebuildTaskIndex, updateTaskIndex } from \"./task-index.js\";\nimport { overwriteYamlFile } from \"./yaml-store.js\";\n\n// ============================================================================\n// File format constants\n// ============================================================================\n\nconst FRONT_MATTER_DELIM = \"---\";\n// Raised from the original 40-char cap to 80 chars so long task /\n// reconcile titles retain their core information. The same cap applies\n// to `Ad-hoc task:`, `Ad-hoc task status:`, and `Ad-hoc task reconcile:`\n// labels so the three ad-hoc label generators stay consistent with the\n// decision-side cap (cli/src/commands/decision.ts).\nconst LABEL_TITLE_MAX = 80;\nconst LABEL_TRUNCATE_HEAD = LABEL_TITLE_MAX - 3;\n\nconst DEFAULT_ATTACHABLE_STATUSES: ReadonlySet<AttachableStatus> = new Set<AttachableStatus>([\n \"initialized\",\n \"running\",\n \"waiting_approval\",\n]);\n\n// Boundary parses for direct callers so a malformed task cannot smuggle\n// past the CLI-side parsers and commit a `task_created` event. The set\n// originally rejected `done` / `cancelled` as initial values, but the\n// orchestrator now emits a follow-up `task_status_changed` for terminal\n// initial statuses so retroactively-recorded completed tasks can be\n// entered in one CLI call; widening the schema lets that path through.\nconst InitialTaskStatusSchema = TaskStatusSchema;\nconst TaskTitleSchema = z.string().min(1);\nconst TaskLabelSchema = z.string().min(1);\n// `completedAt` is an optional ISO-8601 string. Validate it at the boundary\n// so a direct (non-CLI) caller cannot smuggle a garbage timestamp past the\n// orchestrator and leave durable `task_created` / `task_status_changed`\n// events with no valid task.md to back them up.\nconst CompletedAtSchema = IsoTimestampSchema;\n\nconst TERMINAL_TASK_STATUSES: ReadonlySet<TaskStatus> = new Set<TaskStatus>([\"done\", \"cancelled\"]);\n\nfunction isTerminalTaskStatus(status: TaskStatus): boolean {\n return TERMINAL_TASK_STATUSES.has(status);\n}\n\n// ============================================================================\n// File read / parse\n// ============================================================================\n\nexport type TaskDocument = {\n /** Parsed + zod-validated front matter. */\n task: Task;\n /** Raw markdown body after the closing front matter delimiter. */\n body: string;\n};\n\n/**\n * Split a task.md file body into the YAML front matter and the trailing\n * markdown body. The expected format is:\n *\n * ---\\n\n * <yaml>\\n\n * ---\\n\n * <body>\n *\n * Strict rules:\n * - A UTF-8 BOM at the head is rejected.\n * - CRLF inside the file is normalised to LF before delimiter scanning so\n * editors that auto-convert line endings stay compatible.\n * - The closing delimiter is the FIRST `---` line after the opening one,\n * so `---` lines inside the markdown body do not confuse the parser.\n */\nfunction splitFrontMatter(raw: string): { yamlText: string; body: string } {\n if (raw.length > 0 && raw.charCodeAt(0) === 0xfeff) {\n throw new Error(\"Invalid task file format\");\n }\n const normalised = raw.replace(/\\r\\n/g, \"\\n\");\n if (!normalised.startsWith(`${FRONT_MATTER_DELIM}\\n`)) {\n throw new Error(\"Invalid task file format\");\n }\n const remainder = normalised.slice(FRONT_MATTER_DELIM.length + 1);\n // Find the first line that is exactly `---`. Scan line-by-line so a `---`\n // appearing mid-line inside YAML text is not matched.\n const lines = remainder.split(\"\\n\");\n let closingIdx = -1;\n for (let i = 0; i < lines.length; i++) {\n if (lines[i] === FRONT_MATTER_DELIM) {\n closingIdx = i;\n break;\n }\n }\n if (closingIdx < 0) {\n throw new Error(\"Invalid task file format\");\n }\n const yamlText = lines.slice(0, closingIdx).join(\"\\n\");\n // The body is everything after the closing delimiter line, with one\n // separating newline consumed (if present) so the body does not start\n // with a stray blank line.\n const afterClosing = lines.slice(closingIdx + 1);\n let body = afterClosing.join(\"\\n\");\n if (body.startsWith(\"\\n\")) body = body.slice(1);\n return { yamlText, body };\n}\n\n/**\n * Read and validate `<paths.tasks>/<taskId>.md`. Returns the parsed front\n * matter (Task) plus the markdown body string. Error contract:\n *\n * - ENOENT → throw `\"Task file not found\"`.\n * - format violation → throw `\"Invalid task file format\"`.\n * - YAML parse / schema violation → throw `\"Failed to read task file\"`.\n * - any other I/O failure → throw `\"Failed to read task file\"` with cause.\n */\nexport async function readTaskFile(paths: BasouPaths, taskId: string): Promise<TaskDocument> {\n const filePath = join(paths.tasks, `${taskId}.md`);\n let raw: string;\n try {\n raw = await readFile(filePath, \"utf8\");\n } catch (error: unknown) {\n if (findErrorCode(error, \"ENOENT\")) {\n throw new Error(\"Task file not found\", { cause: error });\n }\n throw new Error(\"Failed to read task file\", { cause: error });\n }\n let split: { yamlText: string; body: string };\n try {\n split = splitFrontMatter(raw);\n } catch (error: unknown) {\n if (error instanceof Error && error.message === \"Invalid task file format\") {\n throw error;\n }\n throw new Error(\"Failed to read task file\", { cause: error });\n }\n let parsed: unknown;\n try {\n parsed = parseYaml(split.yamlText);\n } catch (error: unknown) {\n throw new Error(\"Failed to read task file\", { cause: error });\n }\n const result = TaskSchema.safeParse(parsed);\n if (!result.success) {\n throw new Error(\"Failed to read task file\", { cause: result.error });\n }\n return { task: result.data, body: split.body };\n}\n\n// ============================================================================\n// File write (atomic, mode-aware)\n// ============================================================================\n\nexport type WriteTaskFileMode = \"create\" | \"overwrite\";\n\n/**\n * Atomically write `<paths.tasks>/<taskId>.md`.\n *\n * `mode: \"create\"` delegates to {@link atomicCreate} so a pre-existing file\n * fails fast with EEXIST → `\"Task file already exists\"`.\n * `mode: \"overwrite\"` delegates to {@link atomicReplace} and silently\n * replaces any prior file.\n *\n * The serialised body is structured as:\n *\n * ---\\n\n * <yaml>\\n\n * ---\\n\n * \\n\n * <body>\\n (only when body is non-empty)\n */\nexport async function writeTaskFile(\n paths: BasouPaths,\n taskId: string,\n doc: TaskDocument,\n options: { mode: WriteTaskFileMode },\n): Promise<void> {\n // Runtime self-defense: even if a caller bypassed the TypeScript boundary,\n // a malformed task object cannot reach disk.\n const validated = TaskSchema.parse(doc.task);\n\n const filePath = join(paths.tasks, `${taskId}.md`);\n const yamlText = stringifyYaml(validated);\n const trimmedBody =\n doc.body.length === 0 ? \"\" : `\\n${doc.body.endsWith(\"\\n\") ? doc.body : `${doc.body}\\n`}`;\n const fileBody = `${FRONT_MATTER_DELIM}\\n${yamlText}${FRONT_MATTER_DELIM}\\n${trimmedBody}`;\n\n if (options.mode === \"create\") {\n try {\n await atomicCreate(filePath, fileBody);\n } catch (error: unknown) {\n if (findErrorCode(error, \"EEXIST\")) {\n throw new Error(\"Task file already exists\", { cause: error });\n }\n throw new Error(\"Failed to write task file\", { cause: error });\n }\n return;\n }\n\n // overwrite mode\n try {\n await atomicReplace(filePath, fileBody);\n } catch (error: unknown) {\n throw new Error(\"Failed to write task file\", { cause: error });\n }\n}\n\n// ============================================================================\n// Directory enumeration / loading\n// ============================================================================\n\nconst TASK_FILENAME_RE = /^(.+)\\.md$/;\n\n/**\n * Enumerate task ids by listing `<paths.tasks>/`. Filenames that do not\n * match the `<task_id>.md` shape, or that decode to a non-conforming task\n * id (per `TaskIdSchema`), are silently skipped — they are surfaced via\n * the caller's `options.onSkip` hook in {@link loadTaskEntries} so list\n * commands can show a warning row.\n *\n * Returns ids in ULID-ascending order (filename sort matches ULID order).\n * Empty directory or ENOENT → `[]`. Other I/O failures throw\n * `\"Failed to enumerate tasks\"`.\n */\nexport async function enumerateTaskIds(paths: BasouPaths): Promise<string[]> {\n // Fast path: read `tasks/index.json` if it exists and is valid. The index\n // is maintained write-through by every task mutation API so the cache\n // matches disk except across crashes / hand-edits / version bumps.\n try {\n const index = await readTaskIndex(paths);\n return index.tasks.map((t) => t.id);\n } catch {\n // Index missing / parse fail / schema mismatch — fall through to the\n // disk-scan rebuild path below. The discrete error classes from\n // readTaskIndex (Task index not found / Invalid task index / Failed\n // to read task index) are all equivalent here.\n }\n\n const ids = await enumerateTaskIdsFromDisk(paths);\n\n // Skip the lazy rebuild entirely when there is nothing to record: a\n // pre-init / empty workspace has no tasks/ dir, so a rebuild attempt\n // would fail with ENOENT and emit a misleading warning. The next write\n // will recreate the index from scratch via updateTaskIndex anyway.\n if (ids.length === 0) {\n return ids;\n }\n\n // Lazy rebuild: scan each task.md and write the resulting index. Best-\n // effort — a per-task read failure (= a malformed file we'd surface as\n // a skip elsewhere) is excluded from the rebuilt index but still\n // returned to the caller in `ids` so loadTaskEntries can surface its\n // own skip reason. Rebuild write failure (disk full, EACCES) is logged\n // and swallowed; the next enumerateTaskIds call will retry the rebuild.\n const entries: TaskIndexEntry[] = [];\n for (const id of ids) {\n try {\n const doc = await readTaskFile(paths, id);\n entries.push(buildTaskIndexEntry(doc.task.task));\n } catch {\n // Skip unreadable entry from the rebuild.\n }\n }\n await rebuildTaskIndex(paths, entries).catch(() => {\n console.warn(\"Failed to rebuild tasks/index.json; subsequent reads will retry\");\n });\n return ids;\n}\n\nasync function enumerateTaskIdsFromDisk(paths: BasouPaths): Promise<string[]> {\n let entries: string[];\n try {\n entries = (await readdir(paths.tasks, { withFileTypes: true }))\n .filter((d) => d.isFile())\n .map((d) => d.name);\n } catch (error: unknown) {\n if (findErrorCode(error, \"ENOENT\")) return [];\n throw new Error(\"Failed to enumerate tasks\", { cause: error });\n }\n const taskIds: string[] = [];\n for (const name of entries) {\n const match = TASK_FILENAME_RE.exec(name);\n if (match === null) continue;\n const candidate = match[1] as string;\n if (!TaskIdSchema.safeParse(candidate).success) continue;\n taskIds.push(candidate);\n }\n taskIds.sort();\n return taskIds;\n}\n\n/**\n * Convert the inner `task` portion of a task.md document into the\n * compact entry shape stored inside `tasks/index.json`. Omits `label`\n * when the task has no label set so the JSON does not store an\n * `undefined` literal.\n */\nfunction buildTaskIndexEntry(task: Task[\"task\"]): TaskIndexEntry {\n return {\n id: task.id,\n status: task.status,\n ...(task.label !== undefined ? { label: task.label } : {}),\n updated_at: task.updated_at,\n };\n}\n\n/**\n * Apply a single write-through update to `tasks/index.json` and swallow\n * any failure with a console.warn so the calling task-write API stays\n * successful (= task.md is the source of truth, index is a soft cache).\n */\nasync function safeUpdateTaskIndex(\n paths: BasouPaths,\n op: Parameters<typeof updateTaskIndex>[1],\n): Promise<void> {\n try {\n await updateTaskIndex(paths, op);\n } catch {\n console.warn(\"Index update failed; rebuild on next read\");\n }\n}\n\nconst ARCHIVE_DIR_NAME = \"archive\";\n\nfunction archiveTasksDir(paths: BasouPaths): string {\n return join(paths.tasks, ARCHIVE_DIR_NAME);\n}\n\n/**\n * Enumerate task ids inside `<paths.tasks>/archive/`. Returns `[]` when the\n * archive directory does not exist (= no task has ever been archived).\n * Filtering / ordering rules mirror {@link enumerateTaskIds}.\n */\nexport async function enumerateArchivedTaskIds(paths: BasouPaths): Promise<string[]> {\n let entries: string[];\n try {\n entries = (await readdir(archiveTasksDir(paths), { withFileTypes: true }))\n .filter((d) => d.isFile())\n .map((d) => d.name);\n } catch (error: unknown) {\n if (findErrorCode(error, \"ENOENT\")) return [];\n throw new Error(\"Failed to enumerate archived tasks\", { cause: error });\n }\n const taskIds: string[] = [];\n for (const name of entries) {\n const match = TASK_FILENAME_RE.exec(name);\n if (match === null) continue;\n const candidate = match[1] as string;\n if (!TaskIdSchema.safeParse(candidate).success) continue;\n taskIds.push(candidate);\n }\n taskIds.sort();\n return taskIds;\n}\n\n/**\n * Read a task.md file looking in the main tasks directory first and falling\n * back to `<paths.tasks>/archive/` if the file is missing there. Returns the\n * parsed document plus a flag indicating whether the hit came from the\n * archive dir. Useful for `basou task show` which surfaces archived tasks\n * read-only without requiring the operator to opt in.\n *\n * Error contract matches {@link readTaskFile} — only the lookup location\n * differs.\n */\nexport async function readTaskFileWithArchiveFallback(\n paths: BasouPaths,\n taskId: string,\n): Promise<{ doc: TaskDocument; archived: boolean }> {\n try {\n const doc = await readTaskFile(paths, taskId);\n return { doc, archived: false };\n } catch (error: unknown) {\n if (!(error instanceof Error && error.message === \"Task file not found\")) {\n throw error;\n }\n }\n const archiveFilePath = join(archiveTasksDir(paths), `${taskId}.md`);\n let raw: string;\n try {\n raw = await readFile(archiveFilePath, \"utf8\");\n } catch (error: unknown) {\n if (findErrorCode(error, \"ENOENT\")) {\n throw new Error(\"Task file not found\", { cause: error });\n }\n throw new Error(\"Failed to read task file\", { cause: error });\n }\n // Parsing mirrors readTaskFile; archived files share the schema.\n let split: { yamlText: string; body: string };\n try {\n split = splitFrontMatter(raw);\n } catch (error: unknown) {\n if (error instanceof Error && error.message === \"Invalid task file format\") {\n throw error;\n }\n throw new Error(\"Failed to read task file\", { cause: error });\n }\n let parsed: unknown;\n try {\n parsed = parseYaml(split.yamlText);\n } catch (error: unknown) {\n throw new Error(\"Failed to read task file\", { cause: error });\n }\n const result = TaskSchema.safeParse(parsed);\n if (!result.success) {\n throw new Error(\"Failed to read task file\", { cause: result.error });\n }\n return { doc: { task: result.data, body: split.body }, archived: true };\n}\n\nexport type TaskSkipReason = \"task_file_invalid\" | \"task_file_unreadable\";\n\nexport type LoadTaskEntriesOptions = {\n onSkip?: (taskId: string, reason: TaskSkipReason) => void;\n};\n\n/**\n * Read every task.md under `<paths.tasks>/` and return the valid documents,\n * skipping malformed / unreadable files with an `onSkip` callback for each.\n *\n * Returned entries are sorted ascending by `task.created_at` (internal asc;\n * the CLI layer reverses for newest-first display).\n */\nexport async function loadTaskEntries(\n paths: BasouPaths,\n options: LoadTaskEntriesOptions = {},\n): Promise<TaskDocument[]> {\n const ids = await enumerateTaskIds(paths);\n const entries: TaskDocument[] = [];\n for (const id of ids) {\n let doc: TaskDocument;\n try {\n doc = await readTaskFile(paths, id);\n } catch (error: unknown) {\n if (error instanceof Error && error.message === \"Invalid task file format\") {\n options.onSkip?.(id, \"task_file_invalid\");\n } else if (error instanceof Error && error.message === \"Failed to read task file\") {\n options.onSkip?.(id, \"task_file_invalid\");\n } else if (error instanceof Error && error.message === \"Task file not found\") {\n // Race: file was enumerated then deleted before read. Treat as unreadable.\n options.onSkip?.(id, \"task_file_unreadable\");\n } else {\n options.onSkip?.(id, \"task_file_unreadable\");\n }\n continue;\n }\n entries.push(doc);\n }\n entries.sort((a, b) => {\n const c = Date.parse(a.task.task.created_at) - Date.parse(b.task.task.created_at);\n return c !== 0 ? c : a.task.task.id.localeCompare(b.task.task.id);\n });\n return entries;\n}\n\n// ============================================================================\n// Status transition rules\n// ============================================================================\n\n// `planned -> done` and `planned -> cancelled` are direct shortcuts so a\n// task that was queued but completed (or abandoned) outside of an explicit\n// `in_progress` phase can be closed with a single CLI call. The 1\n// transition = 1 event invariant is preserved: each shortcut emits exactly\n// one `task_status_changed` event capturing the new from / to pair.\nconst ALLOWED_TRANSITIONS: Readonly<Record<TaskStatus, ReadonlySet<TaskStatus>>> = {\n planned: new Set<TaskStatus>([\"in_progress\", \"done\", \"cancelled\"]),\n in_progress: new Set<TaskStatus>([\"done\", \"cancelled\"]),\n done: new Set<TaskStatus>(),\n cancelled: new Set<TaskStatus>(),\n};\n\nfunction assertTransitionAllowed(from: TaskStatus, to: TaskStatus): void {\n const allowed = ALLOWED_TRANSITIONS[from];\n if (!allowed.has(to)) {\n throw new Error(`Invalid task status transition: ${from} -> ${to}`);\n }\n}\n\n// ============================================================================\n// Specialised error for task.md write failure after the event was persisted\n// ============================================================================\n\n/**\n * Thrown when the task event (`task_created` / `task_status_changed`) was\n * fully persisted to events.jsonl but the accompanying `task.md` write\n * failed. The caller is responsible for surfacing a \"do not rerun\"\n * warning — re-running the same CLI invocation would duplicate the event\n * in events.jsonl.\n *\n * Reconciliation (= regenerating the missing task.md from events) is a\n * v0.2 follow-up (= `task reconcile` family).\n */\n/**\n * `phase` identifies which staged write failed after the event commit:\n * - `create`: task.md create write (ad-hoc or attach path)\n * - `overwrite`: task.md overwrite during a status change\n * - `link-session`: session.yaml `task_id` update during the attach path\n * (split out so CLI warnings describe the actual unsafe artefact\n * instead of always saying \"task.md creation failed\")\n * - `reconcile`: task.md overwrite during `basou task reconcile --write`\n * after the `task_reconciled` event was persisted\n * - `reconcile-finalize`: ad-hoc reconcile session finalize failed (=\n * `FailedToFinalizeError` caught and re-classified)\n * - `reconcile-concurrent`: task.md was modified between the pre-write\n * snapshot and the post-event re-read; the operator is told to re-run\n * reconcile rather than overwrite a stale snapshot\n */\nexport type TaskWriteAfterEventPhase =\n | \"create\"\n | \"overwrite\"\n | \"link-session\"\n | \"reconcile\"\n | \"reconcile-finalize\"\n | \"reconcile-concurrent\"\n // Mirror the reconcile-failure phases for the `refreshTaskLinkedSessions`\n // path. Failure semantics are identical (= ad-hoc session committed, then\n // task.md write / concurrency check failed), but the operator-facing\n // recovery hint must point at `basou task refresh-linkage`, not reconcile.\n | \"linkage-refresh\"\n | \"linkage-refresh-finalize\"\n | \"linkage-refresh-concurrent\"\n // `task_deleted` / `task_archived` event was persisted in events.jsonl but\n // the subsequent file mutation (unlink / move to archive) failed. The\n // event remains the authoritative audit record; the operator must reconcile\n // the residual file state by hand.\n | \"delete\"\n | \"archive\";\n\nexport class TaskWriteAfterEventError extends Error {\n readonly taskId: PrefixedId<\"task\">;\n readonly eventId: PrefixedId<\"evt\">;\n readonly sessionId: PrefixedId<\"ses\">;\n readonly phase: TaskWriteAfterEventPhase;\n\n constructor(args: {\n taskId: PrefixedId<\"task\">;\n eventId: PrefixedId<\"evt\">;\n sessionId: PrefixedId<\"ses\">;\n phase: TaskWriteAfterEventPhase;\n cause: unknown;\n }) {\n super(\"Failed to write task file after event was persisted\", { cause: args.cause });\n this.name = \"TaskWriteAfterEventError\";\n this.taskId = args.taskId;\n this.eventId = args.eventId;\n this.sessionId = args.sessionId;\n this.phase = args.phase;\n }\n}\n\n// ============================================================================\n// Orchestrator: createTaskWithEvent\n// ============================================================================\n\nexport type CreateAdHocTaskInput = {\n mode: \"ad-hoc\";\n paths: BasouPaths;\n manifest: Manifest;\n occurredAt: string;\n taskId: PrefixedId<\"task\">;\n title: string;\n label?: string;\n initialStatus: TaskStatus;\n description: string;\n workingDirectory: string;\n /**\n * Optional override for `task.md.updated_at` when `initialStatus` is a\n * terminal value (done / cancelled). Lets the operator backdate a\n * retroactively-recorded completed task so `task.md` reflects the actual\n * completion moment while `events.jsonl` keeps recording time. Ignored\n * for non-terminal statuses.\n */\n completedAt?: string;\n};\n\nexport type AttachTaskInput = {\n mode: \"attach\";\n paths: BasouPaths;\n occurredAt: string;\n sessionId: PrefixedId<\"ses\">;\n taskId: PrefixedId<\"task\">;\n title: string;\n label?: string;\n initialStatus: TaskStatus;\n description: string;\n attachableStatuses?: ReadonlySet<AttachableStatus>;\n /** See {@link CreateAdHocTaskInput.completedAt}. */\n completedAt?: string;\n};\n\nexport type CreateTaskInput = CreateAdHocTaskInput | AttachTaskInput;\n\nexport type CreateTaskResult = {\n taskId: PrefixedId<\"task\">;\n eventId: PrefixedId<\"evt\">;\n sessionId: PrefixedId<\"ses\">;\n sessionStatus: SessionStatus;\n};\n\nfunction buildTaskCreatedEvent(input: {\n eventId: PrefixedId<\"evt\">;\n sessionId: PrefixedId<\"ses\">;\n taskId: PrefixedId<\"task\">;\n title: string;\n occurredAt: string;\n}): Event {\n return {\n schema_version: \"0.1.0\",\n id: input.eventId,\n session_id: input.sessionId,\n occurred_at: input.occurredAt,\n source: \"local-cli\",\n type: \"task_created\",\n task_id: input.taskId,\n title: input.title,\n };\n}\n\nfunction buildTaskStatusChangedEvent(input: {\n eventId: PrefixedId<\"evt\">;\n sessionId: PrefixedId<\"ses\">;\n taskId: PrefixedId<\"task\">;\n from: TaskStatus;\n to: TaskStatus;\n occurredAt: string;\n}): Event {\n return {\n schema_version: \"0.1.0\",\n id: input.eventId,\n session_id: input.sessionId,\n occurred_at: input.occurredAt,\n source: \"local-cli\",\n type: \"task_status_changed\",\n task_id: input.taskId,\n from: input.from,\n to: input.to,\n };\n}\n\nfunction buildAdHocTaskLabel(title: string, mode: \"new\" | \"status\"): string {\n const truncated =\n title.length > LABEL_TITLE_MAX ? `${title.slice(0, LABEL_TRUNCATE_HEAD)}...` : title;\n return mode === \"new\" ? `Ad-hoc task: ${truncated}` : `Ad-hoc task status: ${truncated}`;\n}\n\n// Kept distinct from buildAdHocTaskLabel rather than threading a third mode\n// through that helper — `basou task reconcile` is a management operation, not\n// a creation/status flow, and the label prefix should read that way.\nfunction buildAdHocReconcileLabel(title: string): string {\n const truncated =\n title.length > LABEL_TITLE_MAX ? `${title.slice(0, LABEL_TRUNCATE_HEAD)}...` : title;\n return `Ad-hoc task reconcile: ${truncated}`;\n}\n\n// Separate label generator for `basou task refresh-linkage` so the operator\n// can distinguish refresh runs from reconcile runs at a glance in session\n// listings — both flow through `createAdHocSessionWithEvent` but answer\n// different questions (broken-ref repair vs. snapshot-vs-events sync).\nfunction buildAdHocRefreshLinkageLabel(title: string): string {\n const truncated =\n title.length > LABEL_TITLE_MAX ? `${title.slice(0, LABEL_TRUNCATE_HEAD)}...` : title;\n return `Ad-hoc task refresh-linkage: ${truncated}`;\n}\n\nfunction buildAdHocDeleteLabel(title: string): string {\n const truncated =\n title.length > LABEL_TITLE_MAX ? `${title.slice(0, LABEL_TRUNCATE_HEAD)}...` : title;\n return `Ad-hoc task delete: ${truncated}`;\n}\n\nfunction buildAdHocArchiveLabel(title: string): string {\n const truncated =\n title.length > LABEL_TITLE_MAX ? `${title.slice(0, LABEL_TRUNCATE_HEAD)}...` : title;\n return `Ad-hoc task archive: ${truncated}`;\n}\n\nfunction buildTaskReconciledEvent(input: {\n eventId: PrefixedId<\"evt\">;\n sessionId: PrefixedId<\"ses\">;\n taskId: PrefixedId<\"task\">;\n removedCreatedInSession: PrefixedId<\"ses\"> | null;\n createdInSessionReplacement: PrefixedId<\"ses\"> | null;\n removedLinkedSessions: PrefixedId<\"ses\">[];\n occurredAt: string;\n}): Event {\n return {\n schema_version: \"0.1.0\",\n id: input.eventId,\n session_id: input.sessionId,\n occurred_at: input.occurredAt,\n source: \"local-cli\",\n type: \"task_reconciled\",\n task_id: input.taskId,\n removed_created_in_session: input.removedCreatedInSession,\n created_in_session_replacement: input.createdInSessionReplacement,\n removed_linked_sessions: input.removedLinkedSessions,\n };\n}\n\nfunction buildTaskDeletedEvent(input: {\n eventId: PrefixedId<\"evt\">;\n sessionId: PrefixedId<\"ses\">;\n taskId: PrefixedId<\"task\">;\n title: string;\n occurredAt: string;\n}): Event {\n return {\n schema_version: \"0.1.0\",\n id: input.eventId,\n session_id: input.sessionId,\n occurred_at: input.occurredAt,\n source: \"local-cli\",\n type: \"task_deleted\",\n task_id: input.taskId,\n title: input.title,\n };\n}\n\nfunction buildTaskArchivedEvent(input: {\n eventId: PrefixedId<\"evt\">;\n sessionId: PrefixedId<\"ses\">;\n taskId: PrefixedId<\"task\">;\n title: string;\n occurredAt: string;\n}): Event {\n return {\n schema_version: \"0.1.0\",\n id: input.eventId,\n session_id: input.sessionId,\n occurred_at: input.occurredAt,\n source: \"local-cli\",\n type: \"task_archived\",\n task_id: input.taskId,\n title: input.title,\n };\n}\n\nfunction buildTaskLinkageRefreshedEvent(input: {\n eventId: PrefixedId<\"evt\">;\n sessionId: PrefixedId<\"ses\">;\n taskId: PrefixedId<\"task\">;\n addedLinkedSessions: PrefixedId<\"ses\">[];\n removedLinkedSessions: PrefixedId<\"ses\">[];\n finalCount: number;\n occurredAt: string;\n}): Event {\n return {\n schema_version: \"0.1.0\",\n id: input.eventId,\n session_id: input.sessionId,\n occurred_at: input.occurredAt,\n source: \"local-cli\",\n type: \"task_linkage_refreshed\",\n task_id: input.taskId,\n added_linked_sessions: input.addedLinkedSessions,\n removed_linked_sessions: input.removedLinkedSessions,\n final_count: input.finalCount,\n };\n}\n\n/**\n * Create a new task: fires a single `task_created` event and writes\n * `.basou/tasks/<taskId>.md` with status = `initialStatus`.\n *\n * Ad-hoc path: a fresh ad-hoc session is minted (5-event bulk write,\n * `task_created` as the target event, session.yaml.task_id pinned to the\n * new task).\n *\n * Attach path: the target session's `task_id` is validated against the\n * session ⇆ task anchor invariant (see\n * `docs/spec/workspace.md#21-confirmed-invariants`; null → updated to the\n * new task; existing X → rejected since X is already owned). If validation\n * passes, the event is appended to events.jsonl and session.yaml's\n * `task_id` is updated to the new task.\n *\n * Race window (v0.1 accepts): stage 2 writes the event, stage 3 writes\n * task.md. A failure on stage 3 leaves events.jsonl ahead of task.md;\n * {@link TaskWriteAfterEventError} surfaces this with a \"do not rerun\"\n * warning so the operator can reconcile manually until the v0.2 reconcile\n * flow arrives.\n */\nexport async function createTaskWithEvent(input: CreateTaskInput): Promise<CreateTaskResult> {\n // Boundary parses so direct (non-CLI) callers can't smuggle in malformed\n // ids / statuses / titles past the CLI-side guards. All checks here run\n // BEFORE any persistent write, so a rejection leaves events.jsonl and\n // task.md untouched.\n TaskIdSchema.parse(input.taskId);\n InitialTaskStatusSchema.parse(input.initialStatus);\n TaskTitleSchema.parse(input.title);\n if (input.label !== undefined) {\n TaskLabelSchema.parse(input.label);\n }\n if (input.completedAt !== undefined) {\n CompletedAtSchema.parse(input.completedAt);\n }\n\n if (input.mode === \"ad-hoc\") {\n return createTaskAdHoc(input);\n }\n return createTaskAttach(input);\n}\n\nasync function createTaskAdHoc(input: CreateAdHocTaskInput): Promise<CreateTaskResult> {\n const adHoc = await createAdHocSessionWithEvent({\n paths: input.paths,\n manifest: input.manifest,\n label: buildAdHocTaskLabel(input.title, \"new\"),\n occurredAt: input.occurredAt,\n sessionSource: \"human\",\n workingDirectory: input.workingDirectory,\n invocation: {\n command: \"basou task new\",\n args: buildTaskNewInvocationArgs(input.title, input.initialStatus, input.completedAt),\n },\n taskId: input.taskId,\n targetEventBuilders: buildTaskNewTargetEventBuilders({\n taskId: input.taskId,\n title: input.title,\n initialStatus: input.initialStatus,\n occurredAt: input.occurredAt,\n }),\n });\n\n const task: Task = buildInitialTask({\n taskId: input.taskId,\n title: input.title,\n ...(input.label !== undefined ? { label: input.label } : {}),\n status: input.initialStatus,\n occurredAt: input.occurredAt,\n ...(input.completedAt !== undefined ? { completedAt: input.completedAt } : {}),\n workspaceId: input.manifest.workspace.id,\n createdInSession: adHoc.sessionId,\n });\n // `targetEventIds[0]` is the `task_created` anchor (= what the caller cares\n // about); a second `task_status_changed` event may also live in this\n // ad-hoc session when initialStatus is terminal, but it is not the\n // primary task-lifecycle anchor.\n const anchorEventId = adHoc.targetEventIds[0] as PrefixedId<\"evt\">;\n try {\n await writeTaskFile(\n input.paths,\n input.taskId,\n { task, body: input.description },\n { mode: \"create\" },\n );\n } catch (error: unknown) {\n throw new TaskWriteAfterEventError({\n taskId: input.taskId,\n eventId: anchorEventId,\n sessionId: adHoc.sessionId,\n phase: \"create\",\n cause: error,\n });\n }\n await safeUpdateTaskIndex(input.paths, { kind: \"add\", entry: buildTaskIndexEntry(task.task) });\n return {\n taskId: input.taskId,\n eventId: anchorEventId,\n sessionId: adHoc.sessionId,\n sessionStatus: \"completed\",\n };\n}\n\nasync function createTaskAttach(input: AttachTaskInput): Promise<CreateTaskResult> {\n SessionIdSchema.parse(input.sessionId);\n\n // Per-session lock spans the entire attach window (session.yaml read →\n // collision-matrix check → task_created append → session.yaml task_id\n // update → optional task_status_changed append). Without it, two\n // concurrent attaches on the same session.yaml could both observe a\n // null task_id, both append their own `task_created`, and race on the\n // final task_id overwrite.\n const sessionLock = await acquireLock(input.paths, \"session\", input.sessionId);\n try {\n return await createTaskAttachLocked(input);\n } finally {\n await sessionLock.release();\n }\n}\n\nasync function createTaskAttachLocked(input: AttachTaskInput): Promise<CreateTaskResult> {\n // 1. Read session.yaml + validate the §F.7.2 collision matrix BEFORE writing\n // anything. status / task_id checks share the same read.\n const sessionDoc = await readSessionYaml(input.paths, input.sessionId);\n const status = sessionDoc.session.status;\n if (status === \"imported\") {\n throw new Error(\"Cannot attach to imported session\");\n }\n const attachable = input.attachableStatuses ?? DEFAULT_ATTACHABLE_STATUSES;\n if (!attachable.has(status as AttachableStatus)) {\n throw new Error(`Session is not active: ${status}`);\n }\n const existingTaskId = sessionDoc.session.task_id ?? null;\n if (existingTaskId !== null && existingTaskId !== input.taskId) {\n throw new Error(`Session already linked to a different task: ${existingTaskId}`);\n }\n if (existingTaskId === input.taskId) {\n // Re-creating the same task on the same session would duplicate\n // `task_created` in events.jsonl. Reject up front.\n throw new Error(`Task already exists: ${input.taskId}`);\n }\n\n // 2. Append `task_created` to events.jsonl. We use appendEventToExistingSession\n // so the same status/imported-rejection logic is shared across attach-flavoured callers.\n const appendResult = await appendEventToExistingSession({\n paths: input.paths,\n sessionId: input.sessionId,\n ...(input.attachableStatuses !== undefined\n ? { attachableStatuses: input.attachableStatuses }\n : {}),\n eventBuilder: (eventId) =>\n buildTaskCreatedEvent({\n eventId,\n sessionId: input.sessionId,\n taskId: input.taskId,\n title: input.title,\n occurredAt: input.occurredAt,\n }),\n });\n\n // 3. Update session.yaml task_id (null → new) so the single-session ↔\n // single-task invariant holds. Failure here puts us into the same\n // \"event persisted, side-effect missing\" band as task.md. Use\n // phase: \"link-session\" so the operator warning identifies the failed\n // artefact correctly.\n try {\n const updated = {\n ...sessionDoc,\n session: { ...sessionDoc.session, task_id: input.taskId },\n };\n await overwriteYamlFile(join(input.paths.sessions, input.sessionId, \"session.yaml\"), updated);\n } catch (error: unknown) {\n throw new TaskWriteAfterEventError({\n taskId: input.taskId,\n eventId: appendResult.eventId,\n sessionId: input.sessionId,\n phase: \"link-session\",\n cause: error,\n });\n }\n\n // 4. For terminal initialStatus (done / cancelled) append a second target\n // event `task_status_changed (planned → terminal)` so the events.jsonl\n // audit trail records the implicit transition. The ALLOWED_TRANSITIONS\n // shortcut from `planned` to `done|cancelled` makes this a single\n // permitted edge. The session.yaml `task_id` link from step 3 covers\n // both events; no further session.yaml write is needed.\n if (isTerminalTaskStatus(input.initialStatus)) {\n await appendEventToExistingSession({\n paths: input.paths,\n sessionId: input.sessionId,\n ...(input.attachableStatuses !== undefined\n ? { attachableStatuses: input.attachableStatuses }\n : {}),\n eventBuilder: (eventId) =>\n buildTaskStatusChangedEvent({\n eventId,\n sessionId: input.sessionId,\n taskId: input.taskId,\n from: \"planned\",\n to: input.initialStatus,\n occurredAt: input.occurredAt,\n }),\n });\n }\n\n // 5. Write task.md (create mode, collision = rerun guard).\n const task: Task = buildInitialTask({\n taskId: input.taskId,\n title: input.title,\n ...(input.label !== undefined ? { label: input.label } : {}),\n status: input.initialStatus,\n occurredAt: input.occurredAt,\n ...(input.completedAt !== undefined ? { completedAt: input.completedAt } : {}),\n workspaceId: sessionDoc.session.workspace_id,\n createdInSession: input.sessionId,\n });\n try {\n await writeTaskFile(\n input.paths,\n input.taskId,\n { task, body: input.description },\n { mode: \"create\" },\n );\n } catch (error: unknown) {\n throw new TaskWriteAfterEventError({\n taskId: input.taskId,\n eventId: appendResult.eventId,\n sessionId: input.sessionId,\n phase: \"create\",\n cause: error,\n });\n }\n\n await safeUpdateTaskIndex(input.paths, { kind: \"add\", entry: buildTaskIndexEntry(task.task) });\n\n return {\n taskId: input.taskId,\n eventId: appendResult.eventId,\n sessionId: input.sessionId,\n sessionStatus: status,\n };\n}\n\nfunction buildInitialTask(input: {\n taskId: PrefixedId<\"task\">;\n title: string;\n label?: string;\n status: TaskStatus;\n occurredAt: string;\n /**\n * Override for `updated_at` when `status` is terminal. Ignored for\n * non-terminal statuses so backdating a non-completed task is not\n * possible by accident.\n */\n completedAt?: string;\n workspaceId: PrefixedId<\"ws\">;\n createdInSession: PrefixedId<\"ses\">;\n}): Task {\n const updatedAt =\n input.completedAt !== undefined && isTerminalTaskStatus(input.status)\n ? input.completedAt\n : input.occurredAt;\n return {\n schema_version: \"0.1.0\",\n task: {\n id: input.taskId,\n title: input.title,\n ...(input.label !== undefined ? { label: input.label } : {}),\n status: input.status,\n created_at: input.occurredAt,\n updated_at: updatedAt,\n workspace_id: input.workspaceId,\n created_in_session: input.createdInSession,\n linked_sessions: [input.createdInSession],\n },\n };\n}\n\n// Helpers for the ad-hoc `task new` path. The invocation args list mirrors\n// the operator's CLI input so the recorded `session.yaml.invocation.args`\n// stays accurate even when `--status` / `--completed-at` were supplied.\nfunction buildTaskNewInvocationArgs(\n title: string,\n initialStatus: TaskStatus,\n completedAt: string | undefined,\n): string[] {\n const args = [\"--title\", title];\n if (initialStatus !== \"planned\") {\n args.push(\"--status\", initialStatus);\n }\n if (completedAt !== undefined && isTerminalTaskStatus(initialStatus)) {\n args.push(\"--completed-at\", completedAt);\n }\n return args;\n}\n\nfunction buildTaskNewTargetEventBuilders(input: {\n taskId: PrefixedId<\"task\">;\n title: string;\n initialStatus: TaskStatus;\n occurredAt: string;\n}): Array<(sessionId: PrefixedId<\"ses\">, eventId: PrefixedId<\"evt\">) => Event> {\n const createdBuilder = (sessionId: PrefixedId<\"ses\">, eventId: PrefixedId<\"evt\">): Event =>\n buildTaskCreatedEvent({\n eventId,\n sessionId,\n taskId: input.taskId,\n title: input.title,\n occurredAt: input.occurredAt,\n });\n if (!isTerminalTaskStatus(input.initialStatus)) {\n return [createdBuilder];\n }\n // For terminal initialStatus, emit `task_status_changed (planned → terminal)`\n // right after `task_created` so replay reconstructs the implicit\n // transition. The shortcut edges `planned → done|cancelled` are already\n // allowed by ALLOWED_TRANSITIONS.\n const statusChangedBuilder = (sessionId: PrefixedId<\"ses\">, eventId: PrefixedId<\"evt\">): Event =>\n buildTaskStatusChangedEvent({\n eventId,\n sessionId,\n taskId: input.taskId,\n from: \"planned\",\n to: input.initialStatus,\n occurredAt: input.occurredAt,\n });\n return [createdBuilder, statusChangedBuilder];\n}\n\n// ============================================================================\n// Orchestrator: updateTaskStatusWithEvent\n// ============================================================================\n\nexport type UpdateAdHocTaskStatusInput = {\n mode: \"ad-hoc\";\n paths: BasouPaths;\n manifest: Manifest;\n occurredAt: string;\n taskId: PrefixedId<\"task\">;\n newStatus: TaskStatus;\n workingDirectory: string;\n};\n\nexport type AttachUpdateTaskStatusInput = {\n mode: \"attach\";\n paths: BasouPaths;\n occurredAt: string;\n sessionId: PrefixedId<\"ses\">;\n taskId: PrefixedId<\"task\">;\n newStatus: TaskStatus;\n attachableStatuses?: ReadonlySet<AttachableStatus>;\n};\n\nexport type UpdateTaskStatusInput = UpdateAdHocTaskStatusInput | AttachUpdateTaskStatusInput;\n\nexport type UpdateTaskStatusResult = {\n taskId: PrefixedId<\"task\">;\n eventId: PrefixedId<\"evt\">;\n sessionId: PrefixedId<\"ses\">;\n sessionStatus: SessionStatus;\n previousStatus: TaskStatus;\n newStatus: TaskStatus;\n};\n\n/**\n * Fire a `task_status_changed` event and overwrite the task.md front matter\n * with the new status / `updated_at` / appended-but-deduped `linked_sessions`.\n *\n * Validates the transition BEFORE any event write so a rejected transition\n * leaves events.jsonl untouched. The canonical edge set lives in\n * {@link ALLOWED_TRANSITIONS}; the current shape is:\n * planned → {in_progress, done, cancelled}\n * in_progress → {done, cancelled}\n * done / cancelled are terminal (= idempotent same-state is rejected too).\n */\nexport async function updateTaskStatusWithEvent(\n input: UpdateTaskStatusInput,\n): Promise<UpdateTaskStatusResult> {\n TaskIdSchema.parse(input.taskId);\n\n // Per-task lock guards the read-modify-write of task.md against concurrent\n // writers on the same task id (= another `task status` / `task edit` /\n // `task reconcile` on the same task). Released in a finally block so the\n // lock never lingers on either the success or the failure path.\n const handle = await acquireLock(input.paths, \"task\", input.taskId);\n try {\n // 1. Load current task.md (= source of truth for current status).\n const currentDoc = await readTaskFile(input.paths, input.taskId);\n const previousStatus = currentDoc.task.task.status;\n\n // 2. Validate transition before touching any persistent state.\n assertTransitionAllowed(previousStatus, input.newStatus);\n\n if (input.mode === \"ad-hoc\") {\n return await updateTaskStatusAdHoc(input, currentDoc, previousStatus);\n }\n return await updateTaskStatusAttach(input, currentDoc, previousStatus);\n } finally {\n await handle.release();\n }\n}\n\nasync function updateTaskStatusAdHoc(\n input: UpdateAdHocTaskStatusInput,\n currentDoc: TaskDocument,\n previousStatus: TaskStatus,\n): Promise<UpdateTaskStatusResult> {\n const title = currentDoc.task.task.title;\n const adHoc = await createAdHocSessionWithEvent({\n paths: input.paths,\n manifest: input.manifest,\n label: buildAdHocTaskLabel(title, \"status\"),\n occurredAt: input.occurredAt,\n sessionSource: \"human\",\n workingDirectory: input.workingDirectory,\n invocation: { command: \"basou task status\", args: [input.taskId, input.newStatus] },\n taskId: input.taskId,\n targetEventBuilders: [\n (sessionId, eventId) =>\n buildTaskStatusChangedEvent({\n eventId,\n sessionId,\n taskId: input.taskId,\n from: previousStatus,\n to: input.newStatus,\n occurredAt: input.occurredAt,\n }),\n ],\n });\n\n const anchorEventId = adHoc.targetEventIds[0] as PrefixedId<\"evt\">;\n // 3. Overwrite task.md (status + updated_at + linked_sessions append-dedup).\n const updatedDoc = buildUpdatedDoc({\n currentDoc,\n newStatus: input.newStatus,\n occurredAt: input.occurredAt,\n appendSessionId: adHoc.sessionId,\n });\n try {\n await writeTaskFile(input.paths, input.taskId, updatedDoc, { mode: \"overwrite\" });\n } catch (error: unknown) {\n throw new TaskWriteAfterEventError({\n taskId: input.taskId,\n eventId: anchorEventId,\n sessionId: adHoc.sessionId,\n phase: \"overwrite\",\n cause: error,\n });\n }\n await safeUpdateTaskIndex(input.paths, {\n kind: \"update\",\n entry: buildTaskIndexEntry(updatedDoc.task.task),\n });\n return {\n taskId: input.taskId,\n eventId: anchorEventId,\n sessionId: adHoc.sessionId,\n sessionStatus: \"completed\",\n previousStatus,\n newStatus: input.newStatus,\n };\n}\n\nasync function updateTaskStatusAttach(\n input: AttachUpdateTaskStatusInput,\n currentDoc: TaskDocument,\n previousStatus: TaskStatus,\n): Promise<UpdateTaskStatusResult> {\n SessionIdSchema.parse(input.sessionId);\n\n // Per-session lock guards the session.yaml read → events.jsonl append\n // window so a concurrent writer on the same session cannot append a\n // conflicting `task_status_changed` or flip session.yaml to a state\n // that invalidates the status check below. We are already inside the\n // per-task lock (updateTaskStatusWithEvent acquires it); the per-task\n // → per-session order is fixed across the codebase to keep cross-API\n // deadlocks impossible.\n const sessionLock = await acquireLock(input.paths, \"session\", input.sessionId);\n try {\n return await updateTaskStatusAttachLocked(input, currentDoc, previousStatus);\n } finally {\n await sessionLock.release();\n }\n}\n\nasync function updateTaskStatusAttachLocked(\n input: AttachUpdateTaskStatusInput,\n currentDoc: TaskDocument,\n previousStatus: TaskStatus,\n): Promise<UpdateTaskStatusResult> {\n const sessionDoc = await readSessionYaml(input.paths, input.sessionId);\n const status = sessionDoc.session.status;\n if (status === \"imported\") {\n throw new Error(\"Cannot attach to imported session\");\n }\n const attachable = input.attachableStatuses ?? DEFAULT_ATTACHABLE_STATUSES;\n if (!attachable.has(status as AttachableStatus)) {\n throw new Error(`Session is not active: ${status}`);\n }\n // task_id collision: the session MUST already be linked to the same task,\n // otherwise a status change on a task that the session does not own would\n // violate the session ⇆ task anchor invariant.\n const existingTaskId = sessionDoc.session.task_id ?? null;\n if (existingTaskId === null) {\n throw new Error(`Session is not linked to task: ${input.taskId}`);\n }\n if (existingTaskId !== input.taskId) {\n throw new Error(`Session already linked to a different task: ${existingTaskId}`);\n }\n\n const appendResult = await appendEventToExistingSession({\n paths: input.paths,\n sessionId: input.sessionId,\n ...(input.attachableStatuses !== undefined\n ? { attachableStatuses: input.attachableStatuses }\n : {}),\n eventBuilder: (eventId) =>\n buildTaskStatusChangedEvent({\n eventId,\n sessionId: input.sessionId,\n taskId: input.taskId,\n from: previousStatus,\n to: input.newStatus,\n occurredAt: input.occurredAt,\n }),\n });\n\n const updatedDoc = buildUpdatedDoc({\n currentDoc,\n newStatus: input.newStatus,\n occurredAt: input.occurredAt,\n appendSessionId: input.sessionId,\n });\n try {\n await writeTaskFile(input.paths, input.taskId, updatedDoc, { mode: \"overwrite\" });\n } catch (error: unknown) {\n throw new TaskWriteAfterEventError({\n taskId: input.taskId,\n eventId: appendResult.eventId,\n sessionId: input.sessionId,\n phase: \"overwrite\",\n cause: error,\n });\n }\n await safeUpdateTaskIndex(input.paths, {\n kind: \"update\",\n entry: buildTaskIndexEntry(updatedDoc.task.task),\n });\n return {\n taskId: input.taskId,\n eventId: appendResult.eventId,\n sessionId: input.sessionId,\n sessionStatus: status,\n previousStatus,\n newStatus: input.newStatus,\n };\n}\n\nfunction buildUpdatedDoc(input: {\n currentDoc: TaskDocument;\n newStatus: TaskStatus;\n occurredAt: string;\n appendSessionId: PrefixedId<\"ses\">;\n}): TaskDocument {\n const linked = input.currentDoc.task.task.linked_sessions;\n const merged = linked.includes(input.appendSessionId)\n ? linked\n : [...linked, input.appendSessionId];\n const next: Task = {\n ...input.currentDoc.task,\n task: {\n ...input.currentDoc.task.task,\n status: input.newStatus,\n updated_at: input.occurredAt,\n linked_sessions: merged,\n },\n };\n return { task: next, body: input.currentDoc.body };\n}\n\n// ============================================================================\n// Reconcile (basou task reconcile)\n// ============================================================================\n\n/**\n * Single-task audit result. Always returned by {@link reconcileTask} regardless\n * of mode: in dry-run the `clean` / `broken*` fields describe what would change\n * and `reconcileSession` is `null`; in write mode the same fields describe\n * what did change and `reconcileSession` carries the minted ad-hoc session +\n * `task_reconciled` event ids.\n *\n * Broken `linked_sessions[]` entries are deduplicated against the same session\n * id appearing more than once in the source task.md (hand-edit defence).\n */\nexport type ReconcileResult = {\n taskId: PrefixedId<\"task\">;\n clean: boolean;\n brokenCreatedInSession: PrefixedId<\"ses\"> | null;\n brokenLinkedSessions: PrefixedId<\"ses\">[];\n reconcileSession: {\n sessionId: PrefixedId<\"ses\">;\n eventId: PrefixedId<\"evt\">;\n } | null;\n};\n\n/**\n * Per-task failure record collected by {@link reconcileAllTasks}. The scan\n * keeps running on isolated failures so one bad task does not freeze the\n * batch; the CLI layer renders this list and exits 1 if any entry is present.\n *\n * `phase` is populated only for {@link TaskWriteAfterEventError}; for any\n * other error class it is `null` and the operator must use `--verbose` to\n * surface the cause chain.\n */\nexport type ReconcileFailure = {\n taskId: PrefixedId<\"task\">;\n errorClass: string;\n phase: TaskWriteAfterEventPhase | null;\n};\n\n/**\n * Batch audit result. Order follows `enumerateTaskIds(paths)` (ULID-ascending).\n * `scanned` is the number of readable task.md files processed (= excludes\n * malformed task.md from the count so an integrity-broken file does not\n * pad the total).\n */\nexport type ReconcileAllResult = {\n results: ReconcileResult[];\n failed: ReconcileFailure[];\n scanned: number;\n};\n\nexport type ReconcileTaskInput = {\n taskId: PrefixedId<\"task\">;\n occurredAt: string;\n workingDirectory: string;\n write: boolean;\n /**\n * Whether the caller invoked reconcile against a single task (`--task <id>`)\n * or as part of a full scan. The ad-hoc reconcile session records the form\n * on its `invocation.args` so audit trails distinguish targeted repairs\n * from sweeps:\n * - `\"single\"` -> `[\"--task\", <taskId>, \"--write\"]`\n * - `\"all\"` -> `[\"--write\"]` (= the operator typed no task id, so the\n * scan-wide intent is preserved instead of synthesising one per task)\n * Defaults to `\"single\"` so direct callers (tests, programmatic uses) keep\n * the targeted form without an explicit argument.\n */\n scope?: \"single\" | \"all\";\n /**\n * Test-only hook: the test runner uses this to mutate the task file\n * from outside the reconcile flow between the pre-write snapshot and\n * the post-event re-read, simulating a concurrent edit so the\n * `reconcile-concurrent` branch can be exercised deterministically.\n * Production callers leave it undefined.\n */\n _onPhaseCompleted?: (phase: \"phase-4-snapshot\" | \"phase-5-bulk-write\") => Promise<void>;\n};\n\nexport type ReconcileAllTasksInput = {\n /**\n * Per-task timestamp factory. Each reconciled task gets a fresh ISO string\n * so concurrent ad-hoc sessions do not collide on `occurred_at`. The CLI\n * layer wires this to `ctx.nowProvider().toISOString()`.\n */\n occurredAt: () => string;\n workingDirectory: string;\n write: boolean;\n};\n\nexport type ReconcileAllTasksOptions = {\n /**\n * When true the result includes clean tasks (= no broken refs). The CLI\n * layer leaves this false so the human output only mentions tasks that\n * actually changed.\n */\n includeClean?: boolean;\n};\n\ntype TaskMdSnapshot = {\n mtimeMs: number;\n hash: string;\n};\n\nasync function computeTaskMdSnapshot(paths: BasouPaths, taskId: string): Promise<TaskMdSnapshot> {\n const filePath = join(paths.tasks, `${taskId}.md`);\n const [stats, raw] = await Promise.all([stat(filePath), readFile(filePath)]);\n const hash = createHash(\"sha256\").update(raw).digest(\"hex\");\n return { mtimeMs: stats.mtimeMs, hash };\n}\n\n// Read task.md and derive its mtime/sha256 snapshot from the SAME raw bytes\n// the TaskDocument was parsed from. An earlier internal review flagged that\n// the previous \"readTaskFile, then computeTaskMdSnapshot\" sequence left a window\n// where a concurrent edit between those two reads could leave the caller\n// acting on stale content while the snapshot already reflected the new\n// content — and stage 7 would then clobber the new bytes with the stale\n// TaskDocument. Sharing the raw bytes here means stage 6's re-read is\n// compared against the EXACT bytes that produced this document, so any\n// drift since this read is caught.\nasync function readTaskFileWithSnapshot(\n paths: BasouPaths,\n taskId: string,\n): Promise<{ doc: TaskDocument; snapshot: TaskMdSnapshot }> {\n const filePath = join(paths.tasks, `${taskId}.md`);\n let rawBuffer: Buffer;\n let stats: Awaited<ReturnType<typeof stat>>;\n try {\n [rawBuffer, stats] = await Promise.all([readFile(filePath), stat(filePath)]);\n } catch (error: unknown) {\n if (findErrorCode(error, \"ENOENT\")) {\n throw new Error(\"Task file not found\", { cause: error });\n }\n throw new Error(\"Failed to read task file\", { cause: error });\n }\n const raw = rawBuffer.toString(\"utf8\");\n const hash = createHash(\"sha256\").update(rawBuffer).digest(\"hex\");\n // Parse logic mirrors readTaskFile so the error contract stays identical\n // (Invalid task file format / Failed to read task file). Duplicated here to\n // avoid a second readFile from the public helper.\n let split: { yamlText: string; body: string };\n try {\n split = splitFrontMatter(raw);\n } catch (error: unknown) {\n if (error instanceof Error && error.message === \"Invalid task file format\") {\n throw error;\n }\n throw new Error(\"Failed to read task file\", { cause: error });\n }\n let parsed: unknown;\n try {\n parsed = parseYaml(split.yamlText);\n } catch (error: unknown) {\n throw new Error(\"Failed to read task file\", { cause: error });\n }\n const result = TaskSchema.safeParse(parsed);\n if (!result.success) {\n throw new Error(\"Failed to read task file\", { cause: result.error });\n }\n return {\n doc: { task: result.data, body: split.body },\n snapshot: { mtimeMs: stats.mtimeMs, hash },\n };\n}\n\ntype DetectedBrokenRefs = {\n brokenCreatedInSession: PrefixedId<\"ses\"> | null;\n brokenLinkedSessions: PrefixedId<\"ses\">[];\n};\n\n// `enumerateSessionDirs` returns directory names only — it does NOT validate\n// the contents of each `session.yaml`. By treating directory existence alone\n// as \"reachable\", reconcile targets the dogfood failure mode where a session\n// directory is removed entirely and a dangling id remains in task.md, while\n// keeping the \"broken\" predicate cheap. A directory that exists but whose\n// session.yaml is missing or schema-invalid is intentionally classified as\n// reachable here; that flavour of corruption is the responsibility of session\n// integrity tooling and is out of scope for v0.2 reconcile.\nasync function detectBrokenRefs(\n paths: BasouPaths,\n task: Task[\"task\"],\n): Promise<DetectedBrokenRefs> {\n const sessionDirs = new Set(await enumerateSessionDirs(paths));\n const brokenCreatedInSession = sessionDirs.has(task.created_in_session)\n ? null\n : (task.created_in_session as PrefixedId<\"ses\">);\n // Deduplicate broken entries so duplicate broken ids in a hand-edited task.md\n // surface as a single entry on the event payload.\n const seen = new Set<string>();\n const brokenLinkedSessions: PrefixedId<\"ses\">[] = [];\n for (const sid of task.linked_sessions) {\n if (sessionDirs.has(sid)) continue;\n if (seen.has(sid)) continue;\n seen.add(sid);\n brokenLinkedSessions.push(sid as PrefixedId<\"ses\">);\n }\n return { brokenCreatedInSession, brokenLinkedSessions };\n}\n\nfunction buildReconciledDoc(input: {\n currentDoc: TaskDocument;\n brokenCreatedInSession: PrefixedId<\"ses\"> | null;\n brokenLinkedSessions: ReadonlyArray<PrefixedId<\"ses\">>;\n reconcileSessionId: PrefixedId<\"ses\">;\n occurredAt: string;\n}): TaskDocument {\n const brokenSet = new Set<string>(input.brokenLinkedSessions);\n const filtered = input.currentDoc.task.task.linked_sessions.filter((sid) => !brokenSet.has(sid));\n const merged: PrefixedId<\"ses\">[] = [...filtered] as PrefixedId<\"ses\">[];\n if (!merged.includes(input.reconcileSessionId)) {\n merged.push(input.reconcileSessionId);\n }\n const nextCreatedInSession =\n input.brokenCreatedInSession !== null\n ? input.reconcileSessionId\n : input.currentDoc.task.task.created_in_session;\n const next: Task = {\n ...input.currentDoc.task,\n task: {\n ...input.currentDoc.task.task,\n created_in_session: nextCreatedInSession,\n updated_at: input.occurredAt,\n linked_sessions: merged,\n },\n };\n return { task: next, body: input.currentDoc.body };\n}\n\n/**\n * Audit a single task's session references. In `write: false` mode this is a\n * pure read-only report (no events, no task.md change). In `write: true` mode,\n * if any broken reference is found, mint an ad-hoc reconcile session, fire\n * `task_reconciled`, and overwrite task.md with the repaired refs.\n *\n * The broken `created_in_session` field is REPLACED with the new reconcile\n * session id rather than nulled out — `TaskSchema.created_in_session` is\n * non-nullable, so dropping it would leave the file schema-invalid.\n * The old broken id is preserved on the event payload via\n * `removed_created_in_session` for audit.\n *\n * Stages — failures after stage 5 surface a phase-specific\n * {@link TaskWriteAfterEventError} so the CLI can render a tailored \"do not\n * rerun\" hint:\n * 1. Boundary parse\n * 2. Read task.md AND snapshot its mtime/hash from the same raw bytes,\n * then detect broken refs (sharing the raw bytes here closes the\n * readTaskFile-then-snapshot race window).\n * 3. Early return when clean (no event fired, no overwrite)\n * 4. (no separate stage anymore — snapshot is taken at stage 2)\n * 5. Mint ad-hoc session + `task_reconciled` event (catch\n * `FailedToFinalizeError` → `phase: \"reconcile-finalize\"`)\n * 6. Re-snapshot task.md; if changed since stage 2 →\n * `phase: \"reconcile-concurrent\"`\n * 7. Overwrite task.md; failure → `phase: \"reconcile\"`\n */\nexport async function reconcileTask(\n paths: BasouPaths,\n manifest: Manifest,\n input: ReconcileTaskInput,\n): Promise<ReconcileResult> {\n TaskIdSchema.parse(input.taskId);\n\n // Per-task lock spans the entire reconcile window (= snapshot read,\n // ad-hoc session + event write, post-write snapshot probe, task.md\n // overwrite) so the mtime/hash invariant cannot be defeated by a\n // concurrent writer the helper would otherwise not notice.\n const handle = await acquireLock(paths, \"task\", input.taskId);\n try {\n return await reconcileTaskLocked(paths, manifest, input);\n } finally {\n await handle.release();\n }\n}\n\nasync function reconcileTaskLocked(\n paths: BasouPaths,\n manifest: Manifest,\n input: ReconcileTaskInput,\n): Promise<ReconcileResult> {\n const { doc: currentDoc, snapshot: preSnapshot } = await readTaskFileWithSnapshot(\n paths,\n input.taskId,\n );\n const { brokenCreatedInSession, brokenLinkedSessions } = await detectBrokenRefs(\n paths,\n currentDoc.task.task,\n );\n\n if (brokenCreatedInSession === null && brokenLinkedSessions.length === 0) {\n return {\n taskId: input.taskId,\n clean: true,\n brokenCreatedInSession: null,\n brokenLinkedSessions: [],\n reconcileSession: null,\n };\n }\n\n if (!input.write) {\n return {\n taskId: input.taskId,\n clean: false,\n brokenCreatedInSession,\n brokenLinkedSessions,\n reconcileSession: null,\n };\n }\n\n if (input._onPhaseCompleted !== undefined) {\n await input._onPhaseCompleted(\"phase-4-snapshot\");\n }\n\n let adHoc: Awaited<ReturnType<typeof createAdHocSessionWithEvent>>;\n try {\n adHoc = await createAdHocSessionWithEvent({\n paths,\n manifest,\n label: buildAdHocReconcileLabel(currentDoc.task.task.title),\n occurredAt: input.occurredAt,\n sessionSource: \"human\",\n workingDirectory: input.workingDirectory,\n invocation: {\n command: \"basou task reconcile\",\n args:\n (input.scope ?? \"single\") === \"single\"\n ? [\"--task\", input.taskId, \"--write\"]\n : [\"--write\"],\n },\n taskId: input.taskId,\n targetEventBuilders: [\n (sessionId, eventId) =>\n buildTaskReconciledEvent({\n eventId,\n sessionId,\n taskId: input.taskId,\n removedCreatedInSession: brokenCreatedInSession,\n createdInSessionReplacement: brokenCreatedInSession !== null ? sessionId : null,\n removedLinkedSessions: brokenLinkedSessions,\n occurredAt: input.occurredAt,\n }),\n ],\n });\n } catch (error: unknown) {\n if (error instanceof FailedToFinalizeError) {\n throw new TaskWriteAfterEventError({\n taskId: input.taskId,\n eventId: error.targetEventIds[0] as PrefixedId<\"evt\">,\n sessionId: error.sessionId,\n phase: \"reconcile-finalize\",\n cause: error,\n });\n }\n throw error;\n }\n\n if (input._onPhaseCompleted !== undefined) {\n await input._onPhaseCompleted(\"phase-5-bulk-write\");\n }\n\n const anchorEventId = adHoc.targetEventIds[0] as PrefixedId<\"evt\">;\n\n const postSnapshot = await computeTaskMdSnapshot(paths, input.taskId);\n if (postSnapshot.mtimeMs !== preSnapshot.mtimeMs || postSnapshot.hash !== preSnapshot.hash) {\n throw new TaskWriteAfterEventError({\n taskId: input.taskId,\n eventId: anchorEventId,\n sessionId: adHoc.sessionId,\n phase: \"reconcile-concurrent\",\n cause: new Error(\"task.md changed during reconcile\"),\n });\n }\n\n const repaired = buildReconciledDoc({\n currentDoc,\n brokenCreatedInSession,\n brokenLinkedSessions,\n reconcileSessionId: adHoc.sessionId,\n occurredAt: input.occurredAt,\n });\n try {\n await writeTaskFile(paths, input.taskId, repaired, { mode: \"overwrite\" });\n } catch (error: unknown) {\n throw new TaskWriteAfterEventError({\n taskId: input.taskId,\n eventId: anchorEventId,\n sessionId: adHoc.sessionId,\n phase: \"reconcile\",\n cause: error,\n });\n }\n\n await safeUpdateTaskIndex(paths, {\n kind: \"update\",\n entry: buildTaskIndexEntry(repaired.task.task),\n });\n\n return {\n taskId: input.taskId,\n clean: false,\n brokenCreatedInSession,\n brokenLinkedSessions,\n reconcileSession: {\n sessionId: adHoc.sessionId,\n eventId: anchorEventId,\n },\n };\n}\n\n/**\n * Reconcile every task in `.basou/tasks/`. Continues on per-task failures so\n * an isolated {@link TaskWriteAfterEventError} does not stop the batch.\n * Malformed task.md files are skipped silently and excluded from `scanned`.\n */\nexport async function reconcileAllTasks(\n paths: BasouPaths,\n manifest: Manifest,\n input: ReconcileAllTasksInput,\n options: ReconcileAllTasksOptions = {},\n): Promise<ReconcileAllResult> {\n const taskIds = await enumerateTaskIds(paths);\n const results: ReconcileResult[] = [];\n const failed: ReconcileFailure[] = [];\n let scanned = 0;\n\n for (const id of taskIds) {\n // Probe readability first so malformed task.md does NOT inflate `scanned`\n // and never reaches the reconcile flow. The readTaskFile call is replayed\n // inside reconcileTask itself — re-reading is cheap and keeps reconcileTask's\n // contract single-purpose.\n try {\n await readTaskFile(paths, id);\n } catch {\n continue;\n }\n scanned += 1;\n\n try {\n const r = await reconcileTask(paths, manifest, {\n taskId: id as PrefixedId<\"task\">,\n occurredAt: input.occurredAt(),\n workingDirectory: input.workingDirectory,\n write: input.write,\n scope: \"all\",\n });\n if (options.includeClean === true || !r.clean) {\n results.push(r);\n }\n } catch (error: unknown) {\n const errorClass = error instanceof Error ? error.constructor.name : \"Error\";\n const phase = error instanceof TaskWriteAfterEventError ? error.phase : null;\n failed.push({\n taskId: id as PrefixedId<\"task\">,\n errorClass,\n phase,\n });\n }\n }\n\n return { results, failed, scanned };\n}\n\n// ============================================================================\n// Linkage refresh: events.jsonl → task.md `linked_sessions[]` forward sync\n// ============================================================================\n\n/**\n * Single-task linkage refresh result. In `write: false` mode this is a pure\n * dry-run report (no event, no task.md change); `addedLinkedSessions` and\n * `removedLinkedSessions` describe what would change. In `write: true` mode\n * the same fields describe what did change and `refreshSession` carries the\n * ad-hoc session + `task_linkage_refreshed` event ids that were minted.\n *\n * `clean === true` means the existing `task.md.linked_sessions[]` already\n * matches the union of `session.yaml.task_id` matches plus the anchor\n * (`created_in_session`) — no event fired, no overwrite.\n */\nexport type RefreshLinkageResult = {\n taskId: PrefixedId<\"task\">;\n clean: boolean;\n addedLinkedSessions: PrefixedId<\"ses\">[];\n removedLinkedSessions: PrefixedId<\"ses\">[];\n /** Number of entries in `linked_sessions[]` after the refresh would run. */\n finalCount: number;\n refreshSession: {\n sessionId: PrefixedId<\"ses\">;\n eventId: PrefixedId<\"evt\">;\n } | null;\n};\n\nexport type RefreshLinkageInput = {\n taskId: PrefixedId<\"task\">;\n occurredAt: string;\n workingDirectory: string;\n write: boolean;\n};\n\ntype DetectedLinkageDelta = {\n addedLinkedSessions: PrefixedId<\"ses\">[];\n removedLinkedSessions: PrefixedId<\"ses\">[];\n finalLinkedSessions: PrefixedId<\"ses\">[];\n};\n\n// Re-derive `linked_sessions[]` from the source of truth: every\n// `session.yaml` whose `task_id` points at this task, plus the\n// `created_in_session` anchor (which is preserved even if its session.yaml\n// no longer carries the task_id — that flavour of drift is the\n// `task reconcile` path's concern, not this one).\n//\n// `enumerateSessionDirs` already filters to dir-named-`ses_<ulid>` entries.\n// Sessions whose `session.yaml` is missing or schema-invalid are silently\n// skipped so a single broken session does not abort the workspace-wide\n// refresh; surfacing those is the responsibility of the session-integrity\n// tooling.\nasync function detectLinkageDelta(\n paths: BasouPaths,\n task: Task[\"task\"],\n): Promise<DetectedLinkageDelta> {\n const sessionIds = await enumerateSessionDirs(paths);\n const reachable = new Set<string>();\n for (const sid of sessionIds) {\n try {\n const doc = await readSessionYaml(paths, sid);\n if (doc.session.task_id === task.id) {\n reachable.add(sid);\n }\n } catch {\n // Missing / malformed session.yaml — skip. Surfacing those is the\n // responsibility of session-integrity tooling, not the linkage-refresh\n // path; a single corrupt session.yaml must not abort the workspace\n // scan.\n }\n }\n // The session ⇆ task anchor invariant\n // (`docs/spec/workspace.md#21-confirmed-invariants`) requires\n // `linked_sessions[]` to always contain `created_in_session`. Preserve it\n // here even if the session.yaml was hand-edited to clear task_id\n // (rare; handled by reconcile).\n const finalSet = new Set<string>(reachable);\n finalSet.add(task.created_in_session);\n\n const currentSet = new Set<string>(task.linked_sessions);\n const addedLinkedSessions: PrefixedId<\"ses\">[] = [];\n const removedLinkedSessions: PrefixedId<\"ses\">[] = [];\n for (const sid of finalSet) {\n if (!currentSet.has(sid)) addedLinkedSessions.push(sid as PrefixedId<\"ses\">);\n }\n for (const sid of currentSet) {\n if (!finalSet.has(sid)) removedLinkedSessions.push(sid as PrefixedId<\"ses\">);\n }\n // Stable ordering: ULID-ascending so two runs against the same workspace\n // produce identical event payloads (matters for replay determinism).\n addedLinkedSessions.sort();\n removedLinkedSessions.sort();\n const finalLinkedSessions = [...finalSet].sort() as PrefixedId<\"ses\">[];\n return { addedLinkedSessions, removedLinkedSessions, finalLinkedSessions };\n}\n\nfunction buildRefreshedDoc(input: {\n currentDoc: TaskDocument;\n finalLinkedSessions: ReadonlyArray<PrefixedId<\"ses\">>;\n refreshSessionId: PrefixedId<\"ses\">;\n occurredAt: string;\n}): TaskDocument {\n // Include the refresh session itself in `linked_sessions` (it is the\n // session that wrote the `task_linkage_refreshed` event, so it is by\n // definition linked). Deduplicate via a Set in case the ad-hoc session id\n // somehow already shows up in finalLinkedSessions (defensive).\n const merged = new Set<string>(input.finalLinkedSessions);\n merged.add(input.refreshSessionId);\n const linked = [...merged].sort() as PrefixedId<\"ses\">[];\n const next: Task = {\n ...input.currentDoc.task,\n task: {\n ...input.currentDoc.task.task,\n updated_at: input.occurredAt,\n linked_sessions: linked,\n },\n };\n return { task: next, body: input.currentDoc.body };\n}\n\n/**\n * Refresh `task.md.linked_sessions[]` so it matches the union of\n * `session.yaml.task_id` references in the workspace plus the\n * `created_in_session` anchor. In `write: false` this is a pure read-only\n * report; in `write: true` the diff is recorded as a\n * `task_linkage_refreshed` event inside a fresh ad-hoc session and the\n * task.md is overwritten with the new snapshot.\n *\n * Stages mirror `reconcileTask` so the operator gets the same\n * \"do-not-rerun\" hint shape on partial failure:\n * 1. Boundary parse\n * 2. Read task.md AND snapshot its mtime/hash from the same raw bytes\n * 3. Detect linkage delta (= scan workspace session.yaml)\n * 4. Early return when clean\n * 5. Mint ad-hoc session + `task_linkage_refreshed` event (catch\n * `FailedToFinalizeError` → `phase: \"linkage-refresh-finalize\"`)\n * 6. Re-snapshot task.md; if changed since stage 2 →\n * `phase: \"linkage-refresh-concurrent\"`\n * 7. Overwrite task.md; failure → `phase: \"linkage-refresh\"`\n *\n * The refresh event is distinct from `task_reconciled` (= broken-ref\n * cleanup, `.strict()` with broken-ref-specific fields) so each event\n * carries a single, focused audit story. Reusing `task_reconciled` here\n * would either redefine its semantics or require widening its strict\n * schema, both of which break replay determinism for older events.\n */\nexport async function refreshTaskLinkedSessions(\n paths: BasouPaths,\n manifest: Manifest,\n input: RefreshLinkageInput,\n): Promise<RefreshLinkageResult> {\n TaskIdSchema.parse(input.taskId);\n\n // Per-task lock spans the entire refresh window so the snapshot taken in\n // stage 2 cannot be invalidated by another writer the helper does not see;\n // the stage 6 mtime/hash probe still acts as a belt-and-braces guard\n // against drift from non-locked code paths (e.g. an external `vi` edit).\n const handle = await acquireLock(paths, \"task\", input.taskId);\n try {\n return await refreshTaskLinkedSessionsLocked(paths, manifest, input);\n } finally {\n await handle.release();\n }\n}\n\nasync function refreshTaskLinkedSessionsLocked(\n paths: BasouPaths,\n manifest: Manifest,\n input: RefreshLinkageInput,\n): Promise<RefreshLinkageResult> {\n const { doc: currentDoc, snapshot: preSnapshot } = await readTaskFileWithSnapshot(\n paths,\n input.taskId,\n );\n const { addedLinkedSessions, removedLinkedSessions, finalLinkedSessions } =\n await detectLinkageDelta(paths, currentDoc.task.task);\n\n if (addedLinkedSessions.length === 0 && removedLinkedSessions.length === 0) {\n return {\n taskId: input.taskId,\n clean: true,\n addedLinkedSessions: [],\n removedLinkedSessions: [],\n finalCount: finalLinkedSessions.length,\n refreshSession: null,\n };\n }\n\n if (!input.write) {\n return {\n taskId: input.taskId,\n clean: false,\n addedLinkedSessions,\n removedLinkedSessions,\n finalCount: finalLinkedSessions.length,\n refreshSession: null,\n };\n }\n\n // The refresh session is itself a new linked entry; account for it on\n // the event payload's `final_count` so the audit number matches the\n // post-write task.md. This is a +1 over the workspace-scan count.\n const finalCountWithRefreshSession = finalLinkedSessions.length + 1;\n\n let adHoc: Awaited<ReturnType<typeof createAdHocSessionWithEvent>>;\n try {\n adHoc = await createAdHocSessionWithEvent({\n paths,\n manifest,\n label: buildAdHocRefreshLinkageLabel(currentDoc.task.task.title),\n occurredAt: input.occurredAt,\n sessionSource: \"human\",\n workingDirectory: input.workingDirectory,\n invocation: {\n command: \"basou task refresh-linkage\",\n args: [input.taskId, \"--write\"],\n },\n taskId: input.taskId,\n targetEventBuilders: [\n (sessionId, eventId) =>\n buildTaskLinkageRefreshedEvent({\n eventId,\n sessionId,\n taskId: input.taskId,\n addedLinkedSessions,\n removedLinkedSessions,\n finalCount: finalCountWithRefreshSession,\n occurredAt: input.occurredAt,\n }),\n ],\n });\n } catch (error: unknown) {\n if (error instanceof FailedToFinalizeError) {\n throw new TaskWriteAfterEventError({\n taskId: input.taskId,\n eventId: error.targetEventIds[0] as PrefixedId<\"evt\">,\n sessionId: error.sessionId,\n phase: \"linkage-refresh-finalize\",\n cause: error,\n });\n }\n throw error;\n }\n\n const anchorEventId = adHoc.targetEventIds[0] as PrefixedId<\"evt\">;\n\n const postSnapshot = await computeTaskMdSnapshot(paths, input.taskId);\n if (postSnapshot.mtimeMs !== preSnapshot.mtimeMs || postSnapshot.hash !== preSnapshot.hash) {\n throw new TaskWriteAfterEventError({\n taskId: input.taskId,\n eventId: anchorEventId,\n sessionId: adHoc.sessionId,\n phase: \"linkage-refresh-concurrent\",\n cause: new Error(\"task.md changed during linkage refresh\"),\n });\n }\n\n const refreshed = buildRefreshedDoc({\n currentDoc,\n finalLinkedSessions,\n refreshSessionId: adHoc.sessionId,\n occurredAt: input.occurredAt,\n });\n try {\n await writeTaskFile(paths, input.taskId, refreshed, { mode: \"overwrite\" });\n } catch (error: unknown) {\n throw new TaskWriteAfterEventError({\n taskId: input.taskId,\n eventId: anchorEventId,\n sessionId: adHoc.sessionId,\n phase: \"linkage-refresh\",\n cause: error,\n });\n }\n\n await safeUpdateTaskIndex(paths, {\n kind: \"update\",\n entry: buildTaskIndexEntry(refreshed.task.task),\n });\n\n return {\n taskId: input.taskId,\n clean: false,\n addedLinkedSessions,\n removedLinkedSessions,\n finalCount: finalCountWithRefreshSession,\n refreshSession: {\n sessionId: adHoc.sessionId,\n eventId: anchorEventId,\n },\n };\n}\n\n// ============================================================================\n// editTask — field-level update (no event for pure title edits)\n// ============================================================================\n\nexport type EditTaskInput = {\n paths: BasouPaths;\n taskId: PrefixedId<\"task\">;\n /** New title; rejected when empty. Undefined leaves the field unchanged. */\n title?: string;\n /**\n * New status; routed through transition rules so the call rejects\n * invalid edges (e.g. `done -> planned`). Undefined leaves the field\n * unchanged.\n */\n newStatus?: TaskStatus;\n occurredAt: string;\n /**\n * Required when {@link newStatus} is provided — the status change fires\n * a `task_status_changed` event in a fresh ad-hoc session, which needs\n * a Manifest to seed the new session record. Title-only edits ignore\n * this field.\n */\n manifest?: Manifest;\n /** Working directory for the ad-hoc status-change session. */\n workingDirectory?: string;\n};\n\nexport type EditTaskResult = {\n taskId: PrefixedId<\"task\">;\n titleUpdated: boolean;\n statusUpdated: boolean;\n /** When {@link statusUpdated} is true, the previous status before the edit. */\n previousStatus: TaskStatus | null;\n /** When {@link statusUpdated} is true, the new status. */\n newStatus: TaskStatus | null;\n /** ad-hoc session minted when status was changed; null for title-only edits. */\n statusChangeSession: {\n sessionId: PrefixedId<\"ses\">;\n eventId: PrefixedId<\"evt\">;\n } | null;\n};\n\n/**\n * Update one or both of the user-editable fields on a task.md.\n *\n * - `title`: in-place overwrite of `task.md` only. v0.1 does not emit a\n * `task_title_changed` event — title changes are storage-level metadata\n * maintenance, not part of the audit trail.\n * - `newStatus`: routed through {@link updateTaskStatusWithEvent} so the\n * ALLOWED_TRANSITIONS gate is honored and a `task_status_changed` event is\n * appended to the audit trail.\n *\n * When both are supplied the status change runs first (= event committed)\n * and then the title overwrite runs against the freshly updated task.md\n * (= same `updated_at` from the status change). A failure of the\n * subsequent title overwrite leaves the status change committed; the\n * status-change side of an edit is the only side with an event, so the\n * audit trail is consistent regardless.\n */\nexport async function editTask(input: EditTaskInput): Promise<EditTaskResult> {\n TaskIdSchema.parse(input.taskId);\n if (input.title === undefined && input.newStatus === undefined) {\n throw new Error(\"Nothing to edit: provide --title or --status\");\n }\n if (input.title !== undefined) {\n TaskTitleSchema.parse(input.title);\n }\n\n let statusUpdated = false;\n let previousStatus: TaskStatus | null = null;\n let newStatus: TaskStatus | null = null;\n let statusChangeSession: EditTaskResult[\"statusChangeSession\"] = null;\n\n // Stage 1: status change (if any). Failure here exits with the existing\n // transition / not-found errors and leaves task.md untouched.\n if (input.newStatus !== undefined) {\n if (input.manifest === undefined || input.workingDirectory === undefined) {\n throw new Error(\"editTask requires manifest + workingDirectory when newStatus is supplied\");\n }\n const result = await updateTaskStatusWithEvent({\n mode: \"ad-hoc\",\n paths: input.paths,\n manifest: input.manifest,\n occurredAt: input.occurredAt,\n taskId: input.taskId,\n newStatus: input.newStatus,\n workingDirectory: input.workingDirectory,\n });\n statusUpdated = true;\n previousStatus = result.previousStatus;\n newStatus = result.newStatus;\n statusChangeSession = { sessionId: result.sessionId, eventId: result.eventId };\n }\n\n // Stage 2: title overwrite (if any). Re-read so the status change above\n // (which updated linked_sessions / updated_at) is preserved. The lock is\n // taken HERE (not around the whole function) because stage 1 calls\n // `updateTaskStatusWithEvent` which acquires the same per-task lock\n // internally — wrapping both stages in one outer lock would deadlock on\n // its own helper. Each stage being independently atomic is the operator-\n // visible invariant we care about, not stage-1-and-2 atomicity.\n let titleUpdated = false;\n if (input.title !== undefined) {\n const handle = await acquireLock(input.paths, \"task\", input.taskId);\n try {\n const doc = await readTaskFile(input.paths, input.taskId);\n if (doc.task.task.title !== input.title) {\n const next: Task = {\n ...doc.task,\n task: {\n ...doc.task.task,\n title: input.title,\n updated_at: input.occurredAt,\n },\n };\n await writeTaskFile(\n input.paths,\n input.taskId,\n { task: next, body: doc.body },\n { mode: \"overwrite\" },\n );\n await safeUpdateTaskIndex(input.paths, {\n kind: \"update\",\n entry: buildTaskIndexEntry(next.task),\n });\n titleUpdated = true;\n }\n } finally {\n await handle.release();\n }\n }\n\n return {\n taskId: input.taskId,\n titleUpdated,\n statusUpdated,\n previousStatus,\n newStatus,\n statusChangeSession,\n };\n}\n\n// ============================================================================\n// deleteTask — destructive removal with audit event\n// ============================================================================\n\nexport type DeleteTaskInput = {\n paths: BasouPaths;\n manifest: Manifest;\n taskId: PrefixedId<\"task\">;\n occurredAt: string;\n workingDirectory: string;\n};\n\nexport type DeleteTaskResult = {\n taskId: PrefixedId<\"task\">;\n title: string;\n sessionId: PrefixedId<\"ses\">;\n eventId: PrefixedId<\"evt\">;\n};\n\n/**\n * Hard-delete a task.md file with a `task_deleted` audit event.\n *\n * Sequence:\n * 1. Read task.md to capture the current title (which goes onto the\n * event payload so the audit record is self-describing even after\n * the file is gone).\n * 2. Mint an ad-hoc session, fire `task_deleted` as the target event.\n * The session's `task_id` is intentionally NOT pinned to the\n * to-be-deleted task — otherwise the audit session would carry a\n * broken reference the moment we unlink the file.\n * 3. Unlink `<paths.tasks>/<task_id>.md`.\n *\n * Failure of step 3 after the event is committed surfaces as a\n * {@link TaskWriteAfterEventError} with `phase: \"delete\"`; the operator\n * is told the event is durable but task.md still exists, and that a\n * manual `rm` (or a rerun) is required.\n *\n * v0.1 contract: no tombstone, no recovery. Restoring a deleted task is\n * not supported; the event payload (`task_id` + `title`) is the only\n * persistent record after the unlink succeeds.\n */\nexport async function deleteTask(input: DeleteTaskInput): Promise<DeleteTaskResult> {\n TaskIdSchema.parse(input.taskId);\n\n // Per-task lock keeps the read → audit event → unlink chain free of a\n // concurrent writer that could otherwise observe task.md after we read it\n // and before we delete it (e.g. another CLI running `task status`).\n const handle = await acquireLock(input.paths, \"task\", input.taskId);\n try {\n return await deleteTaskLocked(input);\n } finally {\n await handle.release();\n }\n}\n\nasync function deleteTaskLocked(input: DeleteTaskInput): Promise<DeleteTaskResult> {\n // Stage 1: capture the current title before mint.\n const doc = await readTaskFile(input.paths, input.taskId);\n const title = doc.task.task.title;\n\n // Stage 2: fire the audit event. NOTE we do NOT pass `taskId` to the\n // ad-hoc session — pinning the session to a task that is about to vanish\n // would create a guaranteed broken reference on session.yaml.task_id.\n const adHoc = await createAdHocSessionWithEvent({\n paths: input.paths,\n manifest: input.manifest,\n label: buildAdHocDeleteLabel(title),\n occurredAt: input.occurredAt,\n sessionSource: \"human\",\n workingDirectory: input.workingDirectory,\n invocation: {\n command: \"basou task delete\",\n args: [input.taskId, \"--yes\"],\n },\n targetEventBuilders: [\n (sessionId, eventId) =>\n buildTaskDeletedEvent({\n eventId,\n sessionId,\n taskId: input.taskId,\n title,\n occurredAt: input.occurredAt,\n }),\n ],\n });\n const eventId = adHoc.targetEventIds[0] as PrefixedId<\"evt\">;\n\n // Stage 3: unlink the file.\n try {\n await unlink(join(input.paths.tasks, `${input.taskId}.md`));\n } catch (error: unknown) {\n throw new TaskWriteAfterEventError({\n taskId: input.taskId,\n eventId,\n sessionId: adHoc.sessionId,\n phase: \"delete\",\n cause: error,\n });\n }\n\n await safeUpdateTaskIndex(input.paths, { kind: \"remove\", id: input.taskId });\n\n return {\n taskId: input.taskId,\n title,\n sessionId: adHoc.sessionId,\n eventId,\n };\n}\n\n// ============================================================================\n// archiveTask — move main/<id>.md to archive/<id>.md with audit event\n// ============================================================================\n\nexport type ArchiveTaskInput = {\n paths: BasouPaths;\n manifest: Manifest;\n taskId: PrefixedId<\"task\">;\n occurredAt: string;\n workingDirectory: string;\n};\n\nexport type ArchiveTaskResult = {\n taskId: PrefixedId<\"task\">;\n title: string;\n sessionId: PrefixedId<\"ses\">;\n eventId: PrefixedId<\"evt\">;\n};\n\n/**\n * Move a task.md file from `<paths.tasks>/<id>.md` to\n * `<paths.tasks>/archive/<id>.md` with a `task_archived` audit event.\n *\n * Sequence:\n * 1. Read task.md to capture the current title and existing content.\n * 2. Mint an ad-hoc session, fire `task_archived` as the target event.\n * The session's `task_id` IS pinned to the archived task — unlike\n * `task_deleted`, the task continues to exist (just at a new path),\n * so the session-task linkage stays a valid forward reference.\n * 3. Append the audit session to the task's `linked_sessions[]` and\n * overwrite the source task.md so the snapshot reflects the archive\n * session before the move.\n * 4. Ensure the archive directory exists.\n * 5. Rename main/<id>.md to archive/<id>.md (= atomic on the same fs).\n *\n * Failure modes after step 2 surface as\n * {@link TaskWriteAfterEventError} with `phase: \"archive\"`; the operator\n * is told the event is durable but the on-disk move is incomplete and\n * must be resolved manually (typically by rerunning `task archive`).\n */\nexport async function archiveTask(input: ArchiveTaskInput): Promise<ArchiveTaskResult> {\n TaskIdSchema.parse(input.taskId);\n\n // Per-task lock spans read → audit event → task.md overwrite → rename so\n // a concurrent writer cannot interleave between the linked_sessions\n // append and the move into archive/.\n const handle = await acquireLock(input.paths, \"task\", input.taskId);\n try {\n return await archiveTaskLocked(input);\n } finally {\n await handle.release();\n }\n}\n\nasync function archiveTaskLocked(input: ArchiveTaskInput): Promise<ArchiveTaskResult> {\n const doc = await readTaskFile(input.paths, input.taskId);\n const title = doc.task.task.title;\n\n const adHoc = await createAdHocSessionWithEvent({\n paths: input.paths,\n manifest: input.manifest,\n label: buildAdHocArchiveLabel(title),\n occurredAt: input.occurredAt,\n sessionSource: \"human\",\n workingDirectory: input.workingDirectory,\n invocation: {\n command: \"basou task archive\",\n args: [input.taskId, \"--yes\"],\n },\n taskId: input.taskId,\n targetEventBuilders: [\n (sessionId, eventId) =>\n buildTaskArchivedEvent({\n eventId,\n sessionId,\n taskId: input.taskId,\n title,\n occurredAt: input.occurredAt,\n }),\n ],\n });\n const eventId = adHoc.targetEventIds[0] as PrefixedId<\"evt\">;\n\n // Stage 3-5 share the same recovery contract: any failure surfaces as\n // phase \"archive\" so the operator gets a uniform \"rerun task archive\"\n // hint. Specific failure cases:\n // - 3: writeTaskFile (overwrite) — fs/yaml-serialize error\n // - 4: mkdir of archive dir — usually EACCES\n // - 5: rename across the same fs — EEXIST when archive/<id>.md is\n // already there, EACCES, or rare ENOSPC\n try {\n const linked = doc.task.task.linked_sessions;\n const merged = linked.includes(adHoc.sessionId) ? linked : [...linked, adHoc.sessionId];\n const next: Task = {\n ...doc.task,\n task: {\n ...doc.task.task,\n updated_at: input.occurredAt,\n linked_sessions: merged,\n },\n };\n await writeTaskFile(\n input.paths,\n input.taskId,\n { task: next, body: doc.body },\n { mode: \"overwrite\" },\n );\n\n await mkdir(archiveTasksDir(input.paths), { recursive: true });\n await rename(\n join(input.paths.tasks, `${input.taskId}.md`),\n join(archiveTasksDir(input.paths), `${input.taskId}.md`),\n );\n } catch (error: unknown) {\n throw new TaskWriteAfterEventError({\n taskId: input.taskId,\n eventId,\n sessionId: adHoc.sessionId,\n phase: \"archive\",\n cause: error,\n });\n }\n\n // Archived tasks live under tasks/archive/<id>.md, which enumerateTaskIds\n // ignores. Remove the entry from the active index so `task list` matches\n // disk reality.\n await safeUpdateTaskIndex(input.paths, { kind: \"remove\", id: input.taskId });\n\n return {\n taskId: input.taskId,\n title,\n sessionId: adHoc.sessionId,\n eventId,\n };\n}\n","import { z } from \"zod\";\nimport {\n IsoTimestampSchema,\n SchemaVersionSchema,\n SessionIdSchema,\n TaskIdSchema,\n WorkspaceIdSchema,\n} from \"./shared.schema.js\";\n\n/**\n * Task lifecycle states.\n *\n * The storage layer's `ALLOWED_TRANSITIONS` map (= source of truth in\n * `tasks.ts`) is the authoritative graph; the comment below is a snapshot.\n * `planned` reaches `done` / `cancelled` directly so tasks completed (or\n * abandoned) outside an explicit in-progress phase can close in a single\n * CLI call:\n *\n * planned → {in_progress | done | cancelled}\n * in_progress → {done | cancelled}\n * done / cancelled = terminal\n *\n * Self-edges are rejected so the audit trail stays monotonic.\n */\nexport const TaskStatusSchema = z.enum([\"planned\", \"in_progress\", \"done\", \"cancelled\"]);\n/** Inferred runtime type for {@link TaskStatusSchema}. */\nexport type TaskStatus = z.infer<typeof TaskStatusSchema>;\n\nconst TaskInnerSchema = z.object({\n id: TaskIdSchema,\n title: z.string().min(1),\n label: z.string().min(1).optional(),\n status: TaskStatusSchema,\n created_at: IsoTimestampSchema,\n updated_at: IsoTimestampSchema,\n workspace_id: WorkspaceIdSchema,\n /**\n * Session id that anchors this task. For freshly created tasks it is the\n * session that wrote the `task_created` event (= ad-hoc reconcile target\n * for ad-hoc paths, or the target session id for attach paths). After\n * `basou task reconcile --write` repairs a broken anchor the\n * value is replaced with the ad-hoc reconcile session id; the old broken\n * session_id is preserved on the `task_reconciled` event payload via\n * `removed_created_in_session` for audit. So this field always names a\n * reachable session, even after the original anchor is gone.\n */\n created_in_session: SessionIdSchema,\n /**\n * Snapshot of sessions linked to this task. The events.jsonl history is\n * the source of truth (see\n * `docs/spec/generated-markdown.md#105-decisionsmd-generation-principle`);\n * this field is maintained as a UX-only cache so editors can read the\n * task.md and immediately see related sessions. Defaults to `[]` for\n * backward compatibility.\n */\n linked_sessions: z.array(SessionIdSchema).default([]),\n});\n\n/**\n * Schema for the YAML front matter of `.basou/tasks/<task_id>.md`.\n *\n * The markdown body after the front matter is intentionally NOT modelled\n * here — it is free-form user-edited content. The storage layer splits\n * the file into `task` (this schema) and `body` (the trailing string).\n */\nexport const TaskSchema = z.object({\n schema_version: SchemaVersionSchema,\n task: TaskInnerSchema,\n});\n/** Inferred runtime type for {@link TaskSchema}. */\nexport type Task = z.infer<typeof TaskSchema>;\n","import { mkdir, rm } from \"node:fs/promises\";\nimport { homedir } from \"node:os\";\nimport { join } from \"node:path\";\nimport { appendChainedEventLocked } from \"../events/chained-append.js\";\nimport { writeEventsBulk } from \"../events/event-writer.js\";\nimport { type PrefixedId, prefixedUlid } from \"../ids/ulid.js\";\nimport { findErrorCode } from \"../lib/error-codes.js\";\nimport { sanitizeWorkingDirectory } from \"../lib/path-sanitizer.js\";\nimport type { Event } from \"../schemas/event.schema.js\";\nimport type { Manifest } from \"../schemas/manifest.schema.js\";\nimport {\n type Session,\n SessionSchema,\n type SessionSourceKind,\n SessionSourceKindSchema,\n type SessionStatus,\n} from \"../schemas/session.schema.js\";\nimport { SessionIdSchema } from \"../schemas/shared.schema.js\";\nimport type { BasouPaths } from \"./basou-dir.js\";\nimport { acquireLock } from \"./lockfile.js\";\nimport { readSessionYaml } from \"./sessions.js\";\nimport { linkYamlFile, overwriteYamlFile } from \"./yaml-store.js\";\n\n// ============================================================================\n// Finalization-failure error\n// ============================================================================\n\n/**\n * Thrown when the ad-hoc session was fully written to disk (4 lifecycle\n * events + N target events plus the initial `session.yaml`) but the final\n * `session.yaml` update to status `completed` failed. The caller can read\n * `sessionId` / `targetEventIds` to emit a retry-duplicate-prevention\n * warning, since the target events themselves are already persisted in\n * `events.jsonl`.\n *\n * `targetEventIds` is an array because a single ad-hoc session may carry\n * multiple target events (e.g. `task new --status done` fires both\n * `task_created` and `task_status_changed`). Callers that need a single\n * anchor id should use `targetEventIds[0]`, which by convention is the\n * primary event for the operation.\n */\nexport class FailedToFinalizeError extends Error {\n readonly sessionId: PrefixedId<\"ses\">;\n readonly targetEventIds: ReadonlyArray<PrefixedId<\"evt\">>;\n\n constructor(\n sessionId: PrefixedId<\"ses\">,\n targetEventIds: ReadonlyArray<PrefixedId<\"evt\">>,\n cause: unknown,\n ) {\n super(\"Failed to finalize ad-hoc session\", { cause });\n this.name = \"FailedToFinalizeError\";\n if (targetEventIds.length === 0) {\n // Defensive guard for direct (non-orchestrator) constructors. The\n // orchestrator already rejects an empty `targetEventBuilders` array\n // before any ID minting, but `FailedToFinalizeError` is a public\n // exported class and `error-render.ts` reads `targetEventIds[0]` as\n // the operator-facing anchor — an empty array there would surface as\n // `\"Recorded undefined ...\"`.\n throw new Error(\"FailedToFinalizeError requires at least one target event id\");\n }\n this.sessionId = sessionId;\n this.targetEventIds = targetEventIds;\n }\n}\n\n// ============================================================================\n// Ad-hoc session path\n// ============================================================================\n\nexport type CreateAdHocSessionInput = {\n paths: BasouPaths;\n manifest: Manifest;\n /** Pre-built session label (caller is responsible for truncation). */\n label: string;\n /** ISO timestamp shared across the 5 lifecycle/target events. */\n occurredAt: string;\n sessionSource: SessionSourceKind;\n workingDirectory: string;\n invocation: { command: string; args: string[] };\n /**\n * Optional task id to link this ad-hoc session to. When provided, both the\n * initial and the final `session.yaml` writes embed `task_id` so the\n * single-session-to-single-task invariant (see\n * `docs/spec/workspace.md#21-confirmed-invariants`) holds for task-flavoured\n * ad-hoc paths (`basou task new` / `task status` without `--session`).\n * Defaults to `null` so existing callers (decision / note) are unchanged.\n */\n taskId?: PrefixedId<\"task\">;\n /**\n * Builds the variant-specific target events. Each builder receives the\n * freshly minted session id and a freshly minted event id (one per\n * builder) so callers can fill in cross-reference fields (`decision_id`,\n * `body`, ...) without owning ID generation.\n *\n * The most common case is a single-element array (`[builder]`) for the\n * one-target-event flows (`basou decision record`, `basou session note`,\n * `basou task new --status planned`, `basou task status`,\n * `basou task reconcile`). Two-element arrays are used by\n * `basou task new --status done|cancelled` to emit `task_created` plus\n * an immediate `task_status_changed` in the same atomic bulk write.\n *\n * Must be non-empty; an empty array is rejected at the start of\n * {@link createAdHocSessionWithEvent}.\n */\n targetEventBuilders: ReadonlyArray<\n (sessionId: PrefixedId<\"ses\">, eventId: PrefixedId<\"evt\">) => Event\n >;\n};\n\nexport type CreateAdHocSessionResult = {\n sessionId: PrefixedId<\"ses\">;\n /**\n * Target event IDs in the order their builders were supplied. Length\n * equals `input.targetEventBuilders.length`. Callers that conceptually\n * have a single anchor event should use `targetEventIds[0]`.\n */\n targetEventIds: PrefixedId<\"evt\">[];\n /**\n * Lifecycle event IDs in chronological order:\n * `[started, status→running, status→completed, ended]`.\n * Target event IDs are reported separately in {@link targetEventIds}.\n */\n lifecycleEventIds: PrefixedId<\"evt\">[];\n};\n\n/**\n * Atomically create a fresh ad-hoc session that produces one or more target\n * events then immediately closes itself. The session lifecycle\n * (`initialized → running → completed`, see\n * `docs/spec/terminal-and-import.md#62-transition-diagram`) is honored:\n * `4 + N` events are\n * written in one bulk atomic pass (where N = number of target builders) and\n * `session.yaml` is written twice (`initialized` → `completed`).\n *\n * The single-target case (N = 1) covers `basou decision record`,\n * `basou session note`, `basou task new --status planned|in_progress`,\n * `basou task status`, and `basou task reconcile`. The two-target case\n * (N = 2) covers `basou task new --status done|cancelled` which fires\n * `task_created` followed immediately by `task_status_changed (planned → terminal)`\n * so the audit trail captures the implicit transition.\n *\n * Failures during `mkdir`, the initial `session.yaml` write, or the bulk\n * `events.jsonl` write trigger a best-effort `rm -rf` of the session\n * directory so partial ad-hoc sessions do not pollute the workspace.\n *\n * A failure on the final `session.yaml` status update is fatal but the\n * session directory is NOT cleaned up — `events.jsonl` is consistent and\n * carries the full lifecycle trail, so callers can reconcile manually. The\n * thrown {@link FailedToFinalizeError} carries the `sessionId` and\n * `targetEventIds` so the CLI layer can warn the user not to re-run the\n * command and duplicate the target events.\n *\n * Direct (non-CLI) callers are self-defended by zod boundary parses on\n * `sessionSource` and the initial session record.\n */\nexport async function createAdHocSessionWithEvent(\n input: CreateAdHocSessionInput,\n): Promise<CreateAdHocSessionResult> {\n // 1. core boundary parse — direct callers may pass arbitrary strings.\n SessionSourceKindSchema.parse(input.sessionSource);\n if (input.targetEventBuilders.length === 0) {\n throw new Error(\"Ad-hoc session requires at least one target event builder\");\n }\n\n // 2. ID minting. One target event id per builder; lifecycle ids are fixed.\n const sessionId = prefixedUlid(\"ses\");\n const startedEventId = prefixedUlid(\"evt\");\n const statusToRunningEventId = prefixedUlid(\"evt\");\n const targetEventIds = input.targetEventBuilders.map(() => prefixedUlid(\"evt\"));\n const statusToCompletedEventId = prefixedUlid(\"evt\");\n const endedEventId = prefixedUlid(\"evt\");\n\n // 3. Build the initial session record (status=initialized) and validate it\n // so a malformed input shape fails fast before any disk write.\n const initialSession: Session = SessionSchema.parse(\n buildInitialSession({\n sessionId,\n workspaceId: input.manifest.workspace.id,\n sourceKind: input.sessionSource,\n startedAt: input.occurredAt,\n label: input.label,\n workingDirectory: input.workingDirectory,\n invocation: input.invocation,\n taskId: input.taskId ?? null,\n }),\n );\n\n // Hold the session lock across the whole create sequence (initial yaml ->\n // bulk events -> final yaml). The session is briefly `initialized` and thus\n // attachable; without the lock a foreign attach could append a line into\n // that window which the atomic bulk write would then clobber, breaking the\n // chain. The session id is freshly minted, so no caller already holds it.\n const sessionDir = join(input.paths.sessions, sessionId);\n const sessionYamlPath = join(sessionDir, \"session.yaml\");\n const lock = await acquireLock(input.paths, \"session\", sessionId);\n let bulkResult: Awaited<ReturnType<typeof writeEventsBulk>> = null;\n try {\n // 4. Create the session directory (recursive=true so a stripped-down\n // workspace with `.basou/sessions` missing still recovers).\n try {\n await mkdir(sessionDir, { recursive: true });\n } catch (error: unknown) {\n throw new Error(\"Failed to create session directory\", { cause: error });\n }\n\n // 5. Initial session.yaml write (status=initialized).\n try {\n await linkYamlFile(sessionYamlPath, initialSession);\n } catch (error: unknown) {\n await rm(sessionDir, { recursive: true, force: true }).catch(() => undefined);\n if (findErrorCode(error, \"EEXIST\")) {\n throw new Error(\"Session directory collision (retry the command)\", {\n cause: error,\n });\n }\n throw error;\n }\n\n // 6. events.jsonl bulk write — the full lifecycle batch written atomically\n // in a single tmp+rename pass, hash-chained (chain:true) so the ad-hoc\n // log is tamper-evident like an imported one. A failure here removes the\n // session directory so no partial state survives (status=initialized +\n // no events is not visible in `basou session list`).\n try {\n const targetEvents: Event[] = input.targetEventBuilders.map((build, index) => {\n const targetEventId = targetEventIds[index] as PrefixedId<\"evt\">;\n return assertTargetEventIdentity(build(sessionId, targetEventId), sessionId, targetEventId);\n });\n const events: Event[] = [\n {\n schema_version: \"0.1.0\",\n id: startedEventId,\n session_id: sessionId,\n occurred_at: input.occurredAt,\n source: \"local-cli\",\n type: \"session_started\",\n },\n {\n schema_version: \"0.1.0\",\n id: statusToRunningEventId,\n session_id: sessionId,\n occurred_at: input.occurredAt,\n source: \"local-cli\",\n type: \"session_status_changed\",\n from: \"initialized\",\n to: \"running\",\n },\n ...targetEvents,\n {\n schema_version: \"0.1.0\",\n id: statusToCompletedEventId,\n session_id: sessionId,\n occurred_at: input.occurredAt,\n source: \"local-cli\",\n type: \"session_status_changed\",\n from: \"running\",\n to: \"completed\",\n },\n {\n schema_version: \"0.1.0\",\n id: endedEventId,\n session_id: sessionId,\n occurred_at: input.occurredAt,\n source: \"local-cli\",\n type: \"session_ended\",\n exit_code: 0,\n },\n ];\n bulkResult = await writeEventsBulk(sessionDir, events, { chain: true });\n } catch (error: unknown) {\n await rm(sessionDir, { recursive: true, force: true }).catch(() => undefined);\n throw error;\n }\n\n // 7. Finalize: overwrite session.yaml with status=completed + ended_at +\n // invocation.exit_code=0 + the integrity head anchor from the chained\n // bulk write. Failure is fatal but events.jsonl is already complete, so\n // the directory is intentionally NOT removed — the caller surfaces the\n // partial state via FailedToFinalizeError.\n try {\n const finalSession: Session = SessionSchema.parse({\n ...initialSession,\n session: {\n ...initialSession.session,\n status: \"completed\" satisfies SessionStatus,\n ended_at: input.occurredAt,\n invocation: { ...initialSession.session.invocation, exit_code: 0 },\n ...(bulkResult !== null\n ? { integrity: { head_hash: bulkResult.headHash, event_count: bulkResult.count } }\n : {}),\n },\n });\n await overwriteYamlFile(sessionYamlPath, finalSession);\n } catch (error: unknown) {\n throw new FailedToFinalizeError(sessionId, targetEventIds, error);\n }\n } finally {\n await lock.release();\n }\n\n return {\n sessionId,\n targetEventIds,\n lifecycleEventIds: [\n startedEventId,\n statusToRunningEventId,\n statusToCompletedEventId,\n endedEventId,\n ],\n };\n}\n\nfunction buildInitialSession(input: {\n sessionId: PrefixedId<\"ses\">;\n workspaceId: PrefixedId<\"ws\">;\n sourceKind: SessionSourceKind;\n startedAt: string;\n label: string;\n workingDirectory: string;\n invocation: { command: string; args: string[] };\n taskId: PrefixedId<\"task\"> | null;\n}): Session {\n return {\n schema_version: \"0.1.0\",\n session: {\n id: input.sessionId,\n label: input.label,\n task_id: input.taskId,\n workspace_id: input.workspaceId,\n source: { kind: input.sourceKind, version: \"0.1.0\" },\n started_at: input.startedAt,\n status: \"initialized\",\n working_directory: sanitizeWorkingDirectory(input.workingDirectory, { homedir: homedir() }),\n invocation: { ...input.invocation, exit_code: null },\n related_files: [],\n events_log: \"events.jsonl\",\n },\n };\n}\n\n// ============================================================================\n// Attach path\n// ============================================================================\n\nexport type AttachableStatus = \"initialized\" | \"running\" | \"waiting_approval\";\n\nconst DEFAULT_ATTACHABLE_STATUSES: ReadonlySet<AttachableStatus> = new Set<AttachableStatus>([\n \"initialized\",\n \"running\",\n \"waiting_approval\",\n]);\n\nexport type AppendEventToExistingInput = {\n paths: BasouPaths;\n /** Already resolved via `resolveSessionId`; parsed at boundary again. */\n sessionId: PrefixedId<\"ses\">;\n attachableStatuses?: ReadonlySet<AttachableStatus>;\n eventBuilder: (eventId: PrefixedId<\"evt\">) => Event;\n};\n\nexport type AppendEventToExistingResult = {\n eventId: PrefixedId<\"evt\">;\n sessionStatus: SessionStatus;\n};\n\n/**\n * Read `session.yaml`, verify the session is in an attachable state, and\n * append a single event to its `events.jsonl`. `session.yaml` is NOT modified\n * so the caller can safely append `decision_recorded` / `note_added` without\n * mutating `related_files`, `summary`, or the session status.\n *\n * Race note: the status check and the event append are not atomic.\n * Between them another writer (e.g. `basou run claude-code` ending its\n * session) can flip the YAML to `completed` and append `session_ended`.\n * v0.1 accepts this race; the `events_say_ended_but_yaml_running`-style\n * suspect rule surfaces the inconsistency. Per-session locking is\n * deferred to a v0.3+ follow-up.\n */\nexport async function appendEventToExistingSession(\n input: AppendEventToExistingInput,\n): Promise<AppendEventToExistingResult> {\n // 1. Boundary parse (direct caller self-defense).\n SessionIdSchema.parse(input.sessionId);\n\n // 2. Read session.yaml.\n const sessionDoc = await readSessionYaml(input.paths, input.sessionId);\n const status = sessionDoc.session.status;\n\n // 3. Status check.\n if (status === \"imported\") {\n throw new Error(\"Cannot attach to imported session\");\n }\n const attachable = input.attachableStatuses ?? DEFAULT_ATTACHABLE_STATUSES;\n if (!attachable.has(status as AttachableStatus)) {\n throw new Error(`Session is not active: ${status}`);\n }\n\n // 4. Mint event ID and build payload.\n const eventId = prefixedUlid(\"evt\");\n const event = assertTargetEventIdentity(input.eventBuilder(eventId), input.sessionId, eventId);\n\n // 5. Append, chaining onto the on-disk tail. The CALLER owns the session\n // lock (decision record / session note / task attach each acquire it\n // around this whole read-check-append window), so the lock-assumed\n // primitive is used here and must NOT re-acquire the lock.\n await appendChainedEventLocked(input.paths, input.sessionId, event);\n\n return { eventId, sessionStatus: status };\n}\n\n/**\n * Defensive check: a builder closure could in principle hand back\n * an event whose `id` or `session_id` differs from the orchestrator's\n * minted values. EventSchema only validates the shape, so this slip would\n * silently corrupt events.jsonl. Reject with a fixed pathless message so\n * direct-caller misuse never reaches disk.\n */\nfunction assertTargetEventIdentity(\n event: Event,\n expectedSessionId: PrefixedId<\"ses\">,\n expectedEventId: PrefixedId<\"evt\">,\n): Event {\n if (event.session_id !== expectedSessionId) {\n throw new Error(\"Target event session_id mismatch\");\n }\n if (event.id !== expectedEventId) {\n throw new Error(\"Target event id mismatch\");\n }\n return event;\n}\n","import { posix as path } from \"node:path\";\n\n/**\n * Options for {@link sanitizePath}. Both `workingDirectory` and `homedir`\n * are absolute POSIX paths the caller has already resolved (typically via\n * `process.cwd()` and `os.homedir()`). Callers are responsible for passing\n * fully normalised values; the sanitizer normalises them again internally\n * so a trailing slash or `.`-segment does not corrupt the prefix match.\n */\nexport type SanitizePathOptions = {\n /**\n * The session's working directory (= the `working_directory` field the\n * caller is about to write). Paths under this directory are rewritten\n * relative to it so the operator-private absolute prefix never leaks\n * into the workspace's persistent state.\n */\n workingDirectory: string;\n /**\n * The operator's home directory. Paths under this directory (but NOT\n * under `workingDirectory`) are rewritten with a `~/` prefix.\n */\n homedir: string;\n};\n\n/**\n * Rewrite an absolute path into a workspace-friendly form so the persisted\n * state of `.basou/` does not leak the operator's machine layout:\n *\n * 1. Paths under `opts.workingDirectory` become repository-relative\n * (e.g. `<wd>/src/x.ts` → `src/x.ts`, `<wd>` itself → `.`).\n * 2. Paths under `opts.homedir` (but not workingDirectory) become\n * tilde-prefixed (`/Users/u/notes/x.md` → `~/notes/x.md`,\n * `/Users/u` → `~`).\n * 3. Anything else — relative paths, system paths under `/etc/*`,\n * `..`-escapes from either base, paths that simply do not share a\n * prefix with either option — is returned verbatim (after `..`\n * normalisation). The sanitizer is intentionally non-redacting on\n * system paths so an operator who deliberately recorded a system\n * file (e.g. `/etc/hosts`) is not silently stripped of context.\n *\n * Hardening:\n * - A null byte in the input is rejected with `Invalid path: contains\n * null byte` (= POSIX path APIs treat \\0 as terminator and any path\n * containing one is malformed; we never accept it on the write side).\n * - `..` segments are resolved purely (no fs access) so the prefix\n * match cannot be defeated by `<wd>/../escape/x.ts` masquerading as\n * workingDirectory-internal.\n * - Backslashes are folded to forward slashes so a Windows-style input\n * can still be matched against POSIX bases. v0.3 targets macOS /\n * Linux only; full Windows support is a v0.4+ task.\n */\nexport function sanitizePath(rawPath: string, opts: SanitizePathOptions): string {\n if (rawPath.includes(\"\\0\")) {\n throw new Error(\"Invalid path: contains null byte\");\n }\n const normalized = path.normalize(rawPath.replace(/\\\\/g, \"/\"));\n const wd = path.normalize(opts.workingDirectory.replace(/\\\\/g, \"/\"));\n const home = path.normalize(opts.homedir.replace(/\\\\/g, \"/\"));\n\n // Only attempt prefix matching for absolute inputs; an already-relative\n // path stays as-is so write paths that pre-relativised do not get\n // mangled.\n if (!path.isAbsolute(normalized)) {\n return normalized;\n }\n\n // (1) workingDirectory-internal -> repo-relative.\n if (normalized === wd) return \".\";\n const wdRel = path.relative(wd, normalized);\n if (wdRel !== \"\" && !wdRel.startsWith(\"..\")) {\n return wdRel;\n }\n\n // (2) homedir-internal -> ~/...\n if (normalized === home) return \"~\";\n const homeRel = path.relative(home, normalized);\n if (homeRel !== \"\" && !homeRel.startsWith(\"..\")) {\n return `~/${homeRel}`;\n }\n\n // (3) preserve as-is.\n return normalized;\n}\n\n/**\n * Sanitize the `working_directory` field itself. This is a distinct entry\n * point because the field's own value is the workingDirectory of every\n * `related_files[]` entry written alongside it — running it through\n * {@link sanitizePath} with `opts.workingDirectory = rawPath` would\n * collapse the result to `\".\"` and lose the homedir-relative form the\n * spec requires.\n *\n * Strategy: bypass the workingDirectory rule entirely by passing a\n * sentinel that no real path can match. The homedir rule (rule 2) and\n * the preserve-as-is rule (rule 3) still apply, so:\n * - `/Users/u/projects/foo` → `~/projects/foo`\n * - `/Users/u` → `~`\n * - `/srv/work` → `/srv/work` (preserved, off-tree)\n *\n * Callers should still pass the live `homedir` so the rewrite uses the\n * real operator-private prefix.\n */\nexport function sanitizeWorkingDirectory(\n rawPath: string,\n opts: Pick<SanitizePathOptions, \"homedir\">,\n): string {\n // A sentinel that no real absolute path on disk can equal or be under.\n // `path.posix.normalize` collapses leading `/` so any sentinel must\n // remain non-prefixing post-normalisation; the sentinel below survives\n // normalisation as itself and never matches a real path.\n return sanitizePath(rawPath, {\n workingDirectory: \"/__basou_sentinel_never_match__\",\n homedir: opts.homedir,\n });\n}\n\n/** Result of {@link sanitizeRelatedFiles}. */\nexport type SanitizeRelatedFilesResult = {\n /** Sanitized path list (same length as the input). */\n sanitized: string[];\n /** Number of entries whose sanitized form differs from the input. */\n mutationCount: number;\n};\n\n/**\n * Apply {@link sanitizePath} to every entry of a `related_files[]` array\n * and report how many entries actually changed shape so callers (e.g. the\n * session-import CLI) can surface a single-line warning. The helper does\n * not deduplicate — callers already collect related_files into a Set\n * before serialising.\n */\nexport function sanitizeRelatedFiles(\n paths: ReadonlyArray<string>,\n opts: SanitizePathOptions,\n): SanitizeRelatedFilesResult {\n const sanitized: string[] = [];\n let mutationCount = 0;\n for (const p of paths) {\n const next = sanitizePath(p, opts);\n sanitized.push(next);\n if (next !== p) mutationCount += 1;\n }\n return { sanitized, mutationCount };\n}\n","import { readFile } from \"node:fs/promises\";\nimport { join } from \"node:path\";\nimport { findErrorCode } from \"../lib/error-codes.js\";\nimport {\n TASK_INDEX_SCHEMA_VERSION,\n type TaskIndex,\n type TaskIndexEntry,\n TaskIndexSchema,\n} from \"../schemas/task-index.schema.js\";\nimport { atomicReplace } from \"./atomic.js\";\nimport type { BasouPaths } from \"./basou-dir.js\";\n\n/**\n * Absolute path of the workspace's `tasks/index.json`. The index lives\n * INSIDE `<paths.tasks>` (not under `<paths.root>`) so a future\n * monorepo-style layout with multiple task families could carry its own\n * index without colliding at the basou root.\n */\nexport function taskIndexPath(paths: BasouPaths): string {\n return join(paths.tasks, \"index.json\");\n}\n\n/**\n * Read and validate `tasks/index.json`. Returns the parsed payload only\n * when the schema_version matches the current literal — a mismatch is\n * surfaced as a schema parse failure so the caller falls through to the\n * lazy-rebuild path.\n *\n * Error contract:\n * - ENOENT → throw `Error(\"Task index not found\", { cause })`\n * - JSON parse / schema fail / version mismatch → throw\n * `Error(\"Invalid task index\", { cause })`\n * - any other I/O failure → throw `Error(\"Failed to read task index\", { cause })`\n *\n * Callers should treat all three as \"rebuild from disk\"; the distinct\n * messages exist so debug output / dogfood notes can tell them apart.\n */\nexport async function readTaskIndex(paths: BasouPaths): Promise<TaskIndex> {\n const filePath = taskIndexPath(paths);\n let raw: string;\n try {\n raw = await readFile(filePath, \"utf8\");\n } catch (error: unknown) {\n if (findErrorCode(error, \"ENOENT\")) {\n throw new Error(\"Task index not found\", { cause: error });\n }\n throw new Error(\"Failed to read task index\", { cause: error });\n }\n let parsedJson: unknown;\n try {\n parsedJson = JSON.parse(raw);\n } catch (error: unknown) {\n throw new Error(\"Invalid task index\", { cause: error });\n }\n const result = TaskIndexSchema.safeParse(parsedJson);\n if (!result.success) {\n throw new Error(\"Invalid task index\", { cause: result.error });\n }\n if (result.data.schema_version !== TASK_INDEX_SCHEMA_VERSION) {\n // Reject older / newer schema versions so a future bump triggers a\n // forced rebuild rather than silent migration.\n throw new Error(\"Invalid task index\", {\n cause: new Error(`Unsupported task index schema_version: ${result.data.schema_version}`),\n });\n }\n return result.data;\n}\n\n/**\n * Atomically write `tasks/index.json` with the given entries. Entries\n * are sorted by id (= ULID-ascending) so two rebuilds on the same disk\n * state produce byte-identical output and `git diff` stays clean.\n *\n * Caller-controlled `now` lets tests assert on `last_rebuilt_at`\n * without faking `Date`. When omitted the current wall clock is used.\n */\nexport async function rebuildTaskIndex(\n paths: BasouPaths,\n entries: ReadonlyArray<TaskIndexEntry>,\n now?: () => Date,\n): Promise<TaskIndex> {\n const sorted = [...entries].sort((a, b) => a.id.localeCompare(b.id));\n const payload: TaskIndex = {\n schema_version: TASK_INDEX_SCHEMA_VERSION,\n tasks: sorted,\n last_rebuilt_at: (now ?? (() => new Date()))().toISOString(),\n };\n // Self-defense — boundary-parse so a buggy caller cannot smuggle in\n // an invalid entry shape past the read-side schema check.\n TaskIndexSchema.parse(payload);\n await atomicReplace(taskIndexPath(paths), `${JSON.stringify(payload, null, 2)}\\n`);\n return payload;\n}\n\n/**\n * Mutation kind for {@link updateTaskIndex}. `add` and `update` carry a\n * full entry payload; `remove` carries only the id (the entry is gone\n * from disk by the time we write the index).\n *\n * archiveTask uses `remove` too: the archived task no longer participates\n * in the active task index because `enumerateTaskIds` (= the index's\n * read consumer) scans only `tasks/<id>.md`, not `tasks/archive/<id>.md`.\n */\nexport type TaskIndexOp =\n | { kind: \"add\"; entry: TaskIndexEntry }\n | { kind: \"update\"; entry: TaskIndexEntry }\n | { kind: \"remove\"; id: string };\n\n/**\n * Apply a single mutation to `tasks/index.json` and atomically rewrite\n * it. Falls through to {@link rebuildTaskIndex} when the current index is\n * missing / invalid so the first write after a workspace migration\n * still produces a valid file.\n *\n * Write failure (atomic-rename ENOSPC / EACCES etc.) is re-thrown\n * unwrapped so the caller (= each task write API) can decide whether to\n * surface it as a warning or escalate. The recommended policy in\n * `tasks.ts` is `console.warn(...)` plus keep the task.md write\n * successful (= index is a soft cache, not source of truth).\n */\nexport async function updateTaskIndex(\n paths: BasouPaths,\n op: TaskIndexOp,\n options?: { now?: () => Date },\n): Promise<TaskIndex> {\n const nowFn = options?.now ?? (() => new Date());\n let current: TaskIndex;\n try {\n current = await readTaskIndex(paths);\n } catch {\n // Index missing or invalid — rebuild empty before applying op.\n current = {\n schema_version: TASK_INDEX_SCHEMA_VERSION,\n tasks: [],\n last_rebuilt_at: nowFn().toISOString(),\n };\n }\n\n let nextTasks: TaskIndexEntry[];\n switch (op.kind) {\n case \"add\":\n nextTasks = current.tasks.some((t) => t.id === op.entry.id)\n ? current.tasks.map((t) => (t.id === op.entry.id ? op.entry : t))\n : [...current.tasks, op.entry];\n break;\n case \"update\":\n nextTasks = current.tasks.some((t) => t.id === op.entry.id)\n ? current.tasks.map((t) => (t.id === op.entry.id ? op.entry : t))\n : [...current.tasks, op.entry];\n break;\n case \"remove\":\n nextTasks = current.tasks.filter((t) => t.id !== op.id);\n break;\n }\n\n return await rebuildTaskIndex(paths, nextTasks, nowFn);\n}\n","import { z } from \"zod\";\nimport { IsoTimestampSchema, SchemaVersionSchema, TaskIdSchema } from \"./shared.schema.js\";\nimport { TaskStatusSchema } from \"./task.schema.js\";\n\n/**\n * Single entry inside `.basou/tasks/index.json`.\n *\n * Source of truth remains `task.md`; this is a derived cache populated\n * write-through on every task mutation (`createTask`,\n * `updateTaskStatusWithEvent`, `editTask`, `deleteTask`, `archiveTask`,\n * `reconcileTask`, `refreshTaskLinkedSessions`). The minimum field set\n * lets `basou task list` filter / sort without re-parsing every front\n * matter, while keeping the index small enough that rebuilds stay cheap.\n *\n * `label` is omitted when the task has no explicit label so the JSON\n * round-trips without storing `undefined` literals.\n */\nexport const TaskIndexEntrySchema = z\n .object({\n id: TaskIdSchema,\n status: TaskStatusSchema,\n label: z.string().min(1).optional(),\n updated_at: IsoTimestampSchema,\n })\n .strict();\nexport type TaskIndexEntry = z.infer<typeof TaskIndexEntrySchema>;\n\n/**\n * Top-level schema for `.basou/tasks/index.json`. `tasks[]` is the\n * compact projection used for fast enumeration; `last_rebuilt_at`\n * records the wall-clock moment of the latest full readdir rebuild so\n * a future migration / debugging tool can spot stale caches without\n * comparing every entry against disk.\n *\n * `schema_version` lets a future bump trigger a forced rebuild instead\n * of attempting silent schema migration — readTaskIndex returns the\n * parsed payload only when the version matches the current literal, so\n * a mismatch falls through to the lazy-rebuild path.\n */\nexport const TaskIndexSchema = z\n .object({\n schema_version: SchemaVersionSchema,\n tasks: z.array(TaskIndexEntrySchema),\n last_rebuilt_at: IsoTimestampSchema,\n })\n .strict();\nexport type TaskIndex = z.infer<typeof TaskIndexSchema>;\n\n/** Current schema version. Bump triggers a forced rebuild on next read. */\nexport const TASK_INDEX_SCHEMA_VERSION = \"0.1.0\" as const;\n","// `[1-9]\\d*` rejects \"0\" and leading zeros so that callers cannot smuggle in\n// a non-positive duration (which the underlying spawn validators would later\n// reject anyway). The unit is fixed to `ms`/`s`/`m`/`h`; days and weeks are\n// out of scope for v0.1.\nconst DURATION_RE = /^([1-9]\\d*)(ms|s|m|h)$/;\n\n/**\n * Parse a unit-suffixed duration string (e.g. `30s`, `5m`, `1h`, `100ms`)\n * into milliseconds.\n *\n * Rejects formats that cannot represent a positive, finite millisecond\n * value: malformed inputs, zero, leading-zero values, and computations that\n * overflow to `Infinity`. The returned number is always a positive integer.\n *\n * Supported units: `ms` (milliseconds), `s` (seconds), `m` (minutes),\n * `h` (hours).\n *\n * @param input duration string with required unit suffix\n * @returns duration in milliseconds (positive, finite)\n * @throws Error with message\n * `Invalid duration: <input>. Expected format: <positive-integer><unit> where unit is ms/s/m/h`\n * for format errors, or `Duration overflow: <input>` for non-finite results.\n */\nexport function parseDuration(input: string): number {\n const trimmed = input.trim();\n const match = DURATION_RE.exec(trimmed);\n if (!match) {\n throw new Error(\n `Invalid duration: ${trimmed}. Expected format: <positive-integer><unit> where unit is ms/s/m/h`,\n );\n }\n const value = Number(match[1]);\n const unit = match[2];\n let ms: number;\n switch (unit) {\n case \"ms\":\n ms = value;\n break;\n case \"s\":\n ms = value * 1000;\n break;\n case \"m\":\n ms = value * 60_000;\n break;\n case \"h\":\n ms = value * 3_600_000;\n break;\n default:\n // Unreachable per the regex; satisfy exhaustiveness analysis.\n throw new Error(`Invalid duration unit: ${unit}`);\n }\n if (!Number.isFinite(ms)) {\n throw new Error(`Duration overflow: ${trimmed}`);\n }\n return ms;\n}\n","/**\n * Coarse human duration from milliseconds: \"3h 05m\" / \"12m 30s\" / \"8s\".\n * Shared by the work-stats surfaces (`basou stats`, `basou session show`) and\n * the report renderer so they format identically.\n */\nexport function formatDurationMs(ms: number): string {\n const totalSeconds = Math.round(ms / 1000);\n const hours = Math.floor(totalSeconds / 3600);\n const minutes = Math.floor((totalSeconds % 3600) / 60);\n const seconds = totalSeconds % 60;\n if (hours > 0) return `${hours}h ${String(minutes).padStart(2, \"0\")}m`;\n if (minutes > 0) return `${minutes}m ${String(seconds).padStart(2, \"0\")}s`;\n return `${seconds}s`;\n}\n","import type { BasouPaths } from \"../storage/basou-dir.js\";\nimport { enumerateSessionDirs } from \"../storage/sessions.js\";\nimport { enumerateArchivedTaskIds, enumerateTaskIds } from \"../storage/tasks.js\";\n\n/**\n * Resolve a possibly-truncated session id prefix to a full session id by\n * scanning `<paths.sessions>/`. Existing message contract (carried over\n * from `packages/cli/src/commands/session.ts`) is\n * preserved exactly so callers that grep stderr keep working:\n *\n * - `\"Session id is empty\"`\n * - `\"Session not found: <input>\"`\n * - `\"Ambiguous session id '<input>': matched <N> sessions. Disambiguate\n * with a longer prefix.\"`\n */\nexport async function resolveSessionId(paths: BasouPaths, input: string): Promise<string> {\n return resolveIdInternal(paths, input, \"session\");\n}\n\n/**\n * Resolve a possibly-truncated task id prefix to a full task id by scanning\n * `<paths.tasks>/`. Mirrors {@link resolveSessionId} with the noun changed\n * to `task` in every error message.\n *\n * `options.includeArchived` extends the scan to `<paths.tasks>/archive/` so\n * read-only commands (e.g. `basou task show`) can address tasks that were\n * archived by `basou task archive`. Defaults to `false` so destructive flows\n * (status change, edit, delete, archive itself) cannot operate on archived\n * tasks accidentally.\n */\nexport async function resolveTaskId(\n paths: BasouPaths,\n input: string,\n options: { includeArchived?: boolean } = {},\n): Promise<string> {\n return resolveIdInternal(paths, input, \"task\", options);\n}\n\ntype IdKind = \"session\" | \"task\";\n\ntype KindConfig = {\n prefix: string;\n noun: string;\n nounPlural: string;\n capNoun: string;\n enumerate: (paths: BasouPaths) => Promise<string[]>;\n};\n\nconst KIND_CONFIG: Record<IdKind, KindConfig> = {\n session: {\n prefix: \"ses_\",\n noun: \"session\",\n nounPlural: \"sessions\",\n capNoun: \"Session\",\n enumerate: enumerateSessionDirs,\n },\n task: {\n prefix: \"task_\",\n noun: \"task\",\n nounPlural: \"tasks\",\n capNoun: \"Task\",\n enumerate: enumerateTaskIds,\n },\n};\n\nasync function resolveIdInternal(\n paths: BasouPaths,\n input: string,\n kind: IdKind,\n options: { includeArchived?: boolean } = {},\n): Promise<string> {\n const cfg = KIND_CONFIG[kind];\n const trimmed = input.trim();\n if (trimmed.length === 0) {\n throw new Error(`${cfg.capNoun} id is empty`);\n }\n const normalized = trimmed.startsWith(cfg.prefix) ? trimmed : `${cfg.prefix}${trimmed}`;\n if (normalized.length <= cfg.prefix.length) {\n throw new Error(`${cfg.capNoun} not found: ${input}`);\n }\n const primary = await cfg.enumerate(paths);\n // Merge in archived task ids when the caller opts in. Dedupe via a Set so\n // a single id appearing in both surfaces (shouldn't happen but defend\n // anyway) does not falsely register as ambiguous.\n const merged = new Set<string>(primary);\n if (kind === \"task\" && options.includeArchived === true) {\n for (const id of await enumerateArchivedTaskIds(paths)) {\n merged.add(id);\n }\n }\n if (merged.size === 0) {\n throw new Error(`${cfg.capNoun} not found: ${input}`);\n }\n const matches = [...merged].filter((e) => e.startsWith(normalized));\n if (matches.length === 0) {\n throw new Error(`${cfg.capNoun} not found: ${input}`);\n }\n if (matches.length > 1) {\n throw new Error(\n `Ambiguous ${cfg.noun} id '${input}': matched ${matches.length} ${cfg.nounPlural}. Disambiguate with a longer prefix.`,\n );\n }\n return matches[0] as string;\n}\n","import { promises as fs } from \"node:fs\";\nimport { homedir as osHomedir } from \"node:os\";\nimport { basename, dirname, isAbsolute, join, normalize, relative, resolve } from \"node:path\";\n\n/**\n * Cross-project boundary classification: split a session's `related_files`\n * into those that resolve INSIDE the project's declared `source_roots` and\n * those that confidently resolve OUTSIDE all of them.\n *\n * Why this exists: the claude-code adapter records every file a transcript\n * edited, regardless of where the file lives. A session is attributed to a\n * project by its recorded cwd (the import-time cwd guard), but a session that\n * legitimately belongs to project A can still have edited files under an\n * unrelated repo B. Those B paths then surface in `basou orient`'s \"recent\n * files\" and can mislead a resuming agent into continuing the wrong project's\n * work. This helper is the read-only primitive both the import warning and the\n * orientation advisory use to flag that boundary crossing — it never mutates\n * the trail.\n *\n * Resolution is realpath-aware so a file recorded through a workspace-view\n * symlink (e.g. `~/projects/foo-workspace/foo -> ../foo`) is NOT mis-flagged as\n * out-of-root. The bias is deliberately toward NOT crying wolf: a path is only\n * reported out-of-root when it confidently resolves outside every source root.\n * Anything that cannot be resolved with confidence stays classified in-root.\n */\n\n/**\n * The agent's / basou's own tooling directories. Edits here (plans, memory,\n * the trail store itself) are routine infrastructure, not another project's\n * work, so callers pass these as `extraInRoot` to keep them out of the\n * cross-project out-of-root flag.\n */\nexport const AGENT_INFRA_DIRS: readonly string[] = [\"~/.claude\", \"~/.codex\", \"~/.basou\"];\n\n/** Result of {@link classifyFilesBySourceRoot}: a partition of the input. */\nexport type SourceRootScope = {\n /** Entries (verbatim, as passed in) that resolve under a source root, or that could not be resolved with confidence. */\n inRoot: string[];\n /** Entries (verbatim) that confidently resolve outside every source root. */\n outOfRoot: string[];\n};\n\n/**\n * Resolve a `realpath`, tolerating a non-existent tail: realpath the longest\n * existing ANCESTOR and re-append the missing segments. A file recorded in a\n * past session may have since moved or been deleted, but we still want to\n * classify the LOCATION it referred to (and resolve any symlink in its\n * existing ancestry). Falls back to the lexical input on any non-ENOENT error\n * or once the filesystem root is reached without an existing ancestor.\n */\nasync function realpathBestEffort(absPath: string): Promise<string> {\n let current = normalize(absPath);\n const tail: string[] = [];\n // Bound the walk by path depth so a pathological input cannot loop forever.\n for (let guard = 0; guard < 4096; guard += 1) {\n try {\n const real = await fs.realpath(current);\n return tail.length > 0 ? join(real, ...tail.reverse()) : real;\n } catch (error: unknown) {\n const code = (error as NodeJS.ErrnoException | undefined)?.code;\n if (code !== \"ENOENT\" && code !== \"ENOTDIR\") {\n // Permission error etc.: do not guess, fall back to the lexical path.\n return normalize(absPath);\n }\n const parent = dirname(current);\n if (parent === current) return normalize(absPath); // reached root, nothing existed\n tail.push(basename(current));\n current = parent;\n }\n }\n return normalize(absPath);\n}\n\n/** Expand a leading `~` / `~/` to the home directory; leave other forms as-is. */\nfunction expandTilde(p: string, homedir: string): string {\n if (p === \"~\") return homedir;\n if (p.startsWith(\"~/\")) return join(homedir, p.slice(2));\n return p;\n}\n\n/**\n * Resolve a stored (sanitized) path to an absolute path before realpath:\n * - `~` / `~/x` → under homedir\n * - absolute → as-is\n * - relative → resolved against the session working directory\n * `workingDirectory` is itself sanitized (typically `~/...`), so it is\n * tilde-expanded first.\n */\nfunction toAbsolute(p: string, workingDirAbs: string, homedir: string): string {\n const expanded = expandTilde(p, homedir);\n if (isAbsolute(expanded)) return normalize(expanded);\n return normalize(resolve(workingDirAbs, expanded));\n}\n\n/**\n * True when `child` is `parent` itself or lives underneath it. Uses\n * `path.relative` rather than raw `startsWith` so a trailing separator or a\n * `..`/`.` segment in either operand cannot defeat the prefix match (the\n * `startsWith` form is a known foot-gun this codebase moved away from in\n * realpath comparisons elsewhere).\n */\nfunction isUnder(child: string, parent: string): boolean {\n if (child === parent) return true;\n const rel = relative(parent, child);\n return rel !== \"\" && !rel.startsWith(\"..\") && !isAbsolute(rel);\n}\n\n/**\n * Partition `files` into in-root / out-of-root against the project's\n * `source_roots`.\n *\n * - `sourceRoots` are the manifest's `import.source_roots` (relative to\n * `masterRoot`). An absent/empty list means \"the whole repo root\" — matching\n * the effective-source-roots rule elsewhere — so a solo project never reports\n * anything out-of-root.\n * - `masterRoot` is the absolute repository root the source roots resolve\n * against (the parent of `.basou`).\n *\n * Returns `{ inRoot, outOfRoot }` preserving the original entry strings. Empty\n * input or zero resolvable roots yields everything in-root (no false alarms).\n */\nexport async function classifyFilesBySourceRoot(input: {\n files: readonly string[];\n workingDirectory: string;\n sourceRoots: readonly string[] | null | undefined;\n masterRoot: string;\n /**\n * Extra directories (absolute or `~`-prefixed) that also count as in-root.\n * Callers pass the agent's own tooling dirs (`~/.claude`, `~/.codex`,\n * `~/.basou`) so routine plan / memory / store edits are NOT flagged as\n * another project's work — they are infrastructure, not a cross-project\n * crossing. Resolved against the home directory, not `masterRoot`.\n */\n extraInRoot?: readonly string[];\n homedir?: string;\n}): Promise<SourceRootScope> {\n const inRoot: string[] = [];\n const outOfRoot: string[] = [];\n if (input.files.length === 0) return { inRoot, outOfRoot };\n\n const homedir = input.homedir ?? osHomedir();\n const workingDirAbs = toAbsolute(input.workingDirectory, homedir, homedir);\n\n // Effective roots: a declared list is used verbatim; absent/empty means the\n // whole repo root (mirrors `effectiveSourceRoots`). Resolve + realpath each;\n // drop any that fail to resolve so a single bad entry does not void the rest.\n const declared =\n input.sourceRoots && input.sourceRoots.length > 0 ? [...input.sourceRoots] : [\".\"];\n const rootsAbs: string[] = [];\n for (const r of declared) {\n const expanded = expandTilde(r, homedir);\n const abs = isAbsolute(expanded)\n ? normalize(expanded)\n : normalize(resolve(input.masterRoot, expanded));\n rootsAbs.push(await realpathBestEffort(abs));\n }\n // Extra in-root dirs (agent/tool infra) resolve against the home directory.\n for (const e of input.extraInRoot ?? []) {\n const expanded = expandTilde(e, homedir);\n const abs = isAbsolute(expanded) ? normalize(expanded) : normalize(resolve(homedir, expanded));\n rootsAbs.push(await realpathBestEffort(abs));\n }\n // No resolvable roots → cannot judge; keep everything in-root.\n if (rootsAbs.length === 0) {\n return { inRoot: [...input.files], outOfRoot };\n }\n\n for (const file of input.files) {\n try {\n const abs = toAbsolute(file, workingDirAbs, homedir);\n const real = await realpathBestEffort(abs);\n const within = rootsAbs.some((root) => isUnder(real, root));\n (within ? inRoot : outOfRoot).push(file);\n } catch {\n // Any unexpected resolution failure: bias to in-root (do not cry wolf).\n inRoot.push(file);\n }\n }\n\n return { inRoot, outOfRoot };\n}\n","import { dirname, join } from \"node:path\";\nimport { enumerateApprovals, isLazyExpired, loadApproval } from \"../approval/approval-store.js\";\nimport { type ReplayWarning, replayEvents } from \"../events/event-replay.js\";\nimport { formatDurationMs } from \"../lib/format-duration.js\";\nimport { isTrailingStale, pickLatestSubstantiveEntry } from \"../lib/recency.js\";\nimport { AGENT_INFRA_DIRS, classifyFilesBySourceRoot } from \"../lib/source-root-scope.js\";\nimport type { BasouPaths } from \"../storage/basou-dir.js\";\nimport { readManifest } from \"../storage/manifest.js\";\nimport {\n type FederatedRoot,\n loadFederatedSessionEntries,\n loadSessionEntries,\n type SessionSkipReason,\n type SuspectReason,\n} from \"../storage/sessions.js\";\nimport { loadTaskEntries, type TaskSkipReason } from \"../storage/tasks.js\";\n\n/** Input contract for {@link renderOrientation} and {@link summarizeOrientation}. */\nexport type OrientationRendererInput = {\n paths: BasouPaths;\n /** ISO timestamp embedded in the header AND used as \"now\" for freshness + suspect classification. */\n nowIso: string;\n onWarning?: (warning: ReplayWarning, sessionId: string) => void;\n onSessionSkip?: (sessionId: string, reason: SessionSkipReason) => void;\n onTaskSkip?: (taskId: string, reason: TaskSkipReason) => void;\n /** Maximum related_files entries to display before `... +N more`. Default 10. */\n relatedFilesLimit?: number;\n /**\n * Result of a read-only dry-run staleness probe (sessions a `basou refresh`\n * would add or update), computed by the CLI which holds the import context.\n * Drives the plain \"これは最新か\" verdict. `null` / omitted = not probed, so\n * the verdict says it cannot confirm freshness rather than claiming current.\n */\n staleness?: {\n newSessions: number;\n updatedSessions: number;\n unverifiableSessions?: number;\n } | null;\n /**\n * Append the raw freshness telemetry (ISO timestamp, per-source counts, source\n * roots, suspect count) under the plain verdict. Off by default so the section\n * reads as a verdict for a supervisor, not developer diagnostics.\n */\n verbose?: boolean;\n /**\n * Additional trail stores to MERGE into this orientation, each a local path\n * (an SSHFS mount / rsync mirror of another host's `.basou`) tagged with a\n * host label. Absent / empty = local-only (byte-identical to before). basou\n * performs no network I/O; the operator's existing tooling places these paths.\n */\n federatedRoots?: FederatedRoot[];\n /**\n * Called when a federated (non-local) host root is present but cannot be\n * enumerated (e.g. an unreadable mount). That host is skipped; the local\n * store and other hosts still render. An absent root path is silently empty.\n */\n onHostUnavailable?: (host: string, error: unknown) => void;\n};\n\nexport type OrientationRendererResult = {\n /** Generated body. orientation.md is overwritten whole (no markers, gitignored). */\n body: string;\n sessionCount: number;\n pendingApprovalsCount: number;\n suspectCount: number;\n /** Tasks whose status is `planned` or `in_progress`. */\n inFlightTaskCount: number;\n decisionCount: number;\n /** Open (non-voided) `kind: \"track\"` decisions surfaced as strategic continuation. */\n openTrackCount: number;\n};\n\ntype DecisionRecord = {\n decisionId: string;\n title: string;\n occurredAt: string;\n sessionId: string;\n host: string | null;\n};\n\n/**\n * An open (non-voided) decision recorded with `kind: \"track\"` — a strategic,\n * unfinished direction the forward section resurfaces every session until it is\n * closed via `decision void` / supersede. Carries the rationale (the WHY) so the\n * surfaced track answers not just \"what to build next\" but \"and why\", which is\n * exactly the intent that otherwise lives only in the conversation.\n */\ntype TrackRecord = {\n decisionId: string;\n title: string;\n rationale: string | null;\n occurredAt: string;\n sessionId: string;\n host: string | null;\n};\n\ntype NoteRecord = { body: string; sessionId: string; occurredAt: string; host: string | null };\n\ntype PendingApproval = {\n id: string;\n risk: string;\n kind: string;\n reason: string;\n sessionId: string;\n createdAt: string;\n expired: boolean;\n};\n\ntype InFlightTask = { id: string; title: string; status: string; linkedSessions: number };\ntype PlannedTask = { id: string; title: string };\ntype SuspectSession = {\n sessionId: string;\n status: string;\n reason: SuspectReason | null;\n host: string | null;\n};\ntype LatestSession = {\n sessionId: string;\n label: string | null;\n status: string;\n host: string | null;\n};\ntype SourceCount = { kind: string; count: number };\n\n/**\n * The vendor-neutral, serializable structured summary behind orientation. This\n * is the single source of the four orientation questions (where am I now / what\n * is in flight / where am I heading / is this current). {@link renderOrientation}\n * formats it into markdown; programmatic consumers (e.g. a multi-workspace\n * portfolio view) read it directly without parsing prose.\n *\n * It carries STRUCTURED FACTS only — the pending-approval list with risk/reason,\n * suspect sessions, in-flight task linkage, capture freshness/coverage, the\n * latest decision. It deliberately holds NO work-stats (volume / active time /\n * tokens) and NO per-agent scorecards, productivity, or utilization metrics:\n * orientation shows product state, not surveillance of the fleet.\n */\nexport type OrientationSummary = {\n /** ISO \"now\"; the header timestamp and the basis for freshness/suspect classification. */\n generatedAt: string;\n /** All captured sessions (archived included), matching the count line. */\n sessionCount: number;\n /** Newest non-archived, non-import session (\"where am I now\"); null when none. */\n latestSession: LatestSession | null;\n /** Most recent `decision_recorded` across all sessions; null when none. */\n latestDecision: DecisionRecord | null;\n decisionCount: number;\n /**\n * Open (non-voided) `kind: \"track\"` decisions — strategic, unfinished\n * directions that the forward section (\"どこへ向かう\") resurfaces every session\n * until they are closed with `decision void` / supersede. Newest first. This\n * is the intent-continuity layer: distinct from the single latest decision\n * (point-in-time) and the recorded next step (`note`), an open track keeps\n * carrying \"the next essential thing to build, and why\" across sessions so it\n * does not sink into the flat decision list. Empty when none are open.\n */\n openTracks: TrackRecord[];\n /**\n * Most recent `note_added` over non-archived sessions — the recorded next\n * step / handoff (\"次の起点\") surfaced in the forward section; null when none.\n */\n latestNote: NoteRecord | null;\n /**\n * related_files of the latest session, deduped + sorted + capped at the\n * display limit. `outOfRoot` lists the entries (over the FULL deduped set,\n * not just `displayed`) that resolve OUTSIDE the project's `source_roots` — a\n * cross-project boundary crossing worth flagging so a resuming agent does not\n * mistake another repo's edits for this project's work. Empty unless the\n * latest session is local (a federated host's source_roots are not loaded\n * here) and confidently has out-of-root edits.\n */\n relatedFiles: { displayed: string[]; overflow: number; outOfRoot: string[] };\n /** Tasks whose status is `planned` or `in_progress`. */\n inFlightTasks: InFlightTask[];\n /** Tasks whose status is `planned` (\"where am I heading\"). */\n plannedTasks: PlannedTask[];\n pendingApprovals: PendingApproval[];\n suspects: SuspectSession[];\n /**\n * Distinct non-local host labels present in the merged set (sorted). Empty\n * for a local-only orientation. Lets a consumer render the multi-host banner\n * and the local-only-freshness caveat without re-deriving from sessions.\n */\n hosts: string[];\n freshness: {\n /** started_at of the newest non-archived session, or null when none captured. */\n newestStartedAt: string | null;\n /** source.kind of the newest non-archived session, or null when none captured. */\n newestSource: string | null;\n /**\n * Tail of captured activity over non-archived sessions = max of each\n * session's boundary (`ended_at` ?? `started_at`) and every captured event's\n * `occurred_at`. Folding event times covers a live session whose `ended_at`\n * is not yet written. Used to flag a latest-recorded decision that trails\n * real activity; null when no non-archived sessions exist.\n */\n latestActivityAt: string | null;\n /** Session counts per source kind, sorted by kind. Counts only — never volume/time. */\n bySource: SourceCount[];\n /** manifest `import.source_roots`, or null when single-root / unreadable. */\n sourceRoots: string[] | null;\n };\n};\n\n/**\n * Gather the structured orientation facts for a workspace. Read-only and runs\n * NO imports: freshness reflects already-captured state, so a stale capture is\n * visible rather than silently refreshed (run `basou refresh` to re-import).\n *\n * Returns a fully serializable {@link OrientationSummary}. See its docstring for\n * the positioning constraint (no work-stats, no surveillance metrics).\n */\nexport async function summarizeOrientation(\n input: OrientationRendererInput,\n): Promise<OrientationSummary> {\n const limit = input.relatedFilesLimit ?? 10;\n const now = new Date(input.nowIso);\n\n // `exactOptionalPropertyTypes` forbids passing literal `undefined`, so build\n // the options object conditionally (mirrors the handoff renderer).\n const loadOpts: Parameters<typeof loadSessionEntries>[1] = { now };\n if (input.onSessionSkip !== undefined) loadOpts.onSkip = input.onSessionSkip;\n if (input.onWarning !== undefined) loadOpts.onWarning = input.onWarning;\n const entries =\n input.federatedRoots !== undefined && input.federatedRoots.length > 0\n ? await loadFederatedSessionEntries(\n [{ paths: input.paths, host: null }, ...input.federatedRoots],\n {\n ...loadOpts,\n ...(input.onHostUnavailable !== undefined\n ? { onRootUnavailable: input.onHostUnavailable }\n : {}),\n },\n )\n : await loadSessionEntries(input.paths, loadOpts);\n\n // One replay pass per session yields three facts:\n // - `decisions`: chronological `decision_recorded` across ALL sessions.\n // - `latestNote`: the most recent `note_added` over NON-archived sessions —\n // the operator's recorded next step / handoff (\"次の起点\"), surfaced in the\n // forward section so a free-text resume hint survives into the next session.\n // - `latestActivityAt`: the tail of captured activity over NON-archived\n // sessions = max of the session boundary (ended_at ?? started_at) AND every\n // event's occurred_at. Folding event times (not just ended_at) is what makes\n // the trailing-decision note fire for a LIVE session: a running session has\n // no ended_at yet, but its post-decision events (more commands, notes, task\n // attaches via `decision record --session`) are already captured. Without\n // this, a mid-session decision in an ongoing long session — the exact case\n // the note targets — would be silently treated as current (a false-clear).\n // The population is intentionally asymmetric: decisions span archived\n // sessions (a past decision still answers \"what did I last decide\"), while\n // the activity tail and latest note are non-archived only (they answer \"is\n // there newer work\" / \"where do I resume\").\n const decisions: DecisionRecord[] = [];\n // Decisions recorded with `kind: \"track\"` (a strategic, unfinished direction).\n // Collected across the same pass; the open subset (minus voided) is surfaced\n // in the forward section and resurfaces until closed.\n const tracks: TrackRecord[] = [];\n // decision_ids marked no longer in force by a `decision_voided` event; the\n // \"latest decision\" pointer skips them so a voided decision is never\n // surfaced as the current direction.\n const voidedDecisionIds = new Set<string>();\n let latestActivityAt: string | null = null;\n let latestNote: NoteRecord | null = null;\n const noteActivity = (iso: string): void => {\n if (latestActivityAt === null || Date.parse(iso) > Date.parse(latestActivityAt)) {\n latestActivityAt = iso;\n }\n };\n for (const entry of entries) {\n const sessionDir = join(entry.sourceRoot.sessions, entry.sessionId);\n const counted = entry.session.session.status !== \"archived\";\n // Seed with the session boundary so a session whose events are empty or\n // unreadable still contributes its known activity window.\n if (counted) noteActivity(entry.session.session.ended_at ?? entry.session.session.started_at);\n try {\n for await (const ev of replayEvents(sessionDir, {\n onWarning: (w) => input.onWarning?.(w, entry.sessionId),\n })) {\n if (ev.type === \"decision_recorded\") {\n decisions.push({\n decisionId: ev.decision_id,\n title: ev.title,\n occurredAt: ev.occurred_at,\n sessionId: entry.sessionId,\n host: entry.host,\n });\n // Tracks (kind === \"track\") are an unfinished direction; collect them\n // separately with their rationale so the forward section can resurface\n // them until closed. A void recorded later removes the id from the open\n // set (resolved below, after the full scan, so a void seen before its\n // target decision still applies).\n if (ev.kind === \"track\") {\n tracks.push({\n decisionId: ev.decision_id,\n title: ev.title,\n rationale: ev.rationale ?? null,\n occurredAt: ev.occurred_at,\n sessionId: entry.sessionId,\n host: entry.host,\n });\n }\n } else if (ev.type === \"decision_voided\") {\n voidedDecisionIds.add(ev.decision_id);\n }\n // Only `next_step`-kind notes (from `basou note`) are resume hints; a\n // plain `basou session note` annotation (kind absent) is not surfaced.\n if (counted && ev.type === \"note_added\" && ev.kind === \"next_step\") {\n if (\n latestNote === null ||\n Date.parse(ev.occurred_at) > Date.parse(latestNote.occurredAt)\n ) {\n latestNote = {\n body: ev.body,\n sessionId: entry.sessionId,\n occurredAt: ev.occurred_at,\n host: entry.host,\n };\n }\n }\n if (counted) noteActivity(ev.occurred_at);\n }\n } catch {\n input.onSessionSkip?.(entry.sessionId, \"events_jsonl_unreadable\");\n }\n }\n decisions.sort((a, b) => {\n const c = Date.parse(a.occurredAt) - Date.parse(b.occurredAt);\n return c !== 0 ? c : a.decisionId.localeCompare(b.decisionId);\n });\n // The latest-decision pointer is the newest decision NOT voided — a voided\n // decision must not be presented as the current direction. decisions.md still\n // lists it (struck) for the audit trail.\n let latestDecision: DecisionRecord | undefined;\n for (let i = decisions.length - 1; i >= 0; i -= 1) {\n const d = decisions[i];\n if (d !== undefined && !voidedDecisionIds.has(d.decisionId)) {\n latestDecision = d;\n break;\n }\n }\n\n // Open tracks: every `kind: \"track\"` decision not yet voided/superseded, newest\n // first (most recent strategic direction leads). These resurface in the forward\n // section every session until explicitly closed — the durable intent layer.\n const openTracks: TrackRecord[] = tracks\n .filter((t) => !voidedDecisionIds.has(t.decisionId))\n .sort((a, b) => {\n const c = Date.parse(b.occurredAt) - Date.parse(a.occurredAt);\n return c !== 0 ? c : b.decisionId.localeCompare(a.decisionId);\n });\n\n // Tasks: in-flight (planned / in_progress) carry the cross-session linkage\n // that a flat transcript scan cannot reconstruct.\n const taskLoadOpts: Parameters<typeof loadTaskEntries>[1] = {};\n if (input.onTaskSkip !== undefined) taskLoadOpts.onSkip = input.onTaskSkip;\n const taskEntries = await loadTaskEntries(input.paths, taskLoadOpts);\n const inFlightTasks: InFlightTask[] = taskEntries\n .filter((t) => t.task.task.status === \"in_progress\" || t.task.task.status === \"planned\")\n .map((t) => ({\n id: t.task.task.id,\n title: t.task.task.title,\n status: t.task.task.status,\n linkedSessions: t.task.task.linked_sessions?.length ?? 0,\n }));\n const plannedTasks: PlannedTask[] = taskEntries\n .filter((t) => t.task.task.status === \"planned\")\n .map((t) => ({ id: t.task.task.id, title: t.task.task.title }));\n\n // Pending approvals: enumerateApprovals returns IDs only, so each pending id\n // is read via loadApproval to surface risk / action / reason (handoff shows\n // only a count). A null load (race / removed mid-read) is skipped.\n const { pending: pendingIds } = await enumerateApprovals(input.paths);\n const pendingApprovals: PendingApproval[] = [];\n for (const id of [...pendingIds].sort()) {\n const loaded = await loadApproval(input.paths, id);\n if (loaded === null) continue;\n const a = loaded.approval;\n pendingApprovals.push({\n id,\n risk: a.risk_level,\n kind: a.action.kind,\n reason: a.reason,\n sessionId: a.session_id,\n createdAt: a.created_at,\n expired: isLazyExpired(a, now),\n });\n }\n\n const suspects: SuspectSession[] = entries\n .filter((e) => e.suspect)\n .map((e) => ({\n sessionId: e.sessionId,\n status: e.session.session.status,\n reason: e.suspectReason,\n host: e.host,\n }));\n\n // \"where am I now\" latest session: exclude archived + cross-workspace round-trip\n // imports (`source.kind === \"import\"`), matching the handoff renderer.\n // claude-code-import / codex-import sessions ARE the operator's own captured\n // work, so they remain in scope.\n const liveEntries = entries.filter(\n (e) => e.session.session.status !== \"archived\" && e.session.session.source.kind !== \"import\",\n );\n // Represent \"最終 session\" with the most recent SUBSTANTIVE session, not a bare\n // resume/refresh session (e.g. 1 command, 0 files) that merely happens to be\n // newest — the latter hides the real-work session and makes 最終 session and\n // 直近の判断 disagree. Freshness (\"newest captured session\", below) still uses\n // pure recency, so the staleness signal stays honest.\n const latestEntry = pickLatestSubstantiveEntry(liveEntries);\n // `label` is `z.string().optional()` in the session schema — a parsed session\n // is `string | undefined`, never `null`. So `?? null` only maps `undefined`,\n // and the formatter's `label !== null && label !== \"\"` is byte-identical to\n // the original `label !== undefined && label !== \"\"` predicate.\n const latestSession: LatestSession | null =\n latestEntry !== undefined\n ? {\n sessionId: latestEntry.sessionId,\n label: latestEntry.session.session.label ?? null,\n status: latestEntry.session.session.status,\n host: latestEntry.host,\n }\n : null;\n\n // Freshness: newest started_at over all non-archived sessions (= most recent\n // captured activity). This is an honest staleness signal, NOT a completeness\n // claim — orientation runs no import, so what is not yet captured is not\n // counted here.\n const activityEntries = entries.filter((e) => e.session.session.status !== \"archived\");\n const newest = [...activityEntries].sort(\n (a, b) => Date.parse(b.session.session.started_at) - Date.parse(a.session.session.started_at),\n )[0];\n\n const bySourceMap = new Map<string, number>();\n for (const e of entries) {\n const k = e.session.session.source.kind;\n bySourceMap.set(k, (bySourceMap.get(k) ?? 0) + 1);\n }\n const bySource: SourceCount[] = [...bySourceMap.entries()]\n .sort((a, b) => a[0].localeCompare(b[0]))\n .map(([kind, count]) => ({ kind, count }));\n\n let sourceRoots: string[] | null = null;\n try {\n const manifest = await readManifest(input.paths);\n sourceRoots = manifest.import?.source_roots ?? null;\n } catch {\n // A missing / unreadable manifest leaves the source-roots line absent; the\n // CLI asserts the workspace is initialized before calling, so this is rare.\n sourceRoots = null;\n }\n\n const latestFiles = latestEntry?.session.session.related_files ?? [];\n const uniqueFiles = new Set(latestFiles);\n const sortedFiles = [...uniqueFiles].sort();\n const displayed = sortedFiles.slice(0, limit);\n const overflow = Math.max(0, uniqueFiles.size - limit);\n\n // Flag the files that resolve OUTSIDE this project's source_roots (a\n // cross-project boundary crossing). Classify the FULL file set, not just the\n // displayed slice, so an out-of-root file past the display cap is still\n // counted. Gated to projects that DECLARE source_roots (a multi-repo\n // workspace): a solo project's effective root is the whole repo, so there is\n // no declared boundary to cross and flagging would be noise. Scoped to a\n // LOCAL latest session — a federated host's source_roots are not loaded here,\n // so classifying its files against the local roots would cry wolf. Agent/tool\n // infra dirs count as in-root so routine plan / memory edits are not mistaken\n // for another project. dirname(.basou) is the repo root the source_roots\n // resolve against.\n let outOfRoot: string[] = [];\n if (\n latestEntry !== undefined &&\n latestEntry.host === null &&\n sortedFiles.length > 0 &&\n sourceRoots !== null &&\n sourceRoots.length > 0\n ) {\n try {\n const scope = await classifyFilesBySourceRoot({\n files: sortedFiles,\n workingDirectory: latestEntry.session.session.working_directory,\n sourceRoots,\n masterRoot: dirname(input.paths.root),\n extraInRoot: AGENT_INFRA_DIRS,\n });\n outOfRoot = scope.outOfRoot;\n } catch {\n // Classification is advisory only; never let it break orientation.\n outOfRoot = [];\n }\n }\n\n const hosts = [\n ...new Set(entries.map((e) => e.host).filter((h): h is string => h !== null)),\n ].sort();\n\n return {\n generatedAt: input.nowIso,\n sessionCount: entries.length,\n latestSession,\n latestDecision: latestDecision ?? null,\n decisionCount: decisions.length,\n openTracks,\n latestNote,\n relatedFiles: { displayed, overflow, outOfRoot },\n inFlightTasks,\n plannedTasks,\n pendingApprovals,\n suspects,\n hosts,\n freshness: {\n newestStartedAt: newest?.session.session.started_at ?? null,\n newestSource: newest?.session.session.source.kind ?? null,\n latestActivityAt,\n bySource,\n sourceRoots,\n },\n };\n}\n\n/**\n * Render `.basou/orientation.md`: a point-in-time \"current position\" view for a\n * supervisor who delegated execution to AI agents. Unlike `handoff.md` (a\n * session-resume narrative) this answers four orientation questions —\n * where am I now / what is in flight / where am I heading / is this current —\n * and deliberately leads with STRUCTURED FACTS an LLM cannot reliably derive\n * from raw transcripts (the\n * pending-approval list with risk/reason, suspect sessions, in-flight task\n * linkage, capture freshness/coverage) rather than prose synthesis.\n *\n * The renderer is read-only and runs NO imports: the freshness section reflects\n * already-captured state, so a stale capture is visible rather than silently\n * refreshed (use `basou refresh` to re-import). It must never emit per-agent\n * scorecards, productivity, or utilization metrics — orientation shows product\n * state, not surveillance of the fleet.\n *\n * Formatting only: the facts come from {@link summarizeOrientation}.\n */\nexport async function renderOrientation(\n input: OrientationRendererInput,\n): Promise<OrientationRendererResult> {\n const summary = await summarizeOrientation(input);\n return {\n body: formatOrientationBody(summary, {\n staleness: input.staleness ?? null,\n verbose: input.verbose === true,\n }),\n sessionCount: summary.sessionCount,\n pendingApprovalsCount: summary.pendingApprovals.length,\n suspectCount: summary.suspects.length,\n inFlightTaskCount: summary.inFlightTasks.length,\n decisionCount: summary.decisionCount,\n openTrackCount: summary.openTracks.length,\n };\n}\n\nfunction formatOrientationBody(\n summary: OrientationSummary,\n opts: {\n staleness: {\n newSessions: number;\n updatedSessions: number;\n unverifiableSessions?: number;\n } | null;\n verbose: boolean;\n },\n): string {\n const lines: string[] = [];\n const now = new Date(summary.generatedAt);\n const newestRel = relativeAge(summary.freshness.newestStartedAt ?? undefined, now);\n // Multi-host attribution suffix: only non-local rows carry it, so a\n // single-host (local-only) orientation is byte-identical to before.\n const hostSuffix = (h: string | null): string => (h !== null ? ` @${h}` : \"\");\n\n lines.push(\"# Orientation\");\n lines.push(\"\");\n lines.push(\n `> Generated at ${summary.generatedAt} · sessions ${summary.sessionCount} · newest ${newestRel} · pending ${summary.pendingApprovals.length} · suspect ${summary.suspects.length}`,\n );\n if (summary.hosts.length > 0) {\n lines.push(`> hosts: local, ${summary.hosts.join(\", \")}`);\n }\n lines.push(\"\");\n\n // \"where am I now\"\n lines.push(\"## 今どこにいる\");\n lines.push(\"\");\n if (summary.latestSession !== null) {\n const s = summary.latestSession;\n const sid = shortId(s.sessionId);\n if (s.label !== null && s.label !== \"\") {\n lines.push(`- 最終 session: ${s.label} (${s.status}) [${sid}]${hostSuffix(s.host)}`);\n } else {\n lines.push(`- 最終 session: ${sid} (${s.status})${hostSuffix(s.host)}`);\n }\n } else {\n lines.push(\"- 最終 session: (no live sessions)\");\n }\n if (summary.latestDecision !== null) {\n const dec = summary.latestDecision;\n const decAge = relativeAgeJa(dec.occurredAt, now);\n lines.push(\n `- 直近の判断: ${dec.title} [${shortId(dec.decisionId)}] (${decAge})${hostSuffix(dec.host)}`,\n );\n // Honesty over recency theater: this is the latest *recorded* decision, not\n // necessarily the latest decision. When captured activity continued well\n // past it, the operator's current direction may simply be unrecorded\n // (conversational decisions are not auto-captured), so note the gap rather\n // than presenting a stale decision as the current direction. The wording\n // states only what is certain — the decision predates the latest activity —\n // and does not assert that decisions were made in between, so it stays\n // honest whether the later activity is in the same session or another.\n const activityAt = summary.freshness.latestActivityAt;\n if (activityAt !== null && isTrailingStale(activityAt, dec.occurredAt)) {\n lines.push(\n ` - 注: これは最後に「記録された」判断です。最終活動 (${relativeAgeJa(activityAt, now)}) はこれより後のため、現在の方針が反映されていない可能性があります(会話での意思決定は自動記録されません。\\`basou decision capture\\` でこの session の判断を記録できます)。`,\n );\n }\n // When the latest recorded decision comes from a DIFFERENT session than the\n // representative latest session, the two \"latest\" pointers disagree. Say so,\n // so a resume reader does not treat an older thread's decision as this\n // session's direction (a linear-timeline cue, not a stale claim).\n if (summary.latestSession !== null && dec.sessionId !== summary.latestSession.sessionId) {\n lines.push(\n ` - 注: この判断は最終 session とは別の session [${shortId(dec.sessionId)}] のものです。`,\n );\n }\n if (summary.decisionCount > 1) {\n lines.push(` - ${summary.decisionCount} decisions total — see decisions.md`);\n }\n } else {\n lines.push(\"- 直近の判断: (no decisions recorded yet; capture with `basou decision capture`)\");\n }\n if (summary.relatedFiles.displayed.length > 0) {\n const shown = summary.relatedFiles.displayed.join(\", \");\n const more =\n summary.relatedFiles.overflow > 0 ? ` (... +${summary.relatedFiles.overflow} more)` : \"\";\n lines.push(`- 直近の変更ファイル: ${shown}${more}`);\n if (summary.relatedFiles.outOfRoot.length > 0) {\n // Cross-project boundary crossing: the latest session edited files\n // outside this project's source_roots. Flag it so a resuming agent does\n // not adopt another repo's work as this project's continuation. The count\n // reflects ALL out-of-root files; the listed paths are capped like the\n // line above.\n const OUT_OF_ROOT_DISPLAY = 10;\n const out = summary.relatedFiles.outOfRoot;\n const shownOut = out.slice(0, OUT_OF_ROOT_DISPLAY).join(\", \");\n const outMore =\n out.length > OUT_OF_ROOT_DISPLAY ? ` (... +${out.length - OUT_OF_ROOT_DISPLAY} more)` : \"\";\n lines.push(\n ` - ⚠ source_roots 外 ${out.length} 件 (別プロジェクトの可能性): ${shownOut}${outMore}`,\n );\n }\n } else {\n lines.push(\"- 直近の変更ファイル: (none recorded)\");\n }\n lines.push(\"\");\n\n // \"what is in flight\" — structured facts\n lines.push(\"## 何が動く\");\n lines.push(\"\");\n lines.push(`### 進行中 task (${summary.inFlightTasks.length})`);\n if (summary.inFlightTasks.length === 0) {\n lines.push(\"- (none)\");\n } else {\n for (const t of summary.inFlightTasks) {\n const linkedSuffix = t.linkedSessions > 1 ? ` — linked_sessions: ${t.linkedSessions}` : \"\";\n lines.push(`- ${t.title} (${t.status}) [${shortId(t.id)}]${linkedSuffix}`);\n }\n }\n lines.push(\"\");\n lines.push(`### 承認待ち (${summary.pendingApprovals.length})`);\n if (summary.pendingApprovals.length === 0) {\n lines.push(\"- (none)\");\n } else {\n for (const a of summary.pendingApprovals) {\n const expired = a.expired ? \" (expired)\" : \"\";\n lines.push(\n `- [${a.risk}] ${a.kind}: ${a.reason} — session ${shortId(a.sessionId)}, since ${a.createdAt}${expired}`,\n );\n }\n }\n lines.push(\"\");\n lines.push(`### 要注意 session (${summary.suspects.length})`);\n if (summary.suspects.length === 0) {\n lines.push(\"- (none)\");\n } else {\n for (const e of summary.suspects) {\n lines.push(\n `- ${shortId(e.sessionId)} (${e.status}) — ${suspectText(e.reason)}${hostSuffix(e.host)}`,\n );\n }\n }\n lines.push(\"\");\n\n // \"where am I heading\"\n lines.push(\"## どこへ向かう\");\n lines.push(\"\");\n // Open tracks lead the forward section: a strategic, unfinished direction\n // (\"the next essential thing to build, and why\") is the most important thing to\n // carry across a session boundary, and it resurfaces here every time until\n // explicitly closed. Distinct from the recorded next step (a terminal `note`)\n // and from in-flight tasks (mechanical). This is the intent-continuity layer —\n // without it an agreed direction sinks into the flat decision list and the next\n // session never sees it (the failure this section exists to prevent).\n if (summary.openTracks.length > 0) {\n const TRACK_DISPLAY_LIMIT = 10;\n const shownTracks = summary.openTracks.slice(0, TRACK_DISPLAY_LIMIT);\n const trackOverflow = summary.openTracks.length - shownTracks.length;\n lines.push(`### 未完トラック (close まで継続表示) (${summary.openTracks.length})`);\n for (const t of shownTracks) {\n const trackAge = relativeAgeJa(t.occurredAt, now);\n lines.push(`- ${t.title} [${shortId(t.decisionId)}] (${trackAge})${hostSuffix(t.host)}`);\n if (t.rationale !== null && t.rationale.trim() !== \"\") {\n lines.push(` - 理由: ${trackRationale(t.rationale)}`);\n }\n }\n if (trackOverflow > 0) {\n lines.push(`- ... +${trackOverflow} more (see decisions.md)`);\n }\n // Section-scoped close instruction: a top-level line (not an indented sub-\n // bullet) so it reads as guidance for the whole list, mirroring handoff.\n lines.push(\n \"完了したら `basou decision void <decision_id>` で閉じてください。閉じるまで毎回ここに表示されます。\",\n );\n lines.push(\"\");\n }\n // The recorded next step (a `basou note`) is the operator's explicit resume\n // hint; surface it first so a free-text handoff survives into the next session\n // rather than living only in a decision title or an external memory file.\n if (summary.latestNote !== null) {\n const noteAge = relativeAgeJa(summary.latestNote.occurredAt, now);\n lines.push(\n `- 次の起点 (記録済み, ${noteAge}): ${noteSummary(summary.latestNote.body)} [session ${shortId(summary.latestNote.sessionId)}]${hostSuffix(summary.latestNote.host)}`,\n );\n // Same honesty guard as the latest decision: if captured activity continued\n // well past when this resume hint was recorded, the work may have moved on,\n // so flag it rather than presenting a stale starting point as current.\n const activityAt = summary.freshness.latestActivityAt;\n if (activityAt !== null && isTrailingStale(activityAt, summary.latestNote.occurredAt)) {\n lines.push(\n ` - 注: この起点の記録後 (最終活動 ${relativeAgeJa(activityAt, now)}) も作業が続いています。再開点が古い可能性があります。`,\n );\n }\n }\n for (const t of summary.plannedTasks) {\n lines.push(`- ${t.title} [${shortId(t.id)}]`);\n }\n // Fall back to the decision hint only when there is no open track, no recorded\n // next step, and no planned task — otherwise the section already says where to\n // go (an open track is the strongest such signal).\n if (\n summary.openTracks.length === 0 &&\n summary.latestNote === null &&\n summary.plannedTasks.length === 0\n ) {\n const dec = summary.latestDecision;\n if (dec === null) {\n lines.push(\"- (no planned tasks or recorded next step yet)\");\n } else if (isTrailingStale(summary.freshness.latestActivityAt, dec.occurredAt)) {\n // The misfire guard: do NOT present a STALE decision as direction. Activity\n // continued well after it, so it may already be resolved/executed; an agent\n // that treats it as the next task can re-attempt completed work. Ask for the\n // continuation point instead, and demote the decision to a labelled\n // reference rather than an instruction (aligns the forward section with the\n // staleness warning already shown on the 直近の判断 line above).\n lines.push(\n \"- (no planned tasks or recorded next step — 最終活動は直近の判断より後です。継続点をユーザに確認してください)\",\n );\n lines.push(` - 参考 (古い可能性・方針ではない): ${dec.title}`);\n } else {\n lines.push(\"- (no planned tasks — direction is inferred from recent decisions)\");\n lines.push(` - 直近の判断: ${dec.title}`);\n }\n // Discoverability nudge: fires when there ARE recorded decisions but none give\n // a durable forward direction (latest is stale, or just point-in-time) — the\n // moment a strategic direction is most likely sitting only in conversation.\n // Point the agent at tracks so the next agreed direction is captured durably\n // instead of leaking again. Suppressed for a pristine workspace (no decisions\n // yet) so it is a hint at the right time, not noise, and never shown when an\n // open track / note / planned task already gives direction.\n if (dec !== null) {\n lines.push(\n ' - 次に作るべき本質的な方向性が定まったら `basou decision capture` (`\"kind\":\"track\"`) / `basou decision record --track` で track 化すると、close まで毎 session ここに継続表示されます。',\n );\n }\n }\n lines.push(\"\");\n\n // \"is this current\" — a plain verdict for a supervisor, not telemetry: is what\n // I am looking at the latest and complete, and if not, what should I do? Raw\n // ISO / per-source counts / source roots / a zero suspect count are diagnostics\n // and move under `--verbose`.\n lines.push(\"## これは最新か\");\n lines.push(\"\");\n for (const line of freshnessVerdict(summary, opts.staleness, now)) lines.push(line);\n // The verdict above reflects the LOCAL store only (the dry-run probe reads\n // this machine's native logs). With federated hosts merged in, do not let it\n // imply the whole multi-host view is current — the other hosts' freshness is\n // unknowable here (their native logs are not on this machine).\n if (summary.hosts.length > 0) {\n lines.push(\"\");\n lines.push(\n \"注: 鮮度判定はこのマシンのローカルストアのみが対象です。他ホストの取りこぼしは判定できません(各ホストで basou refresh を実行し同期してください)。\",\n );\n }\n\n if (opts.verbose) {\n lines.push(\"\");\n lines.push(\"<!-- verbose: raw freshness telemetry -->\");\n if (summary.freshness.newestStartedAt !== null) {\n lines.push(`- newest captured session: ${summary.freshness.newestStartedAt} (${newestRel})`);\n } else {\n lines.push(\"- newest captured session: (no sessions captured yet)\");\n }\n if (summary.freshness.latestActivityAt !== null) {\n lines.push(\n `- latest activity: ${summary.freshness.latestActivityAt} (${relativeAge(summary.freshness.latestActivityAt, now)})`,\n );\n }\n const sourceBreakdown = summary.freshness.bySource\n .map(({ kind, count }) => `${kind} ${count}`)\n .join(\", \");\n lines.push(\n `- sessions: ${summary.sessionCount}${sourceBreakdown !== \"\" ? ` (${sourceBreakdown})` : \"\"}`,\n );\n if (summary.freshness.sourceRoots !== null && summary.freshness.sourceRoots.length > 0) {\n lines.push(`- source roots: ${summary.freshness.sourceRoots.join(\", \")}`);\n } else {\n lines.push(\"- source roots: (single root)\");\n }\n lines.push(`- suspect sessions: ${summary.suspects.length}`);\n const probe =\n opts.staleness === null\n ? \"not run\"\n : `new ${opts.staleness.newSessions}, updated ${opts.staleness.updatedSessions}, unverifiable ${opts.staleness.unverifiableSessions ?? 0}`;\n lines.push(`- staleness probe: ${probe}`);\n }\n\n return lines.join(\"\\n\");\n}\n\n/**\n * Translate an internal source kind into the tool name a supervisor recognizes.\n * Unknown kinds pass through verbatim so a new adapter is never silently mislabeled.\n */\nfunction toolDisplayName(kind: string | null): string {\n switch (kind) {\n case \"claude-code-import\":\n case \"claude-code-adapter\":\n return \"Claude Code\";\n case \"codex-import\":\n return \"Codex\";\n case \"terminal\":\n return \"ターミナル\";\n case \"human\":\n return \"手動メモ\";\n case \"import\":\n return \"他ワークスペース\";\n default:\n return kind ?? \"不明\";\n }\n}\n\n/**\n * The plain \"これは最新か\" verdict: a status line plus one human sentence that\n * answers \"is this current, and if not what do I do?\". Freshness comes from the\n * dry-run `staleness` probe (uncaptured/grown native work); when it was not run\n * the verdict says so instead of claiming current. A non-zero suspect count is\n * surfaced as a caution even when the capture is fresh.\n */\nfunction freshnessVerdict(\n summary: OrientationSummary,\n staleness: { newSessions: number; updatedSessions: number; unverifiableSessions?: number } | null,\n now: Date,\n): string[] {\n // Unverifiable wins absolutely first: a source that GREW but could not be\n // re-imported safely (broken chain / unreadable / non-append) means the\n // capture is provably behind AND a plain `basou refresh` would skip it again.\n // Claiming \"current\" here is the false-clear this verdict exists to prevent,\n // so it is surfaced ahead of every other state, including \"no records\".\n if (staleness !== null && (staleness.unverifiableSessions ?? 0) > 0) {\n return [\n `⚠️ 最新か確認できません。変化したが安全に取り込めないセッションが ${staleness.unverifiableSessions} 件あります(ハッシュチェーン破損・非追記変更など)。`,\n \"`basou verify` で確認し、`basou refresh --force` で再取り込みしてください。\",\n ];\n }\n\n // Stale wins next: uncaptured/grown native work means there IS work to pull\n // in, even when the store itself is still empty — so this must be checked\n // before the \"no records\" branch.\n if (staleness !== null && (staleness.newSessions > 0 || staleness.updatedSessions > 0)) {\n const parts: string[] = [];\n if (staleness.newSessions > 0) parts.push(`新規 ${staleness.newSessions} 件`);\n if (staleness.updatedSessions > 0) parts.push(`更新 ${staleness.updatedSessions} 件`);\n return [\n `⚠️ 古いかもしれません。最後の取り込み以降に未取り込みの作業があります(${parts.join(\"・\")})。`,\n \"`basou refresh` で更新してください。\",\n ];\n }\n\n if (summary.freshness.newestStartedAt === null) {\n return [\n \"ℹ️ まだ記録がありません。\",\n \"このワークスペースで作業すると、ここに現在地が表示されます。\",\n ];\n }\n\n const rel = relativeAgeJa(summary.freshness.newestStartedAt, now);\n const tool = toolDisplayName(summary.freshness.newestSource);\n const suspectCount = summary.suspects.length;\n\n if (staleness === null) {\n return [\n `ℹ️ 取り込み済みの状態を表示しています。最後の作業は ${rel}(${tool})。`,\n \"最新か確認するには `basou refresh` を実行してください。\",\n ];\n }\n\n // The probe ran and found no uncaptured/grown native sessions, so the IMPORT is\n // current. Scope the claim to exactly that — the old \"取りこぼし・要注意なし\"\n // (no omissions / nothing to worry about) overclaimed: this verdict only checks\n // that captured native sessions are imported and none are suspect. It does NOT\n // (and from telemetry alone cannot) detect planning/implementation drift or\n // unrecorded decisions, so it must not imply provenance is comprehensive.\n // Federated views merge other hosts' sessions, but this verdict is driven by\n // a LOCAL dry-run probe (the remote hosts' native logs are not on this\n // machine). Scope the green claim to THIS host so it never reads as \"the whole\n // multi-host view is current\" — the local-only-freshness caveat below adds the\n // per-host sync guidance. Local-only views keep the original wording.\n const localScope = summary.hosts.length > 0 ? \"このホスト(ローカル)の\" : \"\";\n const lines = [\n `✅ ${localScope}取り込みは最新です。最後の作業は ${rel}(${tool})。未取り込みの native セッションはありません。`,\n ];\n if (suspectCount > 0) {\n lines.push(`ただし要注意セッションが ${suspectCount} 件あります(上記「要注意 session」参照)。`);\n }\n lines.push(\n \"注: この判定は取り込み済み native セッションの鮮度と suspect の有無だけを見ます。計画↔実装のドリフトや未記録の意思決定までは検知しません。\",\n );\n return lines;\n}\n\n/** Japanese relative age, e.g. \"7時間26分前\" / \"3日前\" / \"たった今\", for the verdict line. */\nfunction relativeAgeJa(startedAt: string | null, now: Date): string {\n if (startedAt === null) return \"(不明)\";\n const ms = now.getTime() - Date.parse(startedAt);\n if (!Number.isFinite(ms) || ms < 0) return \"たった今\";\n if (ms < 60_000) return \"たった今\";\n const totalMin = Math.floor(ms / 60_000);\n const days = Math.floor(totalMin / 1440);\n const hours = Math.floor((totalMin % 1440) / 60);\n const mins = totalMin % 60;\n if (days > 0) return hours > 0 ? `${days}日${hours}時間前` : `${days}日前`;\n if (hours > 0) return mins > 0 ? `${hours}時間${mins}分前` : `${hours}時間前`;\n return `${mins}分前`;\n}\n\n/** \"3h 05m ago\" / \"just now\" / \"(unknown)\" for a session's age relative to `now`. */\nfunction relativeAge(startedAt: string | undefined, now: Date): string {\n if (startedAt === undefined) return \"(unknown)\";\n const ms = now.getTime() - Date.parse(startedAt);\n if (!Number.isFinite(ms)) return \"(unknown)\";\n if (ms < 0) return \"just now\";\n if (ms < 1000) return \"just now\";\n return `${formatDurationMs(ms)} ago`;\n}\n\n// A recorded note can be multi-line and arbitrarily long; collapse whitespace\n// to keep it on one orientation bullet and cap it so a verbose handoff does not\n// dominate the view. The full body is preserved in the event (see session show).\nconst NOTE_SUMMARY_MAX = 200;\nfunction noteSummary(body: string): string {\n const oneLine = body.replace(/\\s+/g, \" \").trim();\n return oneLine.length > NOTE_SUMMARY_MAX ? `${oneLine.slice(0, NOTE_SUMMARY_MAX - 1)}…` : oneLine;\n}\n\n// A track's rationale is the WHY behind the direction; like a note it can be\n// multi-line and long, so collapse whitespace to one line and cap it. The full\n// text is preserved in the decision_recorded event (see decisions.md).\nconst TRACK_RATIONALE_MAX = 240;\nfunction trackRationale(rationale: string): string {\n const oneLine = rationale.replace(/\\s+/g, \" \").trim();\n return oneLine.length > TRACK_RATIONALE_MAX\n ? `${oneLine.slice(0, TRACK_RATIONALE_MAX - 1)}…`\n : oneLine;\n}\n\nfunction suspectText(reason: SuspectReason | null): string {\n if (reason === \"events_say_ended_but_yaml_running\") return \"ended (yaml stale)\";\n if (reason === \"running_no_end_event\") return \"no end event\";\n return \"suspect\";\n}\n\n// Prose-line short id: keep the type prefix and truncate the ULID body to its\n// first 10 chars, e.g. `task_01KRNHYRS91F5GBX...` -> `task_01KRNHYRS9`.\nfunction shortId(id: string): string {\n const sep = id.indexOf(\"_\");\n if (sep === -1) return id.slice(0, 10);\n return id.slice(0, sep + 1) + id.slice(sep + 1, sep + 1 + 10);\n}\n","import { lstat } from \"node:fs/promises\";\nimport { type PrefixedId, prefixedUlid } from \"../ids/ulid.js\";\nimport { type Manifest, ManifestSchema } from \"../schemas/manifest.schema.js\";\nimport type { BasouPaths } from \"./basou-dir.js\";\nimport { readYamlFile, writeYamlFile } from \"./yaml-store.js\";\n\n/**\n * Inputs for {@link createManifest}. Optional fields drop out of the\n * resulting Manifest entirely (they are not emitted as `null`/`undefined`\n * in YAML); pass `null` for `repositoryUrl` to keep an explicit `null`.\n */\nexport type CreateManifestInput = {\n workspaceName: string;\n projectName?: string;\n projectDescription?: string;\n repositoryUrl?: string | null;\n /** Override for tests; defaults to `new Date()`. */\n now?: Date;\n /** Override for tests; defaults to a freshly generated `ws_<ULID>`. */\n workspaceId?: PrefixedId<\"ws\">;\n /**\n * Import source roots, each RELATIVE to the repository root (e.g. `\".\"`,\n * `\"../basou-workspace\"`). Persisted under `import.source_roots` so\n * `basou refresh` / `basou import` aggregate several sibling repos into one\n * `.basou/`. Validated by `ManifestSchema` (absolute paths are rejected).\n * Omitted from the manifest entirely when absent or empty.\n */\n sourceRoots?: string[];\n};\n\n/**\n * Build a fresh Manifest object that satisfies the manifest schema's\n * minimum shape. Performs no I/O. Returned object is parse-validated by\n * `ManifestSchema`.\n */\nexport function createManifest(input: CreateManifestInput): Manifest {\n if (input.workspaceName.length === 0) {\n throw new Error(\"Workspace name is empty. Pass --name explicitly.\");\n }\n const now = (input.now ?? new Date()).toISOString();\n const workspaceId = input.workspaceId ?? prefixedUlid(\"ws\");\n\n const project: Manifest[\"project\"] = {\n ...(input.projectName !== undefined ? { name: input.projectName } : {}),\n ...(input.projectDescription !== undefined ? { description: input.projectDescription } : {}),\n ...(input.repositoryUrl !== undefined ? { repository_url: input.repositoryUrl } : {}),\n };\n\n const manifest: Manifest = {\n schema_version: \"0.1.0\",\n basou_version: \"0.1.0\",\n workspace: {\n id: workspaceId,\n name: input.workspaceName,\n created_at: now,\n updated_at: now,\n },\n project,\n capabilities: {\n enabled: [\"core\", \"claude-code-adapter\", \"terminal-recording\", \"git-capability\", \"approval\"],\n },\n approval: {\n required_for: [\"destructive_command\", \"external_send\"],\n default_risk_level: \"medium\",\n },\n adapters: {\n \"claude-code\": { enabled: true },\n },\n git: { events_log: \"ignore\" },\n ...(input.sourceRoots !== undefined && input.sourceRoots.length > 0\n ? { import: { source_roots: input.sourceRoots } }\n : {}),\n };\n return ManifestSchema.parse(manifest);\n}\n\n/**\n * Write a Manifest to `paths.files.manifest`. Re-validates via\n * `ManifestSchema` before serialization.\n *\n * Refuses to overwrite an existing manifest unless `force: true`.\n */\nexport async function writeManifest(\n paths: BasouPaths,\n manifest: Manifest,\n options?: { force?: boolean },\n): Promise<void> {\n const force = options?.force === true;\n const validated = ManifestSchema.parse(manifest);\n\n if (!force) {\n let existed = false;\n try {\n await lstat(paths.files.manifest);\n existed = true;\n } catch (error: unknown) {\n if (!hasErrorCode(error) || error.code !== \"ENOENT\") {\n throw new Error(\"Failed to inspect existing manifest\", { cause: error });\n }\n }\n if (existed) {\n throw new Error(\"Already initialized. Use --force to overwrite.\");\n }\n }\n\n await writeYamlFile(paths.files.manifest, validated);\n}\n\n/**\n * Read and parse a Manifest from `paths.files.manifest`. Throws if the file\n * is missing or contents fail `ManifestSchema` validation.\n */\nexport async function readManifest(paths: BasouPaths): Promise<Manifest> {\n const raw = await readYamlFile(paths.files.manifest);\n return ManifestSchema.parse(raw);\n}\n\nfunction hasErrorCode(error: unknown): error is Error & { code: string } {\n if (!(error instanceof Error)) return false;\n return typeof (error as unknown as Record<string, unknown>).code === \"string\";\n}\n","import { z } from \"zod\";\nimport { IsoTimestampSchema, SchemaVersionSchema, WorkspaceIdSchema } from \"./shared.schema.js\";\n\nconst ProjectSchema = z.looseObject({\n name: z.string().optional(),\n description: z.string().optional(),\n repository_url: z.string().nullable().optional(),\n});\n\nconst CapabilitiesSchema = z.looseObject({\n enabled: z.array(z.string()),\n});\n\nconst ApprovalConfigSchema = z.looseObject({\n required_for: z.array(z.string()).optional(),\n default_risk_level: z.enum([\"low\", \"medium\", \"high\", \"critical\"]),\n});\n\nconst ClaudeCodeAdapterConfigSchema = z.looseObject({\n enabled: z.boolean(),\n config_path: z.string().optional(),\n});\n\nconst AdaptersSchema = z.looseObject({\n \"claude-code\": ClaudeCodeAdapterConfigSchema,\n});\n\nconst GitConfigSchema = z.looseObject({\n events_log: z.enum([\"ignore\", \"commit\"]).default(\"ignore\"),\n});\n\n/**\n * A source root is RELATIVE to the manifest's repository root (it is resolved\n * to an absolute path at import time). manifest.yaml is a commit candidate, so\n * absolute machine paths (`/Users/...`), home-expansion (`~`), and stray\n * backslashes are rejected to keep committed manifests path-clean and\n * machine-portable. A `..`-prefixed sibling (e.g. `../basou-workspace`) is\n * allowed.\n *\n * Encoded as a regex (not a Zod refinement) so the constraint is also emitted\n * into the published JSON Schema's `pattern`, letting cross-language validators\n * enforce the same rule. It rejects: a leading `~` (home), a leading `/` (POSIX\n * absolute), any backslash anywhere (UNC / Windows / stray), a `<drive>:`\n * prefix, and null bytes; `min(1)` rejects the empty string. It also rejects\n * leading/trailing whitespace: without the `(?!\\s)` and trailing `[^\\0\\\\\\s]`\n * guards a leading space would \"shield\" a forbidden first char (\" ~/x\" would\n * pass), and `basou project sync` normalizes (`.trim()`) before persisting, so\n * a padded path that passed at read time would fail re-validation on write —\n * and a padded `source_roots` entry resolves (path.resolve) to a missed repo\n * while sync wrongly reports it covered. Interior whitespace stays allowed\n * (`../my dir` is a legitimate directory name).\n */\nconst SOURCE_ROOT_PATTERN = /^(?![~/\\\\])(?![A-Za-z]:)(?!\\s)[^\\0\\\\]*[^\\0\\\\\\s]$/;\n\nconst SourceRootSchema = z.string().min(1).regex(SOURCE_ROOT_PATTERN, {\n message:\n \"source_roots entries must be relative paths (no absolute path, '~', '\\\\', or null byte)\",\n});\n\n/**\n * Optional import config. `source_roots` lets one `.basou/` aggregate the\n * native logs of several sibling repositories (each a path relative to the\n * repo root, e.g. `[\".\"`, `\"../basou\"]`). `basou refresh` / `basou import`\n * scan every listed root; the list is the complete set, so include `\".\"` to\n * keep the host repository itself. Absent => the host repository root only.\n */\nconst ImportConfigSchema = z.looseObject({\n source_roots: z.array(SourceRootSchema).min(1).optional(),\n});\n\n/**\n * A project's declared repo roster (the \"saddle\" model): the single source of\n * truth for which repos make up this project. The capture config\n * (`import.source_roots`) is reconciled against this list, and\n * `basou project check` reports drift between the two (e.g. a companion repo\n * wired into the workspace but never added to `source_roots`). Each `path` is\n * relative to the manifest repo root, reusing the machine-portable source-root\n * constraint. `visibility` is the repo's git visibility, `language` its source\n * (commit/comment/code) language, and `publishes` the surfaces it deploys, each\n * independent of the others. `visibility`, `language`, and `publishes` are all\n * optional so a roster can be adopted first and enriched incrementally.\n */\nconst RepoVisibilitySchema = z.enum([\"public\", \"private\", \"future-public\"]);\n\n/**\n * The audience-driven language axis, independent of visibility:\n * `en` / `ja` for a single audience, `en+ja` when both are served.\n */\nconst RepoLanguageSchema = z.enum([\"en\", \"ja\", \"en+ja\"]);\n\n/** A published surface kind: a deployed website or a package registry. */\nconst PublishKindSchema = z.enum([\"web\", \"npm\"]);\n\n/**\n * One published surface. Its `visibility` and `language` are independent of the\n * source repo's (a private repo commonly publishes a public site) and both are\n * optional so a surface can be declared before those facts are pinned down.\n * `kind` is required: a surface with no kind is meaningless.\n */\nconst PublishTargetSchema = z.looseObject({\n kind: PublishKindSchema,\n visibility: RepoVisibilitySchema.optional(),\n language: RepoLanguageSchema.optional(),\n});\n\nconst RepoEntrySchema = z.looseObject({\n path: SourceRootSchema,\n visibility: RepoVisibilitySchema.optional(),\n language: RepoLanguageSchema.optional(),\n publishes: z.array(PublishTargetSchema).optional(),\n});\n\nconst WorkspaceMetaSchema = z.looseObject({\n id: WorkspaceIdSchema,\n name: z.string().min(1),\n created_at: IsoTimestampSchema,\n updated_at: IsoTimestampSchema,\n /**\n * The generated workspace view: a throwaway directory that aggregates the\n * roster repos via symlinks (one `<repo-basename>` symlink per repo). A path\n * relative to the manifest root, reusing the machine-portable source-root\n * constraint. Absent for a solo project (no view needed); `basou project\n * workspace` reconciles the view's symlinks to the declared roster.\n */\n view: SourceRootSchema.optional(),\n});\n\n/**\n * Schema for `.basou/manifest.yaml`. The minimal manifest carries\n * schema_version, basou_version, workspace metadata, project info, enabled\n * capabilities, approval policy, adapter config, and git policy. The\n * `adapters.\"claude-code\"` key uses a hyphen; downstream code accesses it\n * via bracket notation.\n *\n * Every object here is `looseObject` (NOT the default strip), so unknown keys\n * at every level survive parse. The manifest is the declarative source of truth\n * and is git-tracked and read-modify-written by `basou project` commands; with\n * the default strip, a field this basou does not recognize — a newer version's\n * additive field, a future adapter under `adapters`, a hand-added key — would be\n * silently dropped on the next write. Preserving them keeps basou from destroying\n * config it does not understand (forward-compatible), while known fields are still\n * fully type-checked and validated. {@link unknownManifestKeys} surfaces the\n * unrecognized top-level keys so preservation is not silent.\n */\nexport const ManifestSchema = z.looseObject({\n schema_version: SchemaVersionSchema,\n basou_version: z.literal(\"0.1.0\"),\n workspace: WorkspaceMetaSchema,\n project: ProjectSchema,\n capabilities: CapabilitiesSchema,\n approval: ApprovalConfigSchema,\n adapters: AdaptersSchema,\n git: GitConfigSchema,\n import: ImportConfigSchema.optional(),\n repos: z.array(RepoEntrySchema).min(1).optional(),\n});\n\n/** Inferred runtime type for {@link ManifestSchema}. */\nexport type Manifest = z.infer<typeof ManifestSchema>;\n\n/** The declared top-level manifest keys, derived from the schema (no hardcoded drift). */\nconst KNOWN_TOP_LEVEL_KEYS: ReadonlySet<string> = new Set(Object.keys(ManifestSchema.shape));\n\n/**\n * The unrecognized TOP-LEVEL keys a parsed manifest carries — fields preserved by\n * the loose schema that this basou does not know. Returned sorted, for surfacing as\n * an advisory by the read-modify-write commands so preservation is not silent (a\n * newer version's section, or a hand-added/typo'd key, is flagged rather than\n * dropped). Nested unknown keys are preserved too but not enumerated here; this is\n * the high-signal top-level case. Read-only — never mutates.\n */\nexport function unknownManifestKeys(manifest: Manifest): string[] {\n return Object.keys(manifest)\n .filter((k) => !KNOWN_TOP_LEVEL_KEYS.has(k))\n .sort();\n}\n","/**\n * The single lexical relative-path normalizer shared by every `basou project`\n * command (roster drift, source-root reconcile, archive/rename matching, view +\n * symlink + preset dedup). It produces a canonical COMPARISON key — it is the\n * answer to \"do these two declared paths denote the same location?\", not a\n * validator (see `SOURCE_ROOT_PATTERN` in the manifest schema) and not an identity\n * resolver (see `realpathSync` for on-disk identity).\n *\n * It is string-pure (NO filesystem access): two paths must compare equal from\n * their spelling alone, so the manifest can be reasoned about without touching\n * disk. It:\n * - trims surrounding whitespace (declared paths carry no leading/trailing space);\n * - drops empty segments (collapsing `//` and a trailing `/`) and `.` segments;\n * - resolves a `..` against the preceding NORMAL segment, and otherwise keeps it\n * (a relative path may ascend: `../b`, `../../b` are preserved);\n * - preserves whitespace INSIDE a segment (a directory may legitimately be named\n * with spaces — `../my repo` stays `../my repo`), never collapsing it; and\n * - yields `.` for an empty / all-dot result.\n *\n * So `../b`, `../b/`, `../b/.`, `./../b`, and `a/../../b` all canonicalize to\n * `../b`, while `x/..` and `a/b/../..` canonicalize to `.`. Absolute input (which\n * declared paths never are) is normalized defensively, `..` above the root being\n * dropped.\n */\nexport function normalizeRelativePath(p: string): string {\n const trimmed = p.trim();\n // Absolute detection is on the trimmed string, so a malformed leading-\n // whitespace-then-slash input (` /a`) canonicalizes as absolute rather than\n // mis-resolving. This is defensive only: a declared/validated path is always\n // relative (SOURCE_ROOT_PATTERN forbids both leading whitespace and a leading\n // slash), so this branch is unreachable from any manifest value.\n const absolute = trimmed.startsWith(\"/\");\n const out: string[] = [];\n for (const seg of trimmed.split(\"/\")) {\n if (seg === \"\" || seg === \".\") continue;\n if (seg === \"..\") {\n const top = out[out.length - 1];\n if (top !== undefined && top !== \"..\") {\n out.pop(); // resolve against a preceding normal segment\n } else if (!absolute) {\n out.push(\"..\"); // a relative path may ascend; an absolute one cannot pass root\n }\n continue;\n }\n out.push(seg);\n }\n const joined = out.join(\"/\");\n if (absolute) return `/${joined}`;\n return joined.length === 0 ? \".\" : joined;\n}\n","/**\n * Archive (fold) a repo out of a project's declared roster — the inverse of\n * `adopt` + `sync`, and the first PRUNING step in the saddle model (every prior\n * slice was additive and deliberately deferred removal). When a repo has served\n * its purpose, archiving removes it from the declared `repos` roster and prunes\n * its capture entry from `source_roots`, so it is no longer part of the project\n * or scanned by `refresh`.\n *\n * Pure: it computes the manifest mutation from the DECLARED lists alone — no\n * filesystem or git I/O — so it works even when the repo is already gone from\n * disk (the common \"I deleted the repo, now clean basou\" case). Historical\n * captured data in the anchor is NOT touched (archiving stops future capture,\n * it does not erase the past). The repo-side wiring teardown (view symlink,\n * instruction symlinks, .gitignore, canonical) is the caller's separate,\n * higher-blast-radius concern; this only mutates the manifest's declaration.\n */\n\nimport { normalizeRelativePath as normalize } from \"./relative-path.js\";\nimport type { RepoEntry } from \"./roster.js\";\n\nexport type ArchivePlan = {\n /** The normalized target path being archived. */\n target: string;\n /** True when the target is declared in the roster. */\n found: boolean;\n /**\n * True when the target resolves to the anchor/host (`.`). Archiving the\n * project's own root is refused: it is the home of the manifest, not a member\n * repo to fold. The caller writes nothing in this case.\n */\n isAnchor: boolean;\n /** The roster entry that would be removed (echoed for the report); set only when found & not anchor. */\n rosterEntry?: RepoEntry | undefined;\n /** The roster after removal. An empty array means the project closes (the `repos` key is dropped). */\n nextRepos: RepoEntry[];\n /** True when removal leaves the roster empty (the unified-instruction project is fully closed). */\n reposEmptied: boolean;\n /** The `source_roots` entry (normalized) that would be pruned; set only when the target was captured. */\n sourceRootRemoval?: string | undefined;\n /** The `source_roots` after pruning; set only when a prune actually happens. */\n nextSourceRoots?: string[] | undefined;\n /** Declared repos remaining after removal. */\n remainingCount: number;\n /** True when exactly one repo remains: the project becomes solo and the workspace view is no longer needed. */\n becomesSolo: boolean;\n};\n\n/**\n * Compute the {@link ArchivePlan} for folding `target` out of the project. Pure:\n * it partitions the declared `repos` and `source_roots` by normalized path.\n *\n * - Archiving the anchor (`.`, or a path the caller resolved to the manifest\n * root) is refused — the plan reports `isAnchor` and removes nothing.\n * - A target not in the roster yields `found: false` and no change (the caller\n * reports the declared paths so the operator sees what to type).\n * - Otherwise EVERY roster entry matching the normalized target is removed (so a\n * path declared twice does not survive), and the matching `source_roots` entry\n * (commonly the same path) is pruned. Only the EXACT normalized target is\n * pruned from `source_roots`; entries for every other path — the host `.`, a\n * generated workspace-view source root — survive.\n * - When removal empties the roster, `nextRepos` is `[]` and `reposEmptied` is\n * true (the caller drops the `repos` key — `repos: []` is not a valid roster).\n */\nexport function planArchive(input: {\n repos?: RepoEntry[];\n sourceRoots?: string[];\n target: string;\n targetIsAnchor?: boolean;\n}): ArchivePlan {\n const target = normalize(input.target);\n const repos = input.repos ?? [];\n const isAnchor = input.targetIsAnchor === true || target === \".\";\n const matched = repos.filter((r) => normalize(r.path) === target);\n const found = matched.length > 0;\n\n // Refusals / no-ops: archiving the anchor, or a target not in the roster,\n // changes nothing. Report the situation and leave the lists untouched.\n if (isAnchor || !found) {\n return {\n target,\n found,\n isAnchor,\n nextRepos: repos,\n reposEmptied: false,\n remainingCount: repos.length,\n becomesSolo: false,\n };\n }\n\n const nextRepos = repos.filter((r) => normalize(r.path) !== target);\n const remainingCount = nextRepos.length;\n\n let sourceRootRemoval: string | undefined;\n let nextSourceRoots: string[] | undefined;\n if (input.sourceRoots !== undefined) {\n const pruned = input.sourceRoots.filter((s) => normalize(s) !== target);\n if (pruned.length !== input.sourceRoots.length) {\n sourceRootRemoval = target;\n nextSourceRoots = pruned;\n }\n }\n\n return {\n target,\n found: true,\n isAnchor: false,\n rosterEntry: matched[matched.length - 1],\n nextRepos,\n reposEmptied: remainingCount === 0,\n ...(sourceRootRemoval !== undefined ? { sourceRootRemoval } : {}),\n ...(nextSourceRoots !== undefined ? { nextSourceRoots } : {}),\n remainingCount,\n becomesSolo: remainingCount === 1,\n };\n}\n","/**\n * Plan the agent instruction-file `.gitignore` entries a declared repo needs\n * (the first generation step of the \"saddle\" model). For a public-facing repo,\n * the agent instruction files (AGENTS.md, CLAUDE.md, …) must be GITIGNORED so the\n * gitignored symlinks to the private canonical never enter public git history.\n * `basou project gitignore` reconciles each repo's `.gitignore` to that; this is\n * the pure planner behind it.\n *\n * Pure: it diffs the REQUIRED patterns against the repo's CURRENT `.gitignore`\n * lines (both gathered by the caller) and reports only what is MISSING — it never\n * proposes removing a line. The realpath / file reading / writing is the caller's\n * job. The privacy decision is visibility-aware: only public / future-public\n * repos require the patterns (a private anchor may legitimately track its\n * canonical), and a repo with unset visibility is skipped (reported), never\n * acted on by guesswork.\n */\n\nimport type { RepoVisibility } from \"./roster.js\";\n\n/** A declared repo's current `.gitignore` state, gathered by the caller. */\nexport type RepoGitignoreFacts = {\n /** Roster repo path (relative to the manifest root). */\n path: string;\n /** Declared visibility; undefined when the operator has not set it yet. */\n visibility?: RepoVisibility | undefined;\n /** False when the repo path could not be resolved / is not a usable git repo. */\n reachable: boolean;\n /** Existing `.gitignore` lines, trimmed; an empty array when there is no `.gitignore`. */\n currentLines: string[];\n};\n\n/** The patterns to ADD to one repo's `.gitignore` (never any to remove). */\nexport type RepoGitignorePlan = {\n path: string;\n toAdd: string[];\n};\n\nexport type GitignorePlanSummary = {\n /** Repos that need patterns added (those with an empty `toAdd` are omitted). */\n plans: RepoGitignorePlan[];\n /** Repo paths skipped because visibility is unset (cannot decide safely). */\n unknown: string[];\n /** Repo paths that could not be resolved / are not usable git repos. */\n unreachable: string[];\n /**\n * True only when nothing needs adding AND every repo was judgeable and\n * reachable — so a clean verdict is never claimed while some repos were\n * skipped (unset visibility) or could not be inspected (unreachable).\n */\n ok: boolean;\n};\n\n/** Whether a visibility exposes git history to the public (so instruction files must be ignored). */\nfunction isPublicFacing(v: RepoVisibility | undefined): v is \"public\" | \"future-public\" {\n return v === \"public\" || v === \"future-public\";\n}\n\n/**\n * Compute the {@link GitignorePlanSummary}: for each public-facing, reachable\n * repo, the `required` patterns that are not already present in its `.gitignore`\n * (compared by trimmed exact line). Private repos require nothing; unset\n * visibility is reported as `unknown` and unreachable repos as `unreachable`.\n * `ok` is true when no repo needs any addition.\n */\nexport function planGitignore(input: {\n repos: RepoGitignoreFacts[];\n required: string[];\n}): GitignorePlanSummary {\n const plans: RepoGitignorePlan[] = [];\n const unknown: string[] = [];\n const unreachable: string[] = [];\n\n for (const repo of input.repos) {\n if (!repo.reachable) {\n unreachable.push(repo.path);\n continue;\n }\n if (repo.visibility === undefined) {\n unknown.push(repo.path);\n continue;\n }\n if (!isPublicFacing(repo.visibility)) continue;\n\n // A line already present suppresses re-adding. Treat an anchored-root form\n // (`/AGENTS.md`) as covering the plain pattern (`AGENTS.md`) so we do not add\n // a redundant equivalent rule. A directory-only `AGENTS.md/` or a comment is\n // intentionally NOT treated as equivalent.\n const present = new Set<string>();\n for (const line of repo.currentLines) {\n const trimmed = line.trim();\n present.add(trimmed);\n if (trimmed.startsWith(\"/\")) present.add(trimmed.slice(1));\n }\n const toAdd = input.required.filter((p) => !present.has(p));\n if (toAdd.length > 0) plans.push({ path: repo.path, toAdd });\n }\n\n return {\n plans,\n unknown,\n unreachable,\n ok: plans.length === 0 && unknown.length === 0 && unreachable.length === 0,\n };\n}\n","/**\n * Agent instruction-file \"A preset\" generation. A repo's\n * canonical instruction file splits into a STABLE PRESET (its source\n * visibility, source language, and published surfaces — facts derived from the\n * manifest) and a HAND-AUTHORED POLICY (tech choices, coding rules). This\n * renders the stable preset from the declaration so the operator stops\n * hand-typing it into every prompt, and plans how that generated region\n * reconciles against each repo's canonical — so `basou project preset` keeps it\n * in sync without ever touching the hand-authored content around it.\n *\n * Pure: it renders deterministic markdown from declared fields and judges\n * already-gathered facts (does the canonical exist? what does its generated\n * region currently hold?). The filesystem / marker reading / writing is the\n * caller's job.\n */\n\nimport { normalizeRelativePath as normalizePath } from \"./relative-path.js\";\nimport type { PublishTarget, RepoLanguage, RepoVisibility } from \"./roster.js\";\n\n/** The declared fields the preset block is rendered from. */\nexport type PresetRepo = {\n visibility?: RepoVisibility | undefined;\n language?: RepoLanguage | undefined;\n publishes?: PublishTarget[] | undefined;\n};\n\n/** Source git-visibility, rendered with the consequence the agent must respect. */\nfunction visibilityLabel(v: RepoVisibility | undefined): string {\n switch (v) {\n case \"public\":\n return \"public(git 履歴は公開)\";\n case \"private\":\n return \"private(git 履歴は非公開)\";\n case \"future-public\":\n return \"future-public(現在は非公開・将来公開予定)\";\n default:\n return \"未設定\";\n }\n}\n\n/** Source language (commits/comments/code), rendered with the audience it serves. */\nfunction sourceLanguageLabel(l: RepoLanguage | undefined): string {\n switch (l) {\n case \"en\":\n return \"en(commit・コメント・コードは英語)\";\n case \"ja\":\n return \"ja(commit・コメント・コードは日本語)\";\n case \"en+ja\":\n return \"en+ja(commit・コメント・コードは日英)\";\n default:\n return \"未設定\";\n }\n}\n\n/** Published-surface kind. */\nfunction publishKindLabel(k: PublishTarget[\"kind\"]): string {\n return k === \"web\" ? \"web(デプロイ)\" : \"npm(パッケージ)\";\n}\n\n/** A published surface's visibility (independent of the source repo's). */\nfunction publishVisibilityLabel(v: RepoVisibility | undefined): string {\n switch (v) {\n case \"public\":\n return \"公開\";\n case \"private\":\n return \"非公開\";\n case \"future-public\":\n return \"将来公開\";\n default:\n return \"可視性未設定\";\n }\n}\n\n/** A published surface's content language (read by end users; may differ from source). */\nfunction contentLanguageLabel(l: RepoLanguage | undefined): string {\n return l ?? \"言語未設定\";\n}\n\n/**\n * Whether a repo has anything to render. A repo with no visibility, no language,\n * and no published surface yields an all-\"未設定\" block that helps no one, so it\n * is reported as `undeclared` rather than generated.\n */\nexport function isRenderable(repo: PresetRepo): boolean {\n return (\n repo.visibility !== undefined ||\n repo.language !== undefined ||\n (repo.publishes !== undefined && repo.publishes.length > 0)\n );\n}\n\n/**\n * Render the stable-preset markdown block (the content that lives BETWEEN the\n * BASOU:GENERATED markers in a canonical). Deterministic and OSS-generic: it\n * derives entirely from the declared fields, embedding no operator-specific\n * names, so re-running on an unchanged manifest produces byte-identical output\n * (the basis for drift detection). The published surfaces are listed in their\n * declared order. Returns the block WITHOUT a trailing newline; the marker\n * writer adds the surrounding structure.\n */\nexport function renderPresetBlock(repo: PresetRepo): string {\n const lines: string[] = [];\n lines.push(\"## プロジェクト構成(basou が生成 — manifest が正本)\");\n lines.push(\"\");\n lines.push(\n \"このセクションは `.basou/manifest.yaml` の宣言から `basou project preset` が生成します。編集は manifest 側で行ってください(マーカー外の記述は保持されます)。\",\n );\n lines.push(\"\");\n lines.push(`- ソース可視性: ${visibilityLabel(repo.visibility)}`);\n lines.push(`- ソース言語: ${sourceLanguageLabel(repo.language)}`);\n const publishes = repo.publishes ?? [];\n if (publishes.length === 0) {\n lines.push(\"- 配信物: なし\");\n } else {\n lines.push(\"- 配信物:\");\n for (const p of publishes) {\n lines.push(\n ` - ${publishKindLabel(p.kind)} — ${publishVisibilityLabel(p.visibility)} / ${contentLanguageLabel(p.language)}`,\n );\n }\n }\n return lines.join(\"\\n\");\n}\n\n/**\n * The canonical's marker state as parsed by the caller (mirrors\n * `markdown-store`'s `MarkerSection.kind`). `ok` means exactly one well-ordered\n * marker pair; any other value means the generated region cannot be located\n * safely, so the preset is NOT injected (we never clobber a hand-authored file).\n */\nexport type PresetMarkerKind =\n | \"ok\"\n | \"no_markers\"\n | \"missing_start\"\n | \"missing_end\"\n | \"multiple_pairs\"\n | \"wrong_order\";\n\n/** The gathered facts for one declared repo. */\nexport type RepoPresetFacts = {\n /** Roster repo path (relative to the manifest root). */\n path: string;\n /** True when this repo IS the project anchor (its own AGENTS.md is hand-maintained; skipped). */\n isAnchor: boolean;\n /** False when the repo path could not be resolved / is not a usable git repo. */\n reachable: boolean;\n /** Declared fields (the render input). */\n visibility?: RepoVisibility | undefined;\n language?: RepoLanguage | undefined;\n publishes?: PublishTarget[] | undefined;\n /**\n * The canonical's repo name (`<name>` in `agents/<name>/AGENTS.md`). Two\n * DISTINCT repos sharing it would write to one canonical, so it is used to\n * detect that. Set by the caller for reachable, non-anchor repos.\n */\n canonicalName?: string | undefined;\n /** Whether the canonical file exists on disk. */\n canonicalPresent: boolean;\n /**\n * Whether the present canonical could be read. Defaults to readable; the\n * caller sets it `false` when the file exists but a non-ENOENT read failed\n * (e.g. it is a directory, or permission denied), so one bad canonical\n * degrades only that repo instead of crashing the whole report.\n */\n canonicalReadable?: boolean | undefined;\n /** Marker parse result of the canonical (only meaningful when present and readable). */\n markerKind?: PresetMarkerKind | undefined;\n /** Current generated-region content (only when `markerKind === \"ok\"`). */\n currentBlock?: string | undefined;\n};\n\n/** `create` seeds an absent canonical; `update` replaces the region of an existing one. */\nexport type PresetAction = \"create\" | \"update\";\n\n/** A repo whose canonical's generated region will be created or updated. */\nexport type RepoPresetPlan = {\n path: string;\n canonicalName: string;\n action: PresetAction;\n /** The block that will be written (the marker-delimited content). */\n desiredBlock: string;\n};\n\n/**\n * A canonical that exists but whose markers cannot be located safely (absent or\n * malformed). The region is NOT injected — surfaced so the operator can add the\n * markers (or remove the malformed ones) by hand.\n */\nexport type PresetMarkerConflict = {\n repo: string;\n reason: Exclude<PresetMarkerKind, \"ok\">;\n};\n\n/**\n * Two or more DISTINCT declared repos whose canonical resolves to the same\n * `agents/<canonicalName>/AGENTS.md`. They would write over one canonical, so\n * neither is generated; the operator must disambiguate.\n */\nexport type PresetCollision = {\n canonicalName: string;\n repos: string[];\n};\n\nexport type PresetPlanSummary = {\n /** Repos whose canonical's generated region will be created/updated (only those with work). */\n plans: RepoPresetPlan[];\n /** Repos already in sync (canonical present, ok markers, block matches). */\n inSync: string[];\n /** Repos with nothing declared to render (no visibility, language, or published surface). */\n undeclared: string[];\n /** Canonicals that exist but whose markers are absent/malformed — not overwritten. */\n markerConflicts: PresetMarkerConflict[];\n /** Repos whose canonical exists but could not be read (degraded, not generated). */\n unreadable: string[];\n /** Groups of distinct repos that resolve to the same canonical (ambiguous; not generated). */\n collisions: PresetCollision[];\n /** Repos that resolve to the anchor (their own AGENTS.md is hand-maintained; skipped). */\n anchors: string[];\n /** Repo paths that could not be resolved / are not usable git repos. */\n unreachable: string[];\n /**\n * True only when nothing needs writing AND there are no marker conflicts, no\n * unreadable canonicals, no collisions, no unreachable repos, and no\n * undeclared repos — so a clean \"all in sync\" verdict is never claimed while\n * some repo was skipped or unjudgeable. Anchors do not block it (they are\n * intentionally not generated).\n */\n ok: boolean;\n};\n\n/** Normalize a block for in-sync comparison: LF line endings, no trailing blank lines. */\nfunction normalizeBlock(s: string): string {\n return s.replace(/\\r\\n/g, \"\\n\").replace(/\\n+$/, \"\");\n}\n\n/**\n * Compute the {@link PresetPlanSummary} from per-repo facts. For each declared,\n * non-anchor, reachable, renderable repo: an absent canonical is a `create`, an\n * existing canonical with an `ok` marker region is an `update` (or `inSync` when\n * the region already matches), and a canonical with absent/malformed markers is\n * a {@link PresetMarkerConflict} (never overwritten). The anchor is skipped\n * (`anchors`), an unrenderable repo is `undeclared`, and an unresolvable repo is\n * `unreachable`.\n *\n * Robustness:\n * - Facts are deduped by normalized path (first wins), so a repo listed twice\n * never yields duplicate plans / report entries.\n * - Two DISTINCT repos resolving to the same canonical name are a\n * {@link PresetCollision} and neither is generated (silent clobbering of one\n * canonical is surfaced, not actioned).\n */\nexport function summarizePresetPlan(facts: RepoPresetFacts[]): PresetPlanSummary {\n // Dedup by normalized path (first declaration wins).\n const deduped: RepoPresetFacts[] = [];\n const seenPath = new Set<string>();\n for (const f of facts) {\n const key = normalizePath(f.path);\n if (seenPath.has(key)) continue;\n seenPath.add(key);\n deduped.push(f);\n }\n\n // Detect canonical-name collisions among repos that would actually generate\n // (non-anchor, reachable, renderable, canonical name known).\n const byCanonical = new Map<string, string[]>();\n for (const f of deduped) {\n if (f.isAnchor || !f.reachable || f.canonicalName === undefined || !isRenderable(f)) continue;\n const repos = byCanonical.get(f.canonicalName) ?? [];\n repos.push(f.path);\n byCanonical.set(f.canonicalName, repos);\n }\n const collisions: PresetCollision[] = [];\n const collidingPaths = new Set<string>();\n for (const [canonicalName, repos] of byCanonical) {\n if (repos.length > 1) {\n collisions.push({ canonicalName, repos });\n for (const r of repos) collidingPaths.add(r);\n }\n }\n\n const plans: RepoPresetPlan[] = [];\n const inSync: string[] = [];\n const undeclared: string[] = [];\n const markerConflicts: PresetMarkerConflict[] = [];\n const unreadable: string[] = [];\n const anchors: string[] = [];\n const unreachable: string[] = [];\n\n for (const f of deduped) {\n if (f.isAnchor) {\n anchors.push(f.path);\n continue;\n }\n if (!f.reachable) {\n unreachable.push(f.path);\n continue;\n }\n if (!isRenderable(f)) {\n undeclared.push(f.path);\n continue;\n }\n // A repo sharing a canonical name with another is surfaced as a collision\n // and never generated.\n if (collidingPaths.has(f.path)) continue;\n // Reachable + renderable + non-colliding repos always have a canonical name\n // (the caller sets it from the resolved basename); guard for type-safety.\n if (f.canonicalName === undefined) {\n unreachable.push(f.path);\n continue;\n }\n\n const desiredBlock = renderPresetBlock(f);\n if (!f.canonicalPresent) {\n plans.push({ path: f.path, canonicalName: f.canonicalName, action: \"create\", desiredBlock });\n continue;\n }\n // Canonical exists but could not be read — degrade this repo, do not generate.\n if (f.canonicalReadable === false) {\n unreadable.push(f.path);\n continue;\n }\n if (f.markerKind === \"ok\") {\n if (normalizeBlock(f.currentBlock ?? \"\") === normalizeBlock(desiredBlock)) {\n inSync.push(f.path);\n } else {\n plans.push({\n path: f.path,\n canonicalName: f.canonicalName,\n action: \"update\",\n desiredBlock,\n });\n }\n continue;\n }\n // Canonical present but markers absent/malformed — do not clobber.\n markerConflicts.push({ repo: f.path, reason: f.markerKind ?? \"no_markers\" });\n }\n\n return {\n plans,\n inSync,\n undeclared,\n markerConflicts,\n unreadable,\n collisions,\n anchors,\n unreachable,\n ok:\n plans.length === 0 &&\n markerConflicts.length === 0 &&\n unreadable.length === 0 &&\n collisions.length === 0 &&\n unreachable.length === 0 &&\n undeclared.length === 0,\n };\n}\n","/**\n * Rename (re-path) a repo in a project's declared roster. When a repo's\n * directory is moved or renamed on disk, its declared `path` (and the matching\n * `source_roots` capture entry) must follow, or the roster drifts from reality.\n * This is the saddle model's maintenance counterpart to `archive` — it mutates\n * the manifest's path references rather than removing them.\n *\n * Pure: it computes the mutation from the DECLARED lists alone — no filesystem\n * or git I/O — so it works regardless of whether the move has happened on disk\n * yet. The repo-side wiring that embeds the old basename (the anchor canonical\n * `agents/<basename>/AGENTS.md`, the workspace-view symlink, the relative\n * targets of the repo's own instruction symlinks) is the caller's separate\n * concern; this only re-paths the declaration.\n */\n\nimport { normalizeRelativePath as normalize } from \"./relative-path.js\";\nimport type { RepoEntry } from \"./roster.js\";\n\nexport type RenamePlan = {\n /** The normalized source path being renamed. */\n oldTarget: string;\n /** The normalized destination path. */\n newTarget: string;\n /** True when old and new normalize to the same path (nothing to do). */\n noop: boolean;\n /**\n * True when the source resolves to the anchor/host (`.`). Renaming the\n * project's own root is refused; the caller writes nothing.\n */\n isAnchor: boolean;\n /** True when the source path is declared in the roster. */\n found: boolean;\n /** True when the destination path is ALREADY declared (a distinct entry) — refused to avoid a duplicate. */\n collision: boolean;\n /** The roster entry being renamed (echoed for the report); set only in the actionable case. */\n rosterEntry?: RepoEntry | undefined;\n /** The roster after re-pathing the entry (other fields preserved). */\n nextRepos: RepoEntry[];\n /** True when the roster changed. */\n reposChanged: boolean;\n /** The old normalized path that was re-pathed in `source_roots`; set only when it was captured. */\n sourceRootRenamed?: string | undefined;\n /** The `source_roots` after re-pathing; set only when a rename happened. */\n nextSourceRoots?: string[] | undefined;\n /** True when the basename changes (old/new last segment differ) — the repo-side canonical/view names need renaming too. */\n basenameChanged: boolean;\n};\n\n/** The last path segment of a normalized relative path (e.g. \"../a/x\" => \"x\", \".\" => \".\"). */\nexport function pathBasename(p: string): string {\n const parts = normalize(p).split(\"/\");\n return parts[parts.length - 1] as string;\n}\n\n/** Dedup roster entries by normalized path (first wins). */\nfunction dedupRepos(entries: RepoEntry[]): RepoEntry[] {\n const seen = new Set<string>();\n const out: RepoEntry[] = [];\n for (const e of entries) {\n const k = normalize(e.path);\n if (seen.has(k)) continue;\n seen.add(k);\n out.push(e);\n }\n return out;\n}\n\n/** Dedup source-root strings by normalized form (first occurrence wins, original form kept). */\nfunction dedupNorm(items: string[]): string[] {\n const seen = new Set<string>();\n const out: string[] = [];\n for (const s of items) {\n const k = normalize(s);\n if (seen.has(k)) continue;\n seen.add(k);\n out.push(s);\n }\n return out;\n}\n\n/**\n * Compute the {@link RenamePlan} for re-pathing `oldPath` to `newPath`. Pure: it\n * re-maps the declared `repos` and `source_roots` by normalized path.\n *\n * - A no-op (old === new), renaming the anchor, a source not in the roster, or a\n * destination that already exists as a distinct entry (collision) all change\n * nothing — the caller writes nothing and the report explains.\n * - Otherwise EVERY roster entry matching the old path is re-pathed to the new\n * path (preserving its visibility/language/publishes), and the result is\n * deduped (so an old path declared twice collapses to one new entry). The\n * matching `source_roots` entry (commonly the same path) is re-pathed and\n * deduped likewise; all other entries (the host `.`, a view source root) keep\n * their position and form.\n */\nexport function planRename(input: {\n repos?: RepoEntry[];\n sourceRoots?: string[];\n oldPath: string;\n newPath: string;\n oldIsAnchor?: boolean;\n}): RenamePlan {\n const oldTarget = normalize(input.oldPath);\n const newTarget = normalize(input.newPath);\n const repos = input.repos ?? [];\n const basenameChanged = pathBasename(oldTarget) !== pathBasename(newTarget);\n const noop = oldTarget === newTarget;\n const isAnchor = input.oldIsAnchor === true || oldTarget === \".\";\n const found = repos.some((r) => normalize(r.path) === oldTarget);\n const collision = !noop && repos.some((r) => normalize(r.path) === newTarget);\n\n if (noop || isAnchor || !found || collision) {\n return {\n oldTarget,\n newTarget,\n noop,\n isAnchor,\n found,\n collision,\n nextRepos: repos,\n reposChanged: false,\n basenameChanged,\n };\n }\n\n // FIRST match wins, consistently: dedupRepos below also keeps the first, so the\n // echoed entry and the written entry are the same object when a path is\n // (malformed-ly) declared twice with differing metadata.\n const rosterEntry = repos.find((r) => normalize(r.path) === oldTarget);\n const nextRepos = dedupRepos(\n repos.map((r) => (normalize(r.path) === oldTarget ? { ...r, path: newTarget } : r)),\n );\n\n let sourceRootRenamed: string | undefined;\n let nextSourceRoots: string[] | undefined;\n if (\n input.sourceRoots !== undefined &&\n input.sourceRoots.some((s) => normalize(s) === oldTarget)\n ) {\n nextSourceRoots = dedupNorm(\n input.sourceRoots.map((s) => (normalize(s) === oldTarget ? newTarget : s)),\n );\n sourceRootRenamed = oldTarget;\n }\n\n return {\n oldTarget,\n newTarget,\n noop: false,\n isAnchor: false,\n found: true,\n collision: false,\n rosterEntry,\n nextRepos,\n reposChanged: true,\n ...(sourceRootRenamed !== undefined ? { sourceRootRenamed } : {}),\n ...(nextSourceRoots !== undefined ? { nextSourceRoots } : {}),\n basenameChanged,\n };\n}\n","/**\n * Project roster drift (the \"saddle\" model). A project's repos are DECLARED\n * once in the manifest's `repos` list; the capture config (`source_roots`) must\n * cover every declared repo. This computes the drift between the two so\n * `basou project check` can surface a declared repo that is NOT being captured\n * — the class of bug where a companion repo was wired into the workspace but\n * never added to `source_roots`, so its work silently fell out of capture.\n *\n * Pure: it compares declared relative paths against captured relative paths and\n * performs no filesystem or git I/O. Paths are compared as declared (both lists\n * use the same machine-portable relative-path form), not resolved on disk.\n */\n\nimport { normalizeRelativePath as normalize } from \"./relative-path.js\";\n\nexport type RepoVisibility = \"public\" | \"private\" | \"future-public\";\n\n/**\n * The audience-driven language axis. Independent of visibility: a private repo\n * can publish English content, a public repo can carry bilingual docs. `en` /\n * `ja` for a single audience, `en+ja` when both are served.\n */\nexport type RepoLanguage = \"en\" | \"ja\" | \"en+ja\";\n\n/** A published surface a repo emits: a deployed website or a package registry. */\nexport type PublishKind = \"web\" | \"npm\";\n\n/**\n * One published surface. Its visibility and language are INDEPENDENT of the\n * source repo's: a private repo commonly publishes a public website. Both are\n * optional so a surface can be declared\n * before those facts are pinned down (mirroring how `adopt` leaves repo\n * visibility unset for the operator to fill in).\n */\nexport type PublishTarget = {\n kind: PublishKind;\n visibility?: RepoVisibility | undefined;\n language?: RepoLanguage | undefined;\n};\n\nexport type RepoEntry = {\n /** Path relative to the manifest repo root (e.g. \".\", \"../takuhon\"). */\n path: string;\n // `| undefined` so a zod-inferred manifest entry (visibility?: X | undefined,\n // under exactOptionalPropertyTypes) is assignable without remapping.\n visibility?: RepoVisibility | undefined;\n /** Source language (commits/comments/code, read by contributors). Independent of visibility. */\n language?: RepoLanguage | undefined;\n /** Published surfaces this repo emits (opt-in; absent for a repo that publishes nothing). */\n publishes?: PublishTarget[] | undefined;\n};\n\nexport type RosterDriftSummary = {\n declaredCount: number;\n capturedCount: number;\n /** Declared in `repos` but absent from `source_roots`: a capture gap. */\n gaps: RepoEntry[];\n /** In `source_roots` but not declared in `repos` (e.g. a workspace view, or a stray). */\n extra: string[];\n /** Declared paths that are also captured. */\n matched: string[];\n /** True when there is no capture gap (every declared repo is covered). */\n ok: boolean;\n};\n\n/**\n * Compute the {@link RosterDriftSummary} for a project. A declared repo missing\n * from the captured set is a `gap` (the surfaced suspicion); a captured path not\n * in the declared set is `extra` (commonly the workspace view, which is a\n * capture source but not itself a project repo). With no declared roster, there\n * are no gaps (nothing to check against) and every captured path is `extra`.\n */\nexport function summarizeRosterDrift(input: {\n repos?: RepoEntry[];\n sourceRoots?: string[];\n}): RosterDriftSummary {\n const captured = new Set((input.sourceRoots ?? []).map(normalize));\n // Last declaration of a given normalized path wins, but carry it as one entry.\n const declared = new Map<string, RepoEntry>();\n for (const r of input.repos ?? []) declared.set(normalize(r.path), r);\n\n const gaps: RepoEntry[] = [];\n const matched: string[] = [];\n for (const [norm, entry] of declared) {\n if (captured.has(norm)) matched.push(norm);\n else gaps.push(entry);\n }\n const extra = [...captured].filter((c) => !declared.has(c)).sort();\n\n return {\n declaredCount: declared.size,\n capturedCount: captured.size,\n gaps,\n extra,\n matched: matched.sort(),\n ok: gaps.length === 0,\n };\n}\n\nexport type SourceRootsReconcile = {\n /**\n * The reconciled `source_roots`: the existing entries verbatim, then every\n * declared repo path that was missing (normalized, in roster order). Existing\n * order and form are preserved so the manifest diff is minimal and reversible.\n */\n next: string[];\n /** Declared repo paths (normalized) that were appended because `source_roots` did not cover them. */\n added: string[];\n /** True when `source_roots` already covers every declared repo (`next` equals the current list). */\n unchanged: boolean;\n};\n\n/**\n * Derive the `source_roots` a project's declared repo roster requires. The\n * roster (`repos`) is the single source of truth for which repos belong to the\n * project; this is the actuator behind `basou project sync`, computing the\n * additive reconciliation so every declared repo is captured.\n *\n * ADDITIVE ONLY: it appends declared paths that are missing and never removes\n * an existing entry. A captured-but-undeclared path (commonly the generated\n * workspace view — a legitimate capture source that is not itself a project\n * repo) is preserved; pruning strays is deferred to the slice that generates\n * the view (so basou knows which extras it owns). Existing entries are kept\n * byte-identical; only appended paths are normalized.\n *\n * Pure: no filesystem or git I/O. Paths are compared in the same normalized\n * form as {@link summarizeRosterDrift}, so a trailing-slash variant of an\n * already-captured repo is not re-appended.\n */\nexport function reconcileSourceRoots(input: {\n repos?: RepoEntry[];\n sourceRoots?: string[];\n}): SourceRootsReconcile {\n const current = input.sourceRoots ?? [];\n const seen = new Set(current.map(normalize));\n const added: string[] = [];\n for (const r of input.repos ?? []) {\n const norm = normalize(r.path);\n if (seen.has(norm)) continue;\n seen.add(norm);\n added.push(norm);\n }\n return {\n next: [...current, ...added],\n added,\n unchanged: added.length === 0,\n };\n}\n\n/**\n * On-disk classification of a source-root candidate during adoption: a git repo\n * root (→ becomes a roster entry), a resolved-but-non-repo directory (the\n * generated workspace view, `/tmp`, a scratch dir → excluded), or a path that\n * could not be resolved on disk (→ excluded).\n */\nexport type AdoptCandidateKind = \"repo\" | \"non-repo\" | \"unresolved\";\n\nexport type AdoptCandidate = {\n /** Source-root path as declared (relative to the manifest root). */\n path: string;\n /** On-disk classification; the filesystem probing that produces it is the caller's job. */\n kind: AdoptCandidateKind;\n};\n\nexport type RosterAdoptionPlan = {\n /** Proposed `repos` entries: the candidates that are git repos (visibility left unset for the operator). */\n repos: RepoEntry[];\n /** Candidates excluded from the roster, with why (a non-repo directory, or an unresolvable path). */\n excluded: { path: string; kind: Exclude<AdoptCandidateKind, \"repo\"> }[];\n};\n\n/**\n * Plan a `repos` roster from classified source-root candidates (the actuator\n * behind `basou project adopt`). Pure: it partitions already-classified\n * candidates — the realpath / `.git` filesystem probing that produces each\n * `kind` is the caller's job, so this stays testable without disk I/O.\n *\n * A git repo becomes a roster entry (path only; visibility is left unset because\n * it is a human judgment, kept independent of the other axes). A non-repo\n * (commonly the generated workspace view) or an unresolvable path is excluded and\n * reported, so the operator sees what was dropped and why before editing. Repo\n * paths are deduped by normalized form, preserving the first declared form and\n * order.\n */\nexport function planRosterAdoption(candidates: AdoptCandidate[]): RosterAdoptionPlan {\n const repos: RepoEntry[] = [];\n const excluded: { path: string; kind: Exclude<AdoptCandidateKind, \"repo\"> }[] = [];\n const seen = new Set<string>();\n for (const c of candidates) {\n // Dedup by normalized path across ALL kinds (a trailing-slash variant of a\n // path already seen — repo or excluded — is not listed twice).\n const norm = normalize(c.path);\n if (seen.has(norm)) continue;\n seen.add(norm);\n if (c.kind === \"repo\") repos.push({ path: c.path });\n else excluded.push({ path: c.path, kind: c.kind });\n }\n return { repos, excluded };\n}\n","/**\n * Plan the agent instruction-file symlinks a declared repo needs (the\n * generation step that follows `basou project gitignore` in the \"saddle\"\n * model). Each repo's agent instruction files are GITIGNORED symlinks that\n * resolve to a single canonical source kept in the project's private anchor —\n * so the canonical (which may carry private planning content) is edited once\n * and every CLI reads it through a symlink, never committed to a public repo's\n * history.\n *\n * The on-disk topology (verified against the operator's live environment) is a\n * hub-and-spoke:\n *\n * <repo>/AGENTS.md -> <anchor>/agents/<repo>/AGENTS.md (the hub → canonical)\n * <repo>/CLAUDE.md -> AGENTS.md (a spoke → the hub)\n * <repo>/.github/copilot-instructions.md -> ../AGENTS.md (a spoke → the hub)\n *\n * Only AGENTS.md points at the anchor's canonical; CLAUDE.md and Copilot point\n * back at the repo's own AGENTS.md, so there is exactly one link per repo that\n * depends on the anchor path. GEMINI.md is intentionally not generated (the\n * Gemini CLI was discontinued for personal use).\n *\n * Pure: it judges already-gathered, per-file facts (does the link exist? does\n * it point where it should?) and reports only what is MISSING. It never\n * proposes overwriting an existing file or repointing a link that points\n * elsewhere — those surface as conflicts for the operator to resolve by hand\n * (non-destructive, like the additive `.gitignore` planner). The realpath /\n * symlink reading / writing is the caller's job.\n */\n\nimport { normalizeRelativePath as normalize } from \"./relative-path.js\";\n\n/**\n * The on-disk state of one instruction file relative to the symlink it should\n * be. `correct` = the expected link already exists (idempotent skip); `missing`\n * = nothing there (ENOENT), so it can be created; `mismatch` = a symlink\n * pointing somewhere else; `occupied` = a real file or directory; `blocked` =\n * the path could not be inspected (e.g. a parent component is a file → ENOTDIR,\n * or permission denied). Only `missing` is actionable; the rest are left\n * untouched. `blocked` is distinct from `missing` so a non-ENOENT lstat error\n * is never mistaken for a creatable gap (which would crash `--apply`).\n */\nexport type InstructionSymlinkState = \"correct\" | \"missing\" | \"mismatch\" | \"occupied\" | \"blocked\";\n\n/** The gathered facts for one instruction file in one declared repo. */\nexport type InstructionSymlinkFact = {\n /** Repo-relative file name, e.g. \"AGENTS.md\", \".github/copilot-instructions.md\". */\n name: string;\n /** The relative symlink target this file should have (computed by the caller). */\n expectedTarget: string;\n /** On-disk state of the path. */\n state: InstructionSymlinkState;\n /** The link's current target, present only when `state` is `mismatch`. */\n actualTarget?: string;\n};\n\n/** The gathered symlink facts for one declared repo. */\nexport type RepoSymlinkFacts = {\n /** Roster repo path (relative to the manifest root). */\n path: string;\n /**\n * True when this repo IS the project anchor (it owns the canonical sources, so\n * it never links to itself). An anchor entry is skipped entirely.\n */\n isAnchor: boolean;\n /** False when the repo path could not be resolved / is not a usable git repo. */\n reachable: boolean;\n /**\n * Whether the anchor's canonical source for this repo\n * (`<anchor>/agents/<repo>/AGENTS.md`) exists. Without it the hub link would\n * dangle, so no links are planned (reported as a missing canonical instead).\n */\n canonicalPresent: boolean;\n /**\n * The canonical's repo name (the `<repo>` in `agents/<repo>/AGENTS.md`) this\n * repo wires to. Two DISTINCT repos sharing one canonical name would silently\n * collide on a single canonical, so it is used to detect that. Set by the\n * caller for reachable, canonical-present repos; undefined otherwise.\n */\n canonicalName?: string;\n /** Per instruction-file facts (empty when anchor / unreachable / canonical absent). */\n files: InstructionSymlinkFact[];\n};\n\n/** The instruction-file symlinks to CREATE in one repo (only the `missing` ones). */\nexport type RepoSymlinkPlan = {\n path: string;\n toCreate: { name: string; target: string }[];\n};\n\n/**\n * A symlink that already exists but is not what we would generate: a symlink\n * pointing elsewhere (`mismatch`), a real file/directory (`occupied`), or a path\n * that could not be inspected (`blocked`, e.g. a parent is a file). Surfaced,\n * never overwritten.\n */\nexport type SymlinkConflict = {\n repo: string;\n file: string;\n reason: \"mismatch\" | \"occupied\" | \"blocked\";\n /** The conflicting link's current target, present only when `reason` is `mismatch`. */\n actualTarget?: string;\n};\n\n/**\n * Two or more DISTINCT declared repos whose canonical resolves to the same\n * `agents/<canonicalName>/AGENTS.md`. They would silently share one canonical,\n * so neither is auto-wired; the operator must disambiguate.\n */\nexport type SymlinkCollision = {\n canonicalName: string;\n repos: string[];\n};\n\nexport type SymlinkPlanSummary = {\n /** Repos with at least one link to create (those with nothing to create are omitted). */\n plans: RepoSymlinkPlan[];\n /** Existing files/links that block generation and are left untouched for the operator. */\n conflicts: SymlinkConflict[];\n /** Repo paths whose anchor canonical (`agents/<repo>/AGENTS.md`) is absent, so nothing can be wired. */\n missingCanonical: string[];\n /** Repo paths that could not be resolved / are not usable git repos. */\n unreachable: string[];\n /** Groups of distinct repos that resolve to the same canonical (ambiguous; not auto-wired). */\n collisions: SymlinkCollision[];\n /**\n * True only when nothing needs creating AND there are no conflicts, no missing\n * canonicals, no unreachable repos, and no collisions — so a clean \"all wired\"\n * verdict is never claimed while some repo was blocked, ambiguous, or could not\n * be inspected.\n */\n ok: boolean;\n};\n\n/**\n * Compute the {@link SymlinkPlanSummary} from per-repo facts. For each declared,\n * non-anchor, reachable repo whose canonical exists: a `missing` link becomes a\n * create, a `mismatch`/`occupied`/`blocked` link becomes a {@link SymlinkConflict}\n * (never a create — we do not overwrite), and a `correct` link is a no-op. The\n * anchor is skipped (it owns the canonical), an absent canonical is reported as\n * `missingCanonical` (no links planned, since the hub would dangle), and an\n * unresolvable repo as `unreachable`.\n *\n * Robustness:\n * - Facts are deduped by normalized path (first wins), so a repo listed twice in\n * the manifest never yields duplicate plans (which would make `--apply` create\n * the same link twice → EEXIST) or duplicate report entries.\n * - Two DISTINCT repos resolving to the same canonical name are reported as a\n * {@link SymlinkCollision} and neither is auto-wired (silent sharing of one\n * canonical is surfaced, not actioned).\n *\n * `ok` is true only when there is genuinely nothing to do and every repo was\n * judgeable, reachable, and unambiguous.\n */\nexport function summarizeSymlinkPlan(facts: RepoSymlinkFacts[]): SymlinkPlanSummary {\n // Dedup by normalized path (first declaration wins).\n const deduped: RepoSymlinkFacts[] = [];\n const seenPath = new Set<string>();\n for (const f of facts) {\n const key = normalize(f.path);\n if (seenPath.has(key)) continue;\n seenPath.add(key);\n deduped.push(f);\n }\n\n // Detect canonical-name collisions among the repos that would actually wire\n // (reachable, canonical present). Distinct repo paths sharing a canonical name\n // are ambiguous: surface them and wire neither.\n const byCanonical = new Map<string, string[]>();\n for (const f of deduped) {\n if (f.isAnchor || !f.reachable || !f.canonicalPresent || f.canonicalName === undefined) {\n continue;\n }\n const repos = byCanonical.get(f.canonicalName) ?? [];\n repos.push(f.path);\n byCanonical.set(f.canonicalName, repos);\n }\n const collisions: SymlinkCollision[] = [];\n const collidingPaths = new Set<string>();\n for (const [canonicalName, repos] of byCanonical) {\n if (repos.length > 1) {\n collisions.push({ canonicalName, repos });\n for (const r of repos) collidingPaths.add(r);\n }\n }\n\n const plans: RepoSymlinkPlan[] = [];\n const conflicts: SymlinkConflict[] = [];\n const missingCanonical: string[] = [];\n const unreachable: string[] = [];\n\n for (const f of deduped) {\n if (f.isAnchor) continue;\n if (!f.reachable) {\n unreachable.push(f.path);\n continue;\n }\n if (!f.canonicalPresent) {\n missingCanonical.push(f.path);\n continue;\n }\n // A repo sharing a canonical name with another is surfaced as a collision and\n // never auto-wired (avoids silently pointing two repos at one canonical).\n if (collidingPaths.has(f.path)) continue;\n\n const toCreate: { name: string; target: string }[] = [];\n for (const file of f.files) {\n if (file.state === \"missing\") {\n toCreate.push({ name: file.name, target: file.expectedTarget });\n } else if (file.state === \"mismatch\") {\n conflicts.push({\n repo: f.path,\n file: file.name,\n reason: \"mismatch\",\n ...(file.actualTarget !== undefined ? { actualTarget: file.actualTarget } : {}),\n });\n } else if (file.state === \"occupied\") {\n conflicts.push({ repo: f.path, file: file.name, reason: \"occupied\" });\n } else if (file.state === \"blocked\") {\n conflicts.push({ repo: f.path, file: file.name, reason: \"blocked\" });\n }\n // \"correct\" → already wired, nothing to do.\n }\n if (toCreate.length > 0) plans.push({ path: f.path, toCreate });\n }\n\n return {\n plans,\n conflicts,\n missingCanonical,\n unreachable,\n collisions,\n ok:\n plans.length === 0 &&\n conflicts.length === 0 &&\n missingCanonical.length === 0 &&\n unreachable.length === 0 &&\n collisions.length === 0,\n };\n}\n","/**\n * Agent instruction-file wiring (the read-only first step of the \"saddle\"\n * model's view/instruction/gitignore generation). For each declared repo, the\n * agent instruction files (AGENTS.md, CLAUDE.md, .github/copilot-instructions.md)\n * should be present as GITIGNORED symlinks to a canonical source, never tracked\n * in a public repo's git history (where they would expose the private canonical\n * content they point at). This summarizes the on-disk + git facts the CLI\n * gathers and surfaces the privacy-relevant drift; it generates nothing.\n *\n * Pure: it judges already-gathered facts (presence + git-tracked status). The\n * filesystem / git probing that produces those facts is the caller's job.\n */\n\nimport type { RepoVisibility } from \"./roster.js\";\n\n/** On-disk + git state of one instruction file in one repo. */\nexport type InstructionFileFact = {\n /** Repo-relative file name, e.g. \"AGENTS.md\", \".github/copilot-instructions.md\". */\n name: string;\n /** Exists on disk (a regular file or a symlink, including a broken one). */\n present: boolean;\n /** Tracked by the repo's git (committed / staged). */\n tracked: boolean;\n};\n\n/** The gathered wiring facts for one declared repo. */\nexport type RepoWiringFacts = {\n /** Roster repo path (relative to the manifest root). */\n path: string;\n /** Declared visibility; undefined when the operator has not set it yet. */\n visibility?: RepoVisibility | undefined;\n /** False when the repo path could not be resolved / is not a usable git repo. */\n reachable: boolean;\n /** Per instruction-file facts (omitted/empty when unreachable). */\n instructionFiles: InstructionFileFact[];\n};\n\n/**\n * A privacy risk: a public-facing repo tracks an instruction file in git, which\n * can expose the private canonical content the file points at.\n */\nexport type WiringRisk = {\n repo: string;\n visibility: Extract<RepoVisibility, \"public\" | \"future-public\">;\n file: string;\n};\n\nexport type WiringSummary = {\n /** Echo of the per-repo facts (for `--json` and the detailed view). */\n repos: RepoWiringFacts[];\n /** Public / future-public repos with a tracked instruction file. */\n risks: WiringRisk[];\n /** Repo paths whose visibility is unset, so the privacy verdict cannot be judged. */\n unknown: string[];\n /** Repos missing one or more instruction files (a wiring gap a later generate slice fills). */\n incomplete: { repo: string; missing: string[] }[];\n /** Repo paths that could not be resolved / are not usable git repos. */\n unreachable: string[];\n /** True when there are no risks, no unknown visibility, and no unreachable repos. */\n ok: boolean;\n};\n\n/** Whether a visibility exposes git history to the public (so tracked instruction files leak). */\nfunction isPublicFacing(v: RepoVisibility | undefined): v is \"public\" | \"future-public\" {\n return v === \"public\" || v === \"future-public\";\n}\n\n/**\n * Summarize {@link RepoWiringFacts} into the privacy-relevant verdict. A\n * public-facing repo that TRACKS an instruction file is a {@link WiringRisk}\n * (its git history can expose the private canonical it points at); a repo with\n * unset visibility cannot be judged (`unknown`); a repo missing instruction\n * files is `incomplete` (a wiring gap, not a privacy problem). `ok` is true only\n * when nothing is at risk, every repo is judgeable, and every repo is reachable.\n */\nexport function summarizeWiring(facts: RepoWiringFacts[]): WiringSummary {\n const risks: WiringRisk[] = [];\n const unknown: string[] = [];\n const incomplete: { repo: string; missing: string[] }[] = [];\n const unreachable: string[] = [];\n\n for (const f of facts) {\n if (!f.reachable) {\n unreachable.push(f.path);\n continue;\n }\n if (isPublicFacing(f.visibility)) {\n for (const file of f.instructionFiles) {\n if (file.tracked) risks.push({ repo: f.path, visibility: f.visibility, file: file.name });\n }\n } else if (f.visibility === undefined) {\n unknown.push(f.path);\n }\n const missing = f.instructionFiles.filter((file) => !file.present).map((file) => file.name);\n if (missing.length > 0) incomplete.push({ repo: f.path, missing });\n }\n\n return {\n repos: facts,\n risks,\n unknown,\n incomplete,\n unreachable,\n ok: risks.length === 0 && unknown.length === 0 && unreachable.length === 0,\n };\n}\n","/**\n * Plan the symlinks a project's throwaway \"view\" needs (the generation step\n * after the instruction-file symlinks in the \"saddle\" model). When a project\n * has 2+ repos, basou generates a view directory that aggregates every roster\n * repo via one symlink each, named by the repo's basename and pointing at the\n * repo (relative to the view):\n *\n * <view>/app -> ../app\n * <view>/anchor -> ../anchor (the anchor's \".\" entry is aggregated here too,\n * <view>/app-site -> ../app-site unlike the instruction symlinks)\n *\n * The view is git-unmanaged and regenerable; its location is declared once\n * (`workspace.view` in the manifest) and its contents are derived from the\n * roster. This is the pure planner: it judges already-gathered, per-repo facts\n * (does the view link exist? does it point at the repo?) and reports only what\n * is MISSING. It never overwrites an existing file or repoints a link that\n * points elsewhere — those surface as conflicts for the operator to resolve by\n * hand (non-destructive). The realpath / readlink / symlink I/O is the caller's\n * job.\n *\n * Pruning stray entries already in the view IS now in scope (see `toPrune` /\n * `strayUnknown` and {@link ExistingViewLink}): the ownership model that tells an\n * orphaned repo link from the view's own instruction files / local state lives\n * here. Still NOT in scope: generating the view's own instruction files.\n */\n\nimport { normalizeRelativePath as normalize } from \"./relative-path.js\";\n\n/**\n * The on-disk state of one repo's view symlink. `correct` = the expected link\n * exists (idempotent skip); `missing` = nothing there (ENOENT) so it can be\n * created; `mismatch` = a symlink pointing elsewhere; `occupied` = a real file\n * or directory; `blocked` = the path could not be inspected (a non-ENOENT lstat\n * error, e.g. a parent component is a file). Only `missing` is actionable.\n */\nexport type ViewLinkState = \"correct\" | \"missing\" | \"mismatch\" | \"occupied\" | \"blocked\";\n\n/** The gathered facts for one roster repo's place in the view. */\nexport type ViewRepoFact = {\n /** Roster repo path (relative to the manifest root), e.g. \".\", \"../basou\". */\n path: string;\n /**\n * False when the repo cannot be aggregated into the view: its path did not\n * resolve on disk, or it resolves to the view directory itself (a self-link).\n */\n reachable: boolean;\n /** The view symlink name (the repo's basename), e.g. \"basou\". Set when reachable. */\n linkName?: string;\n /** The relative target the view link should have, e.g. \"../basou\". Set when reachable. */\n expectedTarget?: string;\n /** On-disk state of `<view>/<linkName>`. Set when reachable. */\n state?: ViewLinkState;\n /** The link's current target, present only when `state` is `mismatch`. */\n actualTarget?: string;\n};\n\n/**\n * An existing view entry that is not what we would generate: a symlink pointing\n * elsewhere (`mismatch`), a real file/directory (`occupied`), or an\n * uninspectable path (`blocked`). Surfaced, never overwritten.\n */\nexport type ViewConflict = {\n name: string;\n reason: \"mismatch\" | \"occupied\" | \"blocked\";\n /** The conflicting link's current target, present only when `reason` is `mismatch`. */\n actualTarget?: string;\n};\n\n/**\n * Two or more DISTINCT roster repos whose basename is the same, so they would\n * collide on a single `<view>/<basename>` link. Neither is auto-wired; the\n * operator must disambiguate.\n */\nexport type ViewCollision = {\n linkName: string;\n repos: string[];\n};\n\n/**\n * An entry actually present in the view directory, pre-classified by the caller's\n * filesystem probe, for stray detection (the inverse of generation). Only\n * symlinks are candidates — a real file or directory is never the caller's to\n * remove and is not gathered here. `kind` tells the planner whether a symlink not\n * tied to any roster repo is a basou-generated repo link safe to prune:\n *\n * - `repo`: a relative target that follows to an existing git repository — a\n * directory containing a `.git` entry, whether a directory OR a gitdir-pointer\n * FILE (git worktrees and submodules), matching the project family's repo test\n * (`existsSync(<dir>/.git)`, as `adopt`/`wiring` use). Exactly what\n * {@link planWorkspaceView}'s `toCreate` produces. A stray of this kind is prunable.\n * - `broken`: a relative target that does not resolve on disk (e.g. the repo was\n * moved/deleted). Reported, never auto-pruned (we cannot confirm it was ours).\n * - `non-repo`: a relative target that resolves to a non-repository path (a file,\n * or a directory without `.git`). Reported, never auto-pruned.\n * - `absolute`: an absolute target. basou never writes absolute view links, so it\n * is not ours. Reported, never auto-pruned.\n *\n * The caller filters out the view's OWN instruction-file symlinks (e.g. a top-level\n * `AGENTS.md`/`CLAUDE.md`) before classifying, so they never surface as strays.\n */\nexport type ExistingViewLink = {\n name: string;\n target: string;\n kind: \"repo\" | \"broken\" | \"non-repo\" | \"absolute\";\n};\n\n/**\n * A view symlink not tied to any current roster repo that was NOT auto-pruned\n * because we could not confirm it is a basou-generated repo link. Surfaced for\n * the operator to resolve by hand; never removed.\n */\nexport type ViewStrayUnknown = {\n name: string;\n target: string;\n reason: \"broken\" | \"non-repo\" | \"absolute\";\n};\n\nexport type WorkspaceViewPlan = {\n /** The view symlinks to create (the `missing` ones), as name + relative target. */\n toCreate: { name: string; target: string }[];\n /** Existing entries that block generation and are left untouched. */\n conflicts: ViewConflict[];\n /** Distinct repos colliding on one view link name (not auto-wired). */\n collisions: ViewCollision[];\n /** Roster repo paths that cannot be aggregated: unresolved on disk, or resolving to the view itself. */\n unreachable: string[];\n /**\n * Stray basou-generated repo links to remove: a view symlink whose name is not\n * a current roster repo and whose relative target follows to a git repository.\n * Removed only under `--prune` (its own opt-in, separate from `--apply`).\n */\n toPrune: { name: string; target: string }[];\n /**\n * Stray view symlinks not tied to any roster repo that we did NOT recognize as\n * a basou-generated repo link (broken, non-repo, or absolute target). Reported,\n * never auto-pruned.\n */\n strayUnknown: ViewStrayUnknown[];\n /** Count of repos whose view link is already correct (for the report). */\n correctCount: number;\n /**\n * True only when nothing needs creating, there are no conflicts, no collisions,\n * no unreachable repos, AND no strays (prunable or unknown) — so a clean \"view\n * in sync\" verdict is never claimed while a repo was blocked, ambiguous, could\n * not be resolved, or the view still carries an entry the roster no longer backs.\n */\n ok: boolean;\n};\n\n/**\n * Compute the {@link WorkspaceViewPlan} from per-repo facts. For each declared,\n * reachable, non-colliding repo: a `missing` view link becomes a create, a\n * `mismatch`/`occupied`/`blocked` link becomes a {@link ViewConflict} (never a\n * create — we do not overwrite), and a `correct` link is counted. The view\n * aggregates exactly the DECLARED roster — the anchor is aggregated when present\n * as its `.` entry (which `adopt` always adds) and, unlike the instruction\n * symlinks, is NOT skipped; it is not implicitly injected when absent (the roster\n * is the single source of truth). An unresolvable repo is `unreachable`, and two\n * distinct repos sharing a basename are a {@link ViewCollision} (neither\n * auto-wired).\n *\n * Stray detection (the inverse of generation): given the entries actually present\n * in the view (`existing`), any symlink whose name is NOT owned by a declared\n * roster repo is a stray. A stray classified `repo` (a relative target following to\n * a git repository — basou's own generation shape) goes to `toPrune` (removed only\n * under the separate `--prune` opt-in); a stray we cannot confirm is ours (broken,\n * non-repo, or absolute target) goes to `strayUnknown` (reported, never removed).\n *\n * Ownership (what is NEVER a stray): a name is owned if it is the link name of a\n * reachable roster repo OR appears in `rosterNames` — the basenames of EVERY\n * declared roster entry, supplied independent of reachability. The reachability-\n * independent set is load-bearing: a roster repo whose path transiently fails to\n * resolve (an unmounted volume, a mid-edit symlinked parent, an uncloned sibling)\n * still owns its view link name, so its live link is never mislabeled a stray and\n * pruned. (A roster repo's link reached under a DIFFERENT name — e.g. an aliased\n * roster path — is excluded by the caller before it reaches `existing`, by matching\n * the link's resolved target against the roster's resolved repos.) `existing` and\n * `rosterNames` default to empty, so a caller that does not scan the view gets the\n * original create-only plan.\n *\n * Robustness (mirroring the instruction-symlink planner):\n * - Facts are deduped by normalized path (a repo declared twice yields one link,\n * never a duplicate `symlinkSync` → EEXIST or a duplicate report entry).\n * - `ok` is true only when there is genuinely nothing to do, every repo was\n * resolvable and unambiguous, and the view carries no stray.\n */\nexport function planWorkspaceView(\n facts: ViewRepoFact[],\n existing: ExistingViewLink[] = [],\n rosterNames: string[] = [],\n): WorkspaceViewPlan {\n // Dedup by normalized path (first declaration wins).\n const deduped: ViewRepoFact[] = [];\n const seenPath = new Set<string>();\n for (const f of facts) {\n const key = normalize(f.path);\n if (seenPath.has(key)) continue;\n seenPath.add(key);\n deduped.push(f);\n }\n\n // Detect basename collisions among the reachable repos (distinct paths sharing\n // one link name would clobber a single `<view>/<basename>`).\n const byLinkName = new Map<string, string[]>();\n for (const f of deduped) {\n if (!f.reachable || f.linkName === undefined) continue;\n const repos = byLinkName.get(f.linkName) ?? [];\n repos.push(f.path);\n byLinkName.set(f.linkName, repos);\n }\n const collisions: ViewCollision[] = [];\n const collidingPaths = new Set<string>();\n for (const [linkName, repos] of byLinkName) {\n if (repos.length > 1) {\n collisions.push({ linkName, repos });\n for (const r of repos) collidingPaths.add(r);\n }\n }\n\n const toCreate: { name: string; target: string }[] = [];\n const conflicts: ViewConflict[] = [];\n const unreachable: string[] = [];\n let correctCount = 0;\n\n for (const f of deduped) {\n if (!f.reachable) {\n unreachable.push(f.path);\n continue;\n }\n if (collidingPaths.has(f.path)) continue; // surfaced as a collision; not auto-wired\n if (f.linkName === undefined || f.expectedTarget === undefined || f.state === undefined) {\n continue;\n }\n\n if (f.state === \"missing\") {\n toCreate.push({ name: f.linkName, target: f.expectedTarget });\n } else if (f.state === \"mismatch\") {\n conflicts.push({\n name: f.linkName,\n reason: \"mismatch\",\n ...(f.actualTarget !== undefined ? { actualTarget: f.actualTarget } : {}),\n });\n } else if (f.state === \"occupied\") {\n conflicts.push({ name: f.linkName, reason: \"occupied\" });\n } else if (f.state === \"blocked\") {\n conflicts.push({ name: f.linkName, reason: \"blocked\" });\n } else {\n correctCount += 1; // \"correct\"\n }\n }\n\n // Stray detection: every declared roster repo \"owns\" its link name and must\n // never have its link pruned — even a colliding one (the repo still wants it)\n // and even an unreachable one (a transient resolution failure must not expose a\n // live link to deletion). Ownership is the union of reachable repos' resolved\n // link names and EVERY declared entry's basename (`rosterNames`, reachability-\n // independent). Any existing view symlink whose name is outside that set is a\n // stray candidate.\n const ownedNames = new Set<string>(rosterNames);\n for (const f of deduped) {\n if (f.reachable && f.linkName !== undefined) ownedNames.add(f.linkName);\n }\n\n const toPrune: { name: string; target: string }[] = [];\n const strayUnknown: ViewStrayUnknown[] = [];\n const seenExisting = new Set<string>();\n for (const e of existing) {\n if (ownedNames.has(e.name)) continue; // a declared repo's own link, not a stray\n if (seenExisting.has(e.name)) continue; // a name can appear once on disk; guard anyway\n seenExisting.add(e.name);\n if (e.kind === \"repo\") {\n toPrune.push({ name: e.name, target: e.target });\n } else {\n strayUnknown.push({ name: e.name, target: e.target, reason: e.kind });\n }\n }\n\n return {\n toCreate,\n conflicts,\n collisions,\n unreachable,\n toPrune,\n strayUnknown,\n correctCount,\n ok:\n toCreate.length === 0 &&\n conflicts.length === 0 &&\n collisions.length === 0 &&\n unreachable.length === 0 &&\n toPrune.length === 0 &&\n strayUnknown.length === 0,\n };\n}\n","import { join } from \"node:path\";\nimport {\n enumerateApprovals,\n type LoadedApproval,\n loadApproval,\n} from \"../approval/approval-store.js\";\nimport { type ReplayWarning, replayEvents } from \"../events/event-replay.js\";\nimport { type ChainVerdictStatus, verifyEventsChain } from \"../events/verify.js\";\nimport { formatDurationMs } from \"../lib/format-duration.js\";\nimport type {\n ApprovalStatus,\n RiskLevel,\n SessionSourceKind,\n SessionStatus,\n TaskStatus,\n} from \"../schemas/index.js\";\nimport { computeWorkStats, type StatusCount } from \"../stats/work-stats.js\";\nimport type { BasouPaths } from \"../storage/basou-dir.js\";\nimport {\n loadSessionEntries,\n type SessionEntry,\n type SessionSkipReason,\n} from \"../storage/sessions.js\";\nimport { loadTaskEntries, type TaskSkipReason } from \"../storage/tasks.js\";\n\n/**\n * Caps on how many items each section lists in the rendered markdown. The\n * structured `ReportData` always keeps the FULL set (machine consumers get\n * everything); only the human-facing markdown truncates (with a `... +N more`\n * line) so a report over a large workspace stays readable and \"簡易\".\n */\nconst CHANGED_FILES_MARKDOWN_LIMIT = 50;\nconst DECISIONS_MARKDOWN_LIMIT = 20;\nconst SESSIONS_MARKDOWN_LIMIT = 30;\nconst TASKS_MARKDOWN_LIMIT = 30;\nconst APPROVALS_MARKDOWN_LIMIT = 30;\n\n/** Render order for the session-status breakdown (most-relevant-first). */\nconst SESSION_STATUS_ORDER: readonly SessionStatus[] = [\n \"completed\",\n \"failed\",\n \"running\",\n \"waiting_approval\",\n \"interrupted\",\n \"initialized\",\n \"imported\",\n \"archived\",\n];\n\n/** Render order for the task-status breakdown. */\nconst TASK_STATUS_ORDER: readonly TaskStatus[] = [\"planned\", \"in_progress\", \"done\", \"cancelled\"];\n\nexport type ReportRendererInput = {\n paths: BasouPaths;\n /** ISO timestamp stamped into the report header and used as the clock. */\n nowIso: string;\n /** Optional subject line surfaced in the report title. */\n title?: string;\n /**\n * IANA timezone passed through to {@link computeWorkStats} (it labels the\n * time figures with the zone). The CLI omits this (host default); tests and\n * the SDK pass a fixed value for deterministic output. [Codex #5]\n */\n timeZone?: string;\n onWarning?: (warning: ReplayWarning, sessionId: string) => void;\n onSessionSkip?: (sessionId: string, reason: SessionSkipReason) => void;\n onTaskSkip?: (taskId: string, reason: TaskSkipReason) => void;\n};\n\nexport type ReportSessionItem = {\n id: string;\n label: string | null;\n status: SessionStatus;\n source: SessionSourceKind;\n startedAt: string;\n activeMs: number;\n outputTokens: number;\n};\n\nexport type ReportDecisionItem = {\n id: string;\n title: string;\n occurredAt: string;\n /** True when a later `decision_voided` event retracted this decision. */\n voided?: boolean;\n /** True when the decision was recorded as a strategic track (`kind: \"track\"`). */\n track?: boolean;\n};\nexport type ReportTaskItem = { id: string; title: string; status: TaskStatus };\nexport type ReportApprovalItem = {\n id: string;\n reason: string;\n status: ApprovalStatus;\n riskLevel: RiskLevel;\n};\nexport type TaskStatusCount = { status: TaskStatus; count: number };\n\n/**\n * Curated, purpose-built structured shape behind `basou report generate\n * --json`. Deliberately NOT the full {@link WorkStatsResult} — report's JSON\n * stays a stable contract decoupled from the stats schema. Field names avoid\n * the word \"billable\": a report is a neutral work-explanation export, not a\n * billing artifact. [Codex #2]\n */\nexport type ReportData = {\n generatedAt: string;\n title?: string;\n /** Earliest session start .. latest session end (or `now` for open sessions). */\n period: { from: string | null; to: string | null };\n sessions: { total: number; byStatus: StatusCount[]; items: ReportSessionItem[] };\n volume: {\n outputTokens: number;\n reasoningTokens: number;\n commandCount: number;\n fileChangedCount: number;\n decisionCount: number;\n tokensAvailable: boolean;\n };\n time: {\n activeMs: number;\n machineActiveMs: number;\n machineAvailable: boolean;\n spanMs: number;\n commandTimeMs: number;\n timeZone: string;\n };\n decisions: { count: number; items: ReportDecisionItem[] };\n approvals: {\n pending: number;\n approved: number;\n rejected: number;\n expired: number;\n items: ReportApprovalItem[];\n };\n tasks: { total: number; byStatus: TaskStatusCount[]; items: ReportTaskItem[] };\n /** Union of related files across non-`import` sessions (full; markdown truncates). */\n changedFiles: string[];\n integrity: {\n total: number;\n verified: number;\n unchained: number;\n empty: number;\n incomplete: number;\n in_progress: number;\n tampered: number;\n /** Session ids whose chain is `tampered`, surfaced for follow-up. */\n tamperedSessions: string[];\n };\n};\n\nexport type ReportRendererResult = { body: string; data: ReportData };\n\n/**\n * Render a neutral \"work report\" — a point-in-time export that explains the\n * work captured in a workspace: how much, what was decided / approved /\n * undertaken, which files changed, and whether the local provenance is\n * internally consistent. It composes existing read primitives only and writes\n * nothing; the caller chooses where `body` goes (stdout / a file) and whether\n * to emit the structured `data` as JSON.\n *\n * Warning surfaces mirror the sibling renderers: `loadSessionEntries` (suspect\n * classification) and the decision-aggregation replay (with the same\n * unreadable-skip wrapper as `decisions-renderer.ts`) report through the\n * callbacks. {@link computeWorkStats} runs SILENTLY here — it re-reads the same\n * sessions/events, so surfacing its warnings too would double-emit. [Codex #6]\n */\nexport async function renderReport(input: ReportRendererInput): Promise<ReportRendererResult> {\n const now = new Date(input.nowIso);\n\n // Track which sessions already surfaced `events_jsonl_unreadable` so a\n // non-running session whose log is unreadable still warns once (not twice,\n // not zero times) across the suspect pass and the decision replay.\n const unreadableEmitted = new Set<string>();\n const wrappedSkip: (sid: string, reason: SessionSkipReason) => void = (sid, reason) => {\n if (reason === \"events_jsonl_unreadable\") unreadableEmitted.add(sid);\n input.onSessionSkip?.(sid, reason);\n };\n\n const loadOpts: Parameters<typeof loadSessionEntries>[1] = { now, onSkip: wrappedSkip };\n if (input.onWarning !== undefined) loadOpts.onWarning = input.onWarning;\n const entries = await loadSessionEntries(input.paths, loadOpts);\n\n // Volume / time / per-session active+tokens. Silent: the warning surface is\n // the two passes above (this re-reads the same data).\n const statsInput: Parameters<typeof computeWorkStats>[0] = { paths: input.paths, now };\n if (input.timeZone !== undefined) statsInput.timeZone = input.timeZone;\n const stats = await computeWorkStats(statsInput);\n const statsBySession = new Map(stats.sessions.map((s) => [s.sessionId, s]));\n\n // Decisions: replicate decisions-renderer's full collection — the\n // unreadable-skip wrapper AND the (occurred_at, id) sort, not just the loop.\n const decisions: ReportDecisionItem[] = [];\n // decision_ids retracted by a later `decision_voided` event; marked on the\n // items below so the report annotates them instead of presenting a retracted\n // decision as if it still stands (mirrors decisions.md / orient / handoff).\n const voidedDecisionIds = new Set<string>();\n for (const entry of entries) {\n const sessionDir = join(input.paths.sessions, entry.sessionId);\n try {\n for await (const ev of replayEvents(sessionDir, {\n onWarning: (w) => input.onWarning?.(w, entry.sessionId),\n })) {\n if (ev.type === \"decision_recorded\") {\n decisions.push({\n id: ev.decision_id,\n title: ev.title,\n occurredAt: ev.occurred_at,\n ...(ev.kind === \"track\" ? { track: true } : {}),\n });\n } else if (ev.type === \"decision_voided\") {\n voidedDecisionIds.add(ev.decision_id);\n }\n }\n } catch {\n if (!unreadableEmitted.has(entry.sessionId)) {\n wrappedSkip(entry.sessionId, \"events_jsonl_unreadable\");\n }\n }\n }\n for (const d of decisions) {\n if (voidedDecisionIds.has(d.id)) d.voided = true;\n }\n decisions.sort((a, b) => {\n const c = Date.parse(a.occurredAt) - Date.parse(b.occurredAt);\n return c !== 0 ? c : a.id.localeCompare(b.id);\n });\n\n // Tasks.\n const taskLoadOpts: Parameters<typeof loadTaskEntries>[1] = {};\n if (input.onTaskSkip !== undefined) taskLoadOpts.onSkip = input.onTaskSkip;\n const taskEntries = await loadTaskEntries(input.paths, taskLoadOpts);\n const taskItems: ReportTaskItem[] = taskEntries.map((t) => ({\n id: t.task.task.id,\n title: t.task.task.title,\n status: t.task.task.status,\n }));\n const tasksByStatus = tallyTaskStatus(taskItems);\n\n // Approvals: dedupe a stale pending id that is also resolved (resolved wins),\n // then tally by status (mirrors the SDK's listApprovals dedupe).\n const approvalIds = await enumerateApprovals(input.paths);\n const resolvedSet = new Set(approvalIds.resolved);\n const pendingIds = approvalIds.pending.filter((id) => !resolvedSet.has(id));\n const loadedApprovals = (\n await Promise.all(\n [...pendingIds, ...approvalIds.resolved].map((id) => loadApproval(input.paths, id)),\n )\n ).filter((a): a is LoadedApproval => a !== null);\n const approvalItems: ReportApprovalItem[] = loadedApprovals.map((a) => ({\n id: a.approval.id,\n reason: a.approval.reason,\n status: a.approval.status,\n riskLevel: a.approval.risk_level,\n }));\n const approvalCounts = { pending: 0, approved: 0, rejected: 0, expired: 0 };\n for (const a of approvalItems) approvalCounts[a.status] += 1;\n\n // Changed files: union over NON-import sessions only, so cross-workspace\n // round-trip imports don't dominate (matches handoff's precedent). [Codex #4]\n const changedSet = new Set<string>();\n for (const entry of entries) {\n if (entry.session.session.source.kind === \"import\") continue;\n for (const f of entry.session.session.related_files) changedSet.add(f);\n }\n const changedFiles = [...changedSet].sort();\n\n // Integrity: verify each session's chain and tally by verdict. A session\n // whose events.jsonl is unreadable (a non-ENOENT I/O error) makes\n // verifyEventsChain throw; surface it as a skip and leave it out of the tally\n // so a single bad file never fails the whole report (a successful render must\n // exit 0). `total` therefore counts only the sessions that could be verified.\n const integrity = {\n total: 0,\n verified: 0,\n unchained: 0,\n empty: 0,\n incomplete: 0,\n in_progress: 0,\n tampered: 0,\n tamperedSessions: [] as string[],\n };\n for (const entry of entries) {\n const verdict = await verifyEventsChain(input.paths, entry.sessionId).catch(() => null);\n if (verdict === null) {\n if (!unreadableEmitted.has(entry.sessionId)) {\n wrappedSkip(entry.sessionId, \"events_jsonl_unreadable\");\n }\n continue;\n }\n integrity.total += 1;\n integrity[verdict.status] += 1;\n if (verdict.status === \"tampered\") integrity.tamperedSessions.push(entry.sessionId);\n }\n\n // Session table rows + period, from the per-session stats joined onto the\n // canonical session list (newest-first).\n const sessionItems: ReportSessionItem[] = [...entries]\n .sort(\n (a, b) => Date.parse(b.session.session.started_at) - Date.parse(a.session.session.started_at),\n )\n .map((e) => {\n const w = statsBySession.get(e.sessionId);\n return {\n id: e.sessionId,\n label: e.session.session.label ?? null,\n status: e.session.session.status,\n source: e.session.session.source.kind,\n startedAt: e.session.session.started_at,\n activeMs: w?.activeTimeMs ?? 0,\n outputTokens: w?.tokens.output ?? 0,\n };\n });\n const period = computePeriod(entries, input.nowIso);\n\n const t = stats.totals;\n const data: ReportData = {\n generatedAt: input.nowIso,\n ...(input.title !== undefined ? { title: input.title } : {}),\n period,\n sessions: { total: entries.length, byStatus: stats.byStatus, items: sessionItems },\n volume: {\n outputTokens: t.tokens.output,\n reasoningTokens: t.tokens.reasoning,\n commandCount: t.commandCount,\n fileChangedCount: t.fileChangedCount,\n decisionCount: t.decisionCount,\n tokensAvailable: t.tokensAvailable,\n },\n time: {\n activeMs: t.billableActiveTimeMs,\n machineActiveMs: t.machineActiveTimeMs,\n machineAvailable: t.machineActiveAvailable,\n spanMs: t.sessionSpanMs,\n commandTimeMs: t.commandTimeMs,\n timeZone: stats.timeZone,\n },\n decisions: { count: decisions.length, items: decisions },\n approvals: { ...approvalCounts, items: approvalItems },\n tasks: { total: taskEntries.length, byStatus: tasksByStatus, items: taskItems },\n changedFiles,\n integrity,\n };\n\n return { body: formatReportBody(data), data };\n}\n\nfunction computePeriod(\n entries: ReadonlyArray<SessionEntry>,\n nowIso: string,\n): { from: string | null; to: string | null } {\n if (entries.length === 0) return { from: null, to: null };\n let from = entries[0]?.session.session.started_at ?? nowIso;\n let to = nowIso;\n let sawEnd = false;\n for (const e of entries) {\n const s = e.session.session.started_at;\n if (Date.parse(s) < Date.parse(from)) from = s;\n const end = e.session.session.ended_at ?? nowIso;\n if (!sawEnd || Date.parse(end) > Date.parse(to)) {\n to = end;\n sawEnd = true;\n }\n }\n // Guard a clock-skewed session (ended_at < started_at) from producing a\n // reversed window where `to` precedes `from`.\n if (Date.parse(to) < Date.parse(from)) to = from;\n return { from, to };\n}\n\nfunction tallyTaskStatus(items: ReadonlyArray<ReportTaskItem>): TaskStatusCount[] {\n const counts = new Map<TaskStatus, number>();\n for (const i of items) counts.set(i.status, (counts.get(i.status) ?? 0) + 1);\n return TASK_STATUS_ORDER.filter((s) => (counts.get(s) ?? 0) > 0).map((status) => ({\n status,\n count: counts.get(status) as number,\n }));\n}\n\nfunction formatReportBody(data: ReportData): string {\n const lines: string[] = [];\n const titleSuffix = data.title !== undefined ? ` — ${data.title}` : \"\";\n lines.push(`# Report${titleSuffix}`);\n lines.push(\"\");\n const periodSuffix =\n data.period.from !== null && data.period.to !== null\n ? ` (${data.period.from.slice(0, 10)}..${data.period.to.slice(0, 10)})`\n : \"\";\n lines.push(`> Generated at ${data.generatedAt}${periodSuffix}`);\n lines.push(\"\");\n\n // Summary\n lines.push(\"## 概要\");\n lines.push(\"\");\n lines.push(`- ${formatSessionsLine(data)}`);\n lines.push(\n `- Active time ${formatDurationMs(data.time.activeMs)}, ${formatInt(data.volume.outputTokens)} output tokens`,\n );\n lines.push(\"\");\n\n // Volume + time\n lines.push(\"## 作業量\");\n lines.push(\"\");\n const tokenCaveat = data.volume.tokensAvailable ? \"\" : \" (no token data captured)\";\n lines.push(`- Output tokens: ${formatInt(data.volume.outputTokens)}${tokenCaveat}`);\n if (data.volume.reasoningTokens > 0) {\n lines.push(`- Reasoning tokens: ${formatInt(data.volume.reasoningTokens)} (Codex)`);\n }\n lines.push(\n `- Actions: ${data.volume.commandCount} commands, ${data.volume.fileChangedCount} files, ${data.volume.decisionCount} decisions`,\n );\n lines.push(\n `- Active time: ${formatDurationMs(data.time.activeMs)} (union; idle gaps > 5m excluded; tz ${data.time.timeZone})`,\n );\n if (data.time.machineAvailable) {\n lines.push(\n `- Model working: ${formatDurationMs(data.time.machineActiveMs)} (model compute, subset of active)`,\n );\n }\n lines.push(`- Span: ${formatDurationMs(data.time.spanMs)} (total elapsed)`);\n lines.push(\"\");\n\n // Decisions — the most recent ones (the report explains what was decided;\n // ids live in --json, omitted here to keep the human narrative clean and\n // because batch-imported ids share a ULID timestamp prefix).\n lines.push(\"## 判断\");\n lines.push(\"\");\n if (data.decisions.items.length === 0) {\n lines.push(\"(no decisions recorded yet)\");\n } else {\n const total = data.decisions.items.length;\n const shown =\n total > DECISIONS_MARKDOWN_LIMIT\n ? data.decisions.items.slice(-DECISIONS_MARKDOWN_LIMIT)\n : data.decisions.items;\n if (total > DECISIONS_MARKDOWN_LIMIT) {\n lines.push(`(showing the ${DECISIONS_MARKDOWN_LIMIT} most recent of ${total})`);\n lines.push(\"\");\n }\n for (const d of shown) {\n const trackTag = d.track === true ? \" [track]\" : \"\";\n const voidedTag = d.voided === true ? \" (voided)\" : \"\";\n lines.push(`- ${d.occurredAt.slice(0, 10)} · ${d.title}${trackTag}${voidedTag}`);\n }\n }\n lines.push(\"\");\n\n // Approvals\n lines.push(\"## 承認\");\n lines.push(\"\");\n if (data.approvals.items.length === 0) {\n lines.push(\"(none)\");\n } else {\n const a = data.approvals;\n lines.push(\n `Pending ${a.pending} · Approved ${a.approved} · Rejected ${a.rejected} · Expired ${a.expired}`,\n );\n lines.push(\"\");\n for (const item of data.approvals.items.slice(0, APPROVALS_MARKDOWN_LIMIT)) {\n lines.push(`- ${item.reason} (${item.status}, ${item.riskLevel})`);\n }\n const overflow = data.approvals.items.length - APPROVALS_MARKDOWN_LIMIT;\n if (overflow > 0) lines.push(`- ... +${overflow} more`);\n }\n lines.push(\"\");\n\n // Tasks\n lines.push(\"## タスク\");\n lines.push(\"\");\n if (data.tasks.items.length === 0) {\n lines.push(\"(no tasks recorded yet)\");\n } else {\n const breakdown = data.tasks.byStatus.map((s) => `${s.status} ${s.count}`).join(\", \");\n lines.push(`Tasks: ${data.tasks.total} (${breakdown})`);\n lines.push(\"\");\n for (const item of data.tasks.items.slice(0, TASKS_MARKDOWN_LIMIT)) {\n lines.push(`- ${item.title} (${item.status})`);\n }\n const overflow = data.tasks.items.length - TASKS_MARKDOWN_LIMIT;\n if (overflow > 0) lines.push(`- ... +${overflow} more`);\n }\n lines.push(\"\");\n\n // Changed files\n lines.push(\"## 変更ファイル\");\n lines.push(\"\");\n if (data.changedFiles.length === 0) {\n lines.push(\"(no related files recorded)\");\n } else {\n for (const f of data.changedFiles.slice(0, CHANGED_FILES_MARKDOWN_LIMIT)) lines.push(`- ${f}`);\n const overflow = data.changedFiles.length - CHANGED_FILES_MARKDOWN_LIMIT;\n if (overflow > 0) lines.push(`- ... +${overflow} more`);\n }\n lines.push(\"\");\n\n // Sessions — newest first. The started_at is the human row key; full ids are\n // in --json (and would collide as short ids for batch imports).\n lines.push(\"## セッション一覧\");\n lines.push(\"\");\n if (data.sessions.items.length === 0) {\n lines.push(\"(no sessions yet)\");\n } else {\n lines.push(\"| started_at | source | status | active | out tok |\");\n lines.push(\"|---|---|---|---|---|\");\n for (const s of data.sessions.items.slice(0, SESSIONS_MARKDOWN_LIMIT)) {\n lines.push(\n `| ${s.startedAt} | ${s.source} | ${s.status} | ${formatDurationMs(s.activeMs)} | ${formatInt(s.outputTokens)} |`,\n );\n }\n const overflow = data.sessions.items.length - SESSIONS_MARKDOWN_LIMIT;\n if (overflow > 0) {\n lines.push(\"\");\n lines.push(`... +${overflow} more sessions`);\n }\n }\n lines.push(\"\");\n\n // Integrity\n lines.push(\"## 整合性\");\n lines.push(\"\");\n const i = data.integrity;\n lines.push(\n `Provenance internally tamper-checked: ${i.verified} verified, ${i.unchained} unchained, ${i.empty} empty, ${i.incomplete} incomplete, ${i.in_progress} in_progress, ${i.tampered} tampered (of ${i.total} sessions).`,\n );\n lines.push(\"\");\n lines.push(\n \"This reflects internal consistency of the local event-log hash chain — not a third-party cryptographic proof.\",\n );\n if (i.tampered > 0) {\n lines.push(\"\");\n // Full ids here: a tampered verdict is actionable, so surface the exact\n // session to investigate with `basou verify --session <id>`.\n for (const id of i.tamperedSessions) lines.push(`- Tampered: ${id}`);\n }\n\n return lines.join(\"\\n\");\n}\n\nfunction formatSessionsLine(data: ReportData): string {\n const counts = new Map<SessionStatus, number>();\n for (const s of data.sessions.byStatus) counts.set(s.status, s.count);\n const breakdown = SESSION_STATUS_ORDER.filter((s) => (counts.get(s) ?? 0) > 0)\n .map((s) => `${s} ${counts.get(s)}`)\n .join(\", \");\n return breakdown !== \"\"\n ? `Sessions: ${data.sessions.total} (${breakdown})`\n : `Sessions: ${data.sessions.total}`;\n}\n\n/** \"1,234,567\" — thousands-separated, fixed en-US so output is deterministic. */\nfunction formatInt(n: number): string {\n return n.toLocaleString(\"en-US\");\n}\n\n// Re-export the verdict-status type so callers (and tests) can name it without\n// reaching into the events module.\nexport type { ChainVerdictStatus };\n","import { join } from \"node:path\";\nimport { type ReplayWarning, replayEvents } from \"../events/event-replay.js\";\nimport type { Event } from \"../schemas/event.schema.js\";\nimport type {\n Session,\n SessionMetrics,\n SessionSourceKind,\n SessionStatus,\n} from \"../schemas/session.schema.js\";\nimport type { BasouPaths } from \"../storage/basou-dir.js\";\nimport { loadSessionEntries, type SessionSkipReason } from \"../storage/sessions.js\";\nimport {\n ACTIVE_GAP_CAP_MS,\n activeTimeFromTimestamps,\n type IntervalMs,\n type IsoInterval,\n intervalsIsoToMs,\n intervalsMsToIso,\n unionDurationMs,\n} from \"./active-time.js\";\n\n// Re-exported for callers that imported the cap from this module historically.\nexport { ACTIVE_GAP_CAP_MS };\n\n/**\n * Resolve the timezone used to bucket per-day stats. Native logs are UTC, so a\n * billing day needs an explicit timezone; default to the host's local zone.\n */\nfunction resolveTimeZone(timeZone: string | undefined): string {\n if (timeZone !== undefined && timeZone.length > 0) return timeZone;\n return Intl.DateTimeFormat().resolvedOptions().timeZone;\n}\n\nexport type WorkStatsInput = {\n paths: BasouPaths;\n /** Shared clock; running sessions are measured up to this instant. */\n now: Date;\n /**\n * IANA timezone used to bucket the per-day breakdown (logs are UTC, so a\n * billing day needs an explicit zone). Defaults to the host's local zone;\n * injectable for deterministic tests.\n */\n timeZone?: string;\n onWarning?: (warning: ReplayWarning, sessionId: string) => void;\n onSessionSkip?: (sessionId: string, reason: SessionSkipReason) => void;\n};\n\n/** Which measures are meaningful for a given session / source. */\nexport type MeasureAvailability = {\n /** Always true (started_at + now bound the span). */\n span: boolean;\n /**\n * `commandTimeMs` reflects real shell time. False for `claude-code-import`,\n * whose transcript carries no per-command duration (recorded as 0).\n */\n commandTime: boolean;\n /** At least one active interval could be measured (stored or event-derived). */\n activeTime: boolean;\n /** Token totals were captured (model-usage metrics present). */\n tokens: boolean;\n /** Model compute time was captured (`machine_active_time_ms`; Codex only). */\n machineActive: boolean;\n};\n\n/** Token rollup. Zero when not captured; `reasoning` is Codex-only. */\nexport type TokenTotals = {\n output: number;\n input: number;\n cached: number;\n reasoning: number;\n};\n\n/** How a session's active time was derived. */\nexport type ActiveTimeBasis = \"engaged-turns\" | \"events\";\n\nexport type SessionWorkStats = {\n sessionId: string;\n label: string | undefined;\n status: SessionStatus;\n sourceKind: SessionSourceKind;\n startedAt: string;\n endedAt: string | undefined;\n /** ended_at absent: span is measured to `now`. */\n open: boolean;\n sessionSpanMs: number;\n commandTimeMs: number;\n activeTimeMs: number;\n /**\n * How `activeTimeMs` / `activeIntervals` were derived: `engaged-turns` from\n * the engagement timestamps stored at import (captures conversation), or\n * `events` from the action-event stream (live sessions and pre-v2 imports).\n */\n activeTimeBasis: ActiveTimeBasis;\n /**\n * Merged active wall-clock ranges. Their summed duration equals\n * `activeTimeMs`; the aggregator unions them across sessions so overlapping\n * (concurrent) work is not double-counted in billable totals.\n */\n activeIntervals: IsoInterval[];\n /**\n * Model compute time: the source's summed per-turn duration\n * (`metrics.machine_active_time_ms`). A subset of `activeTimeMs`; 0 when the\n * source records no per-turn duration (everything but Codex today).\n */\n machineActiveTimeMs: number;\n /**\n * Methodology lock copied from `metrics.active_time_method` (e.g.\n * `turn-intervals` / `engaged-turns`); undefined when active time was derived\n * from the event stream rather than stored metrics.\n */\n activeTimeMethod: string | undefined;\n commandCount: number;\n fileChangedCount: number;\n decisionCount: number;\n eventCount: number;\n tokens: TokenTotals;\n availability: MeasureAvailability;\n /** ended_at < started_at (clock skew): span was clamped to 0. */\n spanClamped: boolean;\n /** events.jsonl could not be read: action / time counts are 0 + untrustworthy. */\n eventsUnreadable: boolean;\n};\n\nexport type SourceWorkStats = {\n sourceKind: SessionSourceKind;\n sessionCount: number;\n sessionSpanMs: number;\n commandTimeMs: number;\n activeTimeMs: number;\n machineActiveTimeMs: number;\n commandCount: number;\n fileChangedCount: number;\n decisionCount: number;\n eventCount: number;\n tokens: TokenTotals;\n /** Every session of this kind reports real command time. */\n commandTimeReliable: boolean;\n /** At least one session of this kind captured token totals. */\n tokensAvailable: boolean;\n /** At least one session of this kind captured model compute time. */\n machineActiveAvailable: boolean;\n};\n\nexport type StatusCount = { status: SessionStatus; count: number };\n\n/**\n * One calendar day of the time x volume billing view. `billableActiveTimeMs` is\n * the union of active intervals starting on this date (so per-day sums to the\n * de-duplicated workspace total); volume is attributed to each session's\n * `started_at` date.\n */\nexport type DayWorkStats = {\n /** Calendar date `YYYY-MM-DD` in the report timezone. */\n date: string;\n billableActiveTimeMs: number;\n /**\n * Model compute time for sessions started on this date (summed\n * `machine_active_time_ms`). Not wall-clock-deduplicated, so — unlike\n * `billableActiveTimeMs` — concurrent sessions sum freely.\n */\n machineActiveTimeMs: number;\n sessionCount: number;\n commandCount: number;\n fileChangedCount: number;\n decisionCount: number;\n tokens: TokenTotals;\n};\n\nexport type WorkStatsTotals = {\n sessionCount: number;\n openSessionCount: number;\n sessionSpanMs: number;\n commandTimeMs: number;\n /** Naive sum of per-session active time; double-counts overlapping sessions. */\n activeTimeMs: number;\n /**\n * Billable active time: the UNION of every session's active intervals, so\n * concurrent sessions do not double-count human wall-clock. Equals\n * `activeTimeMs` when no sessions overlap, and is smaller when they do.\n */\n billableActiveTimeMs: number;\n /**\n * Workspace-wide model compute time: summed `machine_active_time_ms`. A plain\n * sum (not interval union), so it can exceed `billableActiveTimeMs` when\n * sessions ran concurrently — two models working at once is two machine-hours\n * in one wall-clock hour.\n */\n machineActiveTimeMs: number;\n commandCount: number;\n fileChangedCount: number;\n decisionCount: number;\n eventCount: number;\n tokens: TokenTotals;\n /** No `claude-code-import` sessions present, so command time is workspace-wide real. */\n commandTimeReliable: boolean;\n tokensAvailable: boolean;\n /** At least one session captured model compute time (`machine_active_time_ms`). */\n machineActiveAvailable: boolean;\n};\n\nexport type WorkStatsResult = {\n generatedAt: string;\n /** Idle-gap cap applied to active time (methodology lock). */\n activeGapCapMs: number;\n /** IANA timezone used to bucket {@link WorkStatsResult.byDay}. */\n timeZone: string;\n totals: WorkStatsTotals;\n /** Per session, started_at ascending (loadSessionEntries order). */\n sessions: SessionWorkStats[];\n bySource: SourceWorkStats[];\n byStatus: StatusCount[];\n /** Per-day time x volume billing view, date ascending. */\n byDay: DayWorkStats[];\n};\n\n// Fixed display order, mirroring the handoff renderer (+ archived appended).\nconst STATUS_ORDER: readonly SessionStatus[] = [\n \"completed\",\n \"failed\",\n \"running\",\n \"interrupted\",\n \"waiting_approval\",\n \"initialized\",\n \"imported\",\n \"archived\",\n];\n\n/**\n * Aggregate work + engaged-time across the workspace's sessions.\n *\n * Honesty note: this returns a LABELED SET of measures, not one number. Token\n * volume (when captured) is the most direct \"how much the AI produced\" signal.\n * The time measures are proxies, ordered from most to least billing-relevant:\n *\n * - `billableActiveTimeMs` (totals) is the headline for billing human harness\n * labor: the UNION of every session's active intervals, so two sessions run\n * concurrently do not bill the same wall-clock twice. `activeTimeMs` is the\n * naive sum, kept only to expose the overlap delta.\n * - Per-session active time is derived from the session's ENGAGED series. For\n * imported sessions this is the genuine engagement timestamps captured at\n * import (conversation turns plus action events), so design discussion that\n * produced few tool calls is still counted; idle gaps over `ACTIVE_GAP_CAP_MS`\n * (5 min) are not credited. Live sessions and pre-v2 imports lack that signal\n * and fall back to the action-event stream (`activeTimeBasis: \"events\"`).\n * - `sessionSpanMs` overcounts (includes idle) and `commandTimeMs` is\n * shell-execution only (0 for `claude-code-import`); both are kept as context.\n *\n * The per-day view buckets the union intervals by `timeZone` (logs are UTC, so\n * a billing day needs an explicit zone). A union interval crossing local\n * midnight is attributed to its start day; per-day time still sums to the\n * billable total. Availability flags let callers caveat each measure.\n *\n * Session enumeration goes through {@link loadSessionEntries} (the handoff /\n * decisions path), so `session.yaml`-broken sessions are skipped consistently.\n */\nexport async function computeWorkStats(input: WorkStatsInput): Promise<WorkStatsResult> {\n const { now } = input;\n const timeZone = resolveTimeZone(input.timeZone);\n // Surface events_jsonl_unreadable exactly once per session even when the\n // throw happens in our own replay loop below (verbatim from the renderers).\n const unreadableEmitted = new Set<string>();\n const wrappedSkip: (sid: string, reason: SessionSkipReason) => void = (sid, reason) => {\n if (reason === \"events_jsonl_unreadable\") unreadableEmitted.add(sid);\n input.onSessionSkip?.(sid, reason);\n };\n const loadOpts: Parameters<typeof loadSessionEntries>[1] = { now, onSkip: wrappedSkip };\n if (input.onWarning !== undefined) loadOpts.onWarning = input.onWarning;\n const entries = await loadSessionEntries(input.paths, loadOpts);\n\n const sessions: SessionWorkStats[] = [];\n for (const entry of entries) {\n const events: Event[] = [];\n let eventsUnreadable = false;\n try {\n for await (const ev of replayEvents(join(input.paths.sessions, entry.sessionId), {\n onWarning: (w) => input.onWarning?.(w, entry.sessionId),\n })) {\n events.push(ev);\n }\n } catch {\n eventsUnreadable = true;\n if (!unreadableEmitted.has(entry.sessionId)) {\n wrappedSkip(entry.sessionId, \"events_jsonl_unreadable\");\n }\n }\n sessions.push(\n sessionWorkStatsFromEvents(\n entry.sessionId,\n entry.session.session,\n events,\n now,\n eventsUnreadable,\n ),\n );\n }\n\n // Union every session's active intervals once; both the billable total and\n // the per-day view are attributed from the same merged ranges so they agree.\n const allIntervals: IntervalMs[] = [];\n for (const s of sessions) allIntervals.push(...intervalsIsoToMs(s.activeIntervals));\n const union = unionDurationMs(allIntervals);\n\n return {\n generatedAt: now.toISOString(),\n activeGapCapMs: ACTIVE_GAP_CAP_MS,\n timeZone,\n totals: computeTotals(sessions, union.ms),\n sessions,\n bySource: computeBySource(sessions),\n byStatus: computeByStatus(sessions),\n byDay: computeByDay(sessions, union.merged, timeZone),\n };\n}\n\n/**\n * Compute one session's work stats from its inner record + event list. Pure\n * and exported so a single-session surface (e.g. `basou session show`) can\n * reuse the exact same measures the workspace aggregator produces.\n */\nexport function sessionWorkStatsFromEvents(\n sessionId: string,\n inner: Session[\"session\"],\n events: ReadonlyArray<Event>,\n now: Date,\n eventsUnreadable = false,\n): SessionWorkStats {\n let commandCount = 0;\n let fileChangedCount = 0;\n let decisionCount = 0;\n let commandTimeMs = 0;\n const timestamps: number[] = [];\n for (const ev of events) {\n const t = Date.parse(ev.occurred_at);\n if (Number.isFinite(t)) timestamps.push(t);\n if (ev.type === \"command_executed\") {\n commandCount++;\n commandTimeMs += ev.duration_ms;\n } else if (ev.type === \"file_changed\") {\n fileChangedCount++;\n } else if (ev.type === \"decision_recorded\") {\n decisionCount++;\n }\n }\n const span = computeSpan(inner.started_at, inner.ended_at, now);\n const tokens = readTokens(inner.metrics);\n const active = resolveActiveTime(inner.metrics, timestamps);\n const machineActiveTimeMs = inner.metrics?.machine_active_time_ms ?? 0;\n return {\n sessionId,\n label: inner.label,\n status: inner.status,\n sourceKind: inner.source.kind,\n startedAt: inner.started_at,\n endedAt: inner.ended_at,\n open: inner.ended_at === undefined,\n sessionSpanMs: span.ms,\n commandTimeMs,\n activeTimeMs: active.ms,\n activeTimeBasis: active.basis,\n activeIntervals: intervalsMsToIso(active.intervals),\n machineActiveTimeMs,\n activeTimeMethod: inner.metrics?.active_time_method,\n commandCount,\n fileChangedCount,\n decisionCount,\n eventCount: events.length,\n tokens,\n availability: {\n span: true,\n commandTime: inner.source.kind !== \"claude-code-import\",\n activeTime: active.intervals.length > 0,\n tokens: hasTokens(tokens),\n machineActive: machineActiveTimeMs > 0,\n },\n spanClamped: span.clamped,\n eventsUnreadable,\n };\n}\n\n/**\n * Resolve a session's active time + intervals. Prefer the engaged-time\n * intervals stored at import (they capture conversation turns the event stream\n * misses); otherwise derive from the action-event timestamps. Either way\n * `ms` equals the summed interval duration.\n */\nfunction resolveActiveTime(\n metrics: SessionMetrics | undefined,\n eventTimestamps: number[],\n): { ms: number; intervals: IntervalMs[]; basis: ActiveTimeBasis } {\n const stored = metrics?.active_intervals;\n if (stored !== undefined && stored.length > 0) {\n const intervals = intervalsIsoToMs(stored);\n const ms = intervals.reduce((n, [start, end]) => n + (end - start), 0);\n return { ms, intervals, basis: \"engaged-turns\" };\n }\n const derived = activeTimeFromTimestamps(eventTimestamps, ACTIVE_GAP_CAP_MS);\n return { ms: derived.ms, intervals: derived.intervals, basis: \"events\" };\n}\n\nfunction computeSpan(\n startedAt: string,\n endedAt: string | undefined,\n now: Date,\n): { ms: number; clamped: boolean } {\n const start = Date.parse(startedAt);\n const end = endedAt !== undefined ? Date.parse(endedAt) : now.getTime();\n if (!Number.isFinite(start) || !Number.isFinite(end)) return { ms: 0, clamped: true };\n const raw = end - start;\n return raw < 0 ? { ms: 0, clamped: true } : { ms: raw, clamped: false };\n}\n\nfunction readTokens(metrics: SessionMetrics | undefined): TokenTotals {\n return {\n output: metrics?.output_tokens ?? 0,\n input: metrics?.input_tokens ?? 0,\n cached: metrics?.cached_input_tokens ?? 0,\n reasoning: metrics?.reasoning_output_tokens ?? 0,\n };\n}\n\nfunction hasTokens(t: TokenTotals): boolean {\n return t.output > 0 || t.input > 0 || t.cached > 0 || t.reasoning > 0;\n}\n\nfunction emptyTokens(): TokenTotals {\n return { output: 0, input: 0, cached: 0, reasoning: 0 };\n}\n\nfunction addTokens(a: TokenTotals, b: TokenTotals): void {\n a.output += b.output;\n a.input += b.input;\n a.cached += b.cached;\n a.reasoning += b.reasoning;\n}\n\nfunction computeTotals(\n sessions: readonly SessionWorkStats[],\n billableActiveTimeMs: number,\n): WorkStatsTotals {\n const tokens = emptyTokens();\n const totals: WorkStatsTotals = {\n sessionCount: sessions.length,\n openSessionCount: 0,\n sessionSpanMs: 0,\n commandTimeMs: 0,\n activeTimeMs: 0,\n billableActiveTimeMs,\n machineActiveTimeMs: 0,\n commandCount: 0,\n fileChangedCount: 0,\n decisionCount: 0,\n eventCount: 0,\n tokens,\n commandTimeReliable: true,\n tokensAvailable: false,\n machineActiveAvailable: false,\n };\n for (const s of sessions) {\n if (s.open) totals.openSessionCount++;\n totals.sessionSpanMs += s.sessionSpanMs;\n totals.commandTimeMs += s.commandTimeMs;\n totals.activeTimeMs += s.activeTimeMs;\n totals.machineActiveTimeMs += s.machineActiveTimeMs;\n totals.commandCount += s.commandCount;\n totals.fileChangedCount += s.fileChangedCount;\n totals.decisionCount += s.decisionCount;\n totals.eventCount += s.eventCount;\n addTokens(tokens, s.tokens);\n if (!s.availability.commandTime) totals.commandTimeReliable = false;\n if (s.availability.tokens) totals.tokensAvailable = true;\n if (s.availability.machineActive) totals.machineActiveAvailable = true;\n }\n return totals;\n}\n\nfunction computeBySource(sessions: readonly SessionWorkStats[]): SourceWorkStats[] {\n const map = new Map<SessionSourceKind, SourceWorkStats>();\n for (const s of sessions) {\n let row = map.get(s.sourceKind);\n if (row === undefined) {\n row = {\n sourceKind: s.sourceKind,\n sessionCount: 0,\n sessionSpanMs: 0,\n commandTimeMs: 0,\n activeTimeMs: 0,\n machineActiveTimeMs: 0,\n commandCount: 0,\n fileChangedCount: 0,\n decisionCount: 0,\n eventCount: 0,\n tokens: emptyTokens(),\n commandTimeReliable: true,\n tokensAvailable: false,\n machineActiveAvailable: false,\n };\n map.set(s.sourceKind, row);\n }\n row.sessionCount++;\n row.sessionSpanMs += s.sessionSpanMs;\n row.commandTimeMs += s.commandTimeMs;\n row.activeTimeMs += s.activeTimeMs;\n row.machineActiveTimeMs += s.machineActiveTimeMs;\n row.commandCount += s.commandCount;\n row.fileChangedCount += s.fileChangedCount;\n row.decisionCount += s.decisionCount;\n row.eventCount += s.eventCount;\n addTokens(row.tokens, s.tokens);\n if (!s.availability.commandTime) row.commandTimeReliable = false;\n if (s.availability.tokens) row.tokensAvailable = true;\n if (s.availability.machineActive) row.machineActiveAvailable = true;\n }\n return [...map.values()].sort((a, b) => a.sourceKind.localeCompare(b.sourceKind));\n}\n\nfunction computeByStatus(sessions: readonly SessionWorkStats[]): StatusCount[] {\n const counts = new Map<SessionStatus, number>();\n for (const s of sessions) counts.set(s.status, (counts.get(s.status) ?? 0) + 1);\n const ordered: StatusCount[] = [];\n for (const status of STATUS_ORDER) {\n const count = counts.get(status);\n if (count !== undefined && count > 0) ordered.push({ status, count });\n }\n return ordered;\n}\n\n/**\n * Build the per-day billing view. Time comes from the pre-merged union\n * intervals, attributed to each interval's start date so the per-day totals sum\n * exactly to `totals.billableActiveTimeMs`. Volume (tokens, action counts) is\n * attributed to each session's `started_at` date.\n */\nfunction computeByDay(\n sessions: readonly SessionWorkStats[],\n unionMerged: readonly IntervalMs[],\n timeZone: string,\n): DayWorkStats[] {\n const days = new Map<string, DayWorkStats>();\n const ensure = (date: string): DayWorkStats => {\n let day = days.get(date);\n if (day === undefined) {\n day = {\n date,\n billableActiveTimeMs: 0,\n machineActiveTimeMs: 0,\n sessionCount: 0,\n commandCount: 0,\n fileChangedCount: 0,\n decisionCount: 0,\n tokens: emptyTokens(),\n };\n days.set(date, day);\n }\n return day;\n };\n for (const [start, end] of unionMerged) {\n ensure(tzDate(start, timeZone)).billableActiveTimeMs += end - start;\n }\n for (const s of sessions) {\n const startedMs = Date.parse(s.startedAt);\n if (!Number.isFinite(startedMs)) continue;\n const day = ensure(tzDate(startedMs, timeZone));\n day.sessionCount++;\n day.machineActiveTimeMs += s.machineActiveTimeMs;\n day.commandCount += s.commandCount;\n day.fileChangedCount += s.fileChangedCount;\n day.decisionCount += s.decisionCount;\n addTokens(day.tokens, s.tokens);\n }\n return [...days.values()].sort((a, b) => a.date.localeCompare(b.date));\n}\n\n/** Calendar date (`YYYY-MM-DD`) of an instant in the given IANA timezone. */\nfunction tzDate(ms: number, timeZone: string): string {\n return new Intl.DateTimeFormat(\"en-CA\", {\n timeZone,\n year: \"numeric\",\n month: \"2-digit\",\n day: \"2-digit\",\n }).format(new Date(ms));\n}\n","import { existsSync, realpathSync } from \"node:fs\";\nimport { homedir } from \"node:os\";\nimport { basename, isAbsolute, join } from \"node:path\";\nimport { type ReplayWarning, replayEvents } from \"../events/event-replay.js\";\nimport type { BasouPaths } from \"../storage/basou-dir.js\";\nimport { loadSessionEntries, type SessionSkipReason } from \"../storage/sessions.js\";\n\n/**\n * Review-gap surfacer: a read-only, advisory check for the \"external\n * adversarial review before commit\" protocol. For each unit of work that landed\n * commits, it asks whether a CROSS-MODEL review session (a different vendor than\n * the one that wrote the code — here: Codex) actually examined that repo's diff\n * before the commit.\n *\n * Hard design rule, learned from killing the naive time-window v1 (which\n * false-cleared the very omission that motivated this): it NEVER emits a\n * confident \"reviewed / clear\" verdict. Temporal proximity is not binding. The\n * worst failure mode is falsely reassuring the operator that a protocol was\n * followed when it was not, so this surfaces SUSPICION and leaves the final\n * binding to a human:\n *\n * - `omission` no cross-model review of this repo in the preceding window.\n * - `near_unbound` a review session was nearby but did not examine this repo's\n * diff or any changed file (the exact class naive v1 cleared).\n * - `candidate` a review session examined this repo's diff / overlapping\n * files — listed for the human to confirm it covered THIS\n * change. NOT an automatic pass.\n * - `unknown` the repo or time could not be derived; abstain rather than\n * guess (an abstention is never counted as a clear).\n *\n * It reads only captured provenance and writes nothing.\n */\n\nexport type ReviewGapVerdict = \"omission\" | \"near_unbound\" | \"candidate\" | \"unknown\";\n\n/** A cross-model review session cited as (possibly) covering a unit of work. */\nexport type CitedReview = {\n sessionId: string;\n /** The session ran `git diff` / `git show` in the repo (examined the diff). */\n examinedDiff: boolean;\n /** Basenames of files the session read/inspected in the repo (capped). */\n files: string[];\n endedAt: string | null;\n};\n\n/** One unit of work (a committing session's commits in one repo) and its verdict. */\nexport type ReviewGapUnit = {\n repo: string;\n /** The session whose commits form this unit. */\n sessionId: string;\n commitCount: number;\n firstCommitAt: string | null;\n lastCommitAt: string | null;\n verdict: ReviewGapVerdict;\n /** For `candidate` / `near_unbound`: the review sessions considered. */\n reviews: CitedReview[];\n};\n\nexport type ReviewGapRepoSummary = {\n repo: string;\n units: number;\n omissionUnits: number;\n nearUnboundUnits: number;\n candidateUnits: number;\n unknownUnits: number;\n};\n\nexport type ReviewGapsSummary = {\n generatedAt: string;\n windowHours: number;\n /** Repos the scope was restricted to, or null when every repo was considered. */\n scope: string[] | null;\n repos: ReviewGapRepoSummary[];\n /** Units WITHOUT a binding review trail (omission + near_unbound), recent-first. */\n gaps: ReviewGapUnit[];\n /** Units WITH a review candidate, recent-first (surfaced for confirmation). */\n candidates: ReviewGapUnit[];\n /** Units whose repo/time could not be derived from the captured command; abstained, not cleared. */\n unknowns: ReviewGapUnit[];\n /** Newest captured commit considered; commits not yet imported are invisible. */\n newestCommitAt: string | null;\n};\n\n/** Strip one layer of matching surrounding quotes (e.g. `cd \"…/repo\"`). */\nfunction stripQuotes(s: string): string {\n if (s.length >= 2 && ((s[0] === '\"' && s.at(-1) === '\"') || (s[0] === \"'\" && s.at(-1) === \"'\"))) {\n return s.slice(1, -1);\n }\n return s;\n}\n\n/**\n * Per-process cache of realpath resolutions. A stored `null` records that\n * realpath FAILED for that input (the path is absent), so a repeat lookup of the\n * same absent path neither re-issues the syscall nor is mistaken for a cache\n * miss. The filesystem is assumed stable for the duration of a single command\n * run. Bounded in practice: one entry per distinct repo path seen (O(10–100)).\n */\nconst realpathCache = new Map<string, string | null>();\n\n/** realpath an absolute path, caching both success and failure; null when unresolvable. */\nfunction resolveRealpath(absPath: string): string | null {\n // Stored values are `string | null`; only an ABSENT key reads back as\n // `undefined`, so a cached failure (null) returns without re-issuing realpath.\n const cached = realpathCache.get(absPath);\n if (cached !== undefined) return cached;\n let resolved: string | null;\n try {\n resolved = realpathSync(absPath);\n } catch {\n resolved = null;\n }\n realpathCache.set(absPath, resolved);\n return resolved;\n}\n\n/** Per-process cache of git-repo-root checks, keyed by resolved (realpath) path. */\nconst repoRootCache = new Map<string, boolean>();\n\n/**\n * Whether a resolved path is a git repo root, i.e. contains a `.git` (a directory\n * for a normal clone, a file for a worktree/submodule). Used to reject a real but\n * non-repo directory (a workspace view root, `/tmp`, a scratch dir) so it never\n * becomes a binding key. A bare repo (no working tree, no `.git` child) is not\n * recognized — review-gaps tracks working-tree commits, which bare repos lack.\n */\nfunction isRepoRoot(realPath: string): boolean {\n const cached = repoRootCache.get(realPath);\n if (cached !== undefined) return cached;\n const result = existsSync(join(realPath, \".git\"));\n repoRootCache.set(realPath, result);\n return result;\n}\n\n/**\n * Normalize a path to a stable BINDING key: the canonical full path (NOT just a\n * basename), so a commit in `/u/projects/basou` and a review in\n * `/u/projects/basou` bind, while a same-named checkout elsewhere\n * (`/tmp/x/basou`) does not.\n *\n * A workspace \"view\" reaches sibling repos through symlinks\n * (`<view>/<repo> -> ../<repo>`), and commits are often run with\n * `cd <view>/<repo>`. To collapse the view-routed path and the direct path to\n * one key REGARDLESS of the view directory's name, the path is resolved with\n * realpath (which also unifies platform aliases such as macOS `/tmp` ->\n * `/private/tmp`). Only absolute paths are resolved; a relative `cd ../x` target\n * would realpath against the wrong base, so it is left to the fallback.\n *\n * A resolved path is accepted as a key only when it is an actual git repo root\n * (contains `.git`); a real but non-repo directory (a view root, `/tmp`, a\n * scratch dir) returns null so the caller abstains (`unknown`) rather than\n * mislabeling it a repo. When realpath cannot resolve the path (e.g. a historical\n * capture whose repo has since moved), it FALLS BACK to a string heuristic that\n * collapses a `*-workspace`-named view and rejects the view root itself. Returns\n * null for a non-repo / view root, an unexpanded shell var, or empty input.\n *\n * The realpath / `.git` probes are the only filesystem I/O this otherwise\n * string-pure key function performs, and their results are cached for the\n * process lifetime.\n */\nexport function normalizeRepoPath(p: string | null | undefined): string | null {\n if (!p) return null;\n let s = stripQuotes(p.trim()).replace(/\\/+$/, \"\");\n if (s.length === 0 || s === \"~\") return null;\n // expand a leading ~ so the same repo recorded as `~/projects/x` and\n // `/Users/u/projects/x` collapses to one binding key (the events capture both).\n if (s.startsWith(\"~/\")) s = homedir() + s.slice(1);\n\n // Prefer the on-disk truth: realpath follows the view's symlink so ANY view\n // name (not only `*-workspace`) collapses to the real repo path. Only absolute\n // paths are resolved; a relative target would resolve against the wrong base.\n if (isAbsolute(s)) {\n const real = resolveRealpath(s);\n if (real !== null) {\n // Resolved on disk: bind only when it is an actual git repo root. A real\n // but non-repo directory must not become a key, and must NOT fall through\n // to the string heuristic (which would mislabel `/tmp`, scratch dirs, a\n // view root) — abstain (null -> `unknown`) instead.\n return isRepoRoot(real) ? real : null;\n }\n // real === null: path absent (e.g. a moved/historical capture) -> fall\n // through to the legacy *-workspace string heuristic below.\n }\n\n // Fallback for paths not present on disk (historical/imported captures): the\n // legacy string heuristic, name-bound to `*-workspace` views.\n // a path THROUGH a *-workspace view: .../foo-workspace/foo-planning -> .../foo-planning\n s = s.replace(/\\/[^/]*-workspace\\/([^/]+)/, \"/$1\");\n const seg = s\n .split(\"/\")\n .filter((x) => x.length > 0)\n .pop();\n if (seg === undefined) return null;\n // the view dir itself is not a repo; an unexpanded shell var is not a repo\n if (/-workspace$/.test(seg) || seg.includes(\"$\")) return null;\n return s;\n}\n\n/**\n * Short repo key (the final path segment) for DISPLAY and `--scope` matching.\n * Binding uses {@link normalizeRepoPath} to avoid basename collisions; this is\n * only the human-facing label.\n */\nexport function normalizeRepoKey(p: string | null | undefined): string | null {\n const full = normalizeRepoPath(p);\n return full === null ? null : basename(full);\n}\n\n/** Files a single command read/inspected, and whether it inspected the git diff. */\nfunction inspectCommand(args: string[]): { files: string[]; examinedDiff: boolean } {\n const a = args.join(\" \");\n const files = new Set<string>();\n const examinedDiff = /\\bgit\\s+(?:diff|show|log\\s+-p|add\\s+-p)\\b/.test(a);\n for (const re of [\n /\\b(?:cat|less|bat|head|tail)\\s+([^\\s|&;<>]+)/g,\n /\\bsed\\s+-n\\s+'[^']*'\\s+([^\\s|&;<>]+)/g,\n /\\b(?:rg|grep)\\b[^|&;]*?\\s([^\\s|&;<>]+\\.[A-Za-z0-9]+)(?:\\s|$)/g,\n ]) {\n let m: RegExpExecArray | null;\n // biome-ignore lint/suspicious/noAssignInExpressions: standard regex exec loop\n while ((m = re.exec(a)) !== null) {\n const f = m[1];\n if (f !== undefined) files.add(basename(f));\n }\n }\n return { files: [...files], examinedDiff };\n}\n\n/** Repo a command effectively ran in: an explicit `cd <repo> &&` wins over cwd. */\nfunction commandRepo(args: string[], cwd: string): string | null {\n // An explicit `cd <target> &&` wins over cwd — and wins EVEN WHEN the target\n // resolves to null (a non-repo dir): the command ran there, so it must not be\n // silently re-credited to the session's cwd (which could falsely bind an\n // unrelated repo and clear a real gap). Fall back to cwd only when there was\n // no explicit `cd`.\n const cd = args.join(\" \").match(/\\bcd\\s+(\"[^\"]+\"|'[^']+'|[^\\s&]+)\\s*&&/);\n if (cd) return normalizeRepoPath(cd[1]);\n return normalizeRepoPath(cwd);\n}\n\n/** True when a captured command exited non-zero (a failure is not evidence / not landed work). */\nfunction commandFailed(exitCode: number | null): boolean {\n return exitCode !== null && exitCode !== 0;\n}\n\n/** Changed files named inline on the commit's command (`git add A B`); heuristic. */\nfunction commitFiles(args: string[]): string[] {\n const a = args.join(\" \");\n const add = a.match(/git add\\s+([^&|;]+)/);\n if (!add?.[1]) return [];\n return add[1]\n .split(/\\s+/)\n .filter((t) => /\\.[A-Za-z]/.test(t) && !t.startsWith(\"-\"))\n .map((t) => basename(t));\n}\n\ntype CommitRec = { repo: string; at: number; files: string[] };\ntype ReviewRec = {\n sessionId: string;\n endedAt: number | null;\n /** repo key -> what the review touched in it. */\n repos: Map<string, { examinedDiff: boolean; files: Set<string> }>;\n};\n\nconst REVIEW_SOURCE = \"codex-import\"; // the cross-model reviewer vendor (v1)\nconst DEFAULT_WINDOW_HOURS = 24;\n\nexport type ReviewGapsInput = {\n paths: BasouPaths;\n /** ISO \"now\"; basis for `generatedAt`. */\n nowIso: string;\n /** Restrict to these repo keys (e.g. [\"basou\"]); omit/empty = every repo seen. */\n scope?: string[];\n /** Coarse pre-filter window before a commit to look for a review; default 24h. */\n windowHours?: number;\n onWarning?: (warning: ReplayWarning, sessionId: string) => void;\n onSessionSkip?: (sessionId: string, reason: SessionSkipReason) => void;\n};\n\n/**\n * Compute the {@link ReviewGapsSummary} for a workspace. Read-only: reads\n * captured sessions / events and writes nothing.\n */\nexport async function findReviewGaps(input: ReviewGapsInput): Promise<ReviewGapsSummary> {\n const now = new Date(input.nowIso);\n const windowHours = input.windowHours ?? DEFAULT_WINDOW_HOURS;\n const scope = input.scope && input.scope.length > 0 ? input.scope : null;\n\n const loadOpts: Parameters<typeof loadSessionEntries>[1] = { now };\n if (input.onSessionSkip !== undefined) loadOpts.onSkip = input.onSessionSkip;\n if (input.onWarning !== undefined) loadOpts.onWarning = input.onWarning;\n const entries = await loadSessionEntries(input.paths, loadOpts);\n\n const reviews: ReviewRec[] = [];\n // committing session -> repo path -> commits\n const workUnits = new Map<string, Map<string, CommitRec[]>>();\n // committing session -> commit times whose repo/time could not be derived\n const unknownCommits = new Map<string, (number | null)[]>();\n\n for (const entry of entries) {\n const sessionDir = join(input.paths.sessions, entry.sessionId);\n const isReview = entry.session.session.source.kind === REVIEW_SOURCE;\n const reviewRepos = new Map<string, { examinedDiff: boolean; files: Set<string> }>();\n let reviewEnd: number | null = null;\n\n try {\n for await (const ev of replayEvents(sessionDir, {\n onWarning: (w) => input.onWarning?.(w, entry.sessionId),\n })) {\n if (ev.type !== \"command_executed\") continue;\n // A failed command is neither review evidence nor landed work.\n if (commandFailed(ev.exit_code)) continue;\n const at = Date.parse(ev.occurred_at);\n\n if (isReview) {\n // Bind to the repo the command actually ran in (an explicit `cd <repo>`\n // wins over cwd), symmetric with commit derivation, so `cd other &&\n // git diff` is not credited to the session's starting cwd.\n const repo = commandRepo(ev.args, ev.cwd);\n if (repo === null) continue;\n const ins = inspectCommand(ev.args);\n const slot = reviewRepos.get(repo) ?? { examinedDiff: false, files: new Set() };\n if (ins.examinedDiff) slot.examinedDiff = true;\n for (const f of ins.files) slot.files.add(f);\n reviewRepos.set(repo, slot);\n if (!Number.isNaN(at)) reviewEnd = reviewEnd === null ? at : Math.max(reviewEnd, at);\n continue;\n }\n\n // committing (code-author) session: collect git-commit events\n if (!ev.args.join(\" \").includes(\"git commit\")) continue;\n const repo = commandRepo(ev.args, ev.cwd);\n if (repo === null || Number.isNaN(at)) {\n // Surface as unknown rather than silently dropping an observed commit.\n const list = unknownCommits.get(entry.sessionId) ?? [];\n list.push(Number.isNaN(at) ? null : at);\n unknownCommits.set(entry.sessionId, list);\n continue;\n }\n const byRepo = workUnits.get(entry.sessionId) ?? new Map<string, CommitRec[]>();\n const list = byRepo.get(repo) ?? [];\n list.push({ repo, at, files: commitFiles(ev.args) });\n byRepo.set(repo, list);\n workUnits.set(entry.sessionId, byRepo);\n }\n } catch {\n input.onSessionSkip?.(entry.sessionId, \"events_jsonl_unreadable\");\n continue;\n }\n\n if (isReview && reviewRepos.size > 0) {\n reviews.push({ sessionId: entry.sessionId, endedAt: reviewEnd, repos: reviewRepos });\n }\n }\n\n const windowMs = windowHours * 3600 * 1000;\n const units: ReviewGapUnit[] = [];\n let newestCommit: number | null = null;\n\n for (const [sessionId, byRepo] of workUnits) {\n for (const [repoPath, commits] of byRepo) {\n const label = basename(repoPath);\n if (scope !== null && !scope.includes(label)) continue;\n const times = commits.map((c) => c.at).sort((a, b) => a - b);\n const first = times[0] ?? null;\n const last = times[times.length - 1] ?? null;\n if (last !== null) newestCommit = newestCommit === null ? last : Math.max(newestCommit, last);\n const changedFiles = new Set(commits.flatMap((c) => c.files));\n\n // candidate reviews: the SAME repo path (collision-safe), ended before this\n // unit's first commit, within the coarse window. The window is only a\n // pre-filter — binding is by examined diff / overlapping files, never by\n // temporal proximity alone.\n const before = first ?? last ?? 0;\n const nearby = reviews.filter((r) => {\n if (!r.repos.has(repoPath) || r.endedAt === null) return false;\n return r.endedAt <= before && r.endedAt >= before - windowMs;\n });\n const bound = nearby.filter((r) => {\n const touched = r.repos.get(repoPath);\n if (touched === undefined) return false;\n if (touched.examinedDiff) return true;\n for (const f of changedFiles) if (touched.files.has(f)) return true;\n return false;\n });\n\n const verdict: ReviewGapVerdict =\n bound.length > 0 ? \"candidate\" : nearby.length > 0 ? \"near_unbound\" : \"omission\";\n const cited = verdict === \"candidate\" ? bound : verdict === \"near_unbound\" ? nearby : [];\n\n units.push({\n repo: label,\n sessionId,\n commitCount: commits.length,\n firstCommitAt: first === null ? null : new Date(first).toISOString(),\n lastCommitAt: last === null ? null : new Date(last).toISOString(),\n verdict,\n reviews: cited.map((r) => ({\n sessionId: r.sessionId,\n examinedDiff: r.repos.get(repoPath)?.examinedDiff ?? false,\n files: [...(r.repos.get(repoPath)?.files ?? [])].slice(0, 8),\n endedAt: r.endedAt === null ? null : new Date(r.endedAt).toISOString(),\n })),\n });\n }\n }\n\n // Observed commits whose repo/time could not be derived become explicit\n // `unknown` units (an abstention, never a clear). They cannot be attributed to\n // a scoped repo, so they are reported only when no `--repo` scope is applied.\n if (scope === null) {\n for (const [sessionId, times] of unknownCommits) {\n const valid = times.filter((t): t is number => t !== null).sort((a, b) => a - b);\n const first = valid[0] ?? null;\n const last = valid[valid.length - 1] ?? null;\n if (last !== null) newestCommit = newestCommit === null ? last : Math.max(newestCommit, last);\n units.push({\n repo: \"(unknown)\",\n sessionId,\n commitCount: times.length,\n firstCommitAt: first === null ? null : new Date(first).toISOString(),\n lastCommitAt: last === null ? null : new Date(last).toISOString(),\n verdict: \"unknown\",\n reviews: [],\n });\n }\n }\n\n const recentFirst = (a: ReviewGapUnit, b: ReviewGapUnit): number =>\n (Date.parse(b.lastCommitAt ?? \"\") || 0) - (Date.parse(a.lastCommitAt ?? \"\") || 0);\n\n const repoKeys = [...new Set(units.map((u) => u.repo))].sort();\n const repos: ReviewGapRepoSummary[] = repoKeys.map((repo) => {\n const us = units.filter((u) => u.repo === repo);\n return {\n repo,\n units: us.length,\n omissionUnits: us.filter((u) => u.verdict === \"omission\").length,\n nearUnboundUnits: us.filter((u) => u.verdict === \"near_unbound\").length,\n candidateUnits: us.filter((u) => u.verdict === \"candidate\").length,\n unknownUnits: us.filter((u) => u.verdict === \"unknown\").length,\n };\n });\n\n return {\n generatedAt: input.nowIso,\n windowHours,\n scope,\n repos,\n gaps: units\n .filter((u) => u.verdict === \"omission\" || u.verdict === \"near_unbound\")\n .sort(recentFirst),\n candidates: units.filter((u) => u.verdict === \"candidate\").sort(recentFirst),\n unknowns: units.filter((u) => u.verdict === \"unknown\").sort(recentFirst),\n newestCommitAt: newestCommit === null ? null : new Date(newestCommit).toISOString(),\n };\n}\n","import { type ChildProcess, spawn } from \"node:child_process\";\n\nimport { findErrorCode } from \"../storage/status.js\";\n\nimport type { ProcessRunner, RunOptions, RunResult } from \"./process-runner.js\";\n\nconst DEFAULT_KILL_GRACE_MS = 5_000;\n\n/**\n * Spawn-based ProcessRunner implementation.\n *\n * Behavior:\n * - `shell: false` and `detached: false`. The process group is not\n * detached, but the OS does not guarantee the child is reaped when\n * the parent terminates abruptly; callers handle SIGINT/SIGTERM/exit\n * hooks themselves.\n * - `capture: \"buffer\"` (default): `stdio: ['pipe', 'pipe', 'pipe']`,\n * stdout / stderr are decoded as UTF-8 and accumulated as full\n * strings (no streaming callbacks).\n * - `capture: \"none\"`: `stdio: ['inherit', 'inherit', 'inherit']`, the\n * child writes directly to the parent terminal in real time and\n * `RunResult.stdout` / `stderr` are empty strings. `stdin` is\n * incompatible with this mode (the child has no writable stdin pipe)\n * and the combination is rejected before spawn.\n * - `timeout_ms` and `AbortSignal` both trigger a two-stage kill:\n * `SIGTERM`, then `SIGKILL` after `DEFAULT_KILL_GRACE_MS` (5_000 ms).\n * - A non-zero `exit_code` does not throw; it is returned via\n * `RunResult`. Spawn-time errors throw with a pathless message and\n * the original error attached as `cause`.\n *\n * Error message contract: messages never include `cwd` or absolute\n * command paths. The original errno (and any nested wrapping) is\n * preserved on `Error.cause`, allowing callers to classify with\n * `findErrorCode` when needed.\n */\nexport class ChildProcessRunner implements ProcessRunner {\n async run(command: string, args: readonly string[], options: RunOptions): Promise<RunResult> {\n validateOptions(options);\n\n if (options.signal?.aborted) {\n throw new Error(\"Process aborted before spawn\", {\n cause: options.signal.reason,\n });\n }\n\n // Freeze the invocation snapshot at spawn time so the eventual RunResult\n // reflects the call as it was issued, even if the caller mutates `args`\n // or `options` afterward.\n const snapshotCommand = command;\n const snapshotArgs: readonly string[] = [...args];\n const snapshotCwd = options.cwd;\n const captureMode = options.capture ?? \"buffer\";\n\n const started_at = new Date();\n\n let child: ChildProcess;\n try {\n child = spawn(snapshotCommand, [...snapshotArgs], {\n cwd: snapshotCwd,\n env: options.env ?? process.env,\n stdio:\n captureMode === \"none\" ? [\"inherit\", \"inherit\", \"inherit\"] : [\"pipe\", \"pipe\", \"pipe\"],\n shell: false,\n detached: false,\n });\n } catch (error: unknown) {\n throw classifySpawnError(error);\n }\n\n // Notify caller that the child exists so they can wire parent-side\n // cleanup (e.g. an `exit` hook). The runner ignores any throw from\n // the callback; the caller is responsible for keeping it side-effect\n // safe.\n if (options.onSpawn) {\n try {\n options.onSpawn(child);\n } catch {\n // intentional: do not let onSpawn failures abort the run.\n }\n }\n\n let timeoutTimer: NodeJS.Timeout | null = null;\n let killTimer: NodeJS.Timeout | null = null;\n let killed = false;\n let settled = false;\n\n const triggerKill = (): void => {\n if (killed || child.exitCode !== null) return;\n killed = true;\n child.kill(\"SIGTERM\");\n killTimer = setTimeout(() => {\n if (child.exitCode === null) {\n child.kill(\"SIGKILL\");\n }\n }, DEFAULT_KILL_GRACE_MS);\n };\n\n // Attach the abort listener immediately, then re-check `aborted` to\n // close the window between spawn() returning and addEventListener.\n const onAbort = (): void => {\n triggerKill();\n };\n options.signal?.addEventListener(\"abort\", onAbort);\n if (options.signal?.aborted) {\n triggerKill();\n }\n\n let stdout = \"\";\n let stderr = \"\";\n if (captureMode === \"buffer\") {\n // stdio is ['pipe', 'pipe', 'pipe'] so stdout/stderr/stdin are non-null.\n child.stdout?.setEncoding(\"utf8\");\n child.stderr?.setEncoding(\"utf8\");\n child.stdout?.on(\"data\", (chunk: string) => {\n stdout += chunk;\n });\n child.stderr?.on(\"data\", (chunk: string) => {\n stderr += chunk;\n });\n\n if (options.stdin !== undefined) {\n child.stdin?.end(options.stdin);\n } else {\n child.stdin?.end();\n }\n }\n // capture: \"none\" leaves stdio inherited; stdout/stderr remain \"\".\n\n if (options.timeout_ms !== undefined) {\n timeoutTimer = setTimeout(triggerKill, options.timeout_ms);\n }\n\n const cleanup = (): void => {\n if (timeoutTimer !== null) clearTimeout(timeoutTimer);\n if (killTimer !== null) clearTimeout(killTimer);\n options.signal?.removeEventListener(\"abort\", onAbort);\n };\n\n return new Promise<RunResult>((resolve, reject) => {\n child.once(\"error\", (error: Error) => {\n if (settled) return;\n settled = true;\n cleanup();\n reject(classifySpawnError(error));\n });\n child.once(\"close\", (code: number | null, signal: NodeJS.Signals | null) => {\n if (settled) return;\n settled = true;\n cleanup();\n const ended_at = new Date();\n resolve({\n command: snapshotCommand,\n args: snapshotArgs,\n cwd: snapshotCwd,\n exit_code: code,\n signal,\n stdout,\n stderr,\n started_at: started_at.toISOString(),\n ended_at: ended_at.toISOString(),\n duration_ms: ended_at.getTime() - started_at.getTime(),\n pid: child.pid ?? null,\n });\n });\n });\n }\n}\n\nfunction validateOptions(options: RunOptions): void {\n if (\n options.timeout_ms !== undefined &&\n (!Number.isFinite(options.timeout_ms) || options.timeout_ms <= 0)\n ) {\n throw new Error(\"Invalid timeout_ms\");\n }\n if (options.capture === \"none\" && options.stdin !== undefined) {\n throw new Error('Combination of capture: \"none\" and stdin is not supported');\n }\n}\n\nfunction classifySpawnError(error: unknown): Error {\n if (findErrorCode(error, \"ENOENT\")) {\n return new Error(\"Command not found\", { cause: error });\n }\n return new Error(\"Failed to spawn child process\", { cause: error });\n}\n","import { z } from \"zod\";\nimport { ApprovalSchema } from \"./approval.schema.js\";\nimport { EventSchema } from \"./event.schema.js\";\nimport { ManifestSchema } from \"./manifest.schema.js\";\nimport { SessionSchema } from \"./session.schema.js\";\nimport { SessionImportPayloadSchema } from \"./session-import.schema.js\";\nimport { StatusSchema } from \"./status.schema.js\";\nimport { TaskSchema } from \"./task.schema.js\";\nimport { TaskIndexSchema } from \"./task-index.schema.js\";\n\n/**\n * Schema version of the on-disk Basou v0.1 formats these JSON Schemas describe.\n * It tracks {@link SchemaVersionSchema} (the `schema_version` field), NOT the\n * npm package version, so the `$id` URLs stay stable while the package moves.\n */\nexport const JSON_SCHEMA_VERSION = \"0.1.0\";\n\n/** Base of every emitted schema's `$id`. The URL is a stable identifier; it\n * need not resolve (serving the schemas on basou.dev is a separate concern). */\nconst ID_BASE = `https://basou.dev/schemas/${JSON_SCHEMA_VERSION}`;\n\n/** JSON Schema draft the artifacts target (what `z.toJSONSchema` emits). */\nconst JSON_SCHEMA_DIALECT = \"https://json-schema.org/draft/2020-12/schema\";\n\n/**\n * The on-disk Basou documents that get a published JSON Schema, keyed by the\n * artifact basename (`<name>.schema.json`). Each entry maps a `.basou/` file\n * format to the Zod schema that is its single source of truth.\n */\nconst DOCUMENTS: ReadonlyArray<{\n name: string;\n schema: z.ZodType;\n title: string;\n description: string;\n}> = [\n {\n name: \"manifest\",\n schema: ManifestSchema,\n title: \"Basou Manifest\",\n description: \"The `.basou/manifest.yaml` workspace manifest.\",\n },\n {\n name: \"session\",\n schema: SessionSchema,\n title: \"Basou Session\",\n description: \"A `.basou/sessions/<id>/session.yaml` session record.\",\n },\n {\n name: \"event\",\n schema: EventSchema,\n title: \"Basou Event\",\n description:\n \"One line of a `.basou/sessions/<id>/events.jsonl` stream (a discriminated union over the event `type`).\",\n },\n {\n name: \"task\",\n schema: TaskSchema,\n title: \"Basou Task\",\n description: \"The YAML front matter of a `.basou/tasks/<id>.md` task document.\",\n },\n {\n name: \"approval\",\n schema: ApprovalSchema,\n title: \"Basou Approval\",\n description: \"A `.basou/approvals/{pending,resolved}/<id>.yaml` approval record.\",\n },\n {\n name: \"status\",\n schema: StatusSchema,\n title: \"Basou Status\",\n description: \"The `.basou/status.json` workspace status snapshot.\",\n },\n {\n name: \"task-index\",\n schema: TaskIndexSchema,\n title: \"Basou Task Index\",\n description: \"The `.basou/tasks/index.json` task lookup index.\",\n },\n {\n name: \"session-import\",\n schema: SessionImportPayloadSchema,\n title: \"Basou Session Import Payload\",\n description: \"The portable session payload consumed by `basou session import`.\",\n },\n];\n\n/** One emitted JSON Schema artifact. */\nexport type JsonSchemaArtifact = {\n /** Artifact basename without extension (e.g. `session`). */\n name: string;\n /** The JSON Schema document (draft 2020-12). */\n schema: Record<string, unknown>;\n};\n\n/**\n * Build the published JSON Schema artifacts from the canonical Zod schemas.\n *\n * Pure: no disk or environment access. Each artifact is `z.toJSONSchema` of the\n * document schema, re-headed with a stable `$id` / `title` / `description` (the\n * draft `$schema` from zod is preserved). This is the single generator used by\n * both the `gen:schemas` script (which writes the committed files) and the\n * drift-guard test (which asserts the committed files still match), so the two\n * can never disagree.\n *\n * Generated in `io: \"input\"` mode so the artifacts describe what a consumer\n * AUTHORS on disk, not zod's parsed output: a field with a `.default()` (e.g.\n * `events_log`) stays optional rather than `required`, and a non-strict object\n * omits `additionalProperties: false` so additive fields are allowed. Only the\n * `.strict()` event variants (e.g. `adapter_output`) keep\n * `additionalProperties: false`, preserving their reject-unknown contract.\n *\n * Note: prefixed-id fields carry a representable `pattern` (see\n * `createPrefixedIdSchema`); other refinement-only constraints are not\n * expressible in JSON Schema and are intentionally omitted.\n */\nexport function buildJsonSchemas(): JsonSchemaArtifact[] {\n return DOCUMENTS.map((doc) => {\n const generated = z.toJSONSchema(doc.schema, { io: \"input\" }) as Record<string, unknown>;\n const { $schema, ...rest } = generated;\n const schema: Record<string, unknown> = {\n $schema: typeof $schema === \"string\" ? $schema : JSON_SCHEMA_DIALECT,\n $id: `${ID_BASE}/${doc.name}.schema.json`,\n title: doc.title,\n description: doc.description,\n ...rest,\n };\n return { name: doc.name, schema };\n });\n}\n\n/** Serialize an artifact's schema exactly as the committed file stores it\n * (2-space indent, trailing newline) so the generator and the drift-guard test\n * compare byte-for-byte. */\nexport function serializeJsonSchema(schema: Record<string, unknown>): string {\n return `${JSON.stringify(schema, null, 2)}\\n`;\n}\n","import { z } from \"zod\";\nimport { EventSchema } from \"./event.schema.js\";\nimport {\n SessionIntegritySchema,\n SessionMetricsSchema,\n SessionSourceKindSchema,\n SessionStatusSchema,\n} from \"./session.schema.js\";\nimport {\n IsoTimestampSchema,\n SessionIdSchema,\n TaskIdSchema,\n WorkspaceIdSchema,\n} from \"./shared.schema.js\";\n\n// Independent copy of SessionInnerSchema for import payloads. The differences\n// from session.schema.ts are deliberate:\n// - `id` is `SessionIdSchema.optional()` so format is validated when present\n// but the orchestrator discards it and assigns a fresh ULID.\n// - `status` / `source.kind` are validated against the canonical enums but\n// overwritten by the orchestrator (status -> \"imported\", source.kind\n// retained from input).\n// - `events_log` is plain `z.string().optional()`; the orchestrator forces\n// \"events.jsonl\" to block path traversal.\n// - `.strict()` rejects unknown session-level keys at parse time.\n//\n// Events strictness follows EventSchema as authored: `adapter_output` is\n// `.strict()`, the other 14 variants are permissive, and\n// `approval_requested.action` is `.passthrough()`. This keeps the spec's\n// additive-event-fields rule and round-trip imports of post-v0.1 events\n// compatible. A blanket strict wrap for every variant is deferred.\n//\n// `schema_version` at the top level is `z.string()` rather than the\n// `SchemaVersionSchema = z.literal(\"0.1.0\")` literal. The strict reject for\n// unsupported versions emits a dedicated `Unsupported import schema_version`\n// message from the orchestrator; a literal here would short-circuit the\n// branch and turn every mismatched version into the generic\n// `Invalid import payload`.\nexport const SessionInnerImportSchema = z\n .object({\n id: SessionIdSchema.optional(),\n label: z.string().optional(),\n task_id: TaskIdSchema.nullable().optional(),\n workspace_id: WorkspaceIdSchema,\n source: z.object({\n kind: SessionSourceKindSchema,\n version: z.literal(\"0.1.0\"),\n // Source-tool-native id (e.g. Claude Code session UUID), retained so\n // re-imports of the same source can be deduplicated.\n external_id: z.string().optional(),\n // Byte size of the source native log at import time. Declared here too\n // (not only in session.schema.ts) because this inner `source` object is\n // a plain z.object: zod strips keys it does not declare, so a field\n // absent here would be dropped from the parsed payload before persist\n // and the size could never be stored.\n source_size_bytes: z.number().int().nonnegative().optional(),\n }),\n started_at: IsoTimestampSchema,\n ended_at: IsoTimestampSchema.optional(),\n status: SessionStatusSchema,\n working_directory: z.string().min(1),\n invocation: z.object({\n command: z.string().min(1),\n args: z.array(z.string()),\n exit_code: z.number().int().nullable(),\n }),\n related_files: z.array(z.string()).default([]),\n events_log: z.string().optional(),\n summary: z.string().nullable().optional(),\n metrics: SessionMetricsSchema.optional(),\n // Accepted so a payload assembled from an on-disk chained session.yaml\n // round-trips, and DISCARDED by the importer (buildSessionRecord never\n // copies it): the integrity anchor is computed at write time, never\n // imported. Mirrors the accept-and-discard of `prev_hash` on events.\n integrity: SessionIntegritySchema.optional(),\n })\n .strict();\n\n/**\n * Schema for the round-trip JSON payload accepted by `basou session import\n * --format json`. The top level is `.strict()`; unknown keys at the outer\n * envelope are rejected.\n */\nexport const SessionImportPayloadSchema = z\n .object({\n schema_version: z.string(),\n session: SessionInnerImportSchema,\n events: z.array(EventSchema),\n })\n .strict();\n\n/** Inferred runtime type for {@link SessionImportPayloadSchema}. */\nexport type SessionImportPayload = z.infer<typeof SessionImportPayloadSchema>;\n/** Inferred runtime type for {@link SessionInnerImportSchema}. */\nexport type SessionInnerImportInput = z.infer<typeof SessionInnerImportSchema>;\n","import { lstat, mkdir } from \"node:fs/promises\";\nimport { join } from \"node:path\";\n\n/**\n * Absolute paths to the standard `.basou/` directory layout, derived from a\n * given repository root. The shape mirrors the canonical `.basou/` tree\n * (see `docs/spec/workspace.md`). `root` is the `.basou/` directory itself\n * (i.e. `repositoryRoot/.basou`).\n *\n * `files` exposes the well-known top-level files inside `.basou/`. Each path\n * is computed but not created — they are written by their respective\n * subsystems (e.g. `writeManifest` for `manifest.yaml`).\n *\n * All fields are deeply readonly; consumers must not mutate the returned\n * object.\n */\nexport type BasouPaths = {\n readonly root: string;\n readonly sessions: string;\n readonly tasks: string;\n readonly approvals: {\n readonly pending: string;\n readonly resolved: string;\n };\n readonly locks: string;\n readonly logs: string;\n readonly raw: string;\n readonly tmp: string;\n readonly files: {\n readonly manifest: string;\n readonly status: string;\n readonly handoff: string;\n readonly decisions: string;\n readonly orientation: string;\n };\n};\n\n/**\n * Compute absolute paths to the standard `.basou/` directory layout under\n * `repositoryRoot`. Pure: performs no I/O and is safe to call before the\n * directory exists.\n *\n * @param repositoryRoot Absolute path to the git repository root (the\n * parent directory of `.basou/`). Caller is responsible for resolving\n * `process.cwd()` or running `git rev-parse --show-toplevel` upstream;\n * this function does not validate that the path exists or is a git\n * repository.\n */\nexport function basouPaths(repositoryRoot: string): BasouPaths {\n const root = join(repositoryRoot, \".basou\");\n const approvalsBase = join(root, \"approvals\");\n return {\n root,\n sessions: join(root, \"sessions\"),\n tasks: join(root, \"tasks\"),\n approvals: {\n pending: join(approvalsBase, \"pending\"),\n resolved: join(approvalsBase, \"resolved\"),\n },\n locks: join(root, \"locks\"),\n logs: join(root, \"logs\"),\n raw: join(root, \"raw\"),\n tmp: join(root, \"tmp\"),\n files: {\n manifest: join(root, \"manifest.yaml\"),\n status: join(root, \"status.json\"),\n handoff: join(root, \"handoff.md\"),\n decisions: join(root, \"decisions.md\"),\n orientation: join(root, \"orientation.md\"),\n },\n };\n}\n\n// Labels for sub-paths inside `.basou/`. Used in pathless error messages so\n// the surface area for absolute-path leakage is bounded by this map.\nconst PATH_LABELS = {\n sessions: \".basou/sessions\",\n tasks: \".basou/tasks\",\n approvalsPending: \".basou/approvals/pending\",\n approvalsResolved: \".basou/approvals/resolved\",\n locks: \".basou/locks\",\n logs: \".basou/logs\",\n raw: \".basou/raw\",\n tmp: \".basou/tmp\",\n} as const;\n\n/**\n * Create the standard `.basou/` directory layout under `repositoryRoot`.\n *\n * Idempotent: a no-op on an already-initialized layout. Returns the resolved\n * {@link BasouPaths} so callers can immediately use them.\n *\n * Throws if `repositoryRoot/.basou` (or any required subdirectory) exists\n * but is not a directory, or if filesystem permissions prevent creation.\n * All thrown error messages are pathless; the original native error is\n * attached as `cause` for diagnostics.\n *\n * @param repositoryRoot Absolute path to the git repository root. See\n * {@link basouPaths} for the contract on this parameter.\n */\nexport async function ensureBasouDirectory(repositoryRoot: string): Promise<BasouPaths> {\n const paths = basouPaths(repositoryRoot);\n\n // lstat (not stat) so that a symlink at `.basou` is detected as a symlink\n // and rejected; following the link could place Basou state outside the\n // git repository root, violating the workspace-root invariant.\n let existing: Awaited<ReturnType<typeof lstat>> | undefined;\n try {\n existing = await lstat(paths.root);\n } catch (error: unknown) {\n if (!hasErrorCode(error) || error.code !== \"ENOENT\") {\n throw new Error(\"Failed to inspect .basou directory\", { cause: error });\n }\n }\n if (existing !== undefined && !existing.isDirectory()) {\n throw new Error(\"Basou root .basou exists but is not a directory\");\n }\n\n await Promise.all([\n mkdirLabeled(paths.sessions, PATH_LABELS.sessions),\n mkdirLabeled(paths.tasks, PATH_LABELS.tasks),\n mkdirLabeled(paths.approvals.pending, PATH_LABELS.approvalsPending),\n mkdirLabeled(paths.approvals.resolved, PATH_LABELS.approvalsResolved),\n mkdirLabeled(paths.locks, PATH_LABELS.locks),\n mkdirLabeled(paths.logs, PATH_LABELS.logs),\n mkdirLabeled(paths.raw, PATH_LABELS.raw),\n mkdirLabeled(paths.tmp, PATH_LABELS.tmp),\n ]);\n\n return paths;\n}\n\nasync function mkdirLabeled(target: string, label: string): Promise<void> {\n try {\n await mkdir(target, { recursive: true });\n } catch (error: unknown) {\n if (hasErrorCode(error) && (error.code === \"ENOTDIR\" || error.code === \"EEXIST\")) {\n throw new Error(`${label} exists but is not a directory`, { cause: error });\n }\n throw new Error(`Failed to create ${label}`, { cause: error });\n }\n}\n\nfunction hasErrorCode(error: unknown): error is Error & { code: string } {\n if (!(error instanceof Error)) return false;\n const codeProp = (error as unknown as Record<string, unknown>).code;\n return typeof codeProp === \"string\";\n}\n","import { readFile, writeFile } from \"node:fs/promises\";\nimport { join } from \"node:path\";\n\nconst MARKER = \"# Basou - default ignore\";\n\n// Recommended .gitignore block (ignore + commit). The test asserts an\n// exact match against this spec string literal to detect spec drift.\nconst BASOU_GITIGNORE_BLOCK =\n \"# Basou - default ignore\\n\" +\n \".basou/logs/\\n\" +\n \".basou/raw/\\n\" +\n \".basou/tmp/\\n\" +\n \".basou/locks/\\n\" +\n \".basou/status.json\\n\" +\n \".basou/orientation.md\\n\" +\n \".basou/sessions/*/events.jsonl\\n\" +\n \".basou/sessions/*/artifacts/\\n\" +\n \".basou/approvals/pending/\\n\" +\n \".basou/approvals/resolved/\\n\" +\n \"\\n\" +\n \"# Basou - default commit\\n\" +\n \"# .basou/manifest.yaml\\n\" +\n \"# .basou/handoff.md\\n\" +\n \"# .basou/decisions.md\\n\" +\n \"# .basou/tasks/\\n\" +\n \"# .basou/sessions/*/session.yaml\\n\" +\n \"# .basou/sessions/*/transcript.md\\n\" +\n \"# .basou/sessions/*/changed-files.json\\n\";\n\n// Local-only `.basou/` exclude (opt-in via `basou init --local-only`). The trail\n// is never committed — it is personal/local state, regenerable by re-importing\n// from the agents' own logs. Shares the marker line so re-running init is still\n// idempotent. The test asserts an exact match to detect spec drift.\nconst BASOU_GITIGNORE_BLOCK_LOCAL_ONLY =\n \"# Basou - default ignore\\n\" +\n \"# Local-only: basou's trail is never committed (personal/local state,\\n\" +\n \"# regenerable by re-importing from the agents' own logs). Recommended for\\n\" +\n \"# monitored repos and any workspace kept out of version control.\\n\" +\n \".basou/\\n\";\n\nexport type AppendBasouGitignoreResult = {\n /** True if the block was appended (or the file was newly created). */\n readonly appended: boolean;\n};\n\n/** Options for {@link appendBasouGitignore}. */\nexport type AppendBasouGitignoreOptions = {\n /** Write a `.basou/` full-exclude block instead of the default ignore+commit block. */\n readonly localOnly?: boolean;\n};\n\n/**\n * Append Basou's default `.gitignore` block to `repositoryRoot/.gitignore`.\n *\n * The block contents are derived from the Basou v0.1 specification (the\n * standard ignore + commit recommendations). Callers must pass an absolute\n * path to a Git repository root.\n *\n * With `options.localOnly`, a `.basou/` full-exclude block is written instead\n * of the default ignore+commit block (the trail is kept out of version\n * control). The default (no options) is unchanged.\n *\n * Behavior:\n * - If `.gitignore` does not exist, it is created with the chosen Basou block.\n * - If a `# Basou - default ignore` marker OR a standalone `.basou/` exclude\n * line is already present, the file is left untouched and `appended: false`\n * is returned (idempotent across both modes).\n * - If `.gitignore` is a symlink, the link is followed and the target file\n * is updated. Symlinks are not rejected.\n *\n * On I/O failure throws Error with a pathless message\n * (`Failed to read .gitignore` / `Failed to write .gitignore`) and the\n * original native error attached as `cause`.\n */\nexport async function appendBasouGitignore(\n repositoryRoot: string,\n options: AppendBasouGitignoreOptions = {},\n): Promise<AppendBasouGitignoreResult> {\n const gitignorePath = join(repositoryRoot, \".gitignore\");\n\n let body: string;\n let existed: boolean;\n try {\n body = await readFile(gitignorePath, \"utf8\");\n existed = true;\n } catch (error: unknown) {\n if (hasErrorCode(error) && error.code === \"ENOENT\") {\n body = \"\";\n existed = false;\n } else {\n throw new Error(\"Failed to read .gitignore\", { cause: error });\n }\n }\n\n if (existed && hasBasouGitignore(body)) {\n return { appended: false };\n }\n\n const block =\n options.localOnly === true ? BASOU_GITIGNORE_BLOCK_LOCAL_ONLY : BASOU_GITIGNORE_BLOCK;\n const next = composeNextBody(body, block);\n try {\n await writeFile(gitignorePath, next, { encoding: \"utf8\" });\n } catch (error: unknown) {\n throw new Error(\"Failed to write .gitignore\", { cause: error });\n }\n return { appended: true };\n}\n\n/** True if the file already carries a Basou block (marker) or a `.basou/` full-exclude line. */\nfunction hasBasouGitignore(body: string): boolean {\n for (const rawLine of body.split(\"\\n\")) {\n const line = rawLine.trimEnd();\n if (line.startsWith(MARKER)) return true;\n if (line === \".basou/\" || line === \"/.basou/\") return true;\n }\n return false;\n}\n\nfunction composeNextBody(existing: string, block: string): string {\n if (existing.length === 0) return block;\n const normalized = existing.endsWith(\"\\n\") ? existing : `${existing}\\n`;\n return `${normalized}\\n${block}`;\n}\n\nfunction hasErrorCode(error: unknown): error is Error & { code: string } {\n if (!(error instanceof Error)) return false;\n return typeof (error as unknown as Record<string, unknown>).code === \"string\";\n}\n","import { readFile } from \"node:fs/promises\";\nimport { atomicReplace } from \"./atomic.js\";\n\n/** Marker line that begins the auto-generated region. */\nexport const GENERATED_START = \"<!-- BASOU:GENERATED:START -->\";\n/** Marker line that ends the auto-generated region. */\nexport const GENERATED_END = \"<!-- BASOU:GENERATED:END -->\";\n\n/** Marker line that begins a managed protocol block in a foreign instruction file. */\nexport const PROTOCOL_START = \"<!-- BASOU:PROTOCOLS:START -->\";\n/** Marker line that ends a managed protocol block. */\nexport const PROTOCOL_END = \"<!-- BASOU:PROTOCOLS:END -->\";\n\n/** A start/end marker pair. Both lines are matched whole-line, exact. */\nexport type Markers = { start: string; end: string };\n\n/** Default marker pair: the BASOU:GENERATED region used by handoff/decisions/orient. */\nconst DEFAULT_MARKERS: Markers = { start: GENERATED_START, end: GENERATED_END };\n\n/**\n * Result of parsing a markdown body for the BASOU:GENERATED marker region.\n *\n * The spec mandates strict line-level matching (see\n * `docs/spec/generated-markdown.md#102-marker-convention`): a marker is\n * only recognized when an entire line is exactly the marker string.\n * Leading/trailing whitespace and comment compression are treated as legacy\n * formats (`no_markers`) so that re-generation refuses to silently overwrite a\n * mismatched manual edit. A leading UTF-8 BOM is the one exception: it is\n * tolerated (stripped for matching, re-prepended on render) so a BOM-prefixed\n * file round-trips instead of duplicating the block.\n */\nexport type MarkerSection =\n | { kind: \"ok\"; before: string; generated: string; after: string }\n | { kind: \"no_markers\" }\n | { kind: \"missing_start\" }\n | { kind: \"missing_end\" }\n | { kind: \"multiple_pairs\" }\n | { kind: \"wrong_order\" };\n\n/**\n * Read a markdown file as UTF-8 text. Returns `null` when the file does not\n * exist; throws `Error(\"Failed to read markdown file\", { cause })` for other\n * I/O failures (pathless contract — never embed the absolute path in the\n * thrown `message`).\n */\nexport async function readMarkdownFile(filePath: string): Promise<string | null> {\n try {\n return await readFile(filePath, \"utf8\");\n } catch (error: unknown) {\n if (hasErrorCode(error) && error.code === \"ENOENT\") return null;\n throw new Error(\"Failed to read markdown file\", { cause: error });\n }\n}\n\n/**\n * Atomically write a markdown body via {@link atomicReplace}. The shared\n * helper handles the tmp-file + rename sequence, `wx` collision guard, and\n * best-effort tmp cleanup on failure.\n *\n * On any failure the original error is re-thrown as\n * `Error(\"Failed to write markdown file\", { cause })` (pathless contract).\n */\nexport async function writeMarkdownFile(filePath: string, body: string): Promise<void> {\n try {\n await atomicReplace(filePath, body);\n } catch (error: unknown) {\n throw new Error(\"Failed to write markdown file\", { cause: error });\n }\n}\n\n/**\n * Parse a markdown body and identify the BASOU:GENERATED marker region.\n *\n * Returns one of six `kind` discriminants:\n * - `ok`: exactly one START line followed by exactly one END line in the\n * correct order. `before` / `generated` / `after` slice the original\n * text by character offsets so CRLF / LF are preserved verbatim outside\n * the marker region.\n * - `no_markers`: both START and END absent (legacy file / fresh write).\n * - `missing_start` / `missing_end`: exactly one of the pair is present.\n * - `multiple_pairs`: more than one START or END line.\n * - `wrong_order`: END appears before START.\n *\n * Matching is strict: leading/trailing whitespace and comment compression\n * (`<!--BASOU:...-->`) bypass the marker and are treated as legacy content. A\n * leading UTF-8 BOM is the exception: it is stripped before matching and\n * re-prepended to `before`, so a marker on the first line of a BOM-prefixed\n * file still matches and the file round-trips.\n */\nexport function parseMarkers(content: string, markers: Markers = DEFAULT_MARKERS): MarkerSection {\n // Tolerate a leading UTF-8 BOM: strip it for line matching and offset math,\n // then re-prepend it to `before` so a BOM-prefixed file round-trips. Without\n // this, a marker on line 0 of a BOM file would fail the exact-line compare\n // and the block would be duplicated on the next render.\n const bom = content.charCodeAt(0) === 0xfeff ? \"\\uFEFF\" : \"\";\n const body = bom === \"\" ? content : content.slice(1);\n\n // Split on either CRLF or LF so the line count is consistent regardless of\n // the file's line ending. The reconstruction step below slices the original\n // string by character offsets to preserve the actual line endings outside\n // the generated region.\n const lines = body.split(/\\r?\\n/);\n const startLines: number[] = [];\n const endLines: number[] = [];\n for (let i = 0; i < lines.length; i++) {\n if (lines[i] === markers.start) startLines.push(i);\n else if (lines[i] === markers.end) endLines.push(i);\n }\n if (startLines.length === 0 && endLines.length === 0) return { kind: \"no_markers\" };\n if (startLines.length === 0) return { kind: \"missing_start\" };\n if (endLines.length === 0) return { kind: \"missing_end\" };\n if (startLines.length >= 2 || endLines.length >= 2) return { kind: \"multiple_pairs\" };\n const startLineIdx = startLines[0] as number;\n const endLineIdx = endLines[0] as number;\n if (endLineIdx < startLineIdx) return { kind: \"wrong_order\" };\n\n // Walk the (BOM-stripped) string to find byte offsets of the marker lines.\n // This preserves CRLF vs LF in the surrounding text — splitting and\n // re-joining would normalize the line endings.\n const startOffset = lineStartOffset(body, startLineIdx);\n const endLineStart = lineStartOffset(body, endLineIdx);\n const startLineEnd = startOffset + markers.start.length;\n const endLineEnd = endLineStart + markers.end.length;\n\n const before = bom + body.slice(0, startOffset);\n // The generated region is everything between the two marker lines,\n // exclusive of the marker lines themselves but including the newline after\n // START and excluding the newline before END (so re-render can plug in\n // its own body without doubling separators).\n const afterStartNewline = skipOneNewline(body, startLineEnd);\n const beforeEndNewline = trimOneNewline(body, endLineStart);\n const generated = body.slice(afterStartNewline, beforeEndNewline);\n const after = body.slice(endLineEnd);\n return { kind: \"ok\", before, generated, after };\n}\n\n/**\n * Build the final markdown body by replacing the BASOU:GENERATED region.\n *\n * - `existing === null` (no file yet): return `<START>\\n<generated>\\n<END>\\n`.\n * - existing parses to `ok`: replace the marked region and keep everything\n * before START and after END untouched (preserving manual additions).\n * - any other parse result: throw a pathless error referencing `fileLabel`.\n *\n * The caller passes `fileLabel` (e.g. `\"handoff.md\"` or `\"decisions.md\"`)\n * so the error message is informative without leaking an absolute path.\n */\nexport function renderWithMarkers(\n existing: string | null,\n generated: string,\n fileLabel: string,\n markers: Markers = DEFAULT_MARKERS,\n): string {\n const normalized = generated.endsWith(\"\\n\") ? generated : `${generated}\\n`;\n if (existing === null) {\n return `${markers.start}\\n${normalized}${markers.end}\\n`;\n }\n const section = parseMarkers(existing, markers);\n switch (section.kind) {\n case \"ok\":\n return `${section.before}${markers.start}\\n${normalized}${markers.end}${section.after}`;\n case \"no_markers\":\n throw new Error(`Markers missing in ${fileLabel}`);\n case \"missing_start\":\n case \"missing_end\":\n case \"multiple_pairs\":\n case \"wrong_order\":\n throw new Error(`Markers mismatched in ${fileLabel}`);\n }\n}\n\n/**\n * Remove a marker region from `existing`, returning the body without the block.\n *\n * - `no_markers`: returns `existing` unchanged (nothing to remove).\n * - `ok`: drops both marker lines and the generated region, collapsing the\n * single newline that terminated the END marker line so no stray blank line\n * is left behind.\n * - any other parse result: throws a pathless error referencing `fileLabel`\n * (mismatched markers must not be silently rewritten).\n */\nexport function removeMarkerSection(\n existing: string,\n fileLabel: string,\n markers: Markers = DEFAULT_MARKERS,\n): string {\n const section = parseMarkers(existing, markers);\n switch (section.kind) {\n case \"no_markers\":\n return existing;\n case \"ok\": {\n const after = section.after.replace(/^\\r?\\n/, \"\");\n return section.before + after;\n }\n case \"missing_start\":\n case \"missing_end\":\n case \"multiple_pairs\":\n case \"wrong_order\":\n throw new Error(`Markers mismatched in ${fileLabel}`);\n }\n}\n\n/** Character offset of the first character of `lineIdx` (0-based). */\nfunction lineStartOffset(content: string, lineIdx: number): number {\n if (lineIdx === 0) return 0;\n let offset = 0;\n let line = 0;\n while (offset < content.length && line < lineIdx) {\n const ch = content[offset];\n if (ch === \"\\n\") {\n line += 1;\n offset += 1;\n } else if (ch === \"\\r\") {\n // CR or CRLF both count as a line terminator.\n offset += 1;\n if (content[offset] === \"\\n\") offset += 1;\n line += 1;\n } else {\n offset += 1;\n }\n }\n return offset;\n}\n\n/** Advance past one trailing `\\n` or `\\r\\n` if present. */\nfunction skipOneNewline(content: string, offset: number): number {\n if (content[offset] === \"\\r\" && content[offset + 1] === \"\\n\") return offset + 2;\n if (content[offset] === \"\\n\") return offset + 1;\n return offset;\n}\n\n/** Walk back past one leading `\\n` or `\\r\\n` if present. */\nfunction trimOneNewline(content: string, offset: number): number {\n if (offset >= 2 && content[offset - 2] === \"\\r\" && content[offset - 1] === \"\\n\")\n return offset - 2;\n if (offset >= 1 && content[offset - 1] === \"\\n\") return offset - 1;\n return offset;\n}\n\nfunction hasErrorCode(error: unknown): error is Error & { code: string } {\n if (!(error instanceof Error)) return false;\n const codeProp = (error as unknown as Record<string, unknown>).code;\n return typeof codeProp === \"string\";\n}\n","import { mkdir, readFile, rm } from \"node:fs/promises\";\nimport { homedir } from \"node:os\";\nimport { join } from \"node:path\";\nimport { chainRawJsonLines } from \"../events/chain.js\";\nimport { readAllEvents } from \"../events/event-replay.js\";\nimport { type BulkChainResult, writeEventsBulk } from \"../events/event-writer.js\";\nimport { verifyEventsChain } from \"../events/verify.js\";\nimport { type PrefixedId, prefixedUlid } from \"../ids/ulid.js\";\nimport { findErrorCode } from \"../lib/error-codes.js\";\nimport { sanitizeRelatedFiles, sanitizeWorkingDirectory } from \"../lib/path-sanitizer.js\";\nimport { type Event, EventSchema } from \"../schemas/event.schema.js\";\nimport type { Manifest } from \"../schemas/manifest.schema.js\";\nimport type { Session, SessionSourceKind, SessionStatus } from \"../schemas/session.schema.js\";\nimport type {\n SessionImportPayload,\n SessionInnerImportInput,\n} from \"../schemas/session-import.schema.js\";\nimport { TaskIdSchema } from \"../schemas/shared.schema.js\";\nimport { atomicReplace } from \"./atomic.js\";\nimport type { BasouPaths } from \"./basou-dir.js\";\nimport { acquireLock } from \"./lockfile.js\";\nimport { readSessionYaml } from \"./sessions.js\";\nimport { enumerateTaskIds } from \"./tasks.js\";\nimport { linkYamlFile, overwriteYamlFile } from \"./yaml-store.js\";\n\n/**\n * Options for {@link importSessionFromJson}. All fields are optional.\n *\n * - `labelOverride` / `taskIdOverride` come from the CLI `--label` / `--task`\n * flags and win over the corresponding fields on the input payload.\n * - `dryRun` skips disk writes entirely and returns a preview result.\n */\nexport type ImportSessionOptions = {\n labelOverride?: string;\n taskIdOverride?: string;\n dryRun?: boolean;\n};\n\n/**\n * Result of a successful import. `finalStatus` is always the literal\n * `\"imported\"` (per the import-session lifecycle policy); `finalSourceKind`\n * mirrors the input's `session.source.kind` so round-trip imports preserve\n * provenance.\n *\n * `pathSanitizeReport` summarises how many path-shaped fields the importer\n * rewrote on the way in: `related_files[]` entries plus a single boolean\n * for `working_directory`. The CLI wrapper surfaces this as a one-line\n * stderr warning when the total is non-zero so the operator sees that\n * machine-private prefixes were stripped.\n */\nexport type ImportSessionResult = {\n sessionId: PrefixedId<\"ses\">;\n eventCount: number;\n finalStatus: SessionStatus;\n finalSourceKind: SessionSourceKind;\n pathSanitizeReport: {\n relatedFiles: number;\n workingDirectoryRewritten: boolean;\n };\n};\n\n/**\n * Import a round-trip JSON payload into `.basou/sessions/<new>/`. The caller\n * MUST validate the payload against {@link SessionImportPayloadSchema} first\n * and gate the `schema_version === \"0.1.0\"` literal check externally; this\n * function trusts both invariants.\n *\n * On success a fresh session ID is minted and a complete\n * `session.yaml` + `events.jsonl` pair is written atomically. On any post-\n * mkdir failure the session directory is removed best-effort so partial\n * imports do not leave `session_yaml_missing` half-states behind.\n *\n * Throws `Error` with one of the fixed messages enumerated by the import contract\n * §\"Error messages\" table; the original native error is attached as `cause`\n * for `--verbose` rendering.\n */\nexport async function importSessionFromJson(\n paths: BasouPaths,\n manifest: Manifest,\n payload: SessionImportPayload,\n options: ImportSessionOptions,\n): Promise<ImportSessionResult> {\n // Defense in depth: the CLI converter (parseTaskIdOverride) already gates\n // this, but a direct core API caller could still pass an arbitrary string.\n if (\n options.taskIdOverride !== undefined &&\n !TaskIdSchema.safeParse(options.taskIdOverride).success\n ) {\n throw new Error(`Invalid task_id: ${options.taskIdOverride}`);\n }\n\n // Reachability guard: rewriteEvents\n // preserves variant-specific task_id fields, so importing a session that\n // references a task absent from the local workspace would silently\n // install a dangling reference. Validate every task_id carrier:\n // task_created / task_status_changed / task_reconciled events plus the\n // effective session task_id (override-wins, matches buildSessionRecord\n // below).\n const effectiveSessionTaskId = options.taskIdOverride ?? payload.session.task_id ?? null;\n await assertImportedTaskReferencesAreReachable(paths, payload.events, effectiveSessionTaskId);\n\n const newSessionId = prefixedUlid(\"ses\");\n\n const rewrittenEvents = rewriteEvents(payload.events, newSessionId);\n assertChronologicalOrder(rewrittenEvents);\n\n const { record: sessionRecord, pathSanitizeReport } = buildSessionRecord(\n payload.session,\n manifest,\n newSessionId,\n options,\n );\n\n if (options.dryRun === true) {\n return {\n sessionId: newSessionId,\n eventCount: rewrittenEvents.length,\n finalStatus: \"imported\",\n finalSourceKind: sessionRecord.session.source.kind,\n pathSanitizeReport,\n };\n }\n\n // recursive: true lets a stripped-down workspace (manifest present but\n // `.basou/sessions` missing) recover instead of failing with ENOENT; ULID\n // collision on the new session dir itself is statistically impossible, so\n // the silent EEXIST on an existing directory is acceptable here. Concurrent\n // attempts to write the same session.yaml are caught by linkYamlFile below.\n const sessionDir = join(paths.sessions, newSessionId);\n try {\n await mkdir(sessionDir, { recursive: true });\n } catch (error: unknown) {\n throw new Error(\"Failed to create session directory\", { cause: error });\n }\n\n // Chained write: imported sessions are the tamper-evident corpus, so the\n // bulk write threads the per-line hash chain and returns the head anchor.\n let chainResult: BulkChainResult | null;\n try {\n chainResult = await writeEventsBulk(sessionDir, rewrittenEvents, { chain: true });\n } catch (error: unknown) {\n await rm(sessionDir, { recursive: true, force: true }).catch(() => undefined);\n throw error;\n }\n\n try {\n const sessionYamlPath = join(sessionDir, \"session.yaml\");\n await linkYamlFile(sessionYamlPath, withIntegrity(sessionRecord, chainResult));\n } catch (error: unknown) {\n await rm(sessionDir, { recursive: true, force: true }).catch(() => undefined);\n if (findErrorCode(error, \"EEXIST\")) {\n throw new Error(\"Session directory collision (retry the command)\", {\n cause: error,\n });\n }\n throw error;\n }\n\n return {\n sessionId: newSessionId,\n eventCount: rewrittenEvents.length,\n finalStatus: \"imported\",\n finalSourceKind: sessionRecord.session.source.kind,\n pathSanitizeReport,\n };\n}\n\n// Reachability guard: refuse any payload that\n// references task ids absent from the local workspace, across every carrier:\n// task_created / task_status_changed / task_reconciled events plus the\n// effective session task_id (= the override if supplied, otherwise the\n// imported session.yaml.task_id; matches buildSessionRecord's override-wins\n// semantics so we never reject on an id the final record will discard). The\n// fixed message is pathless-contract compliant; broken ids are not echoed\n// back so an adversarial payload cannot probe the local task namespace.\nasync function assertImportedTaskReferencesAreReachable(\n paths: BasouPaths,\n events: ReadonlyArray<Event>,\n effectiveSessionTaskId: string | null,\n): Promise<void> {\n const taskIdsToCheck = new Set<string>();\n for (const ev of events) {\n if (\n ev.type === \"task_created\" ||\n ev.type === \"task_status_changed\" ||\n ev.type === \"task_reconciled\" ||\n ev.type === \"task_linkage_refreshed\" ||\n ev.type === \"task_deleted\" ||\n ev.type === \"task_archived\"\n ) {\n taskIdsToCheck.add(ev.task_id);\n }\n }\n if (effectiveSessionTaskId !== null) {\n taskIdsToCheck.add(effectiveSessionTaskId);\n }\n if (taskIdsToCheck.size === 0) {\n // skip the tasks-dir scan when nothing references a task,\n // so imports that carry no task_id at all keep the original perf.\n return;\n }\n const knownTaskIds = new Set(await enumerateTaskIds(paths));\n for (const id of taskIdsToCheck) {\n if (!knownTaskIds.has(id)) {\n throw new Error(\"Imported session references unknown task_id\");\n }\n }\n}\n\n// Rewrite each event's `id` and `session_id` to brand-new values while\n// retaining every other field — including variant-specific cross-reference\n// IDs (approval_id, decision_id, task_id, file paths, raw_ref) — so that\n// chains like `approval_requested` -> `approval_approved` remain joinable on\n// the imported side. The events were already validated against EventSchema,\n// and prefixedUlid output satisfies EventIdSchema by construction, so the\n// rewritten events do not need to be re-parsed.\nfunction rewriteEvents(events: Event[], newSessionId: PrefixedId<\"ses\">): Event[] {\n return events.map((event) => ({\n ...event,\n id: prefixedUlid(\"evt\"),\n session_id: newSessionId,\n }));\n}\n\n// Enforce strict chronological order with same-ms duplicates allowed (`>=`).\n// Out-of-order events indicate an exporter bug or\n// hand-edited payload — refuse to silently sort.\nfunction assertChronologicalOrder(events: Event[]): void {\n for (let i = 1; i < events.length; i++) {\n const prevEvent = events[i - 1];\n const currEvent = events[i];\n if (prevEvent === undefined || currEvent === undefined) continue;\n const prev = Date.parse(prevEvent.occurred_at);\n const curr = Date.parse(currEvent.occurred_at);\n if (!Number.isFinite(prev) || !Number.isFinite(curr) || curr < prev) {\n throw new Error(\"Events are not in chronological order\");\n }\n }\n}\n\n// Attach the head anchor returned by a chained writeEventsBulk to a session\n// record about to be persisted. A null chain result (empty event batch) leaves\n// the record anchor-less, matching the zero-byte events.jsonl on disk.\nfunction withIntegrity(record: Session, chainResult: BulkChainResult | null): Session {\n if (chainResult === null) return record;\n return {\n ...record,\n session: {\n ...record.session,\n integrity: { head_hash: chainResult.headHash, event_count: chainResult.count },\n },\n };\n}\n\nfunction buildSessionRecord(\n input: SessionInnerImportInput,\n manifest: Manifest,\n newSessionId: PrefixedId<\"ses\">,\n options: ImportSessionOptions,\n): {\n record: Session;\n pathSanitizeReport: ImportSessionResult[\"pathSanitizeReport\"];\n} {\n // Sanitize before constructing the record so the operator-private\n // absolute prefix never reaches disk (= same write-time policy as\n // run.ts / exec.ts / ad-hoc-session.ts). We use the imported\n // working_directory itself as the base for related_files so paths\n // recorded relative to the original repo continue to read as\n // repo-internal; the working_directory field itself is sanitized via\n // the sentinel-based helper so the same value yields \"~/projects/foo\"\n // instead of collapsing to \".\".\n const home = homedir();\n const workingDirectoryRaw = input.working_directory;\n const workingDirectorySanitized = sanitizeWorkingDirectory(workingDirectoryRaw, {\n homedir: home,\n });\n const relatedSanitized = sanitizeRelatedFiles(input.related_files, {\n workingDirectory: workingDirectoryRaw,\n homedir: home,\n });\n\n const inner: Session[\"session\"] = {\n id: newSessionId,\n ...(options.labelOverride !== undefined || input.label !== undefined\n ? { label: options.labelOverride ?? input.label }\n : {}),\n task_id:\n options.taskIdOverride !== undefined\n ? (options.taskIdOverride as Session[\"session\"][\"task_id\"])\n : (input.task_id ?? null),\n workspace_id: manifest.workspace.id,\n source: input.source,\n started_at: input.started_at,\n ...(input.ended_at !== undefined ? { ended_at: input.ended_at } : {}),\n status: \"imported\",\n working_directory: workingDirectorySanitized,\n invocation: input.invocation,\n related_files: relatedSanitized.sanitized,\n events_log: \"events.jsonl\",\n summary: input.summary ?? null,\n ...(input.metrics !== undefined ? { metrics: input.metrics } : {}),\n };\n return {\n record: { schema_version: \"0.1.0\", session: inner },\n pathSanitizeReport: {\n relatedFiles: relatedSanitized.mutationCount,\n workingDirectoryRewritten: workingDirectorySanitized !== workingDirectoryRaw,\n },\n };\n}\n\n/**\n * The closed allowlist of session source kinds that are import-DERIVED (an\n * adapter mechanically derived the events from a native log). Used to\n * discriminate the events a scoped re-import re-derives from the ones it\n * preserves. `EventSourceSchema` is open vocab, so this is a deliberate\n * allowlist: any source NOT in it is treated as non-derived and preserved.\n */\nconst IMPORT_DERIVED_SOURCES: ReadonlySet<string> = new Set<SessionSourceKind>([\n \"claude-code-import\",\n \"codex-import\",\n]);\n\n/** Whether `source` is one of the known import-derived event sources. */\nexport function isImportDerivedSource(source: string): boolean {\n return IMPORT_DERIVED_SOURCES.has(source);\n}\n\n/** Options for {@link reimportPreservingId}. */\nexport type ReimportOptions = {\n /** Compute the re-import and return its preview without writing to disk. */\n dryRun?: boolean;\n};\n\n/** Result of {@link reimportPreservingId}. */\nexport type ReimportResult =\n | {\n status: \"reimported\";\n sessionId: PrefixedId<\"ses\">;\n /** Total events written to the merged `events.jsonl`. */\n eventCount: number;\n /** Non-derived events (human / unknown source) carried over unchanged. */\n preservedCount: number;\n /** Derived events whose prior id (and decision_id) was reused. */\n reusedIdCount: number;\n }\n | {\n status: \"skipped\";\n // `prior_events_unreadable`: the prior events.jsonl had a line that could\n // not be preserved, so the re-import was aborted to avoid dropping data\n // on the atomic rewrite.\n // `prior_derived_dropped`: re-deriving the (grown) source would NOT\n // reproduce some prior derived event, so its id would vanish — only\n // possible on a non-append-only source change, which is out of scope.\n // Aborted so an id a cross-session `linked_events` may reference is\n // never silently dropped; `--force` rebuilds from scratch.\n // `prior_chain_broken`: the prior events.jsonl failed hash-chain\n // verification (tampered). Aborted so a re-import cannot launder a\n // broken chain into a freshly-valid one; the operator inspects with\n // `basou verify` and decides (`--force` rebuilds from scratch).\n reason: \"prior_events_unreadable\" | \"prior_derived_dropped\" | \"prior_chain_broken\";\n };\n\n/**\n * A stable content key for a DERIVED event, used to match a freshly-derived\n * event to its counterpart in the prior import so the prior event's id (and any\n * id-bearing field such as `decision_id`) can be reused — keeping cross-session\n * `decision_recorded.linked_events` references valid across a re-import. The key\n * is `type + occurred_at + the variant's salient derived fields` (the fields the\n * importers populate deterministically from the source records), so matching is\n * robust to record reordering between imports (a positional match is not).\n * `session_started` / `session_ended` are matched by role instead (a session has\n * exactly one of each, and `session_ended`'s occurred_at moves as the log grows).\n */\nfunction derivedEventContentKey(event: Event): string {\n const base = `${event.type}\u0000${event.occurred_at}`;\n switch (event.type) {\n case \"command_executed\":\n return `${base}\u0000${event.command}\u0000${event.args.join(\"\u0001\")}\u0000${event.cwd}`;\n case \"file_changed\":\n return `${base}\u0000${event.path}\u0000${event.change_type}`;\n case \"decision_recorded\":\n return `${base}\u0000${event.title}`;\n default:\n return base;\n }\n}\n\n/**\n * Re-key the freshly-derived events (which carry placeholder ids) onto the\n * session being re-imported, reusing prior derived events' IDS wherever the\n * derivation is unchanged so their ids stay stable across the re-import:\n *\n * - `session_started` / `session_ended`: matched by role (a session has exactly\n * one of each). The prior id is reused with the FRESH content, so\n * `session_ended`'s occurred_at advances to the log's new end while the id is\n * stable.\n * - every other derived event: matched to a prior derived event by content key\n * (FIFO per key for same-key duplicates). A match reuses the prior `id` (and,\n * for `decision_recorded`, the prior `decision_id`) but keeps the FRESH\n * content, so a re-derived field that changed between imports (e.g. a Codex\n * command's `exit_code` / `duration_ms` that filled in once it completed)\n * updates; a miss is a genuinely new event and gets a fresh ULID.\n *\n * `droppedPriorDerived` is true when a prior derived event was NOT reproduced by\n * the fresh derivation — its id would be dropped. That cannot happen for an\n * append-only growth (the prior derivations all recur); it signals a non-append\n * source change, which the caller treats as out of scope and skips.\n */\nfunction reuseDerivedIds(\n priorDerived: ReadonlyArray<Event>,\n freshDerived: ReadonlyArray<Event>,\n sessionId: PrefixedId<\"ses\">,\n): { events: Event[]; reusedIdCount: number; droppedPriorDerived: boolean } {\n const priorStarted = priorDerived.find((e) => e.type === \"session_started\");\n const priorEnded = priorDerived.find((e) => e.type === \"session_ended\");\n let startedUsed = false;\n let endedUsed = false;\n // FIFO queue of prior MIDDLE events per content key.\n const middleByKey = new Map<string, Event[]>();\n for (const e of priorDerived) {\n if (e.type === \"session_started\" || e.type === \"session_ended\") continue;\n const key = derivedEventContentKey(e);\n const list = middleByKey.get(key);\n if (list === undefined) middleByKey.set(key, [e]);\n else list.push(e);\n }\n let reusedIdCount = 0;\n // Reuse the prior id (so a cross-session `linked_events` target survives) but\n // keep the FRESH content; carry the prior `decision_id` so a decision's\n // identity is stable too.\n const withReusedId = (fresh: Event, prior: Event): Event => {\n reusedIdCount++;\n if (fresh.type === \"decision_recorded\" && prior.type === \"decision_recorded\") {\n return { ...fresh, id: prior.id, session_id: sessionId, decision_id: prior.decision_id };\n }\n return { ...fresh, id: prior.id, session_id: sessionId };\n };\n const events = freshDerived.map((fresh): Event => {\n if (fresh.type === \"session_started\") {\n if (priorStarted !== undefined) {\n startedUsed = true;\n return withReusedId(fresh, priorStarted);\n }\n return { ...fresh, id: prefixedUlid(\"evt\"), session_id: sessionId };\n }\n if (fresh.type === \"session_ended\") {\n if (priorEnded !== undefined) {\n endedUsed = true;\n return withReusedId(fresh, priorEnded);\n }\n return { ...fresh, id: prefixedUlid(\"evt\"), session_id: sessionId };\n }\n const match = middleByKey.get(derivedEventContentKey(fresh))?.shift();\n if (match !== undefined) return withReusedId(fresh, match);\n return { ...fresh, id: prefixedUlid(\"evt\"), session_id: sessionId };\n });\n // A prior derived event not consumed above would be DROPPED on the rewrite.\n const droppedPriorDerived =\n (priorStarted !== undefined && !startedUsed) ||\n (priorEnded !== undefined && !endedUsed) ||\n [...middleByKey.values()].some((q) => q.length > 0);\n return { events, reusedIdCount, droppedPriorDerived };\n}\n\n/**\n * Re-import a source whose native log GREW into the SAME Basou session,\n * preserving its id and any non-derived events, instead of skipping it (default\n * dedup) or deleting + recreating it (`--force`). The caller has already\n * validated `freshPayload` and confirmed (by source byte size) that the source\n * changed; this function re-derives the adapter's events, reuses prior derived\n * event ids for unchanged derivations (so `linked_events` references survive),\n * preserves human / unknown-source events, and rewrites `events.jsonl` +\n * `session.yaml` atomically under the session lock.\n *\n * The whole read-modify-write runs under {@link acquireLock} so a concurrent\n * writer cannot interleave; `dryRun` computes the result and writes nothing\n * (and takes no lock). If the prior `events.jsonl` has any line that cannot be\n * preserved (malformed / schema-invalid / half-written), the re-import is\n * ABORTED (`status: \"skipped\"`) rather than risk dropping data on the rewrite.\n */\nexport async function reimportPreservingId(\n paths: BasouPaths,\n manifest: Manifest,\n priorSessionId: string,\n freshPayload: SessionImportPayload,\n options: ReimportOptions = {},\n): Promise<ReimportResult> {\n // The id originates from an on-disk session directory, so it is already a\n // valid `ses_<ULID>`; the cast threads it into the typed record builders.\n const sessionId = priorSessionId as PrefixedId<\"ses\">;\n const importSource = freshPayload.session.source.kind;\n const sessionDir = join(paths.sessions, priorSessionId);\n\n const lock = options.dryRun === true ? null : await acquireLock(paths, \"session\", priorSessionId);\n try {\n // Pre-verify the prior chain BEFORE deriving anything from it: a re-import\n // that reuses event ids out of a hash-broken log would launder the break\n // into a freshly-valid chain. An `unchained` prior (imported before\n // chaining existed) passes — there is no chain to break, and the rewrite\n // below chains it. `incomplete` (yaml lost) also passes: the events are\n // internally consistent and the rewrite repairs the missing anchor.\n const priorVerdict = await verifyEventsChain(paths, priorSessionId);\n if (priorVerdict.status === \"tampered\") {\n return { status: \"skipped\", reason: \"prior_chain_broken\" };\n }\n\n // Strict read of the prior events: abort on ANY unpreservable line so the\n // atomic rewrite below never silently drops a human / unknown-source event.\n let priorUnreadable = false;\n const priorEvents = await readAllEvents(sessionDir, {\n onWarning: () => {\n priorUnreadable = true;\n },\n });\n if (priorUnreadable) {\n return { status: \"skipped\", reason: \"prior_events_unreadable\" };\n }\n\n // Partition by event source: re-derive exactly the events THIS adapter\n // produced (source === importSource); preserve everything else (human\n // local-cli notes / decisions AND any unknown-source event, since\n // EventSourceSchema is open vocab).\n const priorDerived = priorEvents.filter((e) => e.source === importSource);\n const preserved = priorEvents.filter((e) => e.source !== importSource);\n\n const {\n events: rederived,\n reusedIdCount,\n droppedPriorDerived,\n } = reuseDerivedIds(priorDerived, freshPayload.events, sessionId);\n if (droppedPriorDerived) {\n // The grown source no longer reproduces some prior derived event, so its\n // id would vanish (only happens on a non-append-only change). Abort rather\n // than silently drop an id a cross-session linked_events may reference.\n return { status: \"skipped\", reason: \"prior_derived_dropped\" };\n }\n\n // Merge derived + preserved into one stream ordered by occurred_at (stable\n // sort => derived before preserved at an equal timestamp) and enforce the\n // same monotonic invariant a fresh import guarantees, so a re-imported\n // session is indistinguishable in shape from a freshly imported one.\n const mergedEvents = [...rederived, ...preserved].sort(\n (a, b) => Date.parse(a.occurred_at) - Date.parse(b.occurred_at),\n );\n assertChronologicalOrder(mergedEvents);\n\n // Rebuild session.yaml from the fresh derivation (label, metrics, source +\n // size, related_files, timestamps, sanitized paths), preserving the id and\n // the human-owned fields a re-derivation must not clobber.\n const prior = await readSessionYaml(paths, priorSessionId);\n const { record } = buildSessionRecord(freshPayload.session, manifest, sessionId, {});\n const preservedInner: Session[\"session\"] = {\n ...record.session,\n // Defensive: keep any task_id already present on the prior yaml so a\n // re-derive never drops a link, whatever wrote it.\n task_id: prior.session.task_id ?? null,\n // Re-derivation always yields a null summary; keep a prior non-null one.\n summary: prior.session.summary ?? record.session.summary ?? null,\n };\n const updatedRecord: Session = { schema_version: \"0.1.0\", session: preservedInner };\n\n if (options.dryRun !== true) {\n // Capture the prior events.jsonl RAW BYTES before the rewrite so a\n // session.yaml failure can restore them VERBATIM. Re-serializing\n // `priorEvents` here instead would write an UNCHAINED file (the parsed\n // events lost their byte-exact form), contradicting the prior anchor.\n const eventsPath = join(sessionDir, \"events.jsonl\");\n let priorEventsRaw: Buffer | null = null;\n try {\n priorEventsRaw = await readFile(eventsPath);\n } catch (error: unknown) {\n if (!findErrorCode(error, \"ENOENT\")) {\n throw new Error(\"Failed to read events.jsonl\", { cause: error });\n }\n }\n\n const chainResult = await writeEventsBulk(sessionDir, mergedEvents, { chain: true });\n try {\n await overwriteYamlFile(\n join(sessionDir, \"session.yaml\"),\n withIntegrity(updatedRecord, chainResult),\n );\n } catch (error: unknown) {\n // events.jsonl and session.yaml are two separate atomic writes. If the\n // yaml write fails after the events were rewritten, restore the prior\n // events bytes so the session is never left with fresh events paired\n // with stale metadata (size / label / metrics / integrity anchor). The\n // yaml itself needs no restore: an atomic replace that threw never\n // renamed over the prior file.\n if (priorEventsRaw !== null) {\n await atomicReplace(eventsPath, priorEventsRaw).catch(() => undefined);\n } else {\n await rm(eventsPath, { force: true }).catch(() => undefined);\n }\n throw error;\n }\n }\n\n return {\n status: \"reimported\",\n sessionId,\n eventCount: mergedEvents.length,\n preservedCount: preserved.length,\n reusedIdCount,\n };\n } finally {\n await lock?.release();\n }\n}\n\n/** Options for {@link rechainSessionInPlace}. */\nexport type RechainOptions = {\n /**\n * Compute the outcome and write nothing. The session lock is STILL taken:\n * an unlocked read could observe a concurrent in-place re-import's\n * two-file write window (events rewritten, yaml not yet) and report a\n * state a locked run would never see.\n */\n dryRun?: boolean;\n};\n\n/** Result of {@link rechainSessionInPlace}. */\nexport type RechainResult =\n | { status: \"rechained\"; eventCount: number }\n | {\n status: \"skipped\";\n // `already_chained`: the session is `verified` — idempotent no-op.\n // `empty`: zero events; nothing to chain and no anchor is written\n // (same rule as the import writers).\n // `not_imported`: only the closed imported corpus may be chained — a\n // live/ad-hoc session would be appended to afterwards and turn\n // `tampered`.\n // `tampered`: the log already fails verification; rechaining it would\n // launder the break into a fresh valid chain. Inspect with\n // `basou verify`; `--force` re-import is the explicit override.\n // `events_unreadable`: a line cannot be preserved EXACTLY (blank or\n // whitespace-only line, invalid UTF-8, JSON parse failure, schema\n // gate failure, or a non-byte-identical JSON round-trip) — rechain\n // never normalizes or drops content.\n // `session_id_mismatch`: a line's session_id is not this session's id;\n // chaining it would manufacture an instantly-tampered session.\n // `yaml_missing` / `yaml_unreadable`: session.yaml absent / unparseable.\n reason:\n | \"already_chained\"\n | \"empty\"\n | \"not_imported\"\n | \"tampered\"\n | \"events_unreadable\"\n | \"session_id_mismatch\"\n | \"yaml_missing\"\n | \"yaml_unreadable\";\n };\n\n/**\n * Add the tamper-evidence hash chain, IN PLACE, to an imported session that\n * was written before chaining existed (or whose chain was legitimately never\n * computed). Event ids, order, field sets, values and key order are all\n * preserved exactly — each original line is re-emitted with only `prev_hash`\n * appended (see {@link chainRawJsonLines}); `session.yaml` is rewritten as\n * read with only `integrity` added. Nothing else changes, so cross-session\n * references (`linked_events`) survive, unlike a `--force` re-import.\n *\n * Rechaining asserts tamper-evidence FROM NOW ON; it does not retroactively\n * prove the pre-existing content was never modified before the migration.\n *\n * Refuses anything it cannot preserve exactly or that is not the closed\n * imported corpus — see {@link RechainResult} reasons. Throws (rather than\n * returning a skip) on environment-level I/O failures, mirroring\n * `verifyEventsChain`.\n */\nexport async function rechainSessionInPlace(\n paths: BasouPaths,\n sessionId: string,\n options: RechainOptions = {},\n): Promise<RechainResult> {\n const sessionDir = join(paths.sessions, sessionId);\n // Wrap lock-acquisition failures in the fixed pathless vocabulary: the CLI\n // surfaces per-session error messages verbatim, so a raw fs error here\n // would leak an absolute lockfile path.\n let lock: Awaited<ReturnType<typeof acquireLock>>;\n try {\n lock = await acquireLock(paths, \"session\", sessionId);\n } catch (error: unknown) {\n if (error instanceof Error && error.message === \"Lock is held by another process\") {\n throw error;\n }\n throw new Error(\"Failed to acquire lock\", { cause: error });\n }\n try {\n // 1. Status gate: only the imported corpus is closed against appends.\n let record: Session;\n try {\n record = await readSessionYaml(paths, sessionId);\n } catch (error: unknown) {\n if (error instanceof Error && error.message === \"YAML file not found\") {\n return { status: \"skipped\", reason: \"yaml_missing\" };\n }\n return { status: \"skipped\", reason: \"yaml_unreadable\" };\n }\n if (record.session.status !== \"imported\") {\n return { status: \"skipped\", reason: \"not_imported\" };\n }\n\n // 2. Verdict gate. Only a clean `unchained` log proceeds; `incomplete`\n // is unreachable here because the yaml-missing case returned above.\n const verdict = await verifyEventsChain(paths, sessionId);\n if (verdict.status === \"verified\") {\n return { status: \"skipped\", reason: \"already_chained\" };\n }\n if (verdict.status === \"empty\") {\n return { status: \"skipped\", reason: \"empty\" };\n }\n if (verdict.status !== \"unchained\") {\n return { status: \"skipped\", reason: \"tampered\" };\n }\n\n // 3. Raw-line gate: every line must be preservable EXACTLY. The decoded\n // text must re-encode to the same bytes (invalid UTF-8 would silently\n // normalize to U+FFFD), and every line must be byte-identical to its own\n // JSON round-trip (whitespace padding, duplicate keys or non-canonical\n // escapes would otherwise be silently rewritten). Every line a basou\n // writer produced satisfies both.\n const eventsPath = join(sessionDir, \"events.jsonl\");\n let priorRaw: Buffer;\n try {\n priorRaw = await readFile(eventsPath);\n } catch (error: unknown) {\n throw new Error(\"Failed to read events.jsonl\", { cause: error });\n }\n if (priorRaw.length === 0 || priorRaw[priorRaw.length - 1] !== 0x0a) {\n return { status: \"skipped\", reason: \"events_unreadable\" };\n }\n const text = priorRaw.toString(\"utf8\");\n if (!priorRaw.equals(Buffer.from(text, \"utf8\"))) {\n return { status: \"skipped\", reason: \"events_unreadable\" };\n }\n const rawLines = text.slice(0, -1).split(\"\\n\");\n for (const line of rawLines) {\n if (line.trim().length === 0) {\n return { status: \"skipped\", reason: \"events_unreadable\" };\n }\n let parsed: unknown;\n try {\n parsed = JSON.parse(line);\n } catch {\n return { status: \"skipped\", reason: \"events_unreadable\" };\n }\n if (JSON.stringify(parsed) !== line) {\n return { status: \"skipped\", reason: \"events_unreadable\" };\n }\n // Gate ONLY: the parsed/validated output is never written.\n if (!EventSchema.safeParse(parsed).success) {\n return { status: \"skipped\", reason: \"events_unreadable\" };\n }\n if ((parsed as Record<string, unknown>).session_id !== sessionId) {\n return { status: \"skipped\", reason: \"session_id_mismatch\" };\n }\n }\n\n if (options.dryRun === true) {\n return { status: \"rechained\", eventCount: rawLines.length };\n }\n\n // 4-6. Chain the ORIGINAL lines, write atomically, anchor the yaml read\n // in step 1 (all other fields preserved as-is). On a yaml failure,\n // restore the prior events bytes verbatim — same rollback as the\n // in-place re-import.\n const chainResult = chainRawJsonLines(rawLines, sessionId);\n const body = `${chainResult.lines.join(\"\\n\")}\\n`;\n try {\n await atomicReplace(eventsPath, body);\n } catch (error: unknown) {\n throw new Error(\"Failed to write events.jsonl\", { cause: error });\n }\n try {\n await overwriteYamlFile(\n join(sessionDir, \"session.yaml\"),\n withIntegrity(record, { headHash: chainResult.headHash, count: chainResult.count }),\n );\n } catch (error: unknown) {\n await atomicReplace(eventsPath, priorRaw).catch(() => undefined);\n throw error;\n }\n\n return { status: \"rechained\", eventCount: chainResult.count };\n } finally {\n await lock.release();\n }\n}\n","/**\n * Version of the `@basou/core` package, aligned with `manifest.yaml`'s\n * `basou_version` field as defined in the Basou v0.1 specification.\n */\nexport const BASOU_CORE_VERSION = \"0.1.0\";\n\nexport type {\n ClaudeTranscriptRecord,\n ClaudeTranscriptToPayloadOptions,\n CommandLookup,\n StopHookEvaluation,\n StopHookEvaluationInput,\n StopHookSilentReason,\n} from \"./adapters/claude-code/index.js\";\nexport {\n CLAUDE_IMPORT_SOURCE,\n claudeCodeAdapterMetadata,\n claudeTranscriptToImportPayload,\n DEFAULT_STOP_HOOK_MIN_ACTIONS,\n evaluateStopHook,\n resolveClaudeCodeCommand,\n summarizeAdapterOutput,\n} from \"./adapters/claude-code/index.js\";\nexport type {\n CodexRolloutRecord,\n CodexRolloutToPayloadOptions,\n} from \"./adapters/codex/index.js\";\nexport { CODEX_IMPORT_SOURCE, codexRolloutToImportPayload } from \"./adapters/codex/index.js\";\nexport type { ApprovalLocation, LoadedApproval } from \"./approval/index.js\";\nexport { enumerateApprovals, isLazyExpired, loadApproval } from \"./approval/index.js\";\nexport type { DecisionsRendererInput, DecisionsRendererResult } from \"./decisions/index.js\";\nexport { renderDecisions } from \"./decisions/index.js\";\nexport type {\n BulkChainResult,\n ChainBreakReason,\n ChainedEvents,\n ChainTailState,\n ChainVerdict,\n ChainVerdictStatus,\n ReplayOptions,\n ReplayWarning,\n WriteEventsBulkOptions,\n} from \"./events/index.js\";\nexport {\n appendChainedEvent,\n appendChainedEventLocked,\n appendEvent,\n chainEvents,\n chainRawJsonLines,\n genesisHash,\n inspectChainTail,\n lineHash,\n readAllEvents,\n replayEvents,\n serializeEventLine,\n verifyEventsChain,\n writeEventsBulk,\n} from \"./events/index.js\";\nexport type { DiffResult, FileChange, FileChangeStatus } from \"./git/diff.js\";\nexport { getDiff } from \"./git/diff.js\";\nexport type { GitSnapshot } from \"./git/snapshot.js\";\nexport {\n getSnapshot,\n isGitNotFound,\n resolveBasouRepositoryRoot,\n resolveRepositoryRoot,\n safeSimpleGit,\n tryRemoteUrl,\n} from \"./git/snapshot.js\";\nexport type { HandoffRendererInput, HandoffRendererResult } from \"./handoff/index.js\";\nexport { renderHandoff } from \"./handoff/index.js\";\nexport type { IdPrefix, PrefixedId } from \"./ids/ulid.js\";\nexport { ID_PREFIXES, isValidPrefixedId, prefixedUlid, ulid } from \"./ids/ulid.js\";\nexport { parseDuration } from \"./lib/duration.js\";\nexport { formatDurationMs } from \"./lib/format-duration.js\";\nexport { resolveSessionId, resolveTaskId } from \"./lib/id-resolver.js\";\nexport type { SanitizePathOptions, SanitizeRelatedFilesResult } from \"./lib/path-sanitizer.js\";\nexport {\n sanitizePath,\n sanitizeRelatedFiles,\n sanitizeWorkingDirectory,\n} from \"./lib/path-sanitizer.js\";\nexport type { SourceRootScope } from \"./lib/source-root-scope.js\";\nexport { AGENT_INFRA_DIRS, classifyFilesBySourceRoot } from \"./lib/source-root-scope.js\";\nexport type {\n OrientationRendererInput,\n OrientationRendererResult,\n OrientationSummary,\n} from \"./orientation/index.js\";\nexport { renderOrientation, summarizeOrientation } from \"./orientation/index.js\";\nexport type { ArchivePlan } from \"./project/archive.js\";\nexport { planArchive } from \"./project/archive.js\";\nexport type {\n GitignorePlanSummary,\n RepoGitignoreFacts,\n RepoGitignorePlan,\n} from \"./project/gitignore-plan.js\";\nexport { planGitignore } from \"./project/gitignore-plan.js\";\nexport type {\n PresetAction,\n PresetCollision,\n PresetMarkerConflict,\n PresetMarkerKind,\n PresetPlanSummary,\n PresetRepo,\n RepoPresetFacts,\n RepoPresetPlan,\n} from \"./project/preset.js\";\nexport { isRenderable, renderPresetBlock, summarizePresetPlan } from \"./project/preset.js\";\nexport type { RenamePlan } from \"./project/rename.js\";\nexport { pathBasename, planRename } from \"./project/rename.js\";\nexport type {\n AdoptCandidate,\n AdoptCandidateKind,\n PublishKind,\n PublishTarget,\n RepoEntry,\n RepoLanguage,\n RepoVisibility,\n RosterAdoptionPlan,\n RosterDriftSummary,\n SourceRootsReconcile,\n} from \"./project/roster.js\";\nexport {\n planRosterAdoption,\n reconcileSourceRoots,\n summarizeRosterDrift,\n} from \"./project/roster.js\";\nexport type {\n InstructionSymlinkFact,\n InstructionSymlinkState,\n RepoSymlinkFacts,\n RepoSymlinkPlan,\n SymlinkCollision,\n SymlinkConflict,\n SymlinkPlanSummary,\n} from \"./project/symlinks.js\";\nexport { summarizeSymlinkPlan } from \"./project/symlinks.js\";\nexport type {\n InstructionFileFact,\n RepoWiringFacts,\n WiringRisk,\n WiringSummary,\n} from \"./project/wiring.js\";\nexport { summarizeWiring } from \"./project/wiring.js\";\nexport type {\n ExistingViewLink,\n ViewCollision,\n ViewConflict,\n ViewLinkState,\n ViewRepoFact,\n ViewStrayUnknown,\n WorkspaceViewPlan,\n} from \"./project/workspace-view.js\";\nexport { planWorkspaceView } from \"./project/workspace-view.js\";\nexport type {\n ReportApprovalItem,\n ReportData,\n ReportDecisionItem,\n ReportRendererInput,\n ReportRendererResult,\n ReportSessionItem,\n ReportTaskItem,\n TaskStatusCount,\n} from \"./report/index.js\";\nexport { renderReport } from \"./report/index.js\";\nexport type {\n CitedReview,\n ReviewGapRepoSummary,\n ReviewGapsInput,\n ReviewGapsSummary,\n ReviewGapUnit,\n ReviewGapVerdict,\n} from \"./review/index.js\";\nexport { findReviewGaps, normalizeRepoKey, normalizeRepoPath } from \"./review/index.js\";\nexport { ChildProcessRunner } from \"./runtime/child-process-runner.js\";\nexport type {\n CaptureMode,\n ProcessRunner,\n RunOptions,\n RunResult,\n} from \"./runtime/process-runner.js\";\nexport type {\n AdapterOutputEvent,\n Approval,\n ApprovalApprovedEvent,\n ApprovalExpiredEvent,\n ApprovalRejectedEvent,\n ApprovalRequestedEvent,\n ApprovalStatus,\n CommandExecutedEvent,\n DecisionRecordedEvent,\n Event,\n FileChangedEvent,\n GitSnapshotEvent,\n JsonSchemaArtifact,\n Manifest,\n NoteAddedEvent,\n RiskLevel,\n Session,\n SessionEndedEvent,\n SessionImportPayload,\n SessionInnerImportInput,\n SessionIntegrity,\n SessionMetrics,\n SessionSourceKind,\n SessionStartedEvent,\n SessionStatus,\n SessionStatusChangedEvent,\n StatusSnapshot,\n Task,\n TaskArchivedEvent,\n TaskCreatedEvent,\n TaskDeletedEvent,\n TaskLinkageRefreshedEvent,\n TaskReconciledEvent,\n TaskStatus,\n TaskStatusChangedEvent,\n} from \"./schemas/index.js\";\nexport {\n ApprovalIdSchema,\n ApprovalSchema,\n ApprovalStatusSchema,\n buildJsonSchemas,\n DecisionIdSchema,\n EventIdSchema,\n EventSchema,\n EventSourceSchema,\n IsoTimestampSchema,\n JSON_SCHEMA_VERSION,\n ManifestSchema,\n RiskLevelSchema,\n SchemaVersionSchema,\n SessionIdSchema,\n SessionImportPayloadSchema,\n SessionInnerImportSchema,\n SessionIntegritySchema,\n SessionMetricsSchema,\n SessionSchema,\n SessionSourceKindSchema,\n SessionStatusSchema,\n StatusSchema,\n serializeJsonSchema,\n TaskIdSchema,\n TaskSchema,\n TaskStatusSchema,\n unknownManifestKeys,\n WorkspaceIdSchema,\n} from \"./schemas/index.js\";\nexport type {\n ActiveTimeBasis,\n DayWorkStats,\n MeasureAvailability,\n SessionWorkStats,\n SourceWorkStats,\n StatusCount,\n TokenTotals,\n WorkStatsInput,\n WorkStatsResult,\n WorkStatsTotals,\n} from \"./stats/index.js\";\nexport { ACTIVE_GAP_CAP_MS, computeWorkStats, sessionWorkStatsFromEvents } from \"./stats/index.js\";\nexport type {\n AppendBasouGitignoreOptions,\n AppendBasouGitignoreResult,\n AppendEventToExistingInput,\n AppendEventToExistingResult,\n ArchiveTaskInput,\n ArchiveTaskResult,\n AttachableStatus,\n AttachTaskInput,\n AttachUpdateTaskStatusInput,\n BasouPaths,\n CreateAdHocSessionInput,\n CreateAdHocSessionResult,\n CreateAdHocTaskInput,\n CreateManifestInput,\n CreateTaskInput,\n CreateTaskResult,\n DeleteTaskInput,\n DeleteTaskResult,\n EditTaskInput,\n EditTaskResult,\n FederatedRoot,\n ImportSessionOptions,\n ImportSessionResult,\n LoadFederatedOptions,\n LoadSessionEntriesOptions,\n LoadTaskEntriesOptions,\n LockHandle,\n LockScope,\n MarkerSection,\n Markers,\n RechainOptions,\n RechainResult,\n ReconcileAllResult,\n ReconcileAllTasksInput,\n ReconcileAllTasksOptions,\n ReconcileFailure,\n ReconcileResult,\n ReconcileTaskInput,\n RefreshLinkageInput,\n RefreshLinkageResult,\n ReimportOptions,\n ReimportResult,\n SessionEntry,\n SessionSkipReason,\n SuspectReason,\n TaskDocument,\n TaskSkipReason,\n TaskWriteAfterEventPhase,\n UpdateAdHocTaskStatusInput,\n UpdateTaskStatusInput,\n UpdateTaskStatusResult,\n WriteTaskFileMode,\n} from \"./storage/index.js\";\nexport {\n acquireLock,\n appendBasouGitignore,\n appendEventToExistingSession,\n archiveTask,\n assertBasouRootSafe,\n basouPaths,\n buildStatusSnapshot,\n classifySuspect,\n createAdHocSessionWithEvent,\n createManifest,\n createTaskWithEvent,\n deleteTask,\n editTask,\n ensureBasouDirectory,\n enumerateArchivedTaskIds,\n enumerateSessionDirs,\n enumerateTaskIds,\n FailedToFinalizeError,\n finalizeSessionYaml,\n findErrorCode,\n GENERATED_END,\n GENERATED_START,\n importSessionFromJson,\n isImportDerivedSource,\n linkYamlFile,\n loadFederatedSessionEntries,\n loadSessionEntries,\n loadTaskEntries,\n overwriteYamlFile,\n PROTOCOL_END,\n PROTOCOL_START,\n parseMarkers,\n readManifest,\n readMarkdownFile,\n readSessionYaml,\n readStatus,\n readTaskFile,\n readTaskFileWithArchiveFallback,\n readYamlFile,\n rechainSessionInPlace,\n reconcileAllTasks,\n reconcileTask,\n refreshTaskLinkedSessions,\n reimportPreservingId,\n removeMarkerSection,\n renderWithMarkers,\n STUCK_THRESHOLD_MS,\n TaskWriteAfterEventError,\n updateTaskStatusWithEvent,\n writeManifest,\n writeMarkdownFile,\n writeStatus,\n writeTaskFile,\n writeYamlFile,\n} from \"./storage/index.js\";\n"],"mappings":"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","names":["resolve","readString","isObject","path","readString","payload","isObject","readNonNegInt","commandExecutedEvent","sessionStartedEvent","sessionEndedEvent","baseEvent","z","z","dirname","join","join","z","z","join","readdir","join","readFile","join","readFile","unlink","join","unlink","readFile","ulid","join","join","readFile","z","z","readdir","join","join","dirname","path","appendFile","join","appendFile","join","readFile","join","join","readFile","splitLinesBytes","carriesPrevHash","readdir","stat","join","z","z","stat","hasErrorCode","path","readdir","join","stat","join","createHash","mkdir","readdir","readFile","rename","stat","unlink","join","z","z","z","mkdir","join","join","mkdir","readFile","join","z","z","join","readFile","DEFAULT_ATTACHABLE_STATUSES","z","join","readFile","readdir","stat","createHash","eventId","unlink","mkdir","rename","join","shortId","basename","dirname","join","resolve","homedir","dirname","join","lstat","z","z","lstat","hasErrorCode","join","dirname","isPublicFacing","join","join","join","join","t","homedir","basename","isAbsolute","join","join","homedir","isAbsolute","basename","repo","list","spawn","spawn","resolve","z","z","z","z","lstat","mkdir","join","hasErrorCode","readFile","writeFile","join","hasErrorCode","readFile","readFile","hasErrorCode","mkdir","readFile","rm","homedir","join","join","mkdir","rm","homedir","readFile"]}