kibi-cursor 2.12.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);
633
+ } catch {}
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;
672
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));
673
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);
1269
1211
  }
1270
- function writeMarker(file, content, flag) {
1271
- fs3.mkdirSync(path3.dirname(file), { recursive: true });
1272
- fs3.writeFileSync(file, content, { flag });
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));
1219
+ }
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,16 +1893,17 @@ 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
1899
  function moduleFileFromUrl(readUrl) {
1900
+ let file;
1855
1901
  try {
1856
1902
  const url = readUrl();
1857
- return typeof url === "string" && url !== "" ? fileURLToPath(url) : undefined;
1858
- } catch {
1859
- return;
1860
- }
1903
+ if (typeof url === "string")
1904
+ file = fileURLToPath(url);
1905
+ } catch {}
1906
+ return file;
1861
1907
  }
1862
1908
  function thisModuleFile() {
1863
1909
  return moduleFileFromUrl(() => import.meta.url);
@@ -1866,15 +1912,15 @@ function insideKibiCheckout(file) {
1866
1912
  const resolved = realDirectory(file);
1867
1913
  if (resolved.split(/[\\/]/).includes("node_modules"))
1868
1914
  return false;
1869
- let current = path5.dirname(resolved);
1915
+ let current = path7.dirname(resolved);
1870
1916
  for (;; ) {
1871
1917
  try {
1872
- const manifest = JSON.parse(fs4.readFileSync(path5.join(current, "package.json"), "utf8"));
1873
- 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"))) {
1874
1920
  return true;
1875
1921
  }
1876
1922
  } catch {}
1877
- const parent = path5.dirname(current);
1923
+ const parent = path7.dirname(current);
1878
1924
  if (parent === current)
1879
1925
  return false;
1880
1926
  current = parent;
@@ -1888,7 +1934,7 @@ function telemetryChannel(env = process.env, moduleFile = thisModuleFile() ?? nu
1888
1934
  const explicit = env.KIBI_TELEMETRY_CHANNEL?.trim();
1889
1935
  if (explicit)
1890
1936
  return {
1891
- channel: telemetryCategory(explicit) ?? "release",
1937
+ channel: telemetryCategory(explicit),
1892
1938
  source: "KIBI_TELEMETRY_CHANNEL"
1893
1939
  };
1894
1940
  if (!moduleFile)
@@ -1905,14 +1951,14 @@ function telemetryDirectory(env = process.env) {
1905
1951
  if (explicit)
1906
1952
  return explicit;
1907
1953
  if (process.platform === "win32" && env.LOCALAPPDATA) {
1908
- return path5.join(env.LOCALAPPDATA, "kibi");
1954
+ return path7.join(env.LOCALAPPDATA, "kibi");
1909
1955
  }
1910
1956
  const stateHome = env.XDG_STATE_HOME?.trim();
1911
- 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");
1912
1958
  }
1913
1959
  function readTelemetryConfig(env = process.env) {
1914
1960
  try {
1915
- 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))));
1916
1962
  return typeof parsed === "object" && parsed !== null ? parsed : {};
1917
1963
  } catch {
1918
1964
  return {};
@@ -1920,9 +1966,9 @@ function readTelemetryConfig(env = process.env) {
1920
1966
  }
1921
1967
  function writeTelemetryConfig(config, env) {
1922
1968
  const directory = telemetryDirectory(env);
1923
- fs4.mkdirSync(directory, { recursive: true });
1924
- fs4.writeFileSync(path5.join(directory, CONFIG_FILE), `${JSON.stringify(config, null, 2)}
1925
- `, "utf8");
1969
+ fs6.mkdirSync(directory, { recursive: true });
1970
+ fs6.writeFileSync(path7.join(directory, CONFIG_FILE), `${JSON.stringify(config, null, 2)}
1971
+ `);
1926
1972
  }
1927
1973
  function telemetryStatus(env = process.env) {
1928
1974
  const directory = telemetryDirectory(env);
@@ -1978,31 +2024,31 @@ function installId(env) {
1978
2024
  if (typeof config.installId === "string" && config.installId !== "") {
1979
2025
  return config.installId;
1980
2026
  }
1981
- const created = randomUUID2();
2027
+ const created = randomUUID3();
1982
2028
  try {
1983
2029
  writeTelemetryConfig({ ...config, installId: created }, env);
1984
2030
  } catch {}
1985
2031
  return created;
1986
2032
  }
1987
2033
  function hashTelemetryId(value) {
1988
- 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);
1989
2035
  }
1990
2036
  function runtimeName() {
1991
2037
  return typeof globalThis.Bun === "undefined" ? "node" : "bun";
1992
2038
  }
1993
2039
  function sessionMarkersPath(env) {
1994
- return path5.join(telemetryDirectory(env), SESSIONS_FILE);
2040
+ return path7.join(telemetryDirectory(env), SESSIONS_FILE);
1995
2041
  }
1996
2042
  function readSessionMarkers(env) {
1997
2043
  try {
1998
- const parsed = JSON.parse(fs4.readFileSync(sessionMarkersPath(env), "utf8"));
1999
- 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 : {};
2000
2046
  } catch {
2001
2047
  return {};
2002
2048
  }
2003
2049
  }
2004
2050
  function workspaceKey(workspaceRoot) {
2005
- return hashTelemetryId(`workspace\x00${path5.resolve(workspaceRoot)}`);
2051
+ return hashTelemetryId(`workspace\x00${path7.resolve(workspaceRoot)}`);
2006
2052
  }
2007
2053
  function rememberHookSession(workspaceRoot, sessionId, env = process.env) {
2008
2054
  if (!sessionId.trim() || !telemetryEnabled(env))
@@ -2016,22 +2062,22 @@ function rememberHookSession(workspaceRoot, sessionId, env = process.env) {
2016
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);
2017
2063
  try {
2018
2064
  const file = sessionMarkersPath(env);
2019
- fs4.mkdirSync(path5.dirname(file), { recursive: true });
2020
- const temporary = `${file}.${process.pid}.${randomUUID2()}.tmp`;
2021
- fs4.writeFileSync(temporary, JSON.stringify(Object.fromEntries(kept)));
2022
- 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);
2023
2069
  } catch {}
2024
2070
  }
2025
2071
  function workspaceSessionHash(startDirectory, env = process.env) {
2026
2072
  const markers = readSessionMarkers(env);
2027
2073
  const now = Date.now();
2028
- let current = path5.resolve(startDirectory);
2074
+ let current = path7.resolve(startDirectory);
2029
2075
  for (;; ) {
2030
2076
  const marker = markers[workspaceKey(current)];
2031
2077
  if (marker && typeof marker.session === "string" && typeof marker.at === "number" && now - marker.at <= SESSION_MARKER_MAX_AGE_MS) {
2032
2078
  return marker.session;
2033
2079
  }
2034
- const parent = path5.dirname(current);
2080
+ const parent = path7.dirname(current);
2035
2081
  if (parent === current)
2036
2082
  return;
2037
2083
  current = parent;
@@ -2049,31 +2095,32 @@ function baseProperties(source, env) {
2049
2095
  $process_person_profile: false,
2050
2096
  $geoip_disable: true,
2051
2097
  $lib: "kibi",
2098
+ telemetry_version: TELEMETRY_EVENT_VERSION,
2052
2099
  package: source.packageName,
2053
2100
  package_version: source.packageVersion ?? null,
2054
- host: source.host?.trim() && hostKey(source.host) || detectTelemetryHost(env) || "unknown",
2101
+ host: source.host && hostKey(source.host) || detectTelemetryHost(env) || "unknown",
2055
2102
  remote: remoteEnvironment(env),
2056
2103
  channel: detectTelemetryChannel(env),
2057
2104
  session: sessionProperty(source, env),
2058
2105
  os: process.platform,
2059
2106
  arch: process.arch,
2060
2107
  runtime: runtimeName(),
2061
- runtime_version: process.versions.node.split(".")[0] ?? null
2108
+ runtime_version: process.versions.node.split(".")[0]
2062
2109
  };
2063
2110
  }
2064
2111
  function queuePath(env) {
2065
- return path5.join(telemetryDirectory(env), QUEUE_FILE);
2112
+ return path7.join(telemetryDirectory(env), QUEUE_FILE);
2066
2113
  }
2067
2114
  function appendEvent(event, env) {
2068
2115
  try {
2069
2116
  const file = queuePath(env);
2070
- fs4.mkdirSync(path5.dirname(file), { recursive: true });
2117
+ fs6.mkdirSync(path7.dirname(file), { recursive: true });
2071
2118
  try {
2072
- if (fs4.statSync(file).size > MAX_QUEUE_BYTES)
2119
+ if (fs6.statSync(file).size > MAX_QUEUE_BYTES)
2073
2120
  return;
2074
2121
  } catch {}
2075
- fs4.appendFileSync(file, `${JSON.stringify(event)}
2076
- `, "utf8");
2122
+ fs6.appendFileSync(file, `${JSON.stringify(event)}
2123
+ `);
2077
2124
  } catch {}
2078
2125
  }
2079
2126
  function telemetryCategory(value) {
@@ -2082,6 +2129,11 @@ function telemetryCategory(value) {
2082
2129
  return null;
2083
2130
  return /^[A-Za-z0-9_.:+-]{1,160}$/.test(trimmed) ? trimmed.toLowerCase() : "unclassified";
2084
2131
  }
2132
+ function rememberTelemetrySkillLoad(workspaceRoot, env = process.env) {
2133
+ if (!telemetryEnabled(env))
2134
+ return;
2135
+ rememberSkillLoad(telemetryDirectory(env), workspaceKey(workspaceRoot));
2136
+ }
2085
2137
  function recordHookTelemetry(source, hook, env = process.env) {
2086
2138
  if (!telemetryEnabled(env))
2087
2139
  return;
@@ -2095,7 +2147,8 @@ function recordHookTelemetry(source, hook, env = process.env) {
2095
2147
  path_kind: hook.pathKind ?? null,
2096
2148
  kb_operation: hook.kbOperation ?? null,
2097
2149
  kb_used_before: hook.kbUsedBefore ?? null,
2098
- requirement_count: hook.requirementCount ?? null
2150
+ requirement_count: hook.requirementCount ?? null,
2151
+ skill: hook.skill ?? null
2099
2152
  }
2100
2153
  }, env);
2101
2154
  }
@@ -2103,8 +2156,6 @@ function parseQueued(text, now) {
2103
2156
  const events = [];
2104
2157
  for (const line of text.split(`
2105
2158
  `)) {
2106
- if (line.trim() === "")
2107
- continue;
2108
2159
  try {
2109
2160
  const event = JSON.parse(line);
2110
2161
  const age = now - Date.parse(event.timestamp);
@@ -2117,7 +2168,7 @@ function parseQueued(text, now) {
2117
2168
  function flushDue(file, now) {
2118
2169
  let text;
2119
2170
  try {
2120
- text = fs4.readFileSync(file, "utf8");
2171
+ text = fs6.readFileSync(file, "utf8");
2121
2172
  } catch {
2122
2173
  return false;
2123
2174
  }
@@ -2128,7 +2179,7 @@ function flushDue(file, now) {
2128
2179
  if (lines.length >= FLUSH_BATCH_EVENTS)
2129
2180
  return true;
2130
2181
  try {
2131
- const first = JSON.parse(lines[0] ?? "");
2182
+ const first = JSON.parse(lines[0]);
2132
2183
  return now - Date.parse(first.timestamp) >= FLUSH_MAX_AGE_MS;
2133
2184
  } catch {
2134
2185
  return true;
@@ -2138,26 +2189,26 @@ function claimBatches(directory, now) {
2138
2189
  const claimed = [];
2139
2190
  let names;
2140
2191
  try {
2141
- names = fs4.readdirSync(directory);
2192
+ names = fs6.readdirSync(directory);
2142
2193
  } catch {
2143
2194
  return claimed;
2144
2195
  }
2145
2196
  for (const name of names) {
2146
2197
  if (!name.endsWith(SENDING_SUFFIX))
2147
2198
  continue;
2148
- const file = path5.join(directory, name);
2199
+ const file = path7.join(directory, name);
2149
2200
  try {
2150
- if (now - fs4.statSync(file).mtimeMs < STALE_CLAIM_MS)
2201
+ if (now - fs6.statSync(file).mtimeMs < STALE_CLAIM_MS)
2151
2202
  continue;
2152
- const reclaimed = `${file}.${randomUUID2()}${SENDING_SUFFIX}`;
2153
- fs4.renameSync(file, reclaimed);
2203
+ const reclaimed = `${file}.${randomUUID3()}${SENDING_SUFFIX}`;
2204
+ fs6.renameSync(file, reclaimed);
2154
2205
  claimed.push(reclaimed);
2155
2206
  } catch {}
2156
2207
  }
2157
- const queue = path5.join(directory, QUEUE_FILE);
2158
- 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}`);
2159
2210
  try {
2160
- fs4.renameSync(queue, target);
2211
+ fs6.renameSync(queue, target);
2161
2212
  claimed.push(target);
2162
2213
  } catch {}
2163
2214
  return claimed;
@@ -2170,17 +2221,15 @@ async function flushTelemetry(options = {}, env = process.env) {
2170
2221
  if (!options.force && !flushDue(queuePath(env), now))
2171
2222
  return 0;
2172
2223
  const claimed = claimBatches(directory, now);
2173
- if (claimed.length === 0)
2174
- return 0;
2175
2224
  const events = [];
2176
2225
  for (const file of claimed) {
2177
2226
  try {
2178
- events.push(...parseQueued(fs4.readFileSync(file, "utf8"), now));
2227
+ events.push(...parseQueued(fs6.readFileSync(file, "utf8"), now));
2179
2228
  } catch {}
2180
2229
  }
2181
2230
  const release = () => {
2182
2231
  for (const file of claimed)
2183
- fs4.rmSync(file, { force: true });
2232
+ fs6.rmSync(file, { force: true });
2184
2233
  };
2185
2234
  if (events.length === 0) {
2186
2235
  release();
@@ -2189,7 +2238,7 @@ async function flushTelemetry(options = {}, env = process.env) {
2189
2238
  const fetchImpl = options.fetchImpl ?? globalThis.fetch;
2190
2239
  const controller = new AbortController;
2191
2240
  const timer = setTimeout(() => controller.abort(), options.timeoutMs ?? DEFAULT_FLUSH_TIMEOUT_MS);
2192
- timer.unref?.();
2241
+ timer.unref();
2193
2242
  try {
2194
2243
  const response = await fetchImpl(`${telemetryHost(env)}/batch/`, {
2195
2244
  method: "POST",
@@ -2202,7 +2251,7 @@ async function flushTelemetry(options = {}, env = process.env) {
2202
2251
  signal: controller.signal
2203
2252
  });
2204
2253
  if (!response.ok && response.status >= 500)
2205
- throw new Error("retry");
2254
+ throw new Error;
2206
2255
  release();
2207
2256
  return response.ok ? events.length : 0;
2208
2257
  } catch {
@@ -2218,6 +2267,7 @@ async function flushTelemetry(options = {}, env = process.env) {
2218
2267
  // ../agent-core/dist/hook-usage-log.js
2219
2268
  var HOOK_ACTION_EDITED = "edited";
2220
2269
  var HOOK_ACTION_KB_USAGE = "kb_usage";
2270
+ var HOOK_ACTION_SKILL_LOADED = "skill_loaded";
2221
2271
  function hookTelemetryEnabled(env = process.env) {
2222
2272
  const value = env.KIBI_DIAGNOSTIC_MODE?.trim().toLowerCase();
2223
2273
  return value === "1" || value === "true";
@@ -2242,15 +2292,19 @@ function appendHookUsage(row, env = process.env) {
2242
2292
  pathKind: row.trace.pathKind,
2243
2293
  kbOperation: row.trace.kbOperation,
2244
2294
  kbUsedBefore: row.trace.kbUsedBefore,
2245
- requirementCount: row.trace.requirementIds?.length
2295
+ requirementCount: row.trace.requirementIds?.length,
2296
+ skill: row.trace.skill
2246
2297
  }, env);
2298
+ if (row.trace.action === HOOK_ACTION_SKILL_LOADED) {
2299
+ rememberTelemetrySkillLoad(row.workspaceRoot, env);
2300
+ }
2247
2301
  if (!hookTelemetryEnabled(env))
2248
2302
  return;
2249
2303
  const finishedAt = new Date;
2250
2304
  const { trace } = row;
2251
2305
  const record = {
2252
2306
  timestamp: finishedAt.toISOString(),
2253
- request_id: `hook-${randomUUID3()}`,
2307
+ request_id: `hook-${randomUUID4()}`,
2254
2308
  tool: `hook_${row.event}`,
2255
2309
  interface: "hook",
2256
2310
  host: row.host,
@@ -2269,15 +2323,21 @@ function appendHookUsage(row, env = process.env) {
2269
2323
  duration_ms: finishedAt.getTime() - row.startedAt.getTime()
2270
2324
  };
2271
2325
  try {
2272
- const logPath = path6.join(row.workspaceRoot, ".kb", "usage.log");
2273
- fs5.mkdirSync(path6.dirname(logPath), { recursive: true });
2274
- fs5.appendFileSync(logPath, `${JSON.stringify(record)}
2275
- `, "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
+ `);
2276
2330
  } catch {}
2277
2331
  }
2278
2332
  function kbUsageTrace(toolName, toolInput) {
2279
2333
  const kbOperation = hostKbOperation(toolName, toolInput);
2280
- 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;
2281
2341
  }
2282
2342
  function editTraces(relativePaths, requirementIdsFor) {
2283
2343
  const traces = [];
@@ -2295,8 +2355,6 @@ function editTraces(relativePaths, requirementIdsFor) {
2295
2355
  return traces;
2296
2356
  }
2297
2357
  function appendHookUsageRows(row, traces, env = process.env) {
2298
- if (!hookTracesWanted(env))
2299
- return;
2300
2358
  for (const trace of traces)
2301
2359
  appendHookUsage({ ...row, trace }, env);
2302
2360
  }
@@ -2312,10 +2370,10 @@ async function finishHookTelemetry(input, env = process.env) {
2312
2370
  }
2313
2371
  function readPackageVersion(packageJsonPath) {
2314
2372
  try {
2315
- const parsed = JSON.parse(fs5.readFileSync(packageJsonPath, "utf8"));
2316
- if (typeof parsed === "object" && parsed !== null && "version" in parsed && typeof parsed.version === "string") {
2317
- return parsed.version;
2318
- }
2373
+ const parsed = JSON.parse(String(fs7.readFileSync(packageJsonPath)));
2374
+ const version = parsed.version;
2375
+ if (typeof version === "string")
2376
+ return version;
2319
2377
  } catch {}
2320
2378
  return null;
2321
2379
  }
@@ -2591,10 +2649,10 @@ function parseStdinJson(rawInput) {
2591
2649
  }
2592
2650
 
2593
2651
  // src/hook-state-storage.ts
2594
- import { randomUUID as randomUUID4 } from "node:crypto";
2595
- import fs6 from "node:fs";
2652
+ import { randomUUID as randomUUID5 } from "node:crypto";
2653
+ import fs8 from "node:fs";
2596
2654
  import os2 from "node:os";
2597
- import path7 from "node:path";
2655
+ import path9 from "node:path";
2598
2656
  var stateFileName = "hook-state.json";
2599
2657
  var lockFileName = "hook-state.lock";
2600
2658
  var maxDirtyPaths = 50;
@@ -2604,10 +2662,10 @@ var lockRetryCount = 100;
2604
2662
  var lockRetryDelayMs = 5;
2605
2663
  var staleLockAgeMs = 30000;
2606
2664
  function statePath(pluginData) {
2607
- return path7.join(pluginData, stateFileName);
2665
+ return path9.join(pluginData, stateFileName);
2608
2666
  }
2609
2667
  function lockPath(pluginData) {
2610
- return path7.join(pluginData, lockFileName);
2668
+ return path9.join(pluginData, lockFileName);
2611
2669
  }
2612
2670
  function isRecord5(value) {
2613
2671
  return typeof value === "object" && value !== null && !Array.isArray(value);
@@ -2621,7 +2679,7 @@ function sleepSync(milliseconds) {
2621
2679
  Atomics.wait(view, 0, 0, milliseconds);
2622
2680
  }
2623
2681
  function uniqueTempPath(pluginData) {
2624
- return path7.join(pluginData, `${stateFileName}.${process.pid}.${Date.now()}.${randomUUID4()}.tmp`);
2682
+ return path9.join(pluginData, `${stateFileName}.${process.pid}.${Date.now()}.${randomUUID5()}.tmp`);
2625
2683
  }
2626
2684
  function mergeStringPaths(existingPaths, nextPaths) {
2627
2685
  const merged = [...existingPaths, ...nextPaths].map(normalizePath).filter((dirtyPath) => dirtyPath.length > 0);
@@ -2645,7 +2703,7 @@ function acquireLock(pluginData) {
2645
2703
  const targetLockPath = lockPath(pluginData);
2646
2704
  for (let attempt = 0;attempt < lockRetryCount; attempt++) {
2647
2705
  try {
2648
- return fs6.openSync(targetLockPath, "wx");
2706
+ return fs8.openSync(targetLockPath, "wx");
2649
2707
  } catch (error) {
2650
2708
  if (error instanceof Error && "code" in error && error.code === "EEXIST") {
2651
2709
  if (removeStaleLock(targetLockPath)) {
@@ -2662,7 +2720,7 @@ function acquireLock(pluginData) {
2662
2720
  function removeStaleLock(targetLockPath) {
2663
2721
  let stats;
2664
2722
  try {
2665
- stats = fs6.statSync(targetLockPath);
2723
+ stats = fs8.statSync(targetLockPath);
2666
2724
  } catch {
2667
2725
  return false;
2668
2726
  }
@@ -2670,7 +2728,7 @@ function removeStaleLock(targetLockPath) {
2670
2728
  return false;
2671
2729
  }
2672
2730
  try {
2673
- fs6.unlinkSync(targetLockPath);
2731
+ fs8.unlinkSync(targetLockPath);
2674
2732
  return true;
2675
2733
  } catch {
2676
2734
  return false;
@@ -2678,14 +2736,14 @@ function removeStaleLock(targetLockPath) {
2678
2736
  }
2679
2737
  function releaseLock(pluginData, fileDescriptor) {
2680
2738
  try {
2681
- fs6.closeSync(fileDescriptor);
2739
+ fs8.closeSync(fileDescriptor);
2682
2740
  } catch (error) {
2683
2741
  if (!(error instanceof Error)) {
2684
2742
  throw error;
2685
2743
  }
2686
2744
  }
2687
2745
  try {
2688
- fs6.unlinkSync(lockPath(pluginData));
2746
+ fs8.unlinkSync(lockPath(pluginData));
2689
2747
  } catch (error) {
2690
2748
  if (!(error instanceof Error)) {
2691
2749
  throw error;
@@ -2723,14 +2781,14 @@ function resolveStateDir(pluginData, conversationId) {
2723
2781
  if (!conversationId) {
2724
2782
  return;
2725
2783
  }
2726
- 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, "_"));
2727
2785
  }
2728
2786
  function loadHookState(stateDir) {
2729
2787
  if (!stateDir) {
2730
2788
  return emptyHookState();
2731
2789
  }
2732
2790
  try {
2733
- return coerceHookState(JSON.parse(fs6.readFileSync(statePath(stateDir), "utf8")));
2791
+ return coerceHookState(JSON.parse(fs8.readFileSync(statePath(stateDir), "utf8")));
2734
2792
  } catch {
2735
2793
  return emptyHookState();
2736
2794
  }
@@ -2739,19 +2797,19 @@ function saveHookState(stateDir, state) {
2739
2797
  if (!stateDir) {
2740
2798
  return;
2741
2799
  }
2742
- fs6.mkdirSync(stateDir, { recursive: true });
2800
+ fs8.mkdirSync(stateDir, { recursive: true });
2743
2801
  const boundedState = coerceHookState(state);
2744
2802
  const tempPath = uniqueTempPath(stateDir);
2745
- fs6.writeFileSync(tempPath, `${JSON.stringify(boundedState)}
2803
+ fs8.writeFileSync(tempPath, `${JSON.stringify(boundedState)}
2746
2804
  `);
2747
- fs6.renameSync(tempPath, statePath(stateDir));
2805
+ fs8.renameSync(tempPath, statePath(stateDir));
2748
2806
  }
2749
2807
  function updateHookState(stateDir, updater) {
2750
2808
  const initialState = loadHookState(stateDir);
2751
2809
  if (!stateDir) {
2752
2810
  return updater(initialState);
2753
2811
  }
2754
- fs6.mkdirSync(stateDir, { recursive: true });
2812
+ fs8.mkdirSync(stateDir, { recursive: true });
2755
2813
  const lockFileDescriptor = acquireLock(stateDir);
2756
2814
  if (lockFileDescriptor === undefined) {
2757
2815
  return updater(initialState);
@@ -2846,14 +2904,14 @@ function clearSessionHookState(stateDir) {
2846
2904
  }
2847
2905
 
2848
2906
  // src/source-linked-requirements.ts
2849
- import path8 from "node:path";
2907
+ import path10 from "node:path";
2850
2908
  var CURSOR_INDEX_OPTIONS = {
2851
2909
  legacyLinksAsImplements: false,
2852
2910
  cacheFileName: "cursor-knowledge-index.json"
2853
2911
  };
2854
2912
  function toManifestPath(workspaceRoot, filePath) {
2855
- const absolute = path8.isAbsolute(filePath) ? filePath : path8.resolve(workspaceRoot, filePath);
2856
- 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("/");
2857
2915
  }
2858
2916
  function getSourceLinkedSymbols(workspaceRoot, filePath, cacheDir) {
2859
2917
  const relativePath = toManifestPath(workspaceRoot, filePath);
@@ -2890,7 +2948,7 @@ function firmNotice(workspaceRoot, linked, focus, session, env) {
2890
2948
  return firmEditNotice(symbol ? `${symbol.title} in ${linked.relativePath}` : linked.relativePath, ids.map(summarize));
2891
2949
  }
2892
2950
  function packageVersion() {
2893
- 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"));
2894
2952
  }
2895
2953
  function recordToolTelemetry(input, workspaceRoot, stateDir, startedAt, env) {
2896
2954
  if (!hookTracesWanted(env))
@@ -2926,19 +2984,19 @@ function hasKibiConfig(cwd) {
2926
2984
  if (!cwd) {
2927
2985
  return false;
2928
2986
  }
2929
- return fs7.existsSync(path9.join(cwd, ".kb", "manifest.json"));
2987
+ return fs9.existsSync(path11.join(cwd, ".kb", "manifest.json"));
2930
2988
  }
2931
2989
  function kibiWorkspaceFor(directory) {
2932
2990
  if (!directory)
2933
2991
  return;
2934
- let current = path9.resolve(directory);
2992
+ let current = path11.resolve(directory);
2935
2993
  for (;; ) {
2936
- if (fs7.existsSync(path9.join(current, ".kb", "manifest.json"))) {
2994
+ if (fs9.existsSync(path11.join(current, ".kb", "manifest.json"))) {
2937
2995
  return current;
2938
2996
  }
2939
- if (fs7.existsSync(path9.join(current, ".git")))
2997
+ if (fs9.existsSync(path11.join(current, ".git")))
2940
2998
  return;
2941
- const parent = path9.dirname(current);
2999
+ const parent = path11.dirname(current);
2942
3000
  if (parent === current)
2943
3001
  return;
2944
3002
  current = parent;
@@ -2993,7 +3051,9 @@ ${advisory}` : BOOTSTRAP_REMINDER
2993
3051
  }
2994
3052
  {
2995
3053
  const advisory = interfaceAdvisory(initialState.mcpState, workspaceTrusted);
2996
- 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();
2997
3057
  }
2998
3058
  case "preToolUse": {
2999
3059
  const stamped = stampKibiWorkspace(input.toolName, input.toolInput, kibiWorkspaceFor(input.cwd ?? input.workspaceRoots?.[0]));
@@ -3019,7 +3079,7 @@ ${advisory}` : BOOTSTRAP_REMINDER
3019
3079
  const linked = getSourceLinkedSymbols(cwd, primaryPath, stateDir);
3020
3080
  trace.pathKind = classifyPath(relativePath);
3021
3081
  trace.requirementIds = implementedRequirementIds(linked.symbols);
3022
- const focus = editFocus(path9.resolve(cwd, primaryPath), input.toolInput);
3082
+ const focus = editFocus(path11.resolve(cwd, primaryPath), input.toolInput);
3023
3083
  const ruleNote = takeRuleNote(session);
3024
3084
  const notice = firmNotice(cwd, linked, focus, session, environment.env ?? process.env);
3025
3085
  const guidance = hasGuidedPath(state, "pre-edit", relativePath) ? undefined : preEditGuidance(primaryPath, {
@@ -3102,7 +3162,8 @@ ${advisory}` : BOOTSTRAP_REMINDER
3102
3162
  markDecisionFlag(session, "source-edited");
3103
3163
  }
3104
3164
  }
3105
- 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") {
3106
3167
  markDecisionFlag(session, "kb-recorded");
3107
3168
  if (isRequirementUpsert(input.toolInput)) {
3108
3169
  markDecisionFlag(session, "requirement-recorded");
@@ -3204,7 +3265,7 @@ async function main() {
3204
3265
  `);
3205
3266
  }
3206
3267
  function isInvokedAsCli(argv1, moduleUrl) {
3207
- const invokedPath = argv1 ? pathToFileURL(path9.resolve(argv1)).href : "";
3268
+ const invokedPath = argv1 ? pathToFileURL(path11.resolve(argv1)).href : "";
3208
3269
  return moduleUrl === invokedPath;
3209
3270
  }
3210
3271
  async function runHookCli() {