@basou/core 0.43.0 → 0.45.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.ts CHANGED
@@ -106,6 +106,19 @@ declare function removeStopHook(settings: unknown): StopHookRemoval;
106
106
  /** Return the installed basou Stop hook command, or null if none is registered. */
107
107
  declare function findBasouStopHookCommand(settings: unknown): string | null;
108
108
 
109
+ /**
110
+ * `schema_version` stamped on NEWLY WRITTEN `.basou/manifest.yaml`.
111
+ *
112
+ * 0.2.0 requires seconds in every timestamp. That NARROWS the field's domain,
113
+ * which §7.3 forbids except under the vacuous-narrowing rule the same section
114
+ * states: no value basou has ever written omits seconds, so the set of
115
+ * documents this refuses is empty. See `docs/spec/schemas.md` for the read
116
+ * rule and the measurement. The narrowing is shared with the event format, so
117
+ * every durable document bumps together.
118
+ *
119
+ * Note: basou writes this document.
120
+ */
121
+ declare const MANIFEST_SCHEMA_VERSION: "0.2.0";
109
122
  /**
110
123
  * The manifest, plus one guard the loose object cannot express: a TOP-LEVEL
111
124
  * `confidential` is refused. It belongs under `policies`, and because the
@@ -278,9 +291,17 @@ declare const SessionInnerImportSchema: z.ZodObject<{
278
291
  * validates a payload against the published artifact gets the same answer the
279
292
  * importer will give, instead of passing validation and being rejected at run
280
293
  * time. Unrelated to the events INSIDE the envelope, which carry their own
281
- * `schema_version` and are at 0.2.0.
294
+ * `schema_version` and are at 0.3.0.
295
+ *
296
+ * Bumped to 0.2.0 when timestamps began requiring seconds. That narrowing
297
+ * reaches the envelope's OWN fields -- `session.started_at`, `session.ended_at`
298
+ * and both `active_intervals` bounds -- so unlike the 0.2.0 event bump, whose
299
+ * bytes changed only because of the union it embeds, the envelope's own
300
+ * accepted set moved and the `$id` moves with it. A third party still gets one
301
+ * answer from the artifact and the importer, which is the whole point of
302
+ * pinning the version here.
282
303
  */
