@basou/core 0.19.0 → 0.20.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.ts CHANGED
@@ -588,6 +588,62 @@ type ClaudeTranscriptToPayloadOptions = {
588
588
  */
589
589
  declare function claudeTranscriptToImportPayload(records: ReadonlyArray<ClaudeTranscriptRecord>, options: ClaudeTranscriptToPayloadOptions): SessionImportPayload | null;
590
590
 
591
+ /**
592
+ * Default minimum number of "action" tool uses (Bash commands + file edits) a
593
+ * session must contain before the Stop-hook nudge treats it as substantive
594
+ * enough to be worth a session-end capture. Below this the session reads as a
595
+ * trivial check / quick question and the hook stays silent rather than nagging.
596
+ */
597
+ declare const DEFAULT_STOP_HOOK_MIN_ACTIONS = 5;
598
+ type StopHookEvaluationInput = {
599
+ /**
600
+ * Parsed transcript records, one per JSONL line of the current session's
601
+ * transcript. The caller drops malformed lines; this function reads every
602
+ * field defensively, like the importer, since the format is undocumented.
603
+ */
604
+ records: ReadonlyArray<ClaudeTranscriptRecord>;
605
+ /**
606
+ * The Stop hook's `stop_hook_active` stdin flag: true when Claude is already
607
+ * continuing because of a previous Stop-hook response. When true the nudge
608
+ * stays silent so it can never form a continuation loop.
609
+ */
610
+ stopHookActive: boolean;
611
+ /** Override the substantive-work threshold (defaults to {@link DEFAULT_STOP_HOOK_MIN_ACTIONS}). */
612
+ minActions?: number;
613
+ };
614
+ /** Why the hook stayed silent (useful for tests and `--json` introspection). */
615
+ type StopHookSilentReason = "stop_hook_active" | "not_substantive" | "already_captured";
616
+ type StopHookEvaluation = {
617
+ kind: "silent";
618
+ reason: StopHookSilentReason;
619
+ commandCount: number;
620
+ fileCount: number;
621
+ } | {
622
+ kind: "nudge";
623
+ additionalContext: string;
624
+ commandCount: number;
625
+ fileCount: number;
626
+ };
627
+ /**
628
+ * Decide whether a finished turn warrants a non-blocking capture nudge.
629
+ *
630
+ * Pure: no disk or environment access. The CLI handler reads the Stop hook's
631
+ * stdin payload and the transcript file, parses the JSONL into `records`, and
632
+ * passes them here. A `nudge` result is rendered as
633
+ * `hookSpecificOutput.additionalContext` (non-blocking — Claude may act on it
634
+ * or stop); a `silent` result emits nothing.
635
+ *
636
+ * The nudge fires only when ALL hold:
637
+ * - not already continuing from a prior nudge (`stopHookActive` is false), so
638
+ * the hook never loops;
639
+ * - the session did substantive work (>= `minActions` Bash commands + edits),
640
+ * so trivial check sessions are left alone;
641
+ * - no capture verb (`basou decision capture` / `decision record` / `note`)
642
+ * was run this session, so a session that already recorded its intent is
643
+ * left alone.
644
+ */
645
+ declare function evaluateStopHook(input: StopHookEvaluationInput): StopHookEvaluation;
646
+
591
647
  /**
592
648
  * The `source` string stamped on every event derived from an OpenAI Codex
593
649
  * native rollout log, and the matching session `source.kind`.
@@ -5519,4 +5575,4 @@ declare function overwriteYamlFile(filePath: string, value: unknown): Promise<vo
5519
5575
  */
5520
5576
  declare const BASOU_CORE_VERSION = "0.1.0";
5521
5577
 
5522
- export { ACTIVE_GAP_CAP_MS, AGENT_INFRA_DIRS, type ActiveTimeBasis, type AdapterOutputEvent, type AdoptCandidate, type AdoptCandidateKind, type AppendBasouGitignoreOptions, type AppendBasouGitignoreResult, type AppendEventToExistingInput, type AppendEventToExistingResult, type Approval, type ApprovalApprovedEvent, type ApprovalExpiredEvent, ApprovalIdSchema, type ApprovalLocation, type ApprovalRejectedEvent, type ApprovalRequestedEvent, ApprovalSchema, type ApprovalStatus, ApprovalStatusSchema, type ArchivePlan, type ArchiveTaskInput, type ArchiveTaskResult, type AttachTaskInput, type AttachUpdateTaskStatusInput, type AttachableStatus, BASOU_CORE_VERSION, type BasouPaths, type BulkChainResult, CLAUDE_IMPORT_SOURCE, CODEX_IMPORT_SOURCE, type CaptureMode, type ChainBreakReason, type ChainTailState, type ChainVerdict, type ChainVerdictStatus, type ChainedEvents, ChildProcessRunner, type CitedReview, 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, type ExistingViewLink, FailedToFinalizeError, type FederatedRoot, type FileChange, type FileChangeStatus, type FileChangedEvent, GENERATED_END, GENERATED_START, type GitSnapshot, type GitSnapshotEvent, type GitignorePlanSummary, type HandoffRendererInput, type HandoffRendererResult, ID_PREFIXES, type IdPrefix, type ImportSessionOptions, type ImportSessionResult, type InstructionFileFact, type InstructionSymlinkFact, type InstructionSymlinkState, IsoTimestampSchema, JSON_SCHEMA_VERSION, type JsonSchemaArtifact, type LoadFederatedOptions, type LoadSessionEntriesOptions, type LoadTaskEntriesOptions, type LoadedApproval, type LockHandle, type LockScope, type Manifest, ManifestSchema, type MarkerSection, type Markers, type MeasureAvailability, type NoteAddedEvent, type OrientationRendererInput, type OrientationRendererResult, type OrientationSummary, PROTOCOL_END, PROTOCOL_START, type PrefixedId, type PresetAction, type PresetCollision, type PresetMarkerConflict, type PresetMarkerKind, type PresetPlanSummary, type PresetRepo, type ProcessRunner, type PublishKind, type PublishTarget, type RechainOptions, type RechainResult, type ReconcileAllResult, type ReconcileAllTasksInput, type ReconcileAllTasksOptions, type ReconcileFailure, type ReconcileResult, type ReconcileTaskInput, type RefreshLinkageInput, type RefreshLinkageResult, type ReimportOptions, type ReimportResult, type RenamePlan, type ReplayOptions, type ReplayWarning, type RepoEntry, type RepoGitignoreFacts, type RepoGitignorePlan, type RepoLanguage, type RepoPresetFacts, type RepoPresetPlan, type RepoSymlinkFacts, type RepoSymlinkPlan, type RepoVisibility, type RepoWiringFacts, type ReportApprovalItem, type ReportData, type ReportDecisionItem, type ReportRendererInput, type ReportRendererResult, type ReportSessionItem, type ReportTaskItem, type ReviewGapRepoSummary, type ReviewGapUnit, type ReviewGapVerdict, type ReviewGapsInput, type ReviewGapsSummary, type RiskLevel, RiskLevelSchema, type RosterAdoptionPlan, type RosterDriftSummary, 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 SessionIntegrity, SessionIntegritySchema, type SessionMetrics, SessionMetricsSchema, SessionSchema, type SessionSkipReason, type SessionSourceKind, SessionSourceKindSchema, type SessionStartedEvent, type SessionStatus, type SessionStatusChangedEvent, SessionStatusSchema, type SessionWorkStats, type SourceRootScope, type SourceRootsReconcile, type SourceWorkStats, type StatusCount, StatusSchema, type StatusSnapshot, type SuspectReason, type SymlinkCollision, type SymlinkConflict, type SymlinkPlanSummary, type Task, type TaskArchivedEvent, type TaskCreatedEvent, type TaskDeletedEvent, type TaskDocument, TaskIdSchema, type TaskLinkageRefreshedEvent, type TaskReconciledEvent, TaskSchema, type TaskSkipReason, type TaskStatus, type TaskStatusChangedEvent, type TaskStatusCount, TaskStatusSchema, TaskWriteAfterEventError, type TaskWriteAfterEventPhase, type TokenTotals, type UpdateAdHocTaskStatusInput, type UpdateTaskStatusInput, type UpdateTaskStatusResult, type ViewCollision, type ViewConflict, type ViewLinkState, type ViewRepoFact, type ViewStrayUnknown, type WiringRisk, type WiringSummary, type WorkStatsInput, type WorkStatsResult, type WorkStatsTotals, WorkspaceIdSchema, type WorkspaceViewPlan, type WriteEventsBulkOptions, type WriteTaskFileMode, acquireLock, appendBasouGitignore, appendChainedEvent, appendChainedEventLocked, appendEvent, appendEventToExistingSession, archiveTask, assertBasouRootSafe, basouPaths, buildJsonSchemas, buildStatusSnapshot, chainEvents, chainRawJsonLines, classifyFilesBySourceRoot, classifySuspect, claudeCodeAdapterMetadata, claudeTranscriptToImportPayload, codexRolloutToImportPayload, computeWorkStats, createAdHocSessionWithEvent, createManifest, createTaskWithEvent, deleteTask, editTask, ensureBasouDirectory, enumerateApprovals, enumerateArchivedTaskIds, enumerateSessionDirs, enumerateTaskIds, finalizeSessionYaml, findErrorCode, findReviewGaps, formatDurationMs, genesisHash, getDiff, getSnapshot, importSessionFromJson, inspectChainTail, isGitNotFound, isImportDerivedSource, isLazyExpired, isRenderable, isValidPrefixedId, lineHash, linkYamlFile, loadApproval, loadFederatedSessionEntries, loadSessionEntries, loadTaskEntries, normalizeRepoKey, normalizeRepoPath, overwriteYamlFile, parseDuration, parseMarkers, pathBasename, planArchive, planGitignore, planRename, planRosterAdoption, planWorkspaceView, prefixedUlid, readAllEvents, readManifest, readMarkdownFile, readSessionYaml, readStatus, readTaskFile, readTaskFileWithArchiveFallback, readYamlFile, rechainSessionInPlace, reconcileAllTasks, reconcileSourceRoots, reconcileTask, refreshTaskLinkedSessions, reimportPreservingId, removeMarkerSection, renderDecisions, renderHandoff, renderOrientation, renderPresetBlock, renderReport, renderWithMarkers, replayEvents, resolveBasouRepositoryRoot, resolveClaudeCodeCommand, resolveRepositoryRoot, resolveSessionId, resolveTaskId, safeSimpleGit, sanitizePath, sanitizeRelatedFiles, sanitizeWorkingDirectory, serializeEventLine, serializeJsonSchema, sessionWorkStatsFromEvents, summarizeAdapterOutput, summarizeOrientation, summarizePresetPlan, summarizeRosterDrift, summarizeSymlinkPlan, summarizeWiring, tryRemoteUrl, ulid, unknownManifestKeys, updateTaskStatusWithEvent, verifyEventsChain, writeEventsBulk, writeManifest, writeMarkdownFile, writeStatus, writeTaskFile, writeYamlFile };
5578
+ export { ACTIVE_GAP_CAP_MS, AGENT_INFRA_DIRS, type ActiveTimeBasis, type AdapterOutputEvent, type AdoptCandidate, type AdoptCandidateKind, type AppendBasouGitignoreOptions, type AppendBasouGitignoreResult, type AppendEventToExistingInput, type AppendEventToExistingResult, type Approval, type ApprovalApprovedEvent, type ApprovalExpiredEvent, ApprovalIdSchema, type ApprovalLocation, type ApprovalRejectedEvent, type ApprovalRequestedEvent, ApprovalSchema, type ApprovalStatus, ApprovalStatusSchema, type ArchivePlan, type ArchiveTaskInput, type ArchiveTaskResult, type AttachTaskInput, type AttachUpdateTaskStatusInput, type AttachableStatus, BASOU_CORE_VERSION, type BasouPaths, type BulkChainResult, CLAUDE_IMPORT_SOURCE, CODEX_IMPORT_SOURCE, type CaptureMode, type ChainBreakReason, type ChainTailState, type ChainVerdict, type ChainVerdictStatus, type ChainedEvents, ChildProcessRunner, type CitedReview, type ClaudeTranscriptRecord, type ClaudeTranscriptToPayloadOptions, type CodexRolloutRecord, type CodexRolloutToPayloadOptions, type CommandExecutedEvent, type CommandLookup, type CreateAdHocSessionInput, type CreateAdHocSessionResult, type CreateAdHocTaskInput, type CreateManifestInput, type CreateTaskInput, type CreateTaskResult, DEFAULT_STOP_HOOK_MIN_ACTIONS, type DayWorkStats, DecisionIdSchema, type DecisionRecordedEvent, type DecisionsRendererInput, type DecisionsRendererResult, type DeleteTaskInput, type DeleteTaskResult, type DiffResult, type EditTaskInput, type EditTaskResult, type Event, EventIdSchema, EventSchema, EventSourceSchema, type ExistingViewLink, FailedToFinalizeError, type FederatedRoot, type FileChange, type FileChangeStatus, type FileChangedEvent, GENERATED_END, GENERATED_START, type GitSnapshot, type GitSnapshotEvent, type GitignorePlanSummary, type HandoffRendererInput, type HandoffRendererResult, ID_PREFIXES, type IdPrefix, type ImportSessionOptions, type ImportSessionResult, type InstructionFileFact, type InstructionSymlinkFact, type InstructionSymlinkState, IsoTimestampSchema, JSON_SCHEMA_VERSION, type JsonSchemaArtifact, type LoadFederatedOptions, type LoadSessionEntriesOptions, type LoadTaskEntriesOptions, type LoadedApproval, type LockHandle, type LockScope, type Manifest, ManifestSchema, type MarkerSection, type Markers, type MeasureAvailability, type NoteAddedEvent, type OrientationRendererInput, type OrientationRendererResult, type OrientationSummary, PROTOCOL_END, PROTOCOL_START, type PrefixedId, type PresetAction, type PresetCollision, type PresetMarkerConflict, type PresetMarkerKind, type PresetPlanSummary, type PresetRepo, type ProcessRunner, type PublishKind, type PublishTarget, type RechainOptions, type RechainResult, type ReconcileAllResult, type ReconcileAllTasksInput, type ReconcileAllTasksOptions, type ReconcileFailure, type ReconcileResult, type ReconcileTaskInput, type RefreshLinkageInput, type RefreshLinkageResult, type ReimportOptions, type ReimportResult, type RenamePlan, type ReplayOptions, type ReplayWarning, type RepoEntry, type RepoGitignoreFacts, type RepoGitignorePlan, type RepoLanguage, type RepoPresetFacts, type RepoPresetPlan, type RepoSymlinkFacts, type RepoSymlinkPlan, type RepoVisibility, type RepoWiringFacts, type ReportApprovalItem, type ReportData, type ReportDecisionItem, type ReportRendererInput, type ReportRendererResult, type ReportSessionItem, type ReportTaskItem, type ReviewGapRepoSummary, type ReviewGapUnit, type ReviewGapVerdict, type ReviewGapsInput, type ReviewGapsSummary, type RiskLevel, RiskLevelSchema, type RosterAdoptionPlan, type RosterDriftSummary, 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 SessionIntegrity, SessionIntegritySchema, type SessionMetrics, SessionMetricsSchema, SessionSchema, type SessionSkipReason, type SessionSourceKind, SessionSourceKindSchema, type SessionStartedEvent, type SessionStatus, type SessionStatusChangedEvent, SessionStatusSchema, type SessionWorkStats, type SourceRootScope, type SourceRootsReconcile, type SourceWorkStats, type StatusCount, StatusSchema, type StatusSnapshot, type StopHookEvaluation, type StopHookEvaluationInput, type StopHookSilentReason, type SuspectReason, type SymlinkCollision, type SymlinkConflict, type SymlinkPlanSummary, type Task, type TaskArchivedEvent, type TaskCreatedEvent, type TaskDeletedEvent, type TaskDocument, TaskIdSchema, type TaskLinkageRefreshedEvent, type TaskReconciledEvent, TaskSchema, type TaskSkipReason, type TaskStatus, type TaskStatusChangedEvent, type TaskStatusCount, TaskStatusSchema, TaskWriteAfterEventError, type TaskWriteAfterEventPhase, type TokenTotals, type UpdateAdHocTaskStatusInput, type UpdateTaskStatusInput, type UpdateTaskStatusResult, type ViewCollision, type ViewConflict, type ViewLinkState, type ViewRepoFact, type ViewStrayUnknown, type WiringRisk, type WiringSummary, type WorkStatsInput, type WorkStatsResult, type WorkStatsTotals, WorkspaceIdSchema, type WorkspaceViewPlan, type WriteEventsBulkOptions, type WriteTaskFileMode, acquireLock, appendBasouGitignore, appendChainedEvent, appendChainedEventLocked, appendEvent, appendEventToExistingSession, archiveTask, assertBasouRootSafe, basouPaths, buildJsonSchemas, buildStatusSnapshot, chainEvents, chainRawJsonLines, classifyFilesBySourceRoot, classifySuspect, claudeCodeAdapterMetadata, claudeTranscriptToImportPayload, codexRolloutToImportPayload, computeWorkStats, createAdHocSessionWithEvent, createManifest, createTaskWithEvent, deleteTask, editTask, ensureBasouDirectory, enumerateApprovals, enumerateArchivedTaskIds, enumerateSessionDirs, enumerateTaskIds, evaluateStopHook, finalizeSessionYaml, findErrorCode, findReviewGaps, formatDurationMs, genesisHash, getDiff, getSnapshot, importSessionFromJson, inspectChainTail, isGitNotFound, isImportDerivedSource, isLazyExpired, isRenderable, isValidPrefixedId, lineHash, linkYamlFile, loadApproval, loadFederatedSessionEntries, loadSessionEntries, loadTaskEntries, normalizeRepoKey, normalizeRepoPath, overwriteYamlFile, parseDuration, parseMarkers, pathBasename, planArchive, planGitignore, planRename, planRosterAdoption, planWorkspaceView, prefixedUlid, readAllEvents, readManifest, readMarkdownFile, readSessionYaml, readStatus, readTaskFile, readTaskFileWithArchiveFallback, readYamlFile, rechainSessionInPlace, reconcileAllTasks, reconcileSourceRoots, reconcileTask, refreshTaskLinkedSessions, reimportPreservingId, removeMarkerSection, renderDecisions, renderHandoff, renderOrientation, renderPresetBlock, renderReport, renderWithMarkers, replayEvents, resolveBasouRepositoryRoot, resolveClaudeCodeCommand, resolveRepositoryRoot, resolveSessionId, resolveTaskId, safeSimpleGit, sanitizePath, sanitizeRelatedFiles, sanitizeWorkingDirectory, serializeEventLine, serializeJsonSchema, sessionWorkStatsFromEvents, summarizeAdapterOutput, summarizeOrientation, summarizePresetPlan, summarizeRosterDrift, summarizeSymlinkPlan, summarizeWiring, tryRemoteUrl, ulid, unknownManifestKeys, updateTaskStatusWithEvent, verifyEventsChain, writeEventsBulk, writeManifest, writeMarkdownFile, writeStatus, writeTaskFile, writeYamlFile };
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 = readString(record.timestamp);
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 = readString(record.cwd);
136
- if (claudeSessionId === void 0) claudeSessionId = readString(record.sessionId);
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 = readString(record.type);
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 = isObject(record.message) ? record.message : void 0;
145
- const mid = msg !== void 0 ? readString(msg.id) : 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 (readString(record.type) !== "assistant") continue;
154
- const message = isObject(record.message) ? record.message : void 0;
155
- const usage = message !== void 0 && isObject(message.usage) ? message.usage : 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 ? readString(message.id) : 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 = readString(record.cwd) ?? workingDir ?? ".";
233
+ const cwd = readString2(record.cwd) ?? workingDir ?? ".";
167
234
  for (const item of toolUses(record)) {
168
- const name = readString(item.name);
169
- const input = isObject(item.input) ? item.input : void 0;
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 = readString(input.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 = readString(item.id);
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 = readString(input.file_path) ?? readString(input.notebook_path);
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 readString(value) {
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 isObject(value) {
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 = isObject(record.message) ? record.message : void 0;
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 (!isObject(block)) return false;
311
- const type = readString(block.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 = isObject(record.message) ? record.message : void 0;
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 (isObject(item) && readString(item.type) === "tool_use") {
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 (!isObject(result)) continue;
399
+ if (!isObject2(result)) continue;
333
400
  const answers = result.answers;
334
- if (!isObject(answers)) continue;
335
- const message = isObject(record.message) ? record.message : void 0;
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 (isObject(item) && readString(item.type) === "tool_result") {
339
- const id = readString(item.tool_use_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 = readString2(record.timestamp);
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 = isObject2(record.payload) ? record.payload : void 0;
434
+ const payload2 = isObject3(record.payload) ? record.payload : void 0;
368
435
  if (payload2 === void 0) continue;
369
- if (readString2(record.type) === "session_meta") {
370
- if (workingDir === void 0) workingDir = readString2(payload2.cwd);
371
- if (codexSessionId === void 0) codexSessionId = readString2(payload2.id);
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 (readString2(record.type) === "event_msg" && readString2(payload2.type) === "token_count") {
375
- const info = isObject2(payload2.info) ? payload2.info : void 0;
376
- const totals = info !== void 0 && isObject2(info.total_token_usage) ? info.total_token_usage : 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 (readString2(record.type) === "event_msg") {
381
- const pt = readString2(payload2.type);
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 = readString2(payload2.turn_id);
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 (readString2(record.type) !== "response_item") continue;
399
- if (readString2(payload2.type) !== "function_call") continue;
400
- if (readString2(payload2.name) !== "exec_command") continue;
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 readString2(value) {
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 isObject2(value) {
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 = readString2(value);
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 (!isObject2(parsed)) return void 0;
530
- const cmd = readString2(parsed.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: readString2(parsed.workdir) };
599
+ return { cmd, workdir: readString3(parsed.workdir) };
533
600
  }
534
601
  function readCallId(value, outputs) {
535
- const callId = readString2(value);
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 = readString2(payload.turn_id);
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 (readString2(record.type) !== "event_msg") continue;
552
- const payload = isObject2(record.payload) ? record.payload : void 0;
553
- if (payload === void 0 || readString2(payload.type) !== "task_started") continue;
554
- const turnId = readString2(payload.turn_id);
555
- const startMs = Date.parse(readString2(record.timestamp) ?? "");
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 (readString2(record.type) !== "response_item") continue;
578
- const payload = isObject2(record.payload) ? record.payload : void 0;
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 (readString2(payload.type) !== "function_call_output") continue;
581
- const callId = readString2(payload.call_id);
582
- const output = readString2(payload.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;
@@ -7349,6 +7416,7 @@ export {
7349
7416
  CLAUDE_IMPORT_SOURCE,
7350
7417
  CODEX_IMPORT_SOURCE,
7351
7418
  ChildProcessRunner,
7419
+ DEFAULT_STOP_HOOK_MIN_ACTIONS,
7352
7420
  DecisionIdSchema,
7353
7421
  EventIdSchema,
7354
7422
  EventSchema,
@@ -7408,6 +7476,7 @@ export {
7408
7476
  enumerateArchivedTaskIds,
7409
7477
  enumerateSessionDirs,
7410
7478
  enumerateTaskIds,
7479
+ evaluateStopHook,
7411
7480
  finalizeSessionYaml,
7412
7481
  findErrorCode,
7413
7482
  findReviewGaps,