@basou/core 0.18.0 → 0.20.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +98 -1
- package/dist/index.js +241 -67
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/schemas/event.schema.json +7 -0
- package/schemas/session-import.schema.json +7 -0
package/dist/index.js
CHANGED
|
@@ -21,6 +21,73 @@ function summarizeAdapterOutput(_stream, _raw) {
|
|
|
21
21
|
throw new Error("adapter_output summary is not implemented in this release");
|
|
22
22
|
}
|
|
23
23
|
|
|
24
|
+
// src/adapters/claude-code/stop-hook.ts
|
|
25
|
+
var DEFAULT_STOP_HOOK_MIN_ACTIONS = 5;
|
|
26
|
+
var CAPTURE_COMMAND_PATTERN = /(?:^|[\n;&|(])\s*basou\s+(?:decision\s+(?:capture|record)|note)\b/;
|
|
27
|
+
var FILE_EDIT_TOOLS = /* @__PURE__ */ new Set(["Edit", "Write", "NotebookEdit"]);
|
|
28
|
+
function evaluateStopHook(input) {
|
|
29
|
+
const minActions = input.minActions ?? DEFAULT_STOP_HOOK_MIN_ACTIONS;
|
|
30
|
+
if (input.stopHookActive) {
|
|
31
|
+
return { kind: "silent", reason: "stop_hook_active", commandCount: 0, fileCount: 0 };
|
|
32
|
+
}
|
|
33
|
+
let commandCount = 0;
|
|
34
|
+
let fileCount = 0;
|
|
35
|
+
let captured = false;
|
|
36
|
+
for (const record of input.records) {
|
|
37
|
+
if (readString(record.type) !== "assistant") continue;
|
|
38
|
+
for (const tool of toolUsesOf(record)) {
|
|
39
|
+
const name = readString(tool.name);
|
|
40
|
+
if (name === void 0) continue;
|
|
41
|
+
if (name === "Bash") {
|
|
42
|
+
commandCount += 1;
|
|
43
|
+
const input2 = isObject(tool.input) ? tool.input : void 0;
|
|
44
|
+
const command = input2 !== void 0 ? readString(input2.command) : void 0;
|
|
45
|
+
if (command !== void 0 && CAPTURE_COMMAND_PATTERN.test(command)) captured = true;
|
|
46
|
+
} else if (FILE_EDIT_TOOLS.has(name)) {
|
|
47
|
+
fileCount += 1;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
if (captured) {
|
|
52
|
+
return { kind: "silent", reason: "already_captured", commandCount, fileCount };
|
|
53
|
+
}
|
|
54
|
+
if (commandCount + fileCount < minActions) {
|
|
55
|
+
return { kind: "silent", reason: "not_substantive", commandCount, fileCount };
|
|
56
|
+
}
|
|
57
|
+
return {
|
|
58
|
+
kind: "nudge",
|
|
59
|
+
additionalContext: renderNudge(commandCount, fileCount),
|
|
60
|
+
commandCount,
|
|
61
|
+
fileCount
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
function renderNudge(commandCount, fileCount) {
|
|
65
|
+
const ran = `${commandCount} ${commandCount === 1 ? "command" : "commands"}`;
|
|
66
|
+
const edited = `${fileCount} ${fileCount === 1 ? "file" : "files"}`;
|
|
67
|
+
return [
|
|
68
|
+
`This session ran ${ran} and edited ${edited} but recorded no decisions or next step.`,
|
|
69
|
+
"If meaningful decisions were made (the chosen approach, rejected alternatives, and why) or there is a clear next step, capture them now so the next session can resume correctly:",
|
|
70
|
+
' - Decisions: run `basou decision capture` and pipe a JSON array (one object per decision; "title" required, plus optional rationale/alternatives/rejected_reason/linked_files; set "kind":"track" for an unfinished strategic direction).',
|
|
71
|
+
' - Next step: run `basou note "<what you would do next>"`.',
|
|
72
|
+
"If nothing is worth capturing, just stop \u2014 do not invent decisions."
|
|
73
|
+
].join("\n");
|
|
74
|
+
}
|
|
75
|
+
function readString(value) {
|
|
76
|
+
return typeof value === "string" && value.length > 0 ? value : void 0;
|
|
77
|
+
}
|
|
78
|
+
function isObject(value) {
|
|
79
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
80
|
+
}
|
|
81
|
+
function toolUsesOf(record) {
|
|
82
|
+
const message = isObject(record.message) ? record.message : void 0;
|
|
83
|
+
const content = message !== void 0 && Array.isArray(message.content) ? message.content : [];
|
|
84
|
+
const result = [];
|
|
85
|
+
for (const item of content) {
|
|
86
|
+
if (isObject(item) && readString(item.type) === "tool_use") result.push(item);
|
|
87
|
+
}
|
|
88
|
+
return result;
|
|
89
|
+
}
|
|
90
|
+
|
|
24
91
|
// src/ids/ulid.ts
|
|
25
92
|
import { isValid as isValidUlid, monotonicFactory } from "ulid";
|
|
26
93
|
var ID_PREFIXES = Object.freeze(["ws", "task", "ses", "evt", "appr", "decision"]);
|
|
@@ -128,21 +195,21 @@ function claudeTranscriptToImportPayload(records, options) {
|
|
|
128
195
|
const engagementTsMs = [];
|
|
129
196
|
const seenEngagementMessageIds = /* @__PURE__ */ new Set();
|
|
130
197
|
for (const record of records) {
|
|
131
|
-
const ts =
|
|
198
|
+
const ts = readString2(record.timestamp);
|
|
132
199
|
if (ts === void 0) continue;
|
|
133
200
|
if (minTs === void 0 || Date.parse(ts) < Date.parse(minTs)) minTs = ts;
|
|
134
201
|
if (maxTs === void 0 || Date.parse(ts) > Date.parse(maxTs)) maxTs = ts;
|
|
135
|
-
if (workingDir === void 0) workingDir =
|
|
136
|
-
if (claudeSessionId === void 0) claudeSessionId =
|
|
202
|
+
if (workingDir === void 0) workingDir = readString2(record.cwd);
|
|
203
|
+
if (claudeSessionId === void 0) claudeSessionId = readString2(record.sessionId);
|
|
137
204
|
if (record.isSidechain !== true) {
|
|
138
205
|
const tsMs = Date.parse(ts);
|
|
139
206
|
if (Number.isFinite(tsMs)) {
|
|
140
|
-
const recType =
|
|
207
|
+
const recType = readString2(record.type);
|
|
141
208
|
if (recType === "user") {
|
|
142
209
|
if (isHumanUserMessage(record)) engagementTsMs.push(tsMs);
|
|
143
210
|
} else if (recType === "assistant") {
|
|
144
|
-
const msg =
|
|
145
|
-
const mid = msg !== void 0 ?
|
|
211
|
+
const msg = isObject2(record.message) ? record.message : void 0;
|
|
212
|
+
const mid = msg !== void 0 ? readString2(msg.id) : void 0;
|
|
146
213
|
if (mid === void 0 || !seenEngagementMessageIds.has(mid)) {
|
|
147
214
|
if (mid !== void 0) seenEngagementMessageIds.add(mid);
|
|
148
215
|
engagementTsMs.push(tsMs);
|
|
@@ -150,11 +217,11 @@ function claudeTranscriptToImportPayload(records, options) {
|
|
|
150
217
|
}
|
|
151
218
|
}
|
|
152
219
|
}
|
|
153
|
-
if (
|
|
154
|
-
const message =
|
|
155
|
-
const usage = message !== void 0 &&
|
|
220
|
+
if (readString2(record.type) !== "assistant") continue;
|
|
221
|
+
const message = isObject2(record.message) ? record.message : void 0;
|
|
222
|
+
const usage = message !== void 0 && isObject2(message.usage) ? message.usage : void 0;
|
|
156
223
|
if (usage !== void 0) {
|
|
157
|
-
const messageId = message !== void 0 ?
|
|
224
|
+
const messageId = message !== void 0 ? readString2(message.id) : void 0;
|
|
158
225
|
const alreadyCounted = messageId !== void 0 && seenMessageIds.has(messageId);
|
|
159
226
|
if (!alreadyCounted) {
|
|
160
227
|
if (messageId !== void 0) seenMessageIds.add(messageId);
|
|
@@ -163,20 +230,20 @@ function claudeTranscriptToImportPayload(records, options) {
|
|
|
163
230
|
cachedInputTokens += readNonNegInt(usage.cache_read_input_tokens);
|
|
164
231
|
}
|
|
165
232
|
}
|
|
166
|
-
const cwd =
|
|
233
|
+
const cwd = readString2(record.cwd) ?? workingDir ?? ".";
|
|
167
234
|
for (const item of toolUses(record)) {
|
|
168
|
-
const name =
|
|
169
|
-
const input =
|
|
235
|
+
const name = readString2(item.name);
|
|
236
|
+
const input = isObject2(item.input) ? item.input : void 0;
|
|
170
237
|
if (input === void 0) continue;
|
|
171
238
|
if (name === "Bash") {
|
|
172
|
-
const command =
|
|
239
|
+
const command = readString2(input.command);
|
|
173
240
|
if (command !== void 0) {
|
|
174
241
|
derived.push(commandExecutedEvent(ts, placeholderSessionId, command, cwd));
|
|
175
242
|
}
|
|
176
243
|
continue;
|
|
177
244
|
}
|
|
178
245
|
if (name === "AskUserQuestion") {
|
|
179
|
-
const useId =
|
|
246
|
+
const useId = readString2(item.id);
|
|
180
247
|
const answers = useId !== void 0 ? askAnswers.get(useId) : void 0;
|
|
181
248
|
if (answers !== void 0) {
|
|
182
249
|
for (const [question, answer] of Object.entries(answers)) {
|
|
@@ -189,7 +256,7 @@ function claudeTranscriptToImportPayload(records, options) {
|
|
|
189
256
|
continue;
|
|
190
257
|
}
|
|
191
258
|
if (name === "Edit" || name === "Write" || name === "NotebookEdit") {
|
|
192
|
-
const path2 =
|
|
259
|
+
const path2 = readString2(input.file_path) ?? readString2(input.notebook_path);
|
|
193
260
|
if (path2 !== void 0) {
|
|
194
261
|
const changeType = name === "Write" ? "added" : "modified";
|
|
195
262
|
relatedFiles.add(path2);
|
|
@@ -291,35 +358,35 @@ function decisionRecordedEvent(occurredAt, sessionId, title) {
|
|
|
291
358
|
title
|
|
292
359
|
};
|
|
293
360
|
}
|
|
294
|
-
function
|
|
361
|
+
function readString2(value) {
|
|
295
362
|
return typeof value === "string" && value.length > 0 ? value : void 0;
|
|
296
363
|
}
|
|
297
364
|
function readNonNegInt(value) {
|
|
298
365
|
return typeof value === "number" && Number.isInteger(value) && value >= 0 ? value : 0;
|
|
299
366
|
}
|
|
300
|
-
function
|
|
367
|
+
function isObject2(value) {
|
|
301
368
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
302
369
|
}
|
|
303
370
|
function isHumanUserMessage(record) {
|
|
304
|
-
const message =
|
|
371
|
+
const message = isObject2(record.message) ? record.message : void 0;
|
|
305
372
|
if (message === void 0) return false;
|
|
306
373
|
const content = message.content;
|
|
307
374
|
if (typeof content === "string") return content.length > 0;
|
|
308
375
|
if (Array.isArray(content)) {
|
|
309
376
|
return content.some((block) => {
|
|
310
|
-
if (!
|
|
311
|
-
const type =
|
|
377
|
+
if (!isObject2(block)) return false;
|
|
378
|
+
const type = readString2(block.type);
|
|
312
379
|
return type !== void 0 && type !== "tool_result";
|
|
313
380
|
});
|
|
314
381
|
}
|
|
315
382
|
return false;
|
|
316
383
|
}
|
|
317
384
|
function toolUses(record) {
|
|
318
|
-
const message =
|
|
385
|
+
const message = isObject2(record.message) ? record.message : void 0;
|
|
319
386
|
const content = message !== void 0 && Array.isArray(message.content) ? message.content : [];
|
|
320
387
|
const result = [];
|
|
321
388
|
for (const item of content) {
|
|
322
|
-
if (
|
|
389
|
+
if (isObject2(item) && readString2(item.type) === "tool_use") {
|
|
323
390
|
result.push(item);
|
|
324
391
|
}
|
|
325
392
|
}
|
|
@@ -329,14 +396,14 @@ function indexAskAnswers(records) {
|
|
|
329
396
|
const byId = /* @__PURE__ */ new Map();
|
|
330
397
|
for (const record of records) {
|
|
331
398
|
const result = record.toolUseResult;
|
|
332
|
-
if (!
|
|
399
|
+
if (!isObject2(result)) continue;
|
|
333
400
|
const answers = result.answers;
|
|
334
|
-
if (!
|
|
335
|
-
const message =
|
|
401
|
+
if (!isObject2(answers)) continue;
|
|
402
|
+
const message = isObject2(record.message) ? record.message : void 0;
|
|
336
403
|
const content = message !== void 0 && Array.isArray(message.content) ? message.content : [];
|
|
337
404
|
for (const item of content) {
|
|
338
|
-
if (
|
|
339
|
-
const id =
|
|
405
|
+
if (isObject2(item) && readString2(item.type) === "tool_result") {
|
|
406
|
+
const id = readString2(item.tool_use_id);
|
|
340
407
|
if (id !== void 0) byId.set(id, answers);
|
|
341
408
|
}
|
|
342
409
|
}
|
|
@@ -360,31 +427,31 @@ function codexRolloutToImportPayload(records, options) {
|
|
|
360
427
|
const completions = [];
|
|
361
428
|
const completedTurnIds = /* @__PURE__ */ new Set();
|
|
362
429
|
for (const record of records) {
|
|
363
|
-
const ts =
|
|
430
|
+
const ts = readString3(record.timestamp);
|
|
364
431
|
if (ts === void 0) continue;
|
|
365
432
|
if (minTs === void 0 || Date.parse(ts) < Date.parse(minTs)) minTs = ts;
|
|
366
433
|
if (maxTs === void 0 || Date.parse(ts) > Date.parse(maxTs)) maxTs = ts;
|
|
367
|
-
const payload2 =
|
|
434
|
+
const payload2 = isObject3(record.payload) ? record.payload : void 0;
|
|
368
435
|
if (payload2 === void 0) continue;
|
|
369
|
-
if (
|
|
370
|
-
if (workingDir === void 0) workingDir =
|
|
371
|
-
if (codexSessionId === void 0) codexSessionId =
|
|
436
|
+
if (readString3(record.type) === "session_meta") {
|
|
437
|
+
if (workingDir === void 0) workingDir = readString3(payload2.cwd);
|
|
438
|
+
if (codexSessionId === void 0) codexSessionId = readString3(payload2.id);
|
|
372
439
|
continue;
|
|
373
440
|
}
|
|
374
|
-
if (
|
|
375
|
-
const info =
|
|
376
|
-
const totals = info !== void 0 &&
|
|
441
|
+
if (readString3(record.type) === "event_msg" && readString3(payload2.type) === "token_count") {
|
|
442
|
+
const info = isObject3(payload2.info) ? payload2.info : void 0;
|
|
443
|
+
const totals = info !== void 0 && isObject3(info.total_token_usage) ? info.total_token_usage : void 0;
|
|
377
444
|
if (totals !== void 0) lastTokenTotals = totals;
|
|
378
445
|
continue;
|
|
379
446
|
}
|
|
380
|
-
if (
|
|
381
|
-
const pt =
|
|
447
|
+
if (readString3(record.type) === "event_msg") {
|
|
448
|
+
const pt = readString3(payload2.type);
|
|
382
449
|
if (pt === "user_message" || pt === "agent_message" || pt === "task_started" || pt === "task_complete") {
|
|
383
450
|
const tsMs = Date.parse(ts);
|
|
384
451
|
if (Number.isFinite(tsMs)) engagementTsMs.push(tsMs);
|
|
385
452
|
}
|
|
386
453
|
if (pt === "task_complete") {
|
|
387
|
-
const turnId =
|
|
454
|
+
const turnId = readString3(payload2.turn_id);
|
|
388
455
|
if (turnId === void 0 || !completedTurnIds.has(turnId)) {
|
|
389
456
|
if (turnId !== void 0) completedTurnIds.add(turnId);
|
|
390
457
|
completions.push({
|
|
@@ -395,9 +462,9 @@ function codexRolloutToImportPayload(records, options) {
|
|
|
395
462
|
}
|
|
396
463
|
continue;
|
|
397
464
|
}
|
|
398
|
-
if (
|
|
399
|
-
if (
|
|
400
|
-
if (
|
|
465
|
+
if (readString3(record.type) !== "response_item") continue;
|
|
466
|
+
if (readString3(payload2.type) !== "function_call") continue;
|
|
467
|
+
if (readString3(payload2.name) !== "exec_command") continue;
|
|
401
468
|
const command = readExecCommand(payload2.arguments);
|
|
402
469
|
if (command === void 0) continue;
|
|
403
470
|
const cwd = command.workdir ?? workingDir ?? ".";
|
|
@@ -508,17 +575,17 @@ function commandExecutedEvent2(occurredAt, sessionId, command, cwd, outcome) {
|
|
|
508
575
|
duration_ms: outcome.durationMs
|
|
509
576
|
};
|
|
510
577
|
}
|
|
511
|
-
function
|
|
578
|
+
function readString3(value) {
|
|
512
579
|
return typeof value === "string" && value.length > 0 ? value : void 0;
|
|
513
580
|
}
|
|
514
581
|
function readNonNegInt2(value) {
|
|
515
582
|
return typeof value === "number" && Number.isInteger(value) && value >= 0 ? value : 0;
|
|
516
583
|
}
|
|
517
|
-
function
|
|
584
|
+
function isObject3(value) {
|
|
518
585
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
519
586
|
}
|
|
520
587
|
function readExecCommand(value) {
|
|
521
|
-
const raw =
|
|
588
|
+
const raw = readString3(value);
|
|
522
589
|
if (raw === void 0) return void 0;
|
|
523
590
|
let parsed;
|
|
524
591
|
try {
|
|
@@ -526,19 +593,19 @@ function readExecCommand(value) {
|
|
|
526
593
|
} catch {
|
|
527
594
|
return void 0;
|
|
528
595
|
}
|
|
529
|
-
if (!
|
|
530
|
-
const cmd =
|
|
596
|
+
if (!isObject3(parsed)) return void 0;
|
|
597
|
+
const cmd = readString3(parsed.cmd);
|
|
531
598
|
if (cmd === void 0) return void 0;
|
|
532
|
-
return { cmd, workdir:
|
|
599
|
+
return { cmd, workdir: readString3(parsed.workdir) };
|
|
533
600
|
}
|
|
534
601
|
function readCallId(value, outputs) {
|
|
535
|
-
const callId =
|
|
602
|
+
const callId = readString3(value);
|
|
536
603
|
return callId !== void 0 ? outputs.get(callId) : void 0;
|
|
537
604
|
}
|
|
538
605
|
function turnIntervalFromComplete(endTs, payload, startMsByTurnId) {
|
|
539
606
|
const endMs = Date.parse(endTs);
|
|
540
607
|
if (!Number.isFinite(endMs)) return void 0;
|
|
541
|
-
const turnId =
|
|
608
|
+
const turnId = readString3(payload.turn_id);
|
|
542
609
|
const indexedStart = turnId !== void 0 ? startMsByTurnId.get(turnId) : void 0;
|
|
543
610
|
const durationMs = readNonNegInt2(payload.duration_ms);
|
|
544
611
|
const startMs = indexedStart !== void 0 ? indexedStart : durationMs > 0 ? endMs - durationMs : void 0;
|
|
@@ -548,11 +615,11 @@ function turnIntervalFromComplete(endTs, payload, startMsByTurnId) {
|
|
|
548
615
|
function indexTaskStarts(records) {
|
|
549
616
|
const byTurnId = /* @__PURE__ */ new Map();
|
|
550
617
|
for (const record of records) {
|
|
551
|
-
if (
|
|
552
|
-
const payload =
|
|
553
|
-
if (payload === void 0 ||
|
|
554
|
-
const turnId =
|
|
555
|
-
const startMs = Date.parse(
|
|
618
|
+
if (readString3(record.type) !== "event_msg") continue;
|
|
619
|
+
const payload = isObject3(record.payload) ? record.payload : void 0;
|
|
620
|
+
if (payload === void 0 || readString3(payload.type) !== "task_started") continue;
|
|
621
|
+
const turnId = readString3(payload.turn_id);
|
|
622
|
+
const startMs = Date.parse(readString3(record.timestamp) ?? "");
|
|
556
623
|
if (turnId !== void 0 && Number.isFinite(startMs) && !byTurnId.has(turnId)) {
|
|
557
624
|
byTurnId.set(turnId, startMs);
|
|
558
625
|
}
|
|
@@ -574,12 +641,12 @@ function parseWallTimeMs(output) {
|
|
|
574
641
|
function indexOutputs(records) {
|
|
575
642
|
const byId = /* @__PURE__ */ new Map();
|
|
576
643
|
for (const record of records) {
|
|
577
|
-
if (
|
|
578
|
-
const payload =
|
|
644
|
+
if (readString3(record.type) !== "response_item") continue;
|
|
645
|
+
const payload = isObject3(record.payload) ? record.payload : void 0;
|
|
579
646
|
if (payload === void 0) continue;
|
|
580
|
-
if (
|
|
581
|
-
const callId =
|
|
582
|
-
const output =
|
|
647
|
+
if (readString3(payload.type) !== "function_call_output") continue;
|
|
648
|
+
const callId = readString3(payload.call_id);
|
|
649
|
+
const output = readString3(payload.output);
|
|
583
650
|
if (callId !== void 0 && output !== void 0) byId.set(callId, output);
|
|
584
651
|
}
|
|
585
652
|
return byId;
|
|
@@ -872,7 +939,17 @@ var DecisionRecordedEventSchema = BaseEventSchema.extend({
|
|
|
872
939
|
alternatives: z3.array(z3.string().min(1)).optional(),
|
|
873
940
|
rejected_reason: z3.string().nullable().optional(),
|
|
874
941
|
linked_events: z3.array(EventIdSchema).optional(),
|
|
875
|
-
linked_files: z3.array(z3.string().min(1).max(4096)).optional()
|
|
942
|
+
linked_files: z3.array(z3.string().min(1).max(4096)).optional(),
|
|
943
|
+
// `track` promotes a decision to a strategic, unfinished DIRECTION ("the next
|
|
944
|
+
// essential thing to build, and why") that orientation/handoff resurface every
|
|
945
|
+
// time until it is explicitly closed with `decision void` / supersede — as
|
|
946
|
+
// opposed to a point-in-time `decision`, which is only ever surfaced as the
|
|
947
|
+
// single latest one. This is the intent-continuity layer: a direction agreed
|
|
948
|
+
// in conversation otherwise sinks into the flat decision list and never carries
|
|
949
|
+
// to the next session. Absent (the default) is a plain `decision`, so all
|
|
950
|
+
// pre-existing decision_recorded events round-trip unchanged (additive optional
|
|
951
|
+
// => no schema_version bump; mirrors `note_added.kind`).
|
|
952
|
+
kind: z3.enum(["decision", "track"]).optional()
|
|
876
953
|
});
|
|
877
954
|
var DecisionVoidedEventSchema = BaseEventSchema.extend({
|
|
878
955
|
type: z3.literal("decision_voided"),
|
|
@@ -1487,6 +1564,7 @@ async function renderDecisions(input) {
|
|
|
1487
1564
|
rejectedReason: ev.rejected_reason,
|
|
1488
1565
|
linkedEvents: ev.linked_events,
|
|
1489
1566
|
linkedFiles: ev.linked_files,
|
|
1567
|
+
kind: ev.kind,
|
|
1490
1568
|
voided: void 0
|
|
1491
1569
|
});
|
|
1492
1570
|
} else if (ev.type === "decision_voided") {
|
|
@@ -1542,18 +1620,22 @@ async function formatDecisionsBody(args) {
|
|
|
1542
1620
|
return lines.join("\n");
|
|
1543
1621
|
}
|
|
1544
1622
|
for (const d of args.decisions) {
|
|
1623
|
+
const trackMark = d.kind === "track" ? " [TRACK]" : "";
|
|
1545
1624
|
if (d.voided !== void 0) {
|
|
1546
|
-
lines.push(`## ~~${d.decisionId}: ${d.title}~~ [VOIDED]`);
|
|
1625
|
+
lines.push(`## ~~${d.decisionId}: ${d.title}~~ [VOIDED]${trackMark}`);
|
|
1547
1626
|
lines.push("");
|
|
1548
1627
|
const supersededBy = d.voided.supersededBy !== void 0 ? `, superseded by ${d.voided.supersededBy}` : "";
|
|
1549
1628
|
const reason = typeof d.voided.reason === "string" && d.voided.reason.length > 0 ? `: ${d.voided.reason}` : "";
|
|
1550
1629
|
lines.push(`- \u26A0 VOIDED${reason}${supersededBy}`);
|
|
1551
1630
|
} else {
|
|
1552
|
-
lines.push(`## ${d.decisionId}: ${d.title}`);
|
|
1631
|
+
lines.push(`## ${d.decisionId}: ${d.title}${trackMark}`);
|
|
1553
1632
|
lines.push("");
|
|
1554
1633
|
}
|
|
1555
1634
|
const occurredDate = d.occurredAt.slice(0, 10);
|
|
1556
1635
|
lines.push(`- \u6C7A\u5B9A\u65E5: ${occurredDate}`);
|
|
1636
|
+
if (d.kind === "track" && d.voided === void 0) {
|
|
1637
|
+
lines.push("- \u7A2E\u5225: track (close \u307E\u3067 orient/handoff \u306B\u7D99\u7D9A\u8868\u793A)");
|
|
1638
|
+
}
|
|
1557
1639
|
lines.push(`- session: ${shortDecisionSessionId(d.sessionId)}`);
|
|
1558
1640
|
lines.push(`- \u5224\u65AD: ${d.title}`);
|
|
1559
1641
|
if (typeof d.rationale === "string" && d.rationale.length > 0) {
|
|
@@ -3876,6 +3958,7 @@ async function renderHandoff(input) {
|
|
|
3876
3958
|
if (input.onWarning !== void 0) loadOpts.onWarning = input.onWarning;
|
|
3877
3959
|
const entries = await loadSessionEntries(input.paths, loadOpts);
|
|
3878
3960
|
const decisions = [];
|
|
3961
|
+
const tracks = [];
|
|
3879
3962
|
const voidedDecisionIds = /* @__PURE__ */ new Set();
|
|
3880
3963
|
const tasksCreated = [];
|
|
3881
3964
|
const tasksStatusChanged = [];
|
|
@@ -3901,6 +3984,15 @@ async function renderHandoff(input) {
|
|
|
3901
3984
|
occurredAt: ev.occurred_at,
|
|
3902
3985
|
sessionId: entry.sessionId
|
|
3903
3986
|
});
|
|
3987
|
+
if (ev.kind === "track") {
|
|
3988
|
+
tracks.push({
|
|
3989
|
+
decisionId: ev.decision_id,
|
|
3990
|
+
title: ev.title,
|
|
3991
|
+
rationale: ev.rationale ?? null,
|
|
3992
|
+
occurredAt: ev.occurred_at,
|
|
3993
|
+
sessionId: entry.sessionId
|
|
3994
|
+
});
|
|
3995
|
+
}
|
|
3904
3996
|
} else if (ev.type === "decision_voided") {
|
|
3905
3997
|
voidedDecisionIds.add(ev.decision_id);
|
|
3906
3998
|
} else if (ev.type === "task_created") {
|
|
@@ -3936,6 +4028,10 @@ async function renderHandoff(input) {
|
|
|
3936
4028
|
break;
|
|
3937
4029
|
}
|
|
3938
4030
|
}
|
|
4031
|
+
const openTracks = tracks.filter((t) => !voidedDecisionIds.has(t.decisionId)).sort((a, b) => {
|
|
4032
|
+
const c = Date.parse(b.occurredAt) - Date.parse(a.occurredAt);
|
|
4033
|
+
return c !== 0 ? c : b.decisionId.localeCompare(a.decisionId);
|
|
4034
|
+
});
|
|
3939
4035
|
tasksCreated.sort((a, b) => {
|
|
3940
4036
|
const c = Date.parse(a.occurredAt) - Date.parse(b.occurredAt);
|
|
3941
4037
|
return c !== 0 ? c : a.taskId.localeCompare(b.taskId);
|
|
@@ -3980,6 +4076,7 @@ async function renderHandoff(input) {
|
|
|
3980
4076
|
latestActivityAt,
|
|
3981
4077
|
decisions,
|
|
3982
4078
|
latestDecision,
|
|
4079
|
+
openTracks,
|
|
3983
4080
|
pendingApprovalsCount,
|
|
3984
4081
|
suspectCount,
|
|
3985
4082
|
displayedFiles,
|
|
@@ -4065,6 +4162,23 @@ function formatHandoffBody(args) {
|
|
|
4065
4162
|
lines.push(`(${args.decisions.length} decisions total \u2014 see decisions.md)`);
|
|
4066
4163
|
}
|
|
4067
4164
|
lines.push("");
|
|
4165
|
+
if (args.openTracks.length > 0) {
|
|
4166
|
+
const TRACK_DISPLAY_LIMIT = 10;
|
|
4167
|
+
const shown = args.openTracks.slice(0, TRACK_DISPLAY_LIMIT);
|
|
4168
|
+
const overflow = args.openTracks.length - shown.length;
|
|
4169
|
+
lines.push("## \u672A\u5B8C\u30C8\u30E9\u30C3\u30AF (close \u307E\u3067\u7D99\u7D9A\u8868\u793A)");
|
|
4170
|
+
lines.push("");
|
|
4171
|
+
for (const t of shown) {
|
|
4172
|
+
lines.push(`- ${t.title} [${shortIdWithPrefix(t.decisionId)}]`);
|
|
4173
|
+
if (t.rationale !== null && t.rationale.trim() !== "") {
|
|
4174
|
+
lines.push(` - \u7406\u7531: ${handoffRationale(t.rationale)}`);
|
|
4175
|
+
}
|
|
4176
|
+
}
|
|
4177
|
+
if (overflow > 0) lines.push(`- ... +${overflow} more (see decisions.md)`);
|
|
4178
|
+
lines.push("");
|
|
4179
|
+
lines.push("\u5B8C\u4E86\u3057\u305F\u3089 `basou decision void <decision_id>` \u3067\u9589\u3058\u3066\u304F\u3060\u3055\u3044\u3002");
|
|
4180
|
+
lines.push("");
|
|
4181
|
+
}
|
|
4068
4182
|
lines.push("## \u672A\u6C7A\u4E8B\u9805");
|
|
4069
4183
|
lines.push("");
|
|
4070
4184
|
if (args.pendingApprovalsCount > 0) {
|
|
@@ -4149,6 +4263,11 @@ function formatHandoffBody(args) {
|
|
|
4149
4263
|
lines.push(sessionsLine);
|
|
4150
4264
|
return lines.join("\n");
|
|
4151
4265
|
}
|
|
4266
|
+
var HANDOFF_TRACK_RATIONALE_MAX = 240;
|
|
4267
|
+
function handoffRationale(rationale) {
|
|
4268
|
+
const oneLine = rationale.replace(/\s+/g, " ").trim();
|
|
4269
|
+
return oneLine.length > HANDOFF_TRACK_RATIONALE_MAX ? `${oneLine.slice(0, HANDOFF_TRACK_RATIONALE_MAX - 1)}\u2026` : oneLine;
|
|
4270
|
+
}
|
|
4152
4271
|
function suspectLabel(reason) {
|
|
4153
4272
|
if (reason === "events_say_ended_but_yaml_running") return " \u26A0 ended (yaml stale)";
|
|
4154
4273
|
if (reason === "running_no_end_event") return " \u26A0 no end event";
|
|
@@ -4502,6 +4621,7 @@ async function summarizeOrientation(input) {
|
|
|
4502
4621
|
}
|
|
4503
4622
|
) : await loadSessionEntries(input.paths, loadOpts);
|
|
4504
4623
|
const decisions = [];
|
|
4624
|
+
const tracks = [];
|
|
4505
4625
|
const voidedDecisionIds = /* @__PURE__ */ new Set();
|
|
4506
4626
|
let latestActivityAt = null;
|
|
4507
4627
|
let latestNote = null;
|
|
@@ -4526,6 +4646,16 @@ async function summarizeOrientation(input) {
|
|
|
4526
4646
|
sessionId: entry.sessionId,
|
|
4527
4647
|
host: entry.host
|
|
4528
4648
|
});
|
|
4649
|
+
if (ev.kind === "track") {
|
|
4650
|
+
tracks.push({
|
|
4651
|
+
decisionId: ev.decision_id,
|
|
4652
|
+
title: ev.title,
|
|
4653
|
+
rationale: ev.rationale ?? null,
|
|
4654
|
+
occurredAt: ev.occurred_at,
|
|
4655
|
+
sessionId: entry.sessionId,
|
|
4656
|
+
host: entry.host
|
|
4657
|
+
});
|
|
4658
|
+
}
|
|
4529
4659
|
} else if (ev.type === "decision_voided") {
|
|
4530
4660
|
voidedDecisionIds.add(ev.decision_id);
|
|
4531
4661
|
}
|
|
@@ -4557,6 +4687,10 @@ async function summarizeOrientation(input) {
|
|
|
4557
4687
|
break;
|
|
4558
4688
|
}
|
|
4559
4689
|
}
|
|
4690
|
+
const openTracks = tracks.filter((t) => !voidedDecisionIds.has(t.decisionId)).sort((a, b) => {
|
|
4691
|
+
const c = Date.parse(b.occurredAt) - Date.parse(a.occurredAt);
|
|
4692
|
+
return c !== 0 ? c : b.decisionId.localeCompare(a.decisionId);
|
|
4693
|
+
});
|
|
4560
4694
|
const taskLoadOpts = {};
|
|
4561
4695
|
if (input.onTaskSkip !== void 0) taskLoadOpts.onSkip = input.onTaskSkip;
|
|
4562
4696
|
const taskEntries = await loadTaskEntries(input.paths, taskLoadOpts);
|
|
@@ -4645,6 +4779,7 @@ async function summarizeOrientation(input) {
|
|
|
4645
4779
|
latestSession,
|
|
4646
4780
|
latestDecision: latestDecision ?? null,
|
|
4647
4781
|
decisionCount: decisions.length,
|
|
4782
|
+
openTracks,
|
|
4648
4783
|
latestNote,
|
|
4649
4784
|
relatedFiles: { displayed, overflow, outOfRoot },
|
|
4650
4785
|
inFlightTasks,
|
|
@@ -4672,7 +4807,8 @@ async function renderOrientation(input) {
|
|
|
4672
4807
|
pendingApprovalsCount: summary.pendingApprovals.length,
|
|
4673
4808
|
suspectCount: summary.suspects.length,
|
|
4674
4809
|
inFlightTaskCount: summary.inFlightTasks.length,
|
|
4675
|
-
decisionCount: summary.decisionCount
|
|
4810
|
+
decisionCount: summary.decisionCount,
|
|
4811
|
+
openTrackCount: summary.openTracks.length
|
|
4676
4812
|
};
|
|
4677
4813
|
}
|
|
4678
4814
|
function formatOrientationBody(summary, opts) {
|
|
@@ -4779,6 +4915,26 @@ function formatOrientationBody(summary, opts) {
|
|
|
4779
4915
|
lines.push("");
|
|
4780
4916
|
lines.push("## \u3069\u3053\u3078\u5411\u304B\u3046");
|
|
4781
4917
|
lines.push("");
|
|
4918
|
+
if (summary.openTracks.length > 0) {
|
|
4919
|
+
const TRACK_DISPLAY_LIMIT = 10;
|
|
4920
|
+
const shownTracks = summary.openTracks.slice(0, TRACK_DISPLAY_LIMIT);
|
|
4921
|
+
const trackOverflow = summary.openTracks.length - shownTracks.length;
|
|
4922
|
+
lines.push(`### \u672A\u5B8C\u30C8\u30E9\u30C3\u30AF (close \u307E\u3067\u7D99\u7D9A\u8868\u793A) (${summary.openTracks.length})`);
|
|
4923
|
+
for (const t of shownTracks) {
|
|
4924
|
+
const trackAge = relativeAgeJa(t.occurredAt, now);
|
|
4925
|
+
lines.push(`- ${t.title} [${shortId(t.decisionId)}] (${trackAge})${hostSuffix(t.host)}`);
|
|
4926
|
+
if (t.rationale !== null && t.rationale.trim() !== "") {
|
|
4927
|
+
lines.push(` - \u7406\u7531: ${trackRationale(t.rationale)}`);
|
|
4928
|
+
}
|
|
4929
|
+
}
|
|
4930
|
+
if (trackOverflow > 0) {
|
|
4931
|
+
lines.push(`- ... +${trackOverflow} more (see decisions.md)`);
|
|
4932
|
+
}
|
|
4933
|
+
lines.push(
|
|
4934
|
+
"\u5B8C\u4E86\u3057\u305F\u3089 `basou decision void <decision_id>` \u3067\u9589\u3058\u3066\u304F\u3060\u3055\u3044\u3002\u9589\u3058\u308B\u307E\u3067\u6BCE\u56DE\u3053\u3053\u306B\u8868\u793A\u3055\u308C\u307E\u3059\u3002"
|
|
4935
|
+
);
|
|
4936
|
+
lines.push("");
|
|
4937
|
+
}
|
|
4782
4938
|
if (summary.latestNote !== null) {
|
|
4783
4939
|
const noteAge = relativeAgeJa(summary.latestNote.occurredAt, now);
|
|
4784
4940
|
lines.push(
|
|
@@ -4794,7 +4950,7 @@ function formatOrientationBody(summary, opts) {
|
|
|
4794
4950
|
for (const t of summary.plannedTasks) {
|
|
4795
4951
|
lines.push(`- ${t.title} [${shortId(t.id)}]`);
|
|
4796
4952
|
}
|
|
4797
|
-
if (summary.latestNote === null && summary.plannedTasks.length === 0) {
|
|
4953
|
+
if (summary.openTracks.length === 0 && summary.latestNote === null && summary.plannedTasks.length === 0) {
|
|
4798
4954
|
const dec = summary.latestDecision;
|
|
4799
4955
|
if (dec === null) {
|
|
4800
4956
|
lines.push("- (no planned tasks or recorded next step yet)");
|
|
@@ -4807,6 +4963,11 @@ function formatOrientationBody(summary, opts) {
|
|
|
4807
4963
|
lines.push("- (no planned tasks \u2014 direction is inferred from recent decisions)");
|
|
4808
4964
|
lines.push(` - \u76F4\u8FD1\u306E\u5224\u65AD: ${dec.title}`);
|
|
4809
4965
|
}
|
|
4966
|
+
if (dec !== null) {
|
|
4967
|
+
lines.push(
|
|
4968
|
+
' - \u6B21\u306B\u4F5C\u308B\u3079\u304D\u672C\u8CEA\u7684\u306A\u65B9\u5411\u6027\u304C\u5B9A\u307E\u3063\u305F\u3089 `basou decision capture` (`"kind":"track"`) / `basou decision record --track` \u3067 track \u5316\u3059\u308B\u3068\u3001close \u307E\u3067\u6BCE session \u3053\u3053\u306B\u7D99\u7D9A\u8868\u793A\u3055\u308C\u307E\u3059\u3002'
|
|
4969
|
+
);
|
|
4970
|
+
}
|
|
4810
4971
|
}
|
|
4811
4972
|
lines.push("");
|
|
4812
4973
|
lines.push("## \u3053\u308C\u306F\u6700\u65B0\u304B");
|
|
@@ -4932,6 +5093,11 @@ function noteSummary(body) {
|
|
|
4932
5093
|
const oneLine = body.replace(/\s+/g, " ").trim();
|
|
4933
5094
|
return oneLine.length > NOTE_SUMMARY_MAX ? `${oneLine.slice(0, NOTE_SUMMARY_MAX - 1)}\u2026` : oneLine;
|
|
4934
5095
|
}
|
|
5096
|
+
var TRACK_RATIONALE_MAX = 240;
|
|
5097
|
+
function trackRationale(rationale) {
|
|
5098
|
+
const oneLine = rationale.replace(/\s+/g, " ").trim();
|
|
5099
|
+
return oneLine.length > TRACK_RATIONALE_MAX ? `${oneLine.slice(0, TRACK_RATIONALE_MAX - 1)}\u2026` : oneLine;
|
|
5100
|
+
}
|
|
4935
5101
|
function suspectText(reason) {
|
|
4936
5102
|
if (reason === "events_say_ended_but_yaml_running") return "ended (yaml stale)";
|
|
4937
5103
|
if (reason === "running_no_end_event") return "no end event";
|
|
@@ -5847,7 +6013,12 @@ async function renderReport(input) {
|
|
|
5847
6013
|
onWarning: (w) => input.onWarning?.(w, entry.sessionId)
|
|
5848
6014
|
})) {
|
|
5849
6015
|
if (ev.type === "decision_recorded") {
|
|
5850
|
-
decisions.push({
|
|
6016
|
+
decisions.push({
|
|
6017
|
+
id: ev.decision_id,
|
|
6018
|
+
title: ev.title,
|
|
6019
|
+
occurredAt: ev.occurred_at,
|
|
6020
|
+
...ev.kind === "track" ? { track: true } : {}
|
|
6021
|
+
});
|
|
5851
6022
|
} else if (ev.type === "decision_voided") {
|
|
5852
6023
|
voidedDecisionIds.add(ev.decision_id);
|
|
5853
6024
|
}
|
|
@@ -6033,8 +6204,9 @@ function formatReportBody(data) {
|
|
|
6033
6204
|
lines.push("");
|
|
6034
6205
|
}
|
|
6035
6206
|
for (const d of shown) {
|
|
6207
|
+
const trackTag = d.track === true ? " [track]" : "";
|
|
6036
6208
|
const voidedTag = d.voided === true ? " (voided)" : "";
|
|
6037
|
-
lines.push(`- ${d.occurredAt.slice(0, 10)} \xB7 ${d.title}${voidedTag}`);
|
|
6209
|
+
lines.push(`- ${d.occurredAt.slice(0, 10)} \xB7 ${d.title}${trackTag}${voidedTag}`);
|
|
6038
6210
|
}
|
|
6039
6211
|
}
|
|
6040
6212
|
lines.push("");
|
|
@@ -7244,6 +7416,7 @@ export {
|
|
|
7244
7416
|
CLAUDE_IMPORT_SOURCE,
|
|
7245
7417
|
CODEX_IMPORT_SOURCE,
|
|
7246
7418
|
ChildProcessRunner,
|
|
7419
|
+
DEFAULT_STOP_HOOK_MIN_ACTIONS,
|
|
7247
7420
|
DecisionIdSchema,
|
|
7248
7421
|
EventIdSchema,
|
|
7249
7422
|
EventSchema,
|
|
@@ -7303,6 +7476,7 @@ export {
|
|
|
7303
7476
|
enumerateArchivedTaskIds,
|
|
7304
7477
|
enumerateSessionDirs,
|
|
7305
7478
|
enumerateTaskIds,
|
|
7479
|
+
evaluateStopHook,
|
|
7306
7480
|
finalizeSessionYaml,
|
|
7307
7481
|
findErrorCode,
|
|
7308
7482
|
findReviewGaps,
|