@basou/core 0.49.0 → 0.51.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 +126 -9
- package/dist/index.js +387 -173
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
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
|
|
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 ${
|
|
170
|
+
return `node ${shellQuote2(options.cliEntry)} hook stop${suffix} 2>/dev/null || true`;
|
|
42
171
|
}
|
|
43
|
-
function
|
|
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 (!
|
|
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 (!
|
|
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 (!
|
|
197
|
+
if (!isRecord2(group) || !Array.isArray(group.hooks)) continue;
|
|
69
198
|
for (const entry of group.hooks) {
|
|
70
|
-
if (!
|
|
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 (!
|
|
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 (!
|
|
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 (
|
|
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 (!
|
|
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 (!
|
|
258
|
+
if (!isRecord2(group) || !Array.isArray(group.hooks)) continue;
|
|
130
259
|
for (const entry of group.hooks) {
|
|
131
|
-
if (
|
|
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;
|
|
@@ -4356,6 +4535,31 @@ async function countOpenDecisionGaps(input) {
|
|
|
4356
4535
|
import { lstat as lstat2 } from "fs/promises";
|
|
4357
4536
|
import { dirname as dirname2, join as join11, resolve } from "path";
|
|
4358
4537
|
|
|
4538
|
+
// src/lib/display-path.ts
|
|
4539
|
+
function displayPath(path2) {
|
|
4540
|
+
let shown = "";
|
|
4541
|
+
for (const c of path2) {
|
|
4542
|
+
const code = c.codePointAt(0) ?? 0;
|
|
4543
|
+
if (!isActing(code)) {
|
|
4544
|
+
shown += c;
|
|
4545
|
+
} else if (c === "\n") {
|
|
4546
|
+
shown += "\\n";
|
|
4547
|
+
} else if (c === "\r") {
|
|
4548
|
+
shown += "\\r";
|
|
4549
|
+
} else if (c === " ") {
|
|
4550
|
+
shown += "\\t";
|
|
4551
|
+
} else if (code <= 255) {
|
|
4552
|
+
shown += `\\x${code.toString(16).padStart(2, "0")}`;
|
|
4553
|
+
} else {
|
|
4554
|
+
shown += `\\u${code.toString(16).padStart(4, "0")}`;
|
|
4555
|
+
}
|
|
4556
|
+
}
|
|
4557
|
+
return shown;
|
|
4558
|
+
}
|
|
4559
|
+
function isActing(code) {
|
|
4560
|
+
return code <= 31 || code >= 127 && code <= 159 || code === 8232 || code === 8233 || code >= 8234 && code <= 8238 || code >= 8294 && code <= 8297;
|
|
4561
|
+
}
|
|
4562
|
+
|
|
4359
4563
|
// src/lib/one-line.ts
|
|
4360
4564
|
function oneLine(text) {
|
|
4361
4565
|
return text.replace(/\s+/g, " ").trim();
|
|
@@ -5139,10 +5343,10 @@ async function formatDecisionsBody(args) {
|
|
|
5139
5343
|
if (d.linkedFiles !== void 0 && d.linkedFiles.length > 0) {
|
|
5140
5344
|
const parts = await Promise.all(
|
|
5141
5345
|
d.linkedFiles.map(
|
|
5142
|
-
async (path2) => await args.fileExists(path2) ? path2 : `${path2} (missing)`
|
|
5346
|
+
async (path2) => await args.fileExists(path2) ? displayPath(path2) : `${displayPath(path2)} (missing)`
|
|
5143
5347
|
)
|
|
5144
5348
|
);
|
|
5145
|
-
lines.push(`- linked_files: ${
|
|
5349
|
+
lines.push(`- linked_files: ${parts.join(", ")}`);
|
|
5146
5350
|
}
|
|
5147
5351
|
lines.push("");
|
|
5148
5352
|
}
|
|
@@ -5621,7 +5825,7 @@ async function getDiff(repoRoot, baseRef, headRef) {
|
|
|
5621
5825
|
if (baseRef === headRef) return { changed_files: [] };
|
|
5622
5826
|
let raw;
|
|
5623
5827
|
try {
|
|
5624
|
-
raw = await git.raw(["diff", "--name-status", `${baseRef}..${headRef}`]);
|
|
5828
|
+
raw = await git.raw(["diff", "--name-status", "-z", `${baseRef}..${headRef}`]);
|
|
5625
5829
|
} catch (error) {
|
|
5626
5830
|
throw translateDiffError(error);
|
|
5627
5831
|
}
|
|
@@ -5639,7 +5843,7 @@ async function getChangesSince(repoRoot, baseRef) {
|
|
|
5639
5843
|
}
|
|
5640
5844
|
let raw;
|
|
5641
5845
|
try {
|
|
5642
|
-
raw = await git.raw(["
|
|
5846
|
+
raw = await git.raw(["diff", "--name-status", "-z", baseRef]);
|
|
5643
5847
|
} catch (error) {
|
|
5644
5848
|
throw translateDiffError(error);
|
|
5645
5849
|
}
|
|
@@ -5659,27 +5863,25 @@ function translateDiffError(error) {
|
|
|
5659
5863
|
return new Error("Failed to compute git diff", { cause: error });
|
|
5660
5864
|
}
|
|
5661
5865
|
function parseDiffNameStatus(raw) {
|
|
5662
|
-
const
|
|
5866
|
+
const fields = raw.split("\0");
|
|
5663
5867
|
const changes = [];
|
|
5664
|
-
|
|
5665
|
-
|
|
5666
|
-
const code =
|
|
5667
|
-
|
|
5668
|
-
|
|
5669
|
-
|
|
5670
|
-
|
|
5671
|
-
|
|
5672
|
-
|
|
5673
|
-
|
|
5674
|
-
|
|
5675
|
-
|
|
5676
|
-
});
|
|
5677
|
-
} else if (code === "
|
|
5678
|
-
changes.push({ path:
|
|
5679
|
-
} else if (code === "
|
|
5680
|
-
changes.push({ path:
|
|
5681
|
-
} else if (code === "D" && parts[1]) {
|
|
5682
|
-
changes.push({ path: parts[1], status: "deleted" });
|
|
5868
|
+
let i = 0;
|
|
5869
|
+
while (i < fields.length) {
|
|
5870
|
+
const code = fields[i] ?? "";
|
|
5871
|
+
const twoPaths = code.startsWith("R") || code.startsWith("C");
|
|
5872
|
+
const first = fields[i + 1];
|
|
5873
|
+
const second = twoPaths ? fields[i + 2] : void 0;
|
|
5874
|
+
i += twoPaths ? 3 : 2;
|
|
5875
|
+
if (first === void 0 || first.length === 0) continue;
|
|
5876
|
+
if (code.startsWith("R")) {
|
|
5877
|
+
if (second === void 0 || second.length === 0) continue;
|
|
5878
|
+
changes.push({ path: second, status: "renamed", old_path: first });
|
|
5879
|
+
} else if (code === "A") {
|
|
5880
|
+
changes.push({ path: first, status: "added" });
|
|
5881
|
+
} else if (code === "M") {
|
|
5882
|
+
changes.push({ path: first, status: "modified" });
|
|
5883
|
+
} else if (code === "D") {
|
|
5884
|
+
changes.push({ path: first, status: "deleted" });
|
|
5683
5885
|
}
|
|
5684
5886
|
}
|
|
5685
5887
|
return changes;
|
|
@@ -6091,7 +6293,7 @@ function formatHandoffBody(args) {
|
|
|
6091
6293
|
if (args.displayedFiles.length === 0) {
|
|
6092
6294
|
lines.push("(no related files recorded)");
|
|
6093
6295
|
} else {
|
|
6094
|
-
for (const f of args.displayedFiles) lines.push(`- ${f}`);
|
|
6296
|
+
for (const f of args.displayedFiles) lines.push(`- ${displayPath(f)}`);
|
|
6095
6297
|
if (args.overflow > 0) lines.push(`- ... +${args.overflow} more`);
|
|
6096
6298
|
}
|
|
6097
6299
|
lines.push("");
|
|
@@ -6144,7 +6346,7 @@ function formatHandoffBody(args) {
|
|
|
6144
6346
|
lines.push(t.handoff.headingReadNext);
|
|
6145
6347
|
lines.push("");
|
|
6146
6348
|
lines.push("- .basou/decisions.md");
|
|
6147
|
-
for (const f of args.displayedFiles.slice(0, 3)) lines.push(`- ${f}`);
|
|
6349
|
+
for (const f of args.displayedFiles.slice(0, 3)) lines.push(`- ${displayPath(f)}`);
|
|
6148
6350
|
lines.push("");
|
|
6149
6351
|
lines.push(t.handoff.headingNextWork);
|
|
6150
6352
|
lines.push("");
|
|
@@ -6251,7 +6453,7 @@ function parseBuildStamp(raw) {
|
|
|
6251
6453
|
}
|
|
6252
6454
|
}
|
|
6253
6455
|
var BASOU_CORE_BUILD = parseBuildStamp(
|
|
6254
|
-
true ? '{"version":"0.
|
|
6456
|
+
true ? '{"version":"0.51.0","commit":"22ef44f","committedAt":"2026-09-23T22:27:59+09:00"}' : void 0
|
|
6255
6457
|
);
|
|
6256
6458
|
|
|
6257
6459
|
// src/lib/duration.ts
|
|
@@ -6781,7 +6983,7 @@ function formatOrientationBody(summary, opts) {
|
|
|
6781
6983
|
const parts = [];
|
|
6782
6984
|
if (summary.relatedFiles.displayed.length > 0) {
|
|
6783
6985
|
const more = summary.relatedFiles.overflow > 0 ? ` (... +${summary.relatedFiles.overflow} more)` : "";
|
|
6784
|
-
parts.push(`${summary.relatedFiles.displayed.join(", ")}${more}`);
|
|
6986
|
+
parts.push(`${summary.relatedFiles.displayed.map(displayPath).join(", ")}${more}`);
|
|
6785
6987
|
}
|
|
6786
6988
|
if (summary.relatedFiles.omitted > 0) {
|
|
6787
6989
|
parts.push(t.orientation.scratchOmitted(summary.relatedFiles.omitted));
|
|
@@ -6790,7 +6992,7 @@ function formatOrientationBody(summary, opts) {
|
|
|
6790
6992
|
if (summary.relatedFiles.outOfRoot.length > 0) {
|
|
6791
6993
|
const OUT_OF_ROOT_DISPLAY = 10;
|
|
6792
6994
|
const out = summary.relatedFiles.outOfRoot;
|
|
6793
|
-
const shownOut = out.slice(0, OUT_OF_ROOT_DISPLAY).join(", ");
|
|
6995
|
+
const shownOut = out.slice(0, OUT_OF_ROOT_DISPLAY).map(displayPath).join(", ");
|
|
6794
6996
|
const outMore = out.length > OUT_OF_ROOT_DISPLAY ? ` (... +${out.length - OUT_OF_ROOT_DISPLAY} more)` : "";
|
|
6795
6997
|
lines.push(` - ${t.orientation.outOfRootWarning(out.length, `${shownOut}${outMore}`)}`);
|
|
6796
6998
|
}
|
|
@@ -6818,7 +7020,9 @@ function formatOrientationBody(summary, opts) {
|
|
|
6818
7020
|
lines.push(` - ${t.orientation.recentNextStepLabel}: ${noteSummary(note)}`);
|
|
6819
7021
|
}
|
|
6820
7022
|
if (s.files.length > 0) {
|
|
6821
|
-
lines.push(
|
|
7023
|
+
lines.push(
|
|
7024
|
+
` - ${t.orientation.recentChangedLabel}: ${s.files.map(displayPath).join(", ")}`
|
|
7025
|
+
);
|
|
6822
7026
|
}
|
|
6823
7027
|
}
|
|
6824
7028
|
}
|
|
@@ -8614,7 +8818,9 @@ function formatReportBody(data, t) {
|
|
|
8614
8818
|
if (data.changedFiles.length === 0) {
|
|
8615
8819
|
lines.push("(no related files recorded)");
|
|
8616
8820
|
} else {
|
|
8617
|
-
for (const f of data.changedFiles.slice(0, CHANGED_FILES_MARKDOWN_LIMIT))
|
|
8821
|
+
for (const f of data.changedFiles.slice(0, CHANGED_FILES_MARKDOWN_LIMIT)) {
|
|
8822
|
+
lines.push(`- ${displayPath(f)}`);
|
|
8823
|
+
}
|
|
8618
8824
|
const overflow = data.changedFiles.length - CHANGED_FILES_MARKDOWN_LIMIT;
|
|
8619
8825
|
if (overflow > 0) lines.push(`- ... +${overflow} more`);
|
|
8620
8826
|
}
|
|
@@ -10568,6 +10774,7 @@ export {
|
|
|
10568
10774
|
createManifest,
|
|
10569
10775
|
createTaskWithEvent,
|
|
10570
10776
|
deleteTask,
|
|
10777
|
+
displayPath,
|
|
10571
10778
|
editTask,
|
|
10572
10779
|
ensureBasouDirectory,
|
|
10573
10780
|
enumerateApprovals,
|
|
@@ -10578,10 +10785,12 @@ export {
|
|
|
10578
10785
|
finalizeSessionYaml,
|
|
10579
10786
|
findBasouSessionStartHook,
|
|
10580
10787
|
findBasouStopHookCommand,
|
|
10788
|
+
findClaudeSessionStartHooks,
|
|
10581
10789
|
findDecisionGaps,
|
|
10582
10790
|
findErrorCode,
|
|
10583
10791
|
findReviewGaps,
|
|
10584
10792
|
findUnbindableRepos,
|
|
10793
|
+
findUnrecognizedSessionStart,
|
|
10585
10794
|
formatDurationMs,
|
|
10586
10795
|
genesisHash,
|
|
10587
10796
|
getChangesSince,
|
|
@@ -10592,8 +10801,11 @@ export {
|
|
|
10592
10801
|
importSessionFromJson,
|
|
10593
10802
|
inspectChainTail,
|
|
10594
10803
|
instructionMode,
|
|
10804
|
+
isBasouOrientSessionStartCommand,
|
|
10595
10805
|
isBasouSessionStartHookCommand,
|
|
10596
10806
|
isBasouStopHookCommand,
|
|
10807
|
+
isClaudeSessionStartHookCommand,
|
|
10808
|
+
isClaudeSessionStartMalformed,
|
|
10597
10809
|
isGitNotFound,
|
|
10598
10810
|
isImportDerivedSource,
|
|
10599
10811
|
isLazyExpired,
|
|
@@ -10646,6 +10858,7 @@ export {
|
|
|
10646
10858
|
recordSessionBaseline,
|
|
10647
10859
|
refreshTaskLinkedSessions,
|
|
10648
10860
|
reimportPreservingId,
|
|
10861
|
+
removeClaudeSessionStartHook,
|
|
10649
10862
|
removeMarkerSection,
|
|
10650
10863
|
removeSessionStartHook,
|
|
10651
10864
|
removeStopHook,
|
|
@@ -10693,6 +10906,7 @@ export {
|
|
|
10693
10906
|
unknownManifestKeys,
|
|
10694
10907
|
unstampedProtocolSectionsFrom,
|
|
10695
10908
|
updateTaskStatusWithEvent,
|
|
10909
|
+
upsertClaudeSessionStartHook,
|
|
10696
10910
|
upsertSessionStartHook,
|
|
10697
10911
|
upsertStopHook,
|
|
10698
10912
|
verifyEventsChain,
|