@basou/core 0.49.0 → 0.50.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.js CHANGED
@@ -23,13 +23,142 @@ function summarizeAdapterOutput(_stream, _raw) {
23
23
  throw new Error("adapter_output summary is not implemented in this release");
24
24
  }
25
25
 
26
+ // src/adapters/codex/hooks-json.ts
27
+ var SESSION_START_HOOK_TIMEOUT_SECONDS = 30;
28
+ var SESSION_START_HOOK_MATCHER = "startup|resume|clear";
29
+ var SESSION_START_HOOK_CONTEXT_LIMIT = 0;
30
+ var SESSION_START_HOOK_STATUS_MESSAGE = "basou orient";
31
+ var BASOU_SESSION_START_HOOK = /(?:\bbasou|(?:@basou|packages)\/cli\/dist\/index\.js['"]?)\s+hook\s+session-start\b/;
32
+ function isBasouSessionStartHookCommand(command) {
33
+ return BASOU_SESSION_START_HOOK.test(command);
34
+ }
35
+ function shellQuote(value) {
36
+ return `'${value.replace(/'/g, "'\\''")}'`;
37
+ }
38
+ function buildSessionStartHookCommand(options) {
39
+ return `node ${shellQuote(options.cliEntry)} hook session-start 2>/dev/null || true`;
40
+ }
41
+ function isRecord(value) {
42
+ return typeof value === "object" && value !== null && !Array.isArray(value);
43
+ }
44
+ function cloneHooksFile(hooksFile) {
45
+ if (hooksFile === void 0 || hooksFile === null) return {};
46
+ if (!isRecord(hooksFile)) {
47
+ throw new Error("The Codex hooks.json is not a JSON object.");
48
+ }
49
+ return structuredClone(hooksFile);
50
+ }
51
+ function canonicalHandler(command) {
52
+ return {
53
+ type: "command",
54
+ command,
55
+ timeout: SESSION_START_HOOK_TIMEOUT_SECONDS,
56
+ statusMessage: SESSION_START_HOOK_STATUS_MESSAGE,
57
+ additionalContextLimit: SESSION_START_HOOK_CONTEXT_LIMIT
58
+ };
59
+ }
60
+ function handlerIsCanonical(entry, command) {
61
+ const want = canonicalHandler(command);
62
+ return Object.keys(want).every((k) => entry[k] === want[k]);
63
+ }
64
+ function upsertSessionStartHook(hooksFile, command) {
65
+ const root = cloneHooksFile(hooksFile);
66
+ if (root.hooks === void 0) {
67
+ root.hooks = {};
68
+ } else if (!isRecord(root.hooks)) {
69
+ throw new Error("The 'hooks' key in the Codex hooks.json is not an object.");
70
+ }
71
+ const hooks = root.hooks;
72
+ if (hooks.SessionStart === void 0) {
73
+ hooks.SessionStart = [];
74
+ } else if (!Array.isArray(hooks.SessionStart)) {
75
+ throw new Error("The 'hooks.SessionStart' key in the Codex hooks.json is not an array.");
76
+ }
77
+ const groups = hooks.SessionStart;
78
+ for (const group of groups) {
79
+ if (!isRecord(group) || !Array.isArray(group.hooks)) continue;
80
+ for (const entry of group.hooks) {
81
+ if (!isRecord(entry)) continue;
82
+ if (typeof entry.command === "string" && isBasouSessionStartHookCommand(entry.command)) {
83
+ const unchanged = handlerIsCanonical(entry, command);
84
+ Object.assign(entry, canonicalHandler(command));
85
+ return { hooksFile: root, action: unchanged ? "unchanged" : "updated" };
86
+ }
87
+ }
88
+ }
89
+ groups.push({ matcher: SESSION_START_HOOK_MATCHER, hooks: [canonicalHandler(command)] });
90
+ return { hooksFile: root, action: "installed" };
91
+ }
92
+ function removeSessionStartHook(hooksFile) {
93
+ const root = cloneHooksFile(hooksFile);
94
+ if (!isRecord(root.hooks) || !Array.isArray(root.hooks.SessionStart)) {
95
+ return { hooksFile: root, action: "absent" };
96
+ }
97
+ const hooks = root.hooks;
98
+ const groups = hooks.SessionStart;
99
+ let removed = false;
100
+ const kept = [];
101
+ for (const group of groups) {
102
+ if (!isRecord(group) || !Array.isArray(group.hooks)) {
103
+ kept.push(group);
104
+ continue;
105
+ }
106
+ const keptHandlers = group.hooks.filter((entry) => {
107
+ if (isRecord(entry) && typeof entry.command === "string" && isBasouSessionStartHookCommand(entry.command)) {
108
+ removed = true;
109
+ return false;
110
+ }
111
+ return true;
112
+ });
113
+ if (keptHandlers.length === group.hooks.length) {
114
+ kept.push(group);
115
+ } else if (keptHandlers.length > 0) {
116
+ group.hooks = keptHandlers;
117
+ kept.push(group);
118
+ }
119
+ }
120
+ if (!removed) return { hooksFile: root, action: "absent" };
121
+ if (kept.length === 0) {
122
+ delete hooks.SessionStart;
123
+ } else {
124
+ hooks.SessionStart = kept;
125
+ }
126
+ if (Object.keys(hooks).length === 0) {
127
+ delete root.hooks;
128
+ }
129
+ return { hooksFile: root, action: "removed" };
130
+ }
131
+ function findBasouSessionStartHook(hooksFile) {
132
+ if (!isRecord(hooksFile) || !isRecord(hooksFile.hooks) || !Array.isArray(hooksFile.hooks.SessionStart)) {
133
+ return null;
134
+ }
135
+ const groups = hooksFile.hooks.SessionStart;
136
+ for (let g = 0; g < groups.length; g++) {
137
+ const group = groups[g];
138
+ if (!isRecord(group) || !Array.isArray(group.hooks)) continue;
139
+ for (let h = 0; h < group.hooks.length; h++) {
140
+ const entry = group.hooks[h];
141
+ if (isRecord(entry) && typeof entry.command === "string" && isBasouSessionStartHookCommand(entry.command)) {
142
+ return {
143
+ command: entry.command,
144
+ groupIndex: g,
145
+ handlerIndex: h,
146
+ matcher: typeof group.matcher === "string" ? group.matcher : void 0,
147
+ handler: structuredClone(entry)
148
+ };
149
+ }
150
+ }
151
+ }
152
+ return null;
153
+ }
154
+
26
155
  // src/adapters/claude-code/settings-hook.ts
27
156
  var STOP_HOOK_TIMEOUT_SECONDS = 20;
28
157
  var BASOU_STOP_HOOK = /(?:\bbasou|(?:@basou|packages)\/cli\/dist\/index\.js['"]?)\s+hook\s+stop\b/;
29
158
  function isBasouStopHookCommand(command) {
30
159
  return BASOU_STOP_HOOK.test(command);
31
160
  }
32
- function shellQuote(value) {
161
+ function shellQuote2(value) {
33
162
  return `'${value.replace(/'/g, "'\\''")}'`;
34
163
  }
35
164
  function buildStopHookCommand(options) {
@@ -38,14 +167,14 @@ function buildStopHookCommand(options) {
38
167
  if (options.requireReview === true) flags.push("--require-review");
39
168
  if (options.minEdits !== void 0) flags.push(`--min-edits ${options.minEdits}`);
40
169
  const suffix = flags.length > 0 ? ` ${flags.join(" ")}` : "";
41
- return `node ${shellQuote(options.cliEntry)} hook stop${suffix} 2>/dev/null || true`;
170
+ return `node ${shellQuote2(options.cliEntry)} hook stop${suffix} 2>/dev/null || true`;
42
171
  }
43
- function isRecord(value) {
172
+ function isRecord2(value) {
44
173
  return typeof value === "object" && value !== null && !Array.isArray(value);
45
174
  }
46
175
  function cloneSettings(settings) {
47
176
  if (settings === void 0 || settings === null) return {};
48
- if (!isRecord(settings)) {
177
+ if (!isRecord2(settings)) {
49
178
  throw new Error("Claude settings is not a JSON object.");
50
179
  }
51
180
  return structuredClone(settings);
@@ -54,7 +183,7 @@ function upsertStopHook(settings, command) {
54
183
  const root = cloneSettings(settings);
55
184
  if (root.hooks === void 0) {
56
185
  root.hooks = {};
57
- } else if (!isRecord(root.hooks)) {
186
+ } else if (!isRecord2(root.hooks)) {
58
187
  throw new Error("The 'hooks' key in Claude settings is not an object.");
59
188
  }
60
189
  const hooks = root.hooks;
@@ -65,9 +194,9 @@ function upsertStopHook(settings, command) {
65
194
  }
66
195
  const stop = hooks.Stop;
67
196
  for (const group of stop) {
68
- if (!isRecord(group) || !Array.isArray(group.hooks)) continue;
197
+ if (!isRecord2(group) || !Array.isArray(group.hooks)) continue;
69
198
  for (const entry of group.hooks) {
70
- if (!isRecord(entry)) continue;
199
+ if (!isRecord2(entry)) continue;
71
200
  if (typeof entry.command === "string" && isBasouStopHookCommand(entry.command)) {
72
201
  const unchanged = entry.type === "command" && entry.command === command && entry.timeout === STOP_HOOK_TIMEOUT_SECONDS;
73
202
  entry.type = "command";
@@ -82,7 +211,7 @@ function upsertStopHook(settings, command) {
82
211
  }
83
212
  function removeStopHook(settings) {
84
213
  const root = cloneSettings(settings);
85
- if (!isRecord(root.hooks) || !Array.isArray(root.hooks.Stop)) {
214
+ if (!isRecord2(root.hooks) || !Array.isArray(root.hooks.Stop)) {
86
215
  return { settings: root, action: "absent" };
87
216
  }
88
217
  const hooks = root.hooks;
@@ -90,12 +219,12 @@ function removeStopHook(settings) {
90
219
  let removed = false;
91
220
  const newStop = [];
92
221
  for (const group of stop) {
93
- if (!isRecord(group) || !Array.isArray(group.hooks)) {
222
+ if (!isRecord2(group) || !Array.isArray(group.hooks)) {
94
223
  newStop.push(group);
95
224
  continue;
96
225
  }
97
226
  const keptHooks = group.hooks.filter((entry) => {
98
- if (isRecord(entry) && typeof entry.command === "string" && isBasouStopHookCommand(entry.command)) {
227
+ if (isRecord2(entry) && typeof entry.command === "string" && isBasouStopHookCommand(entry.command)) {
99
228
  removed = true;
100
229
  return false;
101
230
  }
@@ -122,19 +251,198 @@ function removeStopHook(settings) {
122
251
  return { settings: root, action: "removed" };
123
252
  }
124
253
  function findBasouStopHookCommand(settings) {
125
- if (!isRecord(settings) || !isRecord(settings.hooks) || !Array.isArray(settings.hooks.Stop)) {
254
+ if (!isRecord2(settings) || !isRecord2(settings.hooks) || !Array.isArray(settings.hooks.Stop)) {
126
255
  return null;
127
256
  }
128
257
  for (const group of settings.hooks.Stop) {
129
- if (!isRecord(group) || !Array.isArray(group.hooks)) continue;
258
+ if (!isRecord2(group) || !Array.isArray(group.hooks)) continue;
130
259
  for (const entry of group.hooks) {
131
- if (isRecord(entry) && typeof entry.command === "string" && isBasouStopHookCommand(entry.command)) {
260
+ if (isRecord2(entry) && typeof entry.command === "string" && isBasouStopHookCommand(entry.command)) {
132
261
  return entry.command;
133
262
  }
134
263
  }
135
264
  }
136
265
  return null;
137
266
  }
267
+ var ENTRY = String.raw`(?:[^\s'"]*/)?(?:@basou|packages)/cli/dist/index\.js`;
268
+ var INVOCATION = String.raw`(?:basou|node[ \t]+(?:'(?:[^']*/)?(?:@basou|packages)/cli/dist/index\.js'|"(?:[^"]*/)?(?:@basou|packages)/cli/dist/index\.js"|${ENTRY}))`;
269
+ var WRAPPER = String.raw`(?:[ \t]+2>[ \t]*/dev/null)?(?:[ \t]*\|\|[ \t]*true)?`;
270
+ var CLAUDE_SESSION_START = new RegExp(
271
+ String.raw`^[ \t]*${INVOCATION}[ \t]+hook[ \t]+session-start${WRAPPER}[ \t]*$`
272
+ );
273
+ var CLAUDE_ORIENT_SESSION_START = new RegExp(
274
+ String.raw`^[ \t]*${INVOCATION}[ \t]+orient${WRAPPER}[ \t]*$`
275
+ );
276
+ function isClaudeSessionStartHookCommand(command) {
277
+ return CLAUDE_SESSION_START.test(command);
278
+ }
279
+ function isBasouOrientSessionStartCommand(command) {
280
+ return CLAUDE_ORIENT_SESSION_START.test(command);
281
+ }
282
+ function basouSessionStartKind(entry) {
283
+ if (!isRecord2(entry) || typeof entry.command !== "string") return null;
284
+ if (isClaudeSessionStartHookCommand(entry.command)) return "session-start";
285
+ if (isBasouOrientSessionStartCommand(entry.command)) return "orient";
286
+ return null;
287
+ }
288
+ function matcherKey(group) {
289
+ const m = group.matcher;
290
+ return m === void 0 || m === "" || m === "*" ? "*" : String(m);
291
+ }
292
+ function upsertClaudeSessionStartHook(settings, command) {
293
+ const root = cloneSettings(settings);
294
+ if (root.hooks === void 0) {
295
+ root.hooks = {};
296
+ } else if (!isRecord2(root.hooks)) {
297
+ throw new Error("The 'hooks' key in Claude settings is not an object.");
298
+ }
299
+ const hooks = root.hooks;
300
+ if (hooks.SessionStart === void 0) {
301
+ hooks.SessionStart = [];
302
+ } else if (!Array.isArray(hooks.SessionStart)) {
303
+ throw new Error("The 'hooks.SessionStart' key in Claude settings is not an array.");
304
+ }
305
+ const groups = hooks.SessionStart;
306
+ let found = false;
307
+ let changed = false;
308
+ let replacedOrient = false;
309
+ const keptUnder = /* @__PURE__ */ new Set();
310
+ const nextGroups = [];
311
+ for (const group of groups) {
312
+ if (!isRecord2(group) || !Array.isArray(group.hooks)) {
313
+ nextGroups.push(group);
314
+ continue;
315
+ }
316
+ const key = matcherKey(group);
317
+ const nextHooks = [];
318
+ let removedHere = false;
319
+ for (const entry of group.hooks) {
320
+ const kind = basouSessionStartKind(entry);
321
+ if (kind === null || !isRecord2(entry)) {
322
+ nextHooks.push(entry);
323
+ continue;
324
+ }
325
+ found = true;
326
+ if (kind === "orient") replacedOrient = true;
327
+ if (keptUnder.has(key)) {
328
+ changed = true;
329
+ removedHere = true;
330
+ continue;
331
+ }
332
+ if (entry.type !== "command" || entry.command !== command || entry.timeout !== SESSION_START_HOOK_TIMEOUT_SECONDS) {
333
+ changed = true;
334
+ }
335
+ entry.type = "command";
336
+ entry.command = command;
337
+ entry.timeout = SESSION_START_HOOK_TIMEOUT_SECONDS;
338
+ keptUnder.add(key);
339
+ nextHooks.push(entry);
340
+ }
341
+ if (removedHere && nextHooks.length === 0) continue;
342
+ group.hooks = nextHooks;
343
+ nextGroups.push(group);
344
+ }
345
+ hooks.SessionStart = nextGroups;
346
+ if (!found) {
347
+ nextGroups.push({
348
+ matcher: SESSION_START_HOOK_MATCHER,
349
+ hooks: [{ type: "command", command, timeout: SESSION_START_HOOK_TIMEOUT_SECONDS }]
350
+ });
351
+ return { settings: root, action: "installed" };
352
+ }
353
+ if (replacedOrient) return { settings: root, action: "replaced" };
354
+ return { settings: root, action: changed ? "updated" : "unchanged" };
355
+ }
356
+ function removeClaudeSessionStartHook(settings) {
357
+ const root = cloneSettings(settings);
358
+ if (!isRecord2(root.hooks) || !Array.isArray(root.hooks.SessionStart)) {
359
+ return { settings: root, action: "absent" };
360
+ }
361
+ const hooks = root.hooks;
362
+ let removed = false;
363
+ const nextGroups = [];
364
+ for (const group of hooks.SessionStart) {
365
+ if (!isRecord2(group) || !Array.isArray(group.hooks)) {
366
+ nextGroups.push(group);
367
+ continue;
368
+ }
369
+ const keptHooks = group.hooks.filter((entry) => basouSessionStartKind(entry) === null);
370
+ if (keptHooks.length === group.hooks.length) {
371
+ nextGroups.push(group);
372
+ } else {
373
+ removed = true;
374
+ if (keptHooks.length > 0) {
375
+ group.hooks = keptHooks;
376
+ nextGroups.push(group);
377
+ }
378
+ }
379
+ }
380
+ if (!removed) return { settings: root, action: "absent" };
381
+ if (nextGroups.length === 0) {
382
+ delete hooks.SessionStart;
383
+ } else {
384
+ hooks.SessionStart = nextGroups;
385
+ }
386
+ if (Object.keys(hooks).length === 0) delete root.hooks;
387
+ return { settings: root, action: "removed" };
388
+ }
389
+ function findClaudeSessionStartHooks(settings) {
390
+ const found = [];
391
+ if (!isRecord2(settings) || !isRecord2(settings.hooks)) return found;
392
+ const groups = settings.hooks.SessionStart;
393
+ if (!Array.isArray(groups)) return found;
394
+ for (const group of groups) {
395
+ if (!isRecord2(group) || !Array.isArray(group.hooks)) continue;
396
+ for (const entry of group.hooks) {
397
+ const kind = basouSessionStartKind(entry);
398
+ if (kind === null || !isRecord2(entry) || typeof entry.command !== "string") continue;
399
+ found.push({
400
+ command: entry.command,
401
+ kind,
402
+ matcher: typeof group.matcher === "string" ? group.matcher : void 0
403
+ });
404
+ }
405
+ }
406
+ return found;
407
+ }
408
+ function isClaudeSessionStartMalformed(settings) {
409
+ return isRecord2(settings) && isRecord2(settings.hooks) && settings.hooks.SessionStart !== void 0 && !Array.isArray(settings.hooks.SessionStart);
410
+ }
411
+ var RUNS_BASOU = new RegExp(
412
+ String.raw`(?:^|[\s;&|(])(?:basou|node(?:[ \t]+-[^\s]+)*[ \t]+(?:'[^']*(?:@basou|packages)/cli/dist/index\.js'|"[^"]*(?:@basou|packages)/cli/dist/index\.js"|${ENTRY})|npx(?:[ \t]+-y)?[ \t]+@basou/cli)[ \t]+(orient|hook[ \t]+session-start)(?=$|[\s;&|)])`,
413
+ "g"
414
+ );
415
+ function isInsideQuotes(text, index) {
416
+ let single = false;
417
+ let double = false;
418
+ for (let i = 0; i < index; i++) {
419
+ const c = text[i];
420
+ if (c === "'" && !double) single = !single;
421
+ else if (c === '"' && !single) double = !double;
422
+ }
423
+ return single || double;
424
+ }
425
+ function findUnrecognizedSessionStart(settings) {
426
+ const found = [];
427
+ if (!isRecord2(settings) || !isRecord2(settings.hooks)) return found;
428
+ const groups = settings.hooks.SessionStart;
429
+ if (!Array.isArray(groups)) return found;
430
+ for (const group of groups) {
431
+ if (!isRecord2(group) || !Array.isArray(group.hooks)) continue;
432
+ for (const entry of group.hooks) {
433
+ if (!isRecord2(entry) || typeof entry.command !== "string") continue;
434
+ if (basouSessionStartKind(entry) !== null) continue;
435
+ const command = entry.command;
436
+ for (const m of command.matchAll(RUNS_BASOU)) {
437
+ const at = (m.index ?? 0) + (m[0].length - m[0].trimStart().length);
438
+ if (isInsideQuotes(command, at)) continue;
439
+ found.push({ command, runs: m[1] === "orient" ? "orient" : "session-start" });
440
+ break;
441
+ }
442
+ }
443
+ }
444
+ return found;
445
+ }
138
446
 
139
447
  // src/adapters/claude-code/ask-user-question.ts
140
448
  function readString(value) {
@@ -1065,135 +1373,6 @@ async function resolveCodexCommand(lookup = isOnPath) {
1065
1373
  throw new Error("Codex CLI not found in PATH. Install codex first.");
1066
1374
  }
1067
1375
 
1068
- // src/adapters/codex/hooks-json.ts
1069
- var SESSION_START_HOOK_TIMEOUT_SECONDS = 30;
1070
- var SESSION_START_HOOK_MATCHER = "startup|resume|clear";
1071
- var SESSION_START_HOOK_CONTEXT_LIMIT = 0;
1072
- var SESSION_START_HOOK_STATUS_MESSAGE = "basou orient";
1073
- var BASOU_SESSION_START_HOOK = /(?:\bbasou|(?:@basou|packages)\/cli\/dist\/index\.js['"]?)\s+hook\s+session-start\b/;
1074
- function isBasouSessionStartHookCommand(command) {
1075
- return BASOU_SESSION_START_HOOK.test(command);
1076
- }
1077
- function shellQuote2(value) {
1078
- return `'${value.replace(/'/g, "'\\''")}'`;
1079
- }
1080
- function buildSessionStartHookCommand(options) {
1081
- return `node ${shellQuote2(options.cliEntry)} hook session-start 2>/dev/null || true`;
1082
- }
1083
- function isRecord2(value) {
1084
- return typeof value === "object" && value !== null && !Array.isArray(value);
1085
- }
1086
- function cloneHooksFile(hooksFile) {
1087
- if (hooksFile === void 0 || hooksFile === null) return {};
1088
- if (!isRecord2(hooksFile)) {
1089
- throw new Error("The Codex hooks.json is not a JSON object.");
1090
- }
1091
- return structuredClone(hooksFile);
1092
- }
1093
- function canonicalHandler(command) {
1094
- return {
1095
- type: "command",
1096
- command,
1097
- timeout: SESSION_START_HOOK_TIMEOUT_SECONDS,
1098
- statusMessage: SESSION_START_HOOK_STATUS_MESSAGE,
1099
- additionalContextLimit: SESSION_START_HOOK_CONTEXT_LIMIT
1100
- };
1101
- }
1102
- function handlerIsCanonical(entry, command) {
1103
- const want = canonicalHandler(command);
1104
- return Object.keys(want).every((k) => entry[k] === want[k]);
1105
- }
1106
- function upsertSessionStartHook(hooksFile, command) {
1107
- const root = cloneHooksFile(hooksFile);
1108
- if (root.hooks === void 0) {
1109
- root.hooks = {};
1110
- } else if (!isRecord2(root.hooks)) {
1111
- throw new Error("The 'hooks' key in the Codex hooks.json is not an object.");
1112
- }
1113
- const hooks = root.hooks;
1114
- if (hooks.SessionStart === void 0) {
1115
- hooks.SessionStart = [];
1116
- } else if (!Array.isArray(hooks.SessionStart)) {
1117
- throw new Error("The 'hooks.SessionStart' key in the Codex hooks.json is not an array.");
1118
- }
1119
- const groups = hooks.SessionStart;
1120
- for (const group of groups) {
1121
- if (!isRecord2(group) || !Array.isArray(group.hooks)) continue;
1122
- for (const entry of group.hooks) {
1123
- if (!isRecord2(entry)) continue;
1124
- if (typeof entry.command === "string" && isBasouSessionStartHookCommand(entry.command)) {
1125
- const unchanged = handlerIsCanonical(entry, command);
1126
- Object.assign(entry, canonicalHandler(command));
1127
- return { hooksFile: root, action: unchanged ? "unchanged" : "updated" };
1128
- }
1129
- }
1130
- }
1131
- groups.push({ matcher: SESSION_START_HOOK_MATCHER, hooks: [canonicalHandler(command)] });
1132
- return { hooksFile: root, action: "installed" };
1133
- }
1134
- function removeSessionStartHook(hooksFile) {
1135
- const root = cloneHooksFile(hooksFile);
1136
- if (!isRecord2(root.hooks) || !Array.isArray(root.hooks.SessionStart)) {
1137
- return { hooksFile: root, action: "absent" };
1138
- }
1139
- const hooks = root.hooks;
1140
- const groups = hooks.SessionStart;
1141
- let removed = false;
1142
- const kept = [];
1143
- for (const group of groups) {
1144
- if (!isRecord2(group) || !Array.isArray(group.hooks)) {
1145
- kept.push(group);
1146
- continue;
1147
- }
1148
- const keptHandlers = group.hooks.filter((entry) => {
1149
- if (isRecord2(entry) && typeof entry.command === "string" && isBasouSessionStartHookCommand(entry.command)) {
1150
- removed = true;
1151
- return false;
1152
- }
1153
- return true;
1154
- });
1155
- if (keptHandlers.length === group.hooks.length) {
1156
- kept.push(group);
1157
- } else if (keptHandlers.length > 0) {
1158
- group.hooks = keptHandlers;
1159
- kept.push(group);
1160
- }
1161
- }
1162
- if (!removed) return { hooksFile: root, action: "absent" };
1163
- if (kept.length === 0) {
1164
- delete hooks.SessionStart;
1165
- } else {
1166
- hooks.SessionStart = kept;
1167
- }
1168
- if (Object.keys(hooks).length === 0) {
1169
- delete root.hooks;
1170
- }
1171
- return { hooksFile: root, action: "removed" };
1172
- }
1173
- function findBasouSessionStartHook(hooksFile) {
1174
- if (!isRecord2(hooksFile) || !isRecord2(hooksFile.hooks) || !Array.isArray(hooksFile.hooks.SessionStart)) {
1175
- return null;
1176
- }
1177
- const groups = hooksFile.hooks.SessionStart;
1178
- for (let g = 0; g < groups.length; g++) {
1179
- const group = groups[g];
1180
- if (!isRecord2(group) || !Array.isArray(group.hooks)) continue;
1181
- for (let h = 0; h < group.hooks.length; h++) {
1182
- const entry = group.hooks[h];
1183
- if (isRecord2(entry) && typeof entry.command === "string" && isBasouSessionStartHookCommand(entry.command)) {
1184
- return {
1185
- command: entry.command,
1186
- groupIndex: g,
1187
- handlerIndex: h,
1188
- matcher: typeof group.matcher === "string" ? group.matcher : void 0,
1189
- handler: structuredClone(entry)
1190
- };
1191
- }
1192
- }
1193
- }
1194
- return null;
1195
- }
1196
-
1197
1376
  // src/schemas/observed-duration.ts
1198
1377
  function readObservedDuration(ev) {
1199
1378
  const stored = ev.duration_ms;
@@ -6251,7 +6430,7 @@ function parseBuildStamp(raw) {
6251
6430
  }
6252
6431
  }
6253
6432
  var BASOU_CORE_BUILD = parseBuildStamp(
6254
- true ? '{"version":"0.49.0","commit":"6c4704a","committedAt":"2026-09-23T11:46:39+09:00"}' : void 0
6433
+ true ? '{"version":"0.50.0","commit":"5b4c5dc","committedAt":"2026-09-23T19:12:06+09:00"}' : void 0
6255
6434
  );
6256
6435
 
6257
6436
  // src/lib/duration.ts
@@ -10578,10 +10757,12 @@ export {
10578
10757
  finalizeSessionYaml,
10579
10758
  findBasouSessionStartHook,
10580
10759
  findBasouStopHookCommand,
10760
+ findClaudeSessionStartHooks,
10581
10761
  findDecisionGaps,
10582
10762
  findErrorCode,
10583
10763
  findReviewGaps,
10584
10764
  findUnbindableRepos,
10765
+ findUnrecognizedSessionStart,
10585
10766
  formatDurationMs,
10586
10767
  genesisHash,
10587
10768
  getChangesSince,
@@ -10592,8 +10773,11 @@ export {
10592
10773
  importSessionFromJson,
10593
10774
  inspectChainTail,
10594
10775
  instructionMode,
10776
+ isBasouOrientSessionStartCommand,
10595
10777
  isBasouSessionStartHookCommand,
10596
10778
  isBasouStopHookCommand,
10779
+ isClaudeSessionStartHookCommand,
10780
+ isClaudeSessionStartMalformed,
10597
10781
  isGitNotFound,
10598
10782
  isImportDerivedSource,
10599
10783
  isLazyExpired,
@@ -10646,6 +10830,7 @@ export {
10646
10830
  recordSessionBaseline,
10647
10831
  refreshTaskLinkedSessions,
10648
10832
  reimportPreservingId,
10833
+ removeClaudeSessionStartHook,
10649
10834
  removeMarkerSection,
10650
10835
  removeSessionStartHook,
10651
10836
  removeStopHook,
@@ -10693,6 +10878,7 @@ export {
10693
10878
  unknownManifestKeys,
10694
10879
  unstampedProtocolSectionsFrom,
10695
10880
  updateTaskStatusWithEvent,
10881
+ upsertClaudeSessionStartHook,
10696
10882
  upsertSessionStartHook,
10697
10883
  upsertStopHook,
10698
10884
  verifyEventsChain,