283
- declare const SESSION_IMPORT_SCHEMA_VERSION: "0.1.0";
304
+ declare const SESSION_IMPORT_SCHEMA_VERSION: "0.2.0";
284
305
  declare const SessionImportPayloadSchema: z.ZodObject<{
285
306
  schema_version: z.ZodString;
286
307
  session: z.ZodObject<{
@@ -1016,6 +1037,19 @@ type CodexRolloutToPayloadOptions = {
1016
1037
  */
1017
1038
  declare function codexRolloutToImportPayload(records: ReadonlyArray<CodexRolloutRecord>, options: CodexRolloutToPayloadOptions): SessionImportPayload | null;
1018
1039
 
1040
+ /**
1041
+ * `schema_version` stamped on NEWLY WRITTEN `.basou/approvals/**`.
1042
+ *
1043
+ * 0.2.0 requires seconds in every timestamp. That NARROWS the field's domain,
1044
+ * which §7.3 forbids except under the vacuous-narrowing rule the same section
1045
+ * states: no value basou has ever written omits seconds, so the set of
1046
+ * documents this refuses is empty. See `docs/spec/schemas.md` for the read
1047
+ * rule and the measurement. The narrowing is shared with the event format, so
1048
+ * every durable document bumps together.
1049
+ *
1050
+ * Note: basou only READS this document -- approvals are placed by an outside orchestrator, so nothing in this tree stamps the version; the constant exists so the published artifact and the accepted set are declared in one place.
1051
+ */
1052
+ declare const APPROVAL_SCHEMA_VERSION: "0.2.0";
1019
1053
  /**
1020
1054
  * Lifecycle states of a Basou approval. The status is stored directly on
1021
1055
  * the approval YAML (flat shape) so that pending → resolved transitions
@@ -1177,9 +1211,16 @@ declare function enumerateApprovals(paths: BasouPaths): Promise<{
1177
1211
  declare function isLazyExpired(approval: Approval, now: Date): boolean;
1178
1212
 
1179
1213
  /**
1180
- * `schema_version` stamped on NEWLY WRITTEN events. Bumped to 0.2.0 when
1181
- * `command_executed.duration_ms` became nullable, which widened the field's
1182
- * domain and so is a breaking change to the format.
1214
+ * `schema_version` stamped on NEWLY WRITTEN events.
1215
+ *
1216
+ * 0.3.0 requires seconds in every timestamp. That NARROWS the field's domain,
1217
+ * which §7.3 forbids except under the vacuous-narrowing rule the same section
1218
+ * states: no value basou has ever written omits seconds, so the set of
1219
+ * documents this refuses is empty. See `docs/spec/schemas.md` for the read
1220
+ * rule and the measurement.
1221
+ *
1222
+ * 0.2.0 made `command_executed.duration_ms` nullable, which widened the
1223
+ * field's domain and so is a breaking change to the format.
1183
1224
  *
1184
1225
  * The bump does not change what any value already on disk means: `0` meant "not
1185
1226
  * observed" before and still does. What changes is that a writer now says so
@@ -1188,13 +1229,17 @@ declare function isLazyExpired(approval: Approval, now: Date): boolean;
1188
1229
  * `readObservedDuration`), and the
1189
1230
  * version is a statement about validation, not about interpretation.
1190
1231
  *
1191
- * Reading is unaffected — {@link SchemaVersionSchema} accepts any 0.x.y — so
1192
- * events already on disk keep validating, and are not rewritten in place (a
1193
- * session IS re-derived, and restamped, when its source log grows). Only EVENTS
1194
- * carry this version: the other `.basou/` documents did not change, so their
1195
- * `schema_version` stays 0.1.0.
1232
+ * Reading is unaffected by the 0.2.0 bump — {@link SchemaVersionSchema}
1233
+ * accepts any 0.x.y — so events already on disk keep validating, and are not
1234
+ * rewritten in place (a session IS re-derived, and restamped, when its source
1235
+ * log grows). The 0.3.0 narrowing is what a stored value IS checked against,
1236
+ * which is why it had to be empty of real documents before it could land.
1237
+ *
1238
+ * The 0.3.0 timestamp narrowing is shared, so the other durable documents
1239
+ * (manifest, session, task, approval) bump alongside it. The two caches
1240
+ * (`status`, `task-index`) are rebuilt rather than versioned.
1196
1241
  */
1197
- declare const EVENT_SCHEMA_VERSION: "0.2.0";
1242
+ declare const EVENT_SCHEMA_VERSION: "0.3.0";
1198
1243
  declare const SessionStartedEventSchema: z.ZodObject<{
1199
1244
  schema_version: z.ZodString;
1200
1245
  id: z.ZodString & z.ZodType<`evt_${string}`, string, z.core.$ZodTypeInternals<`evt_${string}`, string>>;
@@ -1911,747 +1956,951 @@ declare function replayEvents(sessionDir: string, options?: ReplayOptions): Asyn
1911
1956
  declare function readAllEvents(sessionDir: string, options?: ReplayOptions): Promise<Event[]>;
1912
1957
 
1913
1958
  /**
1914
- * Project roster drift (the "saddle" model). A project's repos are DECLARED
1915
- * once in the manifest's `repos` list; the capture config (`source_roots`) must
1916
- * cover every declared repo. This computes the drift between the two so
1917
- * `basou project check` can surface a declared repo that is NOT being captured
1918
- * — the class of bug where a companion repo was wired into the workspace but
1919
- * never added to `source_roots`, so its work silently fell out of capture.
1959
+ * `schema_version` stamped on NEWLY WRITTEN `.basou/sessions/<id>/session.yaml`.
1920
1960
  *
1921
- * Pure: it compares declared relative paths against captured relative paths and
1922
- * performs no filesystem or git I/O. Paths are compared as declared (both lists
1923
- * use the same machine-portable relative-path form), not resolved on disk.
1924
- */
1925
- type RepoVisibility = "public" | "private" | "future-public";
1926
- /**
1927
- * The audience-driven language axis. Independent of visibility: a private repo
1928
- * can publish English content, a public repo can carry bilingual docs. `en` /
1929
- * `ja` for a single audience, `en+ja` when both are served.
1930
- */
1931
- type RepoLanguage = "en" | "ja" | "en+ja";
1932
- /** A published surface a repo emits: a deployed website or a package registry. */
1933
- type PublishKind = "web" | "npm";
1934
- /**
1935
- * Where a repo's agent instruction files live (the instruction-source axis),
1936
- * independent of visibility / language / publishes. `hub` is basou's native,
1937
- * generated hub-and-spoke topology (canonical in the anchor, gitignored symlinks
1938
- * in each repo); `self` is the additive opt-in where the canonical AGENTS.md is a
1939
- * regular committed file in the repo itself and basou stays hands-off about its
1940
- * content. See {@link instructionMode} for the default (absent => `hub`).
1941
- */
1942
- type RepoInstructions = "hub" | "self";
1943
- /**
1944
- * One published surface. Its visibility and language are INDEPENDENT of the
1945
- * source repo's: a private repo commonly publishes a public website. Both are
1946
- * optional so a surface can be declared
1947
- * before those facts are pinned down (mirroring how `adopt` leaves repo
1948
- * visibility unset for the operator to fill in).
1949
- */
1950
- type PublishTarget = {
1951
- kind: PublishKind;
1952
- visibility?: RepoVisibility | undefined;
1953
- language?: RepoLanguage | undefined;
1954
- };
1955
- type RepoEntry = {
1956
- /** Path relative to the manifest repo root (e.g. ".", "../takuhon"). */
1957
- path: string;
1958
- visibility?: RepoVisibility | undefined;
1959
- /** Source language (commits/comments/code, read by contributors). Independent of visibility. */
1960
- language?: RepoLanguage | undefined;
1961
- /** Published surfaces this repo emits (opt-in; absent for a repo that publishes nothing). */
1962
- publishes?: PublishTarget[] | undefined;
1963
- /**
1964
- * Instruction-source mode. Absent => `hub` (basou's native generated topology),
1965
- * so an existing roster's behavior is unchanged. `self` opts the repo out of
1966
- * generation: its AGENTS.md is a hand-authored committed file and basou stays
1967
- * hands-off. Resolve the effective mode with {@link instructionMode}.
1968
- */
1969
- instructions?: RepoInstructions | undefined;
1970
- };
1971
- /**
1972
- * The effective instruction-source mode for a repo: the declared `instructions`,
1973
- * defaulting to `hub` when absent. The default is the single guarantee that an
1974
- * existing roster (which has no `instructions` field) keeps basou's current
1975
- * hub-and-spoke behavior byte-for-byte — every generator branches on this, never
1976
- * on the raw optional field, so "absent => hub" is decided in exactly one place.
1961
+ * 0.2.0 requires seconds in every timestamp. That NARROWS the field's domain,
1962
+ * which §7.3 forbids except under the vacuous-narrowing rule the same section
1963
+ * states: no value basou has ever written omits seconds, so the set of
1964
+ * documents this refuses is empty. See `docs/spec/schemas.md` for the read
1965
+ * rule and the measurement. The narrowing is shared with the event format, so
1966
+ * every durable document bumps together.
1967
+ *
1968
+ * Note: basou writes this document.
1977
1969
  */
1978
- declare function instructionMode(entry: {
1979
- instructions?: RepoInstructions | undefined;
1980
- }): RepoInstructions;
1981
- type RosterDriftSummary = {
1982
- declaredCount: number;
1983
- capturedCount: number;
1984
- /** Declared in `repos` but absent from `source_roots`: a capture gap. */
1985
- gaps: RepoEntry[];
1986
- /** In `source_roots` but not declared in `repos` (e.g. a workspace view, or a stray). */
1987
- extra: string[];
1988
- /** Declared paths that are also captured. */
1989
- matched: string[];
1990
- /** True when there is no capture gap (every declared repo is covered). */
1991
- ok: boolean;
1992
- };
1970
+ declare const SESSION_SCHEMA_VERSION: "0.2.0";
1971
+ /** Session lifecycle states. */
1972
+ declare const SessionStatusSchema: z.ZodEnum<{
1973
+ initialized: "initialized";
1974
+ running: "running";
1975
+ waiting_approval: "waiting_approval";
1976
+ completed: "completed";
1977
+ failed: "failed";
1978
+ interrupted: "interrupted";
1979
+ imported: "imported";
1980
+ archived: "archived";
1981
+ }>;
1982
+ /** Inferred runtime type for {@link SessionStatusSchema}. */
1983
+ type SessionStatus = z.infer<typeof SessionStatusSchema>;
1993
1984
  /**
1994
- * Compute the {@link RosterDriftSummary} for a project. A declared repo missing
1995
- * from the captured set is a `gap` (the surfaced suspicion); a captured path not
1996
- * in the declared set is `extra` (commonly the workspace view, which is a
1997
- * capture source but not itself a project repo). With no declared roster, there
1998
- * are no gaps (nothing to check against) and every captured path is `extra`.
1985
+ * Source kind that produced the session.
1986
+ *
1987
+ * - `claude-code-adapter` a live `basou run claude-code` process wrap.
1988
+ * - `claude-code-import` derived after the fact from a Claude Code native
1989
+ * transcript (`~/.claude/projects/*.jsonl`) by `basou import claude-code`.
1990
+ * - `codex-adapter` — a live `basou run codex` process wrap.
1991
+ * - `codex-import` — derived after the fact from an OpenAI Codex native
1992
+ * rollout log (date-partitioned `~/.codex/sessions`) by `basou import codex`.
1993
+ * - `import` — a round-trip of a Basou-format export (`basou session import`).
1994
+ * - `human` / `terminal` — manually-authored / terminal-recorded sessions.
1999
1995
  */
2000
- declare function summarizeRosterDrift(input: {
2001
- repos?: RepoEntry[];
2002
- sourceRoots?: string[];
2003
- }): RosterDriftSummary;
2004
- type SourceRootsReconcile = {
2005
- /**
2006
- * The reconciled `source_roots`: the existing entries verbatim, then every
2007
- * declared repo path that was missing (normalized, in roster order). Existing
2008
- * order and form are preserved so the manifest diff is minimal and reversible.
2009
- */
2010
- next: string[];
2011
- /** Declared repo paths (normalized) that were appended because `source_roots` did not cover them. */
2012
- added: string[];
2013
- /** True when `source_roots` already covers every declared repo (`next` equals the current list). */
2014
- unchanged: boolean;
2015
- };
1996
+ declare const SessionSourceKindSchema: z.ZodEnum<{
1997
+ "claude-code-adapter": "claude-code-adapter";
1998
+ import: "import";
1999
+ "claude-code-import": "claude-code-import";
2000
+ "codex-adapter": "codex-adapter";
2001
+ "codex-import": "codex-import";
2002
+ human: "human";
2003
+ terminal: "terminal";
2004
+ }>;
2005
+ /** Inferred runtime type for {@link SessionSourceKindSchema}. */
2006
+ type SessionSourceKind = z.infer<typeof SessionSourceKindSchema>;
2016
2007
  /**
2017
- * Derive the `source_roots` a project's declared repo roster requires. The
2018
- * roster (`repos`) is the single source of truth for which repos belong to the
2019
- * project; this is the actuator behind `basou project sync`, computing the
2020
- * additive reconciliation so every declared repo is captured.
2008
+ * Optional per-session metrics, computed at import time from the source tool's
2009
+ * native log. Two groups, both optional because not every source records them:
2021
2010
  *
2022
- * ADDITIVE ONLY: it appends declared paths that are missing and never removes
2023
- * an existing entry. A captured-but-undeclared path (commonly the generated
2024
- * workspace view a legitimate capture source that is not itself a project
2025
- * repo) is preserved; pruning strays is deferred to the slice that generates
2026
- * the view (so basou knows which extras it owns). Existing entries are kept
2027
- * byte-identical; only appended paths are normalized.
2011
+ * - Model-usage rollup (`*_tokens`): the transcript carries per-message token
2012
+ * usage; these are the session totals. `reasoning_output_tokens` is
2013
+ * Codex-only, and live `run`/`exec` sessions carry no token usage at all.
2014
+ * - Engaged-time metrics (`active_*`): the billing-oriented active time derived
2015
+ * from the session's genuine engagement timestamps (conversation turns plus
2016
+ * action events), with idle gaps capped. `active_intervals` are the merged
2017
+ * wall-clock ranges (so cross-session totals can de-duplicate overlapping
2018
+ * work by interval union); `active_time_ms` is their summed duration;
2019
+ * `active_gap_cap_ms` and `active_time_method` lock the methodology so the
2020
+ * stored numbers stay interpretable if the method changes later. When a
2021
+ * source records explicit per-turn intervals (Codex), `active_time_method` is
2022
+ * `turn-intervals` and the in-turn time is the log's real wall-clock span
2023
+ * rather than a gap-capped approximation; the active semantics are unchanged.
2024
+ * - `machine_active_time_ms`: model compute time — the summed duration of the
2025
+ * source's per-turn spans (Codex `task_complete.duration_ms`), a SUBSET of a
2026
+ * single session's engaged active time. Unlike `active_intervals` it is a
2027
+ * plain sum, NOT wall-clock-deduplicated, so two concurrent sessions can sum
2028
+ * past their billable (union) active wall-clock — that is intended (two models
2029
+ * working at once did two machine-hours in one wall-clock hour). Captured only
2030
+ * for sources that record per-turn duration (Codex); absent otherwise.
2028
2031
  *
2029
- * Pure: no filesystem or git I/O. Paths are compared in the same normalized
2030
- * form as {@link summarizeRosterDrift}, so a trailing-slash variant of an
2031
- * already-captured repo is not re-appended.
2032
+ * Absent on sessions imported before a given field existed (re-import to
2033
+ * backfill). Live sessions carry no engaged-time metrics and fall back to
2034
+ * event-derived active time at stats time.
2032
2035
  */
2033
- declare function reconcileSourceRoots(input: {
2034
- repos?: RepoEntry[];
2035
- sourceRoots?: string[];
2036
- }): SourceRootsReconcile;
2036
+ declare const SessionMetricsSchema: z.ZodObject<{
2037
+ output_tokens: z.ZodOptional<z.ZodNumber>;
2038
+ input_tokens: z.ZodOptional<z.ZodNumber>;
2039
+ cached_input_tokens: z.ZodOptional<z.ZodNumber>;
2040
+ reasoning_output_tokens: z.ZodOptional<z.ZodNumber>;
2041
+ active_time_ms: z.ZodOptional<z.ZodNumber>;
2042
+ active_intervals: z.ZodOptional<z.ZodArray<z.ZodObject<{
2043
+ start: z.ZodString;
2044
+ end: z.ZodString;
2045
+ }, z.core.$loose>>>;
2046
+ active_gap_cap_ms: z.ZodOptional<z.ZodNumber>;
2047
+ active_time_method: z.ZodOptional<z.ZodString>;
2048
+ machine_active_time_ms: z.ZodOptional<z.ZodNumber>;
2049
+ }, z.core.$loose>;
2050
+ /** Inferred runtime type for {@link SessionMetricsSchema}. */
2051
+ type SessionMetrics = z.infer<typeof SessionMetricsSchema>;
2037
2052
  /**
2038
- * On-disk classification of a source-root candidate during adoption: a git repo
2039
- * root (→ becomes a roster entry), a resolved-but-non-repo directory (the
2040
- * generated workspace view, `/tmp`, a scratch dir excluded), or a path that
2041
- * could not be resolved on disk (→ excluded).
2053
+ * Tamper-evidence head anchor for a session whose `events.jsonl` is hash
2054
+ * chained: `head_hash` is the hex sha-256 of the last written event line
2055
+ * (excluding the trailing newline), `event_count` the number of chained lines.
2056
+ * Written by the import / in-place re-import writers and, for a live session
2057
+ * (`exec` / `run` / ad-hoc), by the finalize once it reaches a terminal status.
2058
+ * Absent on a still-live session (the anchor is stamped at finalize) and on a
2059
+ * pre-feature unchained session. Additive optional => no schema_version bump.
2060
+ * `.strict()` because the writers fully own the shape.
2042
2061
  */
2043
- type AdoptCandidateKind = "repo" | "non-repo" | "unresolved";
2044
- type AdoptCandidate = {
2045
- /** Source-root path as declared (relative to the manifest root). */
2046
- path: string;
2047
- /** On-disk classification; the filesystem probing that produces it is the caller's job. */
2048
- kind: AdoptCandidateKind;
2049
- };
2050
- type RosterAdoptionPlan = {
2051
- /** Proposed `repos` entries: the candidates that are git repos (visibility left unset for the operator). */
2052
- repos: RepoEntry[];
2053
- /** Candidates excluded from the roster, with why (a non-repo directory, or an unresolvable path). */
2054
- excluded: {
2055
- path: string;
2056
- kind: Exclude<AdoptCandidateKind, "repo">;
2057
- }[];
2058
- };
2062
+ declare const SessionIntegritySchema: z.ZodObject<{
2063
+ head_hash: z.ZodString;
2064
+ event_count: z.ZodNumber;
2065
+ }, z.core.$strict>;
2066
+ /** Inferred runtime type for {@link SessionIntegritySchema}. */
2067
+ type SessionIntegrity = z.infer<typeof SessionIntegritySchema>;
2059
2068
  /**
2060
- * Plan a `repos` roster from classified source-root candidates (the actuator
2061
- * behind `basou project adopt`). Pure: it partitions already-classified
2062
- * candidates — the realpath / `.git` filesystem probing that produces each
2063
- * `kind` is the caller's job, so this stays testable without disk I/O.
2064
- *
2065
- * A git repo becomes a roster entry (path only; visibility is left unset because
2066
- * it is a human judgment, kept independent of the other axes). A non-repo
2067
- * (commonly the generated workspace view) or an unresolvable path is excluded and
2068
- * reported, so the operator sees what was dropped and why before editing. Repo
2069
- * paths are deduped by normalized form, preserving the first declared form and
2070
- * order.
2069
+ * Schema for `.basou/sessions/<session_id>/session.yaml`. The minimal
2070
+ * session document carries the actual fields nested under the outer
2071
+ * `session:` key.
2071
2072
  */
2072
- declare function planRosterAdoption(candidates: AdoptCandidate[]): RosterAdoptionPlan;
2073
-
2074
- /**
2075
- * The language of the GENERATED-VIEW chrome (headings, labels, verdict prose)
2076
- * in handoff.md / orientation.md / decisions.md / report output.
2073
+ declare const SessionSchema: z.ZodObject<{
2074
+ schema_version: z.ZodString;
2075
+ session: z.ZodObject<{
2076
+ id: z.ZodString & z.ZodType<`ses_${string}`, string, z.core.$ZodTypeInternals<`ses_${string}`, string>>;
2077
+ label: z.ZodOptional<z.ZodString>;
2078
+ task_id: z.ZodOptional<z.ZodNullable<z.ZodString & z.ZodType<`task_${string}`, string, z.core.$ZodTypeInternals<`task_${string}`, string>>>>;
2079
+ workspace_id: z.ZodString & z.ZodType<`ws_${string}`, string, z.core.$ZodTypeInternals<`ws_${string}`, string>>;
2080
+ source: z.ZodObject<{
2081
+ kind: z.ZodEnum<{
2082
+ "claude-code-adapter": "claude-code-adapter";
2083
+ import: "import";
2084
+ "claude-code-import": "claude-code-import";
2085
+ "codex-adapter": "codex-adapter";
2086
+ "codex-import": "codex-import";
2087
+ human: "human";
2088
+ terminal: "terminal";
2089
+ }>;
2090
+ version: z.ZodLiteral<"0.1.0">;
2091
+ external_id: z.ZodOptional<z.ZodString>;
2092
+ source_size_bytes: z.ZodOptional<z.ZodNumber>;
2093
+ }, z.core.$loose>;
2094
+ started_at: z.ZodString;
2095
+ ended_at: z.ZodOptional<z.ZodString>;
2096
+ status: z.ZodEnum<{
2097
+ initialized: "initialized";
2098
+ running: "running";
2099
+ waiting_approval: "waiting_approval";
2100
+ completed: "completed";
2101
+ failed: "failed";
2102
+ interrupted: "interrupted";
2103
+ imported: "imported";
2104
+ archived: "archived";
2105
+ }>;
2106
+ working_directory: z.ZodString;
2107
+ invocation: z.ZodObject<{
2108
+ command: z.ZodString;
2109
+ args: z.ZodDefault<z.ZodArray<z.ZodString>>;
2110
+ exit_code: z.ZodNullable<z.ZodNumber>;
2111
+ }, z.core.$loose>;
2112
+ related_files: z.ZodDefault<z.ZodArray<z.ZodString>>;
2113
+ events_log: z.ZodDefault<z.ZodString>;
2114
+ summary: z.ZodOptional<z.ZodNullable<z.ZodString>>;
2115
+ metrics: z.ZodOptional<z.ZodObject<{
2116
+ output_tokens: z.ZodOptional<z.ZodNumber>;
2117
+ input_tokens: z.ZodOptional<z.ZodNumber>;
2118
+ cached_input_tokens: z.ZodOptional<z.ZodNumber>;
2119
+ reasoning_output_tokens: z.ZodOptional<z.ZodNumber>;
2120
+ active_time_ms: z.ZodOptional<z.ZodNumber>;
2121
+ active_intervals: z.ZodOptional<z.ZodArray<z.ZodObject<{
2122
+ start: z.ZodString;
2123
+ end: z.ZodString;
2124
+ }, z.core.$loose>>>;
2125
+ active_gap_cap_ms: z.ZodOptional<z.ZodNumber>;
2126
+ active_time_method: z.ZodOptional<z.ZodString>;
2127
+ machine_active_time_ms: z.ZodOptional<z.ZodNumber>;
2128
+ }, z.core.$loose>>;
2129
+ integrity: z.ZodOptional<z.ZodObject<{
2130
+ head_hash: z.ZodString;
2131
+ event_count: z.ZodNumber;
2132
+ }, z.core.$strict>>;
2133
+ }, z.core.$loose>;
2134
+ }, z.core.$loose>;
2135
+ /** Inferred runtime type for {@link SessionSchema}. */
2136
+ type Session = z.infer<typeof SessionSchema>;
2137
+
2138
+ /**
2139
+ * Threshold above which a still-`running` session with no `session_ended`
2140
+ * event is flagged suspect.
2077
2141
  *
2078
- * This is deliberately narrower than the manifest's repo `language` axis
2079
- * (`en | ja | en+ja`): a generated view has exactly one chrome language, so
2080
- * `en+ja` resolves to `en`. User data (decision titles, notes, labels, file
2081
- * paths) always passes through verbatim — only the tool-generated strings are
2082
- * localized, which is exactly the split this type exists to keep honest.
2142
+ * 24h: long enough that an active long-running session will not be flagged,
2143
+ * short enough that an abandoned process is surfaced within a working day.
2144
+ * Tunable via CLI option in a later step (continuation backlog #23).
2083
2145
  */
2084
- type ViewLanguage = "en" | "ja";
2146
+ declare const STUCK_THRESHOLD_MS: number;
2147
+ type SuspectReason = "events_say_ended_but_yaml_running" | "running_no_end_event";
2148
+ type SessionEntry = {
2149
+ sessionId: string;
2150
+ session: Session;
2151
+ suspect: boolean;
2152
+ suspectReason: SuspectReason | null;
2153
+ /**
2154
+ * The trail store this entry was read from. Its `sessions` directory locates
2155
+ * the session's `events.jsonl`, so a federated caller can replay events from
2156
+ * the store the session actually lives in (not the local store). For a plain
2157
+ * local load this is the `paths` passed to {@link loadSessionEntries}.
2158
+ */
2159
+ sourceRoot: BasouPaths;
2160
+ /**
2161
+ * Federation host label from the registry (`~/.basou/hosts.yaml`), or `null`
2162
+ * for the local store. Surfaced by orientation so a merged, multi-host view
2163
+ * can attribute the latest session / decision / next-step to its host.
2164
+ */
2165
+ host: string | null;
2166
+ };
2085
2167
  /**
2086
- * Resolve the generated-view language from a manifest: the workspace speaks
2087
- * the language of its ANCHOR repo (the `repos[]` entry whose path is `.`).
2168
+ * Per-session degradation reason emitted by {@link loadSessionEntries.onSkip}.
2088
2169
  *
2089
- * Rules (fixed by design):
2090
- * - anchor declares `ja` -> `ja`
2091
- * - anchor declares `en` / `en+ja` -> `en` (a bilingual surface renders one
2092
- * chrome; en is the shared floor)
2093
- * - no roster / no anchor entry / no declared language -> `en` (the default
2094
- * for basou's English-first OSS surface)
2170
+ * - `session_yaml_missing` (ENOENT) and `session_yaml_invalid` (parse or schema
2171
+ * failure) both omit the entry from the result.
2172
+ * - `events_jsonl_unreadable` still pushes the entry with `suspect=false` so
2173
+ * the session row remains visible to the caller; only the suspect check is
2174
+ * degraded. Matches the existing CLI behaviour at
2175
+ * `packages/cli/src/commands/session.ts` (suspect-check stderr warning).
2176
+ */
2177
+ type SessionSkipReason = "session_yaml_missing" | "session_yaml_invalid" | "events_jsonl_unreadable";
2178
+ type LoadSessionEntriesOptions = {
2179
+ /**
2180
+ * Single `now` shared across every {@link classifySuspect} call so that
2181
+ * sessions classified back-to-back observe the same instant. Avoids
2182
+ * boundary races where a session at age ≈ 24h would flip between calls.
2183
+ */
2184
+ now: Date;
2185
+ onWarning?: (warning: ReplayWarning, sessionId: string) => void;
2186
+ onSkip?: (sessionId: string, reason: SessionSkipReason) => void;
2187
+ };
2188
+ /**
2189
+ * A trail store to read in a federated load, tagged with its host label.
2190
+ * `host: null` denotes the local store; a non-null label comes from the host
2191
+ * registry (`~/.basou/hosts.yaml`). `paths` is where that store is reachable
2192
+ * as a local path on this machine (an SSHFS mount, an rsync mirror, etc.) —
2193
+ * basou itself never performs any network I/O to obtain it.
2194
+ */
2195
+ type FederatedRoot = {
2196
+ paths: BasouPaths;
2197
+ host: string | null;
2198
+ };
2199
+ type LoadFederatedOptions = LoadSessionEntriesOptions & {
2200
+ /**
2201
+ * Called when a NON-local root cannot be enumerated (present-but-unreadable
2202
+ * mount, permission error). That root is skipped best-effort so the local
2203
+ * store and other roots still load. The local root (`host: null`) is never
2204
+ * degraded here — its errors propagate, preserving single-store behaviour.
2205
+ * (An absent root path is not an error: {@link enumerateSessionDirs} returns
2206
+ * `[]` on ENOENT, so a dropped mount is simply an empty host.)
2207
+ */
2208
+ onRootUnavailable?: (host: string, error: unknown) => void;
2209
+ };
2210
+ /**
2211
+ * List session directory names under `paths.sessions`, ULID ascending.
2095
2212
  *
2096
- * Binding the view to the anchor's language is a deliberate, documented
2097
- * coupling: the anchor is the planning/trail home the views live in, so its
2098
- * declared audience is the views' audience. Other repos' languages do not
2099
- * participate.
2213
+ * - Returns `[]` when the sessions directory does not exist (empty workspace
2214
+ * or pre-init state).
2215
+ * - Throws `Error("Failed to enumerate sessions", { cause })` on other I/O.
2216
+ * - Only directories are returned (`.gitkeep` and other files are filtered).
2217
+ *
2218
+ * Sort order is `Array.prototype.sort()` default (Unicode code-point
2219
+ * compare). ULIDs are Crockford base32 in uppercase, so the natural sort
2220
+ * is also chronological session-start order.
2100
2221
  */
2101
- declare function resolveViewLanguage(manifest: Pick<Manifest, "repos"> | null): ViewLanguage;
2222
+ declare function enumerateSessionDirs(paths: BasouPaths): Promise<string[]>;
2102
2223
  /**
2103
- * Manifest-reading convenience for the renderers: resolve the view language
2104
- * for a workspace, defaulting to `en` when the manifest is missing or
2105
- * unreadable (mirrors the orientation renderer's tolerant source_roots read
2106
- * a broken manifest must never break a view render).
2224
+ * Read and validate `<paths.sessions>/<sessionId>/session.yaml`.
2225
+ *
2226
+ * - Re-throws the yaml-store fixed-message `"YAML file not found"` for
2227
+ * ENOENT so the caller can branch on it.
2228
+ * - Throws `Error("Failed to read session.yaml", { cause })` for parse
2229
+ * failures and schema violations (cause is either the YAML parser error
2230
+ * or the zod error).
2107
2231
  */
2108
- declare function resolveViewLanguageFromPaths(paths: BasouPaths): Promise<ViewLanguage>;
2232
+ declare function readSessionYaml(paths: BasouPaths, sessionId: string): Promise<Session>;
2109
2233
  /**
2110
- * Every localized string the four view renderers emit, grouped per renderer
2111
- * with a small `common` set for lines that are byte-identical across views.
2112
- * Parameterized lines are functions so the two languages can order their
2113
- * parts naturally.
2234
+ * Apply a terminal-status mutation to a live session's `session.yaml` AND, in
2235
+ * the same locked write, stamp the tamper-evidence head anchor derived from the
2236
+ * on-disk `events.jsonl` tail. Used by the `exec` / `run` orchestrators for
2237
+ * BOTH terminal writers (the normal end-of-run finalize and the spawn-failure
2238
+ * `failed` finalize).
2114
2239
  *
2115
- * This module is the SINGLE home for generated Japanese the view chrome here
2116
- * and the instruction-file content in {@link PresetStrings} (the E-5
2117
- * language-lint allowlist points here, not at the renderers/generators), so
2118
- * "user data language" and "tool-generated content language" can never blur
2119
- * together again.
2240
+ * Why locked + anchor-from-tail: live appends chain the LOG only and leave the
2241
+ * anchor for finalize. Reading the final tail under the session lock means a
2242
+ * foreign line appended just before finalize (e.g. a `decision record` attached
2243
+ * to a still-running session) is included in the anchor, and a foreign attach
2244
+ * that arrives after the terminal status is set is rejected by the attach gate
2245
+ * — so the anchor can never disagree with the at-rest log. The whole-document
2246
+ * read-modify-write also preserves any field a foreign locked writer set (e.g.
2247
+ * a task attach's `task_id`).
2248
+ *
2249
+ * The anchor is written only when the log is actually chained with at least one
2250
+ * line; a legacy unchained session (and an empty log) is left with no
2251
+ * `integrity` anchor, matching the import writers. The mutator receives the
2252
+ * full {@link Session} document and typically sets
2253
+ * `session.session.status` / `ended_at` / `invocation.exit_code` /
2254
+ * `related_files`.
2255
+ *
2256
+ * Throws the {@link inspectChainTail} errors (torn / mixed log), the
2257
+ * {@link readSessionYaml} errors, a zod error if the mutation produces an
2258
+ * invalid document, or `Error("Failed to overwrite YAML file")` on a disk
2259
+ * failure.
2120
2260
  */
2121
- type ViewStrings = {
2122
- /** Localized relative age for prose lines, e.g. "3日4時間前" / "3d 4h ago". */
2123
- relativeAge: (startedAt: string | null, now: Date) => string;
2124
- common: {
2125
- /** "最終 session" the latest live session pointer. */
2126
- lastSessionLabel: string;
2127
- /** "直近の判断" the latest recorded decision pointer. */
2128
- latestDecisionLabel: string;
2129
- /** "直近の変更ファイル" the latest session's related files. */
2130
- recentFilesLabel: string;
2131
- /** "理由" a track's rationale label. */
2132
- trackWhyLabel: string;
2133
- /** Note that the latest decision comes from a different session. */
2134
- decisionOtherSessionNote: (shortSessionId: string) => string;
2135
- };
2136
- orientation: {
2137
- headingWhere: string;
2138
- headingRecent: (sessionCount: number) => string;
2139
- headingInFlight: string;
2140
- headingForward: string;
2141
- headingCurrency: string;
2142
- inFlightTasksHeading: (n: number) => string;
2143
- /**
2144
- * Body line under the in-flight-tasks heading when NO task was ever
2145
- * recorded here. "(none)" states that nothing is pending a claim about
2146
- * the work. This one claims only what it can see: that the record is
2147
- * empty. It says nothing about whether the workspace should use tasks,
2148
- * and names no command: the renderers report position, and a nudge that
2149
- * cannot be silenced is noise (see `trackNudge`, which is gated).
2150
- */
2151
- noTasksRecorded: string;
2152
- pendingApprovalsHeading: (n: number) => string;
2153
- suspectSessionsHeading: (n: number) => string;
2154
- openTracksHeading: (n: number) => string;
2155
- /** Stale-decision honesty note under 直近の判断. */
2156
- decisionStaleNote: (activityAge: string) => string;
2157
- outOfRootWarning: (count: number, files: string) => string;
2158
- recentEmpty: string;
2159
- recentDecisionsLabel: string;
2160
- recentNextStepLabel: string;
2161
- recentChangedLabel: string;
2162
- /** Trails the recent-files line when scratch paths were left out of it. */
2163
- scratchOmitted: (count: number) => string;
2164
- trackCloseInstruction: string;
2165
- nextStepRecordedLabel: (age: string) => string;
2166
- noteStaleNote: (activityAge: string) => string;
2167
- fallbackStaleDirection: string;
2168
- fallbackStaleReferenceLabel: string;
2169
- trackNudge: string;
2170
- federatedFreshnessNote: string;
2171
- bannerUnverifiable: (n: number) => string;
2172
- bannerStale: (parts: string) => string;
2173
- partNew: (n: number) => string;
2174
- partUpdated: (n: number) => string;
2175
- partsJoiner: string;
2176
- verdictUnverifiable: (n: number) => [string, string];
2177
- verdictStale: (parts: string) => [string, string];
2178
- verdictUpdatedOnly: (n: number) => [string, string];
2179
- verdictSuspectsAlso: (n: number) => string;
2180
- verdictEmpty: [string, string];
2181
- verdictUnprobed: (rel: string, tool: string) => [string, string];
2182
- verdictCurrent: (rel: string, tool: string, hasHosts: boolean) => string;
2183
- verdictSuspectsCaveat: (n: number) => string;
2184
- verdictScopeDisclaimer: string;
2185
- toolTerminal: string;
2186
- toolHuman: string;
2187
- toolImport: string;
2188
- toolUnknown: string;
2189
- };
2190
- handoff: {
2191
- headingCurrentState: string;
2192
- headingRecentFiles: string;
2193
- headingLatestDecision: string;
2194
- headingOpenTracks: string;
2195
- headingUnresolved: string;
2196
- headingReadNext: string;
2197
- headingNextWork: string;
2198
- headingSessions: string;
2199
- lastTaskLabel: string;
2200
- /** "Work to do next" placeholder: tasks exist, none are open. */
2201
- noPendingTasks: string;
2202
- /** "Work to do next" placeholder: no task was ever recorded. */
2203
- noTasksRecorded: string;
2204
- decisionStaleNote: string;
2205
- trackCloseInstruction: string;
2206
- };
2207
- decisions: {
2208
- dateLabel: string;
2209
- trackKindLine: string;
2210
- decisionLabel: string;
2211
- };
2212
- report: {
2213
- headingSummary: string;
2214
- headingVolume: string;
2215
- headingDecisions: string;
2216
- headingApprovals: string;
2217
- headingTasks: string;
2218
- headingChangedFiles: string;
2219
- headingSessions: string;
2220
- headingIntegrity: string;
2221
- };
2261
+ declare function finalizeSessionYaml(paths: BasouPaths, sessionId: string, mutate: (session: Session) => void): Promise<void>;
2262
+ /**
2263
+ * Classify a `running` session as suspect using one of two rules:
2264
+ *
2265
+ * - Rule A (`events_say_ended_but_yaml_running`): events.jsonl contains a
2266
+ * `session_ended` event but the session.yaml is still `running`. The
2267
+ * session ended cleanly in the event log but the YAML write was lost or
2268
+ * never reached.
2269
+ * - Rule B (`running_no_end_event`): no `session_ended` event and the last
2270
+ * event is older than {@link STUCK_THRESHOLD_MS}. The process likely
2271
+ * crashed or was killed.
2272
+ *
2273
+ * Sessions that are not `running` are never suspect.
2274
+ *
2275
+ * I/O failure on events.jsonl is re-thrown unwrapped so the caller can
2276
+ * degrade with a warning instead of treating the session as healthy. The
2277
+ * caller is also responsible for surfacing replay warnings via `onWarning`.
2278
+ */
2279
+ declare function classifySuspect(paths: BasouPaths, sessionId: string, session: Session, now: Date, onWarning?: (warning: ReplayWarning) => void): Promise<{
2280
+ suspect: boolean;
2281
+ suspectReason: SuspectReason | null;
2282
+ }>;
2283
+ declare function loadSessionEntries(paths: BasouPaths, options: LoadSessionEntriesOptions): Promise<SessionEntry[]>;
2284
+ /**
2285
+ * Federated load across multiple trail stores. Each root's sessions are tagged
2286
+ * with that root's host label and `sourceRoot`, so a caller replays events from
2287
+ * the store the session lives in. De-duped by `sessionId` (a per-host random
2288
+ * ULID), then by `source.external_id` when present first occurrence wins, so
2289
+ * pass the local root FIRST to keep it authoritative (e.g. over a re-imported
2290
+ * copy of the same vendor session on another host). A non-local root that
2291
+ * cannot be enumerated is reported via `onRootUnavailable` and skipped; the
2292
+ * local root's errors propagate, matching {@link loadSessionEntries}.
2293
+ */
2294
+ declare function loadFederatedSessionEntries(roots: ReadonlyArray<FederatedRoot>, options: LoadFederatedOptions): Promise<SessionEntry[]>;
2295
+
2296
+ /**
2297
+ * Decision-gap surfacer: which recorded decisions are still waiting for someone.
2298
+ *
2299
+ * The rule it applies, stated by the report that motivated it: a decision that
2300
+ * changes what gets built should have a task carrying it, and the ones with no
2301
+ * task are listed. It reads only captured provenance and writes nothing.
2302
+ *
2303
+ * The question is a RELATION — "is any task carrying this decision" — and the
2304
+ * text scan below is only today's way of answering it. Stating it that way is
2305
+ * deliberate: when `task_created` grows a field naming the decision it serves,
2306
+ * honouring that field answers the same question better and is an
2307
+ * implementation change, where redefining the question would not be.
2308
+ *
2309
+ * Two properties are load-bearing:
2310
+ *
2311
+ * - Nothing here reads what a decision MEANS, so the answer does not depend on
2312
+ * a model and does not change between runs over the same store.
2313
+ * - The default is fail-closed: a decision with no task is listed. An opt-in
2314
+ * mark ("this one produces work") was rejected upstream because forgetting
2315
+ * the mark is the same failure the report exists to catch.
2316
+ *
2317
+ * Fail-closed only pays for itself if the list is drainable, so the population
2318
+ * is cut on four structural grounds — a timestamp, a `source`, a `kind`, and a
2319
+ * void. None requires reading meaning, and each is reported as a count so the
2320
+ * cut is never silent:
2321
+ *
2322
+ * - {@link DECISION_GAPS_EPOCH}: decisions predating this feature are out of
2323
+ * scope. Applying the rule to a whole history measured ~1450 entries on the
2324
+ * store it was built against, which buries the signal on day one.
2325
+ * - {@link LOCAL_CLI_EVENT_SOURCE}: only decisions somebody recorded by
2326
+ * running basou (`basou decision capture` or `basou decision record`), as
2327
+ * opposed to ones a reader derived. An importer derives decisions from a
2328
+ * transcript's in-conversation questions ("how far should I implement? ->
2329
+ * findings 1 and 3"), which were 70% of that store's decisions and 0 of the
2330
+ * ones any task carried. Those are answers given while working, not plans
2331
+ * anybody ratified.
2332
+ * - `kind: "track"`: a track is ALREADY resurfaced, every session, until it is
2333
+ * voided — orientation and handoff both carry it under "open tracks". For a
2334
+ * track the premise "recorded, then never surfaced again" is false by
2335
+ * construction, so listing it here says nothing the reader is not already
2336
+ * shown, and it measured 27-45% of the list.
2337
+ * - voided: `basou decision void` is the closing verb this product tells the
2338
+ * operator to use, and a direction no longer in force is not waiting for
2339
+ * anyone. Honouring it everywhere gives the operator ONE verb that closes;
2340
+ * refusing it here would leave a list drainable only by writing a sham task.
2341
+ *
2342
+ * What it will not do is decide a decision's fate on anything it cannot check.
2343
+ * A task naming an id that no decision in the store has does NOT count as
2344
+ * carrying it, and a store it could only partly read says so rather than
2345
+ * reporting a clean answer over the part it managed.
2346
+ */
2347
+ /**
2348
+ * Start of the population: decisions recorded before this instant are out of
2349
+ * scope, whatever else is true of them.
2350
+ *
2351
+ * A fixed constant rather than per-workspace state, so every workspace answers
2352
+ * the same question and nothing has to be initialised or migrated. The value is
2353
+ * the instant this was written; a release landing later, or a workspace first
2354
+ * running it later, only means that run starts with the decisions recorded in
2355
+ * between, and {@link DecisionGapsSummary.scope} reports the boundary on every
2356
+ * run so the head start is never silent. `--since` overrides it.
2357
+ */
2358
+ declare const DECISION_GAPS_EPOCH = "2026-09-18T12:00:00.000Z";
2359
+ /** A decision in the population that no task carries. */
2360
+ type DecisionGap = {
2361
+ decisionId: string;
2362
+ title: string;
2363
+ recordedAt: string;
2364
+ sessionId: string;
2365
+ };
2366
+ /** The population's boundaries, as the run actually applied them. */
2367
+ type DecisionGapsScope = {
2368
+ /** Decisions recorded before this instant are out of scope. */
2369
+ start: string;
2370
+ /** Only decisions this event source recorded are in scope. */
2371
+ source: string;
2222
2372
  };
2223
- /** Look up the string table for a resolved view language. */
2224
- declare function viewStrings(language: ViewLanguage): ViewStrings;
2225
2373
  /**
2226
- * Resolve a GENERATED INSTRUCTION-FILE's content language from the target
2227
- * repo's declared `language`. Unlike the views (workspace-level artifacts that
2228
- * follow the anchor), a preset block lives inside one repo's instruction file,
2229
- * so its audience is that repo's declared audience: `ja` renders Japanese
2230
- * (byte-identical to the pre-i18n output), `en` / `en+ja` / undeclared render
2231
- * English (one content language per generated block; en is the shared floor).
2374
+ * Why decisions left the population, by ground. The grounds are applied in this
2375
+ * order and each decision is counted under the FIRST that excludes it, so these
2376
+ * partition the excluded set.
2232
2377
  */
2233
- declare function resolveRepoContentLanguage(language: RepoLanguage | undefined): ViewLanguage;
2378
+ type DecisionGapsExcluded = {
2379
+ /** Recorded before {@link DecisionGapsScope.start}. */
2380
+ byStart: number;
2381
+ /** Recorded by something other than {@link DecisionGapsScope.source}. */
2382
+ bySource: number;
2383
+ /** `kind: "track"` — already resurfaced every session until closed. */
2384
+ track: number;
2385
+ /** Closed with `basou decision void`. */
2386
+ voided: number;
2387
+ };
2234
2388
  /**
2235
- * Resolve the content language of a WORKSPACE-LEVEL instruction artifact (the
2236
- * view's AGENTS.md block, the anchor's starter) from an already-gathered
2237
- * roster: the entry flagged `anchor` speaks for the workspace, mirroring the
2238
- * views' anchor-language rule. No anchor entry (or no declared language)
2239
- * resolves to English. When more than one entry carries the flag, the first
2240
- * wins (declared order).
2389
+ * What the run could not read. Every field here can only make {@link
2390
+ * DecisionGapsSummary.gaps} wrong in a way the reader cannot see, so a non-zero
2391
+ * count is reported rather than absorbed.
2392
+ */
2393
+ type DecisionGapsIncomplete = {
2394
+ /**
2395
+ * Sessions this run could not read in full — a `session.yaml` that is missing
2396
+ * or does not validate, or an event log that failed to read.
2397
+ *
2398
+ * What was lost is not knowable from here: a log that fails partway through
2399
+ * has already yielded some events, so a session counted here may have
2400
+ * contributed everything, nothing, or part of what it holds. A decision of
2401
+ * theirs can therefore be missing from every count, and a void of theirs may
2402
+ * not have closed what it closes — so both a missing row and a wrongly
2403
+ * present one are possible.
2404
+ */
2405
+ sessions: number;
2406
+ /** Task files that could not be read, so any decision they carry looks uncarried. */
2407
+ tasks: number;
2408
+ /**
2409
+ * Ids named by a task that no decision in the store has. Counted, and
2410
+ * deliberately NOT treated as carrying anything: a string nobody recorded
2411
+ * must not be able to take a decision off this list.
2412
+ */
2413
+ unknownReferences: number;
2414
+ };
2415
+ type DecisionGapsSummary = {
2416
+ generatedAt: string;
2417
+ scope: DecisionGapsScope;
2418
+ /** Decisions with no task carrying them, newest first. Capped by `limit`. */
2419
+ gaps: DecisionGap[];
2420
+ /** Entries omitted from `gaps` by `limit`; 0 when nothing was cut. */
2421
+ truncated: number;
2422
+ /** Population members some task carries. */
2423
+ carried: number;
2424
+ /** `gaps.length + truncated + carried`. */
2425
+ populationCount: number;
2426
+ excluded: DecisionGapsExcluded;
2427
+ incomplete: DecisionGapsIncomplete;
2428
+ /** Task files read (live and archived). */
2429
+ tasksScanned: number;
2430
+ };
2431
+ type DecisionGapsInput = {
2432
+ paths: BasouPaths;
2433
+ nowIso: string;
2434
+ /** Defaults to {@link DECISION_GAPS_EPOCH}. */
2435
+ start?: string;
2436
+ /**
2437
+ * Maximum entries in `gaps`; the rest are counted in `truncated`. `0` yields
2438
+ * an empty `gaps` with everything in `truncated` — it is a cap of zero, not
2439
+ * "uncapped". Omit the field for uncapped. (The CLI's `--limit 0` means
2440
+ * uncapped and omits this.)
2441
+ */
2442
+ limit?: number;
2443
+ onWarning?: (warning: ReplayWarning, sessionId: string) => void;
2444
+ onSessionSkip?: (sessionId: string, reason: SessionSkipReason) => void;
2445
+ };
2446
+ /**
2447
+ * Find decisions in the population that no task carries.
2241
2448
  *
2242
- * Note the anchor is identified by the CALLER-SET flag, not by this module:
2243
- * {@link resolveViewLanguage} keys on the manifest path being `.`, while the
2244
- * instruction-file callers flag the anchor by resolved-path identity. For a
2245
- * conventional manifest (anchor declared as `.`) the two agree; a roster that
2246
- * reaches the anchor only through an aliased path is where they can diverge,
2247
- * and the caller's flag is authoritative for the instruction files.
2449
+ * Ordering is `recordedAt` descending with the decision id (a ULID, so
2450
+ * monotonic) as tie-breaker, giving a stable newest-first list.
2451
+ */
2452
+ declare function findDecisionGaps(input: DecisionGapsInput): Promise<DecisionGapsSummary>;
2453
+
2454
+ /**
2455
+ * Project roster drift (the "saddle" model). A project's repos are DECLARED
2456
+ * once in the manifest's `repos` list; the capture config (`source_roots`) must
2457
+ * cover every declared repo. This computes the drift between the two so
2458
+ * `basou project check` can surface a declared repo that is NOT being captured
2459
+ * — the class of bug where a companion repo was wired into the workspace but
2460
+ * never added to `source_roots`, so its work silently fell out of capture.
2461
+ *
2462
+ * Pure: it compares declared relative paths against captured relative paths and
2463
+ * performs no filesystem or git I/O. Paths are compared as declared (both lists
2464
+ * use the same machine-portable relative-path form), not resolved on disk.
2465
+ */
2466
+ type RepoVisibility = "public" | "private" | "future-public";
2467
+ /**
2468
+ * The audience-driven language axis. Independent of visibility: a private repo
2469
+ * can publish English content, a public repo can carry bilingual docs. `en` /
2470
+ * `ja` for a single audience, `en+ja` when both are served.
2471
+ */
2472
+ type RepoLanguage = "en" | "ja" | "en+ja";
2473
+ /** A published surface a repo emits: a deployed website or a package registry. */
2474
+ type PublishKind = "web" | "npm";
2475
+ /**
2476
+ * Where a repo's agent instruction files live (the instruction-source axis),
2477
+ * independent of visibility / language / publishes. `hub` is basou's native,
2478
+ * generated hub-and-spoke topology (canonical in the anchor, gitignored symlinks
2479
+ * in each repo); `self` is the additive opt-in where the canonical AGENTS.md is a
2480
+ * regular committed file in the repo itself and basou stays hands-off about its
2481
+ * content. See {@link instructionMode} for the default (absent => `hub`).
2248
2482
  */
2249
- declare function resolveAnchorContentLanguage(repos: ReadonlyArray<{
2250
- anchor?: boolean | undefined;
2483
+ type RepoInstructions = "hub" | "self";
2484
+ /**
2485
+ * One published surface. Its visibility and language are INDEPENDENT of the
2486
+ * source repo's: a private repo commonly publishes a public website. Both are
2487
+ * optional so a surface can be declared
2488
+ * before those facts are pinned down (mirroring how `adopt` leaves repo
2489
+ * visibility unset for the operator to fill in).
2490
+ */
2491
+ type PublishTarget = {
2492
+ kind: PublishKind;
2493
+ visibility?: RepoVisibility | undefined;
2251
2494
  language?: RepoLanguage | undefined;
2252
- }>): ViewLanguage;
2495
+ };
2496
+ type RepoEntry = {
2497
+ /** Path relative to the manifest repo root (e.g. ".", "../takuhon"). */
2498
+ path: string;
2499
+ visibility?: RepoVisibility | undefined;
2500
+ /** Source language (commits/comments/code, read by contributors). Independent of visibility. */
2501
+ language?: RepoLanguage | undefined;
2502
+ /** Published surfaces this repo emits (opt-in; absent for a repo that publishes nothing). */
2503
+ publishes?: PublishTarget[] | undefined;
2504
+ /**
2505
+ * Instruction-source mode. Absent => `hub` (basou's native generated topology),
2506
+ * so an existing roster's behavior is unchanged. `self` opts the repo out of
2507
+ * generation: its AGENTS.md is a hand-authored committed file and basou stays
2508
+ * hands-off. Resolve the effective mode with {@link instructionMode}.
2509
+ */
2510
+ instructions?: RepoInstructions | undefined;
2511
+ };
2253
2512
  /**
2254
- * Every localized string the instruction-file generators emit: the per-repo
2255
- * preset block, the workspace view's block, and the anchor's starter. Lives in
2256
- * this module for the same reason as {@link ViewStrings}: it is the SINGLE
2257
- * home for generated Japanese, so the language-lint E-5 allowlist stays one
2258
- * file and "generated content language" is always a declaration-driven table
2259
- * lookup, never a hardcode.
2513
+ * The effective instruction-source mode for a repo: the declared `instructions`,
2514
+ * defaulting to `hub` when absent. The default is the single guarantee that an
2515
+ * existing roster (which has no `instructions` field) keeps basou's current
2516
+ * hub-and-spoke behavior byte-for-byte every generator branches on this, never
2517
+ * on the raw optional field, so "absent => hub" is decided in exactly one place.
2260
2518
  */
2261
- type PresetStrings = {
2262
- repoBlock: {
2263
- heading: string;
2264
- intro: string;
2265
- /** Source git-visibility, rendered with the consequence the agent must respect. */
2266
- visibilityLabel: (v: RepoVisibility | undefined) => string;
2267
- /**
2268
- * Source language (commits/comments/code), rendered with the audience it
2269
- * serves. Invariant note: the table itself is SELECTED by this same field
2270
- * (ja -> JA table, everything else -> EN), so the JA table's en / en+ja /
2271
- * unset branches and the EN table's ja branch are unreachable from
2272
- * renderPresetBlock — they exist for table completeness (and the
2273
- * both-language sweep test), not because a render can emit them.
2274
- */
2275
- sourceLanguageLabel: (l: RepoLanguage | undefined) => string;
2276
- /** Published-surface kind. */
2277
- publishKindLabel: (k: PublishTarget["kind"]) => string;
2278
- /** A published surface's visibility (independent of the source repo's). */
2279
- publishVisibilityLabel: (v: RepoVisibility | undefined) => string;
2280
- /** A published surface's content language (read by end users; may differ from source). */
2281
- contentLanguageLabel: (l: RepoLanguage | undefined) => string;
2282
- /** "ソース可視性" — the source-visibility line label. */
2283
- sourceVisibilityLabel: string;
2284
- /** "ソース言語" — the source-language line label. */
2285
- sourceLanguageLineLabel: string;
2286
- /** "- 配信物: なし" — no published surfaces. */
2287
- publishesNone: string;
2288
- /** "- 配信物:" — the published-surfaces list header. */
2289
- publishesHeader: string;
2290
- };
2291
- viewBlock: {
2292
- heading: string;
2293
- intro: string;
2294
- selfNote: (viewName: string) => string;
2295
- aggregates: (repoCount: number) => string;
2296
- reposHeading: string;
2297
- tableHeader: string;
2298
- /** Instruction-file ownership labels: who writes the repo's AGENTS.md. */
2299
- instructionsAnchor: string;
2300
- instructionsSelf: string;
2301
- instructionsHub: string;
2302
- /** "未設定" — the short table cell for an undeclared visibility / language. */
2303
- unsetShort: string;
2304
- commitHeading: string;
2305
- commitBody: string;
2306
- conventionsHeading: string;
2307
- conventionsBody: string;
2308
- principlesHeading: string;
2309
- principleStateless: string;
2310
- principleNoFiles: string;
2311
- };
2312
- anchorStarter: {
2313
- identityLine: (title: string) => string;
2314
- starterNote: string;
2315
- basicsHeading: string;
2316
- basicsTodo: string;
2317
- commitHeading: string;
2318
- commitPlanning: string;
2319
- commitImplementation: string;
2320
- commitView: string;
2321
- conventionsHeading: string;
2322
- conventionsBody: string;
2323
- viewPointerLine: (viewName: string) => string;
2324
- policyHeading: string;
2325
- policyTodo: string[];
2326
- };
2519
+ declare function instructionMode(entry: {
2520
+ instructions?: RepoInstructions | undefined;
2521
+ }): RepoInstructions;
2522
+ type RosterDriftSummary = {
2523
+ declaredCount: number;
2524
+ capturedCount: number;
2525
+ /** Declared in `repos` but absent from `source_roots`: a capture gap. */
2526
+ gaps: RepoEntry[];
2527
+ /** In `source_roots` but not declared in `repos` (e.g. a workspace view, or a stray). */
2528
+ extra: string[];
2529
+ /** Declared paths that are also captured. */
2530
+ matched: string[];
2531
+ /** True when there is no capture gap (every declared repo is covered). */
2532
+ ok: boolean;
2327
2533
  };
2328
- /** Look up the instruction-file string table for a resolved content language. */
2329
- declare function presetStrings(language: ViewLanguage): PresetStrings;
2330
-
2331
- /** Session lifecycle states. */
2332
- declare const SessionStatusSchema: z.ZodEnum<{
2333
- initialized: "initialized";
2334
- running: "running";
2335
- waiting_approval: "waiting_approval";
2336
- completed: "completed";
2337
- failed: "failed";
2338
- interrupted: "interrupted";
2339
- imported: "imported";
2340
- archived: "archived";
2341
- }>;
2342
- /** Inferred runtime type for {@link SessionStatusSchema}. */
2343
- type SessionStatus = z.infer<typeof SessionStatusSchema>;
2344
2534
  /**
2345
- * Source kind that produced the session.
2346
- *
2347
- * - `claude-code-adapter` a live `basou run claude-code` process wrap.
2348
- * - `claude-code-import` derived after the fact from a Claude Code native
2349
- * transcript (`~/.claude/projects/*.jsonl`) by `basou import claude-code`.
2350
- * - `codex-adapter` — a live `basou run codex` process wrap.
2351
- * - `codex-import` — derived after the fact from an OpenAI Codex native
2352
- * rollout log (date-partitioned `~/.codex/sessions`) by `basou import codex`.
2353
- * - `import` — a round-trip of a Basou-format export (`basou session import`).
2354
- * - `human` / `terminal` — manually-authored / terminal-recorded sessions.
2535
+ * Compute the {@link RosterDriftSummary} for a project. A declared repo missing
2536
+ * from the captured set is a `gap` (the surfaced suspicion); a captured path not
2537
+ * in the declared set is `extra` (commonly the workspace view, which is a
2538
+ * capture source but not itself a project repo). With no declared roster, there
2539
+ * are no gaps (nothing to check against) and every captured path is `extra`.
2355
2540
  */
2356
- declare const SessionSourceKindSchema: z.ZodEnum<{
2357
- "claude-code-adapter": "claude-code-adapter";
2358
- import: "import";
2359
- "claude-code-import": "claude-code-import";
2360
- "codex-adapter": "codex-adapter";
2361
- "codex-import": "codex-import";
2362
- human: "human";
2363
- terminal: "terminal";
2364
- }>;
2365
- /** Inferred runtime type for {@link SessionSourceKindSchema}. */
2366
- type SessionSourceKind = z.infer<typeof SessionSourceKindSchema>;
2541
+ declare function summarizeRosterDrift(input: {
2542
+ repos?: RepoEntry[];
2543
+ sourceRoots?: string[];
2544
+ }): RosterDriftSummary;
2545
+ type SourceRootsReconcile = {
2546
+ /**
2547
+ * The reconciled `source_roots`: the existing entries verbatim, then every
2548
+ * declared repo path that was missing (normalized, in roster order). Existing
2549
+ * order and form are preserved so the manifest diff is minimal and reversible.
2550
+ */
2551
+ next: string[];
2552
+ /** Declared repo paths (normalized) that were appended because `source_roots` did not cover them. */
2553
+ added: string[];
2554
+ /** True when `source_roots` already covers every declared repo (`next` equals the current list). */
2555
+ unchanged: boolean;
2556
+ };
2367
2557
  /**
2368
- * Optional per-session metrics, computed at import time from the source tool's
2369
- * native log. Two groups, both optional because not every source records them:
2558
+ * Derive the `source_roots` a project's declared repo roster requires. The
2559
+ * roster (`repos`) is the single source of truth for which repos belong to the
2560
+ * project; this is the actuator behind `basou project sync`, computing the
2561
+ * additive reconciliation so every declared repo is captured.
2370
2562
  *
2371
- * - Model-usage rollup (`*_tokens`): the transcript carries per-message token
2372
- * usage; these are the session totals. `reasoning_output_tokens` is
2373
- * Codex-only, and live `run`/`exec` sessions carry no token usage at all.
2374
- * - Engaged-time metrics (`active_*`): the billing-oriented active time derived
2375
- * from the session's genuine engagement timestamps (conversation turns plus
2376
- * action events), with idle gaps capped. `active_intervals` are the merged
2377
- * wall-clock ranges (so cross-session totals can de-duplicate overlapping
2378
- * work by interval union); `active_time_ms` is their summed duration;
2379
- * `active_gap_cap_ms` and `active_time_method` lock the methodology so the
2380
- * stored numbers stay interpretable if the method changes later. When a
2381
- * source records explicit per-turn intervals (Codex), `active_time_method` is
2382
- * `turn-intervals` and the in-turn time is the log's real wall-clock span
2383
- * rather than a gap-capped approximation; the active semantics are unchanged.
2384
- * - `machine_active_time_ms`: model compute time — the summed duration of the
2385
- * source's per-turn spans (Codex `task_complete.duration_ms`), a SUBSET of a
2386
- * single session's engaged active time. Unlike `active_intervals` it is a
2387
- * plain sum, NOT wall-clock-deduplicated, so two concurrent sessions can sum
2388
- * past their billable (union) active wall-clock — that is intended (two models
2389
- * working at once did two machine-hours in one wall-clock hour). Captured only
2390
- * for sources that record per-turn duration (Codex); absent otherwise.
2563
+ * ADDITIVE ONLY: it appends declared paths that are missing and never removes
2564
+ * an existing entry. A captured-but-undeclared path (commonly the generated
2565
+ * workspace view a legitimate capture source that is not itself a project
2566
+ * repo) is preserved; pruning strays is deferred to the slice that generates
2567
+ * the view (so basou knows which extras it owns). Existing entries are kept
2568
+ * byte-identical; only appended paths are normalized.
2391
2569
  *
2392
- * Absent on sessions imported before a given field existed (re-import to
2393
- * backfill). Live sessions carry no engaged-time metrics and fall back to
2394
- * event-derived active time at stats time.
2570
+ * Pure: no filesystem or git I/O. Paths are compared in the same normalized
2571
+ * form as {@link summarizeRosterDrift}, so a trailing-slash variant of an
2572
+ * already-captured repo is not re-appended.
2395
2573
  */
2396
- declare const SessionMetricsSchema: z.ZodObject<{
2397
- output_tokens: z.ZodOptional<z.ZodNumber>;
2398
- input_tokens: z.ZodOptional<z.ZodNumber>;
2399
- cached_input_tokens: z.ZodOptional<z.ZodNumber>;
2400
- reasoning_output_tokens: z.ZodOptional<z.ZodNumber>;
2401
- active_time_ms: z.ZodOptional<z.ZodNumber>;
2402
- active_intervals: z.ZodOptional<z.ZodArray<z.ZodObject<{
2403
- start: z.ZodString;
2404
- end: z.ZodString;
2405
- }, z.core.$loose>>>;
2406
- active_gap_cap_ms: z.ZodOptional<z.ZodNumber>;
2407
- active_time_method: z.ZodOptional<z.ZodString>;
2408
- machine_active_time_ms: z.ZodOptional<z.ZodNumber>;
2409
- }, z.core.$loose>;
2410
- /** Inferred runtime type for {@link SessionMetricsSchema}. */
2411
- type SessionMetrics = z.infer<typeof SessionMetricsSchema>;
2574
+ declare function reconcileSourceRoots(input: {
2575
+ repos?: RepoEntry[];
2576
+ sourceRoots?: string[];
2577
+ }): SourceRootsReconcile;
2412
2578
  /**
2413
- * Tamper-evidence head anchor for a session whose `events.jsonl` is hash
2414
- * chained: `head_hash` is the hex sha-256 of the last written event line
2415
- * (excluding the trailing newline), `event_count` the number of chained lines.
2416
- * Written by the import / in-place re-import writers and, for a live session
2417
- * (`exec` / `run` / ad-hoc), by the finalize once it reaches a terminal status.
2418
- * Absent on a still-live session (the anchor is stamped at finalize) and on a
2419
- * pre-feature unchained session. Additive optional => no schema_version bump.
2420
- * `.strict()` because the writers fully own the shape.
2579
+ * On-disk classification of a source-root candidate during adoption: a git repo
2580
+ * root (→ becomes a roster entry), a resolved-but-non-repo directory (the
2581
+ * generated workspace view, `/tmp`, a scratch dir → excluded), or a path that
2582
+ * could not be resolved on disk (→ excluded).
2421
2583
  */
2422
- declare const SessionIntegritySchema: z.ZodObject<{
2423
- head_hash: z.ZodString;
2424
- event_count: z.ZodNumber;
2425
- }, z.core.$strict>;
2426
- /** Inferred runtime type for {@link SessionIntegritySchema}. */
2427
- type SessionIntegrity = z.infer<typeof SessionIntegritySchema>;
2584
+ type AdoptCandidateKind = "repo" | "non-repo" | "unresolved";
2585
+ type AdoptCandidate = {
2586
+ /** Source-root path as declared (relative to the manifest root). */
2587
+ path: string;
2588
+ /** On-disk classification; the filesystem probing that produces it is the caller's job. */
2589
+ kind: AdoptCandidateKind;
2590
+ };
2591
+ type RosterAdoptionPlan = {
2592
+ /** Proposed `repos` entries: the candidates that are git repos (visibility left unset for the operator). */
2593
+ repos: RepoEntry[];
2594
+ /** Candidates excluded from the roster, with why (a non-repo directory, or an unresolvable path). */
2595
+ excluded: {
2596
+ path: string;
2597
+ kind: Exclude<AdoptCandidateKind, "repo">;
2598
+ }[];
2599
+ };
2428
2600
  /**
2429
- * Schema for `.basou/sessions/<session_id>/session.yaml`. The minimal
2430
- * session document carries the actual fields nested under the outer
2431
- * `session:` key.
2601
+ * Plan a `repos` roster from classified source-root candidates (the actuator
2602
+ * behind `basou project adopt`). Pure: it partitions already-classified
2603
+ * candidates — the realpath / `.git` filesystem probing that produces each
2604
+ * `kind` is the caller's job, so this stays testable without disk I/O.
2605
+ *
2606
+ * A git repo becomes a roster entry (path only; visibility is left unset because
2607
+ * it is a human judgment, kept independent of the other axes). A non-repo
2608
+ * (commonly the generated workspace view) or an unresolvable path is excluded and
2609
+ * reported, so the operator sees what was dropped and why before editing. Repo
2610
+ * paths are deduped by normalized form, preserving the first declared form and
2611
+ * order.
2432
2612
  */
2433
- declare const SessionSchema: z.ZodObject<{
2434
- schema_version: z.ZodString;
2435
- session: z.ZodObject<{
2436
- id: z.ZodString & z.ZodType<`ses_${string}`, string, z.core.$ZodTypeInternals<`ses_${string}`, string>>;
2437
- label: z.ZodOptional<z.ZodString>;
2438
- task_id: z.ZodOptional<z.ZodNullable<z.ZodString & z.ZodType<`task_${string}`, string, z.core.$ZodTypeInternals<`task_${string}`, string>>>>;
2439
- workspace_id: z.ZodString & z.ZodType<`ws_${string}`, string, z.core.$ZodTypeInternals<`ws_${string}`, string>>;
2440
- source: z.ZodObject<{
2441
- kind: z.ZodEnum<{
2442
- "claude-code-adapter": "claude-code-adapter";
2443
- import: "import";
2444
- "claude-code-import": "claude-code-import";
2445
- "codex-adapter": "codex-adapter";
2446
- "codex-import": "codex-import";
2447
- human: "human";
2448
- terminal: "terminal";
2449
- }>;
2450
- version: z.ZodLiteral<"0.1.0">;
2451
- external_id: z.ZodOptional<z.ZodString>;
2452
- source_size_bytes: z.ZodOptional<z.ZodNumber>;
2453
- }, z.core.$loose>;
2454
- started_at: z.ZodString;
2455
- ended_at: z.ZodOptional<z.ZodString>;
2456
- status: z.ZodEnum<{
2457
- initialized: "initialized";
2458
- running: "running";
2459
- waiting_approval: "waiting_approval";
2460
- completed: "completed";
2461
- failed: "failed";
2462
- interrupted: "interrupted";
2463
- imported: "imported";
2464
- archived: "archived";
2465
- }>;
2466
- working_directory: z.ZodString;
2467
- invocation: z.ZodObject<{
2468
- command: z.ZodString;
2469
- args: z.ZodDefault<z.ZodArray<z.ZodString>>;
2470
- exit_code: z.ZodNullable<z.ZodNumber>;
2471
- }, z.core.$loose>;
2472
- related_files: z.ZodDefault<z.ZodArray<z.ZodString>>;
2473
- events_log: z.ZodDefault<z.ZodString>;
2474
- summary: z.ZodOptional<z.ZodNullable<z.ZodString>>;
2475
- metrics: z.ZodOptional<z.ZodObject<{
2476
- output_tokens: z.ZodOptional<z.ZodNumber>;
2477
- input_tokens: z.ZodOptional<z.ZodNumber>;
2478
- cached_input_tokens: z.ZodOptional<z.ZodNumber>;
2479
- reasoning_output_tokens: z.ZodOptional<z.ZodNumber>;
2480
- active_time_ms: z.ZodOptional<z.ZodNumber>;
2481
- active_intervals: z.ZodOptional<z.ZodArray<z.ZodObject<{
2482
- start: z.ZodString;
2483
- end: z.ZodString;
2484
- }, z.core.$loose>>>;
2485
- active_gap_cap_ms: z.ZodOptional<z.ZodNumber>;
2486
- active_time_method: z.ZodOptional<z.ZodString>;
2487
- machine_active_time_ms: z.ZodOptional<z.ZodNumber>;
2488
- }, z.core.$loose>>;
2489
- integrity: z.ZodOptional<z.ZodObject<{
2490
- head_hash: z.ZodString;
2491
- event_count: z.ZodNumber;
2492
- }, z.core.$strict>>;
2493
- }, z.core.$loose>;
2494
- }, z.core.$loose>;
2495
- /** Inferred runtime type for {@link SessionSchema}. */
2496
- type Session = z.infer<typeof SessionSchema>;
2613
+ declare function planRosterAdoption(candidates: AdoptCandidate[]): RosterAdoptionPlan;
2497
2614
 
2498
2615
  /**
2499
- * Threshold above which a still-`running` session with no `session_ended`
2500
- * event is flagged suspect.
2616
+ * The language of the GENERATED-VIEW chrome (headings, labels, verdict prose)
2617
+ * in handoff.md / orientation.md / decisions.md / report output.
2501
2618
  *
2502
- * 24h: long enough that an active long-running session will not be flagged,
2503
- * short enough that an abandoned process is surfaced within a working day.
2504
- * Tunable via CLI option in a later step (continuation backlog #23).
2619
+ * This is deliberately narrower than the manifest's repo `language` axis
2620
+ * (`en | ja | en+ja`): a generated view has exactly one chrome language, so
2621
+ * `en+ja` resolves to `en`. User data (decision titles, notes, labels, file
2622
+ * paths) always passes through verbatim — only the tool-generated strings are
2623
+ * localized, which is exactly the split this type exists to keep honest.
2505
2624
  */
2506
- declare const STUCK_THRESHOLD_MS: number;
2507
- type SuspectReason = "events_say_ended_but_yaml_running" | "running_no_end_event";
2508
- type SessionEntry = {
2509
- sessionId: string;
2510
- session: Session;
2511
- suspect: boolean;
2512
- suspectReason: SuspectReason | null;
2513
- /**
2514
- * The trail store this entry was read from. Its `sessions` directory locates
2515
- * the session's `events.jsonl`, so a federated caller can replay events from
2516
- * the store the session actually lives in (not the local store). For a plain
2517
- * local load this is the `paths` passed to {@link loadSessionEntries}.
2518
- */
2519
- sourceRoot: BasouPaths;
2520
- /**
2521
- * Federation host label from the registry (`~/.basou/hosts.yaml`), or `null`
2522
- * for the local store. Surfaced by orientation so a merged, multi-host view
2523
- * can attribute the latest session / decision / next-step to its host.
2524
- */
2525
- host: string | null;
2526
- };
2625
+ type ViewLanguage = "en" | "ja";
2527
2626
  /**
2528
- * Per-session degradation reason emitted by {@link loadSessionEntries.onSkip}.
2627
+ * Resolve the generated-view language from a manifest: the workspace speaks
2628
+ * the language of its ANCHOR repo (the `repos[]` entry whose path is `.`).
2529
2629
  *
2530
- * - `session_yaml_missing` (ENOENT) and `session_yaml_invalid` (parse or schema
2531
- * failure) both omit the entry from the result.
2532
- * - `events_jsonl_unreadable` still pushes the entry with `suspect=false` so
2533
- * the session row remains visible to the caller; only the suspect check is
2534
- * degraded. Matches the existing CLI behaviour at
2535
- * `packages/cli/src/commands/session.ts` (suspect-check stderr warning).
2630
+ * Rules (fixed by design):
2631
+ * - anchor declares `ja` -> `ja`
2632
+ * - anchor declares `en` / `en+ja` -> `en` (a bilingual surface renders one
2633
+ * chrome; en is the shared floor)
2634
+ * - no roster / no anchor entry / no declared language -> `en` (the default
2635
+ * for basou's English-first OSS surface)
2636
+ *
2637
+ * Binding the view to the anchor's language is a deliberate, documented
2638
+ * coupling: the anchor is the planning/trail home the views live in, so its
2639
+ * declared audience is the views' audience. Other repos' languages do not
2640
+ * participate.
2536
2641
  */
2537
- type SessionSkipReason = "session_yaml_missing" | "session_yaml_invalid" | "events_jsonl_unreadable";
2538
- type LoadSessionEntriesOptions = {
2539
- /**
2540
- * Single `now` shared across every {@link classifySuspect} call so that
2541
- * sessions classified back-to-back observe the same instant. Avoids
2542
- * boundary races where a session at age ≈ 24h would flip between calls.
2543
- */
2544
- now: Date;
2545
- onWarning?: (warning: ReplayWarning, sessionId: string) => void;
2546
- onSkip?: (sessionId: string, reason: SessionSkipReason) => void;
2547
- };
2642
+ declare function resolveViewLanguage(manifest: Pick<Manifest, "repos"> | null): ViewLanguage;
2548
2643
  /**
2549
- * A trail store to read in a federated load, tagged with its host label.
2550
- * `host: null` denotes the local store; a non-null label comes from the host
2551
- * registry (`~/.basou/hosts.yaml`). `paths` is where that store is reachable
2552
- * as a local path on this machine (an SSHFS mount, an rsync mirror, etc.) —
2553
- * basou itself never performs any network I/O to obtain it.
2644
+ * Manifest-reading convenience for the renderers: resolve the view language
2645
+ * for a workspace, defaulting to `en` when the manifest is missing or
2646
+ * unreadable (mirrors the orientation renderer's tolerant source_roots read
2647
+ * a broken manifest must never break a view render).
2554
2648
  */
2555
- type FederatedRoot = {
2556
- paths: BasouPaths;
2557
- host: string | null;
2558
- };
2559
- type LoadFederatedOptions = LoadSessionEntriesOptions & {
2560
- /**
2561
- * Called when a NON-local root cannot be enumerated (present-but-unreadable
2562
- * mount, permission error). That root is skipped best-effort so the local
2563
- * store and other roots still load. The local root (`host: null`) is never
2564
- * degraded here its errors propagate, preserving single-store behaviour.
2565
- * (An absent root path is not an error: {@link enumerateSessionDirs} returns
2566
- * `[]` on ENOENT, so a dropped mount is simply an empty host.)
2567
- */
2568
- onRootUnavailable?: (host: string, error: unknown) => void;
2649
+ declare function resolveViewLanguageFromPaths(paths: BasouPaths): Promise<ViewLanguage>;
2650
+ /**
2651
+ * Every localized string the four view renderers emit, grouped per renderer
2652
+ * with a small `common` set for lines that are byte-identical across views.
2653
+ * Parameterized lines are functions so the two languages can order their
2654
+ * parts naturally.
2655
+ *
2656
+ * This module is the SINGLE home for generated Japanese the view chrome here
2657
+ * and the instruction-file content in {@link PresetStrings} (the E-5
2658
+ * language-lint allowlist points here, not at the renderers/generators), so
2659
+ * "user data language" and "tool-generated content language" can never blur
2660
+ * together again.
2661
+ */
2662
+ type ViewStrings = {
2663
+ /** Localized relative age for prose lines, e.g. "3日4時間前" / "3d 4h ago". */
2664
+ relativeAge: (startedAt: string | null, now: Date) => string;
2665
+ common: {
2666
+ /** "最終 session" — the latest live session pointer. */
2667
+ lastSessionLabel: string;
2668
+ /** "直近の判断" — the latest recorded decision pointer. */
2669
+ latestDecisionLabel: string;
2670
+ /** "直近の変更ファイル" — the latest session's related files. */
2671
+ recentFilesLabel: string;
2672
+ /** "理由" — a track's rationale label. */
2673
+ trackWhyLabel: string;
2674
+ /** Note that the latest decision comes from a different session. */
2675
+ decisionOtherSessionNote: (shortSessionId: string) => string;
2676
+ };
2677
+ orientation: {
2678
+ headingWhere: string;
2679
+ headingRecent: (sessionCount: number) => string;
2680
+ headingInFlight: string;
2681
+ headingForward: string;
2682
+ headingCurrency: string;
2683
+ inFlightTasksHeading: (n: number) => string;
2684
+ /**
2685
+ * Body line under the in-flight-tasks heading when NO task was ever
2686
+ * recorded here. "(none)" states that nothing is pending — a claim about
2687
+ * the work. This one claims only what it can see: that the record is
2688
+ * empty. It says nothing about whether the workspace should use tasks,
2689
+ * and names no command: the renderers report position, and a nudge that
2690
+ * cannot be silenced is noise (see `trackNudge`, which is gated).
2691
+ */
2692
+ noTasksRecorded: string;
2693
+ /**
2694
+ * Body line under the in-flight-tasks heading when tasks ARE on record
2695
+ * here and every one of them parsed, with none open. A bare "(none)" is
2696
+ * literally true and still misread: it answers "is anything in flight?"
2697
+ * with a word that sounds like "nothing is happening". Work can be under
2698
+ * way and simply not filed as a task. This line leads with what the
2699
+ * record does hold, so it cannot be skimmed as its sibling below, and it
2700
+ * claims nothing about the work.
2701
+ */
2702
+ noTasksInFlight: string;
2703
+ /**
2704
+ * Same heading, but a task file could not be read on THIS pass, so its
2705
+ * status is unknown and "none in flight" would be an assertion the
2706
+ * renderer cannot support. Says what it can see and stops. Reading the
2707
+ * tasks directory can rebuild the index without the unreadable file, in
2708
+ * which case a later render does not attempt it and this line gives way
2709
+ * to its sibling -- the line reports a pass, not a standing condition.
2710
+ */
2711
+ tasksUnreadable: string;
2712
+ pendingApprovalsHeading: (n: number) => string;
2713
+ suspectSessionsHeading: (n: number) => string;
2714
+ openTracksHeading: (n: number) => string;
2715
+ /** Stale-decision honesty note under 直近の判断. */
2716
+ decisionStaleNote: (activityAge: string) => string;
2717
+ outOfRootWarning: (count: number, files: string) => string;
2718
+ recentEmpty: string;
2719
+ recentDecisionsLabel: string;
2720
+ recentNextStepLabel: string;
2721
+ recentChangedLabel: string;
2722
+ /** Trails the recent-files line when scratch paths were left out of it. */
2723
+ scratchOmitted: (count: number) => string;
2724
+ trackCloseInstruction: string;
2725
+ /** Forward-section pointer to `basou decision gaps`; omitted when the count is 0. */
2726
+ decisionGapsLine: (n: number) => string;
2727
+ nextStepRecordedLabel: (age: string) => string;
2728
+ noteStaleNote: (activityAge: string) => string;
2729
+ fallbackStaleDirection: string;
2730
+ fallbackStaleReferenceLabel: string;
2731
+ trackNudge: string;
2732
+ federatedFreshnessNote: string;
2733
+ bannerUnverifiable: (n: number) => string;
2734
+ bannerStale: (parts: string) => string;
2735
+ partNew: (n: number) => string;
2736
+ partUpdated: (n: number) => string;
2737
+ partsJoiner: string;
2738
+ verdictUnverifiable: (n: number) => [string, string];
2739
+ verdictStale: (parts: string) => [string, string];
2740
+ verdictUpdatedOnly: (n: number) => [string, string];
2741
+ verdictSuspectsAlso: (n: number) => string;
2742
+ verdictEmpty: [string, string];
2743
+ verdictUnprobed: (rel: string, tool: string) => [string, string];
2744
+ verdictCurrent: (rel: string, tool: string, hasHosts: boolean) => string;
2745
+ verdictSuspectsCaveat: (n: number) => string;
2746
+ verdictScopeDisclaimer: string;
2747
+ toolTerminal: string;
2748
+ toolHuman: string;
2749
+ toolImport: string;
2750
+ toolUnknown: string;
2751
+ };
2752
+ handoff: {
2753
+ headingCurrentState: string;
2754
+ headingRecentFiles: string;
2755
+ headingLatestDecision: string;
2756
+ headingOpenTracks: string;
2757
+ headingUnresolved: string;
2758
+ headingReadNext: string;
2759
+ headingNextWork: string;
2760
+ headingSessions: string;
2761
+ lastTaskLabel: string;
2762
+ /** "Work to do next" placeholder: tasks exist, none are open. */
2763
+ noPendingTasks: string;
2764
+ /** "Work to do next" placeholder: no task was ever recorded. */
2765
+ noTasksRecorded: string;
2766
+ decisionStaleNote: string;
2767
+ trackCloseInstruction: string;
2768
+ };
2769
+ decisions: {
2770
+ dateLabel: string;
2771
+ trackKindLine: string;
2772
+ decisionLabel: string;
2773
+ };
2774
+ report: {
2775
+ headingSummary: string;
2776
+ headingVolume: string;
2777
+ headingDecisions: string;
2778
+ headingApprovals: string;
2779
+ headingTasks: string;
2780
+ headingChangedFiles: string;
2781
+ headingSessions: string;
2782
+ headingIntegrity: string;
2783
+ };
2569
2784
  };
2785
+ /** Look up the string table for a resolved view language. */
2786
+ declare function viewStrings(language: ViewLanguage): ViewStrings;
2570
2787
  /**
2571
- * List session directory names under `paths.sessions`, ULID ascending.
2572
- *
2573
- * - Returns `[]` when the sessions directory does not exist (empty workspace
2574
- * or pre-init state).
2575
- * - Throws `Error("Failed to enumerate sessions", { cause })` on other I/O.
2576
- * - Only directories are returned (`.gitkeep` and other files are filtered).
2577
- *
2578
- * Sort order is `Array.prototype.sort()` default (Unicode code-point
2579
- * compare). ULIDs are Crockford base32 in uppercase, so the natural sort
2580
- * is also chronological session-start order.
2581
- */
2582
- declare function enumerateSessionDirs(paths: BasouPaths): Promise<string[]>;
2583
- /**
2584
- * Read and validate `<paths.sessions>/<sessionId>/session.yaml`.
2585
- *
2586
- * - Re-throws the yaml-store fixed-message `"YAML file not found"` for
2587
- * ENOENT so the caller can branch on it.
2588
- * - Throws `Error("Failed to read session.yaml", { cause })` for parse
2589
- * failures and schema violations (cause is either the YAML parser error
2590
- * or the zod error).
2591
- */
2592
- declare function readSessionYaml(paths: BasouPaths, sessionId: string): Promise<Session>;
2593
- /**
2594
- * Apply a terminal-status mutation to a live session's `session.yaml` AND, in
2595
- * the same locked write, stamp the tamper-evidence head anchor derived from the
2596
- * on-disk `events.jsonl` tail. Used by the `exec` / `run` orchestrators for
2597
- * BOTH terminal writers (the normal end-of-run finalize and the spawn-failure
2598
- * `failed` finalize).
2599
- *
2600
- * Why locked + anchor-from-tail: live appends chain the LOG only and leave the
2601
- * anchor for finalize. Reading the final tail under the session lock means a
2602
- * foreign line appended just before finalize (e.g. a `decision record` attached
2603
- * to a still-running session) is included in the anchor, and a foreign attach
2604
- * that arrives after the terminal status is set is rejected by the attach gate
2605
- * — so the anchor can never disagree with the at-rest log. The whole-document
2606
- * read-modify-write also preserves any field a foreign locked writer set (e.g.
2607
- * a task attach's `task_id`).
2608
- *
2609
- * The anchor is written only when the log is actually chained with at least one
2610
- * line; a legacy unchained session (and an empty log) is left with no
2611
- * `integrity` anchor, matching the import writers. The mutator receives the
2612
- * full {@link Session} document and typically sets
2613
- * `session.session.status` / `ended_at` / `invocation.exit_code` /
2614
- * `related_files`.
2615
- *
2616
- * Throws the {@link inspectChainTail} errors (torn / mixed log), the
2617
- * {@link readSessionYaml} errors, a zod error if the mutation produces an
2618
- * invalid document, or `Error("Failed to overwrite YAML file")` on a disk
2619
- * failure.
2788
+ * Resolve a GENERATED INSTRUCTION-FILE's content language from the target
2789
+ * repo's declared `language`. Unlike the views (workspace-level artifacts that
2790
+ * follow the anchor), a preset block lives inside one repo's instruction file,
2791
+ * so its audience is that repo's declared audience: `ja` renders Japanese
2792
+ * (byte-identical to the pre-i18n output), `en` / `en+ja` / undeclared render
2793
+ * English (one content language per generated block; en is the shared floor).
2620
2794
  */
2621
- declare function finalizeSessionYaml(paths: BasouPaths, sessionId: string, mutate: (session: Session) => void): Promise<void>;
2795
+ declare function resolveRepoContentLanguage(language: RepoLanguage | undefined): ViewLanguage;
2622
2796
  /**
2623
- * Classify a `running` session as suspect using one of two rules:
2624
- *
2625
- * - Rule A (`events_say_ended_but_yaml_running`): events.jsonl contains a
2626
- * `session_ended` event but the session.yaml is still `running`. The
2627
- * session ended cleanly in the event log but the YAML write was lost or
2628
- * never reached.
2629
- * - Rule B (`running_no_end_event`): no `session_ended` event and the last
2630
- * event is older than {@link STUCK_THRESHOLD_MS}. The process likely
2631
- * crashed or was killed.
2632
- *
2633
- * Sessions that are not `running` are never suspect.
2797
+ * Resolve the content language of a WORKSPACE-LEVEL instruction artifact (the
2798
+ * view's AGENTS.md block, the anchor's starter) from an already-gathered
2799
+ * roster: the entry flagged `anchor` speaks for the workspace, mirroring the
2800
+ * views' anchor-language rule. No anchor entry (or no declared language)
2801
+ * resolves to English. When more than one entry carries the flag, the first
2802
+ * wins (declared order).
2634
2803
  *
2635
- * I/O failure on events.jsonl is re-thrown unwrapped so the caller can
2636
- * degrade with a warning instead of treating the session as healthy. The
2637
- * caller is also responsible for surfacing replay warnings via `onWarning`.
2804
+ * Note the anchor is identified by the CALLER-SET flag, not by this module:
2805
+ * {@link resolveViewLanguage} keys on the manifest path being `.`, while the
2806
+ * instruction-file callers flag the anchor by resolved-path identity. For a
2807
+ * conventional manifest (anchor declared as `.`) the two agree; a roster that
2808
+ * reaches the anchor only through an aliased path is where they can diverge,
2809
+ * and the caller's flag is authoritative for the instruction files.
2638
2810
  */
2639
- declare function classifySuspect(paths: BasouPaths, sessionId: string, session: Session, now: Date, onWarning?: (warning: ReplayWarning) => void): Promise<{
2640
- suspect: boolean;
2641
- suspectReason: SuspectReason | null;
2642
- }>;
2643
- declare function loadSessionEntries(paths: BasouPaths, options: LoadSessionEntriesOptions): Promise<SessionEntry[]>;
2811
+ declare function resolveAnchorContentLanguage(repos: ReadonlyArray<{
2812
+ anchor?: boolean | undefined;
2813
+ language?: RepoLanguage | undefined;
2814
+ }>): ViewLanguage;
2644
2815
  /**
2645
- * Federated load across multiple trail stores. Each root's sessions are tagged
2646
- * with that root's host label and `sourceRoot`, so a caller replays events from
2647
- * the store the session lives in. De-duped by `sessionId` (a per-host random
2648
- * ULID), then by `source.external_id` when present first occurrence wins, so
2649
- * pass the local root FIRST to keep it authoritative (e.g. over a re-imported
2650
- * copy of the same vendor session on another host). A non-local root that
2651
- * cannot be enumerated is reported via `onRootUnavailable` and skipped; the
2652
- * local root's errors propagate, matching {@link loadSessionEntries}.
2816
+ * Every localized string the instruction-file generators emit: the per-repo
2817
+ * preset block, the workspace view's block, and the anchor's starter. Lives in
2818
+ * this module for the same reason as {@link ViewStrings}: it is the SINGLE
2819
+ * home for generated Japanese, so the language-lint E-5 allowlist stays one
2820
+ * file and "generated content language" is always a declaration-driven table
2821
+ * lookup, never a hardcode.
2653
2822
  */
2654
- declare function loadFederatedSessionEntries(roots: ReadonlyArray<FederatedRoot>, options: LoadFederatedOptions): Promise<SessionEntry[]>;
2823
+ type PresetStrings = {
2824
+ repoBlock: {
2825
+ heading: string;
2826
+ intro: string;
2827
+ /** Source git-visibility, rendered with the consequence the agent must respect. */
2828
+ visibilityLabel: (v: RepoVisibility | undefined) => string;
2829
+ /**
2830
+ * Source language (commits/comments/code), rendered with the audience it
2831
+ * serves. Invariant note: the table itself is SELECTED by this same field
2832
+ * (ja -> JA table, everything else -> EN), so the JA table's en / en+ja /
2833
+ * unset branches and the EN table's ja branch are unreachable from
2834
+ * renderPresetBlock — they exist for table completeness (and the
2835
+ * both-language sweep test), not because a render can emit them.
2836
+ */
2837
+ sourceLanguageLabel: (l: RepoLanguage | undefined) => string;
2838
+ /** Published-surface kind. */
2839
+ publishKindLabel: (k: PublishTarget["kind"]) => string;
2840
+ /** A published surface's visibility (independent of the source repo's). */
2841
+ publishVisibilityLabel: (v: RepoVisibility | undefined) => string;
2842
+ /** A published surface's content language (read by end users; may differ from source). */
2843
+ contentLanguageLabel: (l: RepoLanguage | undefined) => string;
2844
+ /** "ソース可視性" — the source-visibility line label. */
2845
+ sourceVisibilityLabel: string;
2846
+ /** "ソース言語" — the source-language line label. */
2847
+ sourceLanguageLineLabel: string;
2848
+ /** "- 配信物: なし" — no published surfaces. */
2849
+ publishesNone: string;
2850
+ /** "- 配信物:" — the published-surfaces list header. */
2851
+ publishesHeader: string;
2852
+ };
2853
+ viewBlock: {
2854
+ heading: string;
2855
+ intro: string;
2856
+ selfNote: (viewName: string) => string;
2857
+ aggregates: (repoCount: number) => string;
2858
+ reposHeading: string;
2859
+ tableHeader: string;
2860
+ /** Instruction-file ownership labels: who writes the repo's AGENTS.md. */
2861
+ instructionsAnchor: string;
2862
+ instructionsSelf: string;
2863
+ instructionsHub: string;
2864
+ /** "未設定" — the short table cell for an undeclared visibility / language. */
2865
+ unsetShort: string;
2866
+ commitHeading: string;
2867
+ commitBody: string;
2868
+ conventionsHeading: string;
2869
+ conventionsBody: string;
2870
+ /** Heading for {@link handoffPointer}. Its own, so the pointer is not filed
2871
+ * under "Required reading" while telling the reader not to read it through. */
2872
+ handoffHeading: string;
2873
+ /**
2874
+ * Where the session roster lives. One line, because the adjudication that
2875
+ * chose this destination capped session-start injection: the roster itself
2876
+ * must never be injected, only its address.
2877
+ */
2878
+ handoffPointer: string;
2879
+ principlesHeading: string;
2880
+ principleStateless: string;
2881
+ principleNoFiles: string;
2882
+ };
2883
+ anchorStarter: {
2884
+ identityLine: (title: string) => string;
2885
+ starterNote: string;
2886
+ basicsHeading: string;
2887
+ basicsTodo: string;
2888
+ commitHeading: string;
2889
+ commitPlanning: string;
2890
+ commitImplementation: string;
2891
+ commitView: string;
2892
+ conventionsHeading: string;
2893
+ conventionsBody: string;
2894
+ viewPointerLine: (viewName: string) => string;
2895
+ /** Same route as the view block's, for the anchor — which is where `.basou/` lives. */
2896
+ handoffHeading: string;
2897
+ handoffPointer: string;
2898
+ policyHeading: string;
2899
+ policyTodo: string[];
2900
+ };
2901
+ };
2902
+ /** Look up the instruction-file string table for a resolved content language. */
2903
+ declare function presetStrings(language: ViewLanguage): PresetStrings;
2655
2904
 
2656
2905
  type DecisionsRendererInput = {
2657
2906
  paths: BasouPaths;
@@ -3198,6 +3447,19 @@ declare function prefixedUlid<P extends IdPrefix>(prefix: P): PrefixedId<P>;
3198
3447
  */
3199
3448
  declare function isValidPrefixedId(value: string): boolean;
3200
3449
 
3450
+ /**
3451
+ * `schema_version` stamped on NEWLY WRITTEN `.basou/tasks/<id>.md` front matter.
3452
+ *
3453
+ * 0.2.0 requires seconds in every timestamp. That NARROWS the field's domain,
3454
+ * which §7.3 forbids except under the vacuous-narrowing rule the same section
3455
+ * states: no value basou has ever written omits seconds, so the set of
3456
+ * documents this refuses is empty. See `docs/spec/schemas.md` for the read
3457
+ * rule and the measurement. The narrowing is shared with the event format, so
3458
+ * every durable document bumps together.
3459
+ *
3460
+ * Note: basou writes this document.
3461
+ */
3462
+ declare const TASK_SCHEMA_VERSION: "0.2.0";
3201
3463
  /**
3202
3464
  * Task lifecycle states.
3203
3465
  *
@@ -3214,8 +3476,8 @@ declare function isValidPrefixedId(value: string): boolean;
3214
3476
  * Self-edges are rejected so the audit trail stays monotonic.
3215
3477
  */
3216
3478
  declare const TaskStatusSchema: z.ZodEnum<{
3217
- in_progress: "in_progress";
3218
3479
  planned: "planned";
3480
+ in_progress: "in_progress";
3219
3481
  done: "done";
3220
3482
  cancelled: "cancelled";
3221
3483
  }>;
@@ -3235,8 +3497,8 @@ declare const TaskSchema: z.ZodObject<{
3235
3497
  title: z.ZodString;
3236
3498
  label: z.ZodOptional<z.ZodString>;
3237
3499
  status: z.ZodEnum<{
3238
- in_progress: "in_progress";
3239
3500
  planned: "planned";
3501
+ in_progress: "in_progress";
3240
3502
  done: "done";
3241
3503
  cancelled: "cancelled";
3242
3504
  }>;
@@ -3973,6 +4235,35 @@ type HandoffRendererResult = {
3973
4235
  */
3974
4236
  declare function renderHandoff(input: HandoffRendererInput): Promise<HandoffRendererResult>;
3975
4237
 
4238
+ /** What a build knows about itself. `commit` is `"unknown"` outside a checkout. */
4239
+ type BuildStamp = {
4240
+ readonly version: string;
4241
+ readonly commit: string;
4242
+ readonly committedAt: string;
4243
+ };
4244
+ /**
4245
+ * Parse an injected stamp. Separate from the constant below so it is reachable
4246
+ * from a test: under vitest the module loads from SOURCE, where the injected
4247
+ * identifier does not exist, so every line of the parse would otherwise be
4248
+ * unreachable -- a guarantee with no test behind it, which is the shape of
4249
+ * omission this whole feature exists to correct.
4250
+ *
4251
+ * Anything unparseable yields `undefined` rather than throwing: a malformed
4252
+ * stamp must not stop the CLI from starting.
4253
+ */
4254
+ declare function parseBuildStamp(raw: string | undefined): BuildStamp | undefined;
4255
+ /**
4256
+ * Core's own build identity.
4257
+ *
4258
+ * Core is stamped separately from the CLI because the CLI does not bundle it:
4259
+ * `cli/dist` and `core/dist` are distinct artifacts that a partial build can
4260
+ * leave at different commits. A fresh CLI in front of a stale core is the
4261
+ * dangerous half of that pair -- core is where the renderers and importers
4262
+ * live, so the behaviour would be the old one while the CLI reported the new
4263
+ * version.
4264
+ */
4265
+ declare const BASOU_CORE_BUILD: BuildStamp | undefined;
4266
+
3976
4267
  /**
3977
4268
  * Parse a unit-suffixed duration string (e.g. `30s`, `5m`, `1h`, `100ms`)
3978
4269
  * into milliseconds.
@@ -4355,6 +4646,13 @@ type OrientationSummary = {
4355
4646
  * does not sink into the flat decision list. Empty when none are open.
4356
4647
  */
4357
4648
  openTracks: TrackRecord[];
4649
+ /**
4650
+ * Decisions recorded by `basou decision capture` that are still open and that
4651
+ * no task carries — the count `basou decision gaps` lists. Surfaced here as a
4652
+ * single number because a ratified plan that became no task is invisible
4653
+ * otherwise, and a command nobody is told to run does not fix that.
4654
+ */
4655
+ openDecisionGaps: number;
4358
4656
  /**
4359
4657
  * Most recent `note_added` over non-archived sessions — the recorded next
4360
4658
  * step / handoff ("next step") surfaced in the forward section; null when none.
@@ -4391,6 +4689,15 @@ type OrientationSummary = {
4391
4689
  * zero in-flight count distinguish "all closed" from "never used here".
4392
4690
  * See {@link anyTaskEverRecorded}: a live count is NOT this. */
4393
4691
  anyTaskEverRecorded: boolean;
4692
+ /** Task files THIS pass tried and failed to read, so their status is
4693
+ * unknown. A zero in-flight count is only a claim about the files that
4694
+ * parsed: with one of these present, "none in flight" is not something the
4695
+ * renderer can say. Reported to the caller through `onTaskSkip` as well,
4696
+ * but that goes to stderr and the body travels on its own (hooks ship the
4697
+ * body alone). Not a standing count: enumerating tasks can rebuild the
4698
+ * index without the unreadable file, after which later passes do not
4699
+ * attempt it and this reads zero while the file is still on disk. */
4700
+ unreadableTaskCount: number;
4394
4701
  /** Tasks whose status is `planned` ("where am I heading"). */
4395
4702
  plannedTasks: PlannedTask[];
4396
4703
  pendingApprovals: PendingApproval[];
@@ -4736,8 +5043,9 @@ type ViewPresetInput = {
4736
5043
  * operator-specific string is embedded), so re-running on an unchanged manifest
4737
5044
  * produces byte-identical output. The view is a workspace-level artifact, so
4738
5045
  * its content language follows the ANCHOR entry's declared language (mirroring
4739
- * the generated views' rule); a `ja` anchor renders byte-identical to the
4740
- * pre-i18n output. The repos are listed in the order supplied. An empty roster
5046
+ * the generated views' rule). It no longer reproduces the pre-i18n output: the
5047
+ * block gained a line naming where the session roster lives, which that output
5048
+ * predates. The repos are listed in the order supplied. An empty roster
4741
5049
  * still renders cleanly (a header-only table, empty lists). Returns the block
4742
5050
  * WITHOUT a trailing newline; the marker writer adds the surrounding structure.
4743
5051
  */
@@ -5662,14 +5970,14 @@ declare function planWorkspaceView(facts: ViewRepoFact[], existing?: ExistingVie
5662
5970
  * changed without the envelope's own format changing.
5663
5971
  */
5664
5972
  declare const JSON_SCHEMA_VERSIONS: {
5665
- readonly manifest: "0.1.0";
5666
- readonly session: "0.1.0";
5667
- readonly event: "0.2.0";
5668
- readonly task: "0.1.0";
5669
- readonly approval: "0.1.0";
5973
+ readonly manifest: "0.2.0";
5974
+ readonly session: "0.2.0";
5975
+ readonly event: "0.3.0";
5976
+ readonly task: "0.2.0";
5977
+ readonly approval: "0.2.0";
5670
5978
  readonly status: "0.1.0";
5671
5979
  readonly "task-index": "0.1.0";
5672
- readonly "session-import": "0.1.0";
5980
+ readonly "session-import": "0.2.0";
5673
5981
  };
5674
5982
  /** One emitted JSON Schema artifact. */
5675
5983
  type JsonSchemaArtifact = {
@@ -5803,20 +6111,27 @@ declare const SchemaVersionSchema: z.ZodString;
5803
6111
  /**
5804
6112
  * ISO 8601 timestamp with explicit timezone offset (e.g. `+09:00`).
5805
6113
  *
5806
- * Parsing goes through `.refine`, which is opaque to JSON Schema generation,
5807
- * so the `.meta` mirrors the same expression as a representable `pattern` —
5808
- * the way the prefixed-ID schemas below do. Both are built from the single
5809
- * {@link ISO_TIMESTAMP_PATTERN} constant, so the artifact cannot drift from
5810
- * what the runtime actually accepts.
5811
- *
5812
- * It deliberately does NOT declare `format: "date-time"`. That format names
5813
- * RFC 3339, and the two sets cross rather than nest: basou takes a timestamp
5814
- * without seconds, which RFC 3339 does not, and refuses the lowercase
5815
- * designators and the leap second that RFC 3339 allows. Declaring the format
5816
- * made one artifact answer two ways from the same bytes — a validator
5817
- * asserting it rejected `2026-09-16T01:23Z`, one treating it as an annotation
5818
- * (the JSON Schema 2020-12 default) accepted it. The `pattern` is the
5819
- * contract, and `description` says so, because no format name states this set.
6114
+ * The gate is `.regex`, not a `.refine`: zod emits a regex check as the
6115
+ * artifact's `pattern`, so there is one expression rather than a runtime check
6116
+ * and a `.meta` restating it. A `.refine` is opaque to JSON Schema generation
6117
+ * and would need the pattern written a second time, which is a second place to
6118
+ * forget. {@link SchemaVersionSchema} takes the same form for the same reason.
6119
+ *
6120
+ * It deliberately does NOT declare `format: "date-time"`. The sets used to
6121
+ * CROSS basou took a timestamp without seconds, which RFC 3339 does not
6122
+ * and declaring the format made one artifact answer two ways from the same
6123
+ * bytes: a validator asserting it rejected `2026-09-16T01:23Z`, one treating
6124
+ * it as an annotation (the JSON Schema 2020-12 default) accepted it.
6125
+ *
6126
+ * Requiring seconds ended the crossing: this set is now a strict SUBSET of
6127
+ * RFC 3339 `date-time`, since uppercase-only and no-leap-second are
6128
+ * restrictions on it. So the format could be declared truthfully again. It is
6129
+ * still not declared, for a different reason than before — the `pattern` is
6130
+ * the whole contract and the format names a strictly larger set, so declaring
6131
+ * it would say less than the artifact already says while reintroducing a
6132
+ * keyword whose enforcement varies by validator. Reinstating it is a decision
6133
+ * about what the artifact should assert, not a correction; `description` is
6134
+ * what states this set, because no format name does.
5820
6135
  */
5821
6136
  declare const IsoTimestampSchema: z.ZodString;
5822
6137
  /** Workspace ID schema: validates `ws_<26-char ULID>`. */
@@ -5849,6 +6164,18 @@ type RiskLevel = z.infer<typeof RiskLevelSchema>;
5849
6164
  * non-empty string in v0.1; a stricter enum may be introduced post-v0.1.
5850
6165
  */
5851
6166
  declare const EventSourceSchema: z.ZodString;
6167
+ /**
6168
+ * The `source` basou stamps on an event it wrote itself, in response to a
6169
+ * `basou` command the operator (or an agent acting for them) ran — as opposed
6170
+ * to an event an importer derived from a vendor transcript.
6171
+ *
6172
+ * It is a named constant because a reader distinguishes the two: `plan-gaps`
6173
+ * admits a decision to its population only when this source recorded it, so a
6174
+ * writer drifting from this literal would silently shrink that population
6175
+ * rather than fail. Every local writer imports this rather than repeating the
6176
+ * string, so the reader's predicate and the writers have one origin.
6177
+ */
6178
+ declare const LOCAL_CLI_EVENT_SOURCE = "local-cli";
5852
6179
 
5853
6180
  /**
5854
6181
  * Schema for `.basou/status.json` — a forward-incompat cache of the current
@@ -6993,7 +7320,7 @@ type ImportSessionResult = {
6993
7320
  /**
6994
7321
  * Import a round-trip JSON payload into `.basou/sessions/<new>/`. The caller
6995
7322
  * MUST validate the payload against {@link SessionImportPayloadSchema} first
6996
- * and gate the `schema_version === "0.1.0"` literal check externally; this
7323
+ * and gate the `schema_version === SESSION_IMPORT_SCHEMA_VERSION` check externally; this
6997
7324
  * function trusts both invariants.
6998
7325
  *
6999
7326
  * On success a fresh session ID is minted and a complete
@@ -7181,4 +7508,4 @@ declare function overwriteYamlFile(filePath: string, value: unknown): Promise<vo
7181
7508
  */
7182
7509
  declare const BASOU_CORE_VERSION = "0.1.0";
7183
7510
 
7184
- export { ACTIVE_GAP_CAP_MS, AGENT_INFRA_DIRS, type ActiveTimeBasis, type AdapterOutputEvent, type AdoptCandidate, type AdoptCandidateKind, type AnchorStarterInput, type AnchorStarterRepo, type AppendBasouGitignoreOptions, type AppendBasouGitignoreResult, type AppendEventToExistingInput, type AppendEventToExistingResult, type Approval, type ApprovalApprovedEvent, type ApprovalExpiredEvent, ApprovalIdSchema, type ApprovalLocation, type ApprovalRejectedEvent, type ApprovalRequestedEvent, ApprovalSchema, type ApprovalStatus, ApprovalStatusSchema, type ArchivePlan, type ArchiveTaskInput, type ArchiveTaskResult, type AttachTaskInput, type AttachUpdateTaskStatusInput, type AttachableStatus, BASOU_CORE_VERSION, type BasouPaths, type BuildStopHookCommandOptions, type BulkChainResult, CLAUDE_IMPORT_SOURCE, CODEX_IMPORT_SOURCE, type CaptureMode, type ChainBreakReason, type ChainTailState, type ChainVerdict, type ChainVerdictStatus, type ChainedEvents, ChildProcessRunner, type CitedReview, type ClaudeSettings, type ClaudeTranscriptRecord, type ClaudeTranscriptToPayloadOptions, type CodexCommandLookup, type CodexHooksFile, type CodexRolloutRecord, type CodexRolloutToPayloadOptions, type CommandExecutedEvent, type CommandLookup, type CreateAdHocSessionInput, type CreateAdHocSessionResult, type CreateAdHocTaskInput, type CreateManifestInput, type CreateTaskInput, type CreateTaskResult, DEFAULT_STOP_HOOK_MIN_EDITS, type DayWorkStats, DecisionIdSchema, type DecisionRecordedEvent, type DecisionsRendererInput, type DecisionsRendererResult, type DeleteTaskInput, type DeleteTaskResult, type DiffResult, EVENT_SCHEMA_VERSION, type EditTaskInput, type EditTaskResult, type Event, EventIdSchema, EventSchema, EventSourceSchema, type ExistingViewLink, FailedToFinalizeError, type FederatedRoot, type FileChange, type FileChangeStatus, type FileChangedEvent, GENERATED_END, GENERATED_START, type GitSnapshot, type GitSnapshotEvent, type GitignorePlanSummary, type HandoffRendererInput, type HandoffRendererResult, ID_PREFIXES, type IdPrefix, type ImportSessionOptions, type ImportSessionResult, type IncompleteWiring, type InstructionFileFact, type InstructionSymlinkFact, type InstructionSymlinkState, IsoTimestampSchema, JSON_SCHEMA_VERSIONS, type JsonSchemaArtifact, type LoadFederatedOptions, type LoadSessionEntriesOptions, type LoadTaskEntriesOptions, type LoadedApproval, type LockHandle, type LockScope, type Manifest, ManifestSchema, type MarkerSection, type Markers, type MeasureAvailability, type MissingCanonical, type NoteAddedEvent, ORIENTATION_END, ORIENTATION_START, type OrientationRendererInput, type OrientationRendererResult, type OrientationSummary, PROTOCOL_END, PROTOCOL_START, type PrefixedId, type PresetAction, type PresetCollision, type PresetMarkerConflict, type PresetMarkerKind, type PresetPlanSummary, type PresetRepo, type PresetStrings, type ProcessRunner, type PublishKind, type PublishTarget, REVIEW_RECORD_NO_INPUT_HINT, type RechainOptions, type RechainResult, type ReconcileAllResult, type ReconcileAllTasksInput, type ReconcileAllTasksOptions, type ReconcileFailure, type ReconcileResult, type ReconcileTaskInput, type RefreshLinkageInput, type RefreshLinkageResult, type ReimportOptions, type ReimportResult, type RenamePlan, type ReplayOptions, type ReplayWarning, type RepoEntry, type RepoGitignoreFacts, type RepoGitignorePlan, type RepoInstructions, type RepoLanguage, type RepoPathProblem, type RepoPresetFacts, type RepoPresetPlan, type RepoSymlinkFacts, type RepoSymlinkPlan, type RepoVisibility, type RepoWiringFacts, type ReportApprovalItem, type ReportData, type ReportDecisionItem, type ReportRendererInput, type ReportRendererResult, type ReportSessionItem, type ReportTaskItem, type RetrofitAction, type RetrofitAgentsState, type RetrofitFacts, type RetrofitPlan, type RetrofitReason, type ReviewBlocked, type ReviewFinding, type ReviewGapRepoSummary, type ReviewGapUnit, type ReviewGapVerdict, type ReviewGapsInput, type ReviewGapsSummary, type ReviewGateResult, type ReviewGateSilentReason, type ReviewRecordBlockedInput, type ReviewRecordFindingInput, type ReviewRecordInput, type ReviewRecordedEvent, type RiskLevel, RiskLevelSchema, type RosterAdoptionPlan, type RosterDriftSummary, type RunOptions, type RunResult, SESSION_IMPORT_SCHEMA_VERSION, SESSION_START_HOOK_CONTEXT_LIMIT, SESSION_START_HOOK_MATCHER, SESSION_START_HOOK_STATUS_MESSAGE, SESSION_START_HOOK_TIMEOUT_SECONDS, STOP_HOOK_TIMEOUT_SECONDS, STUCK_THRESHOLD_MS, type SanitizePathOptions, type SanitizeRelatedFilesResult, SchemaVersionSchema, type SelfReportedReview, type Session, type SessionEndedEvent, type SessionEntry, SessionIdSchema, type SessionImportPayload, SessionImportPayloadSchema, type SessionInnerImportInput, SessionInnerImportSchema, type SessionIntegrity, SessionIntegritySchema, type SessionMetrics, SessionMetricsSchema, SessionSchema, type SessionSkipReason, type SessionSourceKind, SessionSourceKindSchema, type SessionStartHookLocation, type SessionStartHookRemoval, type SessionStartHookUpsert, type SessionStartedEvent, type SessionStatus, type SessionStatusChangedEvent, SessionStatusSchema, type SessionWorkStats, type SourceRootScope, type SourceRootsReconcile, type SourceWorkStats, type StatusCount, StatusSchema, type StatusSnapshot, type StopHookEvaluation, type StopHookEvaluationInput, type StopHookRemoval, type StopHookSilentReason, type StopHookUpsert, type SuspectReason, type SymlinkCollision, type SymlinkConflict, type SymlinkPlanSummary, type Task, type TaskArchivedEvent, type TaskCreatedEvent, type TaskDeletedEvent, type TaskDocument, TaskIdSchema, type TaskLinkageRefreshedEvent, type TaskReconciledEvent, TaskSchema, type TaskSkipReason, type TaskStatus, type TaskStatusChangedEvent, type TaskStatusCount, TaskStatusSchema, TaskWriteAfterEventError, type TaskWriteAfterEventPhase, type TokenTotals, type UnattachedSelfReports, type UnbindableRepo, type UpdateAdHocTaskStatusInput, type UpdateTaskStatusInput, type UpdateTaskStatusResult, type ViewCollision, type ViewConflict, type ViewLanguage, type ViewLinkState, type ViewPresetInput, type ViewPresetRepo, type ViewRepoFact, type ViewStrayUnknown, type ViewStrings, type ViewWiringFacts, type WiringCollision, type WiringConflict, type WiringDriftSummary, type WiringRisk, type WiringSummary, type WorkStatsInput, type WorkStatsResult, type WorkStatsTotals, WorkspaceIdSchema, type WorkspaceViewPlan, type WriteEventsBulkOptions, type WriteTaskFileMode, ZERO_DURATION_RETIRED_SINCE, acquireLock, appendBasouGitignore, appendChainedEvent, appendChainedEventLocked, appendEvent, appendEventToExistingSession, archiveTask, assertBasouRootSafe, basouPaths, buildJsonSchemas, buildReviewRecordLabel, buildReviewRecordedEvent, buildSessionStartHookCommand, buildStatusSnapshot, buildStopHookCommand, chainEvents, chainRawJsonLines, classifyFilesBySourceRoot, classifyRetrofit, classifySuspect, claudeCodeAdapterMetadata, claudeTranscriptToImportPayload, codexAdapterMetadata, codexRolloutToImportPayload, computeWorkStats, createAdHocSessionWithEvent, createManifest, createTaskWithEvent, deleteTask, editTask, ensureBasouDirectory, enumerateApprovals, enumerateArchivedTaskIds, enumerateSessionDirs, enumerateTaskIds, evaluateStopHook, finalizeSessionYaml, findBasouSessionStartHook, findBasouStopHookCommand, findErrorCode, findReviewGaps, findUnbindableRepos, formatDurationMs, genesisHash, getDiff, getSnapshot, hasRetiredZeroDuration, importSessionFromJson, inspectChainTail, instructionMode, isBasouSessionStartHookCommand, isBasouStopHookCommand, isGitNotFound, isImportDerivedSource, isLazyExpired, isRenderable, isValidPrefixedId, lineHash, linkYamlFile, loadApproval, loadFederatedSessionEntries, loadSessionEntries, loadTaskEntries, normalizeRepoKey, normalizeRepoPath, overwriteYamlFile, parseDuration, parseMarkers, parseReviewRecordInput, pathBasename, planArchive, planGitignore, planRename, planRosterAdoption, planWorkspaceView, prefixedUlid, presetStrings, readAllEvents, readManifest, readMarkdownFile, readObservedDuration, readSessionYaml, readStatus, readTaskFile, readTaskFileWithArchiveFallback, readYamlFile, rechainSessionInPlace, reconcileAllTasks, reconcileSourceRoots, reconcileTask, refreshTaskLinkedSessions, reimportPreservingId, removeMarkerSection, removeSessionStartHook, removeStopHook, renderAnchorStarter, renderDecisions, renderHandoff, renderOrientation, renderPresetBlock, renderReport, renderViewPresetBlock, renderWithMarkers, replayEvents, resolveAnchorContentLanguage, resolveBasouRepositoryRoot, resolveClaudeCodeCommand, resolveCodexCommand, resolveRepoContentLanguage, resolveRepoRoot, resolveRepositoryRoot, resolveSessionId, resolveTaskId, resolveViewLanguage, resolveViewLanguageFromPaths, safeSimpleGit, sanitizePath, sanitizeRelatedFiles, sanitizeWorkingDirectory, seedMarkers, serializeEventLine, serializeJsonSchema, sessionWorkStatsFromEvents, summarizeAdapterOutput, summarizeOrientation, summarizePresetPlan, summarizeRosterDrift, summarizeSymlinkPlan, summarizeWiring, summarizeWiringDrift, tryRemoteUrl, ulid, unknownManifestKeys, updateTaskStatusWithEvent, upsertSessionStartHook, upsertStopHook, verifyEventsChain, viewStrings, writeEventsBulk, writeManifest, writeMarkdownFile, writeObservedDuration, writeStatus, writeTaskFile, writeYamlFile };
7511
+ export { ACTIVE_GAP_CAP_MS, AGENT_INFRA_DIRS, APPROVAL_SCHEMA_VERSION, type ActiveTimeBasis, type AdapterOutputEvent, type AdoptCandidate, type AdoptCandidateKind, type AnchorStarterInput, type AnchorStarterRepo, type AppendBasouGitignoreOptions, type AppendBasouGitignoreResult, type AppendEventToExistingInput, type AppendEventToExistingResult, type Approval, type ApprovalApprovedEvent, type ApprovalExpiredEvent, ApprovalIdSchema, type ApprovalLocation, type ApprovalRejectedEvent, type ApprovalRequestedEvent, ApprovalSchema, type ApprovalStatus, ApprovalStatusSchema, type ArchivePlan, type ArchiveTaskInput, type ArchiveTaskResult, type AttachTaskInput, type AttachUpdateTaskStatusInput, type AttachableStatus, BASOU_CORE_BUILD, BASOU_CORE_VERSION, type BasouPaths, type BuildStamp, type BuildStopHookCommandOptions, type BulkChainResult, CLAUDE_IMPORT_SOURCE, CODEX_IMPORT_SOURCE, type CaptureMode, type ChainBreakReason, type ChainTailState, type ChainVerdict, type ChainVerdictStatus, type ChainedEvents, ChildProcessRunner, type CitedReview, type ClaudeSettings, type ClaudeTranscriptRecord, type ClaudeTranscriptToPayloadOptions, type CodexCommandLookup, type CodexHooksFile, type CodexRolloutRecord, type CodexRolloutToPayloadOptions, type CommandExecutedEvent, type CommandLookup, type CreateAdHocSessionInput, type CreateAdHocSessionResult, type CreateAdHocTaskInput, type CreateManifestInput, type CreateTaskInput, type CreateTaskResult, DECISION_GAPS_EPOCH, DEFAULT_STOP_HOOK_MIN_EDITS, type DayWorkStats, type DecisionGap, type DecisionGapsExcluded, type DecisionGapsIncomplete, type DecisionGapsInput, type DecisionGapsScope, type DecisionGapsSummary, DecisionIdSchema, type DecisionRecordedEvent, type DecisionsRendererInput, type DecisionsRendererResult, type DeleteTaskInput, type DeleteTaskResult, type DiffResult, EVENT_SCHEMA_VERSION, type EditTaskInput, type EditTaskResult, type Event, EventIdSchema, EventSchema, EventSourceSchema, type ExistingViewLink, FailedToFinalizeError, type FederatedRoot, type FileChange, type FileChangeStatus, type FileChangedEvent, GENERATED_END, GENERATED_START, type GitSnapshot, type GitSnapshotEvent, type GitignorePlanSummary, type HandoffRendererInput, type HandoffRendererResult, ID_PREFIXES, type IdPrefix, type ImportSessionOptions, type ImportSessionResult, type IncompleteWiring, type InstructionFileFact, type InstructionSymlinkFact, type InstructionSymlinkState, IsoTimestampSchema, JSON_SCHEMA_VERSIONS, type JsonSchemaArtifact, LOCAL_CLI_EVENT_SOURCE, type LoadFederatedOptions, type LoadSessionEntriesOptions, type LoadTaskEntriesOptions, type LoadedApproval, type LockHandle, type LockScope, MANIFEST_SCHEMA_VERSION, type Manifest, ManifestSchema, type MarkerSection, type Markers, type MeasureAvailability, type MissingCanonical, type NoteAddedEvent, ORIENTATION_END, ORIENTATION_START, type OrientationRendererInput, type OrientationRendererResult, type OrientationSummary, PROTOCOL_END, PROTOCOL_START, type PrefixedId, type PresetAction, type PresetCollision, type PresetMarkerConflict, type PresetMarkerKind, type PresetPlanSummary, type PresetRepo, type PresetStrings, type ProcessRunner, type PublishKind, type PublishTarget, REVIEW_RECORD_NO_INPUT_HINT, type RechainOptions, type RechainResult, type ReconcileAllResult, type ReconcileAllTasksInput, type ReconcileAllTasksOptions, type ReconcileFailure, type ReconcileResult, type ReconcileTaskInput, type RefreshLinkageInput, type RefreshLinkageResult, type ReimportOptions, type ReimportResult, type RenamePlan, type ReplayOptions, type ReplayWarning, type RepoEntry, type RepoGitignoreFacts, type RepoGitignorePlan, type RepoInstructions, type RepoLanguage, type RepoPathProblem, type RepoPresetFacts, type RepoPresetPlan, type RepoSymlinkFacts, type RepoSymlinkPlan, type RepoVisibility, type RepoWiringFacts, type ReportApprovalItem, type ReportData, type ReportDecisionItem, type ReportRendererInput, type ReportRendererResult, type ReportSessionItem, type ReportTaskItem, type RetrofitAction, type RetrofitAgentsState, type RetrofitFacts, type RetrofitPlan, type RetrofitReason, type ReviewBlocked, type ReviewFinding, type ReviewGapRepoSummary, type ReviewGapUnit, type ReviewGapVerdict, type ReviewGapsInput, type ReviewGapsSummary, type ReviewGateResult, type ReviewGateSilentReason, type ReviewRecordBlockedInput, type ReviewRecordFindingInput, type ReviewRecordInput, type ReviewRecordedEvent, type RiskLevel, RiskLevelSchema, type RosterAdoptionPlan, type RosterDriftSummary, type RunOptions, type RunResult, SESSION_IMPORT_SCHEMA_VERSION, SESSION_SCHEMA_VERSION, SESSION_START_HOOK_CONTEXT_LIMIT, SESSION_START_HOOK_MATCHER, SESSION_START_HOOK_STATUS_MESSAGE, SESSION_START_HOOK_TIMEOUT_SECONDS, STOP_HOOK_TIMEOUT_SECONDS, STUCK_THRESHOLD_MS, type SanitizePathOptions, type SanitizeRelatedFilesResult, SchemaVersionSchema, type SelfReportedReview, type Session, type SessionEndedEvent, type SessionEntry, SessionIdSchema, type SessionImportPayload, SessionImportPayloadSchema, type SessionInnerImportInput, SessionInnerImportSchema, type SessionIntegrity, SessionIntegritySchema, type SessionMetrics, SessionMetricsSchema, SessionSchema, type SessionSkipReason, type SessionSourceKind, SessionSourceKindSchema, type SessionStartHookLocation, type SessionStartHookRemoval, type SessionStartHookUpsert, type SessionStartedEvent, type SessionStatus, type SessionStatusChangedEvent, SessionStatusSchema, type SessionWorkStats, type SourceRootScope, type SourceRootsReconcile, type SourceWorkStats, type StatusCount, StatusSchema, type StatusSnapshot, type StopHookEvaluation, type StopHookEvaluationInput, type StopHookRemoval, type StopHookSilentReason, type StopHookUpsert, type SuspectReason, type SymlinkCollision, type SymlinkConflict, type SymlinkPlanSummary, TASK_SCHEMA_VERSION, type Task, type TaskArchivedEvent, type TaskCreatedEvent, type TaskDeletedEvent, type TaskDocument, TaskIdSchema, type TaskLinkageRefreshedEvent, type TaskReconciledEvent, TaskSchema, type TaskSkipReason, type TaskStatus, type TaskStatusChangedEvent, type TaskStatusCount, TaskStatusSchema, TaskWriteAfterEventError, type TaskWriteAfterEventPhase, type TokenTotals, type UnattachedSelfReports, type UnbindableRepo, type UpdateAdHocTaskStatusInput, type UpdateTaskStatusInput, type UpdateTaskStatusResult, type ViewCollision, type ViewConflict, type ViewLanguage, type ViewLinkState, type ViewPresetInput, type ViewPresetRepo, type ViewRepoFact, type ViewStrayUnknown, type ViewStrings, type ViewWiringFacts, type WiringCollision, type WiringConflict, type WiringDriftSummary, type WiringRisk, type WiringSummary, type WorkStatsInput, type WorkStatsResult, type WorkStatsTotals, WorkspaceIdSchema, type WorkspaceViewPlan, type WriteEventsBulkOptions, type WriteTaskFileMode, ZERO_DURATION_RETIRED_SINCE, acquireLock, appendBasouGitignore, appendChainedEvent, appendChainedEventLocked, appendEvent, appendEventToExistingSession, archiveTask, assertBasouRootSafe, basouPaths, buildJsonSchemas, buildReviewRecordLabel, buildReviewRecordedEvent, buildSessionStartHookCommand, buildStatusSnapshot, buildStopHookCommand, chainEvents, chainRawJsonLines, classifyFilesBySourceRoot, classifyRetrofit, classifySuspect, claudeCodeAdapterMetadata, claudeTranscriptToImportPayload, codexAdapterMetadata, codexRolloutToImportPayload, computeWorkStats, createAdHocSessionWithEvent, createManifest, createTaskWithEvent, deleteTask, editTask, ensureBasouDirectory, enumerateApprovals, enumerateArchivedTaskIds, enumerateSessionDirs, enumerateTaskIds, evaluateStopHook, finalizeSessionYaml, findBasouSessionStartHook, findBasouStopHookCommand, findDecisionGaps, findErrorCode, findReviewGaps, findUnbindableRepos, formatDurationMs, genesisHash, getDiff, getSnapshot, hasRetiredZeroDuration, importSessionFromJson, inspectChainTail, instructionMode, isBasouSessionStartHookCommand, isBasouStopHookCommand, isGitNotFound, isImportDerivedSource, isLazyExpired, isRenderable, isValidPrefixedId, lineHash, linkYamlFile, loadApproval, loadFederatedSessionEntries, loadSessionEntries, loadTaskEntries, normalizeRepoKey, normalizeRepoPath, overwriteYamlFile, parseBuildStamp, parseDuration, parseMarkers, parseReviewRecordInput, pathBasename, planArchive, planGitignore, planRename, planRosterAdoption, planWorkspaceView, prefixedUlid, presetStrings, readAllEvents, readManifest, readMarkdownFile, readObservedDuration, readSessionYaml, readStatus, readTaskFile, readTaskFileWithArchiveFallback, readYamlFile, rechainSessionInPlace, reconcileAllTasks, reconcileSourceRoots, reconcileTask, refreshTaskLinkedSessions, reimportPreservingId, removeMarkerSection, removeSessionStartHook, removeStopHook, renderAnchorStarter, renderDecisions, renderHandoff, renderOrientation, renderPresetBlock, renderReport, renderViewPresetBlock, renderWithMarkers, replayEvents, resolveAnchorContentLanguage, resolveBasouRepositoryRoot, resolveClaudeCodeCommand, resolveCodexCommand, resolveRepoContentLanguage, resolveRepoRoot, resolveRepositoryRoot, resolveSessionId, resolveTaskId, resolveViewLanguage, resolveViewLanguageFromPaths, safeSimpleGit, sanitizePath, sanitizeRelatedFiles, sanitizeWorkingDirectory, seedMarkers, serializeEventLine, serializeJsonSchema, sessionWorkStatsFromEvents, summarizeAdapterOutput, summarizeOrientation, summarizePresetPlan, summarizeRosterDrift, summarizeSymlinkPlan, summarizeWiring, summarizeWiringDrift, tryRemoteUrl, ulid, unknownManifestKeys, updateTaskStatusWithEvent, upsertSessionStartHook, upsertStopHook, verifyEventsChain, viewStrings, writeEventsBulk, writeManifest, writeMarkdownFile, writeObservedDuration, writeStatus, writeTaskFile, writeYamlFile };