@basou/core 0.3.1 → 0.5.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 +1631 -1234
- package/dist/index.js +2554 -1754
- package/dist/index.js.map +1 -1
- package/package.json +8 -3
package/dist/index.d.ts
CHANGED
|
@@ -2,108 +2,47 @@ import { z } from 'zod';
|
|
|
2
2
|
import { ChildProcess } from 'node:child_process';
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*/
|
|
11
|
-
declare const ID_PREFIXES: readonly ["ws", "task", "ses", "evt", "appr", "decision"];
|
|
12
|
-
/**
|
|
13
|
-
* Type prefix used for Basou entity IDs.
|
|
14
|
-
* Format: `<prefix>_<26-char ULID>`, e.g. `ws_01HXABCDEF1234567890ABCDEF`.
|
|
15
|
-
*/
|
|
16
|
-
type IdPrefix = (typeof ID_PREFIXES)[number];
|
|
17
|
-
/**
|
|
18
|
-
* A Basou entity ID as a template literal type.
|
|
19
|
-
*
|
|
20
|
-
* `PrefixedId<"ses">` narrows to ``ses_${string}`` so a session schema can
|
|
21
|
-
* preserve the prefix in its inferred type beyond runtime validation.
|
|
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.
|
|
22
10
|
*/
|
|
23
|
-
|
|
11
|
+
declare const claudeCodeAdapterMetadata: {
|
|
12
|
+
readonly kind: "claude-code-adapter";
|
|
13
|
+
readonly version: "0.1.0";
|
|
14
|
+
};
|
|
24
15
|
/**
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
-
*
|
|
28
|
-
*
|
|
29
|
-
* lifetime of the current process.
|
|
30
|
-
*
|
|
31
|
-
* NOTE: `seedTime` is forwarded to the underlying monotonic factory and is
|
|
32
|
-
* NOT a deterministic seed: repeated calls with the same `seedTime` still
|
|
33
|
-
* return strictly increasing values, because the factory increments its
|
|
34
|
-
* internal counter on each call.
|
|
35
|
-
*
|
|
36
|
-
* @param seedTime Optional millisecond timestamp passed to the monotonic
|
|
37
|
-
* factory. Useful for ordered generation in tests; not deterministic.
|
|
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.
|
|
38
20
|
*/
|
|
39
|
-
|
|
21
|
+
type CommandLookup = (command: string) => Promise<boolean>;
|
|
40
22
|
/**
|
|
41
|
-
*
|
|
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.
|
|
42
25
|
*
|
|
43
|
-
*
|
|
44
|
-
*
|
|
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.
|
|
45
28
|
*
|
|
46
|
-
*
|
|
47
|
-
* defends against JavaScript callers and casted TypeScript that bypass the
|
|
48
|
-
* compile-time `IdPrefix` constraint.
|
|
29
|
+
* @throws Error("Claude Code CLI not found in PATH. Install claude-code (or claude) first.")
|
|
49
30
|
*/
|
|
50
|
-
declare function
|
|
31
|
+
declare function resolveClaudeCodeCommand(lookup?: CommandLookup): Promise<{
|
|
32
|
+
command: string;
|
|
33
|
+
}>;
|
|
51
34
|
/**
|
|
52
|
-
*
|
|
53
|
-
*
|
|
54
|
-
* Returns true only if the string has shape `<prefix>_<ULID>` where prefix is
|
|
55
|
-
* one of {@link ID_PREFIXES} and the trailing 26 characters form a valid
|
|
56
|
-
* Crockford Base32 ULID. Validation combines a strict shape regex (to enforce
|
|
57
|
-
* the 0-7 leading char and the I/L/O/U exclusion) with the npm `ulid`
|
|
58
|
-
* library's `isValid` for forward compatibility.
|
|
35
|
+
* Stub for the future `adapter_output` summary generator.
|
|
59
36
|
*
|
|
60
|
-
*
|
|
61
|
-
*
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
/**
|
|
66
|
-
* Schema version literal pinned to "0.1.0" for Basou v0.1.
|
|
67
|
-
* Reused across every entity schema so inferred types narrow to the literal.
|
|
68
|
-
*/
|
|
69
|
-
declare const SchemaVersionSchema: z.ZodLiteral<"0.1.0">;
|
|
70
|
-
/**
|
|
71
|
-
* ISO 8601 timestamp with explicit timezone offset (e.g. `+09:00`).
|
|
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.
|
|
72
42
|
*
|
|
73
|
-
*
|
|
74
|
-
* rejects offsets) is insufficient; `{ offset: true }` is required.
|
|
75
|
-
*/
|
|
76
|
-
declare const IsoTimestampSchema: z.ZodString;
|
|
77
|
-
/** Workspace ID schema: validates `ws_<26-char ULID>`. */
|
|
78
|
-
declare const WorkspaceIdSchema: z.ZodString & z.ZodType<`ws_${string}`, string, z.core.$ZodTypeInternals<`ws_${string}`, string>>;
|
|
79
|
-
/** Task ID schema: validates `task_<26-char ULID>`. */
|
|
80
|
-
declare const TaskIdSchema: z.ZodString & z.ZodType<`task_${string}`, string, z.core.$ZodTypeInternals<`task_${string}`, string>>;
|
|
81
|
-
/** Session ID schema: validates `ses_<26-char ULID>`. */
|
|
82
|
-
declare const SessionIdSchema: z.ZodString & z.ZodType<`ses_${string}`, string, z.core.$ZodTypeInternals<`ses_${string}`, string>>;
|
|
83
|
-
/** Event ID schema: validates `evt_<26-char ULID>`. */
|
|
84
|
-
declare const EventIdSchema: z.ZodString & z.ZodType<`evt_${string}`, string, z.core.$ZodTypeInternals<`evt_${string}`, string>>;
|
|
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>>;
|
|
89
|
-
/**
|
|
90
|
-
* Risk level vocabulary fixed by the spec. Adapters MUST emit one of these
|
|
91
|
-
* four values; arbitrary strings are rejected at schema parse time.
|
|
92
|
-
*/
|
|
93
|
-
declare const RiskLevelSchema: z.ZodEnum<{
|
|
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>;
|
|
101
|
-
/**
|
|
102
|
-
* Source attribution for events (e.g. "claude-code-adapter",
|
|
103
|
-
* "git-capability", "terminal-recording", "local-cli", "human"). Free-form
|
|
104
|
-
* non-empty string in v0.1; a stricter enum may be introduced post-v0.1.
|
|
43
|
+
* @throws Error - always; not implemented in this release.
|
|
105
44
|
*/
|
|
106
|
-
declare
|
|
45
|
+
declare function summarizeAdapterOutput(_stream: "stdout" | "stderr", _raw: string): string;
|
|
107
46
|
|
|
108
47
|
/**
|
|
109
48
|
* Schema for `.basou/manifest.yaml`. The minimal manifest carries
|
|
@@ -154,79 +93,81 @@ declare const ManifestSchema: z.ZodObject<{
|
|
|
154
93
|
/** Inferred runtime type for {@link ManifestSchema}. */
|
|
155
94
|
type Manifest = z.infer<typeof ManifestSchema>;
|
|
156
95
|
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
96
|
+
declare const SessionInnerImportSchema: z.ZodObject<{
|
|
97
|
+
id: z.ZodOptional<z.ZodString & z.ZodType<`ses_${string}`, string, z.core.$ZodTypeInternals<`ses_${string}`, string>>>;
|
|
98
|
+
label: z.ZodOptional<z.ZodString>;
|
|
99
|
+
task_id: z.ZodOptional<z.ZodNullable<z.ZodString & z.ZodType<`task_${string}`, string, z.core.$ZodTypeInternals<`task_${string}`, string>>>>;
|
|
100
|
+
workspace_id: z.ZodString & z.ZodType<`ws_${string}`, string, z.core.$ZodTypeInternals<`ws_${string}`, string>>;
|
|
101
|
+
source: z.ZodObject<{
|
|
102
|
+
kind: z.ZodEnum<{
|
|
103
|
+
"claude-code-adapter": "claude-code-adapter";
|
|
104
|
+
"claude-code-import": "claude-code-import";
|
|
105
|
+
"codex-import": "codex-import";
|
|
106
|
+
human: "human";
|
|
107
|
+
import: "import";
|
|
108
|
+
terminal: "terminal";
|
|
109
|
+
}>;
|
|
110
|
+
version: z.ZodLiteral<"0.1.0">;
|
|
111
|
+
external_id: z.ZodOptional<z.ZodString>;
|
|
112
|
+
}, z.core.$strip>;
|
|
113
|
+
started_at: z.ZodString;
|
|
114
|
+
ended_at: z.ZodOptional<z.ZodString>;
|
|
115
|
+
status: z.ZodEnum<{
|
|
116
|
+
initialized: "initialized";
|
|
117
|
+
running: "running";
|
|
118
|
+
waiting_approval: "waiting_approval";
|
|
119
|
+
completed: "completed";
|
|
120
|
+
failed: "failed";
|
|
121
|
+
interrupted: "interrupted";
|
|
122
|
+
imported: "imported";
|
|
123
|
+
archived: "archived";
|
|
124
|
+
}>;
|
|
125
|
+
working_directory: z.ZodString;
|
|
126
|
+
invocation: z.ZodObject<{
|
|
127
|
+
command: z.ZodString;
|
|
128
|
+
args: z.ZodArray<z.ZodString>;
|
|
129
|
+
exit_code: z.ZodNullable<z.ZodNumber>;
|
|
130
|
+
}, z.core.$strip>;
|
|
131
|
+
related_files: z.ZodDefault<z.ZodArray<z.ZodString>>;
|
|
132
|
+
events_log: z.ZodOptional<z.ZodString>;
|
|
133
|
+
summary: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
134
|
+
metrics: z.ZodOptional<z.ZodObject<{
|
|
135
|
+
output_tokens: z.ZodOptional<z.ZodNumber>;
|
|
136
|
+
input_tokens: z.ZodOptional<z.ZodNumber>;
|
|
137
|
+
cached_input_tokens: z.ZodOptional<z.ZodNumber>;
|
|
138
|
+
reasoning_output_tokens: z.ZodOptional<z.ZodNumber>;
|
|
139
|
+
active_time_ms: z.ZodOptional<z.ZodNumber>;
|
|
140
|
+
active_intervals: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
141
|
+
start: z.ZodString;
|
|
142
|
+
end: z.ZodString;
|
|
143
|
+
}, z.core.$strip>>>;
|
|
144
|
+
active_gap_cap_ms: z.ZodOptional<z.ZodNumber>;
|
|
145
|
+
active_time_method: z.ZodOptional<z.ZodString>;
|
|
146
|
+
}, z.core.$strip>>;
|
|
184
147
|
}, 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
148
|
/**
|
|
211
|
-
* Schema for
|
|
212
|
-
*
|
|
213
|
-
*
|
|
149
|
+
* Schema for the round-trip JSON payload accepted by `basou session import
|
|
150
|
+
* --format json`. The top level is `.strict()`; unknown keys at the outer
|
|
151
|
+
* envelope are rejected.
|
|
214
152
|
*/
|
|
215
|
-
declare const
|
|
216
|
-
schema_version: z.
|
|
153
|
+
declare const SessionImportPayloadSchema: z.ZodObject<{
|
|
154
|
+
schema_version: z.ZodString;
|
|
217
155
|
session: z.ZodObject<{
|
|
218
|
-
id: z.ZodString & z.ZodType<`ses_${string}`, string, z.core.$ZodTypeInternals<`ses_${string}`, string
|
|
156
|
+
id: z.ZodOptional<z.ZodString & z.ZodType<`ses_${string}`, string, z.core.$ZodTypeInternals<`ses_${string}`, string>>>;
|
|
219
157
|
label: z.ZodOptional<z.ZodString>;
|
|
220
158
|
task_id: z.ZodOptional<z.ZodNullable<z.ZodString & z.ZodType<`task_${string}`, string, z.core.$ZodTypeInternals<`task_${string}`, string>>>>;
|
|
221
159
|
workspace_id: z.ZodString & z.ZodType<`ws_${string}`, string, z.core.$ZodTypeInternals<`ws_${string}`, string>>;
|
|
222
160
|
source: z.ZodObject<{
|
|
223
161
|
kind: z.ZodEnum<{
|
|
224
162
|
"claude-code-adapter": "claude-code-adapter";
|
|
163
|
+
"claude-code-import": "claude-code-import";
|
|
164
|
+
"codex-import": "codex-import";
|
|
225
165
|
human: "human";
|
|
226
166
|
import: "import";
|
|
227
167
|
terminal: "terminal";
|
|
228
168
|
}>;
|
|
229
169
|
version: z.ZodLiteral<"0.1.0">;
|
|
170
|
+
external_id: z.ZodOptional<z.ZodString>;
|
|
230
171
|
}, z.core.$strip>;
|
|
231
172
|
started_at: z.ZodString;
|
|
232
173
|
ended_at: z.ZodOptional<z.ZodString>;
|
|
@@ -243,94 +184,380 @@ declare const SessionSchema: z.ZodObject<{
|
|
|
243
184
|
working_directory: z.ZodString;
|
|
244
185
|
invocation: z.ZodObject<{
|
|
245
186
|
command: z.ZodString;
|
|
246
|
-
args: z.
|
|
187
|
+
args: z.ZodArray<z.ZodString>;
|
|
247
188
|
exit_code: z.ZodNullable<z.ZodNumber>;
|
|
248
189
|
}, z.core.$strip>;
|
|
249
190
|
related_files: z.ZodDefault<z.ZodArray<z.ZodString>>;
|
|
250
|
-
events_log: z.
|
|
191
|
+
events_log: z.ZodOptional<z.ZodString>;
|
|
251
192
|
summary: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
type
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
193
|
+
metrics: z.ZodOptional<z.ZodObject<{
|
|
194
|
+
output_tokens: z.ZodOptional<z.ZodNumber>;
|
|
195
|
+
input_tokens: z.ZodOptional<z.ZodNumber>;
|
|
196
|
+
cached_input_tokens: z.ZodOptional<z.ZodNumber>;
|
|
197
|
+
reasoning_output_tokens: z.ZodOptional<z.ZodNumber>;
|
|
198
|
+
active_time_ms: z.ZodOptional<z.ZodNumber>;
|
|
199
|
+
active_intervals: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
200
|
+
start: z.ZodString;
|
|
201
|
+
end: z.ZodString;
|
|
202
|
+
}, z.core.$strip>>>;
|
|
203
|
+
active_gap_cap_ms: z.ZodOptional<z.ZodNumber>;
|
|
204
|
+
active_time_method: z.ZodOptional<z.ZodString>;
|
|
205
|
+
}, z.core.$strip>>;
|
|
206
|
+
}, z.core.$strict>;
|
|
207
|
+
events: z.ZodArray<z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
208
|
+
schema_version: z.ZodLiteral<"0.1.0">;
|
|
209
|
+
id: z.ZodString & z.ZodType<`evt_${string}`, string, z.core.$ZodTypeInternals<`evt_${string}`, string>>;
|
|
210
|
+
session_id: z.ZodString & z.ZodType<`ses_${string}`, string, z.core.$ZodTypeInternals<`ses_${string}`, string>>;
|
|
211
|
+
occurred_at: z.ZodString;
|
|
212
|
+
source: z.ZodString;
|
|
213
|
+
type: z.ZodLiteral<"session_started">;
|
|
214
|
+
}, z.core.$strip>, z.ZodObject<{
|
|
215
|
+
schema_version: z.ZodLiteral<"0.1.0">;
|
|
216
|
+
id: z.ZodString & z.ZodType<`evt_${string}`, string, z.core.$ZodTypeInternals<`evt_${string}`, string>>;
|
|
217
|
+
session_id: z.ZodString & z.ZodType<`ses_${string}`, string, z.core.$ZodTypeInternals<`ses_${string}`, string>>;
|
|
218
|
+
occurred_at: z.ZodString;
|
|
219
|
+
source: z.ZodString;
|
|
220
|
+
type: z.ZodLiteral<"session_ended">;
|
|
221
|
+
exit_code: z.ZodOptional<z.ZodNumber>;
|
|
222
|
+
}, z.core.$strip>, z.ZodObject<{
|
|
223
|
+
schema_version: z.ZodLiteral<"0.1.0">;
|
|
224
|
+
id: z.ZodString & z.ZodType<`evt_${string}`, string, z.core.$ZodTypeInternals<`evt_${string}`, string>>;
|
|
225
|
+
session_id: z.ZodString & z.ZodType<`ses_${string}`, string, z.core.$ZodTypeInternals<`ses_${string}`, string>>;
|
|
226
|
+
occurred_at: z.ZodString;
|
|
227
|
+
source: z.ZodString;
|
|
228
|
+
type: z.ZodLiteral<"session_status_changed">;
|
|
229
|
+
from: z.ZodString;
|
|
230
|
+
to: z.ZodString;
|
|
231
|
+
}, z.core.$strip>, z.ZodObject<{
|
|
232
|
+
schema_version: z.ZodLiteral<"0.1.0">;
|
|
233
|
+
id: z.ZodString & z.ZodType<`evt_${string}`, string, z.core.$ZodTypeInternals<`evt_${string}`, string>>;
|
|
234
|
+
session_id: z.ZodString & z.ZodType<`ses_${string}`, string, z.core.$ZodTypeInternals<`ses_${string}`, string>>;
|
|
235
|
+
occurred_at: z.ZodString;
|
|
236
|
+
source: z.ZodString;
|
|
237
|
+
type: z.ZodLiteral<"approval_requested">;
|
|
238
|
+
approval_id: z.ZodString & z.ZodType<`appr_${string}`, string, z.core.$ZodTypeInternals<`appr_${string}`, string>>;
|
|
239
|
+
expires_at: z.ZodDefault<z.ZodNullable<z.ZodString>>;
|
|
240
|
+
risk_level: z.ZodEnum<{
|
|
241
|
+
low: "low";
|
|
242
|
+
medium: "medium";
|
|
243
|
+
high: "high";
|
|
244
|
+
critical: "critical";
|
|
245
|
+
}>;
|
|
246
|
+
action: z.ZodObject<{
|
|
247
|
+
kind: z.ZodString;
|
|
248
|
+
}, z.core.$loose>;
|
|
249
|
+
reason: z.ZodString;
|
|
250
|
+
status: z.ZodLiteral<"pending">;
|
|
251
|
+
}, z.core.$strip>, z.ZodObject<{
|
|
252
|
+
schema_version: z.ZodLiteral<"0.1.0">;
|
|
253
|
+
id: z.ZodString & z.ZodType<`evt_${string}`, string, z.core.$ZodTypeInternals<`evt_${string}`, string>>;
|
|
254
|
+
session_id: z.ZodString & z.ZodType<`ses_${string}`, string, z.core.$ZodTypeInternals<`ses_${string}`, string>>;
|
|
255
|
+
occurred_at: z.ZodString;
|
|
256
|
+
source: z.ZodString;
|
|
257
|
+
type: z.ZodLiteral<"approval_approved">;
|
|
258
|
+
approval_id: z.ZodString & z.ZodType<`appr_${string}`, string, z.core.$ZodTypeInternals<`appr_${string}`, string>>;
|
|
259
|
+
resolver: z.ZodOptional<z.ZodString>;
|
|
260
|
+
note: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
261
|
+
}, z.core.$strip>, z.ZodObject<{
|
|
262
|
+
schema_version: z.ZodLiteral<"0.1.0">;
|
|
263
|
+
id: z.ZodString & z.ZodType<`evt_${string}`, string, z.core.$ZodTypeInternals<`evt_${string}`, string>>;
|
|
264
|
+
session_id: z.ZodString & z.ZodType<`ses_${string}`, string, z.core.$ZodTypeInternals<`ses_${string}`, string>>;
|
|
265
|
+
occurred_at: z.ZodString;
|
|
266
|
+
source: z.ZodString;
|
|
267
|
+
type: z.ZodLiteral<"approval_rejected">;
|
|
268
|
+
approval_id: z.ZodString & z.ZodType<`appr_${string}`, string, z.core.$ZodTypeInternals<`appr_${string}`, string>>;
|
|
269
|
+
resolver: z.ZodOptional<z.ZodString>;
|
|
270
|
+
reason: z.ZodString;
|
|
271
|
+
}, z.core.$strip>, z.ZodObject<{
|
|
272
|
+
schema_version: z.ZodLiteral<"0.1.0">;
|
|
273
|
+
id: z.ZodString & z.ZodType<`evt_${string}`, string, z.core.$ZodTypeInternals<`evt_${string}`, string>>;
|
|
274
|
+
session_id: z.ZodString & z.ZodType<`ses_${string}`, string, z.core.$ZodTypeInternals<`ses_${string}`, string>>;
|
|
275
|
+
occurred_at: z.ZodString;
|
|
276
|
+
source: z.ZodString;
|
|
277
|
+
type: z.ZodLiteral<"approval_expired">;
|
|
278
|
+
approval_id: z.ZodString & z.ZodType<`appr_${string}`, string, z.core.$ZodTypeInternals<`appr_${string}`, string>>;
|
|
279
|
+
}, z.core.$strip>, z.ZodObject<{
|
|
280
|
+
schema_version: z.ZodLiteral<"0.1.0">;
|
|
281
|
+
id: z.ZodString & z.ZodType<`evt_${string}`, string, z.core.$ZodTypeInternals<`evt_${string}`, string>>;
|
|
282
|
+
session_id: z.ZodString & z.ZodType<`ses_${string}`, string, z.core.$ZodTypeInternals<`ses_${string}`, string>>;
|
|
283
|
+
occurred_at: z.ZodString;
|
|
284
|
+
source: z.ZodString;
|
|
285
|
+
type: z.ZodLiteral<"command_executed">;
|
|
286
|
+
command: z.ZodString;
|
|
287
|
+
args: z.ZodArray<z.ZodString>;
|
|
288
|
+
cwd: z.ZodString;
|
|
289
|
+
exit_code: z.ZodNullable<z.ZodNumber>;
|
|
290
|
+
signal: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
291
|
+
received_signal: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
292
|
+
duration_ms: z.ZodNumber;
|
|
293
|
+
}, z.core.$strip>, z.ZodObject<{
|
|
294
|
+
schema_version: z.ZodLiteral<"0.1.0">;
|
|
295
|
+
id: z.ZodString & z.ZodType<`evt_${string}`, string, z.core.$ZodTypeInternals<`evt_${string}`, string>>;
|
|
296
|
+
session_id: z.ZodString & z.ZodType<`ses_${string}`, string, z.core.$ZodTypeInternals<`ses_${string}`, string>>;
|
|
297
|
+
occurred_at: z.ZodString;
|
|
298
|
+
source: z.ZodString;
|
|
299
|
+
type: z.ZodLiteral<"git_snapshot">;
|
|
300
|
+
head: z.ZodString;
|
|
301
|
+
branch: z.ZodString;
|
|
302
|
+
dirty: z.ZodBoolean;
|
|
303
|
+
staged: z.ZodArray<z.ZodString>;
|
|
304
|
+
unstaged: z.ZodArray<z.ZodString>;
|
|
305
|
+
untracked: z.ZodArray<z.ZodString>;
|
|
306
|
+
ahead: z.ZodOptional<z.ZodNumber>;
|
|
307
|
+
behind: z.ZodOptional<z.ZodNumber>;
|
|
308
|
+
}, z.core.$strip>, z.ZodObject<{
|
|
309
|
+
schema_version: z.ZodLiteral<"0.1.0">;
|
|
310
|
+
id: z.ZodString & z.ZodType<`evt_${string}`, string, z.core.$ZodTypeInternals<`evt_${string}`, string>>;
|
|
311
|
+
session_id: z.ZodString & z.ZodType<`ses_${string}`, string, z.core.$ZodTypeInternals<`ses_${string}`, string>>;
|
|
312
|
+
occurred_at: z.ZodString;
|
|
313
|
+
source: z.ZodString;
|
|
314
|
+
type: z.ZodLiteral<"file_changed">;
|
|
315
|
+
path: z.ZodString;
|
|
316
|
+
change_type: z.ZodEnum<{
|
|
317
|
+
added: "added";
|
|
318
|
+
modified: "modified";
|
|
319
|
+
deleted: "deleted";
|
|
320
|
+
renamed: "renamed";
|
|
321
|
+
}>;
|
|
322
|
+
old_path: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
323
|
+
}, z.core.$strip>, z.ZodObject<{
|
|
324
|
+
schema_version: z.ZodLiteral<"0.1.0">;
|
|
325
|
+
id: z.ZodString & z.ZodType<`evt_${string}`, string, z.core.$ZodTypeInternals<`evt_${string}`, string>>;
|
|
326
|
+
session_id: z.ZodString & z.ZodType<`ses_${string}`, string, z.core.$ZodTypeInternals<`ses_${string}`, string>>;
|
|
327
|
+
occurred_at: z.ZodString;
|
|
328
|
+
source: z.ZodString;
|
|
329
|
+
type: z.ZodLiteral<"decision_recorded">;
|
|
330
|
+
decision_id: z.ZodString & z.ZodType<`decision_${string}`, string, z.core.$ZodTypeInternals<`decision_${string}`, string>>;
|
|
291
331
|
title: z.ZodString;
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
332
|
+
rationale: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
333
|
+
alternatives: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
334
|
+
rejected_reason: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
335
|
+
linked_events: z.ZodOptional<z.ZodArray<z.ZodString & z.ZodType<`evt_${string}`, string, z.core.$ZodTypeInternals<`evt_${string}`, string>>>>;
|
|
336
|
+
linked_files: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
337
|
+
}, z.core.$strip>, z.ZodObject<{
|
|
338
|
+
schema_version: z.ZodLiteral<"0.1.0">;
|
|
339
|
+
id: z.ZodString & z.ZodType<`evt_${string}`, string, z.core.$ZodTypeInternals<`evt_${string}`, string>>;
|
|
340
|
+
session_id: z.ZodString & z.ZodType<`ses_${string}`, string, z.core.$ZodTypeInternals<`ses_${string}`, string>>;
|
|
341
|
+
occurred_at: z.ZodString;
|
|
342
|
+
source: z.ZodString;
|
|
343
|
+
type: z.ZodLiteral<"task_created">;
|
|
344
|
+
task_id: z.ZodString & z.ZodType<`task_${string}`, string, z.core.$ZodTypeInternals<`task_${string}`, string>>;
|
|
345
|
+
title: z.ZodString;
|
|
346
|
+
}, z.core.$strip>, z.ZodObject<{
|
|
347
|
+
schema_version: z.ZodLiteral<"0.1.0">;
|
|
348
|
+
id: z.ZodString & z.ZodType<`evt_${string}`, string, z.core.$ZodTypeInternals<`evt_${string}`, string>>;
|
|
349
|
+
session_id: z.ZodString & z.ZodType<`ses_${string}`, string, z.core.$ZodTypeInternals<`ses_${string}`, string>>;
|
|
350
|
+
occurred_at: z.ZodString;
|
|
351
|
+
source: z.ZodString;
|
|
352
|
+
type: z.ZodLiteral<"task_status_changed">;
|
|
353
|
+
task_id: z.ZodString & z.ZodType<`task_${string}`, string, z.core.$ZodTypeInternals<`task_${string}`, string>>;
|
|
354
|
+
from: z.ZodString;
|
|
355
|
+
to: z.ZodString;
|
|
356
|
+
}, z.core.$strip>, z.ZodObject<{
|
|
357
|
+
schema_version: z.ZodLiteral<"0.1.0">;
|
|
358
|
+
id: z.ZodString & z.ZodType<`evt_${string}`, string, z.core.$ZodTypeInternals<`evt_${string}`, string>>;
|
|
359
|
+
session_id: z.ZodString & z.ZodType<`ses_${string}`, string, z.core.$ZodTypeInternals<`ses_${string}`, string>>;
|
|
360
|
+
occurred_at: z.ZodString;
|
|
361
|
+
source: z.ZodString;
|
|
362
|
+
type: z.ZodLiteral<"task_reconciled">;
|
|
363
|
+
task_id: z.ZodString & z.ZodType<`task_${string}`, string, z.core.$ZodTypeInternals<`task_${string}`, string>>;
|
|
364
|
+
removed_created_in_session: z.ZodDefault<z.ZodNullable<z.ZodString & z.ZodType<`ses_${string}`, string, z.core.$ZodTypeInternals<`ses_${string}`, string>>>>;
|
|
365
|
+
created_in_session_replacement: z.ZodDefault<z.ZodNullable<z.ZodString & z.ZodType<`ses_${string}`, string, z.core.$ZodTypeInternals<`ses_${string}`, string>>>>;
|
|
366
|
+
removed_linked_sessions: z.ZodDefault<z.ZodArray<z.ZodString & z.ZodType<`ses_${string}`, string, z.core.$ZodTypeInternals<`ses_${string}`, string>>>>;
|
|
367
|
+
}, z.core.$strict>, z.ZodObject<{
|
|
368
|
+
schema_version: z.ZodLiteral<"0.1.0">;
|
|
369
|
+
id: z.ZodString & z.ZodType<`evt_${string}`, string, z.core.$ZodTypeInternals<`evt_${string}`, string>>;
|
|
370
|
+
session_id: z.ZodString & z.ZodType<`ses_${string}`, string, z.core.$ZodTypeInternals<`ses_${string}`, string>>;
|
|
371
|
+
occurred_at: z.ZodString;
|
|
372
|
+
source: z.ZodString;
|
|
373
|
+
type: z.ZodLiteral<"task_linkage_refreshed">;
|
|
374
|
+
task_id: z.ZodString & z.ZodType<`task_${string}`, string, z.core.$ZodTypeInternals<`task_${string}`, string>>;
|
|
375
|
+
added_linked_sessions: z.ZodDefault<z.ZodArray<z.ZodString & z.ZodType<`ses_${string}`, string, z.core.$ZodTypeInternals<`ses_${string}`, string>>>>;
|
|
376
|
+
removed_linked_sessions: z.ZodDefault<z.ZodArray<z.ZodString & z.ZodType<`ses_${string}`, string, z.core.$ZodTypeInternals<`ses_${string}`, string>>>>;
|
|
377
|
+
final_count: z.ZodOptional<z.ZodNumber>;
|
|
378
|
+
}, z.core.$strict>, z.ZodObject<{
|
|
379
|
+
schema_version: z.ZodLiteral<"0.1.0">;
|
|
380
|
+
id: z.ZodString & z.ZodType<`evt_${string}`, string, z.core.$ZodTypeInternals<`evt_${string}`, string>>;
|
|
381
|
+
session_id: z.ZodString & z.ZodType<`ses_${string}`, string, z.core.$ZodTypeInternals<`ses_${string}`, string>>;
|
|
382
|
+
occurred_at: z.ZodString;
|
|
383
|
+
source: z.ZodString;
|
|
384
|
+
type: z.ZodLiteral<"task_deleted">;
|
|
385
|
+
task_id: z.ZodString & z.ZodType<`task_${string}`, string, z.core.$ZodTypeInternals<`task_${string}`, string>>;
|
|
386
|
+
title: z.ZodString;
|
|
387
|
+
}, z.core.$strict>, z.ZodObject<{
|
|
388
|
+
schema_version: z.ZodLiteral<"0.1.0">;
|
|
389
|
+
id: z.ZodString & z.ZodType<`evt_${string}`, string, z.core.$ZodTypeInternals<`evt_${string}`, string>>;
|
|
390
|
+
session_id: z.ZodString & z.ZodType<`ses_${string}`, string, z.core.$ZodTypeInternals<`ses_${string}`, string>>;
|
|
391
|
+
occurred_at: z.ZodString;
|
|
392
|
+
source: z.ZodString;
|
|
393
|
+
type: z.ZodLiteral<"task_archived">;
|
|
394
|
+
task_id: z.ZodString & z.ZodType<`task_${string}`, string, z.core.$ZodTypeInternals<`task_${string}`, string>>;
|
|
395
|
+
title: z.ZodString;
|
|
396
|
+
}, z.core.$strict>, z.ZodObject<{
|
|
397
|
+
schema_version: z.ZodLiteral<"0.1.0">;
|
|
398
|
+
id: z.ZodString & z.ZodType<`evt_${string}`, string, z.core.$ZodTypeInternals<`evt_${string}`, string>>;
|
|
399
|
+
session_id: z.ZodString & z.ZodType<`ses_${string}`, string, z.core.$ZodTypeInternals<`ses_${string}`, string>>;
|
|
400
|
+
occurred_at: z.ZodString;
|
|
401
|
+
source: z.ZodString;
|
|
402
|
+
type: z.ZodLiteral<"note_added">;
|
|
403
|
+
body: z.ZodString;
|
|
404
|
+
}, z.core.$strip>, z.ZodObject<{
|
|
405
|
+
schema_version: z.ZodLiteral<"0.1.0">;
|
|
406
|
+
id: z.ZodString & z.ZodType<`evt_${string}`, string, z.core.$ZodTypeInternals<`evt_${string}`, string>>;
|
|
407
|
+
session_id: z.ZodString & z.ZodType<`ses_${string}`, string, z.core.$ZodTypeInternals<`ses_${string}`, string>>;
|
|
408
|
+
occurred_at: z.ZodString;
|
|
409
|
+
source: z.ZodString;
|
|
410
|
+
type: z.ZodLiteral<"adapter_output">;
|
|
411
|
+
stream: z.ZodEnum<{
|
|
412
|
+
stdout: "stdout";
|
|
413
|
+
stderr: "stderr";
|
|
298
414
|
}>;
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
/** Inferred runtime type for {@link
|
|
307
|
-
type
|
|
415
|
+
summary: z.ZodString;
|
|
416
|
+
raw_ref: z.ZodString;
|
|
417
|
+
redacted: z.ZodOptional<z.ZodBoolean>;
|
|
418
|
+
}, z.core.$strict>], "type">>;
|
|
419
|
+
}, z.core.$strict>;
|
|
420
|
+
/** Inferred runtime type for {@link SessionImportPayloadSchema}. */
|
|
421
|
+
type SessionImportPayload = z.infer<typeof SessionImportPayloadSchema>;
|
|
422
|
+
/** Inferred runtime type for {@link SessionInnerImportSchema}. */
|
|
423
|
+
type SessionInnerImportInput = z.infer<typeof SessionInnerImportSchema>;
|
|
308
424
|
|
|
309
425
|
/**
|
|
310
|
-
*
|
|
311
|
-
*
|
|
312
|
-
* are atomic-move + in-place rewrites rather than schema-variant swaps.
|
|
426
|
+
* The `source` string stamped on every event derived from a Claude Code
|
|
427
|
+
* native transcript, and the matching session `source.kind`.
|
|
313
428
|
*/
|
|
314
|
-
declare const
|
|
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>;
|
|
429
|
+
declare const CLAUDE_IMPORT_SOURCE = "claude-code-import";
|
|
322
430
|
/**
|
|
323
|
-
*
|
|
431
|
+
* One parsed line of a Claude Code native transcript
|
|
432
|
+
* (`~/.claude/projects/<encoded-cwd>/<uuid>.jsonl`). The shape is the
|
|
433
|
+
* vendor's internal message log, not Basou's event schema, so every field
|
|
434
|
+
* is read defensively — unknown record types and missing fields are skipped
|
|
435
|
+
* rather than rejected (transcripts are an undocumented format that may gain
|
|
436
|
+
* fields between Claude Code releases).
|
|
437
|
+
*/
|
|
438
|
+
type ClaudeTranscriptRecord = Record<string, unknown>;
|
|
439
|
+
/** Options for {@link claudeTranscriptToImportPayload}. */
|
|
440
|
+
type ClaudeTranscriptToPayloadOptions = {
|
|
441
|
+
/** Workspace id of the target Basou workspace (from its manifest). */
|
|
442
|
+
workspaceId: Manifest["workspace"]["id"];
|
|
443
|
+
/**
|
|
444
|
+
* Claude Code session id (transcript filename / `sessionId`). Stored as
|
|
445
|
+
* `session.source.external_id` so re-imports can be deduplicated. Falls
|
|
446
|
+
* back to the `sessionId` read from the records when omitted.
|
|
447
|
+
*/
|
|
448
|
+
externalId?: string;
|
|
449
|
+
};
|
|
450
|
+
/**
|
|
451
|
+
* Transform a Claude Code native transcript into a Basou
|
|
452
|
+
* {@link SessionImportPayload}, ready to hand to `importSessionFromJson`.
|
|
324
453
|
*
|
|
325
|
-
*
|
|
326
|
-
*
|
|
327
|
-
*
|
|
328
|
-
* `status === "rejected"`) are enforced at the CLI orchestration layer
|
|
329
|
-
* rather than here, mirroring the approval event variants in
|
|
330
|
-
* `event.schema.ts`.
|
|
454
|
+
* This is a pure function: no disk or environment access. It DERIVES Basou's
|
|
455
|
+
* provenance-level events from the transcript's message-level records, rather
|
|
456
|
+
* than mapping one-to-one:
|
|
331
457
|
*
|
|
332
|
-
*
|
|
333
|
-
*
|
|
458
|
+
* - `session_started` / `session_ended` from the first / last timestamped record.
|
|
459
|
+
* - `command_executed` from each `Bash` tool use, recorded as `bash -c "<cmd>"`
|
|
460
|
+
* (the transcript carries the shell line, not a parsed argv).
|
|
461
|
+
* - `file_changed` from each `Edit` / `Write` / `NotebookEdit` tool use.
|
|
462
|
+
* - `decision_recorded` from each `AskUserQuestion` tool use: one decision per
|
|
463
|
+
* question, titled `<question> -> <chosen answer>`. The chosen answer is read
|
|
464
|
+
* from the paired result record's structured `toolUseResult.answers` map; a
|
|
465
|
+
* question with no recorded string answer is skipped.
|
|
466
|
+
*
|
|
467
|
+
* Exit codes and per-command durations are not present in the transcript, so
|
|
468
|
+
* `command_executed.exit_code` is `null` and `duration_ms` is `0`.
|
|
469
|
+
*
|
|
470
|
+
* Returns `null` when the transcript has no timestamped records, or no
|
|
471
|
+
* observable command / file / decision action — such sessions carry no
|
|
472
|
+
* provenance worth importing and are skipped by the caller.
|
|
473
|
+
*
|
|
474
|
+
* Event `id` / `session_id` are placeholders; `importSessionFromJson` mints
|
|
475
|
+
* fresh ids on the way in. They are valid-by-construction so the payload
|
|
476
|
+
* still passes `SessionImportPayloadSchema` validation upstream.
|
|
477
|
+
*/
|
|
478
|
+
declare function claudeTranscriptToImportPayload(records: ReadonlyArray<ClaudeTranscriptRecord>, options: ClaudeTranscriptToPayloadOptions): SessionImportPayload | null;
|
|
479
|
+
|
|
480
|
+
/**
|
|
481
|
+
* The `source` string stamped on every event derived from an OpenAI Codex
|
|
482
|
+
* native rollout log, and the matching session `source.kind`.
|
|
483
|
+
*/
|
|
484
|
+
declare const CODEX_IMPORT_SOURCE = "codex-import";
|
|
485
|
+
/**
|
|
486
|
+
* One parsed line of a Codex rollout log
|
|
487
|
+
* (`~/.codex/sessions/<YYYY>/<MM>/<DD>/rollout-*.jsonl`). Each line is an
|
|
488
|
+
* envelope `{ type, timestamp, payload }` where `payload` shape depends on
|
|
489
|
+
* `type`. As with the Claude importer the format is the vendor's internal
|
|
490
|
+
* log, not Basou's schema, so every field is read defensively — unknown
|
|
491
|
+
* record / payload types and missing fields are skipped rather than rejected.
|
|
492
|
+
*/
|
|
493
|
+
type CodexRolloutRecord = Record<string, unknown>;
|
|
494
|
+
/** Options for {@link codexRolloutToImportPayload}. */
|
|
495
|
+
type CodexRolloutToPayloadOptions = {
|
|
496
|
+
/** Workspace id of the target Basou workspace (from its manifest). */
|
|
497
|
+
workspaceId: Manifest["workspace"]["id"];
|
|
498
|
+
/**
|
|
499
|
+
* Codex session id (`session_meta.payload.id`). Stored as
|
|
500
|
+
* `session.source.external_id` so re-imports can be deduplicated. Falls back
|
|
501
|
+
* to the id read from the rollout's `session_meta` record when omitted.
|
|
502
|
+
*/
|
|
503
|
+
externalId?: string;
|
|
504
|
+
};
|
|
505
|
+
/**
|
|
506
|
+
* Transform a Codex native rollout log into a Basou {@link SessionImportPayload},
|
|
507
|
+
* ready to hand to `importSessionFromJson`.
|
|
508
|
+
*
|
|
509
|
+
* This is a pure function: no disk or environment access. It DERIVES Basou's
|
|
510
|
+
* provenance-level events from the rollout's message-level records:
|
|
511
|
+
*
|
|
512
|
+
* - `session_started` / `session_ended` from the first / last timestamped record.
|
|
513
|
+
* - `command_executed` from each `exec_command` function call, recorded as
|
|
514
|
+
* `bash -c "<cmd>"`. The shell line and working directory come from the
|
|
515
|
+
* call's JSON `arguments` (`{ cmd, workdir }`); the exit code and duration
|
|
516
|
+
* are parsed from the paired `function_call_output` (matched by `call_id`),
|
|
517
|
+
* whose text carries `Process exited with code N` and `Wall time: X seconds`.
|
|
518
|
+
*
|
|
519
|
+
* Unlike the Claude importer this derives no `file_changed`: Codex has no
|
|
520
|
+
* dedicated edit tool and applies edits inside `exec_command` (e.g.
|
|
521
|
+
* `apply_patch`), so there is no clean file-change signal to map. Decisions
|
|
522
|
+
* and approvals are likewise not derivable — Codex records an `approval_policy`
|
|
523
|
+
* (a policy, not a per-action approval) and has no structured question/answer
|
|
524
|
+
* record. Both are deferred.
|
|
525
|
+
*
|
|
526
|
+
* Returns `null` when the rollout has no timestamped records or no observable
|
|
527
|
+
* `exec_command` — such sessions carry no provenance worth importing and are
|
|
528
|
+
* skipped by the caller.
|
|
529
|
+
*
|
|
530
|
+
* Event `id` / `session_id` are placeholders; `importSessionFromJson` mints
|
|
531
|
+
* fresh ids on the way in. They are valid-by-construction so the payload still
|
|
532
|
+
* passes `SessionImportPayloadSchema` validation upstream.
|
|
533
|
+
*/
|
|
534
|
+
declare function codexRolloutToImportPayload(records: ReadonlyArray<CodexRolloutRecord>, options: CodexRolloutToPayloadOptions): SessionImportPayload | null;
|
|
535
|
+
|
|
536
|
+
/**
|
|
537
|
+
* Lifecycle states of a Basou approval. The status is stored directly on
|
|
538
|
+
* the approval YAML (flat shape) so that pending → resolved transitions
|
|
539
|
+
* are atomic-move + in-place rewrites rather than schema-variant swaps.
|
|
540
|
+
*/
|
|
541
|
+
declare const ApprovalStatusSchema: z.ZodEnum<{
|
|
542
|
+
pending: "pending";
|
|
543
|
+
approved: "approved";
|
|
544
|
+
rejected: "rejected";
|
|
545
|
+
expired: "expired";
|
|
546
|
+
}>;
|
|
547
|
+
/** Inferred runtime type for {@link ApprovalStatusSchema}. */
|
|
548
|
+
type ApprovalStatus = z.infer<typeof ApprovalStatusSchema>;
|
|
549
|
+
/**
|
|
550
|
+
* Schema for `.basou/approvals/{pending,resolved}/<approval_id>.yaml`.
|
|
551
|
+
*
|
|
552
|
+
* The schema is intentionally flat (one shape regardless of `status`) so
|
|
553
|
+
* that pending and resolved YAMLs share the same parser. Required vs.
|
|
554
|
+
* optional semantics by status (e.g. `rejection_reason` MUST be set when
|
|
555
|
+
* `status === "rejected"`) are enforced at the CLI orchestration layer
|
|
556
|
+
* rather than here, mirroring the approval event variants in
|
|
557
|
+
* `event.schema.ts`.
|
|
558
|
+
*
|
|
559
|
+
* The `action` field is `{ kind: string }` with `passthrough()` so that
|
|
560
|
+
* adapter-defined keys (e.g. `command`, `path`, `target_url`) survive the
|
|
334
561
|
* round-trip without being stripped — matching the approval_requested
|
|
335
562
|
* event variant.
|
|
336
563
|
*/
|
|
@@ -364,6 +591,107 @@ declare const ApprovalSchema: z.ZodObject<{
|
|
|
364
591
|
/** Inferred runtime type for {@link ApprovalSchema}. */
|
|
365
592
|
type Approval = z.infer<typeof ApprovalSchema>;
|
|
366
593
|
|
|
594
|
+
/**
|
|
595
|
+
* Absolute paths to the standard `.basou/` directory layout, derived from a
|
|
596
|
+
* given repository root. The shape mirrors the canonical `.basou/` tree
|
|
597
|
+
* (see `docs/spec/workspace.md`). `root` is the `.basou/` directory itself
|
|
598
|
+
* (i.e. `repositoryRoot/.basou`).
|
|
599
|
+
*
|
|
600
|
+
* `files` exposes the well-known top-level files inside `.basou/`. Each path
|
|
601
|
+
* is computed but not created — they are written by their respective
|
|
602
|
+
* subsystems (e.g. `writeManifest` for `manifest.yaml`).
|
|
603
|
+
*
|
|
604
|
+
* All fields are deeply readonly; consumers must not mutate the returned
|
|
605
|
+
* object.
|
|
606
|
+
*/
|
|
607
|
+
type BasouPaths = {
|
|
608
|
+
readonly root: string;
|
|
609
|
+
readonly sessions: string;
|
|
610
|
+
readonly tasks: string;
|
|
611
|
+
readonly approvals: {
|
|
612
|
+
readonly pending: string;
|
|
613
|
+
readonly resolved: string;
|
|
614
|
+
};
|
|
615
|
+
readonly locks: string;
|
|
616
|
+
readonly logs: string;
|
|
617
|
+
readonly raw: string;
|
|
618
|
+
readonly tmp: string;
|
|
619
|
+
readonly files: {
|
|
620
|
+
readonly manifest: string;
|
|
621
|
+
readonly status: string;
|
|
622
|
+
readonly handoff: string;
|
|
623
|
+
readonly decisions: string;
|
|
624
|
+
};
|
|
625
|
+
};
|
|
626
|
+
/**
|
|
627
|
+
* Compute absolute paths to the standard `.basou/` directory layout under
|
|
628
|
+
* `repositoryRoot`. Pure: performs no I/O and is safe to call before the
|
|
629
|
+
* directory exists.
|
|
630
|
+
*
|
|
631
|
+
* @param repositoryRoot Absolute path to the git repository root (the
|
|
632
|
+
* parent directory of `.basou/`). Caller is responsible for resolving
|
|
633
|
+
* `process.cwd()` or running `git rev-parse --show-toplevel` upstream;
|
|
634
|
+
* this function does not validate that the path exists or is a git
|
|
635
|
+
* repository.
|
|
636
|
+
*/
|
|
637
|
+
declare function basouPaths(repositoryRoot: string): BasouPaths;
|
|
638
|
+
/**
|
|
639
|
+
* Create the standard `.basou/` directory layout under `repositoryRoot`.
|
|
640
|
+
*
|
|
641
|
+
* Idempotent: a no-op on an already-initialized layout. Returns the resolved
|
|
642
|
+
* {@link BasouPaths} so callers can immediately use them.
|
|
643
|
+
*
|
|
644
|
+
* Throws if `repositoryRoot/.basou` (or any required subdirectory) exists
|
|
645
|
+
* but is not a directory, or if filesystem permissions prevent creation.
|
|
646
|
+
* All thrown error messages are pathless; the original native error is
|
|
647
|
+
* attached as `cause` for diagnostics.
|
|
648
|
+
*
|
|
649
|
+
* @param repositoryRoot Absolute path to the git repository root. See
|
|
650
|
+
* {@link basouPaths} for the contract on this parameter.
|
|
651
|
+
*/
|
|
652
|
+
declare function ensureBasouDirectory(repositoryRoot: string): Promise<BasouPaths>;
|
|
653
|
+
|
|
654
|
+
/** Which side of `.basou/approvals/` an approval YAML lives on. */
|
|
655
|
+
type ApprovalLocation = "pending" | "resolved";
|
|
656
|
+
/** Result returned by {@link loadApproval}: the parsed approval and where it was found. */
|
|
657
|
+
type LoadedApproval = {
|
|
658
|
+
approval: Approval;
|
|
659
|
+
location: ApprovalLocation;
|
|
660
|
+
};
|
|
661
|
+
/**
|
|
662
|
+
* Locate and load the approval YAML for `approvalId`. Searches resolved
|
|
663
|
+
* first so that a duplicated YAML (the crash-window scenario where both
|
|
664
|
+
* pending and resolved exist for the same id) returns the resolved-side
|
|
665
|
+
* record — matching the dedupe rule used by `approval list` and
|
|
666
|
+
* `resolveApprovalId`. Returns null if neither directory contains the
|
|
667
|
+
* YAML. Throws with a pathless message on read or schema-validation
|
|
668
|
+
* failure.
|
|
669
|
+
*/
|
|
670
|
+
declare function loadApproval(paths: BasouPaths, approvalId: string): Promise<LoadedApproval | null>;
|
|
671
|
+
/**
|
|
672
|
+
* Enumerate approval IDs by inspecting `<id>.yaml` filenames in pending
|
|
673
|
+
* and resolved. ENOENT on either directory is treated as empty (e.g. a
|
|
674
|
+
* workspace that has no resolved approvals yet). YAML parse and schema
|
|
675
|
+
* validation are NOT performed; callers that need the parsed approval
|
|
676
|
+
* should use {@link loadApproval} per ID.
|
|
677
|
+
*/
|
|
678
|
+
declare function enumerateApprovals(paths: BasouPaths): Promise<{
|
|
679
|
+
pending: string[];
|
|
680
|
+
resolved: string[];
|
|
681
|
+
}>;
|
|
682
|
+
/**
|
|
683
|
+
* Return true when an approval is in `pending` state and its `expires_at`
|
|
684
|
+
* timestamp has elapsed. Used by `basou approval list` / `show` to surface
|
|
685
|
+
* a `(expired)` label without mutating the YAML file. Approval expiry uses
|
|
686
|
+
* lazy-evaluation semantics; actual `approval_expired` event firing is
|
|
687
|
+
* deferred to a later step.
|
|
688
|
+
*
|
|
689
|
+
* `now` is taken as a parameter so a single CLI invocation can share one
|
|
690
|
+
* "now" across every record it inspects (avoids boundary races where two
|
|
691
|
+
* reads of `Date.now()` straddle an expiry instant).
|
|
692
|
+
*/
|
|
693
|
+
declare function isLazyExpired(approval: Approval, now: Date): boolean;
|
|
694
|
+
|
|
367
695
|
declare const SessionStartedEventSchema: z.ZodObject<{
|
|
368
696
|
schema_version: z.ZodLiteral<"0.1.0">;
|
|
369
697
|
id: z.ZodString & z.ZodType<`evt_${string}`, string, z.core.$ZodTypeInternals<`evt_${string}`, string>>;
|
|
@@ -846,602 +1174,11 @@ type TaskLinkageRefreshedEvent = z.infer<typeof TaskLinkageRefreshedEventSchema>
|
|
|
846
1174
|
/** Narrowed runtime type for the `task_deleted` event variant (.strict()). */
|
|
847
1175
|
type TaskDeletedEvent = z.infer<typeof TaskDeletedEventSchema>;
|
|
848
1176
|
/** Narrowed runtime type for the `task_archived` event variant (.strict()). */
|
|
849
|
-
type TaskArchivedEvent = z.infer<typeof TaskArchivedEventSchema>;
|
|
850
|
-
/** Narrowed runtime type for the `note_added` event variant. */
|
|
851
|
-
type NoteAddedEvent = z.infer<typeof NoteAddedEventSchema>;
|
|
852
|
-
/** Narrowed runtime type for the `adapter_output` event variant (.strict()). */
|
|
853
|
-
type AdapterOutputEvent = z.infer<typeof AdapterOutputEventSchema>;
|
|
854
|
-
|
|
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
|
-
/**
|
|
892
|
-
* Schema for the round-trip JSON payload accepted by `basou session import
|
|
893
|
-
* --format json`. The top level is `.strict()`; unknown keys at the outer
|
|
894
|
-
* envelope are rejected.
|
|
895
|
-
*/
|
|
896
|
-
declare const SessionImportPayloadSchema: z.ZodObject<{
|
|
897
|
-
schema_version: z.ZodString;
|
|
898
|
-
session: z.ZodObject<{
|
|
899
|
-
id: z.ZodOptional<z.ZodString & z.ZodType<`ses_${string}`, string, z.core.$ZodTypeInternals<`ses_${string}`, string>>>;
|
|
900
|
-
label: z.ZodOptional<z.ZodString>;
|
|
901
|
-
task_id: z.ZodOptional<z.ZodNullable<z.ZodString & z.ZodType<`task_${string}`, string, z.core.$ZodTypeInternals<`task_${string}`, string>>>>;
|
|
902
|
-
workspace_id: z.ZodString & z.ZodType<`ws_${string}`, string, z.core.$ZodTypeInternals<`ws_${string}`, string>>;
|
|
903
|
-
source: z.ZodObject<{
|
|
904
|
-
kind: z.ZodEnum<{
|
|
905
|
-
"claude-code-adapter": "claude-code-adapter";
|
|
906
|
-
human: "human";
|
|
907
|
-
import: "import";
|
|
908
|
-
terminal: "terminal";
|
|
909
|
-
}>;
|
|
910
|
-
version: z.ZodLiteral<"0.1.0">;
|
|
911
|
-
}, z.core.$strip>;
|
|
912
|
-
started_at: z.ZodString;
|
|
913
|
-
ended_at: z.ZodOptional<z.ZodString>;
|
|
914
|
-
status: z.ZodEnum<{
|
|
915
|
-
initialized: "initialized";
|
|
916
|
-
running: "running";
|
|
917
|
-
waiting_approval: "waiting_approval";
|
|
918
|
-
completed: "completed";
|
|
919
|
-
failed: "failed";
|
|
920
|
-
interrupted: "interrupted";
|
|
921
|
-
imported: "imported";
|
|
922
|
-
archived: "archived";
|
|
923
|
-
}>;
|
|
924
|
-
working_directory: z.ZodString;
|
|
925
|
-
invocation: z.ZodObject<{
|
|
926
|
-
command: z.ZodString;
|
|
927
|
-
args: z.ZodArray<z.ZodString>;
|
|
928
|
-
exit_code: z.ZodNullable<z.ZodNumber>;
|
|
929
|
-
}, z.core.$strip>;
|
|
930
|
-
related_files: z.ZodDefault<z.ZodArray<z.ZodString>>;
|
|
931
|
-
events_log: z.ZodOptional<z.ZodString>;
|
|
932
|
-
summary: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
933
|
-
}, z.core.$strict>;
|
|
934
|
-
events: z.ZodArray<z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
935
|
-
schema_version: z.ZodLiteral<"0.1.0">;
|
|
936
|
-
id: z.ZodString & z.ZodType<`evt_${string}`, string, z.core.$ZodTypeInternals<`evt_${string}`, string>>;
|
|
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>;
|
|
1151
|
-
|
|
1152
|
-
/**
|
|
1153
|
-
* Absolute paths to the standard `.basou/` directory layout, derived from a
|
|
1154
|
-
* given repository root. The shape mirrors the canonical `.basou/` tree
|
|
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`).
|
|
1161
|
-
*
|
|
1162
|
-
* All fields are deeply readonly; consumers must not mutate the returned
|
|
1163
|
-
* object.
|
|
1164
|
-
*/
|
|
1165
|
-
type BasouPaths = {
|
|
1166
|
-
readonly root: string;
|
|
1167
|
-
readonly sessions: string;
|
|
1168
|
-
readonly tasks: string;
|
|
1169
|
-
readonly approvals: {
|
|
1170
|
-
readonly pending: string;
|
|
1171
|
-
readonly resolved: string;
|
|
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
|
-
};
|
|
1183
|
-
};
|
|
1184
|
-
/**
|
|
1185
|
-
* Compute absolute paths to the standard `.basou/` directory layout under
|
|
1186
|
-
* `repositoryRoot`. Pure: performs no I/O and is safe to call before the
|
|
1187
|
-
* directory exists.
|
|
1188
|
-
*
|
|
1189
|
-
* @param repositoryRoot Absolute path to the git repository root (the
|
|
1190
|
-
* parent directory of `.basou/`). Caller is responsible for resolving
|
|
1191
|
-
* `process.cwd()` or running `git rev-parse --show-toplevel` upstream;
|
|
1192
|
-
* this function does not validate that the path exists or is a git
|
|
1193
|
-
* repository.
|
|
1194
|
-
*/
|
|
1195
|
-
declare function basouPaths(repositoryRoot: string): BasouPaths;
|
|
1196
|
-
/**
|
|
1197
|
-
* Create the standard `.basou/` directory layout under `repositoryRoot`.
|
|
1198
|
-
*
|
|
1199
|
-
* Idempotent: a no-op on an already-initialized layout. Returns the resolved
|
|
1200
|
-
* {@link BasouPaths} so callers can immediately use them.
|
|
1201
|
-
*
|
|
1202
|
-
* Throws if `repositoryRoot/.basou` (or any required subdirectory) exists
|
|
1203
|
-
* but is not a directory, or if filesystem permissions prevent creation.
|
|
1204
|
-
* All thrown error messages are pathless; the original native error is
|
|
1205
|
-
* attached as `cause` for diagnostics.
|
|
1206
|
-
*
|
|
1207
|
-
* @param repositoryRoot Absolute path to the git repository root. See
|
|
1208
|
-
* {@link basouPaths} for the contract on this parameter.
|
|
1209
|
-
*/
|
|
1210
|
-
declare function ensureBasouDirectory(repositoryRoot: string): Promise<BasouPaths>;
|
|
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
|
-
};
|
|
1219
|
-
/**
|
|
1220
|
-
* Locate and load the approval YAML for `approvalId`. Searches resolved
|
|
1221
|
-
* first so that a duplicated YAML (the crash-window scenario where both
|
|
1222
|
-
* pending and resolved exist for the same id) returns the resolved-side
|
|
1223
|
-
* record — matching the dedupe rule used by `approval list` and
|
|
1224
|
-
* `resolveApprovalId`. Returns null if neither directory contains the
|
|
1225
|
-
* YAML. Throws with a pathless message on read or schema-validation
|
|
1226
|
-
* failure.
|
|
1227
|
-
*/
|
|
1228
|
-
declare function loadApproval(paths: BasouPaths, approvalId: string): Promise<LoadedApproval | null>;
|
|
1229
|
-
/**
|
|
1230
|
-
* Enumerate approval IDs by inspecting `<id>.yaml` filenames in pending
|
|
1231
|
-
* and resolved. ENOENT on either directory is treated as empty (e.g. a
|
|
1232
|
-
* workspace that has no resolved approvals yet). YAML parse and schema
|
|
1233
|
-
* validation are NOT performed; callers that need the parsed approval
|
|
1234
|
-
* should use {@link loadApproval} per ID.
|
|
1235
|
-
*/
|
|
1236
|
-
declare function enumerateApprovals(paths: BasouPaths): Promise<{
|
|
1237
|
-
pending: string[];
|
|
1238
|
-
resolved: string[];
|
|
1239
|
-
}>;
|
|
1240
|
-
/**
|
|
1241
|
-
* Return true when an approval is in `pending` state and its `expires_at`
|
|
1242
|
-
* timestamp has elapsed. Used by `basou approval list` / `show` to surface
|
|
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.
|
|
1246
|
-
*
|
|
1247
|
-
* `now` is taken as a parameter so a single CLI invocation can share one
|
|
1248
|
-
* "now" across every record it inspects (avoids boundary races where two
|
|
1249
|
-
* reads of `Date.now()` straddle an expiry instant).
|
|
1250
|
-
*/
|
|
1251
|
-
declare function isLazyExpired(approval: Approval, now: Date): boolean;
|
|
1252
|
-
|
|
1253
|
-
/**
|
|
1254
|
-
* Read a YAML file as `unknown`. Caller MUST validate via a zod schema.
|
|
1255
|
-
*
|
|
1256
|
-
* Throws Error with pathless message and the original native error attached
|
|
1257
|
-
* as `cause` for I/O failures and YAML parse errors. All fs and parse exits
|
|
1258
|
-
* go through fixed messages so absolute paths cannot leak via `error.message`.
|
|
1259
|
-
*/
|
|
1260
|
-
declare function readYamlFile(filePath: string): Promise<unknown>;
|
|
1261
|
-
/**
|
|
1262
|
-
* Write a value as YAML using {@link atomicReplace} for crash-resistant
|
|
1263
|
-
* atomicity. The shared helper handles the tmp-file + rename sequence,
|
|
1264
|
-
* `wx` collision guard, and best-effort tmp cleanup on failure. This
|
|
1265
|
-
* wrapper adds the YAML serialisation and the pathless error vocabulary.
|
|
1266
|
-
*/
|
|
1267
|
-
declare function writeYamlFile(filePath: string, value: unknown): Promise<void>;
|
|
1268
|
-
/**
|
|
1269
|
-
* Atomically create a new YAML file. Like {@link writeYamlFile} but
|
|
1270
|
-
* delegates to {@link atomicCreate} so a pre-existing target fails with
|
|
1271
|
-
* EEXIST instead of being silently overwritten.
|
|
1272
|
-
*
|
|
1273
|
-
* Used by `basou approval approve` / `reject` to write the resolved-side
|
|
1274
|
-
* YAML, so a concurrent resolver cannot overwrite an already-resolved
|
|
1275
|
-
* approval.
|
|
1276
|
-
*
|
|
1277
|
-
* Throws `Error("Failed to write YAML file", { cause })` on failure; if
|
|
1278
|
-
* `cause.code === "EEXIST"` the caller can detect a target-exists race.
|
|
1279
|
-
*/
|
|
1280
|
-
declare function linkYamlFile(filePath: string, value: unknown): Promise<void>;
|
|
1281
|
-
/**
|
|
1282
|
-
* Overwrite an existing YAML file atomically. Like {@link writeYamlFile}
|
|
1283
|
-
* but with a distinct pathless message label, used for files that
|
|
1284
|
-
* legitimately need in-place mutation (e.g. session.yaml's status /
|
|
1285
|
-
* ended_at lifecycle updates).
|
|
1286
|
-
*/
|
|
1287
|
-
declare function overwriteYamlFile(filePath: string, value: unknown): Promise<void>;
|
|
1288
|
-
|
|
1289
|
-
/**
|
|
1290
|
-
* Inputs for {@link createManifest}. Optional fields drop out of the
|
|
1291
|
-
* resulting Manifest entirely (they are not emitted as `null`/`undefined`
|
|
1292
|
-
* in YAML); pass `null` for `repositoryUrl` to keep an explicit `null`.
|
|
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`.
|
|
1308
|
-
*/
|
|
1309
|
-
declare function createManifest(input: CreateManifestInput): Manifest;
|
|
1310
|
-
/**
|
|
1311
|
-
* Write a Manifest to `paths.files.manifest`. Re-validates via
|
|
1312
|
-
* `ManifestSchema` before serialization.
|
|
1313
|
-
*
|
|
1314
|
-
* Refuses to overwrite an existing manifest unless `force: true`.
|
|
1315
|
-
*/
|
|
1316
|
-
declare function writeManifest(paths: BasouPaths, manifest: Manifest, options?: {
|
|
1317
|
-
force?: boolean;
|
|
1318
|
-
}): Promise<void>;
|
|
1319
|
-
/**
|
|
1320
|
-
* Read and parse a Manifest from `paths.files.manifest`. Throws if the file
|
|
1321
|
-
* is missing or contents fail `ManifestSchema` validation.
|
|
1322
|
-
*/
|
|
1323
|
-
declare function readManifest(paths: BasouPaths): Promise<Manifest>;
|
|
1324
|
-
|
|
1325
|
-
type AppendBasouGitignoreResult = {
|
|
1326
|
-
/** True if the block was appended (or the file was newly created). */
|
|
1327
|
-
readonly appended: boolean;
|
|
1328
|
-
};
|
|
1329
|
-
/**
|
|
1330
|
-
* Append Basou's default `.gitignore` block to `repositoryRoot/.gitignore`.
|
|
1331
|
-
*
|
|
1332
|
-
* The block contents are derived from the Basou v0.1 specification (the
|
|
1333
|
-
* standard ignore + commit recommendations). Callers must pass an absolute
|
|
1334
|
-
* path to a Git repository root.
|
|
1335
|
-
*
|
|
1336
|
-
* Behavior:
|
|
1337
|
-
* - If `.gitignore` does not exist, it is created with the Basou block.
|
|
1338
|
-
* - If a line starting with `# Basou - default ignore` is already present,
|
|
1339
|
-
* the file is left untouched and `appended: false` is returned
|
|
1340
|
-
* (idempotent).
|
|
1341
|
-
* - If `.gitignore` is a symlink, the link is followed and the target file
|
|
1342
|
-
* is updated. Symlinks are not rejected.
|
|
1343
|
-
*
|
|
1344
|
-
* On I/O failure throws Error with a pathless message
|
|
1345
|
-
* (`Failed to read .gitignore` / `Failed to write .gitignore`) and the
|
|
1346
|
-
* original native error attached as `cause`.
|
|
1347
|
-
*/
|
|
1348
|
-
declare function appendBasouGitignore(repositoryRoot: string): Promise<AppendBasouGitignoreResult>;
|
|
1349
|
-
|
|
1350
|
-
/**
|
|
1351
|
-
* The two lock scopes basou uses. `task` guards the read-modify-write window
|
|
1352
|
-
* around a single `task.md`; `session` guards the events.jsonl append plus
|
|
1353
|
-
* surrounding `session.yaml` mutation for a single session. Two scopes use
|
|
1354
|
-
* different lockfile names so they never collide on disk.
|
|
1355
|
-
*/
|
|
1356
|
-
type LockScope = "task" | "session";
|
|
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
|
-
};
|
|
1365
|
-
/**
|
|
1366
|
-
* Acquire an advisory lock at `<paths.locks>/<scope>_<id>.lock` for the
|
|
1367
|
-
* lifetime of the returned handle. Lockfile body records the holder's pid
|
|
1368
|
-
* and acquire timestamp so a competitor can detect stale locks left by a
|
|
1369
|
-
* SIGINT'd CLI run and recover automatically.
|
|
1370
|
-
*
|
|
1371
|
-
* Acquisition strategy:
|
|
1372
|
-
* 1. {@link atomicCreate} the lockfile (POSIX link(2) + EEXIST).
|
|
1373
|
-
* 2. On EEXIST, probe the existing lockfile via {@link isStaleLock}.
|
|
1374
|
-
* - If stale (= holder pid is dead or lock is older than
|
|
1375
|
-
* {@link STALE_LOCK_MAX_AGE_MS}), `unlink` the stale file and retry
|
|
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.
|
|
1381
|
-
*
|
|
1382
|
-
* The caller MUST call `release()` (typically from a `finally` block); the
|
|
1383
|
-
* `process.exit()` path or a fatal crash relies on stale-lock detection on
|
|
1384
|
-
* the next acquire to recover.
|
|
1385
|
-
*/
|
|
1386
|
-
declare function acquireLock(paths: BasouPaths, scope: LockScope, resourceId: string): Promise<LockHandle>;
|
|
1387
|
-
|
|
1388
|
-
/**
|
|
1389
|
-
* Walk the cause chain (up to `depth` levels) looking for an Error whose
|
|
1390
|
-
* errno-style `code` matches `code`. Returns true on the first match.
|
|
1391
|
-
* Resilient to wrapper depth changes so that ENOENT detection survives
|
|
1392
|
-
* future error-wrapping refactors.
|
|
1393
|
-
*/
|
|
1394
|
-
declare function findErrorCode(error: unknown, code: string, depth?: number): boolean;
|
|
1395
|
-
|
|
1396
|
-
/**
|
|
1397
|
-
* Refuse to operate on `.basou` if it is a symlink or not a directory. This
|
|
1398
|
-
* prevents `writeStatus` from being tricked into writing `status.json`
|
|
1399
|
-
* outside the repository root via a swapped `.basou` symlink. Mirrors
|
|
1400
|
-
* `ensureBasouDirectory`'s lstat-based guard.
|
|
1401
|
-
*
|
|
1402
|
-
* If `.basou` is absent the underlying ENOENT is propagated (wrapped) so
|
|
1403
|
-
* callers can map it to "workspace not initialized" via `findErrorCode`.
|
|
1404
|
-
*
|
|
1405
|
-
* Note: this is a baseline safety net, not a TOCTOU fix — the directory
|
|
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.
|
|
1409
|
-
*/
|
|
1410
|
-
declare function assertBasouRootSafe(rootPath: string): Promise<void>;
|
|
1411
|
-
/**
|
|
1412
|
-
* Build a StatusSnapshot from a manifest plus the path layout, observing
|
|
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.
|
|
1416
|
-
*
|
|
1417
|
-
* @param input.now Override for testing; defaults to `new Date()`.
|
|
1418
|
-
*/
|
|
1419
|
-
declare function buildStatusSnapshot(input: {
|
|
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>;
|
|
1438
|
-
/**
|
|
1439
|
-
* Read `.basou/status.json` for the current schema_version (0.1.0). This
|
|
1440
|
-
* is a cache reader only; cross-version migration is not supported here.
|
|
1441
|
-
* Older or newer status.json shapes will fail `StatusSchema.parse` —
|
|
1442
|
-
* callers regenerate by calling `buildStatusSnapshot` + `writeStatus`.
|
|
1443
|
-
*/
|
|
1444
|
-
declare function readStatus(paths: BasouPaths): Promise<StatusSnapshot>;
|
|
1177
|
+
type TaskArchivedEvent = z.infer<typeof TaskArchivedEventSchema>;
|
|
1178
|
+
/** Narrowed runtime type for the `note_added` event variant. */
|
|
1179
|
+
type NoteAddedEvent = z.infer<typeof NoteAddedEventSchema>;
|
|
1180
|
+
/** Narrowed runtime type for the `adapter_output` event variant (.strict()). */
|
|
1181
|
+
type AdapterOutputEvent = z.infer<typeof AdapterOutputEventSchema>;
|
|
1445
1182
|
|
|
1446
1183
|
/**
|
|
1447
1184
|
* Recoverable warning surfaced via {@link ReplayOptions.onWarning}. The replay
|
|
@@ -1499,6 +1236,137 @@ declare function replayEvents(sessionDir: string, options?: ReplayOptions): Asyn
|
|
|
1499
1236
|
*/
|
|
1500
1237
|
declare function readAllEvents(sessionDir: string, options?: ReplayOptions): Promise<Event[]>;
|
|
1501
1238
|
|
|
1239
|
+
/** Session lifecycle states. */
|
|
1240
|
+
declare const SessionStatusSchema: z.ZodEnum<{
|
|
1241
|
+
initialized: "initialized";
|
|
1242
|
+
running: "running";
|
|
1243
|
+
waiting_approval: "waiting_approval";
|
|
1244
|
+
completed: "completed";
|
|
1245
|
+
failed: "failed";
|
|
1246
|
+
interrupted: "interrupted";
|
|
1247
|
+
imported: "imported";
|
|
1248
|
+
archived: "archived";
|
|
1249
|
+
}>;
|
|
1250
|
+
/** Inferred runtime type for {@link SessionStatusSchema}. */
|
|
1251
|
+
type SessionStatus = z.infer<typeof SessionStatusSchema>;
|
|
1252
|
+
/**
|
|
1253
|
+
* Source kind that produced the session.
|
|
1254
|
+
*
|
|
1255
|
+
* - `claude-code-adapter` — a live `basou run claude-code` process wrap.
|
|
1256
|
+
* - `claude-code-import` — derived after the fact from a Claude Code native
|
|
1257
|
+
* transcript (`~/.claude/projects/*.jsonl`) by `basou import claude-code`.
|
|
1258
|
+
* - `codex-import` — derived after the fact from an OpenAI Codex native
|
|
1259
|
+
* rollout log (date-partitioned `~/.codex/sessions`) by `basou import codex`.
|
|
1260
|
+
* - `import` — a round-trip of a Basou-format export (`basou session import`).
|
|
1261
|
+
* - `human` / `terminal` — manually-authored / terminal-recorded sessions.
|
|
1262
|
+
*/
|
|
1263
|
+
declare const SessionSourceKindSchema: z.ZodEnum<{
|
|
1264
|
+
"claude-code-adapter": "claude-code-adapter";
|
|
1265
|
+
"claude-code-import": "claude-code-import";
|
|
1266
|
+
"codex-import": "codex-import";
|
|
1267
|
+
human: "human";
|
|
1268
|
+
import: "import";
|
|
1269
|
+
terminal: "terminal";
|
|
1270
|
+
}>;
|
|
1271
|
+
/** Inferred runtime type for {@link SessionSourceKindSchema}. */
|
|
1272
|
+
type SessionSourceKind = z.infer<typeof SessionSourceKindSchema>;
|
|
1273
|
+
/**
|
|
1274
|
+
* Optional per-session metrics, computed at import time from the source tool's
|
|
1275
|
+
* native log. Two groups, both optional because not every source records them:
|
|
1276
|
+
*
|
|
1277
|
+
* - Model-usage rollup (`*_tokens`): the transcript carries per-message token
|
|
1278
|
+
* usage; these are the session totals. `reasoning_output_tokens` is
|
|
1279
|
+
* Codex-only, and live `run`/`exec` sessions carry no token usage at all.
|
|
1280
|
+
* - Engaged-time metrics (`active_*`): the billing-oriented active time derived
|
|
1281
|
+
* from the session's genuine engagement timestamps (conversation turns plus
|
|
1282
|
+
* action events), with idle gaps capped. `active_intervals` are the merged
|
|
1283
|
+
* wall-clock ranges (so cross-session totals can de-duplicate overlapping
|
|
1284
|
+
* work by interval union); `active_time_ms` is their summed duration;
|
|
1285
|
+
* `active_gap_cap_ms` and `active_time_method` lock the methodology so the
|
|
1286
|
+
* stored numbers stay interpretable if the method changes later.
|
|
1287
|
+
*
|
|
1288
|
+
* Absent on sessions imported before a given field existed (re-import to
|
|
1289
|
+
* backfill). Live sessions carry no engaged-time metrics and fall back to
|
|
1290
|
+
* event-derived active time at stats time.
|
|
1291
|
+
*/
|
|
1292
|
+
declare const SessionMetricsSchema: z.ZodObject<{
|
|
1293
|
+
output_tokens: z.ZodOptional<z.ZodNumber>;
|
|
1294
|
+
input_tokens: z.ZodOptional<z.ZodNumber>;
|
|
1295
|
+
cached_input_tokens: z.ZodOptional<z.ZodNumber>;
|
|
1296
|
+
reasoning_output_tokens: z.ZodOptional<z.ZodNumber>;
|
|
1297
|
+
active_time_ms: z.ZodOptional<z.ZodNumber>;
|
|
1298
|
+
active_intervals: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
1299
|
+
start: z.ZodString;
|
|
1300
|
+
end: z.ZodString;
|
|
1301
|
+
}, z.core.$strip>>>;
|
|
1302
|
+
active_gap_cap_ms: z.ZodOptional<z.ZodNumber>;
|
|
1303
|
+
active_time_method: z.ZodOptional<z.ZodString>;
|
|
1304
|
+
}, z.core.$strip>;
|
|
1305
|
+
/** Inferred runtime type for {@link SessionMetricsSchema}. */
|
|
1306
|
+
type SessionMetrics = z.infer<typeof SessionMetricsSchema>;
|
|
1307
|
+
/**
|
|
1308
|
+
* Schema for `.basou/sessions/<session_id>/session.yaml`. The minimal
|
|
1309
|
+
* session document carries the actual fields nested under the outer
|
|
1310
|
+
* `session:` key.
|
|
1311
|
+
*/
|
|
1312
|
+
declare const SessionSchema: z.ZodObject<{
|
|
1313
|
+
schema_version: z.ZodLiteral<"0.1.0">;
|
|
1314
|
+
session: z.ZodObject<{
|
|
1315
|
+
id: z.ZodString & z.ZodType<`ses_${string}`, string, z.core.$ZodTypeInternals<`ses_${string}`, string>>;
|
|
1316
|
+
label: z.ZodOptional<z.ZodString>;
|
|
1317
|
+
task_id: z.ZodOptional<z.ZodNullable<z.ZodString & z.ZodType<`task_${string}`, string, z.core.$ZodTypeInternals<`task_${string}`, string>>>>;
|
|
1318
|
+
workspace_id: z.ZodString & z.ZodType<`ws_${string}`, string, z.core.$ZodTypeInternals<`ws_${string}`, string>>;
|
|
1319
|
+
source: z.ZodObject<{
|
|
1320
|
+
kind: z.ZodEnum<{
|
|
1321
|
+
"claude-code-adapter": "claude-code-adapter";
|
|
1322
|
+
"claude-code-import": "claude-code-import";
|
|
1323
|
+
"codex-import": "codex-import";
|
|
1324
|
+
human: "human";
|
|
1325
|
+
import: "import";
|
|
1326
|
+
terminal: "terminal";
|
|
1327
|
+
}>;
|
|
1328
|
+
version: z.ZodLiteral<"0.1.0">;
|
|
1329
|
+
external_id: z.ZodOptional<z.ZodString>;
|
|
1330
|
+
}, z.core.$strip>;
|
|
1331
|
+
started_at: z.ZodString;
|
|
1332
|
+
ended_at: z.ZodOptional<z.ZodString>;
|
|
1333
|
+
status: z.ZodEnum<{
|
|
1334
|
+
initialized: "initialized";
|
|
1335
|
+
running: "running";
|
|
1336
|
+
waiting_approval: "waiting_approval";
|
|
1337
|
+
completed: "completed";
|
|
1338
|
+
failed: "failed";
|
|
1339
|
+
interrupted: "interrupted";
|
|
1340
|
+
imported: "imported";
|
|
1341
|
+
archived: "archived";
|
|
1342
|
+
}>;
|
|
1343
|
+
working_directory: z.ZodString;
|
|
1344
|
+
invocation: z.ZodObject<{
|
|
1345
|
+
command: z.ZodString;
|
|
1346
|
+
args: z.ZodDefault<z.ZodArray<z.ZodString>>;
|
|
1347
|
+
exit_code: z.ZodNullable<z.ZodNumber>;
|
|
1348
|
+
}, z.core.$strip>;
|
|
1349
|
+
related_files: z.ZodDefault<z.ZodArray<z.ZodString>>;
|
|
1350
|
+
events_log: z.ZodDefault<z.ZodString>;
|
|
1351
|
+
summary: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
1352
|
+
metrics: z.ZodOptional<z.ZodObject<{
|
|
1353
|
+
output_tokens: z.ZodOptional<z.ZodNumber>;
|
|
1354
|
+
input_tokens: z.ZodOptional<z.ZodNumber>;
|
|
1355
|
+
cached_input_tokens: z.ZodOptional<z.ZodNumber>;
|
|
1356
|
+
reasoning_output_tokens: z.ZodOptional<z.ZodNumber>;
|
|
1357
|
+
active_time_ms: z.ZodOptional<z.ZodNumber>;
|
|
1358
|
+
active_intervals: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
1359
|
+
start: z.ZodString;
|
|
1360
|
+
end: z.ZodString;
|
|
1361
|
+
}, z.core.$strip>>>;
|
|
1362
|
+
active_gap_cap_ms: z.ZodOptional<z.ZodNumber>;
|
|
1363
|
+
active_time_method: z.ZodOptional<z.ZodString>;
|
|
1364
|
+
}, z.core.$strip>>;
|
|
1365
|
+
}, z.core.$strip>;
|
|
1366
|
+
}, z.core.$strip>;
|
|
1367
|
+
/** Inferred runtime type for {@link SessionSchema}. */
|
|
1368
|
+
type Session = z.infer<typeof SessionSchema>;
|
|
1369
|
+
|
|
1502
1370
|
/**
|
|
1503
1371
|
* Threshold above which a still-`running` session with no `session_ended`
|
|
1504
1372
|
* event is flagged suspect.
|
|
@@ -1595,133 +1463,290 @@ declare function classifySuspect(paths: BasouPaths, sessionId: string, session:
|
|
|
1595
1463
|
*/
|
|
1596
1464
|
declare function loadSessionEntries(paths: BasouPaths, options: LoadSessionEntriesOptions): Promise<SessionEntry[]>;
|
|
1597
1465
|
|
|
1598
|
-
|
|
1599
|
-
|
|
1600
|
-
|
|
1601
|
-
|
|
1466
|
+
type DecisionsRendererInput = {
|
|
1467
|
+
paths: BasouPaths;
|
|
1468
|
+
nowIso: string;
|
|
1469
|
+
onWarning?: (warning: ReplayWarning, sessionId: string) => void;
|
|
1470
|
+
onSessionSkip?: (sessionId: string, reason: SessionSkipReason) => void;
|
|
1471
|
+
};
|
|
1472
|
+
type DecisionsRendererResult = {
|
|
1473
|
+
/** Generated body WITHOUT BASOU:GENERATED markers. */
|
|
1474
|
+
body: string;
|
|
1475
|
+
decisionCount: number;
|
|
1476
|
+
};
|
|
1602
1477
|
/**
|
|
1603
|
-
*
|
|
1478
|
+
* Render the body of `decisions.md` from `decision_recorded` events across
|
|
1479
|
+
* every healthy session in the workspace.
|
|
1480
|
+
*
|
|
1481
|
+
* Session enumeration goes through {@link loadSessionEntries} (the same path
|
|
1482
|
+
* the handoff renderer uses) so that `session.yaml`-broken sessions are
|
|
1483
|
+
* skipped in BOTH outputs and the handoff's `decisionCount` summary stays
|
|
1484
|
+
* consistent with the number of sections rendered here.
|
|
1485
|
+
*
|
|
1486
|
+
* Order: `occurred_at` ascending with `decisionId` (= ULID) as tie-breaker.
|
|
1487
|
+
* Both fields are monotonic, so the result is a stable cross-session
|
|
1488
|
+
* timeline.
|
|
1489
|
+
*
|
|
1490
|
+
* The decision rich fields (rationale / alternatives / rejected_reason /
|
|
1491
|
+
* linked_events / linked_files) are rendered when the event carries them.
|
|
1492
|
+
* `linked_events` and `linked_files` are OPAQUE references: the schema only
|
|
1493
|
+
* validates the SHAPE, not existence — references that cannot be resolved
|
|
1494
|
+
* to a known event id or an existing file on disk are surfaced inline as
|
|
1495
|
+
* `(missing)` so cross-workspace round-trips never reject parse-time.
|
|
1496
|
+
*/
|
|
1497
|
+
declare function renderDecisions(input: DecisionsRendererInput): Promise<DecisionsRendererResult>;
|
|
1498
|
+
|
|
1499
|
+
/**
|
|
1500
|
+
* Append a single Basou event to `<sessionDir>/events.jsonl`.
|
|
1501
|
+
*
|
|
1502
|
+
* The event is validated against the discriminated union {@link EventSchema}
|
|
1503
|
+
* before being serialized as a single JSONL line (UTF-8, terminated by `\n`).
|
|
1504
|
+
* Validation enforces the per-variant contract (required fields, source
|
|
1505
|
+
* vocabulary, strict variants such as `adapter_output`).
|
|
1506
|
+
*
|
|
1507
|
+
* Atomicity: writes go through `appendFile` which uses `O_APPEND`. Lines up
|
|
1508
|
+
* to `PIPE_BUF` bytes (Linux 4096 / macOS 512) are written atomically by the
|
|
1509
|
+
* kernel; longer lines may interleave with concurrent writers and are not
|
|
1510
|
+
* recovered here. v0.1 assumes a single writer per session, so partial-line
|
|
1511
|
+
* recovery is delegated to the read side (event replay) when introduced.
|
|
1512
|
+
*
|
|
1513
|
+
* Throws if validation fails or the underlying append errors. The thrown
|
|
1514
|
+
* Error message is pathless; the original error is attached as `cause`.
|
|
1515
|
+
*
|
|
1516
|
+
* @param sessionDir absolute path to `.basou/sessions/<session_id>/`
|
|
1517
|
+
* @param event unknown payload to validate and append
|
|
1518
|
+
*/
|
|
1519
|
+
declare function appendEvent(sessionDir: string, event: unknown): Promise<void>;
|
|
1520
|
+
/**
|
|
1521
|
+
* Write `events.jsonl` in one atomic tmp+rename pass via {@link atomicReplace},
|
|
1522
|
+
* validating every event against {@link EventSchema} before any disk I/O so
|
|
1523
|
+
* a payload that fails validation never leaves a partial file behind.
|
|
1524
|
+
*
|
|
1525
|
+
* The helper is used by the round-trip importer (`session-import.ts`) and the
|
|
1526
|
+
* ad-hoc session orchestrator (`ad-hoc-session.ts`) where a small, fixed batch
|
|
1527
|
+
* of events must land together or not at all. Zero events produces a
|
|
1528
|
+
* zero-byte file so the session_yaml `events_log` pointer remains valid.
|
|
1529
|
+
*
|
|
1530
|
+
* Throws `"Invalid Basou event payload"` (same fixed message as
|
|
1531
|
+
* {@link appendEvent}) on validation failure, or `"Failed to write
|
|
1532
|
+
* events.jsonl"` on a disk I/O failure. The original native error is attached
|
|
1533
|
+
* as `cause`.
|
|
1534
|
+
*/
|
|
1535
|
+
declare function writeEventsBulk(sessionDir: string, events: Event[]): Promise<void>;
|
|
1536
|
+
|
|
1537
|
+
/**
|
|
1538
|
+
* Status classification used by the `file_changed` event schema. Limited to
|
|
1539
|
+
* the four classes that simple-git's `git diff --name-status` reliably
|
|
1540
|
+
* surfaces; copy / unmerged / typechange entries are intentionally dropped
|
|
1541
|
+
* to keep the event payload shape narrow.
|
|
1542
|
+
*/
|
|
1543
|
+
type FileChangeStatus = "added" | "modified" | "deleted" | "renamed";
|
|
1544
|
+
/**
|
|
1545
|
+
* Single file-level change observed between two refs. `old_path` is set
|
|
1546
|
+
* only for `renamed` entries (the previous path of the file).
|
|
1547
|
+
*/
|
|
1548
|
+
type FileChange = {
|
|
1549
|
+
path: string;
|
|
1550
|
+
old_path?: string;
|
|
1551
|
+
status: FileChangeStatus;
|
|
1552
|
+
};
|
|
1553
|
+
/**
|
|
1554
|
+
* Result of {@link getDiff}. The `changed_files` array is in git's natural
|
|
1555
|
+
* `--name-status` order; callers requiring deterministic ordering should
|
|
1556
|
+
* sort by `path` themselves.
|
|
1557
|
+
*/
|
|
1558
|
+
type DiffResult = {
|
|
1559
|
+
changed_files: FileChange[];
|
|
1560
|
+
};
|
|
1561
|
+
/**
|
|
1562
|
+
* Compute the file-level diff between two git refs.
|
|
1563
|
+
*
|
|
1564
|
+
* Returns a list of changed file paths classified by status (added /
|
|
1565
|
+
* modified / deleted / renamed). Diff content is intentionally NOT
|
|
1566
|
+
* returned — `file_changed` events record paths only, and raw diff bodies
|
|
1567
|
+
* are excluded so the trace cannot inadvertently leak source code that may
|
|
1568
|
+
* be sensitive. Use `git show <ref>` to obtain the underlying diff.
|
|
1569
|
+
*
|
|
1570
|
+
* Pathless contract: every thrown message is a fixed string from the set
|
|
1571
|
+
* {`Not a git repository`, `Git executable not found in PATH. Install git
|
|
1572
|
+
* first.`, `Invalid ref`, `Failed to compute git diff`}; native errors are
|
|
1573
|
+
* preserved on `Error.cause`.
|
|
1574
|
+
*
|
|
1575
|
+
* Special cases:
|
|
1576
|
+
* - `baseRef === headRef` short-circuits to an empty result
|
|
1577
|
+
* - copy / unmerged / typechange / unknown status codes are skipped
|
|
1578
|
+
*
|
|
1579
|
+
* @param repoRoot absolute path to the git repository root
|
|
1580
|
+
* @param baseRef base ref (e.g. session-start HEAD sha)
|
|
1581
|
+
* @param headRef head ref (e.g. session-end HEAD sha)
|
|
1582
|
+
*/
|
|
1583
|
+
declare function getDiff(repoRoot: string, baseRef: string, headRef: string): Promise<DiffResult>;
|
|
1584
|
+
|
|
1585
|
+
/**
|
|
1586
|
+
* Payload subset of `git_snapshot` event, mechanically derived from the
|
|
1587
|
+
* zod-inferred event type. The wrapping event-shape fields
|
|
1588
|
+
* (schema_version, id, session_id, occurred_at, source, type) are added by
|
|
1589
|
+
* the caller (session lifecycle in later steps) when constructing the
|
|
1590
|
+
* event, so the schema remains the single source of truth.
|
|
1591
|
+
*
|
|
1592
|
+
* `ahead` / `behind` are omitted when there is no remote or no upstream
|
|
1593
|
+
* tracking; the schema declares both as optional non-negative integers.
|
|
1594
|
+
*/
|
|
1595
|
+
type GitSnapshot = Omit<GitSnapshotEvent, "schema_version" | "id" | "session_id" | "occurred_at" | "source" | "type">;
|
|
1596
|
+
/**
|
|
1597
|
+
* Resolve the absolute path of the Git repository root that contains `cwd`.
|
|
1598
|
+
* Equivalent to `git rev-parse --show-toplevel`.
|
|
1599
|
+
*
|
|
1600
|
+
* Throws `Error("Git executable not found in PATH. Install git first.")`
|
|
1601
|
+
* with the spawn error attached as `cause` when git itself is missing.
|
|
1602
|
+
* Throws `Error("Not a git repository")` (without command-specific suffix)
|
|
1603
|
+
* when `cwd` is not inside a repository — callers MAY wrap with their own
|
|
1604
|
+
* "Run 'git init' first, then re-run 'basou XXX'." suffix.
|
|
1605
|
+
*
|
|
1606
|
+
* Pathless contract: the thrown message never embeds `cwd` or any absolute
|
|
1607
|
+
* path; native errors are kept on `error.cause` for verbose surfacing.
|
|
1608
|
+
*/
|
|
1609
|
+
declare function resolveRepositoryRoot(cwd: string): Promise<string>;
|
|
1610
|
+
/**
|
|
1611
|
+
* Read `remote.origin.url` from the local repository config. Returns
|
|
1612
|
+
* `undefined` if the remote is unset, the value is empty, or the lookup
|
|
1613
|
+
* fails for any reason (best-effort).
|
|
1614
|
+
*
|
|
1615
|
+
* The `--local` scope is critical: callers MUST NOT pick up the developer's
|
|
1616
|
+
* global remote.origin.url, which could leak the wrong repository URL into
|
|
1617
|
+
* `manifest.yaml`.
|
|
1618
|
+
*/
|
|
1619
|
+
declare function tryRemoteUrl(repositoryRoot: string): Promise<string | undefined>;
|
|
1620
|
+
/**
|
|
1621
|
+
* Build a {@link GitSnapshot} for the repository at `repositoryRoot`. The
|
|
1622
|
+
* caller is responsible for ensuring `repositoryRoot` is the canonical root
|
|
1623
|
+
* (typically obtained via {@link resolveRepositoryRoot}); this function
|
|
1624
|
+
* verifies repo membership via `git rev-parse --is-inside-work-tree` to
|
|
1625
|
+
* distinguish a non-git directory from an empty repository.
|
|
1626
|
+
*
|
|
1627
|
+
* Edge cases:
|
|
1628
|
+
* - **non-git directory**: throws `Error("Not a git repository")`
|
|
1629
|
+
* - **empty repo (no commits)**: throws `Error("No commits in repository")`
|
|
1630
|
+
* - **detached HEAD**: `branch = "HEAD"`, `head = commit hash`,
|
|
1631
|
+
* `ahead`/`behind` omitted
|
|
1632
|
+
* - **no remote / no upstream tracking**: `ahead`/`behind` omitted
|
|
1633
|
+
*
|
|
1634
|
+
* Pathless contract preserved on every throw path.
|
|
1635
|
+
*/
|
|
1636
|
+
declare function getSnapshot(repositoryRoot: string): Promise<GitSnapshot>;
|
|
1637
|
+
|
|
1638
|
+
/**
|
|
1639
|
+
* Allowed ID type prefixes for Basou entities.
|
|
1604
1640
|
*
|
|
1605
|
-
*
|
|
1606
|
-
*
|
|
1607
|
-
*
|
|
1608
|
-
* Leading/trailing whitespace, comment compression, and BOM are treated as
|
|
1609
|
-
* legacy formats (`no_markers`) so that re-generation refuses to silently
|
|
1610
|
-
* overwrite a mismatched manual edit.
|
|
1641
|
+
* Frozen at runtime so that mutating the exported array cannot diverge from
|
|
1642
|
+
* the validation set used internally. The single source of truth for both
|
|
1643
|
+
* the `IdPrefix` type and runtime prefix checks.
|
|
1611
1644
|
*/
|
|
1612
|
-
|
|
1613
|
-
kind: "ok";
|
|
1614
|
-
before: string;
|
|
1615
|
-
generated: string;
|
|
1616
|
-
after: string;
|
|
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
|
-
};
|
|
1645
|
+
declare const ID_PREFIXES: readonly ["ws", "task", "ses", "evt", "appr", "decision"];
|
|
1628
1646
|
/**
|
|
1629
|
-
*
|
|
1630
|
-
*
|
|
1631
|
-
* I/O failures (pathless contract — never embed the absolute path in the
|
|
1632
|
-
* thrown `message`).
|
|
1647
|
+
* Type prefix used for Basou entity IDs.
|
|
1648
|
+
* Format: `<prefix>_<26-char ULID>`, e.g. `ws_01HXABCDEF1234567890ABCDEF`.
|
|
1633
1649
|
*/
|
|
1634
|
-
|
|
1650
|
+
type IdPrefix = (typeof ID_PREFIXES)[number];
|
|
1635
1651
|
/**
|
|
1636
|
-
*
|
|
1637
|
-
* helper handles the tmp-file + rename sequence, `wx` collision guard, and
|
|
1638
|
-
* best-effort tmp cleanup on failure.
|
|
1652
|
+
* A Basou entity ID as a template literal type.
|
|
1639
1653
|
*
|
|
1640
|
-
*
|
|
1641
|
-
*
|
|
1654
|
+
* `PrefixedId<"ses">` narrows to ``ses_${string}`` so a session schema can
|
|
1655
|
+
* preserve the prefix in its inferred type beyond runtime validation.
|
|
1642
1656
|
*/
|
|
1643
|
-
|
|
1657
|
+
type PrefixedId<P extends IdPrefix = IdPrefix> = `${P}_${string}`;
|
|
1644
1658
|
/**
|
|
1645
|
-
*
|
|
1659
|
+
* Generate a Crockford Base32 ULID.
|
|
1646
1660
|
*
|
|
1647
|
-
*
|
|
1648
|
-
*
|
|
1649
|
-
*
|
|
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.
|
|
1661
|
+
* The result is a 26-character, lexicographically time-sortable identifier.
|
|
1662
|
+
* Multiple calls within the same millisecond are strictly increasing for the
|
|
1663
|
+
* lifetime of the current process.
|
|
1656
1664
|
*
|
|
1657
|
-
*
|
|
1658
|
-
*
|
|
1659
|
-
*
|
|
1665
|
+
* NOTE: `seedTime` is forwarded to the underlying monotonic factory and is
|
|
1666
|
+
* NOT a deterministic seed: repeated calls with the same `seedTime` still
|
|
1667
|
+
* return strictly increasing values, because the factory increments its
|
|
1668
|
+
* internal counter on each call.
|
|
1669
|
+
*
|
|
1670
|
+
* @param seedTime Optional millisecond timestamp passed to the monotonic
|
|
1671
|
+
* factory. Useful for ordered generation in tests; not deterministic.
|
|
1660
1672
|
*/
|
|
1661
|
-
declare function
|
|
1673
|
+
declare function ulid(seedTime?: number): string;
|
|
1662
1674
|
/**
|
|
1663
|
-
*
|
|
1675
|
+
* Generate a prefixed Basou ID, e.g. `ses_01HXABCDEF1234567890ABCDEF`.
|
|
1664
1676
|
*
|
|
1665
|
-
*
|
|
1666
|
-
*
|
|
1667
|
-
* before START and after END untouched (preserving manual additions).
|
|
1668
|
-
* - any other parse result: throw a pathless error referencing `fileLabel`.
|
|
1677
|
+
* The return type preserves the prefix as a template literal type so that
|
|
1678
|
+
* downstream zod schemas can narrow an `IdPrefix` parameter through the API.
|
|
1669
1679
|
*
|
|
1670
|
-
*
|
|
1671
|
-
*
|
|
1680
|
+
* Throws if `prefix` is not one of {@link ID_PREFIXES}. The runtime guard
|
|
1681
|
+
* defends against JavaScript callers and casted TypeScript that bypass the
|
|
1682
|
+
* compile-time `IdPrefix` constraint.
|
|
1672
1683
|
*/
|
|
1673
|
-
declare function
|
|
1674
|
-
|
|
1684
|
+
declare function prefixedUlid<P extends IdPrefix>(prefix: P): PrefixedId<P>;
|
|
1675
1685
|
/**
|
|
1676
|
-
*
|
|
1686
|
+
* Check whether the given string is a valid prefixed Basou ID.
|
|
1677
1687
|
*
|
|
1678
|
-
*
|
|
1679
|
-
*
|
|
1680
|
-
*
|
|
1688
|
+
* Returns true only if the string has shape `<prefix>_<ULID>` where prefix is
|
|
1689
|
+
* one of {@link ID_PREFIXES} and the trailing 26 characters form a valid
|
|
1690
|
+
* Crockford Base32 ULID. Validation combines a strict shape regex (to enforce
|
|
1691
|
+
* the 0-7 leading char and the I/L/O/U exclusion) with the npm `ulid`
|
|
1692
|
+
* library's `isValid` for forward compatibility.
|
|
1693
|
+
*
|
|
1694
|
+
* NOTE: This validates the prefix is known. Schemas that require a specific
|
|
1695
|
+
* prefix (e.g. only `ses_*` for a session ID) must add their own narrowing.
|
|
1681
1696
|
*/
|
|
1682
|
-
|
|
1683
|
-
|
|
1684
|
-
taskIdOverride?: string;
|
|
1685
|
-
dryRun?: boolean;
|
|
1686
|
-
};
|
|
1697
|
+
declare function isValidPrefixedId(value: string): boolean;
|
|
1698
|
+
|
|
1687
1699
|
/**
|
|
1688
|
-
*
|
|
1689
|
-
* `"imported"` (per the import-session lifecycle policy); `finalSourceKind`
|
|
1690
|
-
* mirrors the input's `session.source.kind` so round-trip imports preserve
|
|
1691
|
-
* provenance.
|
|
1700
|
+
* Task lifecycle states.
|
|
1692
1701
|
*
|
|
1693
|
-
* `
|
|
1694
|
-
*
|
|
1695
|
-
*
|
|
1696
|
-
*
|
|
1697
|
-
*
|
|
1702
|
+
* The storage layer's `ALLOWED_TRANSITIONS` map (= source of truth in
|
|
1703
|
+
* `tasks.ts`) is the authoritative graph; the comment below is a snapshot.
|
|
1704
|
+
* `planned` reaches `done` / `cancelled` directly so tasks completed (or
|
|
1705
|
+
* abandoned) outside an explicit in-progress phase can close in a single
|
|
1706
|
+
* CLI call:
|
|
1707
|
+
*
|
|
1708
|
+
* planned → {in_progress | done | cancelled}
|
|
1709
|
+
* in_progress → {done | cancelled}
|
|
1710
|
+
* done / cancelled = terminal
|
|
1711
|
+
*
|
|
1712
|
+
* Self-edges are rejected so the audit trail stays monotonic.
|
|
1698
1713
|
*/
|
|
1699
|
-
|
|
1700
|
-
|
|
1701
|
-
|
|
1702
|
-
|
|
1703
|
-
|
|
1704
|
-
|
|
1705
|
-
|
|
1706
|
-
|
|
1707
|
-
};
|
|
1708
|
-
};
|
|
1714
|
+
declare const TaskStatusSchema: z.ZodEnum<{
|
|
1715
|
+
planned: "planned";
|
|
1716
|
+
in_progress: "in_progress";
|
|
1717
|
+
done: "done";
|
|
1718
|
+
cancelled: "cancelled";
|
|
1719
|
+
}>;
|
|
1720
|
+
/** Inferred runtime type for {@link TaskStatusSchema}. */
|
|
1721
|
+
type TaskStatus = z.infer<typeof TaskStatusSchema>;
|
|
1709
1722
|
/**
|
|
1710
|
-
*
|
|
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.
|
|
1723
|
+
* Schema for the YAML front matter of `.basou/tasks/<task_id>.md`.
|
|
1719
1724
|
*
|
|
1720
|
-
*
|
|
1721
|
-
*
|
|
1722
|
-
*
|
|
1725
|
+
* The markdown body after the front matter is intentionally NOT modelled
|
|
1726
|
+
* here — it is free-form user-edited content. The storage layer splits
|
|
1727
|
+
* the file into `task` (this schema) and `body` (the trailing string).
|
|
1723
1728
|
*/
|
|
1724
|
-
declare
|
|
1729
|
+
declare const TaskSchema: z.ZodObject<{
|
|
1730
|
+
schema_version: z.ZodLiteral<"0.1.0">;
|
|
1731
|
+
task: z.ZodObject<{
|
|
1732
|
+
id: z.ZodString & z.ZodType<`task_${string}`, string, z.core.$ZodTypeInternals<`task_${string}`, string>>;
|
|
1733
|
+
title: z.ZodString;
|
|
1734
|
+
label: z.ZodOptional<z.ZodString>;
|
|
1735
|
+
status: z.ZodEnum<{
|
|
1736
|
+
planned: "planned";
|
|
1737
|
+
in_progress: "in_progress";
|
|
1738
|
+
done: "done";
|
|
1739
|
+
cancelled: "cancelled";
|
|
1740
|
+
}>;
|
|
1741
|
+
created_at: z.ZodString;
|
|
1742
|
+
updated_at: z.ZodString;
|
|
1743
|
+
workspace_id: z.ZodString & z.ZodType<`ws_${string}`, string, z.core.$ZodTypeInternals<`ws_${string}`, string>>;
|
|
1744
|
+
created_in_session: z.ZodString & z.ZodType<`ses_${string}`, string, z.core.$ZodTypeInternals<`ses_${string}`, string>>;
|
|
1745
|
+
linked_sessions: z.ZodDefault<z.ZodArray<z.ZodString & z.ZodType<`ses_${string}`, string, z.core.$ZodTypeInternals<`ses_${string}`, string>>>>;
|
|
1746
|
+
}, z.core.$strip>;
|
|
1747
|
+
}, z.core.$strip>;
|
|
1748
|
+
/** Inferred runtime type for {@link TaskSchema}. */
|
|
1749
|
+
type Task = z.infer<typeof TaskSchema>;
|
|
1725
1750
|
|
|
1726
1751
|
/**
|
|
1727
1752
|
* Thrown when the ad-hoc session was fully written to disk (4 lifecycle
|
|
@@ -2374,17 +2399,95 @@ type ArchiveTaskResult = {
|
|
|
2374
2399
|
* 4. Ensure the archive directory exists.
|
|
2375
2400
|
* 5. Rename main/<id>.md to archive/<id>.md (= atomic on the same fs).
|
|
2376
2401
|
*
|
|
2377
|
-
* Failure modes after step 2 surface as
|
|
2378
|
-
* {@link TaskWriteAfterEventError} with `phase: "archive"`; the operator
|
|
2379
|
-
* is told the event is durable but the on-disk move is incomplete and
|
|
2380
|
-
* must be resolved manually (typically by rerunning `task archive`).
|
|
2402
|
+
* Failure modes after step 2 surface as
|
|
2403
|
+
* {@link TaskWriteAfterEventError} with `phase: "archive"`; the operator
|
|
2404
|
+
* is told the event is durable but the on-disk move is incomplete and
|
|
2405
|
+
* must be resolved manually (typically by rerunning `task archive`).
|
|
2406
|
+
*/
|
|
2407
|
+
declare function archiveTask(input: ArchiveTaskInput): Promise<ArchiveTaskResult>;
|
|
2408
|
+
|
|
2409
|
+
/** Input contract for {@link renderHandoff}. */
|
|
2410
|
+
type HandoffRendererInput = {
|
|
2411
|
+
paths: BasouPaths;
|
|
2412
|
+
/** ISO timestamp embedded in the generated body header. Caller-provided for testability. */
|
|
2413
|
+
nowIso: string;
|
|
2414
|
+
/** Forwarded to {@link replayEvents} / {@link loadSessionEntries} per session. */
|
|
2415
|
+
onWarning?: (warning: ReplayWarning, sessionId: string) => void;
|
|
2416
|
+
/**
|
|
2417
|
+
* Per-session degradation reasons (missing/invalid session.yaml or
|
|
2418
|
+
* unreadable events.jsonl). The CLI maps `events_jsonl_unreadable` to the
|
|
2419
|
+
* existing suspect-check stderr wording to keep the user-facing surface
|
|
2420
|
+
* consistent with `basou session list`.
|
|
2421
|
+
*/
|
|
2422
|
+
onSessionSkip?: (sessionId: string, reason: SessionSkipReason) => void;
|
|
2423
|
+
/**
|
|
2424
|
+
* Per-task degradation reasons (invalid front matter / unreadable file).
|
|
2425
|
+
* Surfaced so the CLI can warn the operator about a malformed task.md
|
|
2426
|
+
* without aborting the handoff render.
|
|
2427
|
+
*/
|
|
2428
|
+
onTaskSkip?: (taskId: string, reason: TaskSkipReason) => void;
|
|
2429
|
+
/** Maximum related_files entries to display before `... +N more`. Default 20. */
|
|
2430
|
+
relatedFilesLimit?: number;
|
|
2431
|
+
};
|
|
2432
|
+
type HandoffRendererResult = {
|
|
2433
|
+
/** Generated body WITHOUT BASOU:GENERATED markers (markdown-store wraps them). */
|
|
2434
|
+
body: string;
|
|
2435
|
+
sessionCount: number;
|
|
2436
|
+
decisionCount: number;
|
|
2437
|
+
pendingApprovalsCount: number;
|
|
2438
|
+
suspectCount: number;
|
|
2439
|
+
/** Total number of task.md files successfully loaded. */
|
|
2440
|
+
taskCount: number;
|
|
2441
|
+
/** Tasks whose status is `planned` or `in_progress` (= shown in 次に実行すべき作業). */
|
|
2442
|
+
pendingTaskCount: number;
|
|
2443
|
+
};
|
|
2444
|
+
/**
|
|
2445
|
+
* Render the body of `handoff.md` from the current workspace state.
|
|
2446
|
+
*
|
|
2447
|
+
* The renderer is a pure function (no I/O beyond {@link replayEvents} /
|
|
2448
|
+
* {@link loadSessionEntries} / {@link enumerateApprovals}). It assembles the
|
|
2449
|
+
* the spec's `handoff.md` sections in order:
|
|
2450
|
+
*
|
|
2451
|
+
* 1. `現在の状態`: latest live session (status not archived, source not import).
|
|
2452
|
+
* 2. `直近の変更ファイル`: the most recent session's `related_files`, dedup +
|
|
2453
|
+
* sorted asc + truncated to `relatedFilesLimit` (default 20).
|
|
2454
|
+
* 3. `直近の判断`: latest `decision_recorded` event (chronological).
|
|
2455
|
+
* 4. `未決事項`: pending-approval count + suspect-session count.
|
|
2456
|
+
* 5. `次に読むべきファイル`: `.basou/decisions.md` + top-3 related files
|
|
2457
|
+
* (the same `displayedFiles` source is intentionally reused in two
|
|
2458
|
+
* sections — overview vs. resume context).
|
|
2459
|
+
* 6. `次に実行すべき作業`: placeholder until task events land.
|
|
2460
|
+
* 7. `セッション一覧`: all sessions newest first with inline suspect labels.
|
|
2461
|
+
*
|
|
2462
|
+
* Session enumeration goes through {@link loadSessionEntries} so the set of
|
|
2463
|
+
* sessions whose `decision_recorded` events we replay matches the
|
|
2464
|
+
* decisions renderer.
|
|
2465
|
+
*/
|
|
2466
|
+
declare function renderHandoff(input: HandoffRendererInput): Promise<HandoffRendererResult>;
|
|
2467
|
+
|
|
2468
|
+
/**
|
|
2469
|
+
* Parse a unit-suffixed duration string (e.g. `30s`, `5m`, `1h`, `100ms`)
|
|
2470
|
+
* into milliseconds.
|
|
2471
|
+
*
|
|
2472
|
+
* Rejects formats that cannot represent a positive, finite millisecond
|
|
2473
|
+
* value: malformed inputs, zero, leading-zero values, and computations that
|
|
2474
|
+
* overflow to `Infinity`. The returned number is always a positive integer.
|
|
2475
|
+
*
|
|
2476
|
+
* Supported units: `ms` (milliseconds), `s` (seconds), `m` (minutes),
|
|
2477
|
+
* `h` (hours).
|
|
2478
|
+
*
|
|
2479
|
+
* @param input duration string with required unit suffix
|
|
2480
|
+
* @returns duration in milliseconds (positive, finite)
|
|
2481
|
+
* @throws Error with message
|
|
2482
|
+
* `Invalid duration: <input>. Expected format: <positive-integer><unit> where unit is ms/s/m/h`
|
|
2483
|
+
* for format errors, or `Duration overflow: <input>` for non-finite results.
|
|
2381
2484
|
*/
|
|
2382
|
-
declare function
|
|
2485
|
+
declare function parseDuration(input: string): number;
|
|
2383
2486
|
|
|
2384
2487
|
/**
|
|
2385
2488
|
* Resolve a possibly-truncated session id prefix to a full session id by
|
|
2386
2489
|
* scanning `<paths.sessions>/`. Existing message contract (carried over
|
|
2387
|
-
* from `packages/cli/src/commands/session.ts`
|
|
2490
|
+
* from `packages/cli/src/commands/session.ts`) is
|
|
2388
2491
|
* preserved exactly so callers that grep stderr keep working:
|
|
2389
2492
|
*
|
|
2390
2493
|
* - `"Session id is empty"`
|
|
@@ -2492,98 +2595,6 @@ type SanitizeRelatedFilesResult = {
|
|
|
2492
2595
|
*/
|
|
2493
2596
|
declare function sanitizeRelatedFiles(paths: ReadonlyArray<string>, opts: SanitizePathOptions): SanitizeRelatedFilesResult;
|
|
2494
2597
|
|
|
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
|
-
/**
|
|
2531
|
-
* Render the body of `handoff.md` from the current workspace state.
|
|
2532
|
-
*
|
|
2533
|
-
* The renderer is a pure function (no I/O beyond {@link replayEvents} /
|
|
2534
|
-
* {@link loadSessionEntries} / {@link enumerateApprovals}). It assembles the
|
|
2535
|
-
* the spec's `handoff.md` sections in order:
|
|
2536
|
-
*
|
|
2537
|
-
* 1. `現在の状態`: latest live session (status not archived, source not import).
|
|
2538
|
-
* 2. `直近の変更ファイル`: union of `related_files` across sessions, dedup +
|
|
2539
|
-
* sorted asc + truncated to `relatedFilesLimit` (default 20).
|
|
2540
|
-
* 3. `直近の判断`: latest `decision_recorded` event (chronological).
|
|
2541
|
-
* 4. `未決事項`: pending-approval count + suspect-session count.
|
|
2542
|
-
* 5. `次に読むべきファイル`: `.basou/decisions.md` + top-3 related files
|
|
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.
|
|
2547
|
-
*
|
|
2548
|
-
* Session enumeration goes through {@link loadSessionEntries} so the set of
|
|
2549
|
-
* sessions whose `decision_recorded` events we replay matches the
|
|
2550
|
-
* decisions renderer.
|
|
2551
|
-
*/
|
|
2552
|
-
declare function renderHandoff(input: HandoffRendererInput): Promise<HandoffRendererResult>;
|
|
2553
|
-
|
|
2554
|
-
type DecisionsRendererInput = {
|
|
2555
|
-
paths: BasouPaths;
|
|
2556
|
-
nowIso: string;
|
|
2557
|
-
onWarning?: (warning: ReplayWarning, sessionId: string) => void;
|
|
2558
|
-
onSessionSkip?: (sessionId: string, reason: SessionSkipReason) => void;
|
|
2559
|
-
};
|
|
2560
|
-
type DecisionsRendererResult = {
|
|
2561
|
-
/** Generated body WITHOUT BASOU:GENERATED markers. */
|
|
2562
|
-
body: string;
|
|
2563
|
-
decisionCount: number;
|
|
2564
|
-
};
|
|
2565
|
-
/**
|
|
2566
|
-
* Render the body of `decisions.md` from `decision_recorded` events across
|
|
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.
|
|
2573
|
-
*
|
|
2574
|
-
* Order: `occurred_at` ascending with `decisionId` (= ULID) as tie-breaker.
|
|
2575
|
-
* Both fields are monotonic, so the result is a stable cross-session
|
|
2576
|
-
* timeline.
|
|
2577
|
-
*
|
|
2578
|
-
* The decision rich fields (rationale / alternatives / rejected_reason /
|
|
2579
|
-
* linked_events / linked_files) are rendered when the event carries them.
|
|
2580
|
-
* `linked_events` and `linked_files` are OPAQUE references: the schema only
|
|
2581
|
-
* validates the SHAPE, not existence — references that cannot be resolved
|
|
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.
|
|
2584
|
-
*/
|
|
2585
|
-
declare function renderDecisions(input: DecisionsRendererInput): Promise<DecisionsRendererResult>;
|
|
2586
|
-
|
|
2587
2598
|
/**
|
|
2588
2599
|
* Internal abstraction over child-process execution.
|
|
2589
2600
|
*
|
|
@@ -2622,283 +2633,669 @@ type RunOptions = {
|
|
|
2622
2633
|
*/
|
|
2623
2634
|
readonly env?: NodeJS.ProcessEnv;
|
|
2624
2635
|
/**
|
|
2625
|
-
* External cancellation. Aborting the signal triggers a two-stage
|
|
2626
|
-
* kill (SIGTERM, then SIGKILL after a short grace period).
|
|
2636
|
+
* External cancellation. Aborting the signal triggers a two-stage
|
|
2637
|
+
* kill (SIGTERM, then SIGKILL after a short grace period).
|
|
2638
|
+
*/
|
|
2639
|
+
readonly signal?: AbortSignal;
|
|
2640
|
+
/**
|
|
2641
|
+
* Internal timeout in milliseconds. Must be a positive finite number.
|
|
2642
|
+
* Triggers the same two-stage kill as `signal`.
|
|
2643
|
+
*/
|
|
2644
|
+
readonly timeout_ms?: number;
|
|
2645
|
+
/**
|
|
2646
|
+
* Optional input written to the child's stdin. The pipe is closed
|
|
2647
|
+
* after the value is written. Incompatible with `capture: "none"`.
|
|
2648
|
+
*/
|
|
2649
|
+
readonly stdin?: string | Buffer;
|
|
2650
|
+
/**
|
|
2651
|
+
* Output capture mode. Defaults to `"buffer"`. See {@link CaptureMode}.
|
|
2652
|
+
*/
|
|
2653
|
+
readonly capture?: CaptureMode;
|
|
2654
|
+
/**
|
|
2655
|
+
* Invoked synchronously immediately after the child has been spawned,
|
|
2656
|
+
* before the runner waits for completion. Callers use this to retain a
|
|
2657
|
+
* reference for parent-side cleanup (e.g. an `exit` hook that SIGKILLs
|
|
2658
|
+
* the child if the parent is forcibly terminated). The runner takes no
|
|
2659
|
+
* action if the callback throws.
|
|
2660
|
+
*/
|
|
2661
|
+
readonly onSpawn?: (child: ChildProcess) => void;
|
|
2662
|
+
};
|
|
2663
|
+
type RunResult = {
|
|
2664
|
+
readonly command: string;
|
|
2665
|
+
readonly args: readonly string[];
|
|
2666
|
+
readonly cwd: string;
|
|
2667
|
+
/** `null` when the process was killed by a signal. */
|
|
2668
|
+
readonly exit_code: number | null;
|
|
2669
|
+
readonly signal: NodeJS.Signals | null;
|
|
2670
|
+
readonly stdout: string;
|
|
2671
|
+
readonly stderr: string;
|
|
2672
|
+
/** ISO 8601 timestamp captured before spawn. */
|
|
2673
|
+
readonly started_at: string;
|
|
2674
|
+
/** ISO 8601 timestamp captured on the `close` event. */
|
|
2675
|
+
readonly ended_at: string;
|
|
2676
|
+
readonly duration_ms: number;
|
|
2677
|
+
readonly pid: number | null;
|
|
2678
|
+
};
|
|
2679
|
+
type ProcessRunner = {
|
|
2680
|
+
run(command: string, args: readonly string[], options: RunOptions): Promise<RunResult>;
|
|
2681
|
+
};
|
|
2682
|
+
|
|
2683
|
+
/**
|
|
2684
|
+
* Spawn-based ProcessRunner implementation.
|
|
2685
|
+
*
|
|
2686
|
+
* Behavior:
|
|
2687
|
+
* - `shell: false` and `detached: false`. The process group is not
|
|
2688
|
+
* detached, but the OS does not guarantee the child is reaped when
|
|
2689
|
+
* the parent terminates abruptly; callers handle SIGINT/SIGTERM/exit
|
|
2690
|
+
* hooks themselves.
|
|
2691
|
+
* - `capture: "buffer"` (default): `stdio: ['pipe', 'pipe', 'pipe']`,
|
|
2692
|
+
* stdout / stderr are decoded as UTF-8 and accumulated as full
|
|
2693
|
+
* strings (no streaming callbacks).
|
|
2694
|
+
* - `capture: "none"`: `stdio: ['inherit', 'inherit', 'inherit']`, the
|
|
2695
|
+
* child writes directly to the parent terminal in real time and
|
|
2696
|
+
* `RunResult.stdout` / `stderr` are empty strings. `stdin` is
|
|
2697
|
+
* incompatible with this mode (the child has no writable stdin pipe)
|
|
2698
|
+
* and the combination is rejected before spawn.
|
|
2699
|
+
* - `timeout_ms` and `AbortSignal` both trigger a two-stage kill:
|
|
2700
|
+
* `SIGTERM`, then `SIGKILL` after `DEFAULT_KILL_GRACE_MS` (5_000 ms).
|
|
2701
|
+
* - A non-zero `exit_code` does not throw; it is returned via
|
|
2702
|
+
* `RunResult`. Spawn-time errors throw with a pathless message and
|
|
2703
|
+
* the original error attached as `cause`.
|
|
2704
|
+
*
|
|
2705
|
+
* Error message contract: messages never include `cwd` or absolute
|
|
2706
|
+
* command paths. The original errno (and any nested wrapping) is
|
|
2707
|
+
* preserved on `Error.cause`, allowing callers to classify with
|
|
2708
|
+
* `findErrorCode` when needed.
|
|
2709
|
+
*/
|
|
2710
|
+
declare class ChildProcessRunner implements ProcessRunner {
|
|
2711
|
+
run(command: string, args: readonly string[], options: RunOptions): Promise<RunResult>;
|
|
2712
|
+
}
|
|
2713
|
+
|
|
2714
|
+
/**
|
|
2715
|
+
* Schema version literal pinned to "0.1.0" for Basou v0.1.
|
|
2716
|
+
* Reused across every entity schema so inferred types narrow to the literal.
|
|
2717
|
+
*/
|
|
2718
|
+
declare const SchemaVersionSchema: z.ZodLiteral<"0.1.0">;
|
|
2719
|
+
/**
|
|
2720
|
+
* ISO 8601 timestamp with explicit timezone offset (e.g. `+09:00`).
|
|
2721
|
+
*
|
|
2722
|
+
* The spec samples include offsets, so the default zod `.datetime()` (which
|
|
2723
|
+
* rejects offsets) is insufficient; `{ offset: true }` is required.
|
|
2724
|
+
*/
|
|
2725
|
+
declare const IsoTimestampSchema: z.ZodString;
|
|
2726
|
+
/** Workspace ID schema: validates `ws_<26-char ULID>`. */
|
|
2727
|
+
declare const WorkspaceIdSchema: z.ZodString & z.ZodType<`ws_${string}`, string, z.core.$ZodTypeInternals<`ws_${string}`, string>>;
|
|
2728
|
+
/** Task ID schema: validates `task_<26-char ULID>`. */
|
|
2729
|
+
declare const TaskIdSchema: z.ZodString & z.ZodType<`task_${string}`, string, z.core.$ZodTypeInternals<`task_${string}`, string>>;
|
|
2730
|
+
/** Session ID schema: validates `ses_<26-char ULID>`. */
|
|
2731
|
+
declare const SessionIdSchema: z.ZodString & z.ZodType<`ses_${string}`, string, z.core.$ZodTypeInternals<`ses_${string}`, string>>;
|
|
2732
|
+
/** Event ID schema: validates `evt_<26-char ULID>`. */
|
|
2733
|
+
declare const EventIdSchema: z.ZodString & z.ZodType<`evt_${string}`, string, z.core.$ZodTypeInternals<`evt_${string}`, string>>;
|
|
2734
|
+
/** Approval ID schema: validates `appr_<26-char ULID>`. */
|
|
2735
|
+
declare const ApprovalIdSchema: z.ZodString & z.ZodType<`appr_${string}`, string, z.core.$ZodTypeInternals<`appr_${string}`, string>>;
|
|
2736
|
+
/** Decision ID schema: validates `decision_<26-char ULID>`. */
|
|
2737
|
+
declare const DecisionIdSchema: z.ZodString & z.ZodType<`decision_${string}`, string, z.core.$ZodTypeInternals<`decision_${string}`, string>>;
|
|
2738
|
+
/**
|
|
2739
|
+
* Risk level vocabulary fixed by the spec. Adapters MUST emit one of these
|
|
2740
|
+
* four values; arbitrary strings are rejected at schema parse time.
|
|
2741
|
+
*/
|
|
2742
|
+
declare const RiskLevelSchema: z.ZodEnum<{
|
|
2743
|
+
low: "low";
|
|
2744
|
+
medium: "medium";
|
|
2745
|
+
high: "high";
|
|
2746
|
+
critical: "critical";
|
|
2747
|
+
}>;
|
|
2748
|
+
/** Inferred runtime type for {@link RiskLevelSchema}. */
|
|
2749
|
+
type RiskLevel = z.infer<typeof RiskLevelSchema>;
|
|
2750
|
+
/**
|
|
2751
|
+
* Source attribution for events (e.g. "claude-code-adapter",
|
|
2752
|
+
* "git-capability", "terminal-recording", "local-cli", "human"). Free-form
|
|
2753
|
+
* non-empty string in v0.1; a stricter enum may be introduced post-v0.1.
|
|
2754
|
+
*/
|
|
2755
|
+
declare const EventSourceSchema: z.ZodString;
|
|
2756
|
+
|
|
2757
|
+
/**
|
|
2758
|
+
* Schema for `.basou/status.json` — a forward-incompat cache of the current
|
|
2759
|
+
* workspace state.
|
|
2760
|
+
*
|
|
2761
|
+
* Each level uses `.strict()` so unknown keys are rejected rather than
|
|
2762
|
+
* silently stripped. A v0.1 reader that encounters a future-shape
|
|
2763
|
+
* `status.json` therefore fails parsing instead of returning a partially
|
|
2764
|
+
* empty snapshot; callers regenerate by calling `buildStatusSnapshot` +
|
|
2765
|
+
* `writeStatus` rather than trying to migrate.
|
|
2766
|
+
*/
|
|
2767
|
+
declare const StatusSchema: z.ZodObject<{
|
|
2768
|
+
schema_version: z.ZodLiteral<"0.1.0">;
|
|
2769
|
+
generated_at: z.ZodString;
|
|
2770
|
+
workspace: z.ZodObject<{
|
|
2771
|
+
id: z.ZodString & z.ZodType<`ws_${string}`, string, z.core.$ZodTypeInternals<`ws_${string}`, string>>;
|
|
2772
|
+
name: z.ZodString;
|
|
2773
|
+
basou_version: z.ZodLiteral<"0.1.0">;
|
|
2774
|
+
}, z.core.$strict>;
|
|
2775
|
+
directories_present: z.ZodObject<{
|
|
2776
|
+
sessions: z.ZodBoolean;
|
|
2777
|
+
tasks: z.ZodBoolean;
|
|
2778
|
+
approvals_pending: z.ZodBoolean;
|
|
2779
|
+
approvals_resolved: z.ZodBoolean;
|
|
2780
|
+
logs: z.ZodBoolean;
|
|
2781
|
+
raw: z.ZodBoolean;
|
|
2782
|
+
tmp: z.ZodBoolean;
|
|
2783
|
+
}, z.core.$strict>;
|
|
2784
|
+
}, z.core.$strict>;
|
|
2785
|
+
/** Inferred runtime type for {@link StatusSchema}. */
|
|
2786
|
+
type StatusSnapshot = z.infer<typeof StatusSchema>;
|
|
2787
|
+
|
|
2788
|
+
/**
|
|
2789
|
+
* Gap longer than this between two consecutive engagement timestamps is treated
|
|
2790
|
+
* as idle and not credited as active time. A deliberately coarse heuristic: a
|
|
2791
|
+
* focus / billable-attention proxy, NOT a measure of model compute. 5 minutes.
|
|
2792
|
+
*/
|
|
2793
|
+
declare const ACTIVE_GAP_CAP_MS: number;
|
|
2794
|
+
/** A wall-clock range expressed as ISO-8601 strings (for persistence). */
|
|
2795
|
+
type IsoInterval = {
|
|
2796
|
+
start: string;
|
|
2797
|
+
end: string;
|
|
2798
|
+
};
|
|
2799
|
+
|
|
2800
|
+
type WorkStatsInput = {
|
|
2801
|
+
paths: BasouPaths;
|
|
2802
|
+
/** Shared clock; running sessions are measured up to this instant. */
|
|
2803
|
+
now: Date;
|
|
2804
|
+
/**
|
|
2805
|
+
* IANA timezone used to bucket the per-day breakdown (logs are UTC, so a
|
|
2806
|
+
* billing day needs an explicit zone). Defaults to the host's local zone;
|
|
2807
|
+
* injectable for deterministic tests.
|
|
2627
2808
|
*/
|
|
2628
|
-
|
|
2809
|
+
timeZone?: string;
|
|
2810
|
+
onWarning?: (warning: ReplayWarning, sessionId: string) => void;
|
|
2811
|
+
onSessionSkip?: (sessionId: string, reason: SessionSkipReason) => void;
|
|
2812
|
+
};
|
|
2813
|
+
/** Which measures are meaningful for a given session / source. */
|
|
2814
|
+
type MeasureAvailability = {
|
|
2815
|
+
/** Always true (started_at + now bound the span). */
|
|
2816
|
+
span: boolean;
|
|
2629
2817
|
/**
|
|
2630
|
-
*
|
|
2631
|
-
*
|
|
2818
|
+
* `commandTimeMs` reflects real shell time. False for `claude-code-import`,
|
|
2819
|
+
* whose transcript carries no per-command duration (recorded as 0).
|
|
2632
2820
|
*/
|
|
2633
|
-
|
|
2821
|
+
commandTime: boolean;
|
|
2822
|
+
/** At least one active interval could be measured (stored or event-derived). */
|
|
2823
|
+
activeTime: boolean;
|
|
2824
|
+
/** Token totals were captured (model-usage metrics present). */
|
|
2825
|
+
tokens: boolean;
|
|
2826
|
+
};
|
|
2827
|
+
/** Token rollup. Zero when not captured; `reasoning` is Codex-only. */
|
|
2828
|
+
type TokenTotals = {
|
|
2829
|
+
output: number;
|
|
2830
|
+
input: number;
|
|
2831
|
+
cached: number;
|
|
2832
|
+
reasoning: number;
|
|
2833
|
+
};
|
|
2834
|
+
/** How a session's active time was derived. */
|
|
2835
|
+
type ActiveTimeBasis = "engaged-turns" | "events";
|
|
2836
|
+
type SessionWorkStats = {
|
|
2837
|
+
sessionId: string;
|
|
2838
|
+
label: string | undefined;
|
|
2839
|
+
status: SessionStatus;
|
|
2840
|
+
sourceKind: SessionSourceKind;
|
|
2841
|
+
startedAt: string;
|
|
2842
|
+
endedAt: string | undefined;
|
|
2843
|
+
/** ended_at absent: span is measured to `now`. */
|
|
2844
|
+
open: boolean;
|
|
2845
|
+
sessionSpanMs: number;
|
|
2846
|
+
commandTimeMs: number;
|
|
2847
|
+
activeTimeMs: number;
|
|
2634
2848
|
/**
|
|
2635
|
-
*
|
|
2636
|
-
*
|
|
2849
|
+
* How `activeTimeMs` / `activeIntervals` were derived: `engaged-turns` from
|
|
2850
|
+
* the engagement timestamps stored at import (captures conversation), or
|
|
2851
|
+
* `events` from the action-event stream (live sessions and pre-v2 imports).
|
|
2637
2852
|
*/
|
|
2638
|
-
|
|
2853
|
+
activeTimeBasis: ActiveTimeBasis;
|
|
2639
2854
|
/**
|
|
2640
|
-
*
|
|
2855
|
+
* Merged active wall-clock ranges. Their summed duration equals
|
|
2856
|
+
* `activeTimeMs`; the aggregator unions them across sessions so overlapping
|
|
2857
|
+
* (concurrent) work is not double-counted in billable totals.
|
|
2641
2858
|
*/
|
|
2642
|
-
|
|
2859
|
+
activeIntervals: IsoInterval[];
|
|
2860
|
+
commandCount: number;
|
|
2861
|
+
fileChangedCount: number;
|
|
2862
|
+
decisionCount: number;
|
|
2863
|
+
eventCount: number;
|
|
2864
|
+
tokens: TokenTotals;
|
|
2865
|
+
availability: MeasureAvailability;
|
|
2866
|
+
/** ended_at < started_at (clock skew): span was clamped to 0. */
|
|
2867
|
+
spanClamped: boolean;
|
|
2868
|
+
/** events.jsonl could not be read: action / time counts are 0 + untrustworthy. */
|
|
2869
|
+
eventsUnreadable: boolean;
|
|
2870
|
+
};
|
|
2871
|
+
type SourceWorkStats = {
|
|
2872
|
+
sourceKind: SessionSourceKind;
|
|
2873
|
+
sessionCount: number;
|
|
2874
|
+
sessionSpanMs: number;
|
|
2875
|
+
commandTimeMs: number;
|
|
2876
|
+
activeTimeMs: number;
|
|
2877
|
+
commandCount: number;
|
|
2878
|
+
fileChangedCount: number;
|
|
2879
|
+
decisionCount: number;
|
|
2880
|
+
eventCount: number;
|
|
2881
|
+
tokens: TokenTotals;
|
|
2882
|
+
/** Every session of this kind reports real command time. */
|
|
2883
|
+
commandTimeReliable: boolean;
|
|
2884
|
+
/** At least one session of this kind captured token totals. */
|
|
2885
|
+
tokensAvailable: boolean;
|
|
2886
|
+
};
|
|
2887
|
+
type StatusCount = {
|
|
2888
|
+
status: SessionStatus;
|
|
2889
|
+
count: number;
|
|
2890
|
+
};
|
|
2891
|
+
/**
|
|
2892
|
+
* One calendar day of the time x volume billing view. `billableActiveTimeMs` is
|
|
2893
|
+
* the union of active intervals starting on this date (so per-day sums to the
|
|
2894
|
+
* de-duplicated workspace total); volume is attributed to each session's
|
|
2895
|
+
* `started_at` date.
|
|
2896
|
+
*/
|
|
2897
|
+
type DayWorkStats = {
|
|
2898
|
+
/** Calendar date `YYYY-MM-DD` in the report timezone. */
|
|
2899
|
+
date: string;
|
|
2900
|
+
billableActiveTimeMs: number;
|
|
2901
|
+
sessionCount: number;
|
|
2902
|
+
commandCount: number;
|
|
2903
|
+
fileChangedCount: number;
|
|
2904
|
+
decisionCount: number;
|
|
2905
|
+
tokens: TokenTotals;
|
|
2906
|
+
};
|
|
2907
|
+
type WorkStatsTotals = {
|
|
2908
|
+
sessionCount: number;
|
|
2909
|
+
openSessionCount: number;
|
|
2910
|
+
sessionSpanMs: number;
|
|
2911
|
+
commandTimeMs: number;
|
|
2912
|
+
/** Naive sum of per-session active time; double-counts overlapping sessions. */
|
|
2913
|
+
activeTimeMs: number;
|
|
2643
2914
|
/**
|
|
2644
|
-
*
|
|
2645
|
-
*
|
|
2646
|
-
*
|
|
2647
|
-
* the child if the parent is forcibly terminated). The runner takes no
|
|
2648
|
-
* action if the callback throws.
|
|
2915
|
+
* Billable active time: the UNION of every session's active intervals, so
|
|
2916
|
+
* concurrent sessions do not double-count human wall-clock. Equals
|
|
2917
|
+
* `activeTimeMs` when no sessions overlap, and is smaller when they do.
|
|
2649
2918
|
*/
|
|
2650
|
-
|
|
2651
|
-
|
|
2652
|
-
|
|
2653
|
-
|
|
2654
|
-
|
|
2655
|
-
|
|
2656
|
-
/** `
|
|
2657
|
-
|
|
2658
|
-
|
|
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;
|
|
2919
|
+
billableActiveTimeMs: number;
|
|
2920
|
+
commandCount: number;
|
|
2921
|
+
fileChangedCount: number;
|
|
2922
|
+
decisionCount: number;
|
|
2923
|
+
eventCount: number;
|
|
2924
|
+
tokens: TokenTotals;
|
|
2925
|
+
/** No `claude-code-import` sessions present, so command time is workspace-wide real. */
|
|
2926
|
+
commandTimeReliable: boolean;
|
|
2927
|
+
tokensAvailable: boolean;
|
|
2667
2928
|
};
|
|
2668
|
-
type
|
|
2669
|
-
|
|
2929
|
+
type WorkStatsResult = {
|
|
2930
|
+
generatedAt: string;
|
|
2931
|
+
/** Idle-gap cap applied to active time (methodology lock). */
|
|
2932
|
+
activeGapCapMs: number;
|
|
2933
|
+
/** IANA timezone used to bucket {@link WorkStatsResult.byDay}. */
|
|
2934
|
+
timeZone: string;
|
|
2935
|
+
totals: WorkStatsTotals;
|
|
2936
|
+
/** Per session, started_at ascending (loadSessionEntries order). */
|
|
2937
|
+
sessions: SessionWorkStats[];
|
|
2938
|
+
bySource: SourceWorkStats[];
|
|
2939
|
+
byStatus: StatusCount[];
|
|
2940
|
+
/** Per-day time x volume billing view, date ascending. */
|
|
2941
|
+
byDay: DayWorkStats[];
|
|
2670
2942
|
};
|
|
2943
|
+
/**
|
|
2944
|
+
* Aggregate work + engaged-time across the workspace's sessions.
|
|
2945
|
+
*
|
|
2946
|
+
* Honesty note: this returns a LABELED SET of measures, not one number. Token
|
|
2947
|
+
* volume (when captured) is the most direct "how much the AI produced" signal.
|
|
2948
|
+
* The time measures are proxies, ordered from most to least billing-relevant:
|
|
2949
|
+
*
|
|
2950
|
+
* - `billableActiveTimeMs` (totals) is the headline for billing human harness
|
|
2951
|
+
* labor: the UNION of every session's active intervals, so two sessions run
|
|
2952
|
+
* concurrently do not bill the same wall-clock twice. `activeTimeMs` is the
|
|
2953
|
+
* naive sum, kept only to expose the overlap delta.
|
|
2954
|
+
* - Per-session active time is derived from the session's ENGAGED series. For
|
|
2955
|
+
* imported sessions this is the genuine engagement timestamps captured at
|
|
2956
|
+
* import (conversation turns plus action events), so design discussion that
|
|
2957
|
+
* produced few tool calls is still counted; idle gaps over `ACTIVE_GAP_CAP_MS`
|
|
2958
|
+
* (5 min) are not credited. Live sessions and pre-v2 imports lack that signal
|
|
2959
|
+
* and fall back to the action-event stream (`activeTimeBasis: "events"`).
|
|
2960
|
+
* - `sessionSpanMs` overcounts (includes idle) and `commandTimeMs` is
|
|
2961
|
+
* shell-execution only (0 for `claude-code-import`); both are kept as context.
|
|
2962
|
+
*
|
|
2963
|
+
* The per-day view buckets the union intervals by `timeZone` (logs are UTC, so
|
|
2964
|
+
* a billing day needs an explicit zone). A union interval crossing local
|
|
2965
|
+
* midnight is attributed to its start day; per-day time still sums to the
|
|
2966
|
+
* billable total. Availability flags let callers caveat each measure.
|
|
2967
|
+
*
|
|
2968
|
+
* Session enumeration goes through {@link loadSessionEntries} (the handoff /
|
|
2969
|
+
* decisions path), so `session.yaml`-broken sessions are skipped consistently.
|
|
2970
|
+
*/
|
|
2971
|
+
declare function computeWorkStats(input: WorkStatsInput): Promise<WorkStatsResult>;
|
|
2972
|
+
/**
|
|
2973
|
+
* Compute one session's work stats from its inner record + event list. Pure
|
|
2974
|
+
* and exported so a single-session surface (e.g. `basou session show`) can
|
|
2975
|
+
* reuse the exact same measures the workspace aggregator produces.
|
|
2976
|
+
*/
|
|
2977
|
+
declare function sessionWorkStatsFromEvents(sessionId: string, inner: Session["session"], events: ReadonlyArray<Event>, now: Date, eventsUnreadable?: boolean): SessionWorkStats;
|
|
2671
2978
|
|
|
2979
|
+
type AppendBasouGitignoreResult = {
|
|
2980
|
+
/** True if the block was appended (or the file was newly created). */
|
|
2981
|
+
readonly appended: boolean;
|
|
2982
|
+
};
|
|
2672
2983
|
/**
|
|
2673
|
-
*
|
|
2984
|
+
* Append Basou's default `.gitignore` block to `repositoryRoot/.gitignore`.
|
|
2985
|
+
*
|
|
2986
|
+
* The block contents are derived from the Basou v0.1 specification (the
|
|
2987
|
+
* standard ignore + commit recommendations). Callers must pass an absolute
|
|
2988
|
+
* path to a Git repository root.
|
|
2674
2989
|
*
|
|
2675
2990
|
* Behavior:
|
|
2676
|
-
* -
|
|
2677
|
-
*
|
|
2678
|
-
* the
|
|
2679
|
-
*
|
|
2680
|
-
* -
|
|
2681
|
-
*
|
|
2682
|
-
* strings (no streaming callbacks).
|
|
2683
|
-
* - `capture: "none"`: `stdio: ['inherit', 'inherit', 'inherit']`, the
|
|
2684
|
-
* child writes directly to the parent terminal in real time and
|
|
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`.
|
|
2991
|
+
* - If `.gitignore` does not exist, it is created with the Basou block.
|
|
2992
|
+
* - If a line starting with `# Basou - default ignore` is already present,
|
|
2993
|
+
* the file is left untouched and `appended: false` is returned
|
|
2994
|
+
* (idempotent).
|
|
2995
|
+
* - If `.gitignore` is a symlink, the link is followed and the target file
|
|
2996
|
+
* is updated. Symlinks are not rejected.
|
|
2693
2997
|
*
|
|
2694
|
-
*
|
|
2695
|
-
*
|
|
2696
|
-
*
|
|
2697
|
-
* `findErrorCode` when needed.
|
|
2998
|
+
* On I/O failure throws Error with a pathless message
|
|
2999
|
+
* (`Failed to read .gitignore` / `Failed to write .gitignore`) and the
|
|
3000
|
+
* original native error attached as `cause`.
|
|
2698
3001
|
*/
|
|
2699
|
-
declare
|
|
2700
|
-
run(command: string, args: readonly string[], options: RunOptions): Promise<RunResult>;
|
|
2701
|
-
}
|
|
3002
|
+
declare function appendBasouGitignore(repositoryRoot: string): Promise<AppendBasouGitignoreResult>;
|
|
2702
3003
|
|
|
2703
3004
|
/**
|
|
2704
|
-
*
|
|
2705
|
-
*
|
|
2706
|
-
*
|
|
2707
|
-
*
|
|
2708
|
-
|
|
3005
|
+
* The two lock scopes basou uses. `task` guards the read-modify-write window
|
|
3006
|
+
* around a single `task.md`; `session` guards the events.jsonl append plus
|
|
3007
|
+
* surrounding `session.yaml` mutation for a single session. Two scopes use
|
|
3008
|
+
* different lockfile names so they never collide on disk.
|
|
3009
|
+
*/
|
|
3010
|
+
type LockScope = "task" | "session";
|
|
3011
|
+
type LockHandle = {
|
|
3012
|
+
/**
|
|
3013
|
+
* Release the lock by unlinking the lockfile. Best-effort: any unlink error
|
|
3014
|
+
* is swallowed so a doubled release does not raise, and disk state never
|
|
3015
|
+
* holds a stranded lockfile after the caller's `finally` block.
|
|
3016
|
+
*/
|
|
3017
|
+
release: () => Promise<void>;
|
|
3018
|
+
};
|
|
3019
|
+
/**
|
|
3020
|
+
* Acquire an advisory lock at `<paths.locks>/<scope>_<id>.lock` for the
|
|
3021
|
+
* lifetime of the returned handle. Lockfile body records the holder's pid
|
|
3022
|
+
* and acquire timestamp so a competitor can detect stale locks left by a
|
|
3023
|
+
* SIGINT'd CLI run and recover automatically.
|
|
2709
3024
|
*
|
|
2710
|
-
*
|
|
2711
|
-
*
|
|
3025
|
+
* Acquisition strategy:
|
|
3026
|
+
* 1. {@link atomicCreate} the lockfile (POSIX link(2) + EEXIST).
|
|
3027
|
+
* 2. On EEXIST, probe the existing lockfile via {@link isStaleLock}.
|
|
3028
|
+
* - If stale (= holder pid is dead or lock is older than
|
|
3029
|
+
* {@link STALE_LOCK_MAX_AGE_MS}), `unlink` the stale file and retry
|
|
3030
|
+
* the atomic create once.
|
|
3031
|
+
* - If still EEXIST after the retry (= another competitor won the race),
|
|
3032
|
+
* throw `"Lock is held by another process"`.
|
|
3033
|
+
* - If the holder is alive, throw `"Lock is held by another process"`
|
|
3034
|
+
* without retrying.
|
|
3035
|
+
*
|
|
3036
|
+
* The caller MUST call `release()` (typically from a `finally` block); the
|
|
3037
|
+
* `process.exit()` path or a fatal crash relies on stale-lock detection on
|
|
3038
|
+
* the next acquire to recover.
|
|
2712
3039
|
*/
|
|
2713
|
-
|
|
3040
|
+
declare function acquireLock(paths: BasouPaths, scope: LockScope, resourceId: string): Promise<LockHandle>;
|
|
3041
|
+
|
|
2714
3042
|
/**
|
|
2715
|
-
*
|
|
2716
|
-
*
|
|
3043
|
+
* Inputs for {@link createManifest}. Optional fields drop out of the
|
|
3044
|
+
* resulting Manifest entirely (they are not emitted as `null`/`undefined`
|
|
3045
|
+
* in YAML); pass `null` for `repositoryUrl` to keep an explicit `null`.
|
|
3046
|
+
*/
|
|
3047
|
+
type CreateManifestInput = {
|
|
3048
|
+
workspaceName: string;
|
|
3049
|
+
projectName?: string;
|
|
3050
|
+
projectDescription?: string;
|
|
3051
|
+
repositoryUrl?: string | null;
|
|
3052
|
+
/** Override for tests; defaults to `new Date()`. */
|
|
3053
|
+
now?: Date;
|
|
3054
|
+
/** Override for tests; defaults to a freshly generated `ws_<ULID>`. */
|
|
3055
|
+
workspaceId?: PrefixedId<"ws">;
|
|
3056
|
+
};
|
|
3057
|
+
/**
|
|
3058
|
+
* Build a fresh Manifest object that satisfies the manifest schema's
|
|
3059
|
+
* minimum shape. Performs no I/O. Returned object is parse-validated by
|
|
3060
|
+
* `ManifestSchema`.
|
|
3061
|
+
*/
|
|
3062
|
+
declare function createManifest(input: CreateManifestInput): Manifest;
|
|
3063
|
+
/**
|
|
3064
|
+
* Write a Manifest to `paths.files.manifest`. Re-validates via
|
|
3065
|
+
* `ManifestSchema` before serialization.
|
|
2717
3066
|
*
|
|
2718
|
-
*
|
|
2719
|
-
|
|
2720
|
-
|
|
2721
|
-
|
|
2722
|
-
|
|
3067
|
+
* Refuses to overwrite an existing manifest unless `force: true`.
|
|
3068
|
+
*/
|
|
3069
|
+
declare function writeManifest(paths: BasouPaths, manifest: Manifest, options?: {
|
|
3070
|
+
force?: boolean;
|
|
3071
|
+
}): Promise<void>;
|
|
3072
|
+
/**
|
|
3073
|
+
* Read and parse a Manifest from `paths.files.manifest`. Throws if the file
|
|
3074
|
+
* is missing or contents fail `ManifestSchema` validation.
|
|
3075
|
+
*/
|
|
3076
|
+
declare function readManifest(paths: BasouPaths): Promise<Manifest>;
|
|
3077
|
+
|
|
3078
|
+
/** Marker line that begins the auto-generated region. */
|
|
3079
|
+
declare const GENERATED_START = "<!-- BASOU:GENERATED:START -->";
|
|
3080
|
+
/** Marker line that ends the auto-generated region. */
|
|
3081
|
+
declare const GENERATED_END = "<!-- BASOU:GENERATED:END -->";
|
|
3082
|
+
/**
|
|
3083
|
+
* Result of parsing a markdown body for the BASOU:GENERATED marker region.
|
|
2723
3084
|
*
|
|
2724
|
-
*
|
|
2725
|
-
*
|
|
3085
|
+
* The spec mandates strict line-level matching (see
|
|
3086
|
+
* `docs/spec/generated-markdown.md#102-marker-convention`): a marker is
|
|
3087
|
+
* only recognized when an entire line is exactly the marker string.
|
|
3088
|
+
* Leading/trailing whitespace, comment compression, and BOM are treated as
|
|
3089
|
+
* legacy formats (`no_markers`) so that re-generation refuses to silently
|
|
3090
|
+
* overwrite a mismatched manual edit.
|
|
2726
3091
|
*/
|
|
2727
|
-
|
|
3092
|
+
type MarkerSection = {
|
|
3093
|
+
kind: "ok";
|
|
3094
|
+
before: string;
|
|
3095
|
+
generated: string;
|
|
3096
|
+
after: string;
|
|
3097
|
+
} | {
|
|
3098
|
+
kind: "no_markers";
|
|
3099
|
+
} | {
|
|
3100
|
+
kind: "missing_start";
|
|
3101
|
+
} | {
|
|
3102
|
+
kind: "missing_end";
|
|
3103
|
+
} | {
|
|
3104
|
+
kind: "multiple_pairs";
|
|
3105
|
+
} | {
|
|
3106
|
+
kind: "wrong_order";
|
|
3107
|
+
};
|
|
2728
3108
|
/**
|
|
2729
|
-
* Read
|
|
2730
|
-
* `
|
|
2731
|
-
*
|
|
3109
|
+
* Read a markdown file as UTF-8 text. Returns `null` when the file does not
|
|
3110
|
+
* exist; throws `Error("Failed to read markdown file", { cause })` for other
|
|
3111
|
+
* I/O failures (pathless contract — never embed the absolute path in the
|
|
3112
|
+
* thrown `message`).
|
|
3113
|
+
*/
|
|
3114
|
+
declare function readMarkdownFile(filePath: string): Promise<string | null>;
|
|
3115
|
+
/**
|
|
3116
|
+
* Atomically write a markdown body via {@link atomicReplace}. The shared
|
|
3117
|
+
* helper handles the tmp-file + rename sequence, `wx` collision guard, and
|
|
3118
|
+
* best-effort tmp cleanup on failure.
|
|
3119
|
+
*
|
|
3120
|
+
* On any failure the original error is re-thrown as
|
|
3121
|
+
* `Error("Failed to write markdown file", { cause })` (pathless contract).
|
|
3122
|
+
*/
|
|
3123
|
+
declare function writeMarkdownFile(filePath: string, body: string): Promise<void>;
|
|
3124
|
+
/**
|
|
3125
|
+
* Parse a markdown body and identify the BASOU:GENERATED marker region.
|
|
3126
|
+
*
|
|
3127
|
+
* Returns one of six `kind` discriminants:
|
|
3128
|
+
* - `ok`: exactly one START line followed by exactly one END line in the
|
|
3129
|
+
* correct order. `before` / `generated` / `after` slice the original
|
|
3130
|
+
* text by character offsets so CRLF / LF are preserved verbatim outside
|
|
3131
|
+
* the marker region.
|
|
3132
|
+
* - `no_markers`: both START and END absent (legacy file / fresh write).
|
|
3133
|
+
* - `missing_start` / `missing_end`: exactly one of the pair is present.
|
|
3134
|
+
* - `multiple_pairs`: more than one START or END line.
|
|
3135
|
+
* - `wrong_order`: END appears before START.
|
|
2732
3136
|
*
|
|
2733
|
-
*
|
|
2734
|
-
*
|
|
2735
|
-
*
|
|
3137
|
+
* Matching is strict: leading/trailing whitespace, BOM, and comment
|
|
3138
|
+
* compression (`<!--BASOU:...-->`) all bypass the marker and are treated
|
|
3139
|
+
* as legacy content.
|
|
2736
3140
|
*/
|
|
2737
|
-
declare function
|
|
3141
|
+
declare function parseMarkers(content: string): MarkerSection;
|
|
2738
3142
|
/**
|
|
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.
|
|
3143
|
+
* Build the final markdown body by replacing the BASOU:GENERATED region.
|
|
2744
3144
|
*
|
|
2745
|
-
*
|
|
2746
|
-
* -
|
|
2747
|
-
*
|
|
2748
|
-
* -
|
|
2749
|
-
* `ahead`/`behind` omitted
|
|
2750
|
-
* - **no remote / no upstream tracking**: `ahead`/`behind` omitted
|
|
3145
|
+
* - `existing === null` (no file yet): return `<START>\n<generated>\n<END>\n`.
|
|
3146
|
+
* - existing parses to `ok`: replace the marked region and keep everything
|
|
3147
|
+
* before START and after END untouched (preserving manual additions).
|
|
3148
|
+
* - any other parse result: throw a pathless error referencing `fileLabel`.
|
|
2751
3149
|
*
|
|
2752
|
-
*
|
|
3150
|
+
* The caller passes `fileLabel` (e.g. `"handoff.md"` or `"decisions.md"`)
|
|
3151
|
+
* so the error message is informative without leaking an absolute path.
|
|
2753
3152
|
*/
|
|
2754
|
-
declare function
|
|
3153
|
+
declare function renderWithMarkers(existing: string | null, generated: string, fileLabel: string): string;
|
|
2755
3154
|
|
|
2756
3155
|
/**
|
|
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).
|
|
3156
|
+
* Options for {@link importSessionFromJson}. All fields are optional.
|
|
3157
|
+
*
|
|
3158
|
+
* - `labelOverride` / `taskIdOverride` come from the CLI `--label` / `--task`
|
|
3159
|
+
* flags and win over the corresponding fields on the input payload.
|
|
3160
|
+
* - `dryRun` skips disk writes entirely and returns a preview result.
|
|
2766
3161
|
*/
|
|
2767
|
-
type
|
|
2768
|
-
|
|
2769
|
-
|
|
2770
|
-
|
|
3162
|
+
type ImportSessionOptions = {
|
|
3163
|
+
labelOverride?: string;
|
|
3164
|
+
taskIdOverride?: string;
|
|
3165
|
+
dryRun?: boolean;
|
|
2771
3166
|
};
|
|
2772
3167
|
/**
|
|
2773
|
-
* Result of
|
|
2774
|
-
*
|
|
2775
|
-
*
|
|
3168
|
+
* Result of a successful import. `finalStatus` is always the literal
|
|
3169
|
+
* `"imported"` (per the import-session lifecycle policy); `finalSourceKind`
|
|
3170
|
+
* mirrors the input's `session.source.kind` so round-trip imports preserve
|
|
3171
|
+
* provenance.
|
|
3172
|
+
*
|
|
3173
|
+
* `pathSanitizeReport` summarises how many path-shaped fields the importer
|
|
3174
|
+
* rewrote on the way in: `related_files[]` entries plus a single boolean
|
|
3175
|
+
* for `working_directory`. The CLI wrapper surfaces this as a one-line
|
|
3176
|
+
* stderr warning when the total is non-zero so the operator sees that
|
|
3177
|
+
* machine-private prefixes were stripped.
|
|
2776
3178
|
*/
|
|
2777
|
-
type
|
|
2778
|
-
|
|
3179
|
+
type ImportSessionResult = {
|
|
3180
|
+
sessionId: PrefixedId<"ses">;
|
|
3181
|
+
eventCount: number;
|
|
3182
|
+
finalStatus: SessionStatus;
|
|
3183
|
+
finalSourceKind: SessionSourceKind;
|
|
3184
|
+
pathSanitizeReport: {
|
|
3185
|
+
relatedFiles: number;
|
|
3186
|
+
workingDirectoryRewritten: boolean;
|
|
3187
|
+
};
|
|
2779
3188
|
};
|
|
2780
3189
|
/**
|
|
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`.
|
|
3190
|
+
* Import a round-trip JSON payload into `.basou/sessions/<new>/`. The caller
|
|
3191
|
+
* MUST validate the payload against {@link SessionImportPayloadSchema} first
|
|
3192
|
+
* and gate the `schema_version === "0.1.0"` literal check externally; this
|
|
3193
|
+
* function trusts both invariants.
|
|
2793
3194
|
*
|
|
2794
|
-
*
|
|
2795
|
-
*
|
|
2796
|
-
*
|
|
3195
|
+
* On success a fresh session ID is minted and a complete
|
|
3196
|
+
* `session.yaml` + `events.jsonl` pair is written atomically. On any post-
|
|
3197
|
+
* mkdir failure the session directory is removed best-effort so partial
|
|
3198
|
+
* imports do not leave `session_yaml_missing` half-states behind.
|
|
2797
3199
|
*
|
|
2798
|
-
*
|
|
2799
|
-
*
|
|
2800
|
-
*
|
|
3200
|
+
* Throws `Error` with one of the fixed messages enumerated by the import contract
|
|
3201
|
+
* §"Error messages" table; the original native error is attached as `cause`
|
|
3202
|
+
* for `--verbose` rendering.
|
|
2801
3203
|
*/
|
|
2802
|
-
declare function
|
|
3204
|
+
declare function importSessionFromJson(paths: BasouPaths, manifest: Manifest, payload: SessionImportPayload, options: ImportSessionOptions): Promise<ImportSessionResult>;
|
|
2803
3205
|
|
|
2804
3206
|
/**
|
|
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.
|
|
3207
|
+
* Walk the cause chain (up to `depth` levels) looking for an Error whose
|
|
3208
|
+
* errno-style `code` matches `code`. Returns true on the first match.
|
|
3209
|
+
* Resilient to wrapper depth changes so that ENOENT detection survives
|
|
3210
|
+
* future error-wrapping refactors.
|
|
2820
3211
|
*/
|
|
2821
|
-
|
|
3212
|
+
declare function findErrorCode(error: unknown, code: string, depth?: number): boolean;
|
|
3213
|
+
|
|
2822
3214
|
/**
|
|
2823
|
-
*
|
|
2824
|
-
*
|
|
3215
|
+
* Refuse to operate on `.basou` if it is a symlink or not a directory. This
|
|
3216
|
+
* prevents `writeStatus` from being tricked into writing `status.json`
|
|
3217
|
+
* outside the repository root via a swapped `.basou` symlink. Mirrors
|
|
3218
|
+
* `ensureBasouDirectory`'s lstat-based guard.
|
|
2825
3219
|
*
|
|
2826
|
-
*
|
|
2827
|
-
* callers can
|
|
3220
|
+
* If `.basou` is absent the underlying ENOENT is propagated (wrapped) so
|
|
3221
|
+
* callers can map it to "workspace not initialized" via `findErrorCode`.
|
|
2828
3222
|
*
|
|
2829
|
-
*
|
|
3223
|
+
* Note: this is a baseline safety net, not a TOCTOU fix — the directory
|
|
3224
|
+
* could still be replaced between this check and the subsequent write. The
|
|
3225
|
+
* goal is to detect already-swapped symlinks, not to race-proof the
|
|
3226
|
+
* filesystem.
|
|
2830
3227
|
*/
|
|
2831
|
-
declare function
|
|
2832
|
-
command: string;
|
|
2833
|
-
}>;
|
|
3228
|
+
declare function assertBasouRootSafe(rootPath: string): Promise<void>;
|
|
2834
3229
|
/**
|
|
2835
|
-
*
|
|
2836
|
-
*
|
|
2837
|
-
*
|
|
2838
|
-
* `
|
|
2839
|
-
* The signature is committed so that Step 12+ can implement raw_ref
|
|
2840
|
-
* generation without retrofitting the adapter scaffold.
|
|
3230
|
+
* Build a StatusSnapshot from a manifest plus the path layout, observing
|
|
3231
|
+
* each subdirectory's presence via `lstat`. Read-only with respect to the
|
|
3232
|
+
* workspace state; writes nothing. The result is re-validated by
|
|
3233
|
+
* `StatusSchema.parse` before being returned.
|
|
2841
3234
|
*
|
|
2842
|
-
* @
|
|
3235
|
+
* @param input.now Override for testing; defaults to `new Date()`.
|
|
2843
3236
|
*/
|
|
2844
|
-
declare function
|
|
2845
|
-
|
|
3237
|
+
declare function buildStatusSnapshot(input: {
|
|
3238
|
+
manifest: Manifest;
|
|
3239
|
+
paths: BasouPaths;
|
|
3240
|
+
now?: Date;
|
|
3241
|
+
}): Promise<StatusSnapshot>;
|
|
2846
3242
|
/**
|
|
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.
|
|
3243
|
+
* Atomically write a StatusSnapshot to `paths.files.status`.
|
|
2859
3244
|
*
|
|
2860
|
-
*
|
|
2861
|
-
*
|
|
3245
|
+
* Re-validates via `StatusSchema.parse` before any file I/O, so an invalid
|
|
3246
|
+
* snapshot throws synchronously and never overwrites the existing
|
|
3247
|
+
* `status.json`. Delegates the tmp-file + rename pass to {@link atomicReplace}.
|
|
2862
3248
|
*
|
|
2863
|
-
*
|
|
2864
|
-
*
|
|
3249
|
+
* **Precondition**: callers MUST invoke {@link assertBasouRootSafe} on
|
|
3250
|
+
* `paths.root` first to ensure `.basou` is a real directory and not a
|
|
3251
|
+
* swapped symlink. `writeStatus` does not redo this guard — it trusts the
|
|
3252
|
+
* caller — so a direct invocation without the guard could write
|
|
3253
|
+
* `status.json` outside the repository root.
|
|
2865
3254
|
*/
|
|
2866
|
-
declare function
|
|
3255
|
+
declare function writeStatus(paths: BasouPaths, snapshot: StatusSnapshot): Promise<void>;
|
|
2867
3256
|
/**
|
|
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`.
|
|
3257
|
+
* Read `.basou/status.json` for the current schema_version (0.1.0). This
|
|
3258
|
+
* is a cache reader only; cross-version migration is not supported here.
|
|
3259
|
+
* Older or newer status.json shapes will fail `StatusSchema.parse` —
|
|
3260
|
+
* callers regenerate by calling `buildStatusSnapshot` + `writeStatus`.
|
|
2881
3261
|
*/
|
|
2882
|
-
declare function
|
|
3262
|
+
declare function readStatus(paths: BasouPaths): Promise<StatusSnapshot>;
|
|
2883
3263
|
|
|
2884
3264
|
/**
|
|
2885
|
-
*
|
|
2886
|
-
* into milliseconds.
|
|
3265
|
+
* Read a YAML file as `unknown`. Caller MUST validate via a zod schema.
|
|
2887
3266
|
*
|
|
2888
|
-
*
|
|
2889
|
-
*
|
|
2890
|
-
*
|
|
3267
|
+
* Throws Error with pathless message and the original native error attached
|
|
3268
|
+
* as `cause` for I/O failures and YAML parse errors. All fs and parse exits
|
|
3269
|
+
* go through fixed messages so absolute paths cannot leak via `error.message`.
|
|
3270
|
+
*/
|
|
3271
|
+
declare function readYamlFile(filePath: string): Promise<unknown>;
|
|
3272
|
+
/**
|
|
3273
|
+
* Write a value as YAML using {@link atomicReplace} for crash-resistant
|
|
3274
|
+
* atomicity. The shared helper handles the tmp-file + rename sequence,
|
|
3275
|
+
* `wx` collision guard, and best-effort tmp cleanup on failure. This
|
|
3276
|
+
* wrapper adds the YAML serialisation and the pathless error vocabulary.
|
|
3277
|
+
*/
|
|
3278
|
+
declare function writeYamlFile(filePath: string, value: unknown): Promise<void>;
|
|
3279
|
+
/**
|
|
3280
|
+
* Atomically create a new YAML file. Like {@link writeYamlFile} but
|
|
3281
|
+
* delegates to {@link atomicCreate} so a pre-existing target fails with
|
|
3282
|
+
* EEXIST instead of being silently overwritten.
|
|
2891
3283
|
*
|
|
2892
|
-
*
|
|
2893
|
-
*
|
|
3284
|
+
* Used by `basou approval approve` / `reject` to write the resolved-side
|
|
3285
|
+
* YAML, so a concurrent resolver cannot overwrite an already-resolved
|
|
3286
|
+
* approval.
|
|
2894
3287
|
*
|
|
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.
|
|
3288
|
+
* Throws `Error("Failed to write YAML file", { cause })` on failure; if
|
|
3289
|
+
* `cause.code === "EEXIST"` the caller can detect a target-exists race.
|
|
2900
3290
|
*/
|
|
2901
|
-
declare function
|
|
3291
|
+
declare function linkYamlFile(filePath: string, value: unknown): Promise<void>;
|
|
3292
|
+
/**
|
|
3293
|
+
* Overwrite an existing YAML file atomically. Like {@link writeYamlFile}
|
|
3294
|
+
* but with a distinct pathless message label, used for files that
|
|
3295
|
+
* legitimately need in-place mutation (e.g. session.yaml's status /
|
|
3296
|
+
* ended_at lifecycle updates).
|
|
3297
|
+
*/
|
|
3298
|
+
declare function overwriteYamlFile(filePath: string, value: unknown): Promise<void>;
|
|
2902
3299
|
|
|
2903
3300
|
/**
|
|
2904
3301
|
* Version of the `@basou/core` package, aligned with `manifest.yaml`'s
|
|
@@ -2906,4 +3303,4 @@ declare function parseDuration(input: string): number;
|
|
|
2906
3303
|
*/
|
|
2907
3304
|
declare const BASOU_CORE_VERSION = "0.1.0";
|
|
2908
3305
|
|
|
2909
|
-
export { type AdapterOutputEvent, type AppendBasouGitignoreResult, type AppendEventToExistingInput, type AppendEventToExistingResult, type Approval, type ApprovalApprovedEvent, type ApprovalExpiredEvent, ApprovalIdSchema, type ApprovalLocation, type ApprovalRejectedEvent, type ApprovalRequestedEvent, ApprovalSchema, type ApprovalStatus, ApprovalStatusSchema, type ArchiveTaskInput, type ArchiveTaskResult, type AttachTaskInput, type AttachUpdateTaskStatusInput, type AttachableStatus, BASOU_CORE_VERSION, type BasouPaths, type CaptureMode, ChildProcessRunner, type CommandExecutedEvent, type CommandLookup, type CreateAdHocSessionInput, type CreateAdHocSessionResult, type CreateAdHocTaskInput, type CreateManifestInput, type CreateTaskInput, type CreateTaskResult, DecisionIdSchema, type DecisionRecordedEvent, type DecisionsRendererInput, type DecisionsRendererResult, type DeleteTaskInput, type DeleteTaskResult, type DiffResult, type EditTaskInput, type EditTaskResult, type Event, EventIdSchema, EventSchema, EventSourceSchema, FailedToFinalizeError, type FileChange, type FileChangeStatus, type FileChangedEvent, GENERATED_END, GENERATED_START, type GitSnapshot, type GitSnapshotEvent, type HandoffRendererInput, type HandoffRendererResult, ID_PREFIXES, type IdPrefix, type ImportSessionOptions, type ImportSessionResult, IsoTimestampSchema, type LoadSessionEntriesOptions, type LoadTaskEntriesOptions, type LoadedApproval, type LockHandle, type LockScope, type Manifest, ManifestSchema, type MarkerSection, type NoteAddedEvent, type PrefixedId, type ProcessRunner, type ReconcileAllResult, type ReconcileAllTasksInput, type ReconcileAllTasksOptions, type ReconcileFailure, type ReconcileResult, type ReconcileTaskInput, type RefreshLinkageInput, type RefreshLinkageResult, type ReplayOptions, type ReplayWarning, type RiskLevel, RiskLevelSchema, type RunOptions, type RunResult, STUCK_THRESHOLD_MS, type SanitizePathOptions, type SanitizeRelatedFilesResult, SchemaVersionSchema, type Session, type SessionEndedEvent, type SessionEntry, SessionIdSchema, type SessionImportPayload, SessionImportPayloadSchema, type SessionInnerImportInput, SessionInnerImportSchema, SessionSchema, type SessionSkipReason, type SessionSourceKind, SessionSourceKindSchema, type SessionStartedEvent, type SessionStatus, type SessionStatusChangedEvent, SessionStatusSchema, StatusSchema, type StatusSnapshot, type SuspectReason, type Task, type TaskArchivedEvent, type TaskCreatedEvent, type TaskDeletedEvent, type TaskDocument, TaskIdSchema, type TaskLinkageRefreshedEvent, type TaskReconciledEvent, TaskSchema, type TaskSkipReason, type TaskStatus, type TaskStatusChangedEvent, TaskStatusSchema, TaskWriteAfterEventError, type TaskWriteAfterEventPhase, type UpdateAdHocTaskStatusInput, type UpdateTaskStatusInput, type UpdateTaskStatusResult, WorkspaceIdSchema, type WriteTaskFileMode, acquireLock, appendBasouGitignore, appendEvent, appendEventToExistingSession, archiveTask, assertBasouRootSafe, basouPaths, buildStatusSnapshot, classifySuspect, claudeCodeAdapterMetadata, createAdHocSessionWithEvent, createManifest, createTaskWithEvent, deleteTask, editTask, ensureBasouDirectory, enumerateApprovals, enumerateArchivedTaskIds, enumerateSessionDirs, enumerateTaskIds, findErrorCode, getDiff, getSnapshot, importSessionFromJson, isLazyExpired, isValidPrefixedId, linkYamlFile, loadApproval, loadSessionEntries, loadTaskEntries, overwriteYamlFile, parseDuration, parseMarkers, prefixedUlid, readAllEvents, readManifest, readMarkdownFile, readSessionYaml, readStatus, readTaskFile, readTaskFileWithArchiveFallback, readYamlFile, reconcileAllTasks, reconcileTask, refreshTaskLinkedSessions, renderDecisions, renderHandoff, renderWithMarkers, replayEvents, resolveClaudeCodeCommand, resolveRepositoryRoot, resolveSessionId, resolveTaskId, sanitizePath, sanitizeRelatedFiles, sanitizeWorkingDirectory, summarizeAdapterOutput, tryRemoteUrl, ulid, updateTaskStatusWithEvent, writeEventsBulk, writeManifest, writeMarkdownFile, writeStatus, writeTaskFile, writeYamlFile };
|
|
3306
|
+
export { ACTIVE_GAP_CAP_MS, type ActiveTimeBasis, type AdapterOutputEvent, type AppendBasouGitignoreResult, type AppendEventToExistingInput, type AppendEventToExistingResult, type Approval, type ApprovalApprovedEvent, type ApprovalExpiredEvent, ApprovalIdSchema, type ApprovalLocation, type ApprovalRejectedEvent, type ApprovalRequestedEvent, ApprovalSchema, type ApprovalStatus, ApprovalStatusSchema, type ArchiveTaskInput, type ArchiveTaskResult, type AttachTaskInput, type AttachUpdateTaskStatusInput, type AttachableStatus, BASOU_CORE_VERSION, type BasouPaths, CLAUDE_IMPORT_SOURCE, CODEX_IMPORT_SOURCE, type CaptureMode, ChildProcessRunner, type ClaudeTranscriptRecord, type ClaudeTranscriptToPayloadOptions, type CodexRolloutRecord, type CodexRolloutToPayloadOptions, type CommandExecutedEvent, type CommandLookup, type CreateAdHocSessionInput, type CreateAdHocSessionResult, type CreateAdHocTaskInput, type CreateManifestInput, type CreateTaskInput, type CreateTaskResult, type DayWorkStats, DecisionIdSchema, type DecisionRecordedEvent, type DecisionsRendererInput, type DecisionsRendererResult, type DeleteTaskInput, type DeleteTaskResult, type DiffResult, type EditTaskInput, type EditTaskResult, type Event, EventIdSchema, EventSchema, EventSourceSchema, FailedToFinalizeError, type FileChange, type FileChangeStatus, type FileChangedEvent, GENERATED_END, GENERATED_START, type GitSnapshot, type GitSnapshotEvent, type HandoffRendererInput, type HandoffRendererResult, ID_PREFIXES, type IdPrefix, type ImportSessionOptions, type ImportSessionResult, IsoTimestampSchema, type LoadSessionEntriesOptions, type LoadTaskEntriesOptions, type LoadedApproval, type LockHandle, type LockScope, type Manifest, ManifestSchema, type MarkerSection, type MeasureAvailability, type NoteAddedEvent, type PrefixedId, type ProcessRunner, type ReconcileAllResult, type ReconcileAllTasksInput, type ReconcileAllTasksOptions, type ReconcileFailure, type ReconcileResult, type ReconcileTaskInput, type RefreshLinkageInput, type RefreshLinkageResult, type ReplayOptions, type ReplayWarning, type RiskLevel, RiskLevelSchema, type RunOptions, type RunResult, STUCK_THRESHOLD_MS, type SanitizePathOptions, type SanitizeRelatedFilesResult, SchemaVersionSchema, type Session, type SessionEndedEvent, type SessionEntry, SessionIdSchema, type SessionImportPayload, SessionImportPayloadSchema, type SessionInnerImportInput, SessionInnerImportSchema, type SessionMetrics, SessionMetricsSchema, SessionSchema, type SessionSkipReason, type SessionSourceKind, SessionSourceKindSchema, type SessionStartedEvent, type SessionStatus, type SessionStatusChangedEvent, SessionStatusSchema, type SessionWorkStats, type SourceWorkStats, type StatusCount, StatusSchema, type StatusSnapshot, type SuspectReason, type Task, type TaskArchivedEvent, type TaskCreatedEvent, type TaskDeletedEvent, type TaskDocument, TaskIdSchema, type TaskLinkageRefreshedEvent, type TaskReconciledEvent, TaskSchema, type TaskSkipReason, type TaskStatus, type TaskStatusChangedEvent, TaskStatusSchema, TaskWriteAfterEventError, type TaskWriteAfterEventPhase, type TokenTotals, type UpdateAdHocTaskStatusInput, type UpdateTaskStatusInput, type UpdateTaskStatusResult, type WorkStatsInput, type WorkStatsResult, type WorkStatsTotals, WorkspaceIdSchema, type WriteTaskFileMode, acquireLock, appendBasouGitignore, appendEvent, appendEventToExistingSession, archiveTask, assertBasouRootSafe, basouPaths, buildStatusSnapshot, classifySuspect, claudeCodeAdapterMetadata, claudeTranscriptToImportPayload, codexRolloutToImportPayload, computeWorkStats, createAdHocSessionWithEvent, createManifest, createTaskWithEvent, deleteTask, editTask, ensureBasouDirectory, enumerateApprovals, enumerateArchivedTaskIds, enumerateSessionDirs, enumerateTaskIds, findErrorCode, getDiff, getSnapshot, importSessionFromJson, isLazyExpired, isValidPrefixedId, linkYamlFile, loadApproval, loadSessionEntries, loadTaskEntries, overwriteYamlFile, parseDuration, parseMarkers, prefixedUlid, readAllEvents, readManifest, readMarkdownFile, readSessionYaml, readStatus, readTaskFile, readTaskFileWithArchiveFallback, readYamlFile, reconcileAllTasks, reconcileTask, refreshTaskLinkedSessions, renderDecisions, renderHandoff, renderWithMarkers, replayEvents, resolveClaudeCodeCommand, resolveRepositoryRoot, resolveSessionId, resolveTaskId, sanitizePath, sanitizeRelatedFiles, sanitizeWorkingDirectory, sessionWorkStatsFromEvents, summarizeAdapterOutput, tryRemoteUrl, ulid, updateTaskStatusWithEvent, writeEventsBulk, writeManifest, writeMarkdownFile, writeStatus, writeTaskFile, writeYamlFile };
|