skalpel 4.0.26 → 4.0.27
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/install.mjs +61 -27
- package/package.json +1 -1
- package/skalpel-statusline.mjs +6 -2
package/install.mjs
CHANGED
|
@@ -252,46 +252,80 @@ function tomlBlock(event, marker, command) {
|
|
|
252
252
|
return `\n${tomlMark(event, marker)}\n[[hooks.${event}]]\n[[hooks.${event}.hooks]]\ntype = "command"\ncommand = ${tomlString(command)}\ntimeout = 8\n`;
|
|
253
253
|
}
|
|
254
254
|
|
|
255
|
+
// Remove ONLY skalpel's own hook blocks from a Codex config.toml, leaving every foreign block
|
|
256
|
+
// (someone else's `[[hooks.UserPromptSubmit]]`, a `[model]` table, comments, blank lines) byte-for-byte
|
|
257
|
+
// intact. Two identifiers mark a block as ours: the managed marker comment we write above every block,
|
|
258
|
+
// and — for legacy pre-marker installs — a `command` whose basename isOurs. The strip must be robust
|
|
259
|
+
// to a foreign hook block appearing BEFORE or AFTER ours: the previous fixed-width `i += 6` walk and
|
|
260
|
+
// the "stop only at the next `[[hooks.<event>]]` header" bound both mis-scanned that layout, so a
|
|
261
|
+
// foreign block sitting ahead of ours swallowed our marker line (defeating the marker fast-path) and
|
|
262
|
+
// our own block — whose `command = "node \"…\""` line can't be parsed by the command regex — was then
|
|
263
|
+
// never recognized, leaving uninstall a no-op and re-install appending DUPLICATES.
|
|
255
264
|
function stripManagedTomlBlocks(txt) {
|
|
256
265
|
const lines = txt.split("\n");
|
|
257
266
|
const out = [];
|
|
258
267
|
let removed = 0;
|
|
259
268
|
|
|
269
|
+
const HOOK_HEADER = /^\s*\[\[hooks\.(UserPromptSubmit|SessionStart)\]\]\s*$/;
|
|
270
|
+
const isMarker = (line) => TOML_MARKERS.some((marker) => line.includes(marker));
|
|
271
|
+
const isBlank = (line) => line.trim() === "";
|
|
272
|
+
// Any TOML table header — `[table]` or `[[array-of-tables]]`, dotted keys, optional trailing comment.
|
|
273
|
+
const isHeader = (line) => /^\s*\[\[?[^\][]*\]\]?\s*(#.*)?$/.test(line);
|
|
274
|
+
|
|
275
|
+
// Extent of ONE hook block that begins at `start` (a `[[hooks.<event>]]` header). The block owns its
|
|
276
|
+
// `[[hooks.<event>.hooks]]` sub-tables and their keys; it ENDS at the first line that opens a NEW
|
|
277
|
+
// section — a blank line, a skalpel marker, or any table header that is NOT this block's own
|
|
278
|
+
// sub-table (so a following `[model]`, `[[hooks.SessionStart]]`, etc. is never absorbed). Returns the
|
|
279
|
+
// index one past the block.
|
|
280
|
+
const scanBlock = (start, event) => {
|
|
281
|
+
const ownSub = new RegExp(`^\\s*\\[\\[hooks\\.${event}\\.hooks\\]\\]\\s*$`);
|
|
282
|
+
let j = start + 1;
|
|
283
|
+
while (j < lines.length) {
|
|
284
|
+
const line = lines[j];
|
|
285
|
+
if (isBlank(line) || isMarker(line)) break;
|
|
286
|
+
if (isHeader(line) && !ownSub.test(line)) break;
|
|
287
|
+
j += 1;
|
|
288
|
+
}
|
|
289
|
+
return j;
|
|
290
|
+
};
|
|
291
|
+
|
|
260
292
|
for (let i = 0; i < lines.length; ) {
|
|
261
|
-
|
|
262
|
-
|
|
293
|
+
// 1) A managed marker begins OUR block. Drop the marker and the `[[hooks.<event>]]` block that
|
|
294
|
+
// follows it, bounded by scanBlock — never relying on the command line, which is
|
|
295
|
+
// quoted-within-quotes and unparseable. A stray marker with no block after it is dropped alone.
|
|
296
|
+
if (isMarker(lines[i])) {
|
|
297
|
+
let j = i + 1;
|
|
298
|
+
const header = j < lines.length ? lines[j].match(HOOK_HEADER) : null;
|
|
299
|
+
if (header) j = scanBlock(j, header[1]);
|
|
263
300
|
removed += 1;
|
|
301
|
+
i = j;
|
|
264
302
|
continue;
|
|
265
303
|
}
|
|
266
304
|
|
|
267
|
-
|
|
268
|
-
if
|
|
269
|
-
|
|
270
|
-
|
|
305
|
+
// 2) A bare `[[hooks.<event>]]` header with no marker: a legacy pre-marker skalpel block OR a
|
|
306
|
+
// foreign hook. Gather the whole block and remove it ONLY if its command isOurs; foreign blocks
|
|
307
|
+
// pass through untouched.
|
|
308
|
+
const section = lines[i].match(HOOK_HEADER);
|
|
309
|
+
if (section) {
|
|
310
|
+
const event = section[1];
|
|
311
|
+
const marker = HOOKS.find(([hookEvent]) => hookEvent === event)?.[1];
|
|
312
|
+
const end = scanBlock(i, event);
|
|
313
|
+
const block = lines.slice(i, end);
|
|
314
|
+
const command = block
|
|
315
|
+
.map((line) => line.match(/^\s*command\s*=\s*"([^"]*)"\s*$/)?.[1])
|
|
316
|
+
.find(Boolean);
|
|
317
|
+
if (marker && isOurs(command, marker)) {
|
|
318
|
+
removed += 1;
|
|
319
|
+
} else {
|
|
320
|
+
out.push(...block);
|
|
321
|
+
}
|
|
322
|
+
i = end;
|
|
271
323
|
continue;
|
|
272
324
|
}
|
|
273
325
|
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
let j = i + 1;
|
|
278
|
-
while (
|
|
279
|
-
j < lines.length &&
|
|
280
|
-
!/^\[\[hooks\.(UserPromptSubmit|SessionStart)\]\]\s*$/.test(lines[j])
|
|
281
|
-
) {
|
|
282
|
-
j += 1;
|
|
283
|
-
block.push(lines[j - 1]);
|
|
284
|
-
}
|
|
285
|
-
|
|
286
|
-
const command = block
|
|
287
|
-
.map((line) => line.match(/^\s*command\s*=\s*"([^"]*)"\s*$/)?.[1])
|
|
288
|
-
.find(Boolean);
|
|
289
|
-
if (marker && isOurs(command, marker)) {
|
|
290
|
-
removed += 1;
|
|
291
|
-
} else {
|
|
292
|
-
out.push(...block);
|
|
293
|
-
}
|
|
294
|
-
i = j;
|
|
326
|
+
// 3) Everything else — foreign tables, comments, blank lines — kept verbatim.
|
|
327
|
+
out.push(lines[i]);
|
|
328
|
+
i += 1;
|
|
295
329
|
}
|
|
296
330
|
|
|
297
331
|
return { text: out.join("\n"), removed };
|
package/package.json
CHANGED
package/skalpel-statusline.mjs
CHANGED
|
@@ -116,7 +116,7 @@ function revealNote() {
|
|
|
116
116
|
}
|
|
117
117
|
}
|
|
118
118
|
|
|
119
|
-
function main() {
|
|
119
|
+
async function main() {
|
|
120
120
|
const payload = readPayload();
|
|
121
121
|
|
|
122
122
|
// AHA CATCH REVEAL (opt-in). The single highest-priority line: the agent claimed done but its OWN proof,
|
|
@@ -149,4 +149,8 @@ function main() {
|
|
|
149
149
|
// Clean session → COUNT-OR-NOTHING resolves to nothing. Emit no bytes.
|
|
150
150
|
}
|
|
151
151
|
|
|
152
|
-
|
|
152
|
+
// Fail-open, matching the other three hooks (skalpel-hook / -session / -session-end): any error →
|
|
153
|
+
// print nothing → exit 0, so a thrown error can never spill a stack trace into the live status bar.
|
|
154
|
+
main()
|
|
155
|
+
.then(() => process.exit(0))
|
|
156
|
+
.catch(() => process.exit(0));
|