@basou/core 0.3.1 → 0.4.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 +1419 -1418
- package/dist/index.js +1658 -1642
- package/dist/index.js.map +1 -1
- package/package.json +7 -2
package/dist/index.d.ts
CHANGED
|
@@ -2,369 +2,208 @@ import { z } from 'zod';
|
|
|
2
2
|
import { ChildProcess } from 'node:child_process';
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
5
|
+
* Static metadata identifying the claude-code adapter as the session source.
|
|
6
|
+
* Consumed by the CLI orchestration when populating `session.yaml.source`
|
|
7
|
+
* and event `source` fields. The literal `kind` is part of the wire format
|
|
8
|
+
* defined by the session schema; do not change without coordinated schema
|
|
9
|
+
* migration.
|
|
10
10
|
*/
|
|
11
|
-
declare const
|
|
11
|
+
declare const claudeCodeAdapterMetadata: {
|
|
12
|
+
readonly kind: "claude-code-adapter";
|
|
13
|
+
readonly version: "0.1.0";
|
|
14
|
+
};
|
|
12
15
|
/**
|
|
13
|
-
*
|
|
14
|
-
*
|
|
16
|
+
* Lookup predicate used by {@link resolveClaudeCodeCommand} to decide
|
|
17
|
+
* whether a candidate executable is reachable on PATH. Exposed as a
|
|
18
|
+
* parameter so tests can substitute a deterministic mock; production
|
|
19
|
+
* callers should omit it and rely on the default `which`-based lookup.
|
|
15
20
|
*/
|
|
16
|
-
type
|
|
21
|
+
type CommandLookup = (command: string) => Promise<boolean>;
|
|
17
22
|
/**
|
|
18
|
-
*
|
|
23
|
+
* Resolve the Claude Code CLI executable name. Tries `claude-code` first
|
|
24
|
+
* and falls back to `claude`; the first candidate found on PATH wins.
|
|
19
25
|
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
26
|
+
* Throws a fixed-message Error when neither candidate is reachable, so
|
|
27
|
+
* callers can present a single user-facing prompt to install the CLI.
|
|
28
|
+
*
|
|
29
|
+
* @throws Error("Claude Code CLI not found in PATH. Install claude-code (or claude) first.")
|
|
22
30
|
*/
|
|
23
|
-
|
|
31
|
+
declare function resolveClaudeCodeCommand(lookup?: CommandLookup): Promise<{
|
|
32
|
+
command: string;
|
|
33
|
+
}>;
|
|
24
34
|
/**
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
-
* The result is a 26-character, lexicographically time-sortable identifier.
|
|
28
|
-
* Multiple calls within the same millisecond are strictly increasing for the
|
|
29
|
-
* lifetime of the current process.
|
|
35
|
+
* Stub for the future `adapter_output` summary generator.
|
|
30
36
|
*
|
|
31
|
-
*
|
|
32
|
-
*
|
|
33
|
-
*
|
|
34
|
-
*
|
|
37
|
+
* The current release keeps `capture: "none"` and intentionally does
|
|
38
|
+
* not emit `adapter_output` events, so this hook has no production
|
|
39
|
+
* callers yet. The signature is committed so a later release can
|
|
40
|
+
* implement raw_ref generation without retrofitting the adapter
|
|
41
|
+
* scaffold.
|
|
35
42
|
*
|
|
36
|
-
* @
|
|
37
|
-
* factory. Useful for ordered generation in tests; not deterministic.
|
|
43
|
+
* @throws Error - always; not implemented in this release.
|
|
38
44
|
*/
|
|
39
|
-
declare function
|
|
45
|
+
declare function summarizeAdapterOutput(_stream: "stdout" | "stderr", _raw: string): string;
|
|
46
|
+
|
|
40
47
|
/**
|
|
41
|
-
*
|
|
48
|
+
* Lifecycle states of a Basou approval. The status is stored directly on
|
|
49
|
+
* the approval YAML (flat shape) so that pending → resolved transitions
|
|
50
|
+
* are atomic-move + in-place rewrites rather than schema-variant swaps.
|
|
51
|
+
*/
|
|
52
|
+
declare const ApprovalStatusSchema: z.ZodEnum<{
|
|
53
|
+
pending: "pending";
|
|
54
|
+
approved: "approved";
|
|
55
|
+
rejected: "rejected";
|
|
56
|
+
expired: "expired";
|
|
57
|
+
}>;
|
|
58
|
+
/** Inferred runtime type for {@link ApprovalStatusSchema}. */
|
|
59
|
+
type ApprovalStatus = z.infer<typeof ApprovalStatusSchema>;
|
|
60
|
+
/**
|
|
61
|
+
* Schema for `.basou/approvals/{pending,resolved}/<approval_id>.yaml`.
|
|
42
62
|
*
|
|
43
|
-
* The
|
|
44
|
-
*
|
|
63
|
+
* The schema is intentionally flat (one shape regardless of `status`) so
|
|
64
|
+
* that pending and resolved YAMLs share the same parser. Required vs.
|
|
65
|
+
* optional semantics by status (e.g. `rejection_reason` MUST be set when
|
|
66
|
+
* `status === "rejected"`) are enforced at the CLI orchestration layer
|
|
67
|
+
* rather than here, mirroring the approval event variants in
|
|
68
|
+
* `event.schema.ts`.
|
|
45
69
|
*
|
|
46
|
-
*
|
|
47
|
-
*
|
|
48
|
-
*
|
|
70
|
+
* The `action` field is `{ kind: string }` with `passthrough()` so that
|
|
71
|
+
* adapter-defined keys (e.g. `command`, `path`, `target_url`) survive the
|
|
72
|
+
* round-trip without being stripped — matching the approval_requested
|
|
73
|
+
* event variant.
|
|
49
74
|
*/
|
|
50
|
-
declare
|
|
75
|
+
declare const ApprovalSchema: z.ZodObject<{
|
|
76
|
+
schema_version: z.ZodLiteral<"0.1.0">;
|
|
77
|
+
id: z.ZodString & z.ZodType<`appr_${string}`, string, z.core.$ZodTypeInternals<`appr_${string}`, string>>;
|
|
78
|
+
session_id: z.ZodString & z.ZodType<`ses_${string}`, string, z.core.$ZodTypeInternals<`ses_${string}`, string>>;
|
|
79
|
+
created_at: z.ZodString;
|
|
80
|
+
status: z.ZodEnum<{
|
|
81
|
+
pending: "pending";
|
|
82
|
+
approved: "approved";
|
|
83
|
+
rejected: "rejected";
|
|
84
|
+
expired: "expired";
|
|
85
|
+
}>;
|
|
86
|
+
risk_level: z.ZodEnum<{
|
|
87
|
+
low: "low";
|
|
88
|
+
medium: "medium";
|
|
89
|
+
high: "high";
|
|
90
|
+
critical: "critical";
|
|
91
|
+
}>;
|
|
92
|
+
action: z.ZodObject<{
|
|
93
|
+
kind: z.ZodString;
|
|
94
|
+
}, z.core.$loose>;
|
|
95
|
+
reason: z.ZodString;
|
|
96
|
+
expires_at: z.ZodDefault<z.ZodNullable<z.ZodString>>;
|
|
97
|
+
resolver: z.ZodDefault<z.ZodNullable<z.ZodString>>;
|
|
98
|
+
resolved_at: z.ZodDefault<z.ZodNullable<z.ZodString>>;
|
|
99
|
+
note: z.ZodDefault<z.ZodNullable<z.ZodString>>;
|
|
100
|
+
rejection_reason: z.ZodDefault<z.ZodNullable<z.ZodString>>;
|
|
101
|
+
}, z.core.$strip>;
|
|
102
|
+
/** Inferred runtime type for {@link ApprovalSchema}. */
|
|
103
|
+
type Approval = z.infer<typeof ApprovalSchema>;
|
|
104
|
+
|
|
51
105
|
/**
|
|
52
|
-
*
|
|
106
|
+
* Absolute paths to the standard `.basou/` directory layout, derived from a
|
|
107
|
+
* given repository root. The shape mirrors the canonical `.basou/` tree
|
|
108
|
+
* (see `docs/spec/workspace.md`). `root` is the `.basou/` directory itself
|
|
109
|
+
* (i.e. `repositoryRoot/.basou`).
|
|
53
110
|
*
|
|
54
|
-
*
|
|
55
|
-
*
|
|
56
|
-
*
|
|
57
|
-
* the 0-7 leading char and the I/L/O/U exclusion) with the npm `ulid`
|
|
58
|
-
* library's `isValid` for forward compatibility.
|
|
111
|
+
* `files` exposes the well-known top-level files inside `.basou/`. Each path
|
|
112
|
+
* is computed but not created — they are written by their respective
|
|
113
|
+
* subsystems (e.g. `writeManifest` for `manifest.yaml`).
|
|
59
114
|
*
|
|
60
|
-
*
|
|
61
|
-
*
|
|
115
|
+
* All fields are deeply readonly; consumers must not mutate the returned
|
|
116
|
+
* object.
|
|
62
117
|
*/
|
|
63
|
-
|
|
64
|
-
|
|
118
|
+
type BasouPaths = {
|
|
119
|
+
readonly root: string;
|
|
120
|
+
readonly sessions: string;
|
|
121
|
+
readonly tasks: string;
|
|
122
|
+
readonly approvals: {
|
|
123
|
+
readonly pending: string;
|
|
124
|
+
readonly resolved: string;
|
|
125
|
+
};
|
|
126
|
+
readonly locks: string;
|
|
127
|
+
readonly logs: string;
|
|
128
|
+
readonly raw: string;
|
|
129
|
+
readonly tmp: string;
|
|
130
|
+
readonly files: {
|
|
131
|
+
readonly manifest: string;
|
|
132
|
+
readonly status: string;
|
|
133
|
+
readonly handoff: string;
|
|
134
|
+
readonly decisions: string;
|
|
135
|
+
};
|
|
136
|
+
};
|
|
65
137
|
/**
|
|
66
|
-
*
|
|
67
|
-
*
|
|
138
|
+
* Compute absolute paths to the standard `.basou/` directory layout under
|
|
139
|
+
* `repositoryRoot`. Pure: performs no I/O and is safe to call before the
|
|
140
|
+
* directory exists.
|
|
141
|
+
*
|
|
142
|
+
* @param repositoryRoot Absolute path to the git repository root (the
|
|
143
|
+
* parent directory of `.basou/`). Caller is responsible for resolving
|
|
144
|
+
* `process.cwd()` or running `git rev-parse --show-toplevel` upstream;
|
|
145
|
+
* this function does not validate that the path exists or is a git
|
|
146
|
+
* repository.
|
|
68
147
|
*/
|
|
69
|
-
declare
|
|
148
|
+
declare function basouPaths(repositoryRoot: string): BasouPaths;
|
|
70
149
|
/**
|
|
71
|
-
*
|
|
150
|
+
* Create the standard `.basou/` directory layout under `repositoryRoot`.
|
|
72
151
|
*
|
|
73
|
-
*
|
|
74
|
-
*
|
|
152
|
+
* Idempotent: a no-op on an already-initialized layout. Returns the resolved
|
|
153
|
+
* {@link BasouPaths} so callers can immediately use them.
|
|
154
|
+
*
|
|
155
|
+
* Throws if `repositoryRoot/.basou` (or any required subdirectory) exists
|
|
156
|
+
* but is not a directory, or if filesystem permissions prevent creation.
|
|
157
|
+
* All thrown error messages are pathless; the original native error is
|
|
158
|
+
* attached as `cause` for diagnostics.
|
|
159
|
+
*
|
|
160
|
+
* @param repositoryRoot Absolute path to the git repository root. See
|
|
161
|
+
* {@link basouPaths} for the contract on this parameter.
|
|
75
162
|
*/
|
|
76
|
-
declare
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
/** Approval ID schema: validates `appr_<26-char ULID>`. */
|
|
86
|
-
declare const ApprovalIdSchema: z.ZodString & z.ZodType<`appr_${string}`, string, z.core.$ZodTypeInternals<`appr_${string}`, string>>;
|
|
87
|
-
/** Decision ID schema: validates `decision_<26-char ULID>`. */
|
|
88
|
-
declare const DecisionIdSchema: z.ZodString & z.ZodType<`decision_${string}`, string, z.core.$ZodTypeInternals<`decision_${string}`, string>>;
|
|
163
|
+
declare function ensureBasouDirectory(repositoryRoot: string): Promise<BasouPaths>;
|
|
164
|
+
|
|
165
|
+
/** Which side of `.basou/approvals/` an approval YAML lives on. */
|
|
166
|
+
type ApprovalLocation = "pending" | "resolved";
|
|
167
|
+
/** Result returned by {@link loadApproval}: the parsed approval and where it was found. */
|
|
168
|
+
type LoadedApproval = {
|
|
169
|
+
approval: Approval;
|
|
170
|
+
location: ApprovalLocation;
|
|
171
|
+
};
|
|
89
172
|
/**
|
|
90
|
-
*
|
|
91
|
-
*
|
|
173
|
+
* Locate and load the approval YAML for `approvalId`. Searches resolved
|
|
174
|
+
* first so that a duplicated YAML (the crash-window scenario where both
|
|
175
|
+
* pending and resolved exist for the same id) returns the resolved-side
|
|
176
|
+
* record — matching the dedupe rule used by `approval list` and
|
|
177
|
+
* `resolveApprovalId`. Returns null if neither directory contains the
|
|
178
|
+
* YAML. Throws with a pathless message on read or schema-validation
|
|
179
|
+
* failure.
|
|
92
180
|
*/
|
|
93
|
-
declare
|
|
94
|
-
low: "low";
|
|
95
|
-
medium: "medium";
|
|
96
|
-
high: "high";
|
|
97
|
-
critical: "critical";
|
|
98
|
-
}>;
|
|
99
|
-
/** Inferred runtime type for {@link RiskLevelSchema}. */
|
|
100
|
-
type RiskLevel = z.infer<typeof RiskLevelSchema>;
|
|
181
|
+
declare function loadApproval(paths: BasouPaths, approvalId: string): Promise<LoadedApproval | null>;
|
|
101
182
|
/**
|
|
102
|
-
*
|
|
103
|
-
*
|
|
104
|
-
*
|
|
183
|
+
* Enumerate approval IDs by inspecting `<id>.yaml` filenames in pending
|
|
184
|
+
* and resolved. ENOENT on either directory is treated as empty (e.g. a
|
|
185
|
+
* workspace that has no resolved approvals yet). YAML parse and schema
|
|
186
|
+
* validation are NOT performed; callers that need the parsed approval
|
|
187
|
+
* should use {@link loadApproval} per ID.
|
|
105
188
|
*/
|
|
106
|
-
declare
|
|
107
|
-
|
|
189
|
+
declare function enumerateApprovals(paths: BasouPaths): Promise<{
|
|
190
|
+
pending: string[];
|
|
191
|
+
resolved: string[];
|
|
192
|
+
}>;
|
|
108
193
|
/**
|
|
109
|
-
*
|
|
110
|
-
*
|
|
111
|
-
*
|
|
112
|
-
*
|
|
113
|
-
*
|
|
194
|
+
* Return true when an approval is in `pending` state and its `expires_at`
|
|
195
|
+
* timestamp has elapsed. Used by `basou approval list` / `show` to surface
|
|
196
|
+
* a `(expired)` label without mutating the YAML file. Approval expiry uses
|
|
197
|
+
* lazy-evaluation semantics; actual `approval_expired` event firing is
|
|
198
|
+
* deferred to a later step.
|
|
199
|
+
*
|
|
200
|
+
* `now` is taken as a parameter so a single CLI invocation can share one
|
|
201
|
+
* "now" across every record it inspects (avoids boundary races where two
|
|
202
|
+
* reads of `Date.now()` straddle an expiry instant).
|
|
114
203
|
*/
|
|
115
|
-
declare
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
workspace: z.ZodObject<{
|
|
119
|
-
id: z.ZodString & z.ZodType<`ws_${string}`, string, z.core.$ZodTypeInternals<`ws_${string}`, string>>;
|
|
120
|
-
name: z.ZodString;
|
|
121
|
-
created_at: z.ZodString;
|
|
122
|
-
updated_at: z.ZodString;
|
|
123
|
-
}, z.core.$strip>;
|
|
124
|
-
project: z.ZodObject<{
|
|
125
|
-
name: z.ZodOptional<z.ZodString>;
|
|
126
|
-
description: z.ZodOptional<z.ZodString>;
|
|
127
|
-
repository_url: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
128
|
-
}, z.core.$strip>;
|
|
129
|
-
capabilities: z.ZodObject<{
|
|
130
|
-
enabled: z.ZodArray<z.ZodString>;
|
|
131
|
-
}, z.core.$strip>;
|
|
132
|
-
approval: z.ZodObject<{
|
|
133
|
-
required_for: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
134
|
-
default_risk_level: z.ZodEnum<{
|
|
135
|
-
low: "low";
|
|
136
|
-
medium: "medium";
|
|
137
|
-
high: "high";
|
|
138
|
-
critical: "critical";
|
|
139
|
-
}>;
|
|
140
|
-
}, z.core.$strip>;
|
|
141
|
-
adapters: z.ZodObject<{
|
|
142
|
-
"claude-code": z.ZodObject<{
|
|
143
|
-
enabled: z.ZodBoolean;
|
|
144
|
-
config_path: z.ZodOptional<z.ZodString>;
|
|
145
|
-
}, z.core.$strip>;
|
|
146
|
-
}, z.core.$strip>;
|
|
147
|
-
git: z.ZodObject<{
|
|
148
|
-
events_log: z.ZodDefault<z.ZodEnum<{
|
|
149
|
-
ignore: "ignore";
|
|
150
|
-
commit: "commit";
|
|
151
|
-
}>>;
|
|
152
|
-
}, z.core.$strip>;
|
|
153
|
-
}, z.core.$strip>;
|
|
154
|
-
/** Inferred runtime type for {@link ManifestSchema}. */
|
|
155
|
-
type Manifest = z.infer<typeof ManifestSchema>;
|
|
156
|
-
|
|
157
|
-
/**
|
|
158
|
-
* Schema for `.basou/status.json` — a forward-incompat cache of the current
|
|
159
|
-
* workspace state.
|
|
160
|
-
*
|
|
161
|
-
* Each level uses `.strict()` so unknown keys are rejected rather than
|
|
162
|
-
* silently stripped. A v0.1 reader that encounters a future-shape
|
|
163
|
-
* `status.json` therefore fails parsing instead of returning a partially
|
|
164
|
-
* empty snapshot; callers regenerate by calling `buildStatusSnapshot` +
|
|
165
|
-
* `writeStatus` rather than trying to migrate.
|
|
166
|
-
*/
|
|
167
|
-
declare const StatusSchema: z.ZodObject<{
|
|
168
|
-
schema_version: z.ZodLiteral<"0.1.0">;
|
|
169
|
-
generated_at: z.ZodString;
|
|
170
|
-
workspace: z.ZodObject<{
|
|
171
|
-
id: z.ZodString & z.ZodType<`ws_${string}`, string, z.core.$ZodTypeInternals<`ws_${string}`, string>>;
|
|
172
|
-
name: z.ZodString;
|
|
173
|
-
basou_version: z.ZodLiteral<"0.1.0">;
|
|
174
|
-
}, z.core.$strict>;
|
|
175
|
-
directories_present: z.ZodObject<{
|
|
176
|
-
sessions: z.ZodBoolean;
|
|
177
|
-
tasks: z.ZodBoolean;
|
|
178
|
-
approvals_pending: z.ZodBoolean;
|
|
179
|
-
approvals_resolved: z.ZodBoolean;
|
|
180
|
-
logs: z.ZodBoolean;
|
|
181
|
-
raw: z.ZodBoolean;
|
|
182
|
-
tmp: z.ZodBoolean;
|
|
183
|
-
}, z.core.$strict>;
|
|
184
|
-
}, z.core.$strict>;
|
|
185
|
-
/** Inferred runtime type for {@link StatusSchema}. */
|
|
186
|
-
type StatusSnapshot = z.infer<typeof StatusSchema>;
|
|
187
|
-
|
|
188
|
-
/** Session lifecycle states. */
|
|
189
|
-
declare const SessionStatusSchema: z.ZodEnum<{
|
|
190
|
-
initialized: "initialized";
|
|
191
|
-
running: "running";
|
|
192
|
-
waiting_approval: "waiting_approval";
|
|
193
|
-
completed: "completed";
|
|
194
|
-
failed: "failed";
|
|
195
|
-
interrupted: "interrupted";
|
|
196
|
-
imported: "imported";
|
|
197
|
-
archived: "archived";
|
|
198
|
-
}>;
|
|
199
|
-
/** Inferred runtime type for {@link SessionStatusSchema}. */
|
|
200
|
-
type SessionStatus = z.infer<typeof SessionStatusSchema>;
|
|
201
|
-
/** Source kind that produced the session. */
|
|
202
|
-
declare const SessionSourceKindSchema: z.ZodEnum<{
|
|
203
|
-
"claude-code-adapter": "claude-code-adapter";
|
|
204
|
-
human: "human";
|
|
205
|
-
import: "import";
|
|
206
|
-
terminal: "terminal";
|
|
207
|
-
}>;
|
|
208
|
-
/** Inferred runtime type for {@link SessionSourceKindSchema}. */
|
|
209
|
-
type SessionSourceKind = z.infer<typeof SessionSourceKindSchema>;
|
|
210
|
-
/**
|
|
211
|
-
* Schema for `.basou/sessions/<session_id>/session.yaml`. The minimal
|
|
212
|
-
* session document carries the actual fields nested under the outer
|
|
213
|
-
* `session:` key.
|
|
214
|
-
*/
|
|
215
|
-
declare const SessionSchema: z.ZodObject<{
|
|
216
|
-
schema_version: z.ZodLiteral<"0.1.0">;
|
|
217
|
-
session: z.ZodObject<{
|
|
218
|
-
id: z.ZodString & z.ZodType<`ses_${string}`, string, z.core.$ZodTypeInternals<`ses_${string}`, string>>;
|
|
219
|
-
label: z.ZodOptional<z.ZodString>;
|
|
220
|
-
task_id: z.ZodOptional<z.ZodNullable<z.ZodString & z.ZodType<`task_${string}`, string, z.core.$ZodTypeInternals<`task_${string}`, string>>>>;
|
|
221
|
-
workspace_id: z.ZodString & z.ZodType<`ws_${string}`, string, z.core.$ZodTypeInternals<`ws_${string}`, string>>;
|
|
222
|
-
source: z.ZodObject<{
|
|
223
|
-
kind: z.ZodEnum<{
|
|
224
|
-
"claude-code-adapter": "claude-code-adapter";
|
|
225
|
-
human: "human";
|
|
226
|
-
import: "import";
|
|
227
|
-
terminal: "terminal";
|
|
228
|
-
}>;
|
|
229
|
-
version: z.ZodLiteral<"0.1.0">;
|
|
230
|
-
}, z.core.$strip>;
|
|
231
|
-
started_at: z.ZodString;
|
|
232
|
-
ended_at: z.ZodOptional<z.ZodString>;
|
|
233
|
-
status: z.ZodEnum<{
|
|
234
|
-
initialized: "initialized";
|
|
235
|
-
running: "running";
|
|
236
|
-
waiting_approval: "waiting_approval";
|
|
237
|
-
completed: "completed";
|
|
238
|
-
failed: "failed";
|
|
239
|
-
interrupted: "interrupted";
|
|
240
|
-
imported: "imported";
|
|
241
|
-
archived: "archived";
|
|
242
|
-
}>;
|
|
243
|
-
working_directory: z.ZodString;
|
|
244
|
-
invocation: z.ZodObject<{
|
|
245
|
-
command: z.ZodString;
|
|
246
|
-
args: z.ZodDefault<z.ZodArray<z.ZodString>>;
|
|
247
|
-
exit_code: z.ZodNullable<z.ZodNumber>;
|
|
248
|
-
}, z.core.$strip>;
|
|
249
|
-
related_files: z.ZodDefault<z.ZodArray<z.ZodString>>;
|
|
250
|
-
events_log: z.ZodDefault<z.ZodString>;
|
|
251
|
-
summary: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
252
|
-
}, z.core.$strip>;
|
|
253
|
-
}, z.core.$strip>;
|
|
254
|
-
/** Inferred runtime type for {@link SessionSchema}. */
|
|
255
|
-
type Session = z.infer<typeof SessionSchema>;
|
|
256
|
-
|
|
257
|
-
/**
|
|
258
|
-
* Task lifecycle states.
|
|
259
|
-
*
|
|
260
|
-
* The storage layer's `ALLOWED_TRANSITIONS` map (= source of truth in
|
|
261
|
-
* `tasks.ts`) is the authoritative graph; the comment below is a snapshot.
|
|
262
|
-
* `planned` reaches `done` / `cancelled` directly so tasks completed (or
|
|
263
|
-
* abandoned) outside an explicit in-progress phase can close in a single
|
|
264
|
-
* CLI call:
|
|
265
|
-
*
|
|
266
|
-
* planned → {in_progress | done | cancelled}
|
|
267
|
-
* in_progress → {done | cancelled}
|
|
268
|
-
* done / cancelled = terminal
|
|
269
|
-
*
|
|
270
|
-
* Self-edges are rejected so the audit trail stays monotonic.
|
|
271
|
-
*/
|
|
272
|
-
declare const TaskStatusSchema: z.ZodEnum<{
|
|
273
|
-
planned: "planned";
|
|
274
|
-
in_progress: "in_progress";
|
|
275
|
-
done: "done";
|
|
276
|
-
cancelled: "cancelled";
|
|
277
|
-
}>;
|
|
278
|
-
/** Inferred runtime type for {@link TaskStatusSchema}. */
|
|
279
|
-
type TaskStatus = z.infer<typeof TaskStatusSchema>;
|
|
280
|
-
/**
|
|
281
|
-
* Schema for the YAML front matter of `.basou/tasks/<task_id>.md`.
|
|
282
|
-
*
|
|
283
|
-
* The markdown body after the front matter is intentionally NOT modelled
|
|
284
|
-
* here — it is free-form user-edited content. The storage layer splits
|
|
285
|
-
* the file into `task` (this schema) and `body` (the trailing string).
|
|
286
|
-
*/
|
|
287
|
-
declare const TaskSchema: z.ZodObject<{
|
|
288
|
-
schema_version: z.ZodLiteral<"0.1.0">;
|
|
289
|
-
task: z.ZodObject<{
|
|
290
|
-
id: z.ZodString & z.ZodType<`task_${string}`, string, z.core.$ZodTypeInternals<`task_${string}`, string>>;
|
|
291
|
-
title: z.ZodString;
|
|
292
|
-
label: z.ZodOptional<z.ZodString>;
|
|
293
|
-
status: z.ZodEnum<{
|
|
294
|
-
planned: "planned";
|
|
295
|
-
in_progress: "in_progress";
|
|
296
|
-
done: "done";
|
|
297
|
-
cancelled: "cancelled";
|
|
298
|
-
}>;
|
|
299
|
-
created_at: z.ZodString;
|
|
300
|
-
updated_at: z.ZodString;
|
|
301
|
-
workspace_id: z.ZodString & z.ZodType<`ws_${string}`, string, z.core.$ZodTypeInternals<`ws_${string}`, string>>;
|
|
302
|
-
created_in_session: z.ZodString & z.ZodType<`ses_${string}`, string, z.core.$ZodTypeInternals<`ses_${string}`, string>>;
|
|
303
|
-
linked_sessions: z.ZodDefault<z.ZodArray<z.ZodString & z.ZodType<`ses_${string}`, string, z.core.$ZodTypeInternals<`ses_${string}`, string>>>>;
|
|
304
|
-
}, z.core.$strip>;
|
|
305
|
-
}, z.core.$strip>;
|
|
306
|
-
/** Inferred runtime type for {@link TaskSchema}. */
|
|
307
|
-
type Task = z.infer<typeof TaskSchema>;
|
|
308
|
-
|
|
309
|
-
/**
|
|
310
|
-
* Lifecycle states of a Basou approval. The status is stored directly on
|
|
311
|
-
* the approval YAML (flat shape) so that pending → resolved transitions
|
|
312
|
-
* are atomic-move + in-place rewrites rather than schema-variant swaps.
|
|
313
|
-
*/
|
|
314
|
-
declare const ApprovalStatusSchema: z.ZodEnum<{
|
|
315
|
-
pending: "pending";
|
|
316
|
-
approved: "approved";
|
|
317
|
-
rejected: "rejected";
|
|
318
|
-
expired: "expired";
|
|
319
|
-
}>;
|
|
320
|
-
/** Inferred runtime type for {@link ApprovalStatusSchema}. */
|
|
321
|
-
type ApprovalStatus = z.infer<typeof ApprovalStatusSchema>;
|
|
322
|
-
/**
|
|
323
|
-
* Schema for `.basou/approvals/{pending,resolved}/<approval_id>.yaml`.
|
|
324
|
-
*
|
|
325
|
-
* The schema is intentionally flat (one shape regardless of `status`) so
|
|
326
|
-
* that pending and resolved YAMLs share the same parser. Required vs.
|
|
327
|
-
* optional semantics by status (e.g. `rejection_reason` MUST be set when
|
|
328
|
-
* `status === "rejected"`) are enforced at the CLI orchestration layer
|
|
329
|
-
* rather than here, mirroring the approval event variants in
|
|
330
|
-
* `event.schema.ts`.
|
|
331
|
-
*
|
|
332
|
-
* The `action` field is `{ kind: string }` with `passthrough()` so that
|
|
333
|
-
* adapter-defined keys (e.g. `command`, `path`, `target_url`) survive the
|
|
334
|
-
* round-trip without being stripped — matching the approval_requested
|
|
335
|
-
* event variant.
|
|
336
|
-
*/
|
|
337
|
-
declare const ApprovalSchema: z.ZodObject<{
|
|
338
|
-
schema_version: z.ZodLiteral<"0.1.0">;
|
|
339
|
-
id: z.ZodString & z.ZodType<`appr_${string}`, string, z.core.$ZodTypeInternals<`appr_${string}`, string>>;
|
|
340
|
-
session_id: z.ZodString & z.ZodType<`ses_${string}`, string, z.core.$ZodTypeInternals<`ses_${string}`, string>>;
|
|
341
|
-
created_at: z.ZodString;
|
|
342
|
-
status: z.ZodEnum<{
|
|
343
|
-
pending: "pending";
|
|
344
|
-
approved: "approved";
|
|
345
|
-
rejected: "rejected";
|
|
346
|
-
expired: "expired";
|
|
347
|
-
}>;
|
|
348
|
-
risk_level: z.ZodEnum<{
|
|
349
|
-
low: "low";
|
|
350
|
-
medium: "medium";
|
|
351
|
-
high: "high";
|
|
352
|
-
critical: "critical";
|
|
353
|
-
}>;
|
|
354
|
-
action: z.ZodObject<{
|
|
355
|
-
kind: z.ZodString;
|
|
356
|
-
}, z.core.$loose>;
|
|
357
|
-
reason: z.ZodString;
|
|
358
|
-
expires_at: z.ZodDefault<z.ZodNullable<z.ZodString>>;
|
|
359
|
-
resolver: z.ZodDefault<z.ZodNullable<z.ZodString>>;
|
|
360
|
-
resolved_at: z.ZodDefault<z.ZodNullable<z.ZodString>>;
|
|
361
|
-
note: z.ZodDefault<z.ZodNullable<z.ZodString>>;
|
|
362
|
-
rejection_reason: z.ZodDefault<z.ZodNullable<z.ZodString>>;
|
|
363
|
-
}, z.core.$strip>;
|
|
364
|
-
/** Inferred runtime type for {@link ApprovalSchema}. */
|
|
365
|
-
type Approval = z.infer<typeof ApprovalSchema>;
|
|
366
|
-
|
|
367
|
-
declare const SessionStartedEventSchema: z.ZodObject<{
|
|
204
|
+
declare function isLazyExpired(approval: Approval, now: Date): boolean;
|
|
205
|
+
|
|
206
|
+
declare const SessionStartedEventSchema: z.ZodObject<{
|
|
368
207
|
schema_version: z.ZodLiteral<"0.1.0">;
|
|
369
208
|
id: z.ZodString & z.ZodType<`evt_${string}`, string, z.core.$ZodTypeInternals<`evt_${string}`, string>>;
|
|
370
209
|
session_id: z.ZodString & z.ZodType<`ses_${string}`, string, z.core.$ZodTypeInternals<`ses_${string}`, string>>;
|
|
@@ -852,51 +691,93 @@ type NoteAddedEvent = z.infer<typeof NoteAddedEventSchema>;
|
|
|
852
691
|
/** Narrowed runtime type for the `adapter_output` event variant (.strict()). */
|
|
853
692
|
type AdapterOutputEvent = z.infer<typeof AdapterOutputEventSchema>;
|
|
854
693
|
|
|
855
|
-
declare const SessionInnerImportSchema: z.ZodObject<{
|
|
856
|
-
id: z.ZodOptional<z.ZodString & z.ZodType<`ses_${string}`, string, z.core.$ZodTypeInternals<`ses_${string}`, string>>>;
|
|
857
|
-
label: z.ZodOptional<z.ZodString>;
|
|
858
|
-
task_id: z.ZodOptional<z.ZodNullable<z.ZodString & z.ZodType<`task_${string}`, string, z.core.$ZodTypeInternals<`task_${string}`, string>>>>;
|
|
859
|
-
workspace_id: z.ZodString & z.ZodType<`ws_${string}`, string, z.core.$ZodTypeInternals<`ws_${string}`, string>>;
|
|
860
|
-
source: z.ZodObject<{
|
|
861
|
-
kind: z.ZodEnum<{
|
|
862
|
-
"claude-code-adapter": "claude-code-adapter";
|
|
863
|
-
human: "human";
|
|
864
|
-
import: "import";
|
|
865
|
-
terminal: "terminal";
|
|
866
|
-
}>;
|
|
867
|
-
version: z.ZodLiteral<"0.1.0">;
|
|
868
|
-
}, z.core.$strip>;
|
|
869
|
-
started_at: z.ZodString;
|
|
870
|
-
ended_at: z.ZodOptional<z.ZodString>;
|
|
871
|
-
status: z.ZodEnum<{
|
|
872
|
-
initialized: "initialized";
|
|
873
|
-
running: "running";
|
|
874
|
-
waiting_approval: "waiting_approval";
|
|
875
|
-
completed: "completed";
|
|
876
|
-
failed: "failed";
|
|
877
|
-
interrupted: "interrupted";
|
|
878
|
-
imported: "imported";
|
|
879
|
-
archived: "archived";
|
|
880
|
-
}>;
|
|
881
|
-
working_directory: z.ZodString;
|
|
882
|
-
invocation: z.ZodObject<{
|
|
883
|
-
command: z.ZodString;
|
|
884
|
-
args: z.ZodArray<z.ZodString>;
|
|
885
|
-
exit_code: z.ZodNullable<z.ZodNumber>;
|
|
886
|
-
}, z.core.$strip>;
|
|
887
|
-
related_files: z.ZodDefault<z.ZodArray<z.ZodString>>;
|
|
888
|
-
events_log: z.ZodOptional<z.ZodString>;
|
|
889
|
-
summary: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
890
|
-
}, z.core.$strict>;
|
|
891
694
|
/**
|
|
892
|
-
*
|
|
893
|
-
*
|
|
894
|
-
*
|
|
695
|
+
* Recoverable warning surfaced via {@link ReplayOptions.onWarning}. The replay
|
|
696
|
+
* generator never throws on these — it skips the offending line and continues.
|
|
697
|
+
*
|
|
698
|
+
* `partial_trailing_line` indicates the events.jsonl did not end with `\n` and
|
|
699
|
+
* the unterminated tail parsed as a complete event. The line is dropped
|
|
700
|
+
* instead of yielded so consumers cannot accidentally observe a
|
|
701
|
+
* partially-written record.
|
|
895
702
|
*/
|
|
896
|
-
|
|
897
|
-
|
|
703
|
+
type ReplayWarning = {
|
|
704
|
+
kind: "partial_trailing_line";
|
|
705
|
+
line: number;
|
|
706
|
+
} | {
|
|
707
|
+
kind: "malformed_json";
|
|
708
|
+
line: number;
|
|
709
|
+
cause: unknown;
|
|
710
|
+
} | {
|
|
711
|
+
kind: "schema_violation";
|
|
712
|
+
line: number;
|
|
713
|
+
cause: unknown;
|
|
714
|
+
};
|
|
715
|
+
type ReplayOptions = {
|
|
716
|
+
/**
|
|
717
|
+
* Hook to receive recoverable warnings (partial line / malformed JSON /
|
|
718
|
+
* schema violation). When omitted, warnings are silently dropped — callers
|
|
719
|
+
* that want to surface them (e.g. CLI orchestration) MUST provide this hook.
|
|
720
|
+
*/
|
|
721
|
+
onWarning?: (warning: ReplayWarning) => void;
|
|
722
|
+
};
|
|
723
|
+
/**
|
|
724
|
+
* Stream events from `<sessionDir>/events.jsonl` line by line.
|
|
725
|
+
*
|
|
726
|
+
* Behavior:
|
|
727
|
+
* - ENOENT or empty file: yields nothing without warning.
|
|
728
|
+
* - I/O error: throws `Error("Failed to read events.jsonl")` with the native
|
|
729
|
+
* error attached as `cause`. The thrown message never embeds an absolute
|
|
730
|
+
* path (pathless contract).
|
|
731
|
+
* - Trailing partial line that parses as a valid event: dropped silently when
|
|
732
|
+
* {@link ReplayOptions.onWarning} is omitted; otherwise reported as
|
|
733
|
+
* `partial_trailing_line`. A trailing partial line that fails JSON parsing
|
|
734
|
+
* is reported as `malformed_json` instead.
|
|
735
|
+
* - Malformed JSON / schema violation: skipped, with the corresponding
|
|
736
|
+
* warning when a hook is provided.
|
|
737
|
+
*
|
|
738
|
+
* Single-writer-per-session is assumed (see `event-writer.ts` JSDoc on
|
|
739
|
+
* {@link appendEvent}). Concurrent writers may interleave lines beyond
|
|
740
|
+
* `PIPE_BUF` and are not recovered here in v0.1.
|
|
741
|
+
*/
|
|
742
|
+
declare function replayEvents(sessionDir: string, options?: ReplayOptions): AsyncGenerator<Event, void, void>;
|
|
743
|
+
/**
|
|
744
|
+
* Eager array helper: collect every event from {@link replayEvents} into
|
|
745
|
+
* memory. Convenience for callers that need the full list in one structure
|
|
746
|
+
* (e.g. `basou session show` rendering).
|
|
747
|
+
*/
|
|
748
|
+
declare function readAllEvents(sessionDir: string, options?: ReplayOptions): Promise<Event[]>;
|
|
749
|
+
|
|
750
|
+
/** Session lifecycle states. */
|
|
751
|
+
declare const SessionStatusSchema: z.ZodEnum<{
|
|
752
|
+
initialized: "initialized";
|
|
753
|
+
running: "running";
|
|
754
|
+
waiting_approval: "waiting_approval";
|
|
755
|
+
completed: "completed";
|
|
756
|
+
failed: "failed";
|
|
757
|
+
interrupted: "interrupted";
|
|
758
|
+
imported: "imported";
|
|
759
|
+
archived: "archived";
|
|
760
|
+
}>;
|
|
761
|
+
/** Inferred runtime type for {@link SessionStatusSchema}. */
|
|
762
|
+
type SessionStatus = z.infer<typeof SessionStatusSchema>;
|
|
763
|
+
/** Source kind that produced the session. */
|
|
764
|
+
declare const SessionSourceKindSchema: z.ZodEnum<{
|
|
765
|
+
"claude-code-adapter": "claude-code-adapter";
|
|
766
|
+
human: "human";
|
|
767
|
+
import: "import";
|
|
768
|
+
terminal: "terminal";
|
|
769
|
+
}>;
|
|
770
|
+
/** Inferred runtime type for {@link SessionSourceKindSchema}. */
|
|
771
|
+
type SessionSourceKind = z.infer<typeof SessionSourceKindSchema>;
|
|
772
|
+
/**
|
|
773
|
+
* Schema for `.basou/sessions/<session_id>/session.yaml`. The minimal
|
|
774
|
+
* session document carries the actual fields nested under the outer
|
|
775
|
+
* `session:` key.
|
|
776
|
+
*/
|
|
777
|
+
declare const SessionSchema: z.ZodObject<{
|
|
778
|
+
schema_version: z.ZodLiteral<"0.1.0">;
|
|
898
779
|
session: z.ZodObject<{
|
|
899
|
-
id: z.
|
|
780
|
+
id: z.ZodString & z.ZodType<`ses_${string}`, string, z.core.$ZodTypeInternals<`ses_${string}`, string>>;
|
|
900
781
|
label: z.ZodOptional<z.ZodString>;
|
|
901
782
|
task_id: z.ZodOptional<z.ZodNullable<z.ZodString & z.ZodType<`task_${string}`, string, z.core.$ZodTypeInternals<`task_${string}`, string>>>>;
|
|
902
783
|
workspace_id: z.ZodString & z.ZodType<`ws_${string}`, string, z.core.$ZodTypeInternals<`ws_${string}`, string>>;
|
|
@@ -924,804 +805,446 @@ declare const SessionImportPayloadSchema: z.ZodObject<{
|
|
|
924
805
|
working_directory: z.ZodString;
|
|
925
806
|
invocation: z.ZodObject<{
|
|
926
807
|
command: z.ZodString;
|
|
927
|
-
args: z.ZodArray<z.ZodString
|
|
808
|
+
args: z.ZodDefault<z.ZodArray<z.ZodString>>;
|
|
928
809
|
exit_code: z.ZodNullable<z.ZodNumber>;
|
|
929
810
|
}, z.core.$strip>;
|
|
930
811
|
related_files: z.ZodDefault<z.ZodArray<z.ZodString>>;
|
|
931
|
-
events_log: z.
|
|
812
|
+
events_log: z.ZodDefault<z.ZodString>;
|
|
932
813
|
summary: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
933
|
-
}, z.core.$
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
session_id: z.ZodString & z.ZodType<`ses_${string}`, string, z.core.$ZodTypeInternals<`ses_${string}`, string>>;
|
|
938
|
-
occurred_at: z.ZodString;
|
|
939
|
-
source: z.ZodString;
|
|
940
|
-
type: z.ZodLiteral<"session_started">;
|
|
941
|
-
}, z.core.$strip>, z.ZodObject<{
|
|
942
|
-
schema_version: z.ZodLiteral<"0.1.0">;
|
|
943
|
-
id: z.ZodString & z.ZodType<`evt_${string}`, string, z.core.$ZodTypeInternals<`evt_${string}`, string>>;
|
|
944
|
-
session_id: z.ZodString & z.ZodType<`ses_${string}`, string, z.core.$ZodTypeInternals<`ses_${string}`, string>>;
|
|
945
|
-
occurred_at: z.ZodString;
|
|
946
|
-
source: z.ZodString;
|
|
947
|
-
type: z.ZodLiteral<"session_ended">;
|
|
948
|
-
exit_code: z.ZodOptional<z.ZodNumber>;
|
|
949
|
-
}, z.core.$strip>, z.ZodObject<{
|
|
950
|
-
schema_version: z.ZodLiteral<"0.1.0">;
|
|
951
|
-
id: z.ZodString & z.ZodType<`evt_${string}`, string, z.core.$ZodTypeInternals<`evt_${string}`, string>>;
|
|
952
|
-
session_id: z.ZodString & z.ZodType<`ses_${string}`, string, z.core.$ZodTypeInternals<`ses_${string}`, string>>;
|
|
953
|
-
occurred_at: z.ZodString;
|
|
954
|
-
source: z.ZodString;
|
|
955
|
-
type: z.ZodLiteral<"session_status_changed">;
|
|
956
|
-
from: z.ZodString;
|
|
957
|
-
to: z.ZodString;
|
|
958
|
-
}, z.core.$strip>, z.ZodObject<{
|
|
959
|
-
schema_version: z.ZodLiteral<"0.1.0">;
|
|
960
|
-
id: z.ZodString & z.ZodType<`evt_${string}`, string, z.core.$ZodTypeInternals<`evt_${string}`, string>>;
|
|
961
|
-
session_id: z.ZodString & z.ZodType<`ses_${string}`, string, z.core.$ZodTypeInternals<`ses_${string}`, string>>;
|
|
962
|
-
occurred_at: z.ZodString;
|
|
963
|
-
source: z.ZodString;
|
|
964
|
-
type: z.ZodLiteral<"approval_requested">;
|
|
965
|
-
approval_id: z.ZodString & z.ZodType<`appr_${string}`, string, z.core.$ZodTypeInternals<`appr_${string}`, string>>;
|
|
966
|
-
expires_at: z.ZodDefault<z.ZodNullable<z.ZodString>>;
|
|
967
|
-
risk_level: z.ZodEnum<{
|
|
968
|
-
low: "low";
|
|
969
|
-
medium: "medium";
|
|
970
|
-
high: "high";
|
|
971
|
-
critical: "critical";
|
|
972
|
-
}>;
|
|
973
|
-
action: z.ZodObject<{
|
|
974
|
-
kind: z.ZodString;
|
|
975
|
-
}, z.core.$loose>;
|
|
976
|
-
reason: z.ZodString;
|
|
977
|
-
status: z.ZodLiteral<"pending">;
|
|
978
|
-
}, z.core.$strip>, z.ZodObject<{
|
|
979
|
-
schema_version: z.ZodLiteral<"0.1.0">;
|
|
980
|
-
id: z.ZodString & z.ZodType<`evt_${string}`, string, z.core.$ZodTypeInternals<`evt_${string}`, string>>;
|
|
981
|
-
session_id: z.ZodString & z.ZodType<`ses_${string}`, string, z.core.$ZodTypeInternals<`ses_${string}`, string>>;
|
|
982
|
-
occurred_at: z.ZodString;
|
|
983
|
-
source: z.ZodString;
|
|
984
|
-
type: z.ZodLiteral<"approval_approved">;
|
|
985
|
-
approval_id: z.ZodString & z.ZodType<`appr_${string}`, string, z.core.$ZodTypeInternals<`appr_${string}`, string>>;
|
|
986
|
-
resolver: z.ZodOptional<z.ZodString>;
|
|
987
|
-
note: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
988
|
-
}, z.core.$strip>, z.ZodObject<{
|
|
989
|
-
schema_version: z.ZodLiteral<"0.1.0">;
|
|
990
|
-
id: z.ZodString & z.ZodType<`evt_${string}`, string, z.core.$ZodTypeInternals<`evt_${string}`, string>>;
|
|
991
|
-
session_id: z.ZodString & z.ZodType<`ses_${string}`, string, z.core.$ZodTypeInternals<`ses_${string}`, string>>;
|
|
992
|
-
occurred_at: z.ZodString;
|
|
993
|
-
source: z.ZodString;
|
|
994
|
-
type: z.ZodLiteral<"approval_rejected">;
|
|
995
|
-
approval_id: z.ZodString & z.ZodType<`appr_${string}`, string, z.core.$ZodTypeInternals<`appr_${string}`, string>>;
|
|
996
|
-
resolver: z.ZodOptional<z.ZodString>;
|
|
997
|
-
reason: z.ZodString;
|
|
998
|
-
}, z.core.$strip>, z.ZodObject<{
|
|
999
|
-
schema_version: z.ZodLiteral<"0.1.0">;
|
|
1000
|
-
id: z.ZodString & z.ZodType<`evt_${string}`, string, z.core.$ZodTypeInternals<`evt_${string}`, string>>;
|
|
1001
|
-
session_id: z.ZodString & z.ZodType<`ses_${string}`, string, z.core.$ZodTypeInternals<`ses_${string}`, string>>;
|
|
1002
|
-
occurred_at: z.ZodString;
|
|
1003
|
-
source: z.ZodString;
|
|
1004
|
-
type: z.ZodLiteral<"approval_expired">;
|
|
1005
|
-
approval_id: z.ZodString & z.ZodType<`appr_${string}`, string, z.core.$ZodTypeInternals<`appr_${string}`, string>>;
|
|
1006
|
-
}, z.core.$strip>, z.ZodObject<{
|
|
1007
|
-
schema_version: z.ZodLiteral<"0.1.0">;
|
|
1008
|
-
id: z.ZodString & z.ZodType<`evt_${string}`, string, z.core.$ZodTypeInternals<`evt_${string}`, string>>;
|
|
1009
|
-
session_id: z.ZodString & z.ZodType<`ses_${string}`, string, z.core.$ZodTypeInternals<`ses_${string}`, string>>;
|
|
1010
|
-
occurred_at: z.ZodString;
|
|
1011
|
-
source: z.ZodString;
|
|
1012
|
-
type: z.ZodLiteral<"command_executed">;
|
|
1013
|
-
command: z.ZodString;
|
|
1014
|
-
args: z.ZodArray<z.ZodString>;
|
|
1015
|
-
cwd: z.ZodString;
|
|
1016
|
-
exit_code: z.ZodNullable<z.ZodNumber>;
|
|
1017
|
-
signal: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
1018
|
-
received_signal: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
1019
|
-
duration_ms: z.ZodNumber;
|
|
1020
|
-
}, z.core.$strip>, z.ZodObject<{
|
|
1021
|
-
schema_version: z.ZodLiteral<"0.1.0">;
|
|
1022
|
-
id: z.ZodString & z.ZodType<`evt_${string}`, string, z.core.$ZodTypeInternals<`evt_${string}`, string>>;
|
|
1023
|
-
session_id: z.ZodString & z.ZodType<`ses_${string}`, string, z.core.$ZodTypeInternals<`ses_${string}`, string>>;
|
|
1024
|
-
occurred_at: z.ZodString;
|
|
1025
|
-
source: z.ZodString;
|
|
1026
|
-
type: z.ZodLiteral<"git_snapshot">;
|
|
1027
|
-
head: z.ZodString;
|
|
1028
|
-
branch: z.ZodString;
|
|
1029
|
-
dirty: z.ZodBoolean;
|
|
1030
|
-
staged: z.ZodArray<z.ZodString>;
|
|
1031
|
-
unstaged: z.ZodArray<z.ZodString>;
|
|
1032
|
-
untracked: z.ZodArray<z.ZodString>;
|
|
1033
|
-
ahead: z.ZodOptional<z.ZodNumber>;
|
|
1034
|
-
behind: z.ZodOptional<z.ZodNumber>;
|
|
1035
|
-
}, z.core.$strip>, z.ZodObject<{
|
|
1036
|
-
schema_version: z.ZodLiteral<"0.1.0">;
|
|
1037
|
-
id: z.ZodString & z.ZodType<`evt_${string}`, string, z.core.$ZodTypeInternals<`evt_${string}`, string>>;
|
|
1038
|
-
session_id: z.ZodString & z.ZodType<`ses_${string}`, string, z.core.$ZodTypeInternals<`ses_${string}`, string>>;
|
|
1039
|
-
occurred_at: z.ZodString;
|
|
1040
|
-
source: z.ZodString;
|
|
1041
|
-
type: z.ZodLiteral<"file_changed">;
|
|
1042
|
-
path: z.ZodString;
|
|
1043
|
-
change_type: z.ZodEnum<{
|
|
1044
|
-
added: "added";
|
|
1045
|
-
modified: "modified";
|
|
1046
|
-
deleted: "deleted";
|
|
1047
|
-
renamed: "renamed";
|
|
1048
|
-
}>;
|
|
1049
|
-
old_path: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
1050
|
-
}, z.core.$strip>, z.ZodObject<{
|
|
1051
|
-
schema_version: z.ZodLiteral<"0.1.0">;
|
|
1052
|
-
id: z.ZodString & z.ZodType<`evt_${string}`, string, z.core.$ZodTypeInternals<`evt_${string}`, string>>;
|
|
1053
|
-
session_id: z.ZodString & z.ZodType<`ses_${string}`, string, z.core.$ZodTypeInternals<`ses_${string}`, string>>;
|
|
1054
|
-
occurred_at: z.ZodString;
|
|
1055
|
-
source: z.ZodString;
|
|
1056
|
-
type: z.ZodLiteral<"decision_recorded">;
|
|
1057
|
-
decision_id: z.ZodString & z.ZodType<`decision_${string}`, string, z.core.$ZodTypeInternals<`decision_${string}`, string>>;
|
|
1058
|
-
title: z.ZodString;
|
|
1059
|
-
rationale: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
1060
|
-
alternatives: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
1061
|
-
rejected_reason: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
1062
|
-
linked_events: z.ZodOptional<z.ZodArray<z.ZodString & z.ZodType<`evt_${string}`, string, z.core.$ZodTypeInternals<`evt_${string}`, string>>>>;
|
|
1063
|
-
linked_files: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
1064
|
-
}, z.core.$strip>, z.ZodObject<{
|
|
1065
|
-
schema_version: z.ZodLiteral<"0.1.0">;
|
|
1066
|
-
id: z.ZodString & z.ZodType<`evt_${string}`, string, z.core.$ZodTypeInternals<`evt_${string}`, string>>;
|
|
1067
|
-
session_id: z.ZodString & z.ZodType<`ses_${string}`, string, z.core.$ZodTypeInternals<`ses_${string}`, string>>;
|
|
1068
|
-
occurred_at: z.ZodString;
|
|
1069
|
-
source: z.ZodString;
|
|
1070
|
-
type: z.ZodLiteral<"task_created">;
|
|
1071
|
-
task_id: z.ZodString & z.ZodType<`task_${string}`, string, z.core.$ZodTypeInternals<`task_${string}`, string>>;
|
|
1072
|
-
title: z.ZodString;
|
|
1073
|
-
}, z.core.$strip>, z.ZodObject<{
|
|
1074
|
-
schema_version: z.ZodLiteral<"0.1.0">;
|
|
1075
|
-
id: z.ZodString & z.ZodType<`evt_${string}`, string, z.core.$ZodTypeInternals<`evt_${string}`, string>>;
|
|
1076
|
-
session_id: z.ZodString & z.ZodType<`ses_${string}`, string, z.core.$ZodTypeInternals<`ses_${string}`, string>>;
|
|
1077
|
-
occurred_at: z.ZodString;
|
|
1078
|
-
source: z.ZodString;
|
|
1079
|
-
type: z.ZodLiteral<"task_status_changed">;
|
|
1080
|
-
task_id: z.ZodString & z.ZodType<`task_${string}`, string, z.core.$ZodTypeInternals<`task_${string}`, string>>;
|
|
1081
|
-
from: z.ZodString;
|
|
1082
|
-
to: z.ZodString;
|
|
1083
|
-
}, z.core.$strip>, z.ZodObject<{
|
|
1084
|
-
schema_version: z.ZodLiteral<"0.1.0">;
|
|
1085
|
-
id: z.ZodString & z.ZodType<`evt_${string}`, string, z.core.$ZodTypeInternals<`evt_${string}`, string>>;
|
|
1086
|
-
session_id: z.ZodString & z.ZodType<`ses_${string}`, string, z.core.$ZodTypeInternals<`ses_${string}`, string>>;
|
|
1087
|
-
occurred_at: z.ZodString;
|
|
1088
|
-
source: z.ZodString;
|
|
1089
|
-
type: z.ZodLiteral<"task_reconciled">;
|
|
1090
|
-
task_id: z.ZodString & z.ZodType<`task_${string}`, string, z.core.$ZodTypeInternals<`task_${string}`, string>>;
|
|
1091
|
-
removed_created_in_session: z.ZodDefault<z.ZodNullable<z.ZodString & z.ZodType<`ses_${string}`, string, z.core.$ZodTypeInternals<`ses_${string}`, string>>>>;
|
|
1092
|
-
created_in_session_replacement: z.ZodDefault<z.ZodNullable<z.ZodString & z.ZodType<`ses_${string}`, string, z.core.$ZodTypeInternals<`ses_${string}`, string>>>>;
|
|
1093
|
-
removed_linked_sessions: z.ZodDefault<z.ZodArray<z.ZodString & z.ZodType<`ses_${string}`, string, z.core.$ZodTypeInternals<`ses_${string}`, string>>>>;
|
|
1094
|
-
}, z.core.$strict>, z.ZodObject<{
|
|
1095
|
-
schema_version: z.ZodLiteral<"0.1.0">;
|
|
1096
|
-
id: z.ZodString & z.ZodType<`evt_${string}`, string, z.core.$ZodTypeInternals<`evt_${string}`, string>>;
|
|
1097
|
-
session_id: z.ZodString & z.ZodType<`ses_${string}`, string, z.core.$ZodTypeInternals<`ses_${string}`, string>>;
|
|
1098
|
-
occurred_at: z.ZodString;
|
|
1099
|
-
source: z.ZodString;
|
|
1100
|
-
type: z.ZodLiteral<"task_linkage_refreshed">;
|
|
1101
|
-
task_id: z.ZodString & z.ZodType<`task_${string}`, string, z.core.$ZodTypeInternals<`task_${string}`, string>>;
|
|
1102
|
-
added_linked_sessions: z.ZodDefault<z.ZodArray<z.ZodString & z.ZodType<`ses_${string}`, string, z.core.$ZodTypeInternals<`ses_${string}`, string>>>>;
|
|
1103
|
-
removed_linked_sessions: z.ZodDefault<z.ZodArray<z.ZodString & z.ZodType<`ses_${string}`, string, z.core.$ZodTypeInternals<`ses_${string}`, string>>>>;
|
|
1104
|
-
final_count: z.ZodOptional<z.ZodNumber>;
|
|
1105
|
-
}, z.core.$strict>, z.ZodObject<{
|
|
1106
|
-
schema_version: z.ZodLiteral<"0.1.0">;
|
|
1107
|
-
id: z.ZodString & z.ZodType<`evt_${string}`, string, z.core.$ZodTypeInternals<`evt_${string}`, string>>;
|
|
1108
|
-
session_id: z.ZodString & z.ZodType<`ses_${string}`, string, z.core.$ZodTypeInternals<`ses_${string}`, string>>;
|
|
1109
|
-
occurred_at: z.ZodString;
|
|
1110
|
-
source: z.ZodString;
|
|
1111
|
-
type: z.ZodLiteral<"task_deleted">;
|
|
1112
|
-
task_id: z.ZodString & z.ZodType<`task_${string}`, string, z.core.$ZodTypeInternals<`task_${string}`, string>>;
|
|
1113
|
-
title: z.ZodString;
|
|
1114
|
-
}, z.core.$strict>, z.ZodObject<{
|
|
1115
|
-
schema_version: z.ZodLiteral<"0.1.0">;
|
|
1116
|
-
id: z.ZodString & z.ZodType<`evt_${string}`, string, z.core.$ZodTypeInternals<`evt_${string}`, string>>;
|
|
1117
|
-
session_id: z.ZodString & z.ZodType<`ses_${string}`, string, z.core.$ZodTypeInternals<`ses_${string}`, string>>;
|
|
1118
|
-
occurred_at: z.ZodString;
|
|
1119
|
-
source: z.ZodString;
|
|
1120
|
-
type: z.ZodLiteral<"task_archived">;
|
|
1121
|
-
task_id: z.ZodString & z.ZodType<`task_${string}`, string, z.core.$ZodTypeInternals<`task_${string}`, string>>;
|
|
1122
|
-
title: z.ZodString;
|
|
1123
|
-
}, z.core.$strict>, z.ZodObject<{
|
|
1124
|
-
schema_version: z.ZodLiteral<"0.1.0">;
|
|
1125
|
-
id: z.ZodString & z.ZodType<`evt_${string}`, string, z.core.$ZodTypeInternals<`evt_${string}`, string>>;
|
|
1126
|
-
session_id: z.ZodString & z.ZodType<`ses_${string}`, string, z.core.$ZodTypeInternals<`ses_${string}`, string>>;
|
|
1127
|
-
occurred_at: z.ZodString;
|
|
1128
|
-
source: z.ZodString;
|
|
1129
|
-
type: z.ZodLiteral<"note_added">;
|
|
1130
|
-
body: z.ZodString;
|
|
1131
|
-
}, z.core.$strip>, z.ZodObject<{
|
|
1132
|
-
schema_version: z.ZodLiteral<"0.1.0">;
|
|
1133
|
-
id: z.ZodString & z.ZodType<`evt_${string}`, string, z.core.$ZodTypeInternals<`evt_${string}`, string>>;
|
|
1134
|
-
session_id: z.ZodString & z.ZodType<`ses_${string}`, string, z.core.$ZodTypeInternals<`ses_${string}`, string>>;
|
|
1135
|
-
occurred_at: z.ZodString;
|
|
1136
|
-
source: z.ZodString;
|
|
1137
|
-
type: z.ZodLiteral<"adapter_output">;
|
|
1138
|
-
stream: z.ZodEnum<{
|
|
1139
|
-
stdout: "stdout";
|
|
1140
|
-
stderr: "stderr";
|
|
1141
|
-
}>;
|
|
1142
|
-
summary: z.ZodString;
|
|
1143
|
-
raw_ref: z.ZodString;
|
|
1144
|
-
redacted: z.ZodOptional<z.ZodBoolean>;
|
|
1145
|
-
}, z.core.$strict>], "type">>;
|
|
1146
|
-
}, z.core.$strict>;
|
|
1147
|
-
/** Inferred runtime type for {@link SessionImportPayloadSchema}. */
|
|
1148
|
-
type SessionImportPayload = z.infer<typeof SessionImportPayloadSchema>;
|
|
1149
|
-
/** Inferred runtime type for {@link SessionInnerImportSchema}. */
|
|
1150
|
-
type SessionInnerImportInput = z.infer<typeof SessionInnerImportSchema>;
|
|
814
|
+
}, z.core.$strip>;
|
|
815
|
+
}, z.core.$strip>;
|
|
816
|
+
/** Inferred runtime type for {@link SessionSchema}. */
|
|
817
|
+
type Session = z.infer<typeof SessionSchema>;
|
|
1151
818
|
|
|
1152
819
|
/**
|
|
1153
|
-
*
|
|
1154
|
-
*
|
|
1155
|
-
* (see `docs/spec/workspace.md`). `root` is the `.basou/` directory itself
|
|
1156
|
-
* (i.e. `repositoryRoot/.basou`).
|
|
1157
|
-
*
|
|
1158
|
-
* `files` exposes the well-known top-level files inside `.basou/`. Each path
|
|
1159
|
-
* is computed but not created — they are written by their respective
|
|
1160
|
-
* subsystems (e.g. `writeManifest` for `manifest.yaml`).
|
|
820
|
+
* Threshold above which a still-`running` session with no `session_ended`
|
|
821
|
+
* event is flagged suspect.
|
|
1161
822
|
*
|
|
1162
|
-
*
|
|
1163
|
-
*
|
|
823
|
+
* 24h: long enough that an active long-running session will not be flagged,
|
|
824
|
+
* short enough that an abandoned process is surfaced within a working day.
|
|
825
|
+
* Tunable via CLI option in a later step (continuation backlog #23).
|
|
1164
826
|
*/
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
};
|
|
1173
|
-
readonly locks: string;
|
|
1174
|
-
readonly logs: string;
|
|
1175
|
-
readonly raw: string;
|
|
1176
|
-
readonly tmp: string;
|
|
1177
|
-
readonly files: {
|
|
1178
|
-
readonly manifest: string;
|
|
1179
|
-
readonly status: string;
|
|
1180
|
-
readonly handoff: string;
|
|
1181
|
-
readonly decisions: string;
|
|
1182
|
-
};
|
|
827
|
+
declare const STUCK_THRESHOLD_MS: number;
|
|
828
|
+
type SuspectReason = "events_say_ended_but_yaml_running" | "running_no_end_event";
|
|
829
|
+
type SessionEntry = {
|
|
830
|
+
sessionId: string;
|
|
831
|
+
session: Session;
|
|
832
|
+
suspect: boolean;
|
|
833
|
+
suspectReason: SuspectReason | null;
|
|
1183
834
|
};
|
|
1184
835
|
/**
|
|
1185
|
-
*
|
|
1186
|
-
* `repositoryRoot`. Pure: performs no I/O and is safe to call before the
|
|
1187
|
-
* directory exists.
|
|
836
|
+
* Per-session degradation reason emitted by {@link loadSessionEntries.onSkip}.
|
|
1188
837
|
*
|
|
1189
|
-
*
|
|
1190
|
-
*
|
|
1191
|
-
*
|
|
1192
|
-
*
|
|
1193
|
-
*
|
|
838
|
+
* - `session_yaml_missing` (ENOENT) and `session_yaml_invalid` (parse or schema
|
|
839
|
+
* failure) both omit the entry from the result.
|
|
840
|
+
* - `events_jsonl_unreadable` still pushes the entry with `suspect=false` so
|
|
841
|
+
* the session row remains visible to the caller; only the suspect check is
|
|
842
|
+
* degraded. Matches the existing CLI behaviour at
|
|
843
|
+
* `packages/cli/src/commands/session.ts` (suspect-check stderr warning).
|
|
1194
844
|
*/
|
|
1195
|
-
|
|
845
|
+
type SessionSkipReason = "session_yaml_missing" | "session_yaml_invalid" | "events_jsonl_unreadable";
|
|
846
|
+
type LoadSessionEntriesOptions = {
|
|
847
|
+
/**
|
|
848
|
+
* Single `now` shared across every {@link classifySuspect} call so that
|
|
849
|
+
* sessions classified back-to-back observe the same instant. Avoids
|
|
850
|
+
* boundary races where a session at age ≈ 24h would flip between calls.
|
|
851
|
+
*/
|
|
852
|
+
now: Date;
|
|
853
|
+
onWarning?: (warning: ReplayWarning, sessionId: string) => void;
|
|
854
|
+
onSkip?: (sessionId: string, reason: SessionSkipReason) => void;
|
|
855
|
+
};
|
|
1196
856
|
/**
|
|
1197
|
-
*
|
|
1198
|
-
*
|
|
1199
|
-
* Idempotent: a no-op on an already-initialized layout. Returns the resolved
|
|
1200
|
-
* {@link BasouPaths} so callers can immediately use them.
|
|
857
|
+
* List session directory names under `paths.sessions`, ULID ascending.
|
|
1201
858
|
*
|
|
1202
|
-
*
|
|
1203
|
-
*
|
|
1204
|
-
*
|
|
1205
|
-
*
|
|
859
|
+
* - Returns `[]` when the sessions directory does not exist (empty workspace
|
|
860
|
+
* or pre-init state).
|
|
861
|
+
* - Throws `Error("Failed to enumerate sessions", { cause })` on other I/O.
|
|
862
|
+
* - Only directories are returned (`.gitkeep` and other files are filtered).
|
|
1206
863
|
*
|
|
1207
|
-
*
|
|
1208
|
-
*
|
|
864
|
+
* Sort order is `Array.prototype.sort()` default (Unicode code-point
|
|
865
|
+
* compare). ULIDs are Crockford base32 in uppercase, so the natural sort
|
|
866
|
+
* is also chronological session-start order.
|
|
1209
867
|
*/
|
|
1210
|
-
declare function
|
|
1211
|
-
|
|
1212
|
-
/** Which side of `.basou/approvals/` an approval YAML lives on. */
|
|
1213
|
-
type ApprovalLocation = "pending" | "resolved";
|
|
1214
|
-
/** Result returned by {@link loadApproval}: the parsed approval and where it was found. */
|
|
1215
|
-
type LoadedApproval = {
|
|
1216
|
-
approval: Approval;
|
|
1217
|
-
location: ApprovalLocation;
|
|
1218
|
-
};
|
|
868
|
+
declare function enumerateSessionDirs(paths: BasouPaths): Promise<string[]>;
|
|
1219
869
|
/**
|
|
1220
|
-
*
|
|
1221
|
-
*
|
|
1222
|
-
*
|
|
1223
|
-
*
|
|
1224
|
-
* `
|
|
1225
|
-
*
|
|
1226
|
-
*
|
|
870
|
+
* Read and validate `<paths.sessions>/<sessionId>/session.yaml`.
|
|
871
|
+
*
|
|
872
|
+
* - Re-throws the yaml-store fixed-message `"YAML file not found"` for
|
|
873
|
+
* ENOENT so the caller can branch on it.
|
|
874
|
+
* - Throws `Error("Failed to read session.yaml", { cause })` for parse
|
|
875
|
+
* failures and schema violations (cause is either the YAML parser error
|
|
876
|
+
* or the zod error).
|
|
1227
877
|
*/
|
|
1228
|
-
declare function
|
|
878
|
+
declare function readSessionYaml(paths: BasouPaths, sessionId: string): Promise<Session>;
|
|
1229
879
|
/**
|
|
1230
|
-
*
|
|
1231
|
-
*
|
|
1232
|
-
*
|
|
1233
|
-
*
|
|
1234
|
-
*
|
|
880
|
+
* Classify a `running` session as suspect using one of two rules:
|
|
881
|
+
*
|
|
882
|
+
* - Rule A (`events_say_ended_but_yaml_running`): events.jsonl contains a
|
|
883
|
+
* `session_ended` event but the session.yaml is still `running`. The
|
|
884
|
+
* session ended cleanly in the event log but the YAML write was lost or
|
|
885
|
+
* never reached.
|
|
886
|
+
* - Rule B (`running_no_end_event`): no `session_ended` event and the last
|
|
887
|
+
* event is older than {@link STUCK_THRESHOLD_MS}. The process likely
|
|
888
|
+
* crashed or was killed.
|
|
889
|
+
*
|
|
890
|
+
* Sessions that are not `running` are never suspect.
|
|
891
|
+
*
|
|
892
|
+
* I/O failure on events.jsonl is re-thrown unwrapped so the caller can
|
|
893
|
+
* degrade with a warning instead of treating the session as healthy. The
|
|
894
|
+
* caller is also responsible for surfacing replay warnings via `onWarning`.
|
|
1235
895
|
*/
|
|
1236
|
-
declare function
|
|
1237
|
-
|
|
1238
|
-
|
|
896
|
+
declare function classifySuspect(paths: BasouPaths, sessionId: string, session: Session, now: Date, onWarning?: (warning: ReplayWarning) => void): Promise<{
|
|
897
|
+
suspect: boolean;
|
|
898
|
+
suspectReason: SuspectReason | null;
|
|
1239
899
|
}>;
|
|
1240
900
|
/**
|
|
1241
|
-
*
|
|
1242
|
-
*
|
|
1243
|
-
* a `(expired)` label without mutating the YAML file. Approval expiry uses
|
|
1244
|
-
* lazy-evaluation semantics; actual `approval_expired` event firing is
|
|
1245
|
-
* deferred to a later step.
|
|
901
|
+
* High-level helper that enumerates session dirs, reads each `session.yaml`,
|
|
902
|
+
* and classifies suspect for `running` sessions in one pass.
|
|
1246
903
|
*
|
|
1247
|
-
*
|
|
1248
|
-
*
|
|
1249
|
-
*
|
|
904
|
+
* Per-session degradations are surfaced via `options.onSkip`:
|
|
905
|
+
* - `session_yaml_missing` (ENOENT) and `session_yaml_invalid` (parse or
|
|
906
|
+
* schema violation): the entry is omitted from the result.
|
|
907
|
+
* - `events_jsonl_unreadable`: the entry is still pushed with `suspect=false`
|
|
908
|
+
* so callers can render the session row plus a CLI-side warning.
|
|
909
|
+
*
|
|
910
|
+
* `options.now` is taken once and threaded into every {@link classifySuspect}
|
|
911
|
+
* call so age comparisons are consistent across sessions.
|
|
1250
912
|
*/
|
|
1251
|
-
declare function
|
|
913
|
+
declare function loadSessionEntries(paths: BasouPaths, options: LoadSessionEntriesOptions): Promise<SessionEntry[]>;
|
|
1252
914
|
|
|
915
|
+
type DecisionsRendererInput = {
|
|
916
|
+
paths: BasouPaths;
|
|
917
|
+
nowIso: string;
|
|
918
|
+
onWarning?: (warning: ReplayWarning, sessionId: string) => void;
|
|
919
|
+
onSessionSkip?: (sessionId: string, reason: SessionSkipReason) => void;
|
|
920
|
+
};
|
|
921
|
+
type DecisionsRendererResult = {
|
|
922
|
+
/** Generated body WITHOUT BASOU:GENERATED markers. */
|
|
923
|
+
body: string;
|
|
924
|
+
decisionCount: number;
|
|
925
|
+
};
|
|
1253
926
|
/**
|
|
1254
|
-
*
|
|
927
|
+
* Render the body of `decisions.md` from `decision_recorded` events across
|
|
928
|
+
* every healthy session in the workspace.
|
|
1255
929
|
*
|
|
1256
|
-
*
|
|
1257
|
-
*
|
|
1258
|
-
*
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
*
|
|
1263
|
-
*
|
|
1264
|
-
*
|
|
1265
|
-
*
|
|
930
|
+
* Session enumeration goes through {@link loadSessionEntries} (the same path
|
|
931
|
+
* the handoff renderer uses) so that `session.yaml`-broken sessions are
|
|
932
|
+
* skipped in BOTH outputs and the handoff's `decisionCount` summary stays
|
|
933
|
+
* consistent with the number of sections rendered here.
|
|
934
|
+
*
|
|
935
|
+
* Order: `occurred_at` ascending with `decisionId` (= ULID) as tie-breaker.
|
|
936
|
+
* Both fields are monotonic, so the result is a stable cross-session
|
|
937
|
+
* timeline.
|
|
938
|
+
*
|
|
939
|
+
* The decision rich fields (rationale / alternatives / rejected_reason /
|
|
940
|
+
* linked_events / linked_files) are rendered when the event carries them.
|
|
941
|
+
* `linked_events` and `linked_files` are OPAQUE references: the schema only
|
|
942
|
+
* validates the SHAPE, not existence — references that cannot be resolved
|
|
943
|
+
* to a known event id or an existing file on disk are surfaced inline as
|
|
944
|
+
* `(missing)` so cross-workspace round-trips never reject parse-time.
|
|
1266
945
|
*/
|
|
1267
|
-
declare function
|
|
946
|
+
declare function renderDecisions(input: DecisionsRendererInput): Promise<DecisionsRendererResult>;
|
|
947
|
+
|
|
1268
948
|
/**
|
|
1269
|
-
*
|
|
1270
|
-
* delegates to {@link atomicCreate} so a pre-existing target fails with
|
|
1271
|
-
* EEXIST instead of being silently overwritten.
|
|
949
|
+
* Append a single Basou event to `<sessionDir>/events.jsonl`.
|
|
1272
950
|
*
|
|
1273
|
-
*
|
|
1274
|
-
*
|
|
1275
|
-
*
|
|
951
|
+
* The event is validated against the discriminated union {@link EventSchema}
|
|
952
|
+
* before being serialized as a single JSONL line (UTF-8, terminated by `\n`).
|
|
953
|
+
* Validation enforces the per-variant contract (required fields, source
|
|
954
|
+
* vocabulary, strict variants such as `adapter_output`).
|
|
1276
955
|
*
|
|
1277
|
-
*
|
|
1278
|
-
* `
|
|
956
|
+
* Atomicity: writes go through `appendFile` which uses `O_APPEND`. Lines up
|
|
957
|
+
* to `PIPE_BUF` bytes (Linux 4096 / macOS 512) are written atomically by the
|
|
958
|
+
* kernel; longer lines may interleave with concurrent writers and are not
|
|
959
|
+
* recovered here. v0.1 assumes a single writer per session, so partial-line
|
|
960
|
+
* recovery is delegated to the read side (event replay) when introduced.
|
|
961
|
+
*
|
|
962
|
+
* Throws if validation fails or the underlying append errors. The thrown
|
|
963
|
+
* Error message is pathless; the original error is attached as `cause`.
|
|
964
|
+
*
|
|
965
|
+
* @param sessionDir absolute path to `.basou/sessions/<session_id>/`
|
|
966
|
+
* @param event unknown payload to validate and append
|
|
1279
967
|
*/
|
|
1280
|
-
declare function
|
|
968
|
+
declare function appendEvent(sessionDir: string, event: unknown): Promise<void>;
|
|
1281
969
|
/**
|
|
1282
|
-
*
|
|
1283
|
-
*
|
|
1284
|
-
*
|
|
1285
|
-
*
|
|
970
|
+
* Write `events.jsonl` in one atomic tmp+rename pass via {@link atomicReplace},
|
|
971
|
+
* validating every event against {@link EventSchema} before any disk I/O so
|
|
972
|
+
* a payload that fails validation never leaves a partial file behind.
|
|
973
|
+
*
|
|
974
|
+
* The helper is used by the round-trip importer (`session-import.ts`) and the
|
|
975
|
+
* ad-hoc session orchestrator (`ad-hoc-session.ts`) where a small, fixed batch
|
|
976
|
+
* of events must land together or not at all. Zero events produces a
|
|
977
|
+
* zero-byte file so the session_yaml `events_log` pointer remains valid.
|
|
978
|
+
*
|
|
979
|
+
* Throws `"Invalid Basou event payload"` (same fixed message as
|
|
980
|
+
* {@link appendEvent}) on validation failure, or `"Failed to write
|
|
981
|
+
* events.jsonl"` on a disk I/O failure. The original native error is attached
|
|
982
|
+
* as `cause`.
|
|
1286
983
|
*/
|
|
1287
|
-
declare function
|
|
984
|
+
declare function writeEventsBulk(sessionDir: string, events: Event[]): Promise<void>;
|
|
1288
985
|
|
|
1289
986
|
/**
|
|
1290
|
-
*
|
|
1291
|
-
*
|
|
1292
|
-
*
|
|
1293
|
-
|
|
1294
|
-
type CreateManifestInput = {
|
|
1295
|
-
workspaceName: string;
|
|
1296
|
-
projectName?: string;
|
|
1297
|
-
projectDescription?: string;
|
|
1298
|
-
repositoryUrl?: string | null;
|
|
1299
|
-
/** Override for tests; defaults to `new Date()`. */
|
|
1300
|
-
now?: Date;
|
|
1301
|
-
/** Override for tests; defaults to a freshly generated `ws_<ULID>`. */
|
|
1302
|
-
workspaceId?: PrefixedId<"ws">;
|
|
1303
|
-
};
|
|
1304
|
-
/**
|
|
1305
|
-
* Build a fresh Manifest object that satisfies the manifest schema's
|
|
1306
|
-
* minimum shape. Performs no I/O. Returned object is parse-validated by
|
|
1307
|
-
* `ManifestSchema`.
|
|
987
|
+
* Status classification used by the `file_changed` event schema. Limited to
|
|
988
|
+
* the four classes that simple-git's `git diff --name-status` reliably
|
|
989
|
+
* surfaces; copy / unmerged / typechange entries are intentionally dropped
|
|
990
|
+
* to keep the event payload shape narrow.
|
|
1308
991
|
*/
|
|
1309
|
-
|
|
992
|
+
type FileChangeStatus = "added" | "modified" | "deleted" | "renamed";
|
|
1310
993
|
/**
|
|
1311
|
-
*
|
|
1312
|
-
* `
|
|
1313
|
-
*
|
|
1314
|
-
* Refuses to overwrite an existing manifest unless `force: true`.
|
|
994
|
+
* Single file-level change observed between two refs. `old_path` is set
|
|
995
|
+
* only for `renamed` entries (the previous path of the file).
|
|
1315
996
|
*/
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
997
|
+
type FileChange = {
|
|
998
|
+
path: string;
|
|
999
|
+
old_path?: string;
|
|
1000
|
+
status: FileChangeStatus;
|
|
1001
|
+
};
|
|
1319
1002
|
/**
|
|
1320
|
-
*
|
|
1321
|
-
*
|
|
1003
|
+
* Result of {@link getDiff}. The `changed_files` array is in git's natural
|
|
1004
|
+
* `--name-status` order; callers requiring deterministic ordering should
|
|
1005
|
+
* sort by `path` themselves.
|
|
1322
1006
|
*/
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
type AppendBasouGitignoreResult = {
|
|
1326
|
-
/** True if the block was appended (or the file was newly created). */
|
|
1327
|
-
readonly appended: boolean;
|
|
1007
|
+
type DiffResult = {
|
|
1008
|
+
changed_files: FileChange[];
|
|
1328
1009
|
};
|
|
1329
1010
|
/**
|
|
1330
|
-
*
|
|
1011
|
+
* Compute the file-level diff between two git refs.
|
|
1331
1012
|
*
|
|
1332
|
-
*
|
|
1333
|
-
*
|
|
1334
|
-
*
|
|
1013
|
+
* Returns a list of changed file paths classified by status (added /
|
|
1014
|
+
* modified / deleted / renamed). Diff content is intentionally NOT
|
|
1015
|
+
* returned — `file_changed` events record paths only, and raw diff bodies
|
|
1016
|
+
* are excluded so the trace cannot inadvertently leak source code that may
|
|
1017
|
+
* be sensitive. Use `git show <ref>` to obtain the underlying diff.
|
|
1335
1018
|
*
|
|
1336
|
-
*
|
|
1337
|
-
*
|
|
1338
|
-
*
|
|
1339
|
-
*
|
|
1340
|
-
* (idempotent).
|
|
1341
|
-
* - If `.gitignore` is a symlink, the link is followed and the target file
|
|
1342
|
-
* is updated. Symlinks are not rejected.
|
|
1019
|
+
* Pathless contract: every thrown message is a fixed string from the set
|
|
1020
|
+
* {`Not a git repository`, `Git executable not found in PATH. Install git
|
|
1021
|
+
* first.`, `Invalid ref`, `Failed to compute git diff`}; native errors are
|
|
1022
|
+
* preserved on `Error.cause`.
|
|
1343
1023
|
*
|
|
1344
|
-
*
|
|
1345
|
-
*
|
|
1346
|
-
*
|
|
1024
|
+
* Special cases:
|
|
1025
|
+
* - `baseRef === headRef` short-circuits to an empty result
|
|
1026
|
+
* - copy / unmerged / typechange / unknown status codes are skipped
|
|
1027
|
+
*
|
|
1028
|
+
* @param repoRoot absolute path to the git repository root
|
|
1029
|
+
* @param baseRef base ref (e.g. session-start HEAD sha)
|
|
1030
|
+
* @param headRef head ref (e.g. session-end HEAD sha)
|
|
1347
1031
|
*/
|
|
1348
|
-
declare function
|
|
1032
|
+
declare function getDiff(repoRoot: string, baseRef: string, headRef: string): Promise<DiffResult>;
|
|
1349
1033
|
|
|
1350
1034
|
/**
|
|
1351
|
-
*
|
|
1352
|
-
*
|
|
1353
|
-
*
|
|
1354
|
-
*
|
|
1035
|
+
* Payload subset of `git_snapshot` event, mechanically derived from the
|
|
1036
|
+
* zod-inferred event type. The wrapping event-shape fields
|
|
1037
|
+
* (schema_version, id, session_id, occurred_at, source, type) are added by
|
|
1038
|
+
* the caller (session lifecycle in later steps) when constructing the
|
|
1039
|
+
* event, so the schema remains the single source of truth.
|
|
1040
|
+
*
|
|
1041
|
+
* `ahead` / `behind` are omitted when there is no remote or no upstream
|
|
1042
|
+
* tracking; the schema declares both as optional non-negative integers.
|
|
1355
1043
|
*/
|
|
1356
|
-
type
|
|
1357
|
-
type LockHandle = {
|
|
1358
|
-
/**
|
|
1359
|
-
* Release the lock by unlinking the lockfile. Best-effort: any unlink error
|
|
1360
|
-
* is swallowed so a doubled release does not raise, and disk state never
|
|
1361
|
-
* holds a stranded lockfile after the caller's `finally` block.
|
|
1362
|
-
*/
|
|
1363
|
-
release: () => Promise<void>;
|
|
1364
|
-
};
|
|
1044
|
+
type GitSnapshot = Omit<GitSnapshotEvent, "schema_version" | "id" | "session_id" | "occurred_at" | "source" | "type">;
|
|
1365
1045
|
/**
|
|
1366
|
-
*
|
|
1367
|
-
*
|
|
1368
|
-
* and acquire timestamp so a competitor can detect stale locks left by a
|
|
1369
|
-
* SIGINT'd CLI run and recover automatically.
|
|
1046
|
+
* Resolve the absolute path of the Git repository root that contains `cwd`.
|
|
1047
|
+
* Equivalent to `git rev-parse --show-toplevel`.
|
|
1370
1048
|
*
|
|
1371
|
-
*
|
|
1372
|
-
*
|
|
1373
|
-
*
|
|
1374
|
-
*
|
|
1375
|
-
*
|
|
1376
|
-
* the atomic create once.
|
|
1377
|
-
* - If still EEXIST after the retry (= another competitor won the race),
|
|
1378
|
-
* throw `"Lock is held by another process"`.
|
|
1379
|
-
* - If the holder is alive, throw `"Lock is held by another process"`
|
|
1380
|
-
* without retrying.
|
|
1049
|
+
* Throws `Error("Git executable not found in PATH. Install git first.")`
|
|
1050
|
+
* with the spawn error attached as `cause` when git itself is missing.
|
|
1051
|
+
* Throws `Error("Not a git repository")` (without command-specific suffix)
|
|
1052
|
+
* when `cwd` is not inside a repository — callers MAY wrap with their own
|
|
1053
|
+
* "Run 'git init' first, then re-run 'basou XXX'." suffix.
|
|
1381
1054
|
*
|
|
1382
|
-
*
|
|
1383
|
-
*
|
|
1384
|
-
* the next acquire to recover.
|
|
1055
|
+
* Pathless contract: the thrown message never embeds `cwd` or any absolute
|
|
1056
|
+
* path; native errors are kept on `error.cause` for verbose surfacing.
|
|
1385
1057
|
*/
|
|
1386
|
-
declare function
|
|
1387
|
-
|
|
1058
|
+
declare function resolveRepositoryRoot(cwd: string): Promise<string>;
|
|
1388
1059
|
/**
|
|
1389
|
-
*
|
|
1390
|
-
*
|
|
1391
|
-
*
|
|
1392
|
-
*
|
|
1060
|
+
* Read `remote.origin.url` from the local repository config. Returns
|
|
1061
|
+
* `undefined` if the remote is unset, the value is empty, or the lookup
|
|
1062
|
+
* fails for any reason (best-effort).
|
|
1063
|
+
*
|
|
1064
|
+
* The `--local` scope is critical: callers MUST NOT pick up the developer's
|
|
1065
|
+
* global remote.origin.url, which could leak the wrong repository URL into
|
|
1066
|
+
* `manifest.yaml`.
|
|
1393
1067
|
*/
|
|
1394
|
-
declare function
|
|
1395
|
-
|
|
1068
|
+
declare function tryRemoteUrl(repositoryRoot: string): Promise<string | undefined>;
|
|
1396
1069
|
/**
|
|
1397
|
-
*
|
|
1398
|
-
*
|
|
1399
|
-
*
|
|
1400
|
-
* `
|
|
1070
|
+
* Build a {@link GitSnapshot} for the repository at `repositoryRoot`. The
|
|
1071
|
+
* caller is responsible for ensuring `repositoryRoot` is the canonical root
|
|
1072
|
+
* (typically obtained via {@link resolveRepositoryRoot}); this function
|
|
1073
|
+
* verifies repo membership via `git rev-parse --is-inside-work-tree` to
|
|
1074
|
+
* distinguish a non-git directory from an empty repository.
|
|
1401
1075
|
*
|
|
1402
|
-
*
|
|
1403
|
-
*
|
|
1076
|
+
* Edge cases:
|
|
1077
|
+
* - **non-git directory**: throws `Error("Not a git repository")`
|
|
1078
|
+
* - **empty repo (no commits)**: throws `Error("No commits in repository")`
|
|
1079
|
+
* - **detached HEAD**: `branch = "HEAD"`, `head = commit hash`,
|
|
1080
|
+
* `ahead`/`behind` omitted
|
|
1081
|
+
* - **no remote / no upstream tracking**: `ahead`/`behind` omitted
|
|
1404
1082
|
*
|
|
1405
|
-
*
|
|
1406
|
-
* could still be replaced between this check and the subsequent write. The
|
|
1407
|
-
* goal is to detect already-swapped symlinks, not to race-proof the
|
|
1408
|
-
* filesystem.
|
|
1083
|
+
* Pathless contract preserved on every throw path.
|
|
1409
1084
|
*/
|
|
1410
|
-
declare function
|
|
1085
|
+
declare function getSnapshot(repositoryRoot: string): Promise<GitSnapshot>;
|
|
1086
|
+
|
|
1411
1087
|
/**
|
|
1412
|
-
*
|
|
1413
|
-
* each subdirectory's presence via `lstat`. Read-only with respect to the
|
|
1414
|
-
* workspace state; writes nothing. The result is re-validated by
|
|
1415
|
-
* `StatusSchema.parse` before being returned.
|
|
1088
|
+
* Allowed ID type prefixes for Basou entities.
|
|
1416
1089
|
*
|
|
1417
|
-
*
|
|
1090
|
+
* Frozen at runtime so that mutating the exported array cannot diverge from
|
|
1091
|
+
* the validation set used internally. The single source of truth for both
|
|
1092
|
+
* the `IdPrefix` type and runtime prefix checks.
|
|
1418
1093
|
*/
|
|
1419
|
-
declare
|
|
1420
|
-
manifest: Manifest;
|
|
1421
|
-
paths: BasouPaths;
|
|
1422
|
-
now?: Date;
|
|
1423
|
-
}): Promise<StatusSnapshot>;
|
|
1424
|
-
/**
|
|
1425
|
-
* Atomically write a StatusSnapshot to `paths.files.status`.
|
|
1426
|
-
*
|
|
1427
|
-
* Re-validates via `StatusSchema.parse` before any file I/O, so an invalid
|
|
1428
|
-
* snapshot throws synchronously and never overwrites the existing
|
|
1429
|
-
* `status.json`. Delegates the tmp-file + rename pass to {@link atomicReplace}.
|
|
1430
|
-
*
|
|
1431
|
-
* **Precondition**: callers MUST invoke {@link assertBasouRootSafe} on
|
|
1432
|
-
* `paths.root` first to ensure `.basou` is a real directory and not a
|
|
1433
|
-
* swapped symlink. `writeStatus` does not redo this guard — it trusts the
|
|
1434
|
-
* caller — so a direct invocation without the guard could write
|
|
1435
|
-
* `status.json` outside the repository root.
|
|
1436
|
-
*/
|
|
1437
|
-
declare function writeStatus(paths: BasouPaths, snapshot: StatusSnapshot): Promise<void>;
|
|
1094
|
+
declare const ID_PREFIXES: readonly ["ws", "task", "ses", "evt", "appr", "decision"];
|
|
1438
1095
|
/**
|
|
1439
|
-
*
|
|
1440
|
-
*
|
|
1441
|
-
* Older or newer status.json shapes will fail `StatusSchema.parse` —
|
|
1442
|
-
* callers regenerate by calling `buildStatusSnapshot` + `writeStatus`.
|
|
1096
|
+
* Type prefix used for Basou entity IDs.
|
|
1097
|
+
* Format: `<prefix>_<26-char ULID>`, e.g. `ws_01HXABCDEF1234567890ABCDEF`.
|
|
1443
1098
|
*/
|
|
1444
|
-
|
|
1445
|
-
|
|
1099
|
+
type IdPrefix = (typeof ID_PREFIXES)[number];
|
|
1446
1100
|
/**
|
|
1447
|
-
*
|
|
1448
|
-
* generator never throws on these — it skips the offending line and continues.
|
|
1101
|
+
* A Basou entity ID as a template literal type.
|
|
1449
1102
|
*
|
|
1450
|
-
* `
|
|
1451
|
-
* the
|
|
1452
|
-
* instead of yielded so consumers cannot accidentally observe a
|
|
1453
|
-
* partially-written record.
|
|
1103
|
+
* `PrefixedId<"ses">` narrows to ``ses_${string}`` so a session schema can
|
|
1104
|
+
* preserve the prefix in its inferred type beyond runtime validation.
|
|
1454
1105
|
*/
|
|
1455
|
-
type
|
|
1456
|
-
kind: "partial_trailing_line";
|
|
1457
|
-
line: number;
|
|
1458
|
-
} | {
|
|
1459
|
-
kind: "malformed_json";
|
|
1460
|
-
line: number;
|
|
1461
|
-
cause: unknown;
|
|
1462
|
-
} | {
|
|
1463
|
-
kind: "schema_violation";
|
|
1464
|
-
line: number;
|
|
1465
|
-
cause: unknown;
|
|
1466
|
-
};
|
|
1467
|
-
type ReplayOptions = {
|
|
1468
|
-
/**
|
|
1469
|
-
* Hook to receive recoverable warnings (partial line / malformed JSON /
|
|
1470
|
-
* schema violation). When omitted, warnings are silently dropped — callers
|
|
1471
|
-
* that want to surface them (e.g. CLI orchestration) MUST provide this hook.
|
|
1472
|
-
*/
|
|
1473
|
-
onWarning?: (warning: ReplayWarning) => void;
|
|
1474
|
-
};
|
|
1106
|
+
type PrefixedId<P extends IdPrefix = IdPrefix> = `${P}_${string}`;
|
|
1475
1107
|
/**
|
|
1476
|
-
*
|
|
1108
|
+
* Generate a Crockford Base32 ULID.
|
|
1477
1109
|
*
|
|
1478
|
-
*
|
|
1479
|
-
*
|
|
1480
|
-
*
|
|
1481
|
-
* error attached as `cause`. The thrown message never embeds an absolute
|
|
1482
|
-
* path (pathless contract).
|
|
1483
|
-
* - Trailing partial line that parses as a valid event: dropped silently when
|
|
1484
|
-
* {@link ReplayOptions.onWarning} is omitted; otherwise reported as
|
|
1485
|
-
* `partial_trailing_line`. A trailing partial line that fails JSON parsing
|
|
1486
|
-
* is reported as `malformed_json` instead.
|
|
1487
|
-
* - Malformed JSON / schema violation: skipped, with the corresponding
|
|
1488
|
-
* warning when a hook is provided.
|
|
1110
|
+
* The result is a 26-character, lexicographically time-sortable identifier.
|
|
1111
|
+
* Multiple calls within the same millisecond are strictly increasing for the
|
|
1112
|
+
* lifetime of the current process.
|
|
1489
1113
|
*
|
|
1490
|
-
*
|
|
1491
|
-
*
|
|
1492
|
-
*
|
|
1493
|
-
|
|
1494
|
-
declare function replayEvents(sessionDir: string, options?: ReplayOptions): AsyncGenerator<Event, void, void>;
|
|
1495
|
-
/**
|
|
1496
|
-
* Eager array helper: collect every event from {@link replayEvents} into
|
|
1497
|
-
* memory. Convenience for callers that need the full list in one structure
|
|
1498
|
-
* (e.g. `basou session show` rendering).
|
|
1499
|
-
*/
|
|
1500
|
-
declare function readAllEvents(sessionDir: string, options?: ReplayOptions): Promise<Event[]>;
|
|
1501
|
-
|
|
1502
|
-
/**
|
|
1503
|
-
* Threshold above which a still-`running` session with no `session_ended`
|
|
1504
|
-
* event is flagged suspect.
|
|
1114
|
+
* NOTE: `seedTime` is forwarded to the underlying monotonic factory and is
|
|
1115
|
+
* NOT a deterministic seed: repeated calls with the same `seedTime` still
|
|
1116
|
+
* return strictly increasing values, because the factory increments its
|
|
1117
|
+
* internal counter on each call.
|
|
1505
1118
|
*
|
|
1506
|
-
*
|
|
1507
|
-
*
|
|
1508
|
-
* Tunable via CLI option in a later step (continuation backlog #23).
|
|
1119
|
+
* @param seedTime Optional millisecond timestamp passed to the monotonic
|
|
1120
|
+
* factory. Useful for ordered generation in tests; not deterministic.
|
|
1509
1121
|
*/
|
|
1510
|
-
declare
|
|
1511
|
-
type SuspectReason = "events_say_ended_but_yaml_running" | "running_no_end_event";
|
|
1512
|
-
type SessionEntry = {
|
|
1513
|
-
sessionId: string;
|
|
1514
|
-
session: Session;
|
|
1515
|
-
suspect: boolean;
|
|
1516
|
-
suspectReason: SuspectReason | null;
|
|
1517
|
-
};
|
|
1122
|
+
declare function ulid(seedTime?: number): string;
|
|
1518
1123
|
/**
|
|
1519
|
-
*
|
|
1124
|
+
* Generate a prefixed Basou ID, e.g. `ses_01HXABCDEF1234567890ABCDEF`.
|
|
1520
1125
|
*
|
|
1521
|
-
*
|
|
1522
|
-
*
|
|
1523
|
-
*
|
|
1524
|
-
*
|
|
1525
|
-
*
|
|
1526
|
-
*
|
|
1126
|
+
* The return type preserves the prefix as a template literal type so that
|
|
1127
|
+
* downstream zod schemas can narrow an `IdPrefix` parameter through the API.
|
|
1128
|
+
*
|
|
1129
|
+
* Throws if `prefix` is not one of {@link ID_PREFIXES}. The runtime guard
|
|
1130
|
+
* defends against JavaScript callers and casted TypeScript that bypass the
|
|
1131
|
+
* compile-time `IdPrefix` constraint.
|
|
1527
1132
|
*/
|
|
1528
|
-
|
|
1529
|
-
type LoadSessionEntriesOptions = {
|
|
1530
|
-
/**
|
|
1531
|
-
* Single `now` shared across every {@link classifySuspect} call so that
|
|
1532
|
-
* sessions classified back-to-back observe the same instant. Avoids
|
|
1533
|
-
* boundary races where a session at age ≈ 24h would flip between calls.
|
|
1534
|
-
*/
|
|
1535
|
-
now: Date;
|
|
1536
|
-
onWarning?: (warning: ReplayWarning, sessionId: string) => void;
|
|
1537
|
-
onSkip?: (sessionId: string, reason: SessionSkipReason) => void;
|
|
1538
|
-
};
|
|
1133
|
+
declare function prefixedUlid<P extends IdPrefix>(prefix: P): PrefixedId<P>;
|
|
1539
1134
|
/**
|
|
1540
|
-
*
|
|
1135
|
+
* Check whether the given string is a valid prefixed Basou ID.
|
|
1541
1136
|
*
|
|
1542
|
-
*
|
|
1543
|
-
*
|
|
1544
|
-
*
|
|
1545
|
-
* -
|
|
1137
|
+
* Returns true only if the string has shape `<prefix>_<ULID>` where prefix is
|
|
1138
|
+
* one of {@link ID_PREFIXES} and the trailing 26 characters form a valid
|
|
1139
|
+
* Crockford Base32 ULID. Validation combines a strict shape regex (to enforce
|
|
1140
|
+
* the 0-7 leading char and the I/L/O/U exclusion) with the npm `ulid`
|
|
1141
|
+
* library's `isValid` for forward compatibility.
|
|
1546
1142
|
*
|
|
1547
|
-
*
|
|
1548
|
-
*
|
|
1549
|
-
* is also chronological session-start order.
|
|
1143
|
+
* NOTE: This validates the prefix is known. Schemas that require a specific
|
|
1144
|
+
* prefix (e.g. only `ses_*` for a session ID) must add their own narrowing.
|
|
1550
1145
|
*/
|
|
1551
|
-
declare function
|
|
1146
|
+
declare function isValidPrefixedId(value: string): boolean;
|
|
1147
|
+
|
|
1552
1148
|
/**
|
|
1553
|
-
*
|
|
1554
|
-
*
|
|
1555
|
-
*
|
|
1556
|
-
*
|
|
1557
|
-
*
|
|
1558
|
-
* failures and schema violations (cause is either the YAML parser error
|
|
1559
|
-
* or the zod error).
|
|
1149
|
+
* Schema for `.basou/manifest.yaml`. The minimal manifest carries
|
|
1150
|
+
* schema_version, basou_version, workspace metadata, project info, enabled
|
|
1151
|
+
* capabilities, approval policy, adapter config, and git policy. The
|
|
1152
|
+
* `adapters."claude-code"` key uses a hyphen; downstream code accesses it
|
|
1153
|
+
* via bracket notation.
|
|
1560
1154
|
*/
|
|
1561
|
-
declare
|
|
1155
|
+
declare const ManifestSchema: z.ZodObject<{
|
|
1156
|
+
schema_version: z.ZodLiteral<"0.1.0">;
|
|
1157
|
+
basou_version: z.ZodLiteral<"0.1.0">;
|
|
1158
|
+
workspace: z.ZodObject<{
|
|
1159
|
+
id: z.ZodString & z.ZodType<`ws_${string}`, string, z.core.$ZodTypeInternals<`ws_${string}`, string>>;
|
|
1160
|
+
name: z.ZodString;
|
|
1161
|
+
created_at: z.ZodString;
|
|
1162
|
+
updated_at: z.ZodString;
|
|
1163
|
+
}, z.core.$strip>;
|
|
1164
|
+
project: z.ZodObject<{
|
|
1165
|
+
name: z.ZodOptional<z.ZodString>;
|
|
1166
|
+
description: z.ZodOptional<z.ZodString>;
|
|
1167
|
+
repository_url: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
1168
|
+
}, z.core.$strip>;
|
|
1169
|
+
capabilities: z.ZodObject<{
|
|
1170
|
+
enabled: z.ZodArray<z.ZodString>;
|
|
1171
|
+
}, z.core.$strip>;
|
|
1172
|
+
approval: z.ZodObject<{
|
|
1173
|
+
required_for: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
1174
|
+
default_risk_level: z.ZodEnum<{
|
|
1175
|
+
low: "low";
|
|
1176
|
+
medium: "medium";
|
|
1177
|
+
high: "high";
|
|
1178
|
+
critical: "critical";
|
|
1179
|
+
}>;
|
|
1180
|
+
}, z.core.$strip>;
|
|
1181
|
+
adapters: z.ZodObject<{
|
|
1182
|
+
"claude-code": z.ZodObject<{
|
|
1183
|
+
enabled: z.ZodBoolean;
|
|
1184
|
+
config_path: z.ZodOptional<z.ZodString>;
|
|
1185
|
+
}, z.core.$strip>;
|
|
1186
|
+
}, z.core.$strip>;
|
|
1187
|
+
git: z.ZodObject<{
|
|
1188
|
+
events_log: z.ZodDefault<z.ZodEnum<{
|
|
1189
|
+
ignore: "ignore";
|
|
1190
|
+
commit: "commit";
|
|
1191
|
+
}>>;
|
|
1192
|
+
}, z.core.$strip>;
|
|
1193
|
+
}, z.core.$strip>;
|
|
1194
|
+
/** Inferred runtime type for {@link ManifestSchema}. */
|
|
1195
|
+
type Manifest = z.infer<typeof ManifestSchema>;
|
|
1196
|
+
|
|
1562
1197
|
/**
|
|
1563
|
-
*
|
|
1198
|
+
* Task lifecycle states.
|
|
1564
1199
|
*
|
|
1565
|
-
*
|
|
1566
|
-
*
|
|
1567
|
-
*
|
|
1568
|
-
*
|
|
1569
|
-
*
|
|
1570
|
-
* event is older than {@link STUCK_THRESHOLD_MS}. The process likely
|
|
1571
|
-
* crashed or was killed.
|
|
1200
|
+
* The storage layer's `ALLOWED_TRANSITIONS` map (= source of truth in
|
|
1201
|
+
* `tasks.ts`) is the authoritative graph; the comment below is a snapshot.
|
|
1202
|
+
* `planned` reaches `done` / `cancelled` directly so tasks completed (or
|
|
1203
|
+
* abandoned) outside an explicit in-progress phase can close in a single
|
|
1204
|
+
* CLI call:
|
|
1572
1205
|
*
|
|
1573
|
-
*
|
|
1206
|
+
* planned → {in_progress | done | cancelled}
|
|
1207
|
+
* in_progress → {done | cancelled}
|
|
1208
|
+
* done / cancelled = terminal
|
|
1574
1209
|
*
|
|
1575
|
-
*
|
|
1576
|
-
* degrade with a warning instead of treating the session as healthy. The
|
|
1577
|
-
* caller is also responsible for surfacing replay warnings via `onWarning`.
|
|
1210
|
+
* Self-edges are rejected so the audit trail stays monotonic.
|
|
1578
1211
|
*/
|
|
1579
|
-
declare
|
|
1580
|
-
|
|
1581
|
-
|
|
1212
|
+
declare const TaskStatusSchema: z.ZodEnum<{
|
|
1213
|
+
planned: "planned";
|
|
1214
|
+
in_progress: "in_progress";
|
|
1215
|
+
done: "done";
|
|
1216
|
+
cancelled: "cancelled";
|
|
1582
1217
|
}>;
|
|
1218
|
+
/** Inferred runtime type for {@link TaskStatusSchema}. */
|
|
1219
|
+
type TaskStatus = z.infer<typeof TaskStatusSchema>;
|
|
1583
1220
|
/**
|
|
1584
|
-
*
|
|
1585
|
-
* and classifies suspect for `running` sessions in one pass.
|
|
1586
|
-
*
|
|
1587
|
-
* Per-session degradations are surfaced via `options.onSkip`:
|
|
1588
|
-
* - `session_yaml_missing` (ENOENT) and `session_yaml_invalid` (parse or
|
|
1589
|
-
* schema violation): the entry is omitted from the result.
|
|
1590
|
-
* - `events_jsonl_unreadable`: the entry is still pushed with `suspect=false`
|
|
1591
|
-
* so callers can render the session row plus a CLI-side warning.
|
|
1221
|
+
* Schema for the YAML front matter of `.basou/tasks/<task_id>.md`.
|
|
1592
1222
|
*
|
|
1593
|
-
*
|
|
1594
|
-
*
|
|
1223
|
+
* The markdown body after the front matter is intentionally NOT modelled
|
|
1224
|
+
* here — it is free-form user-edited content. The storage layer splits
|
|
1225
|
+
* the file into `task` (this schema) and `body` (the trailing string).
|
|
1595
1226
|
*/
|
|
1596
|
-
declare
|
|
1597
|
-
|
|
1598
|
-
|
|
1599
|
-
|
|
1600
|
-
|
|
1601
|
-
|
|
1602
|
-
|
|
1603
|
-
|
|
1604
|
-
|
|
1605
|
-
|
|
1606
|
-
|
|
1607
|
-
|
|
1608
|
-
|
|
1609
|
-
|
|
1610
|
-
|
|
1611
|
-
|
|
1612
|
-
|
|
1613
|
-
|
|
1614
|
-
|
|
1615
|
-
|
|
1616
|
-
|
|
1617
|
-
} | {
|
|
1618
|
-
kind: "no_markers";
|
|
1619
|
-
} | {
|
|
1620
|
-
kind: "missing_start";
|
|
1621
|
-
} | {
|
|
1622
|
-
kind: "missing_end";
|
|
1623
|
-
} | {
|
|
1624
|
-
kind: "multiple_pairs";
|
|
1625
|
-
} | {
|
|
1626
|
-
kind: "wrong_order";
|
|
1627
|
-
};
|
|
1628
|
-
/**
|
|
1629
|
-
* Read a markdown file as UTF-8 text. Returns `null` when the file does not
|
|
1630
|
-
* exist; throws `Error("Failed to read markdown file", { cause })` for other
|
|
1631
|
-
* I/O failures (pathless contract — never embed the absolute path in the
|
|
1632
|
-
* thrown `message`).
|
|
1633
|
-
*/
|
|
1634
|
-
declare function readMarkdownFile(filePath: string): Promise<string | null>;
|
|
1635
|
-
/**
|
|
1636
|
-
* Atomically write a markdown body via {@link atomicReplace}. The shared
|
|
1637
|
-
* helper handles the tmp-file + rename sequence, `wx` collision guard, and
|
|
1638
|
-
* best-effort tmp cleanup on failure.
|
|
1639
|
-
*
|
|
1640
|
-
* On any failure the original error is re-thrown as
|
|
1641
|
-
* `Error("Failed to write markdown file", { cause })` (pathless contract).
|
|
1642
|
-
*/
|
|
1643
|
-
declare function writeMarkdownFile(filePath: string, body: string): Promise<void>;
|
|
1644
|
-
/**
|
|
1645
|
-
* Parse a markdown body and identify the BASOU:GENERATED marker region.
|
|
1646
|
-
*
|
|
1647
|
-
* Returns one of six `kind` discriminants:
|
|
1648
|
-
* - `ok`: exactly one START line followed by exactly one END line in the
|
|
1649
|
-
* correct order. `before` / `generated` / `after` slice the original
|
|
1650
|
-
* text by character offsets so CRLF / LF are preserved verbatim outside
|
|
1651
|
-
* the marker region.
|
|
1652
|
-
* - `no_markers`: both START and END absent (legacy file / fresh write).
|
|
1653
|
-
* - `missing_start` / `missing_end`: exactly one of the pair is present.
|
|
1654
|
-
* - `multiple_pairs`: more than one START or END line.
|
|
1655
|
-
* - `wrong_order`: END appears before START.
|
|
1656
|
-
*
|
|
1657
|
-
* Matching is strict: leading/trailing whitespace, BOM, and comment
|
|
1658
|
-
* compression (`<!--BASOU:...-->`) all bypass the marker and are treated
|
|
1659
|
-
* as legacy content.
|
|
1660
|
-
*/
|
|
1661
|
-
declare function parseMarkers(content: string): MarkerSection;
|
|
1662
|
-
/**
|
|
1663
|
-
* Build the final markdown body by replacing the BASOU:GENERATED region.
|
|
1664
|
-
*
|
|
1665
|
-
* - `existing === null` (no file yet): return `<START>\n<generated>\n<END>\n`.
|
|
1666
|
-
* - existing parses to `ok`: replace the marked region and keep everything
|
|
1667
|
-
* before START and after END untouched (preserving manual additions).
|
|
1668
|
-
* - any other parse result: throw a pathless error referencing `fileLabel`.
|
|
1669
|
-
*
|
|
1670
|
-
* The caller passes `fileLabel` (e.g. `"handoff.md"` or `"decisions.md"`)
|
|
1671
|
-
* so the error message is informative without leaking an absolute path.
|
|
1672
|
-
*/
|
|
1673
|
-
declare function renderWithMarkers(existing: string | null, generated: string, fileLabel: string): string;
|
|
1674
|
-
|
|
1675
|
-
/**
|
|
1676
|
-
* Options for {@link importSessionFromJson}. All fields are optional.
|
|
1677
|
-
*
|
|
1678
|
-
* - `labelOverride` / `taskIdOverride` come from the CLI `--label` / `--task`
|
|
1679
|
-
* flags and win over the corresponding fields on the input payload.
|
|
1680
|
-
* - `dryRun` skips disk writes entirely and returns a preview result.
|
|
1681
|
-
*/
|
|
1682
|
-
type ImportSessionOptions = {
|
|
1683
|
-
labelOverride?: string;
|
|
1684
|
-
taskIdOverride?: string;
|
|
1685
|
-
dryRun?: boolean;
|
|
1686
|
-
};
|
|
1687
|
-
/**
|
|
1688
|
-
* Result of a successful import. `finalStatus` is always the literal
|
|
1689
|
-
* `"imported"` (per the import-session lifecycle policy); `finalSourceKind`
|
|
1690
|
-
* mirrors the input's `session.source.kind` so round-trip imports preserve
|
|
1691
|
-
* provenance.
|
|
1692
|
-
*
|
|
1693
|
-
* `pathSanitizeReport` summarises how many path-shaped fields the importer
|
|
1694
|
-
* rewrote on the way in: `related_files[]` entries plus a single boolean
|
|
1695
|
-
* for `working_directory`. The CLI wrapper surfaces this as a one-line
|
|
1696
|
-
* stderr warning when the total is non-zero so the operator sees that
|
|
1697
|
-
* machine-private prefixes were stripped.
|
|
1698
|
-
*/
|
|
1699
|
-
type ImportSessionResult = {
|
|
1700
|
-
sessionId: PrefixedId<"ses">;
|
|
1701
|
-
eventCount: number;
|
|
1702
|
-
finalStatus: SessionStatus;
|
|
1703
|
-
finalSourceKind: SessionSourceKind;
|
|
1704
|
-
pathSanitizeReport: {
|
|
1705
|
-
relatedFiles: number;
|
|
1706
|
-
workingDirectoryRewritten: boolean;
|
|
1707
|
-
};
|
|
1708
|
-
};
|
|
1709
|
-
/**
|
|
1710
|
-
* Import a round-trip JSON payload into `.basou/sessions/<new>/`. The caller
|
|
1711
|
-
* MUST validate the payload against {@link SessionImportPayloadSchema} first
|
|
1712
|
-
* and gate the `schema_version === "0.1.0"` literal check externally; this
|
|
1713
|
-
* function trusts both invariants.
|
|
1714
|
-
*
|
|
1715
|
-
* On success a fresh session ID is minted and a complete
|
|
1716
|
-
* `session.yaml` + `events.jsonl` pair is written atomically. On any post-
|
|
1717
|
-
* mkdir failure the session directory is removed best-effort so partial
|
|
1718
|
-
* imports do not leave `session_yaml_missing` half-states behind.
|
|
1719
|
-
*
|
|
1720
|
-
* Throws `Error` with one of the fixed messages enumerated by the import contract
|
|
1721
|
-
* §"Error messages" table; the original native error is attached as `cause`
|
|
1722
|
-
* for `--verbose` rendering.
|
|
1723
|
-
*/
|
|
1724
|
-
declare function importSessionFromJson(paths: BasouPaths, manifest: Manifest, payload: SessionImportPayload, options: ImportSessionOptions): Promise<ImportSessionResult>;
|
|
1227
|
+
declare const TaskSchema: z.ZodObject<{
|
|
1228
|
+
schema_version: z.ZodLiteral<"0.1.0">;
|
|
1229
|
+
task: z.ZodObject<{
|
|
1230
|
+
id: z.ZodString & z.ZodType<`task_${string}`, string, z.core.$ZodTypeInternals<`task_${string}`, string>>;
|
|
1231
|
+
title: z.ZodString;
|
|
1232
|
+
label: z.ZodOptional<z.ZodString>;
|
|
1233
|
+
status: z.ZodEnum<{
|
|
1234
|
+
planned: "planned";
|
|
1235
|
+
in_progress: "in_progress";
|
|
1236
|
+
done: "done";
|
|
1237
|
+
cancelled: "cancelled";
|
|
1238
|
+
}>;
|
|
1239
|
+
created_at: z.ZodString;
|
|
1240
|
+
updated_at: z.ZodString;
|
|
1241
|
+
workspace_id: z.ZodString & z.ZodType<`ws_${string}`, string, z.core.$ZodTypeInternals<`ws_${string}`, string>>;
|
|
1242
|
+
created_in_session: z.ZodString & z.ZodType<`ses_${string}`, string, z.core.$ZodTypeInternals<`ses_${string}`, string>>;
|
|
1243
|
+
linked_sessions: z.ZodDefault<z.ZodArray<z.ZodString & z.ZodType<`ses_${string}`, string, z.core.$ZodTypeInternals<`ses_${string}`, string>>>>;
|
|
1244
|
+
}, z.core.$strip>;
|
|
1245
|
+
}, z.core.$strip>;
|
|
1246
|
+
/** Inferred runtime type for {@link TaskSchema}. */
|
|
1247
|
+
type Task = z.infer<typeof TaskSchema>;
|
|
1725
1248
|
|
|
1726
1249
|
/**
|
|
1727
1250
|
* Thrown when the ad-hoc session was fully written to disk (4 lifecycle
|
|
@@ -2381,10 +1904,88 @@ type ArchiveTaskResult = {
|
|
|
2381
1904
|
*/
|
|
2382
1905
|
declare function archiveTask(input: ArchiveTaskInput): Promise<ArchiveTaskResult>;
|
|
2383
1906
|
|
|
1907
|
+
/** Input contract for {@link renderHandoff}. */
|
|
1908
|
+
type HandoffRendererInput = {
|
|
1909
|
+
paths: BasouPaths;
|
|
1910
|
+
/** ISO timestamp embedded in the generated body header. Caller-provided for testability. */
|
|
1911
|
+
nowIso: string;
|
|
1912
|
+
/** Forwarded to {@link replayEvents} / {@link loadSessionEntries} per session. */
|
|
1913
|
+
onWarning?: (warning: ReplayWarning, sessionId: string) => void;
|
|
1914
|
+
/**
|
|
1915
|
+
* Per-session degradation reasons (missing/invalid session.yaml or
|
|
1916
|
+
* unreadable events.jsonl). The CLI maps `events_jsonl_unreadable` to the
|
|
1917
|
+
* existing suspect-check stderr wording to keep the user-facing surface
|
|
1918
|
+
* consistent with `basou session list`.
|
|
1919
|
+
*/
|
|
1920
|
+
onSessionSkip?: (sessionId: string, reason: SessionSkipReason) => void;
|
|
1921
|
+
/**
|
|
1922
|
+
* Per-task degradation reasons (invalid front matter / unreadable file).
|
|
1923
|
+
* Surfaced so the CLI can warn the operator about a malformed task.md
|
|
1924
|
+
* without aborting the handoff render.
|
|
1925
|
+
*/
|
|
1926
|
+
onTaskSkip?: (taskId: string, reason: TaskSkipReason) => void;
|
|
1927
|
+
/** Maximum related_files entries to display before `... +N more`. Default 20. */
|
|
1928
|
+
relatedFilesLimit?: number;
|
|
1929
|
+
};
|
|
1930
|
+
type HandoffRendererResult = {
|
|
1931
|
+
/** Generated body WITHOUT BASOU:GENERATED markers (markdown-store wraps them). */
|
|
1932
|
+
body: string;
|
|
1933
|
+
sessionCount: number;
|
|
1934
|
+
decisionCount: number;
|
|
1935
|
+
pendingApprovalsCount: number;
|
|
1936
|
+
suspectCount: number;
|
|
1937
|
+
/** Total number of task.md files successfully loaded. */
|
|
1938
|
+
taskCount: number;
|
|
1939
|
+
/** Tasks whose status is `planned` or `in_progress` (= shown in 次に実行すべき作業). */
|
|
1940
|
+
pendingTaskCount: number;
|
|
1941
|
+
};
|
|
1942
|
+
/**
|
|
1943
|
+
* Render the body of `handoff.md` from the current workspace state.
|
|
1944
|
+
*
|
|
1945
|
+
* The renderer is a pure function (no I/O beyond {@link replayEvents} /
|
|
1946
|
+
* {@link loadSessionEntries} / {@link enumerateApprovals}). It assembles the
|
|
1947
|
+
* the spec's `handoff.md` sections in order:
|
|
1948
|
+
*
|
|
1949
|
+
* 1. `現在の状態`: latest live session (status not archived, source not import).
|
|
1950
|
+
* 2. `直近の変更ファイル`: union of `related_files` across sessions, dedup +
|
|
1951
|
+
* sorted asc + truncated to `relatedFilesLimit` (default 20).
|
|
1952
|
+
* 3. `直近の判断`: latest `decision_recorded` event (chronological).
|
|
1953
|
+
* 4. `未決事項`: pending-approval count + suspect-session count.
|
|
1954
|
+
* 5. `次に読むべきファイル`: `.basou/decisions.md` + top-3 related files
|
|
1955
|
+
* (the same `displayedFiles` source is intentionally reused in two
|
|
1956
|
+
* sections — overview vs. resume context).
|
|
1957
|
+
* 6. `次に実行すべき作業`: placeholder until task events land.
|
|
1958
|
+
* 7. `セッション一覧`: all sessions newest first with inline suspect labels.
|
|
1959
|
+
*
|
|
1960
|
+
* Session enumeration goes through {@link loadSessionEntries} so the set of
|
|
1961
|
+
* sessions whose `decision_recorded` events we replay matches the
|
|
1962
|
+
* decisions renderer.
|
|
1963
|
+
*/
|
|
1964
|
+
declare function renderHandoff(input: HandoffRendererInput): Promise<HandoffRendererResult>;
|
|
1965
|
+
|
|
1966
|
+
/**
|
|
1967
|
+
* Parse a unit-suffixed duration string (e.g. `30s`, `5m`, `1h`, `100ms`)
|
|
1968
|
+
* into milliseconds.
|
|
1969
|
+
*
|
|
1970
|
+
* Rejects formats that cannot represent a positive, finite millisecond
|
|
1971
|
+
* value: malformed inputs, zero, leading-zero values, and computations that
|
|
1972
|
+
* overflow to `Infinity`. The returned number is always a positive integer.
|
|
1973
|
+
*
|
|
1974
|
+
* Supported units: `ms` (milliseconds), `s` (seconds), `m` (minutes),
|
|
1975
|
+
* `h` (hours).
|
|
1976
|
+
*
|
|
1977
|
+
* @param input duration string with required unit suffix
|
|
1978
|
+
* @returns duration in milliseconds (positive, finite)
|
|
1979
|
+
* @throws Error with message
|
|
1980
|
+
* `Invalid duration: <input>. Expected format: <positive-integer><unit> where unit is ms/s/m/h`
|
|
1981
|
+
* for format errors, or `Duration overflow: <input>` for non-finite results.
|
|
1982
|
+
*/
|
|
1983
|
+
declare function parseDuration(input: string): number;
|
|
1984
|
+
|
|
2384
1985
|
/**
|
|
2385
1986
|
* Resolve a possibly-truncated session id prefix to a full session id by
|
|
2386
1987
|
* scanning `<paths.sessions>/`. Existing message contract (carried over
|
|
2387
|
-
* from `packages/cli/src/commands/session.ts`
|
|
1988
|
+
* from `packages/cli/src/commands/session.ts`) is
|
|
2388
1989
|
* preserved exactly so callers that grep stderr keep working:
|
|
2389
1990
|
*
|
|
2390
1991
|
* - `"Session id is empty"`
|
|
@@ -2492,413 +2093,813 @@ type SanitizeRelatedFilesResult = {
|
|
|
2492
2093
|
*/
|
|
2493
2094
|
declare function sanitizeRelatedFiles(paths: ReadonlyArray<string>, opts: SanitizePathOptions): SanitizeRelatedFilesResult;
|
|
2494
2095
|
|
|
2495
|
-
/** Input contract for {@link renderHandoff}. */
|
|
2496
|
-
type HandoffRendererInput = {
|
|
2497
|
-
paths: BasouPaths;
|
|
2498
|
-
/** ISO timestamp embedded in the generated body header. Caller-provided for testability. */
|
|
2499
|
-
nowIso: string;
|
|
2500
|
-
/** Forwarded to {@link replayEvents} / {@link loadSessionEntries} per session. */
|
|
2501
|
-
onWarning?: (warning: ReplayWarning, sessionId: string) => void;
|
|
2502
|
-
/**
|
|
2503
|
-
* Per-session degradation reasons (missing/invalid session.yaml or
|
|
2504
|
-
* unreadable events.jsonl). The CLI maps `events_jsonl_unreadable` to the
|
|
2505
|
-
* existing suspect-check stderr wording to keep the user-facing surface
|
|
2506
|
-
* consistent with `basou session list`.
|
|
2507
|
-
*/
|
|
2508
|
-
onSessionSkip?: (sessionId: string, reason: SessionSkipReason) => void;
|
|
2509
|
-
/**
|
|
2510
|
-
* Per-task degradation reasons (invalid front matter / unreadable file).
|
|
2511
|
-
* Surfaced so the CLI can warn the operator about a malformed task.md
|
|
2512
|
-
* without aborting the handoff render.
|
|
2513
|
-
*/
|
|
2514
|
-
onTaskSkip?: (taskId: string, reason: TaskSkipReason) => void;
|
|
2515
|
-
/** Maximum related_files entries to display before `... +N more`. Default 20. */
|
|
2516
|
-
relatedFilesLimit?: number;
|
|
2517
|
-
};
|
|
2518
|
-
type HandoffRendererResult = {
|
|
2519
|
-
/** Generated body WITHOUT BASOU:GENERATED markers (markdown-store wraps them). */
|
|
2520
|
-
body: string;
|
|
2521
|
-
sessionCount: number;
|
|
2522
|
-
decisionCount: number;
|
|
2523
|
-
pendingApprovalsCount: number;
|
|
2524
|
-
suspectCount: number;
|
|
2525
|
-
/** Total number of task.md files successfully loaded. */
|
|
2526
|
-
taskCount: number;
|
|
2527
|
-
/** Tasks whose status is `planned` or `in_progress` (= shown in 次に実行すべき作業). */
|
|
2528
|
-
pendingTaskCount: number;
|
|
2529
|
-
};
|
|
2530
2096
|
/**
|
|
2531
|
-
*
|
|
2097
|
+
* Internal abstraction over child-process execution.
|
|
2532
2098
|
*
|
|
2533
|
-
* The
|
|
2534
|
-
*
|
|
2535
|
-
* the
|
|
2099
|
+
* The v0.1 implementation is intentionally minimal:
|
|
2100
|
+
* - Optional UTF-8 stdout/stderr capture (`capture: "buffer"`, default) or
|
|
2101
|
+
* pass-through to the parent's stdio (`capture: "none"`).
|
|
2102
|
+
* - No stream callbacks for partial chunks.
|
|
2103
|
+
* - No event emission. Callers wire any event flow separately.
|
|
2536
2104
|
*
|
|
2537
|
-
*
|
|
2538
|
-
*
|
|
2539
|
-
*
|
|
2540
|
-
|
|
2541
|
-
|
|
2542
|
-
*
|
|
2543
|
-
* (the same `displayedFiles` source is intentionally reused in two
|
|
2544
|
-
* sections — overview vs. resume context).
|
|
2545
|
-
* 6. `次に実行すべき作業`: placeholder until task events land.
|
|
2546
|
-
* 7. `セッション一覧`: all sessions newest first with inline suspect labels.
|
|
2105
|
+
* The boundary is internal: ProcessRunner is not part of the public
|
|
2106
|
+
* adapter surface. Adapters do not import or instantiate it directly;
|
|
2107
|
+
* CLI / Core orchestration owns construction and invocation.
|
|
2108
|
+
*/
|
|
2109
|
+
/**
|
|
2110
|
+
* Output capture mode.
|
|
2547
2111
|
*
|
|
2548
|
-
*
|
|
2549
|
-
*
|
|
2550
|
-
*
|
|
2112
|
+
* - `"buffer"` (default): pipe stdout/stderr to the runner and accumulate
|
|
2113
|
+
* the full UTF-8 string into {@link RunResult}.
|
|
2114
|
+
* - `"none"`: inherit the parent's stdio. The child writes directly to the
|
|
2115
|
+
* parent terminal in real time and {@link RunResult.stdout} /
|
|
2116
|
+
* {@link RunResult.stderr} are empty strings. `stdin` cannot be combined
|
|
2117
|
+
* with `"none"` because the child has no writable stdin pipe.
|
|
2551
2118
|
*/
|
|
2552
|
-
|
|
2553
|
-
|
|
2554
|
-
|
|
2555
|
-
|
|
2556
|
-
|
|
2557
|
-
|
|
2558
|
-
|
|
2119
|
+
type CaptureMode = "buffer" | "none";
|
|
2120
|
+
type RunOptions = {
|
|
2121
|
+
/**
|
|
2122
|
+
* Working directory for the child process. Required: callers resolve
|
|
2123
|
+
* the workspace root themselves; the runner does not validate cwd
|
|
2124
|
+
* existence and surfaces native spawn errors via classification.
|
|
2125
|
+
*/
|
|
2126
|
+
readonly cwd: string;
|
|
2127
|
+
/**
|
|
2128
|
+
* Environment variables for the child. When omitted, the parent's
|
|
2129
|
+
* `process.env` is inherited verbatim. Callers wanting a sanitized
|
|
2130
|
+
* environment must build it explicitly.
|
|
2131
|
+
*/
|
|
2132
|
+
readonly env?: NodeJS.ProcessEnv;
|
|
2133
|
+
/**
|
|
2134
|
+
* External cancellation. Aborting the signal triggers a two-stage
|
|
2135
|
+
* kill (SIGTERM, then SIGKILL after a short grace period).
|
|
2136
|
+
*/
|
|
2137
|
+
readonly signal?: AbortSignal;
|
|
2138
|
+
/**
|
|
2139
|
+
* Internal timeout in milliseconds. Must be a positive finite number.
|
|
2140
|
+
* Triggers the same two-stage kill as `signal`.
|
|
2141
|
+
*/
|
|
2142
|
+
readonly timeout_ms?: number;
|
|
2143
|
+
/**
|
|
2144
|
+
* Optional input written to the child's stdin. The pipe is closed
|
|
2145
|
+
* after the value is written. Incompatible with `capture: "none"`.
|
|
2146
|
+
*/
|
|
2147
|
+
readonly stdin?: string | Buffer;
|
|
2148
|
+
/**
|
|
2149
|
+
* Output capture mode. Defaults to `"buffer"`. See {@link CaptureMode}.
|
|
2150
|
+
*/
|
|
2151
|
+
readonly capture?: CaptureMode;
|
|
2152
|
+
/**
|
|
2153
|
+
* Invoked synchronously immediately after the child has been spawned,
|
|
2154
|
+
* before the runner waits for completion. Callers use this to retain a
|
|
2155
|
+
* reference for parent-side cleanup (e.g. an `exit` hook that SIGKILLs
|
|
2156
|
+
* the child if the parent is forcibly terminated). The runner takes no
|
|
2157
|
+
* action if the callback throws.
|
|
2158
|
+
*/
|
|
2159
|
+
readonly onSpawn?: (child: ChildProcess) => void;
|
|
2559
2160
|
};
|
|
2560
|
-
type
|
|
2561
|
-
|
|
2562
|
-
|
|
2563
|
-
|
|
2161
|
+
type RunResult = {
|
|
2162
|
+
readonly command: string;
|
|
2163
|
+
readonly args: readonly string[];
|
|
2164
|
+
readonly cwd: string;
|
|
2165
|
+
/** `null` when the process was killed by a signal. */
|
|
2166
|
+
readonly exit_code: number | null;
|
|
2167
|
+
readonly signal: NodeJS.Signals | null;
|
|
2168
|
+
readonly stdout: string;
|
|
2169
|
+
readonly stderr: string;
|
|
2170
|
+
/** ISO 8601 timestamp captured before spawn. */
|
|
2171
|
+
readonly started_at: string;
|
|
2172
|
+
/** ISO 8601 timestamp captured on the `close` event. */
|
|
2173
|
+
readonly ended_at: string;
|
|
2174
|
+
readonly duration_ms: number;
|
|
2175
|
+
readonly pid: number | null;
|
|
2176
|
+
};
|
|
2177
|
+
type ProcessRunner = {
|
|
2178
|
+
run(command: string, args: readonly string[], options: RunOptions): Promise<RunResult>;
|
|
2564
2179
|
};
|
|
2180
|
+
|
|
2565
2181
|
/**
|
|
2566
|
-
*
|
|
2567
|
-
* every healthy session in the workspace.
|
|
2568
|
-
*
|
|
2569
|
-
* Session enumeration goes through {@link loadSessionEntries} (the same path
|
|
2570
|
-
* the handoff renderer uses) so that `session.yaml`-broken sessions are
|
|
2571
|
-
* skipped in BOTH outputs and the handoff's `decisionCount` summary stays
|
|
2572
|
-
* consistent with the number of sections rendered here.
|
|
2182
|
+
* Spawn-based ProcessRunner implementation.
|
|
2573
2183
|
*
|
|
2574
|
-
*
|
|
2575
|
-
*
|
|
2576
|
-
*
|
|
2184
|
+
* Behavior:
|
|
2185
|
+
* - `shell: false` and `detached: false`. The process group is not
|
|
2186
|
+
* detached, but the OS does not guarantee the child is reaped when
|
|
2187
|
+
* the parent terminates abruptly; callers handle SIGINT/SIGTERM/exit
|
|
2188
|
+
* hooks themselves.
|
|
2189
|
+
* - `capture: "buffer"` (default): `stdio: ['pipe', 'pipe', 'pipe']`,
|
|
2190
|
+
* stdout / stderr are decoded as UTF-8 and accumulated as full
|
|
2191
|
+
* strings (no streaming callbacks).
|
|
2192
|
+
* - `capture: "none"`: `stdio: ['inherit', 'inherit', 'inherit']`, the
|
|
2193
|
+
* child writes directly to the parent terminal in real time and
|
|
2194
|
+
* `RunResult.stdout` / `stderr` are empty strings. `stdin` is
|
|
2195
|
+
* incompatible with this mode (the child has no writable stdin pipe)
|
|
2196
|
+
* and the combination is rejected before spawn.
|
|
2197
|
+
* - `timeout_ms` and `AbortSignal` both trigger a two-stage kill:
|
|
2198
|
+
* `SIGTERM`, then `SIGKILL` after `DEFAULT_KILL_GRACE_MS` (5_000 ms).
|
|
2199
|
+
* - A non-zero `exit_code` does not throw; it is returned via
|
|
2200
|
+
* `RunResult`. Spawn-time errors throw with a pathless message and
|
|
2201
|
+
* the original error attached as `cause`.
|
|
2577
2202
|
*
|
|
2578
|
-
*
|
|
2579
|
-
*
|
|
2580
|
-
*
|
|
2581
|
-
*
|
|
2582
|
-
* to a known event id or an existing file on disk are surfaced inline as
|
|
2583
|
-
* `(missing)` so cross-workspace round-trips never reject parse-time.
|
|
2203
|
+
* Error message contract: messages never include `cwd` or absolute
|
|
2204
|
+
* command paths. The original errno (and any nested wrapping) is
|
|
2205
|
+
* preserved on `Error.cause`, allowing callers to classify with
|
|
2206
|
+
* `findErrorCode` when needed.
|
|
2584
2207
|
*/
|
|
2585
|
-
declare
|
|
2208
|
+
declare class ChildProcessRunner implements ProcessRunner {
|
|
2209
|
+
run(command: string, args: readonly string[], options: RunOptions): Promise<RunResult>;
|
|
2210
|
+
}
|
|
2211
|
+
|
|
2212
|
+
declare const SessionInnerImportSchema: z.ZodObject<{
|
|
2213
|
+
id: z.ZodOptional<z.ZodString & z.ZodType<`ses_${string}`, string, z.core.$ZodTypeInternals<`ses_${string}`, string>>>;
|
|
2214
|
+
label: z.ZodOptional<z.ZodString>;
|
|
2215
|
+
task_id: z.ZodOptional<z.ZodNullable<z.ZodString & z.ZodType<`task_${string}`, string, z.core.$ZodTypeInternals<`task_${string}`, string>>>>;
|
|
2216
|
+
workspace_id: z.ZodString & z.ZodType<`ws_${string}`, string, z.core.$ZodTypeInternals<`ws_${string}`, string>>;
|
|
2217
|
+
source: z.ZodObject<{
|
|
2218
|
+
kind: z.ZodEnum<{
|
|
2219
|
+
"claude-code-adapter": "claude-code-adapter";
|
|
2220
|
+
human: "human";
|
|
2221
|
+
import: "import";
|
|
2222
|
+
terminal: "terminal";
|
|
2223
|
+
}>;
|
|
2224
|
+
version: z.ZodLiteral<"0.1.0">;
|
|
2225
|
+
}, z.core.$strip>;
|
|
2226
|
+
started_at: z.ZodString;
|
|
2227
|
+
ended_at: z.ZodOptional<z.ZodString>;
|
|
2228
|
+
status: z.ZodEnum<{
|
|
2229
|
+
initialized: "initialized";
|
|
2230
|
+
running: "running";
|
|
2231
|
+
waiting_approval: "waiting_approval";
|
|
2232
|
+
completed: "completed";
|
|
2233
|
+
failed: "failed";
|
|
2234
|
+
interrupted: "interrupted";
|
|
2235
|
+
imported: "imported";
|
|
2236
|
+
archived: "archived";
|
|
2237
|
+
}>;
|
|
2238
|
+
working_directory: z.ZodString;
|
|
2239
|
+
invocation: z.ZodObject<{
|
|
2240
|
+
command: z.ZodString;
|
|
2241
|
+
args: z.ZodArray<z.ZodString>;
|
|
2242
|
+
exit_code: z.ZodNullable<z.ZodNumber>;
|
|
2243
|
+
}, z.core.$strip>;
|
|
2244
|
+
related_files: z.ZodDefault<z.ZodArray<z.ZodString>>;
|
|
2245
|
+
events_log: z.ZodOptional<z.ZodString>;
|
|
2246
|
+
summary: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
2247
|
+
}, z.core.$strict>;
|
|
2248
|
+
/**
|
|
2249
|
+
* Schema for the round-trip JSON payload accepted by `basou session import
|
|
2250
|
+
* --format json`. The top level is `.strict()`; unknown keys at the outer
|
|
2251
|
+
* envelope are rejected.
|
|
2252
|
+
*/
|
|
2253
|
+
declare const SessionImportPayloadSchema: z.ZodObject<{
|
|
2254
|
+
schema_version: z.ZodString;
|
|
2255
|
+
session: z.ZodObject<{
|
|
2256
|
+
id: z.ZodOptional<z.ZodString & z.ZodType<`ses_${string}`, string, z.core.$ZodTypeInternals<`ses_${string}`, string>>>;
|
|
2257
|
+
label: z.ZodOptional<z.ZodString>;
|
|
2258
|
+
task_id: z.ZodOptional<z.ZodNullable<z.ZodString & z.ZodType<`task_${string}`, string, z.core.$ZodTypeInternals<`task_${string}`, string>>>>;
|
|
2259
|
+
workspace_id: z.ZodString & z.ZodType<`ws_${string}`, string, z.core.$ZodTypeInternals<`ws_${string}`, string>>;
|
|
2260
|
+
source: z.ZodObject<{
|
|
2261
|
+
kind: z.ZodEnum<{
|
|
2262
|
+
"claude-code-adapter": "claude-code-adapter";
|
|
2263
|
+
human: "human";
|
|
2264
|
+
import: "import";
|
|
2265
|
+
terminal: "terminal";
|
|
2266
|
+
}>;
|
|
2267
|
+
version: z.ZodLiteral<"0.1.0">;
|
|
2268
|
+
}, z.core.$strip>;
|
|
2269
|
+
started_at: z.ZodString;
|
|
2270
|
+
ended_at: z.ZodOptional<z.ZodString>;
|
|
2271
|
+
status: z.ZodEnum<{
|
|
2272
|
+
initialized: "initialized";
|
|
2273
|
+
running: "running";
|
|
2274
|
+
waiting_approval: "waiting_approval";
|
|
2275
|
+
completed: "completed";
|
|
2276
|
+
failed: "failed";
|
|
2277
|
+
interrupted: "interrupted";
|
|
2278
|
+
imported: "imported";
|
|
2279
|
+
archived: "archived";
|
|
2280
|
+
}>;
|
|
2281
|
+
working_directory: z.ZodString;
|
|
2282
|
+
invocation: z.ZodObject<{
|
|
2283
|
+
command: z.ZodString;
|
|
2284
|
+
args: z.ZodArray<z.ZodString>;
|
|
2285
|
+
exit_code: z.ZodNullable<z.ZodNumber>;
|
|
2286
|
+
}, z.core.$strip>;
|
|
2287
|
+
related_files: z.ZodDefault<z.ZodArray<z.ZodString>>;
|
|
2288
|
+
events_log: z.ZodOptional<z.ZodString>;
|
|
2289
|
+
summary: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
2290
|
+
}, z.core.$strict>;
|
|
2291
|
+
events: z.ZodArray<z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
2292
|
+
schema_version: z.ZodLiteral<"0.1.0">;
|
|
2293
|
+
id: z.ZodString & z.ZodType<`evt_${string}`, string, z.core.$ZodTypeInternals<`evt_${string}`, string>>;
|
|
2294
|
+
session_id: z.ZodString & z.ZodType<`ses_${string}`, string, z.core.$ZodTypeInternals<`ses_${string}`, string>>;
|
|
2295
|
+
occurred_at: z.ZodString;
|
|
2296
|
+
source: z.ZodString;
|
|
2297
|
+
type: z.ZodLiteral<"session_started">;
|
|
2298
|
+
}, z.core.$strip>, z.ZodObject<{
|
|
2299
|
+
schema_version: z.ZodLiteral<"0.1.0">;
|
|
2300
|
+
id: z.ZodString & z.ZodType<`evt_${string}`, string, z.core.$ZodTypeInternals<`evt_${string}`, string>>;
|
|
2301
|
+
session_id: z.ZodString & z.ZodType<`ses_${string}`, string, z.core.$ZodTypeInternals<`ses_${string}`, string>>;
|
|
2302
|
+
occurred_at: z.ZodString;
|
|
2303
|
+
source: z.ZodString;
|
|
2304
|
+
type: z.ZodLiteral<"session_ended">;
|
|
2305
|
+
exit_code: z.ZodOptional<z.ZodNumber>;
|
|
2306
|
+
}, z.core.$strip>, z.ZodObject<{
|
|
2307
|
+
schema_version: z.ZodLiteral<"0.1.0">;
|
|
2308
|
+
id: z.ZodString & z.ZodType<`evt_${string}`, string, z.core.$ZodTypeInternals<`evt_${string}`, string>>;
|
|
2309
|
+
session_id: z.ZodString & z.ZodType<`ses_${string}`, string, z.core.$ZodTypeInternals<`ses_${string}`, string>>;
|
|
2310
|
+
occurred_at: z.ZodString;
|
|
2311
|
+
source: z.ZodString;
|
|
2312
|
+
type: z.ZodLiteral<"session_status_changed">;
|
|
2313
|
+
from: z.ZodString;
|
|
2314
|
+
to: z.ZodString;
|
|
2315
|
+
}, z.core.$strip>, z.ZodObject<{
|
|
2316
|
+
schema_version: z.ZodLiteral<"0.1.0">;
|
|
2317
|
+
id: z.ZodString & z.ZodType<`evt_${string}`, string, z.core.$ZodTypeInternals<`evt_${string}`, string>>;
|
|
2318
|
+
session_id: z.ZodString & z.ZodType<`ses_${string}`, string, z.core.$ZodTypeInternals<`ses_${string}`, string>>;
|
|
2319
|
+
occurred_at: z.ZodString;
|
|
2320
|
+
source: z.ZodString;
|
|
2321
|
+
type: z.ZodLiteral<"approval_requested">;
|
|
2322
|
+
approval_id: z.ZodString & z.ZodType<`appr_${string}`, string, z.core.$ZodTypeInternals<`appr_${string}`, string>>;
|
|
2323
|
+
expires_at: z.ZodDefault<z.ZodNullable<z.ZodString>>;
|
|
2324
|
+
risk_level: z.ZodEnum<{
|
|
2325
|
+
low: "low";
|
|
2326
|
+
medium: "medium";
|
|
2327
|
+
high: "high";
|
|
2328
|
+
critical: "critical";
|
|
2329
|
+
}>;
|
|
2330
|
+
action: z.ZodObject<{
|
|
2331
|
+
kind: z.ZodString;
|
|
2332
|
+
}, z.core.$loose>;
|
|
2333
|
+
reason: z.ZodString;
|
|
2334
|
+
status: z.ZodLiteral<"pending">;
|
|
2335
|
+
}, z.core.$strip>, z.ZodObject<{
|
|
2336
|
+
schema_version: z.ZodLiteral<"0.1.0">;
|
|
2337
|
+
id: z.ZodString & z.ZodType<`evt_${string}`, string, z.core.$ZodTypeInternals<`evt_${string}`, string>>;
|
|
2338
|
+
session_id: z.ZodString & z.ZodType<`ses_${string}`, string, z.core.$ZodTypeInternals<`ses_${string}`, string>>;
|
|
2339
|
+
occurred_at: z.ZodString;
|
|
2340
|
+
source: z.ZodString;
|
|
2341
|
+
type: z.ZodLiteral<"approval_approved">;
|
|
2342
|
+
approval_id: z.ZodString & z.ZodType<`appr_${string}`, string, z.core.$ZodTypeInternals<`appr_${string}`, string>>;
|
|
2343
|
+
resolver: z.ZodOptional<z.ZodString>;
|
|
2344
|
+
note: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
2345
|
+
}, z.core.$strip>, z.ZodObject<{
|
|
2346
|
+
schema_version: z.ZodLiteral<"0.1.0">;
|
|
2347
|
+
id: z.ZodString & z.ZodType<`evt_${string}`, string, z.core.$ZodTypeInternals<`evt_${string}`, string>>;
|
|
2348
|
+
session_id: z.ZodString & z.ZodType<`ses_${string}`, string, z.core.$ZodTypeInternals<`ses_${string}`, string>>;
|
|
2349
|
+
occurred_at: z.ZodString;
|
|
2350
|
+
source: z.ZodString;
|
|
2351
|
+
type: z.ZodLiteral<"approval_rejected">;
|
|
2352
|
+
approval_id: z.ZodString & z.ZodType<`appr_${string}`, string, z.core.$ZodTypeInternals<`appr_${string}`, string>>;
|
|
2353
|
+
resolver: z.ZodOptional<z.ZodString>;
|
|
2354
|
+
reason: z.ZodString;
|
|
2355
|
+
}, z.core.$strip>, z.ZodObject<{
|
|
2356
|
+
schema_version: z.ZodLiteral<"0.1.0">;
|
|
2357
|
+
id: z.ZodString & z.ZodType<`evt_${string}`, string, z.core.$ZodTypeInternals<`evt_${string}`, string>>;
|
|
2358
|
+
session_id: z.ZodString & z.ZodType<`ses_${string}`, string, z.core.$ZodTypeInternals<`ses_${string}`, string>>;
|
|
2359
|
+
occurred_at: z.ZodString;
|
|
2360
|
+
source: z.ZodString;
|
|
2361
|
+
type: z.ZodLiteral<"approval_expired">;
|
|
2362
|
+
approval_id: z.ZodString & z.ZodType<`appr_${string}`, string, z.core.$ZodTypeInternals<`appr_${string}`, string>>;
|
|
2363
|
+
}, z.core.$strip>, z.ZodObject<{
|
|
2364
|
+
schema_version: z.ZodLiteral<"0.1.0">;
|
|
2365
|
+
id: z.ZodString & z.ZodType<`evt_${string}`, string, z.core.$ZodTypeInternals<`evt_${string}`, string>>;
|
|
2366
|
+
session_id: z.ZodString & z.ZodType<`ses_${string}`, string, z.core.$ZodTypeInternals<`ses_${string}`, string>>;
|
|
2367
|
+
occurred_at: z.ZodString;
|
|
2368
|
+
source: z.ZodString;
|
|
2369
|
+
type: z.ZodLiteral<"command_executed">;
|
|
2370
|
+
command: z.ZodString;
|
|
2371
|
+
args: z.ZodArray<z.ZodString>;
|
|
2372
|
+
cwd: z.ZodString;
|
|
2373
|
+
exit_code: z.ZodNullable<z.ZodNumber>;
|
|
2374
|
+
signal: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
2375
|
+
received_signal: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
2376
|
+
duration_ms: z.ZodNumber;
|
|
2377
|
+
}, z.core.$strip>, z.ZodObject<{
|
|
2378
|
+
schema_version: z.ZodLiteral<"0.1.0">;
|
|
2379
|
+
id: z.ZodString & z.ZodType<`evt_${string}`, string, z.core.$ZodTypeInternals<`evt_${string}`, string>>;
|
|
2380
|
+
session_id: z.ZodString & z.ZodType<`ses_${string}`, string, z.core.$ZodTypeInternals<`ses_${string}`, string>>;
|
|
2381
|
+
occurred_at: z.ZodString;
|
|
2382
|
+
source: z.ZodString;
|
|
2383
|
+
type: z.ZodLiteral<"git_snapshot">;
|
|
2384
|
+
head: z.ZodString;
|
|
2385
|
+
branch: z.ZodString;
|
|
2386
|
+
dirty: z.ZodBoolean;
|
|
2387
|
+
staged: z.ZodArray<z.ZodString>;
|
|
2388
|
+
unstaged: z.ZodArray<z.ZodString>;
|
|
2389
|
+
untracked: z.ZodArray<z.ZodString>;
|
|
2390
|
+
ahead: z.ZodOptional<z.ZodNumber>;
|
|
2391
|
+
behind: z.ZodOptional<z.ZodNumber>;
|
|
2392
|
+
}, z.core.$strip>, z.ZodObject<{
|
|
2393
|
+
schema_version: z.ZodLiteral<"0.1.0">;
|
|
2394
|
+
id: z.ZodString & z.ZodType<`evt_${string}`, string, z.core.$ZodTypeInternals<`evt_${string}`, string>>;
|
|
2395
|
+
session_id: z.ZodString & z.ZodType<`ses_${string}`, string, z.core.$ZodTypeInternals<`ses_${string}`, string>>;
|
|
2396
|
+
occurred_at: z.ZodString;
|
|
2397
|
+
source: z.ZodString;
|
|
2398
|
+
type: z.ZodLiteral<"file_changed">;
|
|
2399
|
+
path: z.ZodString;
|
|
2400
|
+
change_type: z.ZodEnum<{
|
|
2401
|
+
added: "added";
|
|
2402
|
+
modified: "modified";
|
|
2403
|
+
deleted: "deleted";
|
|
2404
|
+
renamed: "renamed";
|
|
2405
|
+
}>;
|
|
2406
|
+
old_path: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
2407
|
+
}, z.core.$strip>, z.ZodObject<{
|
|
2408
|
+
schema_version: z.ZodLiteral<"0.1.0">;
|
|
2409
|
+
id: z.ZodString & z.ZodType<`evt_${string}`, string, z.core.$ZodTypeInternals<`evt_${string}`, string>>;
|
|
2410
|
+
session_id: z.ZodString & z.ZodType<`ses_${string}`, string, z.core.$ZodTypeInternals<`ses_${string}`, string>>;
|
|
2411
|
+
occurred_at: z.ZodString;
|
|
2412
|
+
source: z.ZodString;
|
|
2413
|
+
type: z.ZodLiteral<"decision_recorded">;
|
|
2414
|
+
decision_id: z.ZodString & z.ZodType<`decision_${string}`, string, z.core.$ZodTypeInternals<`decision_${string}`, string>>;
|
|
2415
|
+
title: z.ZodString;
|
|
2416
|
+
rationale: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
2417
|
+
alternatives: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
2418
|
+
rejected_reason: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
2419
|
+
linked_events: z.ZodOptional<z.ZodArray<z.ZodString & z.ZodType<`evt_${string}`, string, z.core.$ZodTypeInternals<`evt_${string}`, string>>>>;
|
|
2420
|
+
linked_files: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
2421
|
+
}, z.core.$strip>, z.ZodObject<{
|
|
2422
|
+
schema_version: z.ZodLiteral<"0.1.0">;
|
|
2423
|
+
id: z.ZodString & z.ZodType<`evt_${string}`, string, z.core.$ZodTypeInternals<`evt_${string}`, string>>;
|
|
2424
|
+
session_id: z.ZodString & z.ZodType<`ses_${string}`, string, z.core.$ZodTypeInternals<`ses_${string}`, string>>;
|
|
2425
|
+
occurred_at: z.ZodString;
|
|
2426
|
+
source: z.ZodString;
|
|
2427
|
+
type: z.ZodLiteral<"task_created">;
|
|
2428
|
+
task_id: z.ZodString & z.ZodType<`task_${string}`, string, z.core.$ZodTypeInternals<`task_${string}`, string>>;
|
|
2429
|
+
title: z.ZodString;
|
|
2430
|
+
}, z.core.$strip>, z.ZodObject<{
|
|
2431
|
+
schema_version: z.ZodLiteral<"0.1.0">;
|
|
2432
|
+
id: z.ZodString & z.ZodType<`evt_${string}`, string, z.core.$ZodTypeInternals<`evt_${string}`, string>>;
|
|
2433
|
+
session_id: z.ZodString & z.ZodType<`ses_${string}`, string, z.core.$ZodTypeInternals<`ses_${string}`, string>>;
|
|
2434
|
+
occurred_at: z.ZodString;
|
|
2435
|
+
source: z.ZodString;
|
|
2436
|
+
type: z.ZodLiteral<"task_status_changed">;
|
|
2437
|
+
task_id: z.ZodString & z.ZodType<`task_${string}`, string, z.core.$ZodTypeInternals<`task_${string}`, string>>;
|
|
2438
|
+
from: z.ZodString;
|
|
2439
|
+
to: z.ZodString;
|
|
2440
|
+
}, z.core.$strip>, z.ZodObject<{
|
|
2441
|
+
schema_version: z.ZodLiteral<"0.1.0">;
|
|
2442
|
+
id: z.ZodString & z.ZodType<`evt_${string}`, string, z.core.$ZodTypeInternals<`evt_${string}`, string>>;
|
|
2443
|
+
session_id: z.ZodString & z.ZodType<`ses_${string}`, string, z.core.$ZodTypeInternals<`ses_${string}`, string>>;
|
|
2444
|
+
occurred_at: z.ZodString;
|
|
2445
|
+
source: z.ZodString;
|
|
2446
|
+
type: z.ZodLiteral<"task_reconciled">;
|
|
2447
|
+
task_id: z.ZodString & z.ZodType<`task_${string}`, string, z.core.$ZodTypeInternals<`task_${string}`, string>>;
|
|
2448
|
+
removed_created_in_session: z.ZodDefault<z.ZodNullable<z.ZodString & z.ZodType<`ses_${string}`, string, z.core.$ZodTypeInternals<`ses_${string}`, string>>>>;
|
|
2449
|
+
created_in_session_replacement: z.ZodDefault<z.ZodNullable<z.ZodString & z.ZodType<`ses_${string}`, string, z.core.$ZodTypeInternals<`ses_${string}`, string>>>>;
|
|
2450
|
+
removed_linked_sessions: z.ZodDefault<z.ZodArray<z.ZodString & z.ZodType<`ses_${string}`, string, z.core.$ZodTypeInternals<`ses_${string}`, string>>>>;
|
|
2451
|
+
}, z.core.$strict>, z.ZodObject<{
|
|
2452
|
+
schema_version: z.ZodLiteral<"0.1.0">;
|
|
2453
|
+
id: z.ZodString & z.ZodType<`evt_${string}`, string, z.core.$ZodTypeInternals<`evt_${string}`, string>>;
|
|
2454
|
+
session_id: z.ZodString & z.ZodType<`ses_${string}`, string, z.core.$ZodTypeInternals<`ses_${string}`, string>>;
|
|
2455
|
+
occurred_at: z.ZodString;
|
|
2456
|
+
source: z.ZodString;
|
|
2457
|
+
type: z.ZodLiteral<"task_linkage_refreshed">;
|
|
2458
|
+
task_id: z.ZodString & z.ZodType<`task_${string}`, string, z.core.$ZodTypeInternals<`task_${string}`, string>>;
|
|
2459
|
+
added_linked_sessions: z.ZodDefault<z.ZodArray<z.ZodString & z.ZodType<`ses_${string}`, string, z.core.$ZodTypeInternals<`ses_${string}`, string>>>>;
|
|
2460
|
+
removed_linked_sessions: z.ZodDefault<z.ZodArray<z.ZodString & z.ZodType<`ses_${string}`, string, z.core.$ZodTypeInternals<`ses_${string}`, string>>>>;
|
|
2461
|
+
final_count: z.ZodOptional<z.ZodNumber>;
|
|
2462
|
+
}, z.core.$strict>, z.ZodObject<{
|
|
2463
|
+
schema_version: z.ZodLiteral<"0.1.0">;
|
|
2464
|
+
id: z.ZodString & z.ZodType<`evt_${string}`, string, z.core.$ZodTypeInternals<`evt_${string}`, string>>;
|
|
2465
|
+
session_id: z.ZodString & z.ZodType<`ses_${string}`, string, z.core.$ZodTypeInternals<`ses_${string}`, string>>;
|
|
2466
|
+
occurred_at: z.ZodString;
|
|
2467
|
+
source: z.ZodString;
|
|
2468
|
+
type: z.ZodLiteral<"task_deleted">;
|
|
2469
|
+
task_id: z.ZodString & z.ZodType<`task_${string}`, string, z.core.$ZodTypeInternals<`task_${string}`, string>>;
|
|
2470
|
+
title: z.ZodString;
|
|
2471
|
+
}, z.core.$strict>, z.ZodObject<{
|
|
2472
|
+
schema_version: z.ZodLiteral<"0.1.0">;
|
|
2473
|
+
id: z.ZodString & z.ZodType<`evt_${string}`, string, z.core.$ZodTypeInternals<`evt_${string}`, string>>;
|
|
2474
|
+
session_id: z.ZodString & z.ZodType<`ses_${string}`, string, z.core.$ZodTypeInternals<`ses_${string}`, string>>;
|
|
2475
|
+
occurred_at: z.ZodString;
|
|
2476
|
+
source: z.ZodString;
|
|
2477
|
+
type: z.ZodLiteral<"task_archived">;
|
|
2478
|
+
task_id: z.ZodString & z.ZodType<`task_${string}`, string, z.core.$ZodTypeInternals<`task_${string}`, string>>;
|
|
2479
|
+
title: z.ZodString;
|
|
2480
|
+
}, z.core.$strict>, z.ZodObject<{
|
|
2481
|
+
schema_version: z.ZodLiteral<"0.1.0">;
|
|
2482
|
+
id: z.ZodString & z.ZodType<`evt_${string}`, string, z.core.$ZodTypeInternals<`evt_${string}`, string>>;
|
|
2483
|
+
session_id: z.ZodString & z.ZodType<`ses_${string}`, string, z.core.$ZodTypeInternals<`ses_${string}`, string>>;
|
|
2484
|
+
occurred_at: z.ZodString;
|
|
2485
|
+
source: z.ZodString;
|
|
2486
|
+
type: z.ZodLiteral<"note_added">;
|
|
2487
|
+
body: z.ZodString;
|
|
2488
|
+
}, z.core.$strip>, z.ZodObject<{
|
|
2489
|
+
schema_version: z.ZodLiteral<"0.1.0">;
|
|
2490
|
+
id: z.ZodString & z.ZodType<`evt_${string}`, string, z.core.$ZodTypeInternals<`evt_${string}`, string>>;
|
|
2491
|
+
session_id: z.ZodString & z.ZodType<`ses_${string}`, string, z.core.$ZodTypeInternals<`ses_${string}`, string>>;
|
|
2492
|
+
occurred_at: z.ZodString;
|
|
2493
|
+
source: z.ZodString;
|
|
2494
|
+
type: z.ZodLiteral<"adapter_output">;
|
|
2495
|
+
stream: z.ZodEnum<{
|
|
2496
|
+
stdout: "stdout";
|
|
2497
|
+
stderr: "stderr";
|
|
2498
|
+
}>;
|
|
2499
|
+
summary: z.ZodString;
|
|
2500
|
+
raw_ref: z.ZodString;
|
|
2501
|
+
redacted: z.ZodOptional<z.ZodBoolean>;
|
|
2502
|
+
}, z.core.$strict>], "type">>;
|
|
2503
|
+
}, z.core.$strict>;
|
|
2504
|
+
/** Inferred runtime type for {@link SessionImportPayloadSchema}. */
|
|
2505
|
+
type SessionImportPayload = z.infer<typeof SessionImportPayloadSchema>;
|
|
2506
|
+
/** Inferred runtime type for {@link SessionInnerImportSchema}. */
|
|
2507
|
+
type SessionInnerImportInput = z.infer<typeof SessionInnerImportSchema>;
|
|
2508
|
+
|
|
2509
|
+
/**
|
|
2510
|
+
* Schema version literal pinned to "0.1.0" for Basou v0.1.
|
|
2511
|
+
* Reused across every entity schema so inferred types narrow to the literal.
|
|
2512
|
+
*/
|
|
2513
|
+
declare const SchemaVersionSchema: z.ZodLiteral<"0.1.0">;
|
|
2514
|
+
/**
|
|
2515
|
+
* ISO 8601 timestamp with explicit timezone offset (e.g. `+09:00`).
|
|
2516
|
+
*
|
|
2517
|
+
* The spec samples include offsets, so the default zod `.datetime()` (which
|
|
2518
|
+
* rejects offsets) is insufficient; `{ offset: true }` is required.
|
|
2519
|
+
*/
|
|
2520
|
+
declare const IsoTimestampSchema: z.ZodString;
|
|
2521
|
+
/** Workspace ID schema: validates `ws_<26-char ULID>`. */
|
|
2522
|
+
declare const WorkspaceIdSchema: z.ZodString & z.ZodType<`ws_${string}`, string, z.core.$ZodTypeInternals<`ws_${string}`, string>>;
|
|
2523
|
+
/** Task ID schema: validates `task_<26-char ULID>`. */
|
|
2524
|
+
declare const TaskIdSchema: z.ZodString & z.ZodType<`task_${string}`, string, z.core.$ZodTypeInternals<`task_${string}`, string>>;
|
|
2525
|
+
/** Session ID schema: validates `ses_<26-char ULID>`. */
|
|
2526
|
+
declare const SessionIdSchema: z.ZodString & z.ZodType<`ses_${string}`, string, z.core.$ZodTypeInternals<`ses_${string}`, string>>;
|
|
2527
|
+
/** Event ID schema: validates `evt_<26-char ULID>`. */
|
|
2528
|
+
declare const EventIdSchema: z.ZodString & z.ZodType<`evt_${string}`, string, z.core.$ZodTypeInternals<`evt_${string}`, string>>;
|
|
2529
|
+
/** Approval ID schema: validates `appr_<26-char ULID>`. */
|
|
2530
|
+
declare const ApprovalIdSchema: z.ZodString & z.ZodType<`appr_${string}`, string, z.core.$ZodTypeInternals<`appr_${string}`, string>>;
|
|
2531
|
+
/** Decision ID schema: validates `decision_<26-char ULID>`. */
|
|
2532
|
+
declare const DecisionIdSchema: z.ZodString & z.ZodType<`decision_${string}`, string, z.core.$ZodTypeInternals<`decision_${string}`, string>>;
|
|
2533
|
+
/**
|
|
2534
|
+
* Risk level vocabulary fixed by the spec. Adapters MUST emit one of these
|
|
2535
|
+
* four values; arbitrary strings are rejected at schema parse time.
|
|
2536
|
+
*/
|
|
2537
|
+
declare const RiskLevelSchema: z.ZodEnum<{
|
|
2538
|
+
low: "low";
|
|
2539
|
+
medium: "medium";
|
|
2540
|
+
high: "high";
|
|
2541
|
+
critical: "critical";
|
|
2542
|
+
}>;
|
|
2543
|
+
/** Inferred runtime type for {@link RiskLevelSchema}. */
|
|
2544
|
+
type RiskLevel = z.infer<typeof RiskLevelSchema>;
|
|
2545
|
+
/**
|
|
2546
|
+
* Source attribution for events (e.g. "claude-code-adapter",
|
|
2547
|
+
* "git-capability", "terminal-recording", "local-cli", "human"). Free-form
|
|
2548
|
+
* non-empty string in v0.1; a stricter enum may be introduced post-v0.1.
|
|
2549
|
+
*/
|
|
2550
|
+
declare const EventSourceSchema: z.ZodString;
|
|
2551
|
+
|
|
2552
|
+
/**
|
|
2553
|
+
* Schema for `.basou/status.json` — a forward-incompat cache of the current
|
|
2554
|
+
* workspace state.
|
|
2555
|
+
*
|
|
2556
|
+
* Each level uses `.strict()` so unknown keys are rejected rather than
|
|
2557
|
+
* silently stripped. A v0.1 reader that encounters a future-shape
|
|
2558
|
+
* `status.json` therefore fails parsing instead of returning a partially
|
|
2559
|
+
* empty snapshot; callers regenerate by calling `buildStatusSnapshot` +
|
|
2560
|
+
* `writeStatus` rather than trying to migrate.
|
|
2561
|
+
*/
|
|
2562
|
+
declare const StatusSchema: z.ZodObject<{
|
|
2563
|
+
schema_version: z.ZodLiteral<"0.1.0">;
|
|
2564
|
+
generated_at: z.ZodString;
|
|
2565
|
+
workspace: z.ZodObject<{
|
|
2566
|
+
id: z.ZodString & z.ZodType<`ws_${string}`, string, z.core.$ZodTypeInternals<`ws_${string}`, string>>;
|
|
2567
|
+
name: z.ZodString;
|
|
2568
|
+
basou_version: z.ZodLiteral<"0.1.0">;
|
|
2569
|
+
}, z.core.$strict>;
|
|
2570
|
+
directories_present: z.ZodObject<{
|
|
2571
|
+
sessions: z.ZodBoolean;
|
|
2572
|
+
tasks: z.ZodBoolean;
|
|
2573
|
+
approvals_pending: z.ZodBoolean;
|
|
2574
|
+
approvals_resolved: z.ZodBoolean;
|
|
2575
|
+
logs: z.ZodBoolean;
|
|
2576
|
+
raw: z.ZodBoolean;
|
|
2577
|
+
tmp: z.ZodBoolean;
|
|
2578
|
+
}, z.core.$strict>;
|
|
2579
|
+
}, z.core.$strict>;
|
|
2580
|
+
/** Inferred runtime type for {@link StatusSchema}. */
|
|
2581
|
+
type StatusSnapshot = z.infer<typeof StatusSchema>;
|
|
2586
2582
|
|
|
2583
|
+
type AppendBasouGitignoreResult = {
|
|
2584
|
+
/** True if the block was appended (or the file was newly created). */
|
|
2585
|
+
readonly appended: boolean;
|
|
2586
|
+
};
|
|
2587
2587
|
/**
|
|
2588
|
-
*
|
|
2588
|
+
* Append Basou's default `.gitignore` block to `repositoryRoot/.gitignore`.
|
|
2589
2589
|
*
|
|
2590
|
-
* The v0.1
|
|
2591
|
-
*
|
|
2592
|
-
*
|
|
2593
|
-
* - No stream callbacks for partial chunks.
|
|
2594
|
-
* - No event emission. Callers wire any event flow separately.
|
|
2590
|
+
* The block contents are derived from the Basou v0.1 specification (the
|
|
2591
|
+
* standard ignore + commit recommendations). Callers must pass an absolute
|
|
2592
|
+
* path to a Git repository root.
|
|
2595
2593
|
*
|
|
2596
|
-
*
|
|
2597
|
-
*
|
|
2598
|
-
*
|
|
2594
|
+
* Behavior:
|
|
2595
|
+
* - If `.gitignore` does not exist, it is created with the Basou block.
|
|
2596
|
+
* - If a line starting with `# Basou - default ignore` is already present,
|
|
2597
|
+
* the file is left untouched and `appended: false` is returned
|
|
2598
|
+
* (idempotent).
|
|
2599
|
+
* - If `.gitignore` is a symlink, the link is followed and the target file
|
|
2600
|
+
* is updated. Symlinks are not rejected.
|
|
2601
|
+
*
|
|
2602
|
+
* On I/O failure throws Error with a pathless message
|
|
2603
|
+
* (`Failed to read .gitignore` / `Failed to write .gitignore`) and the
|
|
2604
|
+
* original native error attached as `cause`.
|
|
2599
2605
|
*/
|
|
2606
|
+
declare function appendBasouGitignore(repositoryRoot: string): Promise<AppendBasouGitignoreResult>;
|
|
2607
|
+
|
|
2600
2608
|
/**
|
|
2601
|
-
*
|
|
2602
|
-
*
|
|
2603
|
-
*
|
|
2604
|
-
*
|
|
2605
|
-
* - `"none"`: inherit the parent's stdio. The child writes directly to the
|
|
2606
|
-
* parent terminal in real time and {@link RunResult.stdout} /
|
|
2607
|
-
* {@link RunResult.stderr} are empty strings. `stdin` cannot be combined
|
|
2608
|
-
* with `"none"` because the child has no writable stdin pipe.
|
|
2609
|
+
* The two lock scopes basou uses. `task` guards the read-modify-write window
|
|
2610
|
+
* around a single `task.md`; `session` guards the events.jsonl append plus
|
|
2611
|
+
* surrounding `session.yaml` mutation for a single session. Two scopes use
|
|
2612
|
+
* different lockfile names so they never collide on disk.
|
|
2609
2613
|
*/
|
|
2610
|
-
type
|
|
2611
|
-
type
|
|
2612
|
-
/**
|
|
2613
|
-
* Working directory for the child process. Required: callers resolve
|
|
2614
|
-
* the workspace root themselves; the runner does not validate cwd
|
|
2615
|
-
* existence and surfaces native spawn errors via classification.
|
|
2616
|
-
*/
|
|
2617
|
-
readonly cwd: string;
|
|
2618
|
-
/**
|
|
2619
|
-
* Environment variables for the child. When omitted, the parent's
|
|
2620
|
-
* `process.env` is inherited verbatim. Callers wanting a sanitized
|
|
2621
|
-
* environment must build it explicitly.
|
|
2622
|
-
*/
|
|
2623
|
-
readonly env?: NodeJS.ProcessEnv;
|
|
2624
|
-
/**
|
|
2625
|
-
* External cancellation. Aborting the signal triggers a two-stage
|
|
2626
|
-
* kill (SIGTERM, then SIGKILL after a short grace period).
|
|
2627
|
-
*/
|
|
2628
|
-
readonly signal?: AbortSignal;
|
|
2629
|
-
/**
|
|
2630
|
-
* Internal timeout in milliseconds. Must be a positive finite number.
|
|
2631
|
-
* Triggers the same two-stage kill as `signal`.
|
|
2632
|
-
*/
|
|
2633
|
-
readonly timeout_ms?: number;
|
|
2634
|
-
/**
|
|
2635
|
-
* Optional input written to the child's stdin. The pipe is closed
|
|
2636
|
-
* after the value is written. Incompatible with `capture: "none"`.
|
|
2637
|
-
*/
|
|
2638
|
-
readonly stdin?: string | Buffer;
|
|
2639
|
-
/**
|
|
2640
|
-
* Output capture mode. Defaults to `"buffer"`. See {@link CaptureMode}.
|
|
2641
|
-
*/
|
|
2642
|
-
readonly capture?: CaptureMode;
|
|
2614
|
+
type LockScope = "task" | "session";
|
|
2615
|
+
type LockHandle = {
|
|
2643
2616
|
/**
|
|
2644
|
-
*
|
|
2645
|
-
*
|
|
2646
|
-
*
|
|
2647
|
-
* the child if the parent is forcibly terminated). The runner takes no
|
|
2648
|
-
* action if the callback throws.
|
|
2617
|
+
* Release the lock by unlinking the lockfile. Best-effort: any unlink error
|
|
2618
|
+
* is swallowed so a doubled release does not raise, and disk state never
|
|
2619
|
+
* holds a stranded lockfile after the caller's `finally` block.
|
|
2649
2620
|
*/
|
|
2650
|
-
|
|
2651
|
-
};
|
|
2652
|
-
type RunResult = {
|
|
2653
|
-
readonly command: string;
|
|
2654
|
-
readonly args: readonly string[];
|
|
2655
|
-
readonly cwd: string;
|
|
2656
|
-
/** `null` when the process was killed by a signal. */
|
|
2657
|
-
readonly exit_code: number | null;
|
|
2658
|
-
readonly signal: NodeJS.Signals | null;
|
|
2659
|
-
readonly stdout: string;
|
|
2660
|
-
readonly stderr: string;
|
|
2661
|
-
/** ISO 8601 timestamp captured before spawn. */
|
|
2662
|
-
readonly started_at: string;
|
|
2663
|
-
/** ISO 8601 timestamp captured on the `close` event. */
|
|
2664
|
-
readonly ended_at: string;
|
|
2665
|
-
readonly duration_ms: number;
|
|
2666
|
-
readonly pid: number | null;
|
|
2667
|
-
};
|
|
2668
|
-
type ProcessRunner = {
|
|
2669
|
-
run(command: string, args: readonly string[], options: RunOptions): Promise<RunResult>;
|
|
2621
|
+
release: () => Promise<void>;
|
|
2670
2622
|
};
|
|
2671
|
-
|
|
2672
2623
|
/**
|
|
2673
|
-
*
|
|
2624
|
+
* Acquire an advisory lock at `<paths.locks>/<scope>_<id>.lock` for the
|
|
2625
|
+
* lifetime of the returned handle. Lockfile body records the holder's pid
|
|
2626
|
+
* and acquire timestamp so a competitor can detect stale locks left by a
|
|
2627
|
+
* SIGINT'd CLI run and recover automatically.
|
|
2674
2628
|
*
|
|
2675
|
-
*
|
|
2676
|
-
*
|
|
2677
|
-
*
|
|
2678
|
-
*
|
|
2679
|
-
*
|
|
2680
|
-
*
|
|
2681
|
-
*
|
|
2682
|
-
*
|
|
2683
|
-
*
|
|
2684
|
-
*
|
|
2685
|
-
* `RunResult.stdout` / `stderr` are empty strings. `stdin` is
|
|
2686
|
-
* incompatible with this mode (the child has no writable stdin pipe)
|
|
2687
|
-
* and the combination is rejected before spawn.
|
|
2688
|
-
* - `timeout_ms` and `AbortSignal` both trigger a two-stage kill:
|
|
2689
|
-
* `SIGTERM`, then `SIGKILL` after `DEFAULT_KILL_GRACE_MS` (5_000 ms).
|
|
2690
|
-
* - A non-zero `exit_code` does not throw; it is returned via
|
|
2691
|
-
* `RunResult`. Spawn-time errors throw with a pathless message and
|
|
2692
|
-
* the original error attached as `cause`.
|
|
2629
|
+
* Acquisition strategy:
|
|
2630
|
+
* 1. {@link atomicCreate} the lockfile (POSIX link(2) + EEXIST).
|
|
2631
|
+
* 2. On EEXIST, probe the existing lockfile via {@link isStaleLock}.
|
|
2632
|
+
* - If stale (= holder pid is dead or lock is older than
|
|
2633
|
+
* {@link STALE_LOCK_MAX_AGE_MS}), `unlink` the stale file and retry
|
|
2634
|
+
* the atomic create once.
|
|
2635
|
+
* - If still EEXIST after the retry (= another competitor won the race),
|
|
2636
|
+
* throw `"Lock is held by another process"`.
|
|
2637
|
+
* - If the holder is alive, throw `"Lock is held by another process"`
|
|
2638
|
+
* without retrying.
|
|
2693
2639
|
*
|
|
2694
|
-
*
|
|
2695
|
-
*
|
|
2696
|
-
*
|
|
2697
|
-
* `findErrorCode` when needed.
|
|
2640
|
+
* The caller MUST call `release()` (typically from a `finally` block); the
|
|
2641
|
+
* `process.exit()` path or a fatal crash relies on stale-lock detection on
|
|
2642
|
+
* the next acquire to recover.
|
|
2698
2643
|
*/
|
|
2699
|
-
declare
|
|
2700
|
-
run(command: string, args: readonly string[], options: RunOptions): Promise<RunResult>;
|
|
2701
|
-
}
|
|
2644
|
+
declare function acquireLock(paths: BasouPaths, scope: LockScope, resourceId: string): Promise<LockHandle>;
|
|
2702
2645
|
|
|
2703
2646
|
/**
|
|
2704
|
-
*
|
|
2705
|
-
*
|
|
2706
|
-
*
|
|
2707
|
-
|
|
2708
|
-
|
|
2647
|
+
* Inputs for {@link createManifest}. Optional fields drop out of the
|
|
2648
|
+
* resulting Manifest entirely (they are not emitted as `null`/`undefined`
|
|
2649
|
+
* in YAML); pass `null` for `repositoryUrl` to keep an explicit `null`.
|
|
2650
|
+
*/
|
|
2651
|
+
type CreateManifestInput = {
|
|
2652
|
+
workspaceName: string;
|
|
2653
|
+
projectName?: string;
|
|
2654
|
+
projectDescription?: string;
|
|
2655
|
+
repositoryUrl?: string | null;
|
|
2656
|
+
/** Override for tests; defaults to `new Date()`. */
|
|
2657
|
+
now?: Date;
|
|
2658
|
+
/** Override for tests; defaults to a freshly generated `ws_<ULID>`. */
|
|
2659
|
+
workspaceId?: PrefixedId<"ws">;
|
|
2660
|
+
};
|
|
2661
|
+
/**
|
|
2662
|
+
* Build a fresh Manifest object that satisfies the manifest schema's
|
|
2663
|
+
* minimum shape. Performs no I/O. Returned object is parse-validated by
|
|
2664
|
+
* `ManifestSchema`.
|
|
2665
|
+
*/
|
|
2666
|
+
declare function createManifest(input: CreateManifestInput): Manifest;
|
|
2667
|
+
/**
|
|
2668
|
+
* Write a Manifest to `paths.files.manifest`. Re-validates via
|
|
2669
|
+
* `ManifestSchema` before serialization.
|
|
2709
2670
|
*
|
|
2710
|
-
*
|
|
2711
|
-
* tracking; the schema declares both as optional non-negative integers.
|
|
2671
|
+
* Refuses to overwrite an existing manifest unless `force: true`.
|
|
2712
2672
|
*/
|
|
2713
|
-
|
|
2673
|
+
declare function writeManifest(paths: BasouPaths, manifest: Manifest, options?: {
|
|
2674
|
+
force?: boolean;
|
|
2675
|
+
}): Promise<void>;
|
|
2714
2676
|
/**
|
|
2715
|
-
*
|
|
2716
|
-
*
|
|
2677
|
+
* Read and parse a Manifest from `paths.files.manifest`. Throws if the file
|
|
2678
|
+
* is missing or contents fail `ManifestSchema` validation.
|
|
2679
|
+
*/
|
|
2680
|
+
declare function readManifest(paths: BasouPaths): Promise<Manifest>;
|
|
2681
|
+
|
|
2682
|
+
/** Marker line that begins the auto-generated region. */
|
|
2683
|
+
declare const GENERATED_START = "<!-- BASOU:GENERATED:START -->";
|
|
2684
|
+
/** Marker line that ends the auto-generated region. */
|
|
2685
|
+
declare const GENERATED_END = "<!-- BASOU:GENERATED:END -->";
|
|
2686
|
+
/**
|
|
2687
|
+
* Result of parsing a markdown body for the BASOU:GENERATED marker region.
|
|
2717
2688
|
*
|
|
2718
|
-
*
|
|
2719
|
-
*
|
|
2720
|
-
*
|
|
2721
|
-
*
|
|
2722
|
-
*
|
|
2689
|
+
* The spec mandates strict line-level matching (see
|
|
2690
|
+
* `docs/spec/generated-markdown.md#102-marker-convention`): a marker is
|
|
2691
|
+
* only recognized when an entire line is exactly the marker string.
|
|
2692
|
+
* Leading/trailing whitespace, comment compression, and BOM are treated as
|
|
2693
|
+
* legacy formats (`no_markers`) so that re-generation refuses to silently
|
|
2694
|
+
* overwrite a mismatched manual edit.
|
|
2695
|
+
*/
|
|
2696
|
+
type MarkerSection = {
|
|
2697
|
+
kind: "ok";
|
|
2698
|
+
before: string;
|
|
2699
|
+
generated: string;
|
|
2700
|
+
after: string;
|
|
2701
|
+
} | {
|
|
2702
|
+
kind: "no_markers";
|
|
2703
|
+
} | {
|
|
2704
|
+
kind: "missing_start";
|
|
2705
|
+
} | {
|
|
2706
|
+
kind: "missing_end";
|
|
2707
|
+
} | {
|
|
2708
|
+
kind: "multiple_pairs";
|
|
2709
|
+
} | {
|
|
2710
|
+
kind: "wrong_order";
|
|
2711
|
+
};
|
|
2712
|
+
/**
|
|
2713
|
+
* Read a markdown file as UTF-8 text. Returns `null` when the file does not
|
|
2714
|
+
* exist; throws `Error("Failed to read markdown file", { cause })` for other
|
|
2715
|
+
* I/O failures (pathless contract — never embed the absolute path in the
|
|
2716
|
+
* thrown `message`).
|
|
2717
|
+
*/
|
|
2718
|
+
declare function readMarkdownFile(filePath: string): Promise<string | null>;
|
|
2719
|
+
/**
|
|
2720
|
+
* Atomically write a markdown body via {@link atomicReplace}. The shared
|
|
2721
|
+
* helper handles the tmp-file + rename sequence, `wx` collision guard, and
|
|
2722
|
+
* best-effort tmp cleanup on failure.
|
|
2723
2723
|
*
|
|
2724
|
-
*
|
|
2725
|
-
*
|
|
2724
|
+
* On any failure the original error is re-thrown as
|
|
2725
|
+
* `Error("Failed to write markdown file", { cause })` (pathless contract).
|
|
2726
2726
|
*/
|
|
2727
|
-
declare function
|
|
2727
|
+
declare function writeMarkdownFile(filePath: string, body: string): Promise<void>;
|
|
2728
2728
|
/**
|
|
2729
|
-
*
|
|
2730
|
-
*
|
|
2731
|
-
*
|
|
2729
|
+
* Parse a markdown body and identify the BASOU:GENERATED marker region.
|
|
2730
|
+
*
|
|
2731
|
+
* Returns one of six `kind` discriminants:
|
|
2732
|
+
* - `ok`: exactly one START line followed by exactly one END line in the
|
|
2733
|
+
* correct order. `before` / `generated` / `after` slice the original
|
|
2734
|
+
* text by character offsets so CRLF / LF are preserved verbatim outside
|
|
2735
|
+
* the marker region.
|
|
2736
|
+
* - `no_markers`: both START and END absent (legacy file / fresh write).
|
|
2737
|
+
* - `missing_start` / `missing_end`: exactly one of the pair is present.
|
|
2738
|
+
* - `multiple_pairs`: more than one START or END line.
|
|
2739
|
+
* - `wrong_order`: END appears before START.
|
|
2732
2740
|
*
|
|
2733
|
-
*
|
|
2734
|
-
*
|
|
2735
|
-
*
|
|
2741
|
+
* Matching is strict: leading/trailing whitespace, BOM, and comment
|
|
2742
|
+
* compression (`<!--BASOU:...-->`) all bypass the marker and are treated
|
|
2743
|
+
* as legacy content.
|
|
2736
2744
|
*/
|
|
2737
|
-
declare function
|
|
2745
|
+
declare function parseMarkers(content: string): MarkerSection;
|
|
2738
2746
|
/**
|
|
2739
|
-
* Build
|
|
2740
|
-
* caller is responsible for ensuring `repositoryRoot` is the canonical root
|
|
2741
|
-
* (typically obtained via {@link resolveRepositoryRoot}); this function
|
|
2742
|
-
* verifies repo membership via `git rev-parse --is-inside-work-tree` to
|
|
2743
|
-
* distinguish a non-git directory from an empty repository.
|
|
2747
|
+
* Build the final markdown body by replacing the BASOU:GENERATED region.
|
|
2744
2748
|
*
|
|
2745
|
-
*
|
|
2746
|
-
* -
|
|
2747
|
-
*
|
|
2748
|
-
* -
|
|
2749
|
-
* `ahead`/`behind` omitted
|
|
2750
|
-
* - **no remote / no upstream tracking**: `ahead`/`behind` omitted
|
|
2749
|
+
* - `existing === null` (no file yet): return `<START>\n<generated>\n<END>\n`.
|
|
2750
|
+
* - existing parses to `ok`: replace the marked region and keep everything
|
|
2751
|
+
* before START and after END untouched (preserving manual additions).
|
|
2752
|
+
* - any other parse result: throw a pathless error referencing `fileLabel`.
|
|
2751
2753
|
*
|
|
2752
|
-
*
|
|
2754
|
+
* The caller passes `fileLabel` (e.g. `"handoff.md"` or `"decisions.md"`)
|
|
2755
|
+
* so the error message is informative without leaking an absolute path.
|
|
2753
2756
|
*/
|
|
2754
|
-
declare function
|
|
2757
|
+
declare function renderWithMarkers(existing: string | null, generated: string, fileLabel: string): string;
|
|
2755
2758
|
|
|
2756
2759
|
/**
|
|
2757
|
-
*
|
|
2758
|
-
*
|
|
2759
|
-
*
|
|
2760
|
-
*
|
|
2761
|
-
|
|
2762
|
-
type FileChangeStatus = "added" | "modified" | "deleted" | "renamed";
|
|
2763
|
-
/**
|
|
2764
|
-
* Single file-level change observed between two refs. `old_path` is set
|
|
2765
|
-
* only for `renamed` entries (the previous path of the file).
|
|
2760
|
+
* Options for {@link importSessionFromJson}. All fields are optional.
|
|
2761
|
+
*
|
|
2762
|
+
* - `labelOverride` / `taskIdOverride` come from the CLI `--label` / `--task`
|
|
2763
|
+
* flags and win over the corresponding fields on the input payload.
|
|
2764
|
+
* - `dryRun` skips disk writes entirely and returns a preview result.
|
|
2766
2765
|
*/
|
|
2767
|
-
type
|
|
2768
|
-
|
|
2769
|
-
|
|
2770
|
-
|
|
2766
|
+
type ImportSessionOptions = {
|
|
2767
|
+
labelOverride?: string;
|
|
2768
|
+
taskIdOverride?: string;
|
|
2769
|
+
dryRun?: boolean;
|
|
2771
2770
|
};
|
|
2772
2771
|
/**
|
|
2773
|
-
* Result of
|
|
2774
|
-
*
|
|
2775
|
-
*
|
|
2772
|
+
* Result of a successful import. `finalStatus` is always the literal
|
|
2773
|
+
* `"imported"` (per the import-session lifecycle policy); `finalSourceKind`
|
|
2774
|
+
* mirrors the input's `session.source.kind` so round-trip imports preserve
|
|
2775
|
+
* provenance.
|
|
2776
|
+
*
|
|
2777
|
+
* `pathSanitizeReport` summarises how many path-shaped fields the importer
|
|
2778
|
+
* rewrote on the way in: `related_files[]` entries plus a single boolean
|
|
2779
|
+
* for `working_directory`. The CLI wrapper surfaces this as a one-line
|
|
2780
|
+
* stderr warning when the total is non-zero so the operator sees that
|
|
2781
|
+
* machine-private prefixes were stripped.
|
|
2776
2782
|
*/
|
|
2777
|
-
type
|
|
2778
|
-
|
|
2783
|
+
type ImportSessionResult = {
|
|
2784
|
+
sessionId: PrefixedId<"ses">;
|
|
2785
|
+
eventCount: number;
|
|
2786
|
+
finalStatus: SessionStatus;
|
|
2787
|
+
finalSourceKind: SessionSourceKind;
|
|
2788
|
+
pathSanitizeReport: {
|
|
2789
|
+
relatedFiles: number;
|
|
2790
|
+
workingDirectoryRewritten: boolean;
|
|
2791
|
+
};
|
|
2779
2792
|
};
|
|
2780
2793
|
/**
|
|
2781
|
-
*
|
|
2782
|
-
*
|
|
2783
|
-
*
|
|
2784
|
-
*
|
|
2785
|
-
* returned — `file_changed` events record paths only, and raw diff bodies
|
|
2786
|
-
* are excluded so the trace cannot inadvertently leak source code that may
|
|
2787
|
-
* be sensitive. Use `git show <ref>` to obtain the underlying diff.
|
|
2788
|
-
*
|
|
2789
|
-
* Pathless contract: every thrown message is a fixed string from the set
|
|
2790
|
-
* {`Not a git repository`, `Git executable not found in PATH. Install git
|
|
2791
|
-
* first.`, `Invalid ref`, `Failed to compute git diff`}; native errors are
|
|
2792
|
-
* preserved on `Error.cause`.
|
|
2794
|
+
* Import a round-trip JSON payload into `.basou/sessions/<new>/`. The caller
|
|
2795
|
+
* MUST validate the payload against {@link SessionImportPayloadSchema} first
|
|
2796
|
+
* and gate the `schema_version === "0.1.0"` literal check externally; this
|
|
2797
|
+
* function trusts both invariants.
|
|
2793
2798
|
*
|
|
2794
|
-
*
|
|
2795
|
-
*
|
|
2796
|
-
*
|
|
2799
|
+
* On success a fresh session ID is minted and a complete
|
|
2800
|
+
* `session.yaml` + `events.jsonl` pair is written atomically. On any post-
|
|
2801
|
+
* mkdir failure the session directory is removed best-effort so partial
|
|
2802
|
+
* imports do not leave `session_yaml_missing` half-states behind.
|
|
2797
2803
|
*
|
|
2798
|
-
*
|
|
2799
|
-
*
|
|
2800
|
-
*
|
|
2804
|
+
* Throws `Error` with one of the fixed messages enumerated by the import contract
|
|
2805
|
+
* §"Error messages" table; the original native error is attached as `cause`
|
|
2806
|
+
* for `--verbose` rendering.
|
|
2801
2807
|
*/
|
|
2802
|
-
declare function
|
|
2808
|
+
declare function importSessionFromJson(paths: BasouPaths, manifest: Manifest, payload: SessionImportPayload, options: ImportSessionOptions): Promise<ImportSessionResult>;
|
|
2803
2809
|
|
|
2804
2810
|
/**
|
|
2805
|
-
*
|
|
2806
|
-
*
|
|
2807
|
-
*
|
|
2808
|
-
*
|
|
2809
|
-
* migration.
|
|
2810
|
-
*/
|
|
2811
|
-
declare const claudeCodeAdapterMetadata: {
|
|
2812
|
-
readonly kind: "claude-code-adapter";
|
|
2813
|
-
readonly version: "0.1.0";
|
|
2814
|
-
};
|
|
2815
|
-
/**
|
|
2816
|
-
* Lookup predicate used by {@link resolveClaudeCodeCommand} to decide
|
|
2817
|
-
* whether a candidate executable is reachable on PATH. Exposed as a
|
|
2818
|
-
* parameter so tests can substitute a deterministic mock; production
|
|
2819
|
-
* callers should omit it and rely on the default `which`-based lookup.
|
|
2811
|
+
* Walk the cause chain (up to `depth` levels) looking for an Error whose
|
|
2812
|
+
* errno-style `code` matches `code`. Returns true on the first match.
|
|
2813
|
+
* Resilient to wrapper depth changes so that ENOENT detection survives
|
|
2814
|
+
* future error-wrapping refactors.
|
|
2820
2815
|
*/
|
|
2821
|
-
|
|
2816
|
+
declare function findErrorCode(error: unknown, code: string, depth?: number): boolean;
|
|
2817
|
+
|
|
2822
2818
|
/**
|
|
2823
|
-
*
|
|
2824
|
-
*
|
|
2819
|
+
* Refuse to operate on `.basou` if it is a symlink or not a directory. This
|
|
2820
|
+
* prevents `writeStatus` from being tricked into writing `status.json`
|
|
2821
|
+
* outside the repository root via a swapped `.basou` symlink. Mirrors
|
|
2822
|
+
* `ensureBasouDirectory`'s lstat-based guard.
|
|
2825
2823
|
*
|
|
2826
|
-
*
|
|
2827
|
-
* callers can
|
|
2824
|
+
* If `.basou` is absent the underlying ENOENT is propagated (wrapped) so
|
|
2825
|
+
* callers can map it to "workspace not initialized" via `findErrorCode`.
|
|
2828
2826
|
*
|
|
2829
|
-
*
|
|
2827
|
+
* Note: this is a baseline safety net, not a TOCTOU fix — the directory
|
|
2828
|
+
* could still be replaced between this check and the subsequent write. The
|
|
2829
|
+
* goal is to detect already-swapped symlinks, not to race-proof the
|
|
2830
|
+
* filesystem.
|
|
2830
2831
|
*/
|
|
2831
|
-
declare function
|
|
2832
|
-
command: string;
|
|
2833
|
-
}>;
|
|
2832
|
+
declare function assertBasouRootSafe(rootPath: string): Promise<void>;
|
|
2834
2833
|
/**
|
|
2835
|
-
*
|
|
2836
|
-
*
|
|
2837
|
-
*
|
|
2838
|
-
* `
|
|
2839
|
-
* The signature is committed so that Step 12+ can implement raw_ref
|
|
2840
|
-
* generation without retrofitting the adapter scaffold.
|
|
2834
|
+
* Build a StatusSnapshot from a manifest plus the path layout, observing
|
|
2835
|
+
* each subdirectory's presence via `lstat`. Read-only with respect to the
|
|
2836
|
+
* workspace state; writes nothing. The result is re-validated by
|
|
2837
|
+
* `StatusSchema.parse` before being returned.
|
|
2841
2838
|
*
|
|
2842
|
-
* @
|
|
2839
|
+
* @param input.now Override for testing; defaults to `new Date()`.
|
|
2843
2840
|
*/
|
|
2844
|
-
declare function
|
|
2845
|
-
|
|
2841
|
+
declare function buildStatusSnapshot(input: {
|
|
2842
|
+
manifest: Manifest;
|
|
2843
|
+
paths: BasouPaths;
|
|
2844
|
+
now?: Date;
|
|
2845
|
+
}): Promise<StatusSnapshot>;
|
|
2846
2846
|
/**
|
|
2847
|
-
*
|
|
2848
|
-
*
|
|
2849
|
-
* The event is validated against the discriminated union {@link EventSchema}
|
|
2850
|
-
* before being serialized as a single JSONL line (UTF-8, terminated by `\n`).
|
|
2851
|
-
* Validation enforces the per-variant contract (required fields, source
|
|
2852
|
-
* vocabulary, strict variants such as `adapter_output`).
|
|
2853
|
-
*
|
|
2854
|
-
* Atomicity: writes go through `appendFile` which uses `O_APPEND`. Lines up
|
|
2855
|
-
* to `PIPE_BUF` bytes (Linux 4096 / macOS 512) are written atomically by the
|
|
2856
|
-
* kernel; longer lines may interleave with concurrent writers and are not
|
|
2857
|
-
* recovered here. v0.1 assumes a single writer per session, so partial-line
|
|
2858
|
-
* recovery is delegated to the read side (event replay) when introduced.
|
|
2847
|
+
* Atomically write a StatusSnapshot to `paths.files.status`.
|
|
2859
2848
|
*
|
|
2860
|
-
*
|
|
2861
|
-
*
|
|
2849
|
+
* Re-validates via `StatusSchema.parse` before any file I/O, so an invalid
|
|
2850
|
+
* snapshot throws synchronously and never overwrites the existing
|
|
2851
|
+
* `status.json`. Delegates the tmp-file + rename pass to {@link atomicReplace}.
|
|
2862
2852
|
*
|
|
2863
|
-
*
|
|
2864
|
-
*
|
|
2853
|
+
* **Precondition**: callers MUST invoke {@link assertBasouRootSafe} on
|
|
2854
|
+
* `paths.root` first to ensure `.basou` is a real directory and not a
|
|
2855
|
+
* swapped symlink. `writeStatus` does not redo this guard — it trusts the
|
|
2856
|
+
* caller — so a direct invocation without the guard could write
|
|
2857
|
+
* `status.json` outside the repository root.
|
|
2865
2858
|
*/
|
|
2866
|
-
declare function
|
|
2859
|
+
declare function writeStatus(paths: BasouPaths, snapshot: StatusSnapshot): Promise<void>;
|
|
2867
2860
|
/**
|
|
2868
|
-
*
|
|
2869
|
-
*
|
|
2870
|
-
*
|
|
2871
|
-
*
|
|
2872
|
-
* The helper is used by the round-trip importer (`session-import.ts`) and the
|
|
2873
|
-
* ad-hoc session orchestrator (`ad-hoc-session.ts`) where a small, fixed batch
|
|
2874
|
-
* of events must land together or not at all. Zero events produces a
|
|
2875
|
-
* zero-byte file so the session_yaml `events_log` pointer remains valid.
|
|
2876
|
-
*
|
|
2877
|
-
* Throws `"Invalid Basou event payload"` (same fixed message as
|
|
2878
|
-
* {@link appendEvent}) on validation failure, or `"Failed to write
|
|
2879
|
-
* events.jsonl"` on a disk I/O failure. The original native error is attached
|
|
2880
|
-
* as `cause`.
|
|
2861
|
+
* Read `.basou/status.json` for the current schema_version (0.1.0). This
|
|
2862
|
+
* is a cache reader only; cross-version migration is not supported here.
|
|
2863
|
+
* Older or newer status.json shapes will fail `StatusSchema.parse` —
|
|
2864
|
+
* callers regenerate by calling `buildStatusSnapshot` + `writeStatus`.
|
|
2881
2865
|
*/
|
|
2882
|
-
declare function
|
|
2866
|
+
declare function readStatus(paths: BasouPaths): Promise<StatusSnapshot>;
|
|
2883
2867
|
|
|
2884
2868
|
/**
|
|
2885
|
-
*
|
|
2886
|
-
* into milliseconds.
|
|
2869
|
+
* Read a YAML file as `unknown`. Caller MUST validate via a zod schema.
|
|
2887
2870
|
*
|
|
2888
|
-
*
|
|
2889
|
-
*
|
|
2890
|
-
*
|
|
2871
|
+
* Throws Error with pathless message and the original native error attached
|
|
2872
|
+
* as `cause` for I/O failures and YAML parse errors. All fs and parse exits
|
|
2873
|
+
* go through fixed messages so absolute paths cannot leak via `error.message`.
|
|
2874
|
+
*/
|
|
2875
|
+
declare function readYamlFile(filePath: string): Promise<unknown>;
|
|
2876
|
+
/**
|
|
2877
|
+
* Write a value as YAML using {@link atomicReplace} for crash-resistant
|
|
2878
|
+
* atomicity. The shared helper handles the tmp-file + rename sequence,
|
|
2879
|
+
* `wx` collision guard, and best-effort tmp cleanup on failure. This
|
|
2880
|
+
* wrapper adds the YAML serialisation and the pathless error vocabulary.
|
|
2881
|
+
*/
|
|
2882
|
+
declare function writeYamlFile(filePath: string, value: unknown): Promise<void>;
|
|
2883
|
+
/**
|
|
2884
|
+
* Atomically create a new YAML file. Like {@link writeYamlFile} but
|
|
2885
|
+
* delegates to {@link atomicCreate} so a pre-existing target fails with
|
|
2886
|
+
* EEXIST instead of being silently overwritten.
|
|
2891
2887
|
*
|
|
2892
|
-
*
|
|
2893
|
-
*
|
|
2888
|
+
* Used by `basou approval approve` / `reject` to write the resolved-side
|
|
2889
|
+
* YAML, so a concurrent resolver cannot overwrite an already-resolved
|
|
2890
|
+
* approval.
|
|
2894
2891
|
*
|
|
2895
|
-
*
|
|
2896
|
-
*
|
|
2897
|
-
* @throws Error with message
|
|
2898
|
-
* `Invalid duration: <input>. Expected format: <positive-integer><unit> where unit is ms/s/m/h`
|
|
2899
|
-
* for format errors, or `Duration overflow: <input>` for non-finite results.
|
|
2892
|
+
* Throws `Error("Failed to write YAML file", { cause })` on failure; if
|
|
2893
|
+
* `cause.code === "EEXIST"` the caller can detect a target-exists race.
|
|
2900
2894
|
*/
|
|
2901
|
-
declare function
|
|
2895
|
+
declare function linkYamlFile(filePath: string, value: unknown): Promise<void>;
|
|
2896
|
+
/**
|
|
2897
|
+
* Overwrite an existing YAML file atomically. Like {@link writeYamlFile}
|
|
2898
|
+
* but with a distinct pathless message label, used for files that
|
|
2899
|
+
* legitimately need in-place mutation (e.g. session.yaml's status /
|
|
2900
|
+
* ended_at lifecycle updates).
|
|
2901
|
+
*/
|
|
2902
|
+
declare function overwriteYamlFile(filePath: string, value: unknown): Promise<void>;
|
|
2902
2903
|
|
|
2903
2904
|
/**
|
|
2904
2905
|
* Version of the `@basou/core` package, aligned with `manifest.yaml`'s
|