kibi-cursor 2.11.0 → 2.13.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.
@@ -1,19 +1,68 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  // src/hook-runner.ts
4
- import fs7 from "node:fs";
5
- import path9 from "node:path";
4
+ import fs9 from "node:fs";
5
+ import path11 from "node:path";
6
6
  import { fileURLToPath as fileURLToPath2, pathToFileURL } from "node:url";
7
7
 
8
+ // ../agent-core/dist/checkout-hooks.js
9
+ import { spawnSync } from "node:child_process";
10
+ import * as fs from "node:fs";
11
+ import * as path from "node:path";
12
+ var HOOK_MANAGER_FILES = [
13
+ "lefthook.yml",
14
+ "lefthook.yaml",
15
+ ".lefthook.yml",
16
+ ".lefthook.yaml",
17
+ "lefthook.json",
18
+ ".husky",
19
+ ".pre-commit-config.yaml"
20
+ ];
21
+ function git(workspaceRoot, args) {
22
+ const result = spawnSync("git", args, {
23
+ cwd: workspaceRoot,
24
+ encoding: "utf8",
25
+ timeout: 2000
26
+ });
27
+ return result.status === 0 ? result.stdout.trim() : null;
28
+ }
29
+ function hasKibiPreCommitGate(workspaceRoot) {
30
+ const resolved = git(workspaceRoot, [
31
+ "rev-parse",
32
+ "--git-path",
33
+ "hooks/pre-commit"
34
+ ]);
35
+ const hookPath = resolved !== null ? path.resolve(workspaceRoot, resolved) : path.join(workspaceRoot, ".git", "hooks", "pre-commit");
36
+ try {
37
+ return /kibi[^\n]*\bcheck\b/i.test(String(fs.readFileSync(hookPath)));
38
+ } catch {}
39
+ return false;
40
+ }
41
+ function kibiHooksMissing(workspaceRoot) {
42
+ if (!fs.existsSync(path.join(workspaceRoot, ".kb", "manifest.json")))
43
+ return false;
44
+ if (git(workspaceRoot, ["rev-parse", "--is-inside-work-tree"]) !== "true")
45
+ return false;
46
+ if (git(workspaceRoot, ["config", "--get", "core.hooksPath"]) !== null)
47
+ return false;
48
+ if (HOOK_MANAGER_FILES.some((file) => fs.existsSync(path.join(workspaceRoot, file))))
49
+ return false;
50
+ return !hasKibiPreCommitGate(workspaceRoot);
51
+ }
52
+ var MISSING_HOOKS_NOTE = "Kibi git hooks are not installed in this checkout (a clone never carries .git/hooks), so commits skip `kibi check --staged` and a branch switch does not recompile the KB. Install them with `npx --no-install kibi hooks install` (or the project's package-manager runner), or by installing the project's dependencies when its prepare script runs it.";
53
+ function missingHooksNote(workspaceRoot) {
54
+ return kibiHooksMissing(workspaceRoot) ? MISSING_HOOKS_NOTE : null;
55
+ }
56
+
8
57
  // ../agent-core/dist/decision-guard.js
9
58
  import { createHash as createHash2 } from "node:crypto";
10
- import fs3 from "node:fs";
11
- import path3 from "node:path";
59
+ import fs4 from "node:fs";
60
+ import path4 from "node:path";
12
61
 
13
62
  // ../agent-core/dist/requirement-candidates.js
14
- import path from "node:path";
15
- var REQUIREMENTS_DIR = path.join(".kb", "requirements");
16
- var LAYOUT_WORDS = new Set([
63
+ import path2 from "node:path";
64
+ var REQUIREMENTS_DIR = path2.join(".kb", "requirements");
65
+ var LAYOUT_WORDS = stems([
17
66
  "src",
18
67
  "app",
19
68
  "apps",
@@ -21,44 +70,31 @@ var LAYOUT_WORDS = new Set([
21
70
  "libs",
22
71
  "source",
23
72
  "page",
24
- "pages",
25
73
  "component",
26
- "components",
27
74
  "view",
28
- "views",
29
75
  "screen",
30
- "screens",
31
76
  "package",
32
- "packages",
33
77
  "main",
34
78
  "index",
35
79
  "shared",
36
80
  "common",
37
81
  "core",
38
82
  "util",
39
- "utils",
40
83
  "helper",
41
- "helpers",
42
84
  "public",
43
85
  "static",
44
86
  "asset",
45
- "assets",
46
87
  "web",
47
88
  "client",
48
89
  "server",
49
90
  "frontend",
50
91
  "backend",
51
92
  "module",
52
- "modules",
53
93
  "feature",
54
- "features",
55
94
  "internal",
56
95
  "service",
57
- "services",
58
96
  "test",
59
- "tests",
60
97
  "spec",
61
- "specs",
62
98
  "dist",
63
99
  "build",
64
100
  "html",
@@ -79,7 +115,7 @@ var LAYOUT_WORDS = new Set([
79
115
  "swift",
80
116
  "php"
81
117
  ]);
82
- var FILLER_WORDS = new Set([
118
+ var FILLER_WORDS = stems([
83
119
  "the",
84
120
  "and",
85
121
  "for",
@@ -90,7 +126,6 @@ var FILLER_WORDS = new Set([
90
126
  "may",
91
127
  "can",
92
128
  "are",
93
- "is",
94
129
  "its",
95
130
  "their",
96
131
  "that",
@@ -106,13 +141,16 @@ var FILLER_WORDS = new Set([
106
141
  "auto"
107
142
  ]);
108
143
  function words(text) {
109
- return text.replace(/([a-z0-9])([A-Z])/g, "$1 $2").toLowerCase().split(/[^a-z0-9]+/).filter((word) => word.length >= 3 && !/\d/.test(word)).map((word) => word.length > 4 && word.endsWith("s") && !word.endsWith("ss") ? word.slice(0, -1) : word);
144
+ return text.replace(/([a-z0-9])([A-Z])/g, "$1 $2").toLowerCase().match(/[a-z0-9]+/g)?.filter((word) => word.length >= 3 && !/\d/.test(word)).map((word) => word.length > 4 && word.endsWith("s") && !word.endsWith("ss") ? word.slice(0, -1) : word) ?? [];
145
+ }
146
+ function stems(list) {
147
+ return new Set(list.flatMap(words));
110
148
  }
111
149
  function requestTopicWords(text, limit = 40) {
112
150
  const found = words(text).filter((word) => !FILLER_WORDS.has(word) && !REQUEST_FILLER_WORDS.has(word));
113
151
  return [...new Set(found)].slice(0, limit);
114
152
  }
115
- var REQUEST_FILLER_WORDS = new Set([
153
+ var REQUEST_FILLER_WORDS = stems([
116
154
  "please",
117
155
  "could",
118
156
  "would",
@@ -175,12 +213,12 @@ var REQUEST_FILLER_WORDS = new Set([
175
213
  ]);
176
214
 
177
215
  // ../agent-core/dist/snippets.js
178
- import fs2 from "node:fs";
216
+ import fs3 from "node:fs";
179
217
 
180
218
  // ../agent-core/dist/knowledge-index.js
181
219
  import { createHash, randomUUID } from "node:crypto";
182
- import fs from "node:fs";
183
- import path2 from "node:path";
220
+ import fs2 from "node:fs";
221
+ import path3 from "node:path";
184
222
  var SYMBOLS_MANIFEST = ".kb/symbols.yaml";
185
223
  var SYMBOL_COORDINATES = ".kb/symbol-coordinates.yaml";
186
224
  var INDEX_FORMAT = "kibi-agent-core.knowledge-index.v2";
@@ -197,56 +235,36 @@ function unquote(raw) {
197
235
  if (value.startsWith("'") && value.endsWith("'") && value.length >= 2) {
198
236
  return value.slice(1, -1).replaceAll("''", "'");
199
237
  }
200
- const comment = value.search(/\s#/);
201
- return comment >= 0 ? value.slice(0, comment).trimEnd() : value;
238
+ return value.replace(/\s#[\s\S]*/, "").trimEnd();
202
239
  }
240
+ var FLOW_BLANK = /(?:[ \t\n\r]|#[^\n]*)*/y;
241
+ var DOUBLE_QUOTED = /"(?:[^"\\]|\\[\s\S])*"/y;
242
+ var SINGLE_QUOTED = /'(?:[^']|'')*'/y;
203
243
  function parseFlow(text, start = 0) {
204
244
  let pos = start;
205
245
  const skip = () => {
206
- while (pos < text.length) {
207
- const char = text[pos];
208
- if (char === "#") {
209
- while (pos < text.length && text[pos] !== `
210
- `)
211
- pos += 1;
212
- } else if (char === " " || char === "\t" || char === `
213
- ` || char === "\r") {
214
- pos += 1;
215
- } else {
216
- break;
217
- }
218
- }
246
+ FLOW_BLANK.lastIndex = pos;
247
+ FLOW_BLANK.test(text);
248
+ pos = FLOW_BLANK.lastIndex;
219
249
  };
220
250
  const scalar = (stops) => {
221
- const quote = text[pos];
251
+ const quote = text.charAt(pos);
222
252
  if (quote === '"' || quote === "'") {
223
- let cursor = pos + 1;
224
- while (cursor < text.length) {
225
- if (quote === '"' && text[cursor] === "\\")
226
- cursor += 2;
227
- else if (text[cursor] === quote) {
228
- if (quote === "'" && text[cursor + 1] === "'")
229
- cursor += 2;
230
- else
231
- break;
232
- } else
233
- cursor += 1;
234
- }
235
- if (cursor >= text.length)
236
- return;
237
- const raw = text.slice(pos, cursor + 1);
238
- pos = cursor + 1;
253
+ const pattern = quote === '"' ? DOUBLE_QUOTED : SINGLE_QUOTED;
254
+ pattern.lastIndex = pos;
255
+ const raw = pattern.exec(text)[0];
256
+ pos = pattern.lastIndex;
239
257
  return unquote(raw);
240
258
  }
241
259
  let cursor = pos;
242
- while (cursor < text.length) {
243
- const char = text[cursor] ?? "";
260
+ for (;; ) {
261
+ const char = text.charAt(cursor);
244
262
  if (stops.includes(char) && char !== ":" || char === `
245
263
  ` || char === "\r")
246
264
  break;
247
- if (stops.includes(":") && char === ":" && (cursor + 1 >= text.length || /[\s,\]}]/.test(text[cursor + 1] ?? "")))
265
+ if (stops.includes(":") && char === ":" && /[\s,\]}]/.test(text.charAt(cursor + 1)))
248
266
  break;
249
- if (char === "#" && /\s/.test(text[cursor - 1] ?? ""))
267
+ if (char === "#" && /\s/.test(text.charAt(cursor - 1)))
250
268
  break;
251
269
  cursor += 1;
252
270
  }
@@ -255,27 +273,22 @@ function parseFlow(text, start = 0) {
255
273
  return raw;
256
274
  };
257
275
  const value = () => {
258
- skip();
259
- const open = text[pos];
276
+ const open = text.charAt(pos);
260
277
  if (open === "[") {
261
278
  pos += 1;
262
279
  const items = [];
263
280
  for (;; ) {
264
281
  skip();
265
- if (text[pos] === "]") {
282
+ if (text.charAt(pos) === "]") {
266
283
  pos += 1;
267
284
  return items;
268
285
  }
269
- if (pos >= text.length)
270
- throw new SyntaxError("unterminated flow sequence");
271
- const item = value();
272
- if (item !== undefined)
273
- items.push(item);
286
+ items.push(value());
274
287
  skip();
275
- if (text[pos] === ",")
288
+ if (text.charAt(pos) === ",")
276
289
  pos += 1;
277
- else if (text[pos] !== "]")
278
- throw new SyntaxError("expected , or ]");
290
+ else if (text.charAt(pos) !== "]")
291
+ throw new SyntaxError;
279
292
  }
280
293
  }
281
294
  if (open === "{") {
@@ -283,46 +296,37 @@ function parseFlow(text, start = 0) {
283
296
  const record = {};
284
297
  for (;; ) {
285
298
  skip();
286
- if (text[pos] === "}") {
299
+ if (text.charAt(pos) === "}") {
287
300
  pos += 1;
288
301
  return record;
289
302
  }
290
- if (pos >= text.length)
291
- throw new SyntaxError("unterminated flow mapping");
292
303
  const key = scalar(":,}");
293
- if (key === undefined)
294
- throw new SyntaxError("unterminated key");
295
304
  skip();
296
305
  let entry;
297
- if (text[pos] === ":") {
306
+ if (text.charAt(pos) === ":") {
298
307
  pos += 1;
299
308
  skip();
300
- entry = text[pos] === "," || text[pos] === "}" ? undefined : value();
309
+ entry = text.charAt(pos) === "," || text.charAt(pos) === "}" ? undefined : value();
301
310
  }
302
311
  if (key.length > 0)
303
312
  record[key] = entry;
304
313
  skip();
305
- if (text[pos] === ",")
314
+ if (text.charAt(pos) === ",")
306
315
  pos += 1;
307
- else if (text[pos] !== "}")
308
- throw new SyntaxError("expected , or }");
316
+ else if (text.charAt(pos) !== "}")
317
+ throw new SyntaxError;
309
318
  }
310
319
  }
311
- const raw = scalar(",]}");
312
- if (raw === undefined)
313
- throw new SyntaxError("unterminated scalar");
314
- return raw;
320
+ return scalar(",]}");
315
321
  };
316
322
  try {
317
323
  const parsed = value();
318
324
  return { value: parsed, end: pos };
319
- } catch {
320
- return;
321
- }
325
+ } catch {}
326
+ return;
322
327
  }
323
328
  function opensFlow(raw) {
324
- const value = raw.trim();
325
- return value.startsWith("[") || value.startsWith("{");
329
+ return raw.startsWith("[") || raw.startsWith("{");
326
330
  }
327
331
  var keyValue = /^([A-Za-z0-9_"'-][^:]*?):(?:\s+(.*))?$/;
328
332
  function completeFlowValue(raw) {
@@ -335,7 +339,7 @@ function completeFlowValue(raw) {
335
339
  return rest.length === 0 || rest.startsWith("#") ? parsed : undefined;
336
340
  }
337
341
  function parseScalarOrFlow(raw) {
338
- if (raw === undefined || raw.trim().length === 0)
342
+ if (!raw)
339
343
  return;
340
344
  return completeFlowValue(raw)?.value ?? unquote(raw);
341
345
  }
@@ -349,32 +353,27 @@ function flowRecords(text, start) {
349
353
  const parsed = parseFlow(text, start);
350
354
  if (!Array.isArray(parsed?.value))
351
355
  return;
352
- return parsed.value.filter((item) => item !== null && typeof item === "object" && !Array.isArray(item));
356
+ return parsed.value.filter((item) => typeof item === "object" && !Array.isArray(item));
353
357
  }
354
358
  function scanFlowSection(text, sectionKey) {
355
- const escapedKey = sectionKey.replaceAll(/[.*+?^$(){}|[\]\\]/g, "\\$&");
356
- const header = new RegExp(`^${escapedKey}:[ \\t]*(\\[)?`);
359
+ const header = new RegExp(`^${sectionKey}:[ \\t]*(\\[)?`);
357
360
  let offset = 0;
358
361
  let first = true;
359
362
  let afterHeader = false;
360
363
  for (const piece of text.split(/(\r?\n)/)) {
361
364
  const content = piece.trim();
362
- const indent = piece.length - piece.trimStart().length;
363
365
  if (content.length > 0 && !content.startsWith("#")) {
364
- if (afterHeader) {
365
- return content.startsWith("[") ? flowRecords(text, offset + piece.indexOf("[")) : undefined;
366
- }
367
- if (first && content.startsWith("[")) {
368
- return flowRecords(text, offset + piece.indexOf("["));
369
- }
366
+ const bracket = offset + piece.indexOf("[");
367
+ if ((first || afterHeader) && content.startsWith("["))
368
+ return flowRecords(text, bracket);
369
+ if (afterHeader)
370
+ return;
370
371
  first = false;
371
- if (indent === 0) {
372
- const match = header.exec(piece);
373
- if (match) {
374
- if (match[1] !== undefined)
375
- return flowRecords(text, offset + piece.indexOf("["));
376
- afterHeader = true;
377
- }
372
+ const match = header.exec(piece);
373
+ if (match) {
374
+ if (match[1] !== undefined)
375
+ return flowRecords(text, bracket);
376
+ afterHeader = true;
378
377
  }
379
378
  }
380
379
  offset += piece.length;
@@ -386,123 +385,96 @@ function scanRecordSequence(text, sectionKey) {
386
385
  if (flowSection !== undefined)
387
386
  return flowSection;
388
387
  const records = [];
389
- let itemIndent;
390
- let keyIndent = 0;
391
- let current;
388
+ let item;
392
389
  let listKey;
393
390
  let listItem;
394
- let section = "root";
391
+ let otherSection = false;
395
392
  let pendingFlow;
393
+ const assign = (target, key, raw, recordIndent) => {
394
+ if (raw !== undefined && unbalancedFlow(raw)) {
395
+ pendingFlow = { target, key, first: raw, text: raw, recordIndent };
396
+ return false;
397
+ }
398
+ const value = parseScalarOrFlow(raw);
399
+ if (value === undefined)
400
+ return true;
401
+ target[key] = value;
402
+ return false;
403
+ };
396
404
  for (const rawLine of text.split(/\r?\n/)) {
397
405
  const content = rawLine.trim();
398
406
  if (content.length === 0 || content.startsWith("#"))
399
407
  continue;
400
408
  const indent = rawLine.length - rawLine.trimStart().length;
401
409
  const dash = /^(\s*)-(\s+)(.*)$/.exec(rawLine);
402
- if (pendingFlow !== undefined && current !== undefined) {
403
- const target = pendingFlow.onListItem && listItem !== undefined ? listItem : current;
404
- if (indent > (itemIndent ?? 0) && !(dash && indent === itemIndent)) {
410
+ if (pendingFlow !== undefined) {
411
+ if (indent > pendingFlow.recordIndent) {
405
412
  pendingFlow.text += `
406
413
  ${content}`;
407
414
  const parsed = parseFlow(pendingFlow.text);
408
415
  if (parsed === undefined)
409
416
  continue;
410
- target[pendingFlow.key] = parsed.value;
417
+ pendingFlow.target[pendingFlow.key] = parsed.value;
411
418
  pendingFlow = undefined;
412
419
  continue;
413
420
  }
414
- target[pendingFlow.key] = unquote(pendingFlow.first);
421
+ pendingFlow.target[pendingFlow.key] = unquote(pendingFlow.first);
415
422
  pendingFlow = undefined;
416
423
  }
417
424
  if (indent === 0 && !dash) {
418
425
  const pair = keyValue.exec(content);
419
- section = pair?.[1] !== undefined && unquote(pair[1]) === sectionKey ? "records" : "other";
420
- current = undefined;
421
- itemIndent = undefined;
426
+ otherSection = pair === null || unquote(pair[1]) !== sectionKey;
427
+ item = undefined;
422
428
  continue;
423
429
  }
424
- if (section === "other")
430
+ if (otherSection)
425
431
  continue;
426
- if (dash && (itemIndent === undefined || indent === itemIndent)) {
427
- itemIndent = indent;
428
- keyIndent = (dash[1]?.length ?? 0) + 1 + (dash[2]?.length ?? 1);
429
- current = {};
430
- records.push(current);
432
+ if (dash && (item === undefined || indent === item.indent)) {
433
+ const rest = dash[3];
434
+ item = { record: {}, indent, keyIndent: rawLine.length - rest.length };
435
+ records.push(item.record);
431
436
  listKey = undefined;
432
437
  listItem = undefined;
433
- const pair = keyValue.exec(dash[3] ?? "");
434
- if (pair?.[1] !== undefined) {
438
+ const pair = keyValue.exec(rest);
439
+ if (pair !== null) {
435
440
  const key = unquote(pair[1]);
436
- if (pair[2] !== undefined && unbalancedFlow(pair[2])) {
437
- pendingFlow = {
438
- key,
439
- first: pair[2],
440
- text: pair[2],
441
- onListItem: false
442
- };
443
- continue;
444
- }
445
- const value = parseScalarOrFlow(pair[2]);
446
- if (value !== undefined)
447
- current[key] = value;
448
- else
441
+ if (assign(item.record, key, pair[2], indent))
449
442
  listKey = key;
450
443
  }
451
444
  continue;
452
445
  }
453
- if (current === undefined || itemIndent === undefined)
454
- continue;
455
- if (indent <= itemIndent)
446
+ if (item === undefined || indent <= item.indent)
456
447
  continue;
457
448
  if (dash) {
458
449
  if (listKey === undefined)
459
450
  continue;
460
- const list = Array.isArray(current[listKey]) ? current[listKey] : [];
461
- current[listKey] = list;
462
- const item = dash[3] ?? "";
463
- const flowItem = completeFlowValue(item);
464
- const pair = flowItem === undefined ? keyValue.exec(item) : null;
451
+ const list = Array.isArray(item.record[listKey]) ? item.record[listKey] : [];
452
+ item.record[listKey] = list;
453
+ const rest = dash[3];
454
+ const flowItem = completeFlowValue(rest);
455
+ listItem = undefined;
465
456
  if (flowItem !== undefined) {
466
- listItem = undefined;
467
457
  list.push(flowItem.value);
468
- } else if (pair?.[1] !== undefined && pair[2] !== undefined) {
458
+ continue;
459
+ }
460
+ const pair = keyValue.exec(rest);
461
+ if (pair?.[2] !== undefined) {
469
462
  listItem = { [unquote(pair[1])]: parseScalarOrFlow(pair[2]) };
470
463
  list.push(listItem);
471
464
  } else {
472
- listItem = undefined;
473
- list.push(unquote(item));
465
+ list.push(unquote(rest));
474
466
  }
475
467
  continue;
476
468
  }
477
469
  const pair = keyValue.exec(content);
478
- if (pair?.[1] === undefined)
470
+ if (pair === null)
479
471
  continue;
480
472
  const key = unquote(pair[1]);
481
- if (indent === keyIndent) {
473
+ if (indent === item.keyIndent) {
482
474
  listItem = undefined;
483
- if (pair[2] !== undefined && unbalancedFlow(pair[2])) {
484
- pendingFlow = {
485
- key,
486
- first: pair[2],
487
- text: pair[2],
488
- onListItem: false
489
- };
490
- listKey = undefined;
491
- continue;
492
- }
493
- const value = parseScalarOrFlow(pair[2]);
494
- if (value === undefined) {
495
- listKey = key;
496
- } else {
497
- current[key] = value;
498
- listKey = undefined;
499
- }
500
- } else if (listItem !== undefined && pair[2] !== undefined) {
501
- if (unbalancedFlow(pair[2])) {
502
- pendingFlow = { key, first: pair[2], text: pair[2], onListItem: true };
503
- continue;
504
- }
505
- listItem[key] = parseScalarOrFlow(pair[2]);
475
+ listKey = assign(item.record, key, pair[2], item.indent) ? key : undefined;
476
+ } else if (listItem !== undefined) {
477
+ assign(listItem, key, pair[2], item.indent);
506
478
  }
507
479
  }
508
480
  return records;
@@ -510,49 +482,43 @@ ${content}`;
510
482
  function scanSymbolCoordinates(text) {
511
483
  const coordinates = {};
512
484
  let inCoordinates = false;
513
- let idIndent;
514
- let currentId;
485
+ let current;
515
486
  for (const rawLine of text.split(/\r?\n/)) {
516
- const content = rawLine.trim();
517
- if (content.length === 0 || content.startsWith("#"))
487
+ const pair = keyValue.exec(rawLine.trim());
488
+ if (pair === null)
518
489
  continue;
519
490
  const indent = rawLine.length - rawLine.trimStart().length;
520
- const pair = keyValue.exec(content);
521
- if (pair?.[1] === undefined)
522
- continue;
523
491
  const key = unquote(pair[1]);
524
492
  if (indent === 0) {
525
493
  inCoordinates = key === "coordinates";
526
- idIndent = undefined;
527
- currentId = undefined;
494
+ current = undefined;
528
495
  continue;
529
496
  }
530
497
  if (!inCoordinates)
531
498
  continue;
532
- if (idIndent === undefined || indent === idIndent) {
533
- idIndent = indent;
534
- currentId = key;
535
- coordinates[currentId] ??= {};
499
+ if (current !== undefined && indent < current.indent)
500
+ continue;
501
+ if (current === undefined || indent === current.indent) {
502
+ coordinates[key] ??= {};
503
+ current = { indent, entry: coordinates[key] };
536
504
  continue;
537
505
  }
538
- if (currentId === undefined || indent < idIndent)
506
+ if (pair[2] === undefined)
539
507
  continue;
540
- const entry = coordinates[currentId] ?? {};
508
+ const value = unquote(pair[2]);
541
509
  if (key === "sourceFile") {
542
- const sourceFile = normalizeSourceFile(unquote(pair[2] ?? ""));
510
+ const sourceFile = normalizeSourceFile(value);
543
511
  if (sourceFile.length > 0)
544
- entry.sourceFile = sourceFile;
545
- coordinates[currentId] = entry;
512
+ current.entry.sourceFile = sourceFile;
546
513
  continue;
547
514
  }
548
- const numeric = Number.parseInt(unquote(pair[2] ?? ""), 10);
515
+ const numeric = Number.parseInt(value, 10);
549
516
  if (!Number.isFinite(numeric))
550
517
  continue;
551
518
  if (key === "sourceLine")
552
- entry.line = numeric;
519
+ current.entry.line = numeric;
553
520
  if (key === "sourceEndLine")
554
- entry.endLine = numeric;
555
- coordinates[currentId] = entry;
521
+ current.entry.endLine = numeric;
556
522
  }
557
523
  return coordinates;
558
524
  }
@@ -599,7 +565,19 @@ function buildKnowledgeIndex(records, coordinates = {}, options = {}, manifest =
599
565
  coveredBy: [],
600
566
  executableFor: []
601
567
  };
602
- const relationships = [];
568
+ const relate = (type, target) => {
569
+ if (type === "implements")
570
+ pushUnique(symbol.implements, target);
571
+ else if (type === "covered_by")
572
+ pushUnique(symbol.coveredBy, target);
573
+ else if (type === "executable_for")
574
+ pushUnique(symbol.executableFor, target);
575
+ };
576
+ const relateTyped = (relation) => {
577
+ const typed = relation ?? {};
578
+ if (typeof typed.target === "string")
579
+ relate(typed.type, typed.target);
580
+ };
603
581
  if (options.legacyLinksAsImplements !== false) {
604
582
  let links = [];
605
583
  if (typeof record.links === "string")
@@ -607,31 +585,15 @@ function buildKnowledgeIndex(records, coordinates = {}, options = {}, manifest =
607
585
  else if (Array.isArray(record.links))
608
586
  links = record.links;
609
587
  for (const link of links) {
610
- if (typeof link === "string") {
611
- relationships.push({ type: "implements", target: link });
612
- } else if (link !== null && typeof link === "object") {
613
- const typed = link;
614
- if (typeof typed.type === "string" && typeof typed.target === "string") {
615
- relationships.push({ type: typed.type, target: typed.target });
616
- }
617
- }
588
+ if (typeof link === "string")
589
+ relate("implements", link);
590
+ else
591
+ relateTyped(link);
618
592
  }
619
593
  }
620
- for (const relation of Array.isArray(record.relationships) ? record.relationships : []) {
621
- if (relation === null || typeof relation !== "object")
622
- continue;
623
- const typed = relation;
624
- if (typeof typed.type === "string" && typeof typed.target === "string")
625
- relationships.push({ type: typed.type, target: typed.target });
626
- }
627
- for (const { type, target } of relationships) {
628
- if (type === "implements")
629
- pushUnique(symbol.implements, target);
630
- else if (type === "covered_by")
631
- pushUnique(symbol.coveredBy, target);
632
- else if (type === "executable_for")
633
- pushUnique(symbol.executableFor, target);
634
- }
594
+ if (Array.isArray(record.relationships))
595
+ for (const relation of record.relationships)
596
+ relateTyped(relation);
635
597
  if (symbol.implements.length + symbol.coveredBy.length + symbol.executableFor.length === 0) {
636
598
  continue;
637
599
  }
@@ -650,7 +612,7 @@ function buildKnowledgeIndex(records, coordinates = {}, options = {}, manifest =
650
612
  }
651
613
  function fileSignature(filePath) {
652
614
  try {
653
- const stats = fs.statSync(filePath);
615
+ const stats = fs2.statSync(filePath);
654
616
  return `${stats.size}:${stats.mtimeMs}`;
655
617
  } catch {
656
618
  return "missing";
@@ -658,41 +620,45 @@ function fileSignature(filePath) {
658
620
  }
659
621
  function readText(filePath) {
660
622
  try {
661
- return fs.readFileSync(filePath, "utf8");
662
- } catch {
663
- return;
664
- }
623
+ return fs2.readFileSync(filePath, "utf8");
624
+ } catch {}
625
+ return;
665
626
  }
666
627
  function writeCacheAtomically(cachePath, payload) {
667
628
  try {
668
- fs.mkdirSync(path2.dirname(cachePath), { recursive: true });
629
+ fs2.mkdirSync(path3.dirname(cachePath), { recursive: true });
669
630
  const temporary = `${cachePath}.${process.pid}.${randomUUID()}.tmp`;
670
- fs.writeFileSync(temporary, payload);
671
- fs.renameSync(temporary, cachePath);
631
+ fs2.writeFileSync(temporary, payload);
632
+ fs2.renameSync(temporary, cachePath);
672
633
  } catch {}
673
634
  }
635
+ function readCachedIndex(cachePath, signature) {
636
+ try {
637
+ const parsed = JSON.parse(String(fs2.readFileSync(cachePath)));
638
+ if (parsed.signature === signature && parsed.index)
639
+ return parsed.index;
640
+ } catch {}
641
+ return;
642
+ }
643
+ function scanKnowledgeIndex(symbolsPath, coordinatesPath, options) {
644
+ const symbolsText = readText(symbolsPath);
645
+ const coordinatesText = readText(coordinatesPath);
646
+ const records = symbolsText === undefined ? [] : scanSymbolsManifest(symbolsText);
647
+ return buildKnowledgeIndex(records, coordinatesText === undefined ? {} : scanSymbolCoordinates(coordinatesText), options, manifestStateOf(symbolsText, records.length));
648
+ }
674
649
  function loadKnowledgeIndex(workspaceRoot, cacheDir, options = {}) {
675
- const symbolsPath = path2.join(workspaceRoot, SYMBOLS_MANIFEST);
676
- const coordinatesPath = path2.join(workspaceRoot, SYMBOL_COORDINATES);
650
+ const symbolsPath = path3.join(workspaceRoot, SYMBOLS_MANIFEST);
651
+ const coordinatesPath = path3.join(workspaceRoot, SYMBOL_COORDINATES);
652
+ if (!cacheDir)
653
+ return scanKnowledgeIndex(symbolsPath, coordinatesPath, options);
677
654
  const legacyMode = options.legacyLinksAsImplements === false ? "typed" : "legacy";
678
655
  const signature = `${INDEX_FORMAT}|${legacyMode}|${fileSignature(symbolsPath)}|${fileSignature(coordinatesPath)}`;
679
- const cachePath = cacheDir ? path2.join(cacheDir, options.cacheFileName ?? CACHE_FILE) : undefined;
680
- if (cachePath) {
681
- const cached = readText(cachePath);
682
- if (cached !== undefined) {
683
- try {
684
- const parsed = JSON.parse(cached);
685
- if (parsed.signature === signature && parsed.index)
686
- return parsed.index;
687
- } catch {}
688
- }
689
- }
690
- const symbolsText = readText(symbolsPath);
691
- const records = symbolsText === undefined ? [] : scanSymbolsManifest(symbolsText);
692
- const index = buildKnowledgeIndex(records, scanSymbolCoordinates(readText(coordinatesPath) ?? ""), options, manifestStateOf(symbolsText, records.length));
693
- if (cachePath) {
694
- writeCacheAtomically(cachePath, JSON.stringify({ signature, index }));
695
- }
656
+ const cachePath = path3.join(cacheDir, options.cacheFileName ?? CACHE_FILE);
657
+ const cached = readCachedIndex(cachePath, signature);
658
+ if (cached !== undefined)
659
+ return cached;
660
+ const index = scanKnowledgeIndex(symbolsPath, coordinatesPath, options);
661
+ writeCacheAtomically(cachePath, JSON.stringify({ signature, index }));
696
662
  return index;
697
663
  }
698
664
  var ENTITY_LANES = [
@@ -724,8 +690,7 @@ function readScalar(lines, index, raw) {
724
690
  const block = /^[>|][+-]?$/.test(raw.trim());
725
691
  const parts = block ? [] : [raw.trim()];
726
692
  let last = index;
727
- while (last + 1 < lines.length) {
728
- const next = lines[last + 1] ?? "";
693
+ for (const next of lines.slice(index + 1)) {
729
694
  if (next.trim() === "---" || !/^\s+\S/.test(next))
730
695
  break;
731
696
  if (!block && /^\s+-\s/.test(next))
@@ -733,7 +698,7 @@ function readScalar(lines, index, raw) {
733
698
  parts.push(next.trim());
734
699
  last += 1;
735
700
  }
736
- const joined = parts.join(" ").trim();
701
+ const joined = parts.join(" ");
737
702
  return { value: block ? joined : unquote(joined), last };
738
703
  }
739
704
  function firstParagraph(lines) {
@@ -752,33 +717,30 @@ function firstParagraph(lines) {
752
717
  }
753
718
  paragraph.push(text);
754
719
  }
755
- return paragraph.length > 0 ? paragraph.join(" ") : undefined;
720
+ return paragraph.join(" ");
756
721
  }
757
722
  var MAX_SUMMARY_LINKS = 24;
758
723
  var RELATIONSHIPS_DIR = ".kb/relationships";
759
724
  var shardCache = new Map;
760
725
  function shardPathFor(workspaceRoot, entityId) {
761
726
  const shard = createHash("sha256").update(entityId).digest("hex");
762
- return path2.join(workspaceRoot, RELATIONSHIPS_DIR, `${shard.slice(0, 2)}.yaml`);
727
+ return path3.join(workspaceRoot, RELATIONSHIPS_DIR, `${shard.slice(0, 2)}.yaml`);
763
728
  }
764
729
  function shardLinksFrom(workspaceRoot, entityId) {
765
730
  const shardPath = shardPathFor(workspaceRoot, entityId);
766
731
  const signature = fileSignature(shardPath);
767
- if (signature === "missing") {
768
- shardCache.delete(shardPath);
769
- return [];
770
- }
771
732
  let cached = shardCache.get(shardPath);
772
733
  if (cached?.signature !== signature) {
773
734
  const byFrom = new Map;
774
- const records = scanRecordSequence(readText(shardPath) ?? "", "relationships");
775
- for (const { type, from, to } of records) {
776
- if (typeof type !== "string" || typeof from !== "string" || typeof to !== "string") {
777
- continue;
735
+ const text = readText(shardPath);
736
+ if (text !== undefined) {
737
+ for (const { type, from, to } of scanRecordSequence(text, "relationships")) {
738
+ if (typeof type !== "string" || typeof to !== "string")
739
+ continue;
740
+ const list = byFrom.get(from) ?? [];
741
+ list.push({ type, target: to });
742
+ byFrom.set(from, list);
778
743
  }
779
- const list = byFrom.get(from) ?? [];
780
- list.push({ type, target: to });
781
- byFrom.set(from, list);
782
744
  }
783
745
  cached = { signature, byFrom };
784
746
  shardCache.set(shardPath, cached);
@@ -798,19 +760,19 @@ function readEntitySummary(workspaceRoot, entityId) {
798
760
  return { id: entityId };
799
761
  let head;
800
762
  try {
801
- const descriptor = fs.openSync(path2.join(workspaceRoot, ".kb", lane[1], `${entityId}.md`), "r");
763
+ const descriptor = fs2.openSync(path3.join(workspaceRoot, ".kb", lane[1], `${entityId}.md`), "r");
802
764
  try {
803
765
  const buffer = Buffer.alloc(8192);
804
- const bytes = fs.readSync(descriptor, buffer, 0, buffer.length, 0);
766
+ const bytes = fs2.readSync(descriptor, buffer, 0, buffer.length, 0);
805
767
  head = buffer.subarray(0, bytes).toString("utf8");
806
768
  } finally {
807
- fs.closeSync(descriptor);
769
+ fs2.closeSync(descriptor);
808
770
  }
809
771
  } catch {
810
772
  return { id: entityId };
811
773
  }
812
774
  const lines = head.split(/\r?\n/);
813
- if (lines[0]?.trim() !== "---")
775
+ if (lines[0].trim() !== "---")
814
776
  return { id: entityId };
815
777
  const summary = { id: entityId };
816
778
  const authored = [];
@@ -824,20 +786,20 @@ function readEntitySummary(workspaceRoot, entityId) {
824
786
  let bodyStart = lines.length;
825
787
  let pending;
826
788
  for (let index = 1;index < lines.length; index += 1) {
827
- const line = lines[index] ?? "";
789
+ const line = lines[index];
828
790
  if (line.trim() === "---") {
829
791
  closed = true;
830
792
  bodyStart = index + 1;
831
793
  break;
832
794
  }
833
- const scalar = /^([a-z_]+):\s*(.*)$/.exec(line);
834
- const scalarField = scalar ? SUMMARY_SCALARS[scalar[1] ?? ""] : undefined;
795
+ const scalar = /^([a-z_]+):\s*(.*)/.exec(line);
796
+ const scalarField = scalar ? SUMMARY_SCALARS[scalar[1]] : undefined;
835
797
  if (scalar && scalarField && scalar[2]) {
836
798
  const { value, last } = readScalar(lines, index, scalar[2]);
837
799
  if (value !== "") {
838
800
  if (scalarField === "statement")
839
801
  summary.statement = value;
840
- else if (scalarField !== "values")
802
+ else
841
803
  claim[scalarField] = value;
842
804
  }
843
805
  index = last;
@@ -847,19 +809,19 @@ function readEntitySummary(workspaceRoot, entityId) {
847
809
  inValueSet = false;
848
810
  continue;
849
811
  }
850
- if (scalar && SCALAR_VALUE_FIELDS.has(scalar[1] ?? "") && scalar[2]) {
812
+ if (scalar && SCALAR_VALUE_FIELDS.has(scalar[1]) && scalar[2]) {
851
813
  values.push(unquote(scalar[2]));
852
814
  }
853
815
  const flowSet = /^value_set:\s*\[(.*)\]\s*$/.exec(line);
854
- if (flowSet?.[1]?.trim()) {
816
+ if (flowSet !== null && flowSet[1].trim()) {
855
817
  values.push(...flowSet[1].split(",").map((member) => unquote(member)));
856
818
  }
857
819
  if (/^\S/.test(line))
858
820
  inValueSet = /^value_set:\s*$/.test(line);
859
821
  else if (inValueSet) {
860
- const member = /^\s*-\s*(.+)$/.exec(line);
822
+ const member = /^\s*-\s*(.+)/.exec(line);
861
823
  if (member)
862
- values.push(unquote(member[1] ?? ""));
824
+ values.push(unquote(member[1]));
863
825
  }
864
826
  const flowOrigin = /^origin:\s*\{.*\bkind:\s*["']?([a-z]+)/.exec(line);
865
827
  if (flowOrigin)
@@ -869,7 +831,7 @@ function readEntitySummary(workspaceRoot, entityId) {
869
831
  originKind = nestedKind[1];
870
832
  if (/^\S/.test(line))
871
833
  inOrigin = /^origin:\s*$/.test(line);
872
- const match = /^(title|status|firmness):\s*(.*)$/.exec(line);
834
+ const match = /^(title|status|firmness):\s*(.*)/.exec(line);
873
835
  if (match?.[1] === "title" && match[2])
874
836
  summary.title = unquote(match[2]);
875
837
  if (match?.[1] === "status" && match[2])
@@ -884,19 +846,19 @@ function readEntitySummary(workspaceRoot, entityId) {
884
846
  }
885
847
  if (!inLinks)
886
848
  continue;
887
- const entry = /^\s*-\s*(.*)$/.exec(line);
849
+ const entry = /^\s*-\s*(.*)/.exec(line);
888
850
  if (entry) {
889
851
  flushLink(authored, pending);
890
852
  pending = {};
891
853
  }
892
854
  if (!pending)
893
855
  continue;
894
- const body = (entry ? entry[1] ?? "" : line).trim();
895
- const field = /^(type|target):\s*(.+)$/.exec(body);
856
+ const body = (entry ? entry[1] : line).trim();
857
+ const field = /^(type|target):\s*(.+)/.exec(body);
896
858
  if (field?.[1] === "type")
897
- pending.type = unquote(field[2] ?? "");
859
+ pending.type = unquote(field[2]);
898
860
  else if (field?.[1] === "target")
899
- pending.target = unquote(field[2] ?? "");
861
+ pending.target = unquote(field[2]);
900
862
  else if (entry && SAFE_ENTITY_ID.test(unquote(body)))
901
863
  pending.bare = unquote(body);
902
864
  }
@@ -944,45 +906,28 @@ function truncate(text, limit) {
944
906
  return text.length <= limit ? text : `${text.slice(0, limit - 1)}…`;
945
907
  }
946
908
  function overlaps(symbol, range) {
947
- if (symbol.line === undefined)
948
- return false;
949
- const end = symbol.endLine ?? symbol.line;
950
- return symbol.line <= range.end && end >= range.start;
909
+ const line = Number(symbol.line);
910
+ return line <= range.end && Number(symbol.endLine ?? line) >= range.start;
911
+ }
912
+ function span(symbol) {
913
+ return Number(symbol.endLine ?? symbol.line) - Number(symbol.line);
951
914
  }
952
915
  function focusedSymbols(symbols, focus) {
953
- if (!focus || focus.length === 0)
916
+ if (!focus)
954
917
  return [];
955
- return symbols.filter((symbol) => focus.some((range) => overlaps(symbol, range))).sort((left, right) => (left.endLine ?? left.line ?? 0) - (left.line ?? 0) - ((right.endLine ?? right.line ?? 0) - (right.line ?? 0)));
956
- }
957
- function isRecord(value) {
958
- return typeof value === "object" && value !== null && !Array.isArray(value);
918
+ return symbols.filter((symbol) => focus.some((range) => overlaps(symbol, range))).sort((left, right) => span(left) - span(right));
959
919
  }
960
920
  function editFocus(absolutePath, toolInput) {
961
- if (!isRecord(toolInput))
962
- return;
963
- const needles = [];
964
- const collect = (record) => {
965
- for (const key of ["old_string", "oldString"]) {
966
- const value = record[key];
967
- if (typeof value === "string")
968
- needles.push(value);
969
- }
970
- };
971
- collect(toolInput);
972
- if (Array.isArray(toolInput.edits)) {
973
- for (const edit of toolInput.edits) {
974
- if (isRecord(edit))
975
- collect(edit);
976
- }
977
- }
978
- const usable = needles.filter((needle) => needle.length > 0);
921
+ const input = toolInput ?? {};
922
+ const edits = Array.isArray(input.edits) ? input.edits : [];
923
+ const usable = [input, ...edits.map((edit) => edit ?? {})].flatMap((record) => [record.old_string, record.oldString]).filter((needle) => typeof needle === "string" && needle.length > 0);
979
924
  if (usable.length === 0)
980
925
  return;
981
926
  let content;
982
927
  try {
983
- if (fs2.statSync(absolutePath).size > MAX_FOCUS_SCAN_BYTES)
928
+ if (fs3.statSync(absolutePath).size > MAX_FOCUS_SCAN_BYTES)
984
929
  return;
985
- content = fs2.readFileSync(absolutePath, "utf8");
930
+ content = fs3.readFileSync(absolutePath, "utf8");
986
931
  } catch {
987
932
  return;
988
933
  }
@@ -1004,7 +949,7 @@ function formatList(items, limit) {
1004
949
  }
1005
950
  var RETIRED_STATUS = /supersed|deprecat|reject|obsolete|retired/i;
1006
951
  function isRetiredStatus(status) {
1007
- return status !== undefined && RETIRED_STATUS.test(status);
952
+ return RETIRED_STATUS.test(String(status));
1008
953
  }
1009
954
  function describeEntity(summary) {
1010
955
  const retired = isRetiredStatus(summary.status) ? ` (${summary.status})` : "";
@@ -1021,7 +966,7 @@ function displayTitle(summary) {
1021
966
  if (!/^\.\S/.test(rest))
1022
967
  return title;
1023
968
  const end = rest.search(/[.!?](?:\s|$)/);
1024
- return end < 0 ? statement : statement.slice(0, title.length + end);
969
+ return end === -1 ? statement : statement.slice(0, title.length + end);
1025
970
  }
1026
971
  function jsonString(value) {
1027
972
  return JSON.stringify(value);
@@ -1055,13 +1000,13 @@ var OPERATOR_PHRASES = {
1055
1000
  excludes: "must not include"
1056
1001
  };
1057
1002
  function joinMembers(values, conjunction) {
1058
- if (values.length <= 1)
1059
- return values[0] ?? "";
1003
+ if (values.length === 1)
1004
+ return values[0];
1060
1005
  return `${values.slice(0, -1).join(", ")} ${conjunction} ${values.at(-1)}`;
1061
1006
  }
1062
1007
  var MODAL = /^(.{1,100}?)\s+(?:must|may|can|cannot|should|shall|will|never)\b/i;
1063
1008
  function subjectPhrase(claim) {
1064
- const spoken = MODAL.exec(claim.claimText ?? "")?.[1];
1009
+ const spoken = MODAL.exec(String(claim.claimText))?.[1];
1065
1010
  if (spoken)
1066
1011
  return spoken;
1067
1012
  return claim.subjectKey?.replace(/[._]+/g, " ").trim() || undefined;
@@ -1146,10 +1091,7 @@ function createEntitySummarizer(workspaceRoot) {
1146
1091
  }
1147
1092
  function requirementOwners(symbols, focus) {
1148
1093
  const owners = new Map;
1149
- const ordered = [
1150
- ...focus,
1151
- ...symbols.filter((symbol) => !focus.includes(symbol))
1152
- ];
1094
+ const ordered = new Set([...focus, ...symbols]);
1153
1095
  for (const symbol of ordered) {
1154
1096
  for (const requirement of symbol.implements) {
1155
1097
  const titles = owners.get(requirement) ?? [];
@@ -1161,7 +1103,7 @@ function requirementOwners(symbols, focus) {
1161
1103
  const focusOwners = new Set(focus.flatMap((symbol) => symbol.implements));
1162
1104
  const requirementIds = [...owners.keys()].sort((left, right) => {
1163
1105
  const focusOrder = Number(focusOwners.has(right)) - Number(focusOwners.has(left));
1164
- return focusOrder !== 0 ? focusOrder : (owners.get(right)?.length ?? 0) - (owners.get(left)?.length ?? 0);
1106
+ return focusOrder !== 0 ? focusOrder : owners.get(right).length - owners.get(left).length;
1165
1107
  });
1166
1108
  return { owners, requirementIds };
1167
1109
  }
@@ -1182,7 +1124,7 @@ function fileKnowledgeSnippet(input) {
1182
1124
  `Kibi knowledge for ${relativePath} (symbol manifest):`
1183
1125
  ];
1184
1126
  for (const requirementId of requirementIds.slice(0, MAX_REQUIREMENTS)) {
1185
- const symbolTitles = owners.get(requirementId) ?? [];
1127
+ const symbolTitles = owners.get(requirementId);
1186
1128
  lines.push(`- ${describeEntity(summarize(requirementId))} — ${formatList(symbolTitles, MAX_SYMBOLS_PER_REQUIREMENT)}`);
1187
1129
  }
1188
1130
  if (requirementIds.length > MAX_REQUIREMENTS) {
@@ -1263,32 +1205,39 @@ function firmEditNotice(where, requirements) {
1263
1205
  }
1264
1206
  function sessionPath(session, name) {
1265
1207
  if (!session.stateDir)
1266
- return;
1208
+ throw new Error;
1267
1209
  const key = createHash2("sha256").update(session.sessionId?.trim() || "unattributed").digest("hex").slice(0, 32);
1268
- return path3.join(session.stateDir, "decision-guard", key, name);
1210
+ return path4.join(session.stateDir, "decision-guard", key, name);
1211
+ }
1212
+ function writeMarker(file, content) {
1213
+ fs4.mkdirSync(path4.dirname(file), { recursive: true });
1214
+ fs4.writeFileSync(file, content);
1215
+ }
1216
+ function touchMarker(file, flag) {
1217
+ fs4.mkdirSync(path4.dirname(file), { recursive: true });
1218
+ fs4.closeSync(fs4.openSync(file, flag));
1269
1219
  }
1270
- function writeMarker(file, content, flag) {
1271
- fs3.mkdirSync(path3.dirname(file), { recursive: true });
1272
- fs3.writeFileSync(file, content, { flag });
1220
+ function readMarker(session, name) {
1221
+ return JSON.parse(String(fs4.readFileSync(sessionPath(session, name))));
1222
+ }
1223
+ function stringList(value) {
1224
+ return Array.isArray(value) ? value.filter((item) => typeof item === "string") : [];
1273
1225
  }
1274
1226
  function markDecisionFlag(session, flag) {
1275
- const file = sessionPath(session, flag);
1276
- if (!file)
1277
- return;
1278
1227
  try {
1279
- writeMarker(file, "", "w");
1228
+ touchMarker(sessionPath(session, flag), "w");
1280
1229
  } catch {}
1281
1230
  }
1282
1231
  function hasDecisionFlag(session, flag) {
1283
- const file = sessionPath(session, flag);
1284
- return file !== undefined && fs3.existsSync(file);
1232
+ try {
1233
+ return fs4.existsSync(sessionPath(session, flag));
1234
+ } catch {
1235
+ return false;
1236
+ }
1285
1237
  }
1286
1238
  function claimDecisionFlag(session, flag) {
1287
- const file = sessionPath(session, flag);
1288
- if (!file)
1289
- return false;
1290
1239
  try {
1291
- writeMarker(file, "", "wx");
1240
+ touchMarker(sessionPath(session, flag), "wx");
1292
1241
  return true;
1293
1242
  } catch {
1294
1243
  return false;
@@ -1296,12 +1245,9 @@ function claimDecisionFlag(session, flag) {
1296
1245
  }
1297
1246
  function recordRequest(session, prompt) {
1298
1247
  const rule = statedRule(prompt);
1299
- const file = sessionPath(session, "request-words.json");
1300
- if (file) {
1301
- try {
1302
- writeMarker(file, JSON.stringify(requestTopicWords(prompt)), "w");
1303
- } catch {}
1304
- }
1248
+ try {
1249
+ writeMarker(sessionPath(session, "request-words.json"), JSON.stringify(requestTopicWords(prompt)));
1250
+ } catch {}
1305
1251
  if (rule !== undefined)
1306
1252
  markDecisionFlag(session, "rule-prompt");
1307
1253
  return rule;
@@ -1309,9 +1255,9 @@ function recordRequest(session, prompt) {
1309
1255
  function claimCaptureReminder(session) {
1310
1256
  return hasDecisionFlag(session, "source-edited") && !hasDecisionFlag(session, "kb-recorded") && claimDecisionFlag(session, "capture-asked");
1311
1257
  }
1312
- var SIGNOFF_LATEST_JSON = path3.join(".kb", "briefs", "signoff", "latest.json");
1258
+ var SIGNOFF_LATEST_JSON = path4.join(".kb", "briefs", "signoff", "latest.json");
1313
1259
  function record(value) {
1314
- return typeof value === "object" && value !== null && !Array.isArray(value) ? value : undefined;
1260
+ return typeof value === "object" && !Array.isArray(value) ? value : undefined;
1315
1261
  }
1316
1262
  function isRequirementUpsert(toolInput) {
1317
1263
  const input = record(toolInput);
@@ -1319,20 +1265,18 @@ function isRequirementUpsert(toolInput) {
1319
1265
  return args?.type === "req" && args.dryRun !== true;
1320
1266
  }
1321
1267
  function mtimeMs(file) {
1322
- if (!file)
1323
- return;
1268
+ let time = Number.NaN;
1324
1269
  try {
1325
- return fs3.statSync(file).mtimeMs;
1326
- } catch {
1327
- return;
1328
- }
1270
+ time = fs4.statSync(file()).mtimeMs;
1271
+ } catch {}
1272
+ return time;
1329
1273
  }
1330
1274
  function claimSignoffReminder(session, workspaceRoot) {
1331
- const recorded = mtimeMs(sessionPath(session, "requirement-recorded"));
1332
- if (recorded === undefined)
1275
+ const recorded = mtimeMs(() => sessionPath(session, "requirement-recorded"));
1276
+ if (Number.isNaN(recorded))
1333
1277
  return false;
1334
- const shown = mtimeMs(path3.join(workspaceRoot, SIGNOFF_LATEST_JSON));
1335
- if (shown !== undefined && shown >= recorded)
1278
+ const shown = mtimeMs(() => path4.join(workspaceRoot, SIGNOFF_LATEST_JSON));
1279
+ if (shown >= recorded)
1336
1280
  return false;
1337
1281
  return claimDecisionFlag(session, "signoff-asked");
1338
1282
  }
@@ -1343,55 +1287,43 @@ function signoffReminder() {
1343
1287
  ].join(`
1344
1288
  `);
1345
1289
  }
1346
- function readSettled(file) {
1347
- if (!file)
1348
- return [];
1290
+ function readSettled(session) {
1349
1291
  try {
1350
- const parsed = JSON.parse(fs3.readFileSync(file, "utf8"));
1351
- return Array.isArray(parsed) ? parsed.filter((id) => typeof id === "string") : [];
1292
+ return stringList(readMarker(session, "firm-settled.json"));
1352
1293
  } catch {
1353
1294
  return [];
1354
1295
  }
1355
1296
  }
1356
1297
  function settledFirmRequirements(session) {
1357
- return new Set(readSettled(sessionPath(session, "firm-settled.json")));
1298
+ return new Set(readSettled(session));
1358
1299
  }
1359
1300
  function settleFirmRequirements(session, ids) {
1360
- const file = sessionPath(session, "firm-settled.json");
1361
- if (!file)
1362
- return;
1363
1301
  try {
1364
- writeMarker(file, JSON.stringify([...new Set([...readSettled(file), ...ids])]), "w");
1302
+ writeMarker(sessionPath(session, "firm-settled.json"), JSON.stringify([...new Set([...readSettled(session), ...ids])]));
1365
1303
  } catch {}
1366
1304
  }
1367
1305
  function holdRuleNote(session, phrase) {
1368
- const file = sessionPath(session, "rule-note");
1369
- if (!file)
1370
- return;
1371
1306
  try {
1372
- writeMarker(file, rulePromptNote(phrase), "w");
1307
+ writeMarker(sessionPath(session, "rule-note"), rulePromptNote(phrase));
1373
1308
  } catch {}
1374
1309
  }
1375
1310
  function takeRuleNote(session) {
1376
- const file = sessionPath(session, "rule-note");
1377
- if (!file)
1378
- return;
1311
+ let note = "";
1379
1312
  try {
1380
- const note = fs3.readFileSync(file, "utf8");
1381
- fs3.rmSync(file, { force: true });
1382
- return note || undefined;
1383
- } catch {
1384
- return;
1385
- }
1313
+ const file = sessionPath(session, "rule-note");
1314
+ note = String(fs4.readFileSync(file));
1315
+ fs4.rmSync(file);
1316
+ } catch {}
1317
+ return note || undefined;
1386
1318
  }
1387
1319
 
1388
1320
  // ../agent-core/dist/hook-usage-log.js
1389
- import { randomUUID as randomUUID3 } from "node:crypto";
1390
- import fs5 from "node:fs";
1391
- import path6 from "node:path";
1321
+ import { randomUUID as randomUUID4 } from "node:crypto";
1322
+ import fs7 from "node:fs";
1323
+ import path8 from "node:path";
1392
1324
 
1393
1325
  // ../agent-core/dist/kb-mcp-tools.js
1394
- function isRecord2(value) {
1326
+ function isRecord(value) {
1395
1327
  return typeof value === "object" && value !== null && !Array.isArray(value);
1396
1328
  }
1397
1329
  function readString(record, keys) {
@@ -1425,33 +1357,33 @@ function readStringArray(record, keys) {
1425
1357
  function readRecord(record, keys) {
1426
1358
  for (const key of keys) {
1427
1359
  const value = record[key];
1428
- if (isRecord2(value)) {
1360
+ if (isRecord(value)) {
1429
1361
  return value;
1430
1362
  }
1431
1363
  }
1432
1364
  return;
1433
1365
  }
1434
1366
  function canonicalKbToolName(toolName) {
1435
- const trimmed = toolName?.trim() ?? "";
1436
- const lastSegment = trimmed.includes("__") ? trimmed.split("__").at(-1) ?? "" : trimmed.replace(/^MCP:/i, "");
1367
+ const trimmed = String(toolName).trim();
1368
+ const lastSegment = trimmed.includes("__") ? trimmed.split("__").at(-1) : trimmed.replace(/^MCP:/i, "");
1437
1369
  const operation = lastSegment.replace(/^kibi_/, "");
1438
1370
  return operation.startsWith("kb_") ? operation : undefined;
1439
1371
  }
1440
1372
  var KIBI_WORKSPACE_ARGUMENT = "workspaceRoot";
1441
1373
  function isKibiMcpToolName(toolName) {
1442
- const name = toolName?.trim() ?? "";
1374
+ const name = String(toolName).trim();
1443
1375
  if (/^MCP:kb_[a-z_]+$/i.test(name))
1444
1376
  return true;
1445
1377
  const segments = name.split("__");
1446
- if (segments.length < 3 || segments[0] !== "mcp")
1378
+ if (segments[0] !== "mcp")
1447
1379
  return false;
1448
1380
  const server = segments.slice(1, -1).join("__");
1449
- return /kibi/i.test(server) && /^kb_[a-z_]+$/.test(segments.at(-1) ?? "");
1381
+ return /kibi/i.test(server) && /^kb_[a-z_]+$/.test(segments.at(-1));
1450
1382
  }
1451
1383
  function stampKibiWorkspace(toolName, toolInput, workspaceRoot) {
1452
1384
  if (!workspaceRoot || !isKibiMcpToolName(toolName))
1453
1385
  return;
1454
- const base = isRecord2(toolInput) ? toolInput : {};
1386
+ const base = isRecord(toolInput) ? toolInput : {};
1455
1387
  return { ...base, [KIBI_WORKSPACE_ARGUMENT]: workspaceRoot };
1456
1388
  }
1457
1389
  function resolveKibiInterface(mcpState, workspaceTrusted) {
@@ -1460,58 +1392,31 @@ function resolveKibiInterface(mcpState, workspaceTrusted) {
1460
1392
  }
1461
1393
  return workspaceTrusted ? "cli" : "setup";
1462
1394
  }
1395
+ var TOOL_NAME_KEYS = ["toolName", "tool_name", "name"];
1463
1396
  function extractKbMcpToolCall(toolName, toolInput) {
1464
- let normalizedToolName = canonicalKbToolName(toolName);
1465
- if (isRecord2(toolInput)) {
1466
- normalizedToolName ??= readString(toolInput, [
1467
- "toolName",
1468
- "tool_name",
1469
- "name"
1470
- ]);
1471
- const args = readRecord(toolInput, ["arguments", "args"]);
1472
- const payload = args ?? toolInput;
1473
- const includeImpactDiagnostics = readBoolean(payload, [
1397
+ const input = isRecord(toolInput) ? toolInput : {};
1398
+ let name = canonicalKbToolName(toolName) ?? readString(input, TOOL_NAME_KEYS);
1399
+ if (!name?.startsWith("kb_")) {
1400
+ const nested = input.arguments ?? input.args;
1401
+ const nestedTool = isRecord(nested) ? readString(nested, TOOL_NAME_KEYS) : undefined;
1402
+ return nestedTool?.startsWith("kb_") ? { toolName: nestedTool, impactCheckRun: false, sourceFiles: [] } : undefined;
1403
+ }
1404
+ const payload = readRecord(input, ["arguments", "args"]) ?? input;
1405
+ if (name === "kb_upsert" && readBoolean(payload, ["dryRun"]) === true) {
1406
+ name = "kb_validate_upsert";
1407
+ }
1408
+ const sourceFiles = readStringArray(payload, ["sourceFiles", "source_files"]);
1409
+ return {
1410
+ toolName: name,
1411
+ impactCheckRun: name === "kb_check" && readBoolean(payload, [
1474
1412
  "includeImpactDiagnostics",
1475
1413
  "include_impact_diagnostics"
1476
- ]);
1477
- const includeWorkingTreeDiff = readBoolean(payload, [
1414
+ ]) === true && readBoolean(payload, [
1478
1415
  "includeWorkingTreeDiff",
1479
1416
  "include_working_tree_diff"
1480
- ]);
1481
- const sourceFiles = readStringArray(payload, [
1482
- "sourceFiles",
1483
- "source_files"
1484
- ]);
1485
- if (normalizedToolName === "kb_upsert" && readBoolean(payload, ["dryRun"]) === true) {
1486
- normalizedToolName = "kb_validate_upsert";
1487
- }
1488
- if (normalizedToolName?.startsWith("kb_")) {
1489
- return {
1490
- toolName: normalizedToolName,
1491
- impactCheckRun: normalizedToolName === "kb_check" && includeImpactDiagnostics === true && includeWorkingTreeDiff === true && sourceFiles.length > 0,
1492
- sourceFiles
1493
- };
1494
- }
1495
- const nestedArgs = toolInput.arguments ?? toolInput.args;
1496
- if (isRecord2(nestedArgs)) {
1497
- const nestedTool = readString(nestedArgs, [
1498
- "toolName",
1499
- "tool_name",
1500
- "name"
1501
- ]);
1502
- if (nestedTool?.startsWith("kb_")) {
1503
- return { toolName: nestedTool, impactCheckRun: false, sourceFiles: [] };
1504
- }
1505
- }
1506
- }
1507
- if (normalizedToolName?.startsWith("kb_")) {
1508
- return {
1509
- toolName: normalizedToolName,
1510
- impactCheckRun: false,
1511
- sourceFiles: []
1512
- };
1513
- }
1514
- return;
1417
+ ]) === true && sourceFiles.length > 0,
1418
+ sourceFiles
1419
+ };
1515
1420
  }
1516
1421
  var KIBI_CLI_ROUTES = {
1517
1422
  check: "kb_check",
@@ -1535,16 +1440,16 @@ function kibiCliOperation(command) {
1535
1440
  var SHELL_TOOL_NAMES = new Set(["Bash", "bash", "Shell", "shell"]);
1536
1441
  function hostKbOperation(toolName, toolInput) {
1537
1442
  const mcp = canonicalKbToolName(toolName);
1538
- if (mcp !== undefined && /^kb_[a-z_]+$/.test(mcp))
1443
+ if (/kb_[a-z_]+$/.test(String(mcp)))
1539
1444
  return mcp;
1540
- if (!toolName || !SHELL_TOOL_NAMES.has(toolName) || !isRecord2(toolInput)) {
1445
+ if (!toolName || !SHELL_TOOL_NAMES.has(toolName) || !isRecord(toolInput)) {
1541
1446
  return;
1542
1447
  }
1543
1448
  return kibiCliOperation(toolInput.command);
1544
1449
  }
1545
1450
 
1546
1451
  // ../agent-core/dist/path-policy.js
1547
- import path4 from "node:path";
1452
+ import path5 from "node:path";
1548
1453
  var codeExtensions = new Set([
1549
1454
  ".c",
1550
1455
  ".cc",
@@ -1629,7 +1534,7 @@ var explicitPathKeys = new Set([
1629
1534
  "relative_path",
1630
1535
  "target_path"
1631
1536
  ]);
1632
- function isRecord3(value) {
1537
+ function isRecord2(value) {
1633
1538
  return typeof value === "object" && value !== null && !Array.isArray(value);
1634
1539
  }
1635
1540
  function normalizeWorkspacePath(candidate) {
@@ -1656,7 +1561,7 @@ function visitExplicitPathFields(value, output) {
1656
1561
  visitExplicitPathFields(item, output);
1657
1562
  return;
1658
1563
  }
1659
- if (!isRecord3(value))
1564
+ if (!isRecord2(value))
1660
1565
  return;
1661
1566
  for (const [key, child] of Object.entries(value)) {
1662
1567
  if (explicitPathKeys.has(key.toLowerCase()))
@@ -1678,11 +1583,13 @@ function classifyPath(relativePath) {
1678
1583
  if (segments.some((segment) => documentationSegments.has(segment))) {
1679
1584
  return "other";
1680
1585
  }
1681
- const basename = segments.at(-1) ?? "";
1682
- const extension = path4.extname(basename).toLowerCase();
1586
+ const basename = segments.at(-1);
1587
+ if (basename === undefined)
1588
+ return "other";
1589
+ const extension = path5.extname(basename).toLowerCase();
1683
1590
  if (!codeExtensions.has(extension))
1684
1591
  return "other";
1685
- if (testBasename.test(basename) || segments.slice(0, -1).some((segment) => testSegments.has(segment))) {
1592
+ if (testBasename.test(basename) || segments.some((segment) => testSegments.has(segment))) {
1686
1593
  return "test";
1687
1594
  }
1688
1595
  return "source";
@@ -1691,11 +1598,8 @@ function isKbPath(candidate) {
1691
1598
  return pathSegments(candidate).includes(".kb");
1692
1599
  }
1693
1600
  function isCanonicalKbKnowledgePath(segments) {
1694
- const kbIndex = segments.indexOf(".kb");
1695
- if (kbIndex < 0)
1696
- return false;
1697
- const lane = segments[kbIndex + 1];
1698
- return lane !== undefined && (canonicalKbKnowledgeFiles.has(lane) || canonicalKbKnowledgeLanes.has(lane));
1601
+ const lane = segments[segments.indexOf(".kb") + 1];
1602
+ return canonicalKbKnowledgeFiles.has(lane) || canonicalKbKnowledgeLanes.has(lane);
1699
1603
  }
1700
1604
  function isMeaningfulTrackedPath(candidate) {
1701
1605
  const segments = pathSegments(candidate);
@@ -1703,14 +1607,16 @@ function isMeaningfulTrackedPath(candidate) {
1703
1607
  return false;
1704
1608
  if (segments.includes(".kb"))
1705
1609
  return isCanonicalKbKnowledgePath(segments);
1706
- const basename = segments.at(-1) ?? "";
1707
- const extension = path4.extname(basename).toLowerCase();
1610
+ const basename = segments.at(-1);
1611
+ if (basename === undefined)
1612
+ return false;
1613
+ const extension = path5.extname(basename).toLowerCase();
1708
1614
  if (basename === "README.md")
1709
1615
  return true;
1710
1616
  if (segments.some((segment) => documentationSegments.has(segment))) {
1711
1617
  return documentationExtensions.has(extension);
1712
1618
  }
1713
- if (testBasename.test(basename) || segments.slice(0, -1).some((segment) => testSegments.has(segment))) {
1619
+ if (testBasename.test(basename) || segments.some((segment) => testSegments.has(segment))) {
1714
1620
  return codeExtensions.has(extension) || documentationExtensions.has(extension);
1715
1621
  }
1716
1622
  return codeExtensions.has(extension);
@@ -1720,15 +1626,127 @@ function isSourceImpactRelevantPath(candidate) {
1720
1626
  }
1721
1627
  function isDocumentationTrackedPath(candidate) {
1722
1628
  const segments = pathSegments(candidate);
1723
- const basename = segments.at(-1) ?? "";
1724
- return basename === "README.md" || segments.some((segment) => documentationSegments.has(segment)) || documentationExtensions.has(path4.extname(basename).toLowerCase());
1629
+ const basename = segments.at(-1);
1630
+ if (basename === undefined)
1631
+ return false;
1632
+ return segments.some((segment) => documentationSegments.has(segment)) || documentationExtensions.has(path5.extname(basename).toLowerCase());
1725
1633
  }
1726
1634
 
1727
- // ../agent-core/dist/usage-telemetry.js
1635
+ // ../agent-core/dist/telemetry-signals.js
1728
1636
  import { createHash as createHash3, randomUUID as randomUUID2 } from "node:crypto";
1729
- import fs4 from "node:fs";
1637
+ import fs5 from "node:fs";
1638
+ import path6 from "node:path";
1639
+ var RETRY_WINDOW_MS = 10 * 60 * 1000;
1640
+ var SEARCH_REPEAT_WINDOW_MS = 2 * 60 * 1000;
1641
+ var SKILL_WINDOW_MS = 30 * 60 * 1000;
1642
+ var MAX_RECENT_ENTRIES = 200;
1643
+ var RECENT_FILE = "telemetry-recent.json";
1644
+ var KIBI_SKILL_NAMES = new Set([
1645
+ "kibi-bootstrap",
1646
+ "kibi-commit-review",
1647
+ "kibi-decisions",
1648
+ "kibi-freshness",
1649
+ "kibi-provable-code",
1650
+ "kibi-traceability",
1651
+ "kibi-usage"
1652
+ ]);
1653
+ var RESULT_COUNT_BUCKETS = [
1654
+ [0, 0, "0"],
1655
+ [1, 1, "1"],
1656
+ [2, 5, "2-5"],
1657
+ [6, 10, "6-10"],
1658
+ [11, Number.POSITIVE_INFINITY, "11+"]
1659
+ ];
1660
+ var RELATIONSHIP_COUNT_BUCKETS = [
1661
+ [0, 0, "0"],
1662
+ [1, 1, "1"],
1663
+ [2, 5, "2-5"],
1664
+ [6, Number.POSITIVE_INFINITY, "6+"]
1665
+ ];
1666
+ var SMALL_COUNT_BUCKETS = [
1667
+ [1, 1, "1"],
1668
+ [2, 2, "2"],
1669
+ [3, 3, "3"],
1670
+ [4, Number.POSITIVE_INFINITY, "4+"]
1671
+ ];
1672
+ var KIBI_SOURCE_PACKAGES = "agent-core|cli|mcp|core|runtime|claude|codex|cursor|opencode|zcode|vscode|plugin-[a-z0-9-]+|lang-[a-z0-9-]+";
1673
+ var SOURCE_FRAME = new RegExp(`(?:^|/)packages/(${KIBI_SOURCE_PACKAGES})/((?:src|dist|bin)/.+)$`);
1674
+ function isRecord3(value) {
1675
+ return typeof value === "object" && value !== null;
1676
+ }
1677
+ var SKILL_FILE = /(?:^|[\\/])(kibi-[a-z0-9-]+)[\\/]SKILL\.md\b/i;
1678
+ function skillName(name) {
1679
+ return KIBI_SKILL_NAMES.has(name) ? name : "other";
1680
+ }
1681
+ function skillInStrings(value, depth) {
1682
+ if (typeof value === "string") {
1683
+ const match = SKILL_FILE.exec(value);
1684
+ return match ? skillName(match[1].toLowerCase()) : undefined;
1685
+ }
1686
+ if (depth <= 0)
1687
+ return;
1688
+ for (const child of Object.values(value ?? {}).slice(0, 50)) {
1689
+ const found = skillInStrings(child, depth - 1);
1690
+ if (found)
1691
+ return found;
1692
+ }
1693
+ return;
1694
+ }
1695
+ function kibiSkillLoad(toolName, toolInput) {
1696
+ if (/^skill$/i.test(String(toolName).trim()) && isRecord3(toolInput)) {
1697
+ for (const key of ["skill", "name", "command"]) {
1698
+ const value = toolInput[key];
1699
+ if (typeof value !== "string")
1700
+ continue;
1701
+ const bare = value.trim().split(":").at(-1).toLowerCase();
1702
+ if (/^kibi-[a-z0-9-]+$/.test(bare))
1703
+ return skillName(bare);
1704
+ }
1705
+ }
1706
+ return skillInStrings(toolInput, 3);
1707
+ }
1708
+ function recentPath(directory) {
1709
+ return path6.join(directory, RECENT_FILE);
1710
+ }
1711
+ function entries(value) {
1712
+ return Array.isArray(value) ? value : [];
1713
+ }
1714
+ function fresh(entry, now, windowMs) {
1715
+ return isRecord3(entry) && typeof entry.t === "number" && now - entry.t <= windowMs;
1716
+ }
1717
+ function readRecent(directory, now) {
1718
+ let parsed = {};
1719
+ try {
1720
+ parsed = JSON.parse(String(fs5.readFileSync(recentPath(directory)))) ?? parsed;
1721
+ } catch {}
1722
+ return {
1723
+ calls: entries(parsed.calls).filter((entry) => fresh(entry, now, RETRY_WINDOW_MS) && typeof entry.o === "string"),
1724
+ skills: entries(parsed.skills).filter((entry) => fresh(entry, now, SKILL_WINDOW_MS))
1725
+ };
1726
+ }
1727
+ function writeRecent(directory, recent) {
1728
+ try {
1729
+ fs5.mkdirSync(directory, { recursive: true });
1730
+ const file = recentPath(directory);
1731
+ const temporary = `${file}.${process.pid}.${randomUUID2()}.tmp`;
1732
+ fs5.writeFileSync(temporary, JSON.stringify({
1733
+ calls: recent.calls.slice(-MAX_RECENT_ENTRIES),
1734
+ skills: recent.skills.slice(-MAX_RECENT_ENTRIES)
1735
+ }));
1736
+ fs5.renameSync(temporary, file);
1737
+ } catch {}
1738
+ }
1739
+ function rememberSkillLoad(directory, workspaceHash, now = Date.now()) {
1740
+ const recent = readRecent(directory, now);
1741
+ recent.skills.push({ w: workspaceHash, t: now });
1742
+ writeRecent(directory, recent);
1743
+ }
1744
+
1745
+ // ../agent-core/dist/usage-telemetry.js
1746
+ import { createHash as createHash4, randomUUID as randomUUID3 } from "node:crypto";
1747
+ import fs6 from "node:fs";
1730
1748
  import os from "node:os";
1731
- import path5 from "node:path";
1749
+ import path7 from "node:path";
1732
1750
  import { fileURLToPath } from "node:url";
1733
1751
  var DEFAULT_TELEMETRY_KEY = "phc_A5z8jRhD528abGJiTtPfgu4d4hpbEbfiJr9iUGn9TMsY";
1734
1752
  var DEFAULT_TELEMETRY_HOST = "https://eu.i.posthog.com";
@@ -1744,10 +1762,37 @@ var STALE_CLAIM_MS = 10 * 60 * 1000;
1744
1762
  var MAX_EVENT_AGE_MS = 7 * 24 * 60 * 60 * 1000;
1745
1763
  var MAX_QUEUE_BYTES = 1024 * 1024;
1746
1764
  var DEFAULT_FLUSH_TIMEOUT_MS = 1500;
1765
+ var TELEMETRY_EVENT_VERSION = 2;
1766
+ var REJECTED_CATEGORIES = new Set([
1767
+ "bootstrap_plan_rejected",
1768
+ "check_violations",
1769
+ "coarse_symbol_linkage",
1770
+ "conflicting_input",
1771
+ "entity_not_found",
1772
+ "entity_validation_failed",
1773
+ "firm_decision_locked",
1774
+ "generated_artifacts_stale",
1775
+ "incomplete_propositions",
1776
+ "input_read_failed",
1777
+ "integration_failed",
1778
+ "invalid_project_config",
1779
+ "invalid_relationship",
1780
+ "legacy_branch_storage",
1781
+ "migration_plan_refused",
1782
+ "missing_input",
1783
+ "query_limit_exceeded",
1784
+ "relationship_source_mismatch",
1785
+ "relationship_validation_failed",
1786
+ "semantic_contradiction",
1787
+ "symbol_granularity_violation",
1788
+ "tests_failed",
1789
+ "uncompiled_source",
1790
+ "unknown_operation",
1791
+ "upsert_dry_run_invalid",
1792
+ "validation_failed"
1793
+ ]);
1747
1794
  function flagValue(value) {
1748
- const normalized = value?.trim().toLowerCase();
1749
- if (normalized === undefined || normalized === "")
1750
- return;
1795
+ const normalized = String(value).trim().toLowerCase();
1751
1796
  if (["1", "true", "yes", "on"].includes(normalized))
1752
1797
  return true;
1753
1798
  if (["0", "false", "no", "off"].includes(normalized))
@@ -1811,7 +1856,7 @@ var SESSION_ENV_KEYS = [
1811
1856
  "CODEX_THREAD_ID"
1812
1857
  ];
1813
1858
  function hostKey(name) {
1814
- return name.trim().toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "");
1859
+ return name.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "");
1815
1860
  }
1816
1861
  function detectTelemetryHost(env = process.env) {
1817
1862
  const explicit = env.KIBI_HOST?.trim() || env.KIBI_MCP_HOST?.trim();
@@ -1837,9 +1882,9 @@ function remoteEnvironment(env) {
1837
1882
  }
1838
1883
  function realDirectory(directory) {
1839
1884
  try {
1840
- return fs4.realpathSync(directory);
1885
+ return fs6.realpathSync(directory);
1841
1886
  } catch {
1842
- return path5.resolve(directory);
1887
+ return path7.resolve(directory);
1843
1888
  }
1844
1889
  }
1845
1890
  function sandboxedHome(env) {
@@ -1848,30 +1893,34 @@ function sandboxedHome(env) {
1848
1893
  return false;
1849
1894
  const temp = realDirectory(os.tmpdir());
1850
1895
  const resolved = realDirectory(home);
1851
- return resolved !== temp && resolved.startsWith(`${temp}${path5.sep}`);
1896
+ return resolved.startsWith(`${temp}${path7.sep}`);
1852
1897
  }
1853
1898
  var KIBI_REPOSITORY_PACKAGE = "kibi";
1854
- function thisModuleFile() {
1899
+ function moduleFileFromUrl(readUrl) {
1900
+ let file;
1855
1901
  try {
1856
- const url = import.meta.url;
1857
- return typeof url === "string" && url !== "" ? fileURLToPath(url) : undefined;
1858
- } catch {
1859
- return;
1860
- }
1902
+ const url = readUrl();
1903
+ if (typeof url === "string")
1904
+ file = fileURLToPath(url);
1905
+ } catch {}
1906
+ return file;
1907
+ }
1908
+ function thisModuleFile() {
1909
+ return moduleFileFromUrl(() => import.meta.url);
1861
1910
  }
1862
1911
  function insideKibiCheckout(file) {
1863
1912
  const resolved = realDirectory(file);
1864
1913
  if (resolved.split(/[\\/]/).includes("node_modules"))
1865
1914
  return false;
1866
- let current = path5.dirname(resolved);
1915
+ let current = path7.dirname(resolved);
1867
1916
  for (;; ) {
1868
1917
  try {
1869
- const manifest = JSON.parse(fs4.readFileSync(path5.join(current, "package.json"), "utf8"));
1870
- if (manifest.name === KIBI_REPOSITORY_PACKAGE && fs4.existsSync(path5.join(current, "packages", "agent-core"))) {
1918
+ const manifest = JSON.parse(fs6.readFileSync(path7.join(current, "package.json"), "utf8"));
1919
+ if (manifest.name === KIBI_REPOSITORY_PACKAGE && fs6.existsSync(path7.join(current, "packages", "agent-core"))) {
1871
1920
  return true;
1872
1921
  }
1873
1922
  } catch {}
1874
- const parent = path5.dirname(current);
1923
+ const parent = path7.dirname(current);
1875
1924
  if (parent === current)
1876
1925
  return false;
1877
1926
  current = parent;
@@ -1885,7 +1934,7 @@ function telemetryChannel(env = process.env, moduleFile = thisModuleFile() ?? nu
1885
1934
  const explicit = env.KIBI_TELEMETRY_CHANNEL?.trim();
1886
1935
  if (explicit)
1887
1936
  return {
1888
- channel: telemetryCategory(explicit) ?? "release",
1937
+ channel: telemetryCategory(explicit),
1889
1938
  source: "KIBI_TELEMETRY_CHANNEL"
1890
1939
  };
1891
1940
  if (!moduleFile)
@@ -1902,14 +1951,14 @@ function telemetryDirectory(env = process.env) {
1902
1951
  if (explicit)
1903
1952
  return explicit;
1904
1953
  if (process.platform === "win32" && env.LOCALAPPDATA) {
1905
- return path5.join(env.LOCALAPPDATA, "kibi");
1954
+ return path7.join(env.LOCALAPPDATA, "kibi");
1906
1955
  }
1907
1956
  const stateHome = env.XDG_STATE_HOME?.trim();
1908
- return path5.join(stateHome || path5.join(env.HOME || os.homedir(), ".local", "state"), "kibi");
1957
+ return path7.join(stateHome || path7.join(env.HOME || os.homedir(), ".local", "state"), "kibi");
1909
1958
  }
1910
1959
  function readTelemetryConfig(env = process.env) {
1911
1960
  try {
1912
- const parsed = JSON.parse(fs4.readFileSync(path5.join(telemetryDirectory(env), CONFIG_FILE), "utf8"));
1961
+ const parsed = JSON.parse(String(fs6.readFileSync(path7.join(telemetryDirectory(env), CONFIG_FILE))));
1913
1962
  return typeof parsed === "object" && parsed !== null ? parsed : {};
1914
1963
  } catch {
1915
1964
  return {};
@@ -1917,9 +1966,9 @@ function readTelemetryConfig(env = process.env) {
1917
1966
  }
1918
1967
  function writeTelemetryConfig(config, env) {
1919
1968
  const directory = telemetryDirectory(env);
1920
- fs4.mkdirSync(directory, { recursive: true });
1921
- fs4.writeFileSync(path5.join(directory, CONFIG_FILE), `${JSON.stringify(config, null, 2)}
1922
- `, "utf8");
1969
+ fs6.mkdirSync(directory, { recursive: true });
1970
+ fs6.writeFileSync(path7.join(directory, CONFIG_FILE), `${JSON.stringify(config, null, 2)}
1971
+ `);
1923
1972
  }
1924
1973
  function telemetryStatus(env = process.env) {
1925
1974
  const directory = telemetryDirectory(env);
@@ -1975,31 +2024,31 @@ function installId(env) {
1975
2024
  if (typeof config.installId === "string" && config.installId !== "") {
1976
2025
  return config.installId;
1977
2026
  }
1978
- const created = randomUUID2();
2027
+ const created = randomUUID3();
1979
2028
  try {
1980
2029
  writeTelemetryConfig({ ...config, installId: created }, env);
1981
2030
  } catch {}
1982
2031
  return created;
1983
2032
  }
1984
2033
  function hashTelemetryId(value) {
1985
- return createHash3("sha256").update("kibi-telemetry\x00").update(value).digest("hex").slice(0, 16);
2034
+ return createHash4("sha256").update("kibi-telemetry\x00").update(value).digest("hex").slice(0, 16);
1986
2035
  }
1987
2036
  function runtimeName() {
1988
2037
  return typeof globalThis.Bun === "undefined" ? "node" : "bun";
1989
2038
  }
1990
2039
  function sessionMarkersPath(env) {
1991
- return path5.join(telemetryDirectory(env), SESSIONS_FILE);
2040
+ return path7.join(telemetryDirectory(env), SESSIONS_FILE);
1992
2041
  }
1993
2042
  function readSessionMarkers(env) {
1994
2043
  try {
1995
- const parsed = JSON.parse(fs4.readFileSync(sessionMarkersPath(env), "utf8"));
1996
- return parsed !== null && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : {};
2044
+ const parsed = JSON.parse(String(fs6.readFileSync(sessionMarkersPath(env))));
2045
+ return typeof parsed === "object" && parsed !== null ? parsed : {};
1997
2046
  } catch {
1998
2047
  return {};
1999
2048
  }
2000
2049
  }
2001
2050
  function workspaceKey(workspaceRoot) {
2002
- return hashTelemetryId(`workspace\x00${path5.resolve(workspaceRoot)}`);
2051
+ return hashTelemetryId(`workspace\x00${path7.resolve(workspaceRoot)}`);
2003
2052
  }
2004
2053
  function rememberHookSession(workspaceRoot, sessionId, env = process.env) {
2005
2054
  if (!sessionId.trim() || !telemetryEnabled(env))
@@ -2013,22 +2062,22 @@ function rememberHookSession(workspaceRoot, sessionId, env = process.env) {
2013
2062
  const kept = Object.entries(markers).filter(([, marker]) => typeof marker?.at === "number" && now - marker.at <= SESSION_MARKER_MAX_AGE_MS).sort(([, left], [, right]) => right.at - left.at).slice(0, MAX_SESSION_MARKERS);
2014
2063
  try {
2015
2064
  const file = sessionMarkersPath(env);
2016
- fs4.mkdirSync(path5.dirname(file), { recursive: true });
2017
- const temporary = `${file}.${process.pid}.${randomUUID2()}.tmp`;
2018
- fs4.writeFileSync(temporary, JSON.stringify(Object.fromEntries(kept)));
2019
- fs4.renameSync(temporary, file);
2065
+ fs6.mkdirSync(path7.dirname(file), { recursive: true });
2066
+ const temporary = `${file}.${process.pid}.${randomUUID3()}.tmp`;
2067
+ fs6.writeFileSync(temporary, JSON.stringify(Object.fromEntries(kept)));
2068
+ fs6.renameSync(temporary, file);
2020
2069
  } catch {}
2021
2070
  }
2022
2071
  function workspaceSessionHash(startDirectory, env = process.env) {
2023
2072
  const markers = readSessionMarkers(env);
2024
2073
  const now = Date.now();
2025
- let current = path5.resolve(startDirectory);
2074
+ let current = path7.resolve(startDirectory);
2026
2075
  for (;; ) {
2027
2076
  const marker = markers[workspaceKey(current)];
2028
2077
  if (marker && typeof marker.session === "string" && typeof marker.at === "number" && now - marker.at <= SESSION_MARKER_MAX_AGE_MS) {
2029
2078
  return marker.session;
2030
2079
  }
2031
- const parent = path5.dirname(current);
2080
+ const parent = path7.dirname(current);
2032
2081
  if (parent === current)
2033
2082
  return;
2034
2083
  current = parent;
@@ -2046,31 +2095,32 @@ function baseProperties(source, env) {
2046
2095
  $process_person_profile: false,
2047
2096
  $geoip_disable: true,
2048
2097
  $lib: "kibi",
2098
+ telemetry_version: TELEMETRY_EVENT_VERSION,
2049
2099
  package: source.packageName,
2050
2100
  package_version: source.packageVersion ?? null,
2051
- host: source.host?.trim() && hostKey(source.host) || detectTelemetryHost(env) || "unknown",
2101
+ host: source.host && hostKey(source.host) || detectTelemetryHost(env) || "unknown",
2052
2102
  remote: remoteEnvironment(env),
2053
2103
  channel: detectTelemetryChannel(env),
2054
2104
  session: sessionProperty(source, env),
2055
2105
  os: process.platform,
2056
2106
  arch: process.arch,
2057
2107
  runtime: runtimeName(),
2058
- runtime_version: process.versions.node.split(".")[0] ?? null
2108
+ runtime_version: process.versions.node.split(".")[0]
2059
2109
  };
2060
2110
  }
2061
2111
  function queuePath(env) {
2062
- return path5.join(telemetryDirectory(env), QUEUE_FILE);
2112
+ return path7.join(telemetryDirectory(env), QUEUE_FILE);
2063
2113
  }
2064
2114
  function appendEvent(event, env) {
2065
2115
  try {
2066
2116
  const file = queuePath(env);
2067
- fs4.mkdirSync(path5.dirname(file), { recursive: true });
2117
+ fs6.mkdirSync(path7.dirname(file), { recursive: true });
2068
2118
  try {
2069
- if (fs4.statSync(file).size > MAX_QUEUE_BYTES)
2119
+ if (fs6.statSync(file).size > MAX_QUEUE_BYTES)
2070
2120
  return;
2071
2121
  } catch {}
2072
- fs4.appendFileSync(file, `${JSON.stringify(event)}
2073
- `, "utf8");
2122
+ fs6.appendFileSync(file, `${JSON.stringify(event)}
2123
+ `);
2074
2124
  } catch {}
2075
2125
  }
2076
2126
  function telemetryCategory(value) {
@@ -2079,6 +2129,11 @@ function telemetryCategory(value) {
2079
2129
  return null;
2080
2130
  return /^[A-Za-z0-9_.:+-]{1,160}$/.test(trimmed) ? trimmed.toLowerCase() : "unclassified";
2081
2131
  }
2132
+ function rememberTelemetrySkillLoad(workspaceRoot, env = process.env) {
2133
+ if (!telemetryEnabled(env))
2134
+ return;
2135
+ rememberSkillLoad(telemetryDirectory(env), workspaceKey(workspaceRoot));
2136
+ }
2082
2137
  function recordHookTelemetry(source, hook, env = process.env) {
2083
2138
  if (!telemetryEnabled(env))
2084
2139
  return;
@@ -2092,7 +2147,8 @@ function recordHookTelemetry(source, hook, env = process.env) {
2092
2147
  path_kind: hook.pathKind ?? null,
2093
2148
  kb_operation: hook.kbOperation ?? null,
2094
2149
  kb_used_before: hook.kbUsedBefore ?? null,
2095
- requirement_count: hook.requirementCount ?? null
2150
+ requirement_count: hook.requirementCount ?? null,
2151
+ skill: hook.skill ?? null
2096
2152
  }
2097
2153
  }, env);
2098
2154
  }
@@ -2100,8 +2156,6 @@ function parseQueued(text, now) {
2100
2156
  const events = [];
2101
2157
  for (const line of text.split(`
2102
2158
  `)) {
2103
- if (line.trim() === "")
2104
- continue;
2105
2159
  try {
2106
2160
  const event = JSON.parse(line);
2107
2161
  const age = now - Date.parse(event.timestamp);
@@ -2114,7 +2168,7 @@ function parseQueued(text, now) {
2114
2168
  function flushDue(file, now) {
2115
2169
  let text;
2116
2170
  try {
2117
- text = fs4.readFileSync(file, "utf8");
2171
+ text = fs6.readFileSync(file, "utf8");
2118
2172
  } catch {
2119
2173
  return false;
2120
2174
  }
@@ -2125,7 +2179,7 @@ function flushDue(file, now) {
2125
2179
  if (lines.length >= FLUSH_BATCH_EVENTS)
2126
2180
  return true;
2127
2181
  try {
2128
- const first = JSON.parse(lines[0] ?? "");
2182
+ const first = JSON.parse(lines[0]);
2129
2183
  return now - Date.parse(first.timestamp) >= FLUSH_MAX_AGE_MS;
2130
2184
  } catch {
2131
2185
  return true;
@@ -2135,26 +2189,26 @@ function claimBatches(directory, now) {
2135
2189
  const claimed = [];
2136
2190
  let names;
2137
2191
  try {
2138
- names = fs4.readdirSync(directory);
2192
+ names = fs6.readdirSync(directory);
2139
2193
  } catch {
2140
2194
  return claimed;
2141
2195
  }
2142
2196
  for (const name of names) {
2143
2197
  if (!name.endsWith(SENDING_SUFFIX))
2144
2198
  continue;
2145
- const file = path5.join(directory, name);
2199
+ const file = path7.join(directory, name);
2146
2200
  try {
2147
- if (now - fs4.statSync(file).mtimeMs < STALE_CLAIM_MS)
2201
+ if (now - fs6.statSync(file).mtimeMs < STALE_CLAIM_MS)
2148
2202
  continue;
2149
- const reclaimed = `${file}.${randomUUID2()}${SENDING_SUFFIX}`;
2150
- fs4.renameSync(file, reclaimed);
2203
+ const reclaimed = `${file}.${randomUUID3()}${SENDING_SUFFIX}`;
2204
+ fs6.renameSync(file, reclaimed);
2151
2205
  claimed.push(reclaimed);
2152
2206
  } catch {}
2153
2207
  }
2154
- const queue = path5.join(directory, QUEUE_FILE);
2155
- const target = path5.join(directory, `${QUEUE_FILE}.${process.pid}.${randomUUID2()}${SENDING_SUFFIX}`);
2208
+ const queue = path7.join(directory, QUEUE_FILE);
2209
+ const target = path7.join(directory, `${QUEUE_FILE}.${process.pid}.${randomUUID3()}${SENDING_SUFFIX}`);
2156
2210
  try {
2157
- fs4.renameSync(queue, target);
2211
+ fs6.renameSync(queue, target);
2158
2212
  claimed.push(target);
2159
2213
  } catch {}
2160
2214
  return claimed;
@@ -2167,17 +2221,15 @@ async function flushTelemetry(options = {}, env = process.env) {
2167
2221
  if (!options.force && !flushDue(queuePath(env), now))
2168
2222
  return 0;
2169
2223
  const claimed = claimBatches(directory, now);
2170
- if (claimed.length === 0)
2171
- return 0;
2172
2224
  const events = [];
2173
2225
  for (const file of claimed) {
2174
2226
  try {
2175
- events.push(...parseQueued(fs4.readFileSync(file, "utf8"), now));
2227
+ events.push(...parseQueued(fs6.readFileSync(file, "utf8"), now));
2176
2228
  } catch {}
2177
2229
  }
2178
2230
  const release = () => {
2179
2231
  for (const file of claimed)
2180
- fs4.rmSync(file, { force: true });
2232
+ fs6.rmSync(file, { force: true });
2181
2233
  };
2182
2234
  if (events.length === 0) {
2183
2235
  release();
@@ -2186,7 +2238,7 @@ async function flushTelemetry(options = {}, env = process.env) {
2186
2238
  const fetchImpl = options.fetchImpl ?? globalThis.fetch;
2187
2239
  const controller = new AbortController;
2188
2240
  const timer = setTimeout(() => controller.abort(), options.timeoutMs ?? DEFAULT_FLUSH_TIMEOUT_MS);
2189
- timer.unref?.();
2241
+ timer.unref();
2190
2242
  try {
2191
2243
  const response = await fetchImpl(`${telemetryHost(env)}/batch/`, {
2192
2244
  method: "POST",
@@ -2199,7 +2251,7 @@ async function flushTelemetry(options = {}, env = process.env) {
2199
2251
  signal: controller.signal
2200
2252
  });
2201
2253
  if (!response.ok && response.status >= 500)
2202
- throw new Error("retry");
2254
+ throw new Error;
2203
2255
  release();
2204
2256
  return response.ok ? events.length : 0;
2205
2257
  } catch {
@@ -2215,6 +2267,7 @@ async function flushTelemetry(options = {}, env = process.env) {
2215
2267
  // ../agent-core/dist/hook-usage-log.js
2216
2268
  var HOOK_ACTION_EDITED = "edited";
2217
2269
  var HOOK_ACTION_KB_USAGE = "kb_usage";
2270
+ var HOOK_ACTION_SKILL_LOADED = "skill_loaded";
2218
2271
  function hookTelemetryEnabled(env = process.env) {
2219
2272
  const value = env.KIBI_DIAGNOSTIC_MODE?.trim().toLowerCase();
2220
2273
  return value === "1" || value === "true";
@@ -2239,15 +2292,19 @@ function appendHookUsage(row, env = process.env) {
2239
2292
  pathKind: row.trace.pathKind,
2240
2293
  kbOperation: row.trace.kbOperation,
2241
2294
  kbUsedBefore: row.trace.kbUsedBefore,
2242
- requirementCount: row.trace.requirementIds?.length
2295
+ requirementCount: row.trace.requirementIds?.length,
2296
+ skill: row.trace.skill
2243
2297
  }, env);
2298
+ if (row.trace.action === HOOK_ACTION_SKILL_LOADED) {
2299
+ rememberTelemetrySkillLoad(row.workspaceRoot, env);
2300
+ }
2244
2301
  if (!hookTelemetryEnabled(env))
2245
2302
  return;
2246
2303
  const finishedAt = new Date;
2247
2304
  const { trace } = row;
2248
2305
  const record = {
2249
2306
  timestamp: finishedAt.toISOString(),
2250
- request_id: `hook-${randomUUID3()}`,
2307
+ request_id: `hook-${randomUUID4()}`,
2251
2308
  tool: `hook_${row.event}`,
2252
2309
  interface: "hook",
2253
2310
  host: row.host,
@@ -2266,15 +2323,21 @@ function appendHookUsage(row, env = process.env) {
2266
2323
  duration_ms: finishedAt.getTime() - row.startedAt.getTime()
2267
2324
  };
2268
2325
  try {
2269
- const logPath = path6.join(row.workspaceRoot, ".kb", "usage.log");
2270
- fs5.mkdirSync(path6.dirname(logPath), { recursive: true });
2271
- fs5.appendFileSync(logPath, `${JSON.stringify(record)}
2272
- `, "utf8");
2326
+ const logPath = path8.join(row.workspaceRoot, ".kb", "usage.log");
2327
+ fs7.mkdirSync(path8.dirname(logPath), { recursive: true });
2328
+ fs7.appendFileSync(logPath, `${JSON.stringify(record)}
2329
+ `);
2273
2330
  } catch {}
2274
2331
  }
2275
2332
  function kbUsageTrace(toolName, toolInput) {
2276
2333
  const kbOperation = hostKbOperation(toolName, toolInput);
2277
- return kbOperation ? { action: HOOK_ACTION_KB_USAGE, kbOperation } : undefined;
2334
+ if (kbOperation)
2335
+ return { action: HOOK_ACTION_KB_USAGE, kbOperation };
2336
+ return skillLoadTrace(toolName, toolInput);
2337
+ }
2338
+ function skillLoadTrace(toolName, toolInput) {
2339
+ const skill = kibiSkillLoad(toolName, toolInput);
2340
+ return skill ? { action: HOOK_ACTION_SKILL_LOADED, skill } : undefined;
2278
2341
  }
2279
2342
  function editTraces(relativePaths, requirementIdsFor) {
2280
2343
  const traces = [];
@@ -2292,8 +2355,6 @@ function editTraces(relativePaths, requirementIdsFor) {
2292
2355
  return traces;
2293
2356
  }
2294
2357
  function appendHookUsageRows(row, traces, env = process.env) {
2295
- if (!hookTracesWanted(env))
2296
- return;
2297
2358
  for (const trace of traces)
2298
2359
  appendHookUsage({ ...row, trace }, env);
2299
2360
  }
@@ -2309,10 +2370,10 @@ async function finishHookTelemetry(input, env = process.env) {
2309
2370
  }
2310
2371
  function readPackageVersion(packageJsonPath) {
2311
2372
  try {
2312
- const parsed = JSON.parse(fs5.readFileSync(packageJsonPath, "utf8"));
2313
- if (typeof parsed === "object" && parsed !== null && "version" in parsed && typeof parsed.version === "string") {
2314
- return parsed.version;
2315
- }
2373
+ const parsed = JSON.parse(String(fs7.readFileSync(packageJsonPath)));
2374
+ const version = parsed.version;
2375
+ if (typeof version === "string")
2376
+ return version;
2316
2377
  } catch {}
2317
2378
  return null;
2318
2379
  }
@@ -2588,10 +2649,10 @@ function parseStdinJson(rawInput) {
2588
2649
  }
2589
2650
 
2590
2651
  // src/hook-state-storage.ts
2591
- import { randomUUID as randomUUID4 } from "node:crypto";
2592
- import fs6 from "node:fs";
2652
+ import { randomUUID as randomUUID5 } from "node:crypto";
2653
+ import fs8 from "node:fs";
2593
2654
  import os2 from "node:os";
2594
- import path7 from "node:path";
2655
+ import path9 from "node:path";
2595
2656
  var stateFileName = "hook-state.json";
2596
2657
  var lockFileName = "hook-state.lock";
2597
2658
  var maxDirtyPaths = 50;
@@ -2601,10 +2662,10 @@ var lockRetryCount = 100;
2601
2662
  var lockRetryDelayMs = 5;
2602
2663
  var staleLockAgeMs = 30000;
2603
2664
  function statePath(pluginData) {
2604
- return path7.join(pluginData, stateFileName);
2665
+ return path9.join(pluginData, stateFileName);
2605
2666
  }
2606
2667
  function lockPath(pluginData) {
2607
- return path7.join(pluginData, lockFileName);
2668
+ return path9.join(pluginData, lockFileName);
2608
2669
  }
2609
2670
  function isRecord5(value) {
2610
2671
  return typeof value === "object" && value !== null && !Array.isArray(value);
@@ -2618,7 +2679,7 @@ function sleepSync(milliseconds) {
2618
2679
  Atomics.wait(view, 0, 0, milliseconds);
2619
2680
  }
2620
2681
  function uniqueTempPath(pluginData) {
2621
- return path7.join(pluginData, `${stateFileName}.${process.pid}.${Date.now()}.${randomUUID4()}.tmp`);
2682
+ return path9.join(pluginData, `${stateFileName}.${process.pid}.${Date.now()}.${randomUUID5()}.tmp`);
2622
2683
  }
2623
2684
  function mergeStringPaths(existingPaths, nextPaths) {
2624
2685
  const merged = [...existingPaths, ...nextPaths].map(normalizePath).filter((dirtyPath) => dirtyPath.length > 0);
@@ -2642,7 +2703,7 @@ function acquireLock(pluginData) {
2642
2703
  const targetLockPath = lockPath(pluginData);
2643
2704
  for (let attempt = 0;attempt < lockRetryCount; attempt++) {
2644
2705
  try {
2645
- return fs6.openSync(targetLockPath, "wx");
2706
+ return fs8.openSync(targetLockPath, "wx");
2646
2707
  } catch (error) {
2647
2708
  if (error instanceof Error && "code" in error && error.code === "EEXIST") {
2648
2709
  if (removeStaleLock(targetLockPath)) {
@@ -2659,7 +2720,7 @@ function acquireLock(pluginData) {
2659
2720
  function removeStaleLock(targetLockPath) {
2660
2721
  let stats;
2661
2722
  try {
2662
- stats = fs6.statSync(targetLockPath);
2723
+ stats = fs8.statSync(targetLockPath);
2663
2724
  } catch {
2664
2725
  return false;
2665
2726
  }
@@ -2667,7 +2728,7 @@ function removeStaleLock(targetLockPath) {
2667
2728
  return false;
2668
2729
  }
2669
2730
  try {
2670
- fs6.unlinkSync(targetLockPath);
2731
+ fs8.unlinkSync(targetLockPath);
2671
2732
  return true;
2672
2733
  } catch {
2673
2734
  return false;
@@ -2675,14 +2736,14 @@ function removeStaleLock(targetLockPath) {
2675
2736
  }
2676
2737
  function releaseLock(pluginData, fileDescriptor) {
2677
2738
  try {
2678
- fs6.closeSync(fileDescriptor);
2739
+ fs8.closeSync(fileDescriptor);
2679
2740
  } catch (error) {
2680
2741
  if (!(error instanceof Error)) {
2681
2742
  throw error;
2682
2743
  }
2683
2744
  }
2684
2745
  try {
2685
- fs6.unlinkSync(lockPath(pluginData));
2746
+ fs8.unlinkSync(lockPath(pluginData));
2686
2747
  } catch (error) {
2687
2748
  if (!(error instanceof Error)) {
2688
2749
  throw error;
@@ -2720,14 +2781,14 @@ function resolveStateDir(pluginData, conversationId) {
2720
2781
  if (!conversationId) {
2721
2782
  return;
2722
2783
  }
2723
- return path7.join(os2.tmpdir(), "kibi-cursor-hook-state", conversationId.replaceAll(/[^a-zA-Z0-9._-]/g, "_"));
2784
+ return path9.join(os2.tmpdir(), "kibi-cursor-hook-state", conversationId.replaceAll(/[^a-zA-Z0-9._-]/g, "_"));
2724
2785
  }
2725
2786
  function loadHookState(stateDir) {
2726
2787
  if (!stateDir) {
2727
2788
  return emptyHookState();
2728
2789
  }
2729
2790
  try {
2730
- return coerceHookState(JSON.parse(fs6.readFileSync(statePath(stateDir), "utf8")));
2791
+ return coerceHookState(JSON.parse(fs8.readFileSync(statePath(stateDir), "utf8")));
2731
2792
  } catch {
2732
2793
  return emptyHookState();
2733
2794
  }
@@ -2736,19 +2797,19 @@ function saveHookState(stateDir, state) {
2736
2797
  if (!stateDir) {
2737
2798
  return;
2738
2799
  }
2739
- fs6.mkdirSync(stateDir, { recursive: true });
2800
+ fs8.mkdirSync(stateDir, { recursive: true });
2740
2801
  const boundedState = coerceHookState(state);
2741
2802
  const tempPath = uniqueTempPath(stateDir);
2742
- fs6.writeFileSync(tempPath, `${JSON.stringify(boundedState)}
2803
+ fs8.writeFileSync(tempPath, `${JSON.stringify(boundedState)}
2743
2804
  `);
2744
- fs6.renameSync(tempPath, statePath(stateDir));
2805
+ fs8.renameSync(tempPath, statePath(stateDir));
2745
2806
  }
2746
2807
  function updateHookState(stateDir, updater) {
2747
2808
  const initialState = loadHookState(stateDir);
2748
2809
  if (!stateDir) {
2749
2810
  return updater(initialState);
2750
2811
  }
2751
- fs6.mkdirSync(stateDir, { recursive: true });
2812
+ fs8.mkdirSync(stateDir, { recursive: true });
2752
2813
  const lockFileDescriptor = acquireLock(stateDir);
2753
2814
  if (lockFileDescriptor === undefined) {
2754
2815
  return updater(initialState);
@@ -2843,14 +2904,14 @@ function clearSessionHookState(stateDir) {
2843
2904
  }
2844
2905
 
2845
2906
  // src/source-linked-requirements.ts
2846
- import path8 from "node:path";
2907
+ import path10 from "node:path";
2847
2908
  var CURSOR_INDEX_OPTIONS = {
2848
2909
  legacyLinksAsImplements: false,
2849
2910
  cacheFileName: "cursor-knowledge-index.json"
2850
2911
  };
2851
2912
  function toManifestPath(workspaceRoot, filePath) {
2852
- const absolute = path8.isAbsolute(filePath) ? filePath : path8.resolve(workspaceRoot, filePath);
2853
- return path8.relative(workspaceRoot, absolute).split(path8.sep).join("/");
2913
+ const absolute = path10.isAbsolute(filePath) ? filePath : path10.resolve(workspaceRoot, filePath);
2914
+ return path10.relative(workspaceRoot, absolute).split(path10.sep).join("/");
2854
2915
  }
2855
2916
  function getSourceLinkedSymbols(workspaceRoot, filePath, cacheDir) {
2856
2917
  const relativePath = toManifestPath(workspaceRoot, filePath);
@@ -2887,7 +2948,7 @@ function firmNotice(workspaceRoot, linked, focus, session, env) {
2887
2948
  return firmEditNotice(symbol ? `${symbol.title} in ${linked.relativePath}` : linked.relativePath, ids.map(summarize));
2888
2949
  }
2889
2950
  function packageVersion() {
2890
- return readPackageVersion(path9.join(path9.dirname(fileURLToPath2(import.meta.url)), "..", "package.json"));
2951
+ return readPackageVersion(path11.join(path11.dirname(fileURLToPath2(import.meta.url)), "..", "package.json"));
2891
2952
  }
2892
2953
  function recordToolTelemetry(input, workspaceRoot, stateDir, startedAt, env) {
2893
2954
  if (!hookTracesWanted(env))
@@ -2923,19 +2984,19 @@ function hasKibiConfig(cwd) {
2923
2984
  if (!cwd) {
2924
2985
  return false;
2925
2986
  }
2926
- return fs7.existsSync(path9.join(cwd, ".kb", "manifest.json"));
2987
+ return fs9.existsSync(path11.join(cwd, ".kb", "manifest.json"));
2927
2988
  }
2928
2989
  function kibiWorkspaceFor(directory) {
2929
2990
  if (!directory)
2930
2991
  return;
2931
- let current = path9.resolve(directory);
2992
+ let current = path11.resolve(directory);
2932
2993
  for (;; ) {
2933
- if (fs7.existsSync(path9.join(current, ".kb", "manifest.json"))) {
2994
+ if (fs9.existsSync(path11.join(current, ".kb", "manifest.json"))) {
2934
2995
  return current;
2935
2996
  }
2936
- if (fs7.existsSync(path9.join(current, ".git")))
2997
+ if (fs9.existsSync(path11.join(current, ".git")))
2937
2998
  return;
2938
- const parent = path9.dirname(current);
2999
+ const parent = path11.dirname(current);
2939
3000
  if (parent === current)
2940
3001
  return;
2941
3002
  current = parent;
@@ -2990,7 +3051,9 @@ ${advisory}` : BOOTSTRAP_REMINDER
2990
3051
  }
2991
3052
  {
2992
3053
  const advisory = interfaceAdvisory(initialState.mcpState, workspaceTrusted);
2993
- return advisory ? { additional_context: advisory } : emptyResult();
3054
+ const context = [advisory, missingHooksNote(cwd)].filter((line) => Boolean(line)).join(`
3055
+ `);
3056
+ return context ? { additional_context: context } : emptyResult();
2994
3057
  }
2995
3058
  case "preToolUse": {
2996
3059
  const stamped = stampKibiWorkspace(input.toolName, input.toolInput, kibiWorkspaceFor(input.cwd ?? input.workspaceRoots?.[0]));
@@ -3016,7 +3079,7 @@ ${advisory}` : BOOTSTRAP_REMINDER
3016
3079
  const linked = getSourceLinkedSymbols(cwd, primaryPath, stateDir);
3017
3080
  trace.pathKind = classifyPath(relativePath);
3018
3081
  trace.requirementIds = implementedRequirementIds(linked.symbols);
3019
- const focus = editFocus(path9.resolve(cwd, primaryPath), input.toolInput);
3082
+ const focus = editFocus(path11.resolve(cwd, primaryPath), input.toolInput);
3020
3083
  const ruleNote = takeRuleNote(session);
3021
3084
  const notice = firmNotice(cwd, linked, focus, session, environment.env ?? process.env);
3022
3085
  const guidance = hasGuidedPath(state, "pre-edit", relativePath) ? undefined : preEditGuidance(primaryPath, {
@@ -3099,7 +3162,8 @@ ${advisory}` : BOOTSTRAP_REMINDER
3099
3162
  markDecisionFlag(session, "source-edited");
3100
3163
  }
3101
3164
  }
3102
- if (kbToolCall?.toolName === "kb_upsert" || kbUsageTrace(input.toolName, input.toolInput)?.kbOperation === "kb_upsert") {
3165
+ const kbOperation = kbToolCall ? kbToolCall.toolName : kbUsageTrace(input.toolName, input.toolInput)?.kbOperation;
3166
+ if (kbOperation === "kb_upsert") {
3103
3167
  markDecisionFlag(session, "kb-recorded");
3104
3168
  if (isRequirementUpsert(input.toolInput)) {
3105
3169
  markDecisionFlag(session, "requirement-recorded");
@@ -3201,7 +3265,7 @@ async function main() {
3201
3265
  `);
3202
3266
  }
3203
3267
  function isInvokedAsCli(argv1, moduleUrl) {
3204
- const invokedPath = argv1 ? pathToFileURL(path9.resolve(argv1)).href : "";
3268
+ const invokedPath = argv1 ? pathToFileURL(path11.resolve(argv1)).href : "";
3205
3269
  return moduleUrl === invokedPath;
3206
3270
  }
3207
3271
  async function runHookCli() {