kibi-cursor 2.10.0 → 2.11.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.
Files changed (33) hide show
  1. package/.cursor-plugin/plugin.json +1 -1
  2. package/CHANGELOG.md +91 -0
  3. package/README.md +21 -3
  4. package/agent-plugin/plugin.json +1 -1
  5. package/agent-plugin/skills/kibi-commit-review/SKILL.md +6 -2
  6. package/agent-plugin/skills/kibi-decisions/SKILL.md +7 -2
  7. package/agent-plugin/skills/kibi-decisions/resources/signoff-views.md +3 -1
  8. package/agent-plugin/skills/kibi-usage/SKILL.md +14 -3
  9. package/agent-plugin/skills/kibi-usage/resources/prolog-proofs.md +207 -0
  10. package/agent-plugin/skills/kibi-usage/resources/proof.md +44 -3
  11. package/bin/hook-runner.mjs +3233 -0
  12. package/bin/launch-kibi-mcp.mjs +2 -1
  13. package/dist/.cursor-plugin/plugin.json +1 -1
  14. package/dist/bin/hook-runner.mjs +3233 -0
  15. package/dist/bin/launch-kibi-mcp.mjs +2 -1
  16. package/dist/hook-runner.js +245 -200
  17. package/dist/hooks/hooks.json +6 -6
  18. package/dist/skills/.canon-hash.json +6 -5
  19. package/dist/skills/kibi-commit-review/SKILL.md +6 -2
  20. package/dist/skills/kibi-decisions/SKILL.md +7 -2
  21. package/dist/skills/kibi-decisions/resources/signoff-views.md +3 -1
  22. package/dist/skills/kibi-usage/SKILL.md +14 -3
  23. package/dist/skills/kibi-usage/resources/prolog-proofs.md +207 -0
  24. package/dist/skills/kibi-usage/resources/proof.md +44 -3
  25. package/hooks/hooks.json +6 -6
  26. package/package.json +5 -3
  27. package/skills/.canon-hash.json +6 -5
  28. package/skills/kibi-commit-review/SKILL.md +6 -2
  29. package/skills/kibi-decisions/SKILL.md +7 -2
  30. package/skills/kibi-decisions/resources/signoff-views.md +3 -1
  31. package/skills/kibi-usage/SKILL.md +14 -3
  32. package/skills/kibi-usage/resources/prolog-proofs.md +207 -0
  33. package/skills/kibi-usage/resources/proof.md +44 -3
@@ -0,0 +1,3233 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/hook-runner.ts
4
+ import fs7 from "node:fs";
5
+ import path9 from "node:path";
6
+ import { fileURLToPath as fileURLToPath2, pathToFileURL } from "node:url";
7
+
8
+ // ../agent-core/dist/decision-guard.js
9
+ import { createHash as createHash2 } from "node:crypto";
10
+ import fs3 from "node:fs";
11
+ import path3 from "node:path";
12
+
13
+ // ../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([
17
+ "src",
18
+ "app",
19
+ "apps",
20
+ "lib",
21
+ "libs",
22
+ "source",
23
+ "page",
24
+ "pages",
25
+ "component",
26
+ "components",
27
+ "view",
28
+ "views",
29
+ "screen",
30
+ "screens",
31
+ "package",
32
+ "packages",
33
+ "main",
34
+ "index",
35
+ "shared",
36
+ "common",
37
+ "core",
38
+ "util",
39
+ "utils",
40
+ "helper",
41
+ "helpers",
42
+ "public",
43
+ "static",
44
+ "asset",
45
+ "assets",
46
+ "web",
47
+ "client",
48
+ "server",
49
+ "frontend",
50
+ "backend",
51
+ "module",
52
+ "modules",
53
+ "feature",
54
+ "features",
55
+ "internal",
56
+ "service",
57
+ "services",
58
+ "test",
59
+ "tests",
60
+ "spec",
61
+ "specs",
62
+ "dist",
63
+ "build",
64
+ "html",
65
+ "htm",
66
+ "tsx",
67
+ "jsx",
68
+ "mjs",
69
+ "cjs",
70
+ "vue",
71
+ "svelte",
72
+ "css",
73
+ "scss",
74
+ "less",
75
+ "json",
76
+ "yaml",
77
+ "yml",
78
+ "java",
79
+ "swift",
80
+ "php"
81
+ ]);
82
+ var FILLER_WORDS = new Set([
83
+ "the",
84
+ "and",
85
+ "for",
86
+ "with",
87
+ "must",
88
+ "should",
89
+ "shall",
90
+ "may",
91
+ "can",
92
+ "are",
93
+ "is",
94
+ "its",
95
+ "their",
96
+ "that",
97
+ "this",
98
+ "from",
99
+ "into",
100
+ "when",
101
+ "each",
102
+ "every",
103
+ "all",
104
+ "not",
105
+ "req",
106
+ "auto"
107
+ ]);
108
+ 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);
110
+ }
111
+ function requestTopicWords(text, limit = 40) {
112
+ const found = words(text).filter((word) => !FILLER_WORDS.has(word) && !REQUEST_FILLER_WORDS.has(word));
113
+ return [...new Set(found)].slice(0, limit);
114
+ }
115
+ var REQUEST_FILLER_WORDS = new Set([
116
+ "please",
117
+ "could",
118
+ "would",
119
+ "you",
120
+ "your",
121
+ "our",
122
+ "have",
123
+ "has",
124
+ "make",
125
+ "sure",
126
+ "change",
127
+ "update",
128
+ "add",
129
+ "fix",
130
+ "now",
131
+ "need",
132
+ "want",
133
+ "like",
134
+ "also",
135
+ "just",
136
+ "then",
137
+ "than",
138
+ "there",
139
+ "here",
140
+ "what",
141
+ "which",
142
+ "why",
143
+ "how",
144
+ "will",
145
+ "use",
146
+ "using",
147
+ "new",
148
+ "file",
149
+ "code",
150
+ "never",
151
+ "always",
152
+ "only",
153
+ "any",
154
+ "but",
155
+ "they",
156
+ "them",
157
+ "was",
158
+ "were",
159
+ "been",
160
+ "being",
161
+ "does",
162
+ "don",
163
+ "doesn",
164
+ "let",
165
+ "get",
166
+ "set",
167
+ "one",
168
+ "out",
169
+ "about",
170
+ "over",
171
+ "same",
172
+ "still",
173
+ "too",
174
+ "very"
175
+ ]);
176
+
177
+ // ../agent-core/dist/snippets.js
178
+ import fs2 from "node:fs";
179
+
180
+ // ../agent-core/dist/knowledge-index.js
181
+ import { createHash, randomUUID } from "node:crypto";
182
+ import fs from "node:fs";
183
+ import path2 from "node:path";
184
+ var SYMBOLS_MANIFEST = ".kb/symbols.yaml";
185
+ var SYMBOL_COORDINATES = ".kb/symbol-coordinates.yaml";
186
+ var INDEX_FORMAT = "kibi-agent-core.knowledge-index.v2";
187
+ var CACHE_FILE = "knowledge-index.json";
188
+ function unquote(raw) {
189
+ const value = raw.trim();
190
+ if (value.startsWith('"') && value.endsWith('"') && value.length >= 2) {
191
+ try {
192
+ return JSON.parse(value);
193
+ } catch {
194
+ return value.slice(1, -1);
195
+ }
196
+ }
197
+ if (value.startsWith("'") && value.endsWith("'") && value.length >= 2) {
198
+ return value.slice(1, -1).replaceAll("''", "'");
199
+ }
200
+ const comment = value.search(/\s#/);
201
+ return comment >= 0 ? value.slice(0, comment).trimEnd() : value;
202
+ }
203
+ function parseFlow(text, start = 0) {
204
+ let pos = start;
205
+ 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
+ }
219
+ };
220
+ const scalar = (stops) => {
221
+ const quote = text[pos];
222
+ 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;
239
+ return unquote(raw);
240
+ }
241
+ let cursor = pos;
242
+ while (cursor < text.length) {
243
+ const char = text[cursor] ?? "";
244
+ if (stops.includes(char) && char !== ":" || char === `
245
+ ` || char === "\r")
246
+ break;
247
+ if (stops.includes(":") && char === ":" && (cursor + 1 >= text.length || /[\s,\]}]/.test(text[cursor + 1] ?? "")))
248
+ break;
249
+ if (char === "#" && /\s/.test(text[cursor - 1] ?? ""))
250
+ break;
251
+ cursor += 1;
252
+ }
253
+ const raw = text.slice(pos, cursor).trim();
254
+ pos = cursor;
255
+ return raw;
256
+ };
257
+ const value = () => {
258
+ skip();
259
+ const open = text[pos];
260
+ if (open === "[") {
261
+ pos += 1;
262
+ const items = [];
263
+ for (;; ) {
264
+ skip();
265
+ if (text[pos] === "]") {
266
+ pos += 1;
267
+ return items;
268
+ }
269
+ if (pos >= text.length)
270
+ throw new SyntaxError("unterminated flow sequence");
271
+ const item = value();
272
+ if (item !== undefined)
273
+ items.push(item);
274
+ skip();
275
+ if (text[pos] === ",")
276
+ pos += 1;
277
+ else if (text[pos] !== "]")
278
+ throw new SyntaxError("expected , or ]");
279
+ }
280
+ }
281
+ if (open === "{") {
282
+ pos += 1;
283
+ const record = {};
284
+ for (;; ) {
285
+ skip();
286
+ if (text[pos] === "}") {
287
+ pos += 1;
288
+ return record;
289
+ }
290
+ if (pos >= text.length)
291
+ throw new SyntaxError("unterminated flow mapping");
292
+ const key = scalar(":,}");
293
+ if (key === undefined)
294
+ throw new SyntaxError("unterminated key");
295
+ skip();
296
+ let entry;
297
+ if (text[pos] === ":") {
298
+ pos += 1;
299
+ skip();
300
+ entry = text[pos] === "," || text[pos] === "}" ? undefined : value();
301
+ }
302
+ if (key.length > 0)
303
+ record[key] = entry;
304
+ skip();
305
+ if (text[pos] === ",")
306
+ pos += 1;
307
+ else if (text[pos] !== "}")
308
+ throw new SyntaxError("expected , or }");
309
+ }
310
+ }
311
+ const raw = scalar(",]}");
312
+ if (raw === undefined)
313
+ throw new SyntaxError("unterminated scalar");
314
+ return raw;
315
+ };
316
+ try {
317
+ const parsed = value();
318
+ return { value: parsed, end: pos };
319
+ } catch {
320
+ return;
321
+ }
322
+ }
323
+ function opensFlow(raw) {
324
+ const value = raw.trim();
325
+ return value.startsWith("[") || value.startsWith("{");
326
+ }
327
+ var keyValue = /^([A-Za-z0-9_"'-][^:]*?):(?:\s+(.*))?$/;
328
+ function completeFlowValue(raw) {
329
+ if (!opensFlow(raw))
330
+ return;
331
+ const parsed = parseFlow(raw);
332
+ if (parsed === undefined)
333
+ return;
334
+ const rest = raw.slice(parsed.end).trim();
335
+ return rest.length === 0 || rest.startsWith("#") ? parsed : undefined;
336
+ }
337
+ function parseScalarOrFlow(raw) {
338
+ if (raw === undefined || raw.trim().length === 0)
339
+ return;
340
+ return completeFlowValue(raw)?.value ?? unquote(raw);
341
+ }
342
+ function scanSymbolsManifest(text) {
343
+ return scanRecordSequence(text, "symbols");
344
+ }
345
+ function unbalancedFlow(raw) {
346
+ return opensFlow(raw) && parseFlow(raw) === undefined;
347
+ }
348
+ function flowRecords(text, start) {
349
+ const parsed = parseFlow(text, start);
350
+ if (!Array.isArray(parsed?.value))
351
+ return;
352
+ return parsed.value.filter((item) => item !== null && typeof item === "object" && !Array.isArray(item));
353
+ }
354
+ function scanFlowSection(text, sectionKey) {
355
+ const escapedKey = sectionKey.replaceAll(/[.*+?^$(){}|[\]\\]/g, "\\$&");
356
+ const header = new RegExp(`^${escapedKey}:[ \\t]*(\\[)?`);
357
+ let offset = 0;
358
+ let first = true;
359
+ let afterHeader = false;
360
+ for (const piece of text.split(/(\r?\n)/)) {
361
+ const content = piece.trim();
362
+ const indent = piece.length - piece.trimStart().length;
363
+ 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
+ }
370
+ 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
+ }
378
+ }
379
+ }
380
+ offset += piece.length;
381
+ }
382
+ return;
383
+ }
384
+ function scanRecordSequence(text, sectionKey) {
385
+ const flowSection = scanFlowSection(text, sectionKey);
386
+ if (flowSection !== undefined)
387
+ return flowSection;
388
+ const records = [];
389
+ let itemIndent;
390
+ let keyIndent = 0;
391
+ let current;
392
+ let listKey;
393
+ let listItem;
394
+ let section = "root";
395
+ let pendingFlow;
396
+ for (const rawLine of text.split(/\r?\n/)) {
397
+ const content = rawLine.trim();
398
+ if (content.length === 0 || content.startsWith("#"))
399
+ continue;
400
+ const indent = rawLine.length - rawLine.trimStart().length;
401
+ 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)) {
405
+ pendingFlow.text += `
406
+ ${content}`;
407
+ const parsed = parseFlow(pendingFlow.text);
408
+ if (parsed === undefined)
409
+ continue;
410
+ target[pendingFlow.key] = parsed.value;
411
+ pendingFlow = undefined;
412
+ continue;
413
+ }
414
+ target[pendingFlow.key] = unquote(pendingFlow.first);
415
+ pendingFlow = undefined;
416
+ }
417
+ if (indent === 0 && !dash) {
418
+ const pair = keyValue.exec(content);
419
+ section = pair?.[1] !== undefined && unquote(pair[1]) === sectionKey ? "records" : "other";
420
+ current = undefined;
421
+ itemIndent = undefined;
422
+ continue;
423
+ }
424
+ if (section === "other")
425
+ 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);
431
+ listKey = undefined;
432
+ listItem = undefined;
433
+ const pair = keyValue.exec(dash[3] ?? "");
434
+ if (pair?.[1] !== undefined) {
435
+ 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
449
+ listKey = key;
450
+ }
451
+ continue;
452
+ }
453
+ if (current === undefined || itemIndent === undefined)
454
+ continue;
455
+ if (indent <= itemIndent)
456
+ continue;
457
+ if (dash) {
458
+ if (listKey === undefined)
459
+ 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;
465
+ if (flowItem !== undefined) {
466
+ listItem = undefined;
467
+ list.push(flowItem.value);
468
+ } else if (pair?.[1] !== undefined && pair[2] !== undefined) {
469
+ listItem = { [unquote(pair[1])]: parseScalarOrFlow(pair[2]) };
470
+ list.push(listItem);
471
+ } else {
472
+ listItem = undefined;
473
+ list.push(unquote(item));
474
+ }
475
+ continue;
476
+ }
477
+ const pair = keyValue.exec(content);
478
+ if (pair?.[1] === undefined)
479
+ continue;
480
+ const key = unquote(pair[1]);
481
+ if (indent === keyIndent) {
482
+ 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]);
506
+ }
507
+ }
508
+ return records;
509
+ }
510
+ function scanSymbolCoordinates(text) {
511
+ const coordinates = {};
512
+ let inCoordinates = false;
513
+ let idIndent;
514
+ let currentId;
515
+ for (const rawLine of text.split(/\r?\n/)) {
516
+ const content = rawLine.trim();
517
+ if (content.length === 0 || content.startsWith("#"))
518
+ continue;
519
+ const indent = rawLine.length - rawLine.trimStart().length;
520
+ const pair = keyValue.exec(content);
521
+ if (pair?.[1] === undefined)
522
+ continue;
523
+ const key = unquote(pair[1]);
524
+ if (indent === 0) {
525
+ inCoordinates = key === "coordinates";
526
+ idIndent = undefined;
527
+ currentId = undefined;
528
+ continue;
529
+ }
530
+ if (!inCoordinates)
531
+ continue;
532
+ if (idIndent === undefined || indent === idIndent) {
533
+ idIndent = indent;
534
+ currentId = key;
535
+ coordinates[currentId] ??= {};
536
+ continue;
537
+ }
538
+ if (currentId === undefined || indent < idIndent)
539
+ continue;
540
+ const entry = coordinates[currentId] ?? {};
541
+ if (key === "sourceFile") {
542
+ const sourceFile = normalizeSourceFile(unquote(pair[2] ?? ""));
543
+ if (sourceFile.length > 0)
544
+ entry.sourceFile = sourceFile;
545
+ coordinates[currentId] = entry;
546
+ continue;
547
+ }
548
+ const numeric = Number.parseInt(unquote(pair[2] ?? ""), 10);
549
+ if (!Number.isFinite(numeric))
550
+ continue;
551
+ if (key === "sourceLine")
552
+ entry.line = numeric;
553
+ if (key === "sourceEndLine")
554
+ entry.endLine = numeric;
555
+ coordinates[currentId] = entry;
556
+ }
557
+ return coordinates;
558
+ }
559
+ function toPositiveInt(value) {
560
+ const parsed = typeof value === "number" ? value : Number.parseInt(String(value), 10);
561
+ return Number.isFinite(parsed) && parsed > 0 ? parsed : undefined;
562
+ }
563
+ function pushUnique(list, value) {
564
+ if (!list.includes(value))
565
+ list.push(value);
566
+ }
567
+ function normalizeSourceFile(value) {
568
+ return value.replaceAll("\\", "/").replace(/^\.\//, "");
569
+ }
570
+ function manifestStateOf(text, recordCount) {
571
+ if (text === undefined)
572
+ return "missing";
573
+ if (recordCount > 0)
574
+ return "indexed";
575
+ const meaningful = text.split(/\r?\n/).map((line) => line.trim()).filter((line) => line.length > 0 && !line.startsWith("#")).filter((line) => !/^symbols:\s*(\[\s*\])?$/.test(line)).filter((line) => line !== "[]").filter((line) => !/^(?:---|\.\.\.|%\S.*|version:\s*\S+)$/.test(line));
576
+ return meaningful.length === 0 ? "empty" : "unreadable";
577
+ }
578
+ function buildKnowledgeIndex(records, coordinates = {}, options = {}, manifest = records.length > 0 ? "indexed" : "empty") {
579
+ const files = {};
580
+ let symbolCount = 0;
581
+ const recordedFiles = new Set;
582
+ const coordinateFiles = [];
583
+ for (const entry of Object.values(coordinates)) {
584
+ if (entry.sourceFile !== undefined)
585
+ pushUnique(coordinateFiles, entry.sourceFile);
586
+ }
587
+ coordinateFiles.sort();
588
+ for (const record of records) {
589
+ const id = typeof record.id === "string" ? record.id : undefined;
590
+ const sourceFile = typeof record.sourceFile === "string" ? normalizeSourceFile(record.sourceFile) : undefined;
591
+ if (!id || !sourceFile)
592
+ continue;
593
+ symbolCount += 1;
594
+ recordedFiles.add(sourceFile);
595
+ const symbol = {
596
+ id,
597
+ title: typeof record.title === "string" ? record.title : id,
598
+ implements: [],
599
+ coveredBy: [],
600
+ executableFor: []
601
+ };
602
+ const relationships = [];
603
+ if (options.legacyLinksAsImplements !== false) {
604
+ let links = [];
605
+ if (typeof record.links === "string")
606
+ links = [record.links];
607
+ else if (Array.isArray(record.links))
608
+ links = record.links;
609
+ 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
+ }
618
+ }
619
+ }
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
+ }
635
+ if (symbol.implements.length + symbol.coveredBy.length + symbol.executableFor.length === 0) {
636
+ continue;
637
+ }
638
+ const line = toPositiveInt(record.sourceLine) ?? coordinates[id]?.line ?? undefined;
639
+ const endLine = toPositiveInt(record.sourceEndLine) ?? coordinates[id]?.endLine ?? undefined;
640
+ if (line !== undefined)
641
+ symbol.line = line;
642
+ if (endLine !== undefined)
643
+ symbol.endLine = endLine;
644
+ const bucket = files[sourceFile] ?? [];
645
+ bucket.push(symbol);
646
+ files[sourceFile] = bucket;
647
+ }
648
+ const unlinkedFiles = [...recordedFiles].filter((file) => files[file] === undefined).sort();
649
+ return { files, symbolCount, manifest, coordinateFiles, unlinkedFiles };
650
+ }
651
+ function fileSignature(filePath) {
652
+ try {
653
+ const stats = fs.statSync(filePath);
654
+ return `${stats.size}:${stats.mtimeMs}`;
655
+ } catch {
656
+ return "missing";
657
+ }
658
+ }
659
+ function readText(filePath) {
660
+ try {
661
+ return fs.readFileSync(filePath, "utf8");
662
+ } catch {
663
+ return;
664
+ }
665
+ }
666
+ function writeCacheAtomically(cachePath, payload) {
667
+ try {
668
+ fs.mkdirSync(path2.dirname(cachePath), { recursive: true });
669
+ const temporary = `${cachePath}.${process.pid}.${randomUUID()}.tmp`;
670
+ fs.writeFileSync(temporary, payload);
671
+ fs.renameSync(temporary, cachePath);
672
+ } catch {}
673
+ }
674
+ function loadKnowledgeIndex(workspaceRoot, cacheDir, options = {}) {
675
+ const symbolsPath = path2.join(workspaceRoot, SYMBOLS_MANIFEST);
676
+ const coordinatesPath = path2.join(workspaceRoot, SYMBOL_COORDINATES);
677
+ const legacyMode = options.legacyLinksAsImplements === false ? "typed" : "legacy";
678
+ 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
+ }
696
+ return index;
697
+ }
698
+ var ENTITY_LANES = [
699
+ ["REQ-", "requirements"],
700
+ ["SCEN-", "scenarios"],
701
+ ["TEST-", "tests"],
702
+ ["ADR-", "adr"],
703
+ ["FACT-", "facts"],
704
+ ["FLAG-", "flags"],
705
+ ["EVT-", "events"]
706
+ ];
707
+ var SAFE_ENTITY_ID = /^[A-Za-z0-9][A-Za-z0-9._-]*$/;
708
+ var SUMMARY_SCALARS = {
709
+ semantic_text: "statement",
710
+ fact_kind: "factKind",
711
+ subject_key: "subjectKey",
712
+ property_key: "propertyKey",
713
+ operator: "operator",
714
+ unit: "unit",
715
+ claim_text: "claimText"
716
+ };
717
+ var SCALAR_VALUE_FIELDS = new Set([
718
+ "value_string",
719
+ "value_int",
720
+ "value_number",
721
+ "value_bool"
722
+ ]);
723
+ function readScalar(lines, index, raw) {
724
+ const block = /^[>|][+-]?$/.test(raw.trim());
725
+ const parts = block ? [] : [raw.trim()];
726
+ let last = index;
727
+ while (last + 1 < lines.length) {
728
+ const next = lines[last + 1] ?? "";
729
+ if (next.trim() === "---" || !/^\s+\S/.test(next))
730
+ break;
731
+ if (!block && /^\s+-\s/.test(next))
732
+ break;
733
+ parts.push(next.trim());
734
+ last += 1;
735
+ }
736
+ const joined = parts.join(" ").trim();
737
+ return { value: block ? joined : unquote(joined), last };
738
+ }
739
+ function firstParagraph(lines) {
740
+ const paragraph = [];
741
+ for (const line of lines) {
742
+ const text = line.trim();
743
+ if (text === "") {
744
+ if (paragraph.length > 0)
745
+ break;
746
+ continue;
747
+ }
748
+ if (/^#{1,6}\s/.test(text)) {
749
+ if (paragraph.length > 0)
750
+ break;
751
+ continue;
752
+ }
753
+ paragraph.push(text);
754
+ }
755
+ return paragraph.length > 0 ? paragraph.join(" ") : undefined;
756
+ }
757
+ var MAX_SUMMARY_LINKS = 24;
758
+ var RELATIONSHIPS_DIR = ".kb/relationships";
759
+ var shardCache = new Map;
760
+ function shardPathFor(workspaceRoot, entityId) {
761
+ const shard = createHash("sha256").update(entityId).digest("hex");
762
+ return path2.join(workspaceRoot, RELATIONSHIPS_DIR, `${shard.slice(0, 2)}.yaml`);
763
+ }
764
+ function shardLinksFrom(workspaceRoot, entityId) {
765
+ const shardPath = shardPathFor(workspaceRoot, entityId);
766
+ const signature = fileSignature(shardPath);
767
+ if (signature === "missing") {
768
+ shardCache.delete(shardPath);
769
+ return [];
770
+ }
771
+ let cached = shardCache.get(shardPath);
772
+ if (cached?.signature !== signature) {
773
+ 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;
778
+ }
779
+ const list = byFrom.get(from) ?? [];
780
+ list.push({ type, target: to });
781
+ byFrom.set(from, list);
782
+ }
783
+ cached = { signature, byFrom };
784
+ shardCache.set(shardPath, cached);
785
+ }
786
+ return cached.byFrom.get(entityId) ?? [];
787
+ }
788
+ function flushLink(links, pending) {
789
+ if (pending?.target !== undefined) {
790
+ links.push({ type: pending.type ?? "relates_to", target: pending.target });
791
+ } else if (pending?.bare !== undefined) {
792
+ links.push({ type: "relates_to", target: pending.bare });
793
+ }
794
+ }
795
+ function readEntitySummary(workspaceRoot, entityId) {
796
+ const lane = ENTITY_LANES.find(([prefix]) => entityId.startsWith(prefix));
797
+ if (!lane || !SAFE_ENTITY_ID.test(entityId))
798
+ return { id: entityId };
799
+ let head;
800
+ try {
801
+ const descriptor = fs.openSync(path2.join(workspaceRoot, ".kb", lane[1], `${entityId}.md`), "r");
802
+ try {
803
+ const buffer = Buffer.alloc(8192);
804
+ const bytes = fs.readSync(descriptor, buffer, 0, buffer.length, 0);
805
+ head = buffer.subarray(0, bytes).toString("utf8");
806
+ } finally {
807
+ fs.closeSync(descriptor);
808
+ }
809
+ } catch {
810
+ return { id: entityId };
811
+ }
812
+ const lines = head.split(/\r?\n/);
813
+ if (lines[0]?.trim() !== "---")
814
+ return { id: entityId };
815
+ const summary = { id: entityId };
816
+ const authored = [];
817
+ const claim = {};
818
+ const values = [];
819
+ let inLinks = false;
820
+ let inValueSet = false;
821
+ let inOrigin = false;
822
+ let originKind;
823
+ let closed = false;
824
+ let bodyStart = lines.length;
825
+ let pending;
826
+ for (let index = 1;index < lines.length; index += 1) {
827
+ const line = lines[index] ?? "";
828
+ if (line.trim() === "---") {
829
+ closed = true;
830
+ bodyStart = index + 1;
831
+ break;
832
+ }
833
+ const scalar = /^([a-z_]+):\s*(.*)$/.exec(line);
834
+ const scalarField = scalar ? SUMMARY_SCALARS[scalar[1] ?? ""] : undefined;
835
+ if (scalar && scalarField && scalar[2]) {
836
+ const { value, last } = readScalar(lines, index, scalar[2]);
837
+ if (value !== "") {
838
+ if (scalarField === "statement")
839
+ summary.statement = value;
840
+ else if (scalarField !== "values")
841
+ claim[scalarField] = value;
842
+ }
843
+ index = last;
844
+ flushLink(authored, pending);
845
+ pending = undefined;
846
+ inLinks = false;
847
+ inValueSet = false;
848
+ continue;
849
+ }
850
+ if (scalar && SCALAR_VALUE_FIELDS.has(scalar[1] ?? "") && scalar[2]) {
851
+ values.push(unquote(scalar[2]));
852
+ }
853
+ const flowSet = /^value_set:\s*\[(.*)\]\s*$/.exec(line);
854
+ if (flowSet?.[1]?.trim()) {
855
+ values.push(...flowSet[1].split(",").map((member) => unquote(member)));
856
+ }
857
+ if (/^\S/.test(line))
858
+ inValueSet = /^value_set:\s*$/.test(line);
859
+ else if (inValueSet) {
860
+ const member = /^\s*-\s*(.+)$/.exec(line);
861
+ if (member)
862
+ values.push(unquote(member[1] ?? ""));
863
+ }
864
+ const flowOrigin = /^origin:\s*\{.*\bkind:\s*["']?([a-z]+)/.exec(line);
865
+ if (flowOrigin)
866
+ originKind = flowOrigin[1];
867
+ const nestedKind = inOrigin ? /^\s+kind:\s*["']?([a-z]+)/.exec(line) : null;
868
+ if (nestedKind)
869
+ originKind = nestedKind[1];
870
+ if (/^\S/.test(line))
871
+ inOrigin = /^origin:\s*$/.test(line);
872
+ const match = /^(title|status|firmness):\s*(.*)$/.exec(line);
873
+ if (match?.[1] === "title" && match[2])
874
+ summary.title = unquote(match[2]);
875
+ if (match?.[1] === "status" && match[2])
876
+ summary.status = unquote(match[2]);
877
+ if (match?.[1] === "firmness" && match[2])
878
+ summary.firmness = unquote(match[2]);
879
+ if (/^\S/.test(line)) {
880
+ flushLink(authored, pending);
881
+ pending = undefined;
882
+ inLinks = /^links:\s*$/.test(line);
883
+ continue;
884
+ }
885
+ if (!inLinks)
886
+ continue;
887
+ const entry = /^\s*-\s*(.*)$/.exec(line);
888
+ if (entry) {
889
+ flushLink(authored, pending);
890
+ pending = {};
891
+ }
892
+ if (!pending)
893
+ continue;
894
+ const body = (entry ? entry[1] ?? "" : line).trim();
895
+ const field = /^(type|target):\s*(.+)$/.exec(body);
896
+ if (field?.[1] === "type")
897
+ pending.type = unquote(field[2] ?? "");
898
+ else if (field?.[1] === "target")
899
+ pending.target = unquote(field[2] ?? "");
900
+ else if (entry && SAFE_ENTITY_ID.test(unquote(body)))
901
+ pending.bare = unquote(body);
902
+ }
903
+ flushLink(authored, pending);
904
+ if (summary.firmness === undefined && closed && (lane[1] === "requirements" || lane[1] === "adr"))
905
+ summary.firmness = originKind === "agent" ? "default" : "firm";
906
+ const links = [];
907
+ const seen = new Set;
908
+ for (const link of [
909
+ ...authored,
910
+ ...shardLinksFrom(workspaceRoot, entityId)
911
+ ]) {
912
+ const key = `${link.type}\x00${link.target}`;
913
+ if (link.target.length === 0 || seen.has(key))
914
+ continue;
915
+ seen.add(key);
916
+ links.push(link);
917
+ if (links.length >= MAX_SUMMARY_LINKS)
918
+ break;
919
+ }
920
+ if (links.length > 0)
921
+ summary.links = links;
922
+ if (values.length > 0)
923
+ claim.values = values;
924
+ if (Object.keys(claim).length > 0)
925
+ summary.claim = claim;
926
+ if (summary.statement === undefined && lane[1] === "requirements" && closed) {
927
+ const paragraph = firstParagraph(lines.slice(bodyStart));
928
+ if (paragraph)
929
+ summary.statement = paragraph;
930
+ }
931
+ return summary;
932
+ }
933
+
934
+ // ../agent-core/dist/snippets.js
935
+ var MAX_REQUIREMENTS = 4;
936
+ var MAX_SYMBOLS_PER_REQUIREMENT = 3;
937
+ var MAX_TESTS = 3;
938
+ var MAX_TITLE = 80;
939
+ var MAX_GROUNDING = 2;
940
+ var MAX_CLAIM = 160;
941
+ var MAX_FOCUS_SCAN_BYTES = 2 * 1024 * 1024;
942
+ var MAX_SNIPPET_CHARS = 1200;
943
+ function truncate(text, limit) {
944
+ return text.length <= limit ? text : `${text.slice(0, limit - 1)}…`;
945
+ }
946
+ 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;
951
+ }
952
+ function focusedSymbols(symbols, focus) {
953
+ if (!focus || focus.length === 0)
954
+ 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);
959
+ }
960
+ 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);
979
+ if (usable.length === 0)
980
+ return;
981
+ let content;
982
+ try {
983
+ if (fs2.statSync(absolutePath).size > MAX_FOCUS_SCAN_BYTES)
984
+ return;
985
+ content = fs2.readFileSync(absolutePath, "utf8");
986
+ } catch {
987
+ return;
988
+ }
989
+ const ranges = [];
990
+ for (const needle of usable) {
991
+ const offset = content.indexOf(needle);
992
+ if (offset < 0)
993
+ continue;
994
+ const start = content.slice(0, offset).split(`
995
+ `).length;
996
+ ranges.push({ start, end: start + needle.split(`
997
+ `).length - 1 });
998
+ }
999
+ return ranges.length > 0 ? ranges : undefined;
1000
+ }
1001
+ function formatList(items, limit) {
1002
+ const shown = items.slice(0, limit).join(", ");
1003
+ return items.length > limit ? `${shown} +${items.length - limit}` : shown;
1004
+ }
1005
+ var RETIRED_STATUS = /supersed|deprecat|reject|obsolete|retired/i;
1006
+ function isRetiredStatus(status) {
1007
+ return status !== undefined && RETIRED_STATUS.test(status);
1008
+ }
1009
+ function describeEntity(summary) {
1010
+ const retired = isRetiredStatus(summary.status) ? ` (${summary.status})` : "";
1011
+ const firmness = !retired && (summary.firmness === "firm" || summary.firmness === "exploratory") ? summary.firmness === "firm" ? " [firm: ask before changing]" : " [exploratory]" : "";
1012
+ const notable = `${retired}${firmness}`;
1013
+ const title = displayTitle(summary);
1014
+ return title ? `${summary.id}${notable}: ${truncate(title, MAX_TITLE)}` : `${summary.id}${notable}`;
1015
+ }
1016
+ function displayTitle(summary) {
1017
+ const { title, statement } = summary;
1018
+ if (!title || !statement?.startsWith(title))
1019
+ return title;
1020
+ const rest = statement.slice(title.length);
1021
+ if (!/^\.\S/.test(rest))
1022
+ return title;
1023
+ const end = rest.search(/[.!?](?:\s|$)/);
1024
+ return end < 0 ? statement : statement.slice(0, title.length + end);
1025
+ }
1026
+ function jsonString(value) {
1027
+ return JSON.stringify(value);
1028
+ }
1029
+ function impactCheckLine(relativePath) {
1030
+ return `Behavior changes here are traced to these requirements; kb_check({sourceFiles:[${jsonString(relativePath)}], includeImpactDiagnostics:true, includeWorkingTreeDiff:true}) reports ownership drift after the edit.`;
1031
+ }
1032
+ var GROUNDING_LINK_TYPES = [
1033
+ "constrains",
1034
+ "requires_property",
1035
+ "requires_predicate",
1036
+ "requires_rule"
1037
+ ];
1038
+ var PROPERTY_VERBS = {
1039
+ calls: "call",
1040
+ imports: "import",
1041
+ reads: "read",
1042
+ uses: "use",
1043
+ throws: "throw"
1044
+ };
1045
+ var OPERATOR_PHRASES = {
1046
+ eq: "must be",
1047
+ neq: "must not be",
1048
+ lt: "must be less than",
1049
+ lte: "must be at most",
1050
+ gt: "must be greater than",
1051
+ gte: "must be at least",
1052
+ matches: "must match",
1053
+ includes: "must include",
1054
+ subset_of: "must be within",
1055
+ excludes: "must not include"
1056
+ };
1057
+ function joinMembers(values, conjunction) {
1058
+ if (values.length <= 1)
1059
+ return values[0] ?? "";
1060
+ return `${values.slice(0, -1).join(", ")} ${conjunction} ${values.at(-1)}`;
1061
+ }
1062
+ var MODAL = /^(.{1,100}?)\s+(?:must|may|can|cannot|should|shall|will|never)\b/i;
1063
+ function subjectPhrase(claim) {
1064
+ const spoken = MODAL.exec(claim.claimText ?? "")?.[1];
1065
+ if (spoken)
1066
+ return spoken;
1067
+ return claim.subjectKey?.replace(/[._]+/g, " ").trim() || undefined;
1068
+ }
1069
+ function factClaimSentence(summary) {
1070
+ const claim = summary.claim;
1071
+ if (!claim || claim.factKind === "subject")
1072
+ return;
1073
+ const values = claim.values ?? [];
1074
+ if (claim.propertyKey && claim.operator && values.length > 0) {
1075
+ const subject = subjectPhrase(claim);
1076
+ if (!subject)
1077
+ return claim.claimText;
1078
+ const verb = PROPERTY_VERBS[claim.propertyKey];
1079
+ const verbal = verb ? {
1080
+ excludes: `must not ${verb} ${joinMembers(values, "or")}`,
1081
+ includes: `must ${verb} ${joinMembers(values, "and")}`,
1082
+ subset_of: `may ${verb} only ${joinMembers(values, "or")}`,
1083
+ eq: `must ${verb} exactly ${joinMembers(values, "and")}`
1084
+ }[claim.operator] : undefined;
1085
+ if (verbal)
1086
+ return `${subject} ${verbal}`;
1087
+ const phrase = OPERATOR_PHRASES[claim.operator];
1088
+ if (phrase) {
1089
+ const property = claim.propertyKey.replace(/_+/g, " ");
1090
+ const unit = claim.unit ? ` ${claim.unit}` : "";
1091
+ return `${subject}: ${property} ${phrase} ${values.join(", ")}${unit}`;
1092
+ }
1093
+ }
1094
+ return claim.claimText;
1095
+ }
1096
+ function groundingText(summary) {
1097
+ if (summary.claim?.factKind === "subject")
1098
+ return;
1099
+ return factClaimSentence(summary) ?? summary.title;
1100
+ }
1101
+ function withoutClosingStop(text) {
1102
+ return text.replace(/[.\s]+$/, "");
1103
+ }
1104
+ function requirementGroundingLines(requirementId, summarize, options = {}) {
1105
+ const maxFacts = options.maxFacts ?? MAX_GROUNDING;
1106
+ const requirement = summarize(requirementId);
1107
+ if (isRetiredStatus(requirement.status))
1108
+ return [];
1109
+ const links = requirement.links ?? [];
1110
+ const facts = [
1111
+ ...new Set(links.filter((link) => GROUNDING_LINK_TYPES.includes(link.type)).map((link) => link.target))
1112
+ ];
1113
+ const adrs = [
1114
+ ...new Set(links.filter((link) => link.target.startsWith("ADR-")).map((link) => link.target))
1115
+ ];
1116
+ const lines = [];
1117
+ if (facts.length > 0) {
1118
+ const claims = [
1119
+ ...new Set(facts.map((id) => groundingText(summarize(id))).filter((sentence) => Boolean(sentence)).map(withoutClosingStop))
1120
+ ];
1121
+ const statement = requirement.statement ? withoutClosingStop(requirement.statement) : undefined;
1122
+ const shown = claims.length > 0 ? claims.slice(0, maxFacts) : statement ? [statement] : [];
1123
+ const more = claims.length > maxFacts ? ` +${claims.length - maxFacts} more` : "";
1124
+ if (shown.length > 0) {
1125
+ lines.push(`${requirementId} must keep true: ${shown.map((claim) => truncate(claim, MAX_CLAIM)).join("; ")}${more}.`);
1126
+ }
1127
+ }
1128
+ const adr = adrs[0];
1129
+ if (adr)
1130
+ lines.push(`Decision: ${describeEntity(summarize(adr))}.`);
1131
+ return lines;
1132
+ }
1133
+ function implementedRequirementIds(symbols) {
1134
+ return [...new Set(symbols.flatMap((symbol) => symbol.implements))];
1135
+ }
1136
+ function createEntitySummarizer(workspaceRoot) {
1137
+ const summaries = new Map;
1138
+ return (entityId) => {
1139
+ let summary = summaries.get(entityId);
1140
+ if (!summary) {
1141
+ summary = readEntitySummary(workspaceRoot, entityId);
1142
+ summaries.set(entityId, summary);
1143
+ }
1144
+ return summary;
1145
+ };
1146
+ }
1147
+ function requirementOwners(symbols, focus) {
1148
+ const owners = new Map;
1149
+ const ordered = [
1150
+ ...focus,
1151
+ ...symbols.filter((symbol) => !focus.includes(symbol))
1152
+ ];
1153
+ for (const symbol of ordered) {
1154
+ for (const requirement of symbol.implements) {
1155
+ const titles = owners.get(requirement) ?? [];
1156
+ if (!titles.includes(symbol.title))
1157
+ titles.push(symbol.title);
1158
+ owners.set(requirement, titles);
1159
+ }
1160
+ }
1161
+ const focusOwners = new Set(focus.flatMap((symbol) => symbol.implements));
1162
+ const requirementIds = [...owners.keys()].sort((left, right) => {
1163
+ 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);
1165
+ });
1166
+ return { owners, requirementIds };
1167
+ }
1168
+ function fileKnowledgeSnippet(input) {
1169
+ const { relativePath, symbols, surface, summarize } = input;
1170
+ const focus = focusedSymbols(symbols, input.focus);
1171
+ const { owners, requirementIds } = requirementOwners(symbols, focus);
1172
+ const tests = [
1173
+ ...new Set(symbols.flatMap((symbol) => symbol.coveredBy))
1174
+ ].sort();
1175
+ const executes = [
1176
+ ...new Set(symbols.flatMap((symbol) => symbol.executableFor))
1177
+ ].sort();
1178
+ if (owners.size === 0 && tests.length === 0 && executes.length === 0) {
1179
+ return;
1180
+ }
1181
+ const lines = [
1182
+ `Kibi knowledge for ${relativePath} (symbol manifest):`
1183
+ ];
1184
+ for (const requirementId of requirementIds.slice(0, MAX_REQUIREMENTS)) {
1185
+ const symbolTitles = owners.get(requirementId) ?? [];
1186
+ lines.push(`- ${describeEntity(summarize(requirementId))} — ${formatList(symbolTitles, MAX_SYMBOLS_PER_REQUIREMENT)}`);
1187
+ }
1188
+ if (requirementIds.length > MAX_REQUIREMENTS) {
1189
+ lines.push(`- +${requirementIds.length - MAX_REQUIREMENTS} more requirements: kb_query({sourceFile:${jsonString(relativePath)}})`);
1190
+ }
1191
+ const leadId = requirementIds[0];
1192
+ if (surface === "edit" && leadId) {
1193
+ lines.push(...requirementGroundingLines(leadId, summarize));
1194
+ }
1195
+ if (tests.length > 0) {
1196
+ lines.push(`Covered by: ${formatList(tests, MAX_TESTS)}.`);
1197
+ }
1198
+ if (executes.length > 0) {
1199
+ lines.push(`Test code for: ${formatList(executes, MAX_TESTS)}.`);
1200
+ }
1201
+ const primaryFocus = focus[0];
1202
+ if (primaryFocus) {
1203
+ const owner = primaryFocus.implements[0];
1204
+ const verb = surface === "edit" ? "The edit is inside" : "These lines are inside";
1205
+ lines.push(owner ? `${verb} ${primaryFocus.title}, which implements ${owner}.` : `${verb} ${primaryFocus.title}.`);
1206
+ }
1207
+ const location = primaryFocus ? `{path:${jsonString(relativePath)}, symbol:${jsonString(primaryFocus.title)}}` : `{path:${jsonString(relativePath)}}`;
1208
+ const next = [
1209
+ leadId ? `kb_query({id:${jsonString(leadId)}}) returns full requirement text` : undefined,
1210
+ `kb_search({query:"<topic>", sourceLocations:[${location}]}) answers with governing requirements, facts, decisions, and tests`
1211
+ ].filter((part) => part !== undefined);
1212
+ lines.push(`Next layer: ${next.join("; ")}.`);
1213
+ if (surface === "edit")
1214
+ lines.push(impactCheckLine(relativePath));
1215
+ return truncate(lines.join(`
1216
+ `), input.maxChars ?? MAX_SNIPPET_CHARS);
1217
+ }
1218
+
1219
+ // ../agent-core/dist/decision-guard.js
1220
+ var CONFIRM_FIRM_EDITS_ENV = "KIBI_CONFIRM_FIRM_EDITS";
1221
+ function confirmFirmEdits(env) {
1222
+ const value = env[CONFIRM_FIRM_EDITS_ENV]?.trim().toLowerCase();
1223
+ return !(value === "0" || value === "false" || value === "off");
1224
+ }
1225
+ function firmRequirementsTouched(input) {
1226
+ const touched = input.wholeFile ? input.symbols : focusedSymbols(input.symbols, input.focus);
1227
+ const ids = implementedRequirementIds(touched).filter((id) => {
1228
+ if (input.confirmed?.has(id))
1229
+ return false;
1230
+ const summary = input.summarize(id);
1231
+ return summary.firmness === "firm" && !isRetiredStatus(summary.status);
1232
+ });
1233
+ return { ids, symbol: input.wholeFile ? undefined : touched[0] };
1234
+ }
1235
+ var RULE_PHRASES = /\b(?:from now on|going forward|must(?:n['’]t| not)?|should (?:always|never)|never|always|is not allowed|are not allowed|not be allowed|only (?:allow|admins?|owners?)|is required|are required|has to|have to)\b/i;
1236
+ function statedRule(prompt) {
1237
+ const match = RULE_PHRASES.exec(prompt);
1238
+ return match ? match[0].toLowerCase() : undefined;
1239
+ }
1240
+ function rulePromptNote(phrase) {
1241
+ return [
1242
+ `Kibi: this request reads as a rule or decision (it says "${phrase}").`,
1243
+ "A rule the person states is project knowledge: kb_search shows whether a recorded requirement already says something about it (changing a firm one is a question for the person first), and a new or changed rule is recorded with kb_upsert (a requirement, firm because the person stated it, with the implements link from the code that enforces it) as part of this change, not after it."
1244
+ ].join(`
1245
+ `);
1246
+ }
1247
+ function captureReminder(rulePrompt) {
1248
+ return [
1249
+ rulePrompt ? "Kibi: a request this session read as a rule or decision, the session changed source files, and nothing was recorded in the knowledge base." : "Kibi: this session changed source files and recorded nothing in the knowledge base.",
1250
+ "A decision the person stated or this change made (a rule, a limit, a default, who may do what) is recorded with kb_upsert as a requirement linked to the code that implements it before the work is reported done; when there was none, the final report says so in one line."
1251
+ ].join(`
1252
+ `);
1253
+ }
1254
+ function firmEditNotice(where, requirements) {
1255
+ const shown = requirements.slice(0, 2).map((summary) => summary.title ? `${summary.id}: ${summary.title}` : summary.id);
1256
+ const more = requirements.length > shown.length ? ` (+${requirements.length - shown.length} more)` : "";
1257
+ const one = requirements.length === 1;
1258
+ return [
1259
+ `Kibi: this edit changes ${where}, which implements ${one ? "a firm requirement" : "firm requirements"}, a rule the person set: ${shown.join("; ")}${more}.`,
1260
+ `Unless the edit keeps ${one ? "that rule" : "those rules"}, ask the person before making it and name the rule; change the requirement with kb_upsert in this change only when they say so.`
1261
+ ].join(`
1262
+ `);
1263
+ }
1264
+ function sessionPath(session, name) {
1265
+ if (!session.stateDir)
1266
+ return;
1267
+ const key = createHash2("sha256").update(session.sessionId?.trim() || "unattributed").digest("hex").slice(0, 32);
1268
+ return path3.join(session.stateDir, "decision-guard", key, name);
1269
+ }
1270
+ function writeMarker(file, content, flag) {
1271
+ fs3.mkdirSync(path3.dirname(file), { recursive: true });
1272
+ fs3.writeFileSync(file, content, { flag });
1273
+ }
1274
+ function markDecisionFlag(session, flag) {
1275
+ const file = sessionPath(session, flag);
1276
+ if (!file)
1277
+ return;
1278
+ try {
1279
+ writeMarker(file, "", "w");
1280
+ } catch {}
1281
+ }
1282
+ function hasDecisionFlag(session, flag) {
1283
+ const file = sessionPath(session, flag);
1284
+ return file !== undefined && fs3.existsSync(file);
1285
+ }
1286
+ function claimDecisionFlag(session, flag) {
1287
+ const file = sessionPath(session, flag);
1288
+ if (!file)
1289
+ return false;
1290
+ try {
1291
+ writeMarker(file, "", "wx");
1292
+ return true;
1293
+ } catch {
1294
+ return false;
1295
+ }
1296
+ }
1297
+ function recordRequest(session, prompt) {
1298
+ 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
+ }
1305
+ if (rule !== undefined)
1306
+ markDecisionFlag(session, "rule-prompt");
1307
+ return rule;
1308
+ }
1309
+ function claimCaptureReminder(session) {
1310
+ return hasDecisionFlag(session, "source-edited") && !hasDecisionFlag(session, "kb-recorded") && claimDecisionFlag(session, "capture-asked");
1311
+ }
1312
+ var SIGNOFF_LATEST_JSON = path3.join(".kb", "briefs", "signoff", "latest.json");
1313
+ function record(value) {
1314
+ return typeof value === "object" && value !== null && !Array.isArray(value) ? value : undefined;
1315
+ }
1316
+ function isRequirementUpsert(toolInput) {
1317
+ const input = record(toolInput);
1318
+ const args = record(input?.arguments) ?? record(input?.args) ?? input;
1319
+ return args?.type === "req" && args.dryRun !== true;
1320
+ }
1321
+ function mtimeMs(file) {
1322
+ if (!file)
1323
+ return;
1324
+ try {
1325
+ return fs3.statSync(file).mtimeMs;
1326
+ } catch {
1327
+ return;
1328
+ }
1329
+ }
1330
+ function claimSignoffReminder(session, workspaceRoot) {
1331
+ const recorded = mtimeMs(sessionPath(session, "requirement-recorded"));
1332
+ if (recorded === undefined)
1333
+ return false;
1334
+ const shown = mtimeMs(path3.join(workspaceRoot, SIGNOFF_LATEST_JSON));
1335
+ if (shown !== undefined && shown >= recorded)
1336
+ return false;
1337
+ return claimDecisionFlag(session, "signoff-asked");
1338
+ }
1339
+ function signoffReminder() {
1340
+ return [
1341
+ "Kibi: this session recorded or changed requirements, and the person has not seen them as a signoff view.",
1342
+ "Before reporting the work done, asking to commit, or opening or merging the pull request, run `kibi signoff` and put its output in the reply unchanged; a requirement the agent wrote waits for the person's approval, so ask for it with this view, never in your own words. When they answer, record their answer on the same branch and push it to the same pull request, never to a separate one. When replies go through a messaging tool, pass the output there as the message text, and share `.kb/briefs/signoff/latest.html` only as a rendered page (the Artifact tool in Claude apps), never as an attached file, which chat apps show as raw source (kibi-decisions, resources/signoff-views.md)."
1343
+ ].join(`
1344
+ `);
1345
+ }
1346
+ function readSettled(file) {
1347
+ if (!file)
1348
+ return [];
1349
+ try {
1350
+ const parsed = JSON.parse(fs3.readFileSync(file, "utf8"));
1351
+ return Array.isArray(parsed) ? parsed.filter((id) => typeof id === "string") : [];
1352
+ } catch {
1353
+ return [];
1354
+ }
1355
+ }
1356
+ function settledFirmRequirements(session) {
1357
+ return new Set(readSettled(sessionPath(session, "firm-settled.json")));
1358
+ }
1359
+ function settleFirmRequirements(session, ids) {
1360
+ const file = sessionPath(session, "firm-settled.json");
1361
+ if (!file)
1362
+ return;
1363
+ try {
1364
+ writeMarker(file, JSON.stringify([...new Set([...readSettled(file), ...ids])]), "w");
1365
+ } catch {}
1366
+ }
1367
+ function holdRuleNote(session, phrase) {
1368
+ const file = sessionPath(session, "rule-note");
1369
+ if (!file)
1370
+ return;
1371
+ try {
1372
+ writeMarker(file, rulePromptNote(phrase), "w");
1373
+ } catch {}
1374
+ }
1375
+ function takeRuleNote(session) {
1376
+ const file = sessionPath(session, "rule-note");
1377
+ if (!file)
1378
+ return;
1379
+ try {
1380
+ const note = fs3.readFileSync(file, "utf8");
1381
+ fs3.rmSync(file, { force: true });
1382
+ return note || undefined;
1383
+ } catch {
1384
+ return;
1385
+ }
1386
+ }
1387
+
1388
+ // ../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";
1392
+
1393
+ // ../agent-core/dist/kb-mcp-tools.js
1394
+ function isRecord2(value) {
1395
+ return typeof value === "object" && value !== null && !Array.isArray(value);
1396
+ }
1397
+ function readString(record, keys) {
1398
+ for (const key of keys) {
1399
+ const value = record[key];
1400
+ if (typeof value === "string") {
1401
+ return value;
1402
+ }
1403
+ }
1404
+ return;
1405
+ }
1406
+ function readBoolean(record, keys) {
1407
+ for (const key of keys) {
1408
+ const value = record[key];
1409
+ if (typeof value === "boolean") {
1410
+ return value;
1411
+ }
1412
+ }
1413
+ return;
1414
+ }
1415
+ function readStringArray(record, keys) {
1416
+ for (const key of keys) {
1417
+ const value = record[key];
1418
+ if (!Array.isArray(value)) {
1419
+ continue;
1420
+ }
1421
+ return value.filter((item) => typeof item === "string" && item.length > 0);
1422
+ }
1423
+ return [];
1424
+ }
1425
+ function readRecord(record, keys) {
1426
+ for (const key of keys) {
1427
+ const value = record[key];
1428
+ if (isRecord2(value)) {
1429
+ return value;
1430
+ }
1431
+ }
1432
+ return;
1433
+ }
1434
+ function canonicalKbToolName(toolName) {
1435
+ const trimmed = toolName?.trim() ?? "";
1436
+ const lastSegment = trimmed.includes("__") ? trimmed.split("__").at(-1) ?? "" : trimmed.replace(/^MCP:/i, "");
1437
+ const operation = lastSegment.replace(/^kibi_/, "");
1438
+ return operation.startsWith("kb_") ? operation : undefined;
1439
+ }
1440
+ var KIBI_WORKSPACE_ARGUMENT = "workspaceRoot";
1441
+ function isKibiMcpToolName(toolName) {
1442
+ const name = toolName?.trim() ?? "";
1443
+ if (/^MCP:kb_[a-z_]+$/i.test(name))
1444
+ return true;
1445
+ const segments = name.split("__");
1446
+ if (segments.length < 3 || segments[0] !== "mcp")
1447
+ return false;
1448
+ const server = segments.slice(1, -1).join("__");
1449
+ return /kibi/i.test(server) && /^kb_[a-z_]+$/.test(segments.at(-1) ?? "");
1450
+ }
1451
+ function stampKibiWorkspace(toolName, toolInput, workspaceRoot) {
1452
+ if (!workspaceRoot || !isKibiMcpToolName(toolName))
1453
+ return;
1454
+ const base = isRecord2(toolInput) ? toolInput : {};
1455
+ return { ...base, [KIBI_WORKSPACE_ARGUMENT]: workspaceRoot };
1456
+ }
1457
+ function resolveKibiInterface(mcpState, workspaceTrusted) {
1458
+ if (mcpState === "observed") {
1459
+ return "mcp";
1460
+ }
1461
+ return workspaceTrusted ? "cli" : "setup";
1462
+ }
1463
+ 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, [
1474
+ "includeImpactDiagnostics",
1475
+ "include_impact_diagnostics"
1476
+ ]);
1477
+ const includeWorkingTreeDiff = readBoolean(payload, [
1478
+ "includeWorkingTreeDiff",
1479
+ "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;
1515
+ }
1516
+ var KIBI_CLI_ROUTES = {
1517
+ check: "kb_check",
1518
+ query: "kb_query",
1519
+ "kb-query": "kb_query",
1520
+ search: "kb_search",
1521
+ status: "kb_status",
1522
+ graph: "kb_graph",
1523
+ coverage: "kb_coverage",
1524
+ "find-gaps": "kb_find_gaps",
1525
+ gaps: "kb_find_gaps",
1526
+ upsert: "kb_upsert",
1527
+ sync: "kb_sync"
1528
+ };
1529
+ function kibiCliOperation(command) {
1530
+ if (typeof command !== "string")
1531
+ return;
1532
+ const route = /(?:^|[\s;&|(/])kibi\s+([a-z-]+)/.exec(command)?.[1];
1533
+ return route ? KIBI_CLI_ROUTES[route] : undefined;
1534
+ }
1535
+ var SHELL_TOOL_NAMES = new Set(["Bash", "bash", "Shell", "shell"]);
1536
+ function hostKbOperation(toolName, toolInput) {
1537
+ const mcp = canonicalKbToolName(toolName);
1538
+ if (mcp !== undefined && /^kb_[a-z_]+$/.test(mcp))
1539
+ return mcp;
1540
+ if (!toolName || !SHELL_TOOL_NAMES.has(toolName) || !isRecord2(toolInput)) {
1541
+ return;
1542
+ }
1543
+ return kibiCliOperation(toolInput.command);
1544
+ }
1545
+
1546
+ // ../agent-core/dist/path-policy.js
1547
+ import path4 from "node:path";
1548
+ var codeExtensions = new Set([
1549
+ ".c",
1550
+ ".cc",
1551
+ ".cjs",
1552
+ ".cpp",
1553
+ ".cs",
1554
+ ".css",
1555
+ ".go",
1556
+ ".h",
1557
+ ".hpp",
1558
+ ".html",
1559
+ ".java",
1560
+ ".js",
1561
+ ".jsx",
1562
+ ".kt",
1563
+ ".lua",
1564
+ ".mjs",
1565
+ ".mts",
1566
+ ".cts",
1567
+ ".php",
1568
+ ".pl",
1569
+ ".py",
1570
+ ".rb",
1571
+ ".rs",
1572
+ ".scala",
1573
+ ".sh",
1574
+ ".swift",
1575
+ ".ts",
1576
+ ".tsx",
1577
+ ".vue",
1578
+ ".svelte"
1579
+ ]);
1580
+ var ignoredSegments = new Set([
1581
+ "node_modules",
1582
+ "dist",
1583
+ "build",
1584
+ "out",
1585
+ "coverage",
1586
+ "vendor",
1587
+ "target",
1588
+ ".git",
1589
+ ".next",
1590
+ ".turbo",
1591
+ ".cache",
1592
+ "__pycache__"
1593
+ ]);
1594
+ var testSegments = new Set([
1595
+ "test",
1596
+ "tests",
1597
+ "__tests__",
1598
+ "spec",
1599
+ "specs",
1600
+ "e2e",
1601
+ "__mocks__",
1602
+ "fixtures"
1603
+ ]);
1604
+ var testBasename = /(\.|_)(test|spec|e2e)\.[^.]+$|^test_[^/]+\.py$/;
1605
+ var documentationExtensions = new Set([".md", ".mdx", ".rst", ".txt"]);
1606
+ var documentationSegments = new Set(["docs", "documentation"]);
1607
+ var canonicalKbKnowledgeLanes = new Set([
1608
+ "requirements",
1609
+ "scenarios",
1610
+ "tests",
1611
+ "facts",
1612
+ "adr",
1613
+ "flags",
1614
+ "events"
1615
+ ]);
1616
+ var canonicalKbKnowledgeFiles = new Set([
1617
+ "symbols.yaml",
1618
+ "symbol-coordinates.yaml"
1619
+ ]);
1620
+ var explicitPathKeys = new Set([
1621
+ "absolute_path",
1622
+ "file",
1623
+ "file_path",
1624
+ "filepath",
1625
+ "new_path",
1626
+ "old_path",
1627
+ "path",
1628
+ "paths",
1629
+ "relative_path",
1630
+ "target_path"
1631
+ ]);
1632
+ function isRecord3(value) {
1633
+ return typeof value === "object" && value !== null && !Array.isArray(value);
1634
+ }
1635
+ function normalizeWorkspacePath(candidate) {
1636
+ return candidate.trim().replaceAll("\\", "/");
1637
+ }
1638
+ function pathSegments(candidate) {
1639
+ return normalizeWorkspacePath(candidate).split("/").filter(Boolean);
1640
+ }
1641
+ function collectPathValues(value, output) {
1642
+ if (typeof value === "string") {
1643
+ const normalized = normalizeWorkspacePath(value);
1644
+ if (normalized.length > 0)
1645
+ output.push(normalized);
1646
+ return;
1647
+ }
1648
+ if (!Array.isArray(value))
1649
+ return;
1650
+ for (const item of value)
1651
+ collectPathValues(item, output);
1652
+ }
1653
+ function visitExplicitPathFields(value, output) {
1654
+ if (Array.isArray(value)) {
1655
+ for (const item of value)
1656
+ visitExplicitPathFields(item, output);
1657
+ return;
1658
+ }
1659
+ if (!isRecord3(value))
1660
+ return;
1661
+ for (const [key, child] of Object.entries(value)) {
1662
+ if (explicitPathKeys.has(key.toLowerCase()))
1663
+ collectPathValues(child, output);
1664
+ visitExplicitPathFields(child, output);
1665
+ }
1666
+ }
1667
+ function extractExplicitPathFields(input) {
1668
+ const paths = [];
1669
+ visitExplicitPathFields(input, paths);
1670
+ return [...new Set(paths)];
1671
+ }
1672
+ function classifyPath(relativePath) {
1673
+ const segments = pathSegments(relativePath);
1674
+ if (segments[0] === ".kb")
1675
+ return "kb";
1676
+ if (segments.some((segment) => ignoredSegments.has(segment)))
1677
+ return "other";
1678
+ if (segments.some((segment) => documentationSegments.has(segment))) {
1679
+ return "other";
1680
+ }
1681
+ const basename = segments.at(-1) ?? "";
1682
+ const extension = path4.extname(basename).toLowerCase();
1683
+ if (!codeExtensions.has(extension))
1684
+ return "other";
1685
+ if (testBasename.test(basename) || segments.slice(0, -1).some((segment) => testSegments.has(segment))) {
1686
+ return "test";
1687
+ }
1688
+ return "source";
1689
+ }
1690
+ function isKbPath(candidate) {
1691
+ return pathSegments(candidate).includes(".kb");
1692
+ }
1693
+ 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));
1699
+ }
1700
+ function isMeaningfulTrackedPath(candidate) {
1701
+ const segments = pathSegments(candidate);
1702
+ if (segments.some((segment) => ignoredSegments.has(segment)))
1703
+ return false;
1704
+ if (segments.includes(".kb"))
1705
+ return isCanonicalKbKnowledgePath(segments);
1706
+ const basename = segments.at(-1) ?? "";
1707
+ const extension = path4.extname(basename).toLowerCase();
1708
+ if (basename === "README.md")
1709
+ return true;
1710
+ if (segments.some((segment) => documentationSegments.has(segment))) {
1711
+ return documentationExtensions.has(extension);
1712
+ }
1713
+ if (testBasename.test(basename) || segments.slice(0, -1).some((segment) => testSegments.has(segment))) {
1714
+ return codeExtensions.has(extension) || documentationExtensions.has(extension);
1715
+ }
1716
+ return codeExtensions.has(extension);
1717
+ }
1718
+ function isSourceImpactRelevantPath(candidate) {
1719
+ return classifyPath(normalizeWorkspacePath(candidate)) === "source";
1720
+ }
1721
+ function isDocumentationTrackedPath(candidate) {
1722
+ 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());
1725
+ }
1726
+
1727
+ // ../agent-core/dist/usage-telemetry.js
1728
+ import { createHash as createHash3, randomUUID as randomUUID2 } from "node:crypto";
1729
+ import fs4 from "node:fs";
1730
+ import os from "node:os";
1731
+ import path5 from "node:path";
1732
+ import { fileURLToPath } from "node:url";
1733
+ var DEFAULT_TELEMETRY_KEY = "phc_A5z8jRhD528abGJiTtPfgu4d4hpbEbfiJr9iUGn9TMsY";
1734
+ var DEFAULT_TELEMETRY_HOST = "https://eu.i.posthog.com";
1735
+ var QUEUE_FILE = "telemetry-queue.jsonl";
1736
+ var SESSIONS_FILE = "telemetry-sessions.json";
1737
+ var SESSION_MARKER_MAX_AGE_MS = 30 * 60 * 1000;
1738
+ var MAX_SESSION_MARKERS = 200;
1739
+ var CONFIG_FILE = "telemetry.json";
1740
+ var SENDING_SUFFIX = ".sending";
1741
+ var FLUSH_BATCH_EVENTS = 20;
1742
+ var FLUSH_MAX_AGE_MS = 5 * 60 * 1000;
1743
+ var STALE_CLAIM_MS = 10 * 60 * 1000;
1744
+ var MAX_EVENT_AGE_MS = 7 * 24 * 60 * 60 * 1000;
1745
+ var MAX_QUEUE_BYTES = 1024 * 1024;
1746
+ var DEFAULT_FLUSH_TIMEOUT_MS = 1500;
1747
+ function flagValue(value) {
1748
+ const normalized = value?.trim().toLowerCase();
1749
+ if (normalized === undefined || normalized === "")
1750
+ return;
1751
+ if (["1", "true", "yes", "on"].includes(normalized))
1752
+ return true;
1753
+ if (["0", "false", "no", "off"].includes(normalized))
1754
+ return false;
1755
+ return;
1756
+ }
1757
+ var CI_ENV_KEYS = [
1758
+ "CI",
1759
+ "CONTINUOUS_INTEGRATION",
1760
+ "GITHUB_ACTIONS",
1761
+ "GITLAB_CI",
1762
+ "BUILDKITE",
1763
+ "CIRCLECI",
1764
+ "TRAVIS",
1765
+ "TF_BUILD",
1766
+ "JENKINS_URL",
1767
+ "TEAMCITY_VERSION",
1768
+ "BITBUCKET_BUILD_NUMBER",
1769
+ "CODEBUILD_BUILD_ID",
1770
+ "DRONE",
1771
+ "APPVEYOR",
1772
+ "SEMAPHORE",
1773
+ "BUILDKITE_BUILD_ID",
1774
+ "CF_BUILD_ID",
1775
+ "HEROKU_TEST_RUN_ID"
1776
+ ];
1777
+ var TEST_ENV_KEYS = [
1778
+ "NODE_ENV=test",
1779
+ "JEST_WORKER_ID",
1780
+ "VITEST",
1781
+ "VITEST_WORKER_ID",
1782
+ "NODE_TEST_CONTEXT"
1783
+ ];
1784
+ function automationName(env, keys) {
1785
+ for (const key of keys) {
1786
+ const [name, expected] = key.split("=");
1787
+ const value = env[name]?.trim();
1788
+ if (!value)
1789
+ continue;
1790
+ if (expected !== undefined) {
1791
+ if (value === expected)
1792
+ return key;
1793
+ continue;
1794
+ }
1795
+ if (flagValue(value) !== false)
1796
+ return name;
1797
+ }
1798
+ return;
1799
+ }
1800
+ var HOST_ENV_KEYS = [
1801
+ ["CLAUDECODE=1", "claude-code"],
1802
+ ["CURSOR_AGENT=1", "cursor"],
1803
+ ["CODEX_SANDBOX", "codex"],
1804
+ ["CODEX_THREAD_ID", "codex"],
1805
+ ["GEMINI_CLI=1", "gemini-cli"],
1806
+ ["OPENCODE=1", "opencode"]
1807
+ ];
1808
+ var SESSION_ENV_KEYS = [
1809
+ "KIBI_SESSION_ID",
1810
+ "CLAUDE_CODE_SESSION_ID",
1811
+ "CODEX_THREAD_ID"
1812
+ ];
1813
+ function hostKey(name) {
1814
+ return name.trim().toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "");
1815
+ }
1816
+ function detectTelemetryHost(env = process.env) {
1817
+ const explicit = env.KIBI_HOST?.trim() || env.KIBI_MCP_HOST?.trim();
1818
+ if (explicit)
1819
+ return hostKey(explicit) || undefined;
1820
+ for (const [key, host] of HOST_ENV_KEYS) {
1821
+ if (automationName(env, [key]))
1822
+ return host;
1823
+ }
1824
+ const agent = env.AI_AGENT?.trim().split("_")[0];
1825
+ return agent ? hostKey(agent) || undefined : undefined;
1826
+ }
1827
+ function detectTelemetrySession(env = process.env) {
1828
+ for (const key of SESSION_ENV_KEYS) {
1829
+ const value = env[key]?.trim();
1830
+ if (value)
1831
+ return value;
1832
+ }
1833
+ return;
1834
+ }
1835
+ function remoteEnvironment(env) {
1836
+ return flagValue(env.CLAUDE_CODE_REMOTE) === true || flagValue(env.CODESPACES) === true || Boolean(env.GITPOD_WORKSPACE_ID?.trim());
1837
+ }
1838
+ function realDirectory(directory) {
1839
+ try {
1840
+ return fs4.realpathSync(directory);
1841
+ } catch {
1842
+ return path5.resolve(directory);
1843
+ }
1844
+ }
1845
+ function sandboxedHome(env) {
1846
+ const home = (env.HOME ?? env.USERPROFILE)?.trim();
1847
+ if (!home)
1848
+ return false;
1849
+ const temp = realDirectory(os.tmpdir());
1850
+ const resolved = realDirectory(home);
1851
+ return resolved !== temp && resolved.startsWith(`${temp}${path5.sep}`);
1852
+ }
1853
+ var KIBI_REPOSITORY_PACKAGE = "kibi";
1854
+ function thisModuleFile() {
1855
+ try {
1856
+ const url = import.meta.url;
1857
+ return typeof url === "string" && url !== "" ? fileURLToPath(url) : undefined;
1858
+ } catch {
1859
+ return;
1860
+ }
1861
+ }
1862
+ function insideKibiCheckout(file) {
1863
+ const resolved = realDirectory(file);
1864
+ if (resolved.split(/[\\/]/).includes("node_modules"))
1865
+ return false;
1866
+ let current = path5.dirname(resolved);
1867
+ for (;; ) {
1868
+ try {
1869
+ const manifest = JSON.parse(fs4.readFileSync(path5.join(current, "package.json"), "utf8"));
1870
+ if (manifest.name === KIBI_REPOSITORY_PACKAGE && fs4.existsSync(path5.join(current, "packages", "agent-core"))) {
1871
+ return true;
1872
+ }
1873
+ } catch {}
1874
+ const parent = path5.dirname(current);
1875
+ if (parent === current)
1876
+ return false;
1877
+ current = parent;
1878
+ }
1879
+ }
1880
+ var checkoutCache = new Map;
1881
+ function detectTelemetryChannel(env = process.env, moduleFile = thisModuleFile() ?? null) {
1882
+ return telemetryChannel(env, moduleFile).channel;
1883
+ }
1884
+ function telemetryChannel(env = process.env, moduleFile = thisModuleFile() ?? null) {
1885
+ const explicit = env.KIBI_TELEMETRY_CHANNEL?.trim();
1886
+ if (explicit)
1887
+ return {
1888
+ channel: telemetryCategory(explicit) ?? "release",
1889
+ source: "KIBI_TELEMETRY_CHANNEL"
1890
+ };
1891
+ if (!moduleFile)
1892
+ return { channel: "release", source: "installed" };
1893
+ let fromCheckout = checkoutCache.get(moduleFile);
1894
+ if (fromCheckout === undefined) {
1895
+ fromCheckout = insideKibiCheckout(moduleFile);
1896
+ checkoutCache.set(moduleFile, fromCheckout);
1897
+ }
1898
+ return fromCheckout ? { channel: "source", source: "checkout" } : { channel: "release", source: "installed" };
1899
+ }
1900
+ function telemetryDirectory(env = process.env) {
1901
+ const explicit = env.KIBI_TELEMETRY_DIR?.trim();
1902
+ if (explicit)
1903
+ return explicit;
1904
+ if (process.platform === "win32" && env.LOCALAPPDATA) {
1905
+ return path5.join(env.LOCALAPPDATA, "kibi");
1906
+ }
1907
+ const stateHome = env.XDG_STATE_HOME?.trim();
1908
+ return path5.join(stateHome || path5.join(env.HOME || os.homedir(), ".local", "state"), "kibi");
1909
+ }
1910
+ function readTelemetryConfig(env = process.env) {
1911
+ try {
1912
+ const parsed = JSON.parse(fs4.readFileSync(path5.join(telemetryDirectory(env), CONFIG_FILE), "utf8"));
1913
+ return typeof parsed === "object" && parsed !== null ? parsed : {};
1914
+ } catch {
1915
+ return {};
1916
+ }
1917
+ }
1918
+ function writeTelemetryConfig(config, env) {
1919
+ const directory = telemetryDirectory(env);
1920
+ fs4.mkdirSync(directory, { recursive: true });
1921
+ fs4.writeFileSync(path5.join(directory, CONFIG_FILE), `${JSON.stringify(config, null, 2)}
1922
+ `, "utf8");
1923
+ }
1924
+ function telemetryStatus(env = process.env) {
1925
+ const directory = telemetryDirectory(env);
1926
+ const { channel, source } = telemetryChannel(env);
1927
+ const status = (enabled, reason) => ({
1928
+ enabled,
1929
+ reason,
1930
+ directory,
1931
+ channel,
1932
+ channelSource: source
1933
+ });
1934
+ const explicit = flagValue(env.KIBI_TELEMETRY);
1935
+ if (explicit === false)
1936
+ return status(false, "KIBI_TELEMETRY=0");
1937
+ if (flagValue(env.DO_NOT_TRACK) === true) {
1938
+ return status(false, "DO_NOT_TRACK=1");
1939
+ }
1940
+ const preference = readTelemetryConfig(env).preference;
1941
+ if (preference === "disabled") {
1942
+ return status(false, "disabled with `kibi telemetry disable`");
1943
+ }
1944
+ if (telemetryKey(env) === "")
1945
+ return status(false, "no telemetry key");
1946
+ if (explicit === true)
1947
+ return status(true, "KIBI_TELEMETRY=1");
1948
+ const processEnv = process.env;
1949
+ const ciName = automationName(env, CI_ENV_KEYS) ?? automationName(processEnv, CI_ENV_KEYS);
1950
+ if (ciName)
1951
+ return status(false, `CI environment (${ciName})`);
1952
+ const testName = automationName(env, TEST_ENV_KEYS) ?? automationName(processEnv, TEST_ENV_KEYS);
1953
+ if (testName)
1954
+ return status(false, `test environment (${testName})`);
1955
+ if (sandboxedHome(env)) {
1956
+ return status(false, "sandboxed home in the temp directory (harness run)");
1957
+ }
1958
+ if (preference === "enabled") {
1959
+ return status(true, "enabled with `kibi telemetry enable`");
1960
+ }
1961
+ return status(true, "on by default; anonymous, opt-out");
1962
+ }
1963
+ function telemetryEnabled(env = process.env) {
1964
+ return telemetryStatus(env).enabled;
1965
+ }
1966
+ function telemetryKey(env) {
1967
+ const override = env.KIBI_TELEMETRY_KEY;
1968
+ return (override === undefined ? DEFAULT_TELEMETRY_KEY : override).trim();
1969
+ }
1970
+ function telemetryHost(env) {
1971
+ return (env.KIBI_TELEMETRY_HOST?.trim() || DEFAULT_TELEMETRY_HOST).replace(/\/+$/, "");
1972
+ }
1973
+ function installId(env) {
1974
+ const config = readTelemetryConfig(env);
1975
+ if (typeof config.installId === "string" && config.installId !== "") {
1976
+ return config.installId;
1977
+ }
1978
+ const created = randomUUID2();
1979
+ try {
1980
+ writeTelemetryConfig({ ...config, installId: created }, env);
1981
+ } catch {}
1982
+ return created;
1983
+ }
1984
+ function hashTelemetryId(value) {
1985
+ return createHash3("sha256").update("kibi-telemetry\x00").update(value).digest("hex").slice(0, 16);
1986
+ }
1987
+ function runtimeName() {
1988
+ return typeof globalThis.Bun === "undefined" ? "node" : "bun";
1989
+ }
1990
+ function sessionMarkersPath(env) {
1991
+ return path5.join(telemetryDirectory(env), SESSIONS_FILE);
1992
+ }
1993
+ function readSessionMarkers(env) {
1994
+ try {
1995
+ const parsed = JSON.parse(fs4.readFileSync(sessionMarkersPath(env), "utf8"));
1996
+ return parsed !== null && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : {};
1997
+ } catch {
1998
+ return {};
1999
+ }
2000
+ }
2001
+ function workspaceKey(workspaceRoot) {
2002
+ return hashTelemetryId(`workspace\x00${path5.resolve(workspaceRoot)}`);
2003
+ }
2004
+ function rememberHookSession(workspaceRoot, sessionId, env = process.env) {
2005
+ if (!sessionId.trim() || !telemetryEnabled(env))
2006
+ return;
2007
+ const now = Date.now();
2008
+ const markers = readSessionMarkers(env);
2009
+ markers[workspaceKey(workspaceRoot)] = {
2010
+ session: hashTelemetryId(sessionId.trim()),
2011
+ at: now
2012
+ };
2013
+ const kept = Object.entries(markers).filter(([, marker]) => typeof marker?.at === "number" && now - marker.at <= SESSION_MARKER_MAX_AGE_MS).sort(([, left], [, right]) => right.at - left.at).slice(0, MAX_SESSION_MARKERS);
2014
+ try {
2015
+ const file = sessionMarkersPath(env);
2016
+ fs4.mkdirSync(path5.dirname(file), { recursive: true });
2017
+ const temporary = `${file}.${process.pid}.${randomUUID2()}.tmp`;
2018
+ fs4.writeFileSync(temporary, JSON.stringify(Object.fromEntries(kept)));
2019
+ fs4.renameSync(temporary, file);
2020
+ } catch {}
2021
+ }
2022
+ function workspaceSessionHash(startDirectory, env = process.env) {
2023
+ const markers = readSessionMarkers(env);
2024
+ const now = Date.now();
2025
+ let current = path5.resolve(startDirectory);
2026
+ for (;; ) {
2027
+ const marker = markers[workspaceKey(current)];
2028
+ if (marker && typeof marker.session === "string" && typeof marker.at === "number" && now - marker.at <= SESSION_MARKER_MAX_AGE_MS) {
2029
+ return marker.session;
2030
+ }
2031
+ const parent = path5.dirname(current);
2032
+ if (parent === current)
2033
+ return;
2034
+ current = parent;
2035
+ }
2036
+ }
2037
+ function sessionProperty(source, env) {
2038
+ const sessionId = source.sessionId?.trim() || detectTelemetrySession(env);
2039
+ if (sessionId)
2040
+ return hashTelemetryId(sessionId);
2041
+ return source.workspaceRoot ? workspaceSessionHash(source.workspaceRoot, env) ?? null : null;
2042
+ }
2043
+ function baseProperties(source, env) {
2044
+ return {
2045
+ distinct_id: installId(env),
2046
+ $process_person_profile: false,
2047
+ $geoip_disable: true,
2048
+ $lib: "kibi",
2049
+ package: source.packageName,
2050
+ package_version: source.packageVersion ?? null,
2051
+ host: source.host?.trim() && hostKey(source.host) || detectTelemetryHost(env) || "unknown",
2052
+ remote: remoteEnvironment(env),
2053
+ channel: detectTelemetryChannel(env),
2054
+ session: sessionProperty(source, env),
2055
+ os: process.platform,
2056
+ arch: process.arch,
2057
+ runtime: runtimeName(),
2058
+ runtime_version: process.versions.node.split(".")[0] ?? null
2059
+ };
2060
+ }
2061
+ function queuePath(env) {
2062
+ return path5.join(telemetryDirectory(env), QUEUE_FILE);
2063
+ }
2064
+ function appendEvent(event, env) {
2065
+ try {
2066
+ const file = queuePath(env);
2067
+ fs4.mkdirSync(path5.dirname(file), { recursive: true });
2068
+ try {
2069
+ if (fs4.statSync(file).size > MAX_QUEUE_BYTES)
2070
+ return;
2071
+ } catch {}
2072
+ fs4.appendFileSync(file, `${JSON.stringify(event)}
2073
+ `, "utf8");
2074
+ } catch {}
2075
+ }
2076
+ function telemetryCategory(value) {
2077
+ const trimmed = value?.trim();
2078
+ if (!trimmed)
2079
+ return null;
2080
+ return /^[A-Za-z0-9_.:+-]{1,160}$/.test(trimmed) ? trimmed.toLowerCase() : "unclassified";
2081
+ }
2082
+ function recordHookTelemetry(source, hook, env = process.env) {
2083
+ if (!telemetryEnabled(env))
2084
+ return;
2085
+ appendEvent({
2086
+ event: "kibi_hook",
2087
+ timestamp: new Date().toISOString(),
2088
+ properties: {
2089
+ ...baseProperties(source, env),
2090
+ hook_event: hook.hookEvent,
2091
+ hook_action: hook.hookAction,
2092
+ path_kind: hook.pathKind ?? null,
2093
+ kb_operation: hook.kbOperation ?? null,
2094
+ kb_used_before: hook.kbUsedBefore ?? null,
2095
+ requirement_count: hook.requirementCount ?? null
2096
+ }
2097
+ }, env);
2098
+ }
2099
+ function parseQueued(text, now) {
2100
+ const events = [];
2101
+ for (const line of text.split(`
2102
+ `)) {
2103
+ if (line.trim() === "")
2104
+ continue;
2105
+ try {
2106
+ const event = JSON.parse(line);
2107
+ const age = now - Date.parse(event.timestamp);
2108
+ if (Number.isFinite(age) && age <= MAX_EVENT_AGE_MS)
2109
+ events.push(event);
2110
+ } catch {}
2111
+ }
2112
+ return events;
2113
+ }
2114
+ function flushDue(file, now) {
2115
+ let text;
2116
+ try {
2117
+ text = fs4.readFileSync(file, "utf8");
2118
+ } catch {
2119
+ return false;
2120
+ }
2121
+ const lines = text.split(`
2122
+ `).filter((line) => line.trim() !== "");
2123
+ if (lines.length === 0)
2124
+ return false;
2125
+ if (lines.length >= FLUSH_BATCH_EVENTS)
2126
+ return true;
2127
+ try {
2128
+ const first = JSON.parse(lines[0] ?? "");
2129
+ return now - Date.parse(first.timestamp) >= FLUSH_MAX_AGE_MS;
2130
+ } catch {
2131
+ return true;
2132
+ }
2133
+ }
2134
+ function claimBatches(directory, now) {
2135
+ const claimed = [];
2136
+ let names;
2137
+ try {
2138
+ names = fs4.readdirSync(directory);
2139
+ } catch {
2140
+ return claimed;
2141
+ }
2142
+ for (const name of names) {
2143
+ if (!name.endsWith(SENDING_SUFFIX))
2144
+ continue;
2145
+ const file = path5.join(directory, name);
2146
+ try {
2147
+ if (now - fs4.statSync(file).mtimeMs < STALE_CLAIM_MS)
2148
+ continue;
2149
+ const reclaimed = `${file}.${randomUUID2()}${SENDING_SUFFIX}`;
2150
+ fs4.renameSync(file, reclaimed);
2151
+ claimed.push(reclaimed);
2152
+ } catch {}
2153
+ }
2154
+ const queue = path5.join(directory, QUEUE_FILE);
2155
+ const target = path5.join(directory, `${QUEUE_FILE}.${process.pid}.${randomUUID2()}${SENDING_SUFFIX}`);
2156
+ try {
2157
+ fs4.renameSync(queue, target);
2158
+ claimed.push(target);
2159
+ } catch {}
2160
+ return claimed;
2161
+ }
2162
+ async function flushTelemetry(options = {}, env = process.env) {
2163
+ if (!telemetryEnabled(env))
2164
+ return 0;
2165
+ const directory = telemetryDirectory(env);
2166
+ const now = Date.now();
2167
+ if (!options.force && !flushDue(queuePath(env), now))
2168
+ return 0;
2169
+ const claimed = claimBatches(directory, now);
2170
+ if (claimed.length === 0)
2171
+ return 0;
2172
+ const events = [];
2173
+ for (const file of claimed) {
2174
+ try {
2175
+ events.push(...parseQueued(fs4.readFileSync(file, "utf8"), now));
2176
+ } catch {}
2177
+ }
2178
+ const release = () => {
2179
+ for (const file of claimed)
2180
+ fs4.rmSync(file, { force: true });
2181
+ };
2182
+ if (events.length === 0) {
2183
+ release();
2184
+ return 0;
2185
+ }
2186
+ const fetchImpl = options.fetchImpl ?? globalThis.fetch;
2187
+ const controller = new AbortController;
2188
+ const timer = setTimeout(() => controller.abort(), options.timeoutMs ?? DEFAULT_FLUSH_TIMEOUT_MS);
2189
+ timer.unref?.();
2190
+ try {
2191
+ const response = await fetchImpl(`${telemetryHost(env)}/batch/`, {
2192
+ method: "POST",
2193
+ headers: { "content-type": "application/json" },
2194
+ body: JSON.stringify({
2195
+ api_key: telemetryKey(env),
2196
+ historical_migration: false,
2197
+ batch: events
2198
+ }),
2199
+ signal: controller.signal
2200
+ });
2201
+ if (!response.ok && response.status >= 500)
2202
+ throw new Error("retry");
2203
+ release();
2204
+ return response.ok ? events.length : 0;
2205
+ } catch {
2206
+ release();
2207
+ for (const event of events)
2208
+ appendEvent(event, env);
2209
+ return 0;
2210
+ } finally {
2211
+ clearTimeout(timer);
2212
+ }
2213
+ }
2214
+
2215
+ // ../agent-core/dist/hook-usage-log.js
2216
+ var HOOK_ACTION_EDITED = "edited";
2217
+ var HOOK_ACTION_KB_USAGE = "kb_usage";
2218
+ function hookTelemetryEnabled(env = process.env) {
2219
+ const value = env.KIBI_DIAGNOSTIC_MODE?.trim().toLowerCase();
2220
+ return value === "1" || value === "true";
2221
+ }
2222
+ function hookTracesWanted(env = process.env) {
2223
+ return hookTelemetryEnabled(env) || telemetryEnabled(env);
2224
+ }
2225
+ function adapterPackage(host) {
2226
+ return host === "claude-code" ? "kibi-claude" : `kibi-${host}`;
2227
+ }
2228
+ function appendHookUsage(row, env = process.env) {
2229
+ if (row.trace.action === undefined)
2230
+ return;
2231
+ recordHookTelemetry({
2232
+ packageName: row.packageName ?? adapterPackage(row.host),
2233
+ packageVersion: row.packageVersion,
2234
+ host: row.host,
2235
+ sessionId: row.sessionId
2236
+ }, {
2237
+ hookEvent: row.event,
2238
+ hookAction: row.trace.action,
2239
+ pathKind: row.trace.pathKind,
2240
+ kbOperation: row.trace.kbOperation,
2241
+ kbUsedBefore: row.trace.kbUsedBefore,
2242
+ requirementCount: row.trace.requirementIds?.length
2243
+ }, env);
2244
+ if (!hookTelemetryEnabled(env))
2245
+ return;
2246
+ const finishedAt = new Date;
2247
+ const { trace } = row;
2248
+ const record = {
2249
+ timestamp: finishedAt.toISOString(),
2250
+ request_id: `hook-${randomUUID3()}`,
2251
+ tool: `hook_${row.event}`,
2252
+ interface: "hook",
2253
+ host: row.host,
2254
+ package_version: row.packageVersion ?? null,
2255
+ workspace_root: row.workspaceRoot,
2256
+ session_id: row.sessionId ?? null,
2257
+ hook_event: row.event,
2258
+ host_tool: row.hostTool ?? null,
2259
+ hook_action: trace.action,
2260
+ path: trace.path ?? null,
2261
+ path_kind: trace.pathKind ?? null,
2262
+ requirement_ids: trace.requirementIds ?? [],
2263
+ kb_operation: trace.kbOperation ?? null,
2264
+ kb_used_before: trace.kbUsedBefore ?? null,
2265
+ status: "success",
2266
+ duration_ms: finishedAt.getTime() - row.startedAt.getTime()
2267
+ };
2268
+ try {
2269
+ const logPath = path6.join(row.workspaceRoot, ".kb", "usage.log");
2270
+ fs5.mkdirSync(path6.dirname(logPath), { recursive: true });
2271
+ fs5.appendFileSync(logPath, `${JSON.stringify(record)}
2272
+ `, "utf8");
2273
+ } catch {}
2274
+ }
2275
+ function kbUsageTrace(toolName, toolInput) {
2276
+ const kbOperation = hostKbOperation(toolName, toolInput);
2277
+ return kbOperation ? { action: HOOK_ACTION_KB_USAGE, kbOperation } : undefined;
2278
+ }
2279
+ function editTraces(relativePaths, requirementIdsFor) {
2280
+ const traces = [];
2281
+ for (const relativePath of new Set(relativePaths)) {
2282
+ const pathKind = classifyPath(relativePath);
2283
+ if (pathKind === "other")
2284
+ continue;
2285
+ traces.push({
2286
+ action: HOOK_ACTION_EDITED,
2287
+ path: relativePath,
2288
+ pathKind,
2289
+ requirementIds: pathKind === "kb" ? [] : requirementIdsFor(relativePath)
2290
+ });
2291
+ }
2292
+ return traces;
2293
+ }
2294
+ function appendHookUsageRows(row, traces, env = process.env) {
2295
+ if (!hookTracesWanted(env))
2296
+ return;
2297
+ for (const trace of traces)
2298
+ appendHookUsage({ ...row, trace }, env);
2299
+ }
2300
+ var TURN_END_EVENTS = new Set(["stop", "sessionend"]);
2301
+ async function finishHookTelemetry(input, env = process.env) {
2302
+ try {
2303
+ if (input.workspaceRoot && input.sessionId) {
2304
+ rememberHookSession(input.workspaceRoot, input.sessionId, env);
2305
+ }
2306
+ const turnEnd = TURN_END_EVENTS.has(input.event.toLowerCase().replace(/[^a-z]/g, ""));
2307
+ await flushTelemetry({ force: turnEnd }, env);
2308
+ } catch {}
2309
+ }
2310
+ function readPackageVersion(packageJsonPath) {
2311
+ try {
2312
+ const parsed = JSON.parse(fs5.readFileSync(packageJsonPath, "utf8"));
2313
+ if (typeof parsed === "object" && parsed !== null && "version" in parsed && typeof parsed.version === "string") {
2314
+ return parsed.version;
2315
+ }
2316
+ } catch {}
2317
+ return null;
2318
+ }
2319
+
2320
+ // src/kb-mcp-tools.ts
2321
+ function resolveKibiInterface2(mcpState, workspaceTrusted) {
2322
+ return resolveKibiInterface(mcpState, workspaceTrusted);
2323
+ }
2324
+ function extractKbMcpToolCall2(toolName, toolInput) {
2325
+ return extractKbMcpToolCall(toolName, toolInput);
2326
+ }
2327
+
2328
+ // src/path-policy.ts
2329
+ function isFreshnessLane(lane) {
2330
+ return lane === "requirements" || lane === "scenarios" || lane === "tests" || lane === "facts" || lane === "adr" || lane === "flags" || lane === "events" || lane === "symbols.yaml" || lane === "symbol-coordinates.yaml";
2331
+ }
2332
+ function extractExplicitPathFields2(input) {
2333
+ return extractExplicitPathFields(input);
2334
+ }
2335
+ function isDirectKbPath(candidate) {
2336
+ return isKbPath(candidate);
2337
+ }
2338
+ function isMeaningfulTrackedPath2(candidate) {
2339
+ return isMeaningfulTrackedPath(candidate);
2340
+ }
2341
+ function isKbFreshnessRelevantPath(candidate) {
2342
+ const segments = normalizeWorkspacePath(candidate).split("/").filter(Boolean);
2343
+ if (segments[0] === ".kb" && isFreshnessLane(segments[1]))
2344
+ return true;
2345
+ if (segments.includes("documentation"))
2346
+ return true;
2347
+ return segments[0] === "packages" && segments[1] === "core" && segments[2] === "src";
2348
+ }
2349
+ function isSourceImpactRelevantPath2(candidate) {
2350
+ return isSourceImpactRelevantPath(candidate);
2351
+ }
2352
+ function isDocumentationTrackedPath2(candidate) {
2353
+ return isDocumentationTrackedPath(candidate);
2354
+ }
2355
+ function toRepoRelativePath(candidate, cwd) {
2356
+ const normalized = normalizeWorkspacePath(candidate);
2357
+ if (!cwd)
2358
+ return normalized;
2359
+ const cwdPrefix = `${normalizeWorkspacePath(cwd)}/`;
2360
+ return normalized.startsWith(cwdPrefix) ? normalized.slice(cwdPrefix.length) : normalized;
2361
+ }
2362
+
2363
+ // src/messages.ts
2364
+ var BOOTSTRAP_REMINDER = "Kibi config was not found at the Cursor workspace root. Use visible Kibi MCP tools or a trusted project-local CLI JSON route with --input (peer surfaces) to bootstrap project memory; if neither interface is available, tell the operator. Do not read or edit `.kb/` files directly. Query before mutate, run kb_upsert sequentially, and run kb_check before completion.";
2365
+ var DIRECT_KB_EDIT_WARNING = "Do not read or edit `.kb/` files directly. Use visible Kibi MCP tools or trusted project-local CLI JSON routes with --input (peer surfaces). Query before mutate, run kb_upsert sequentially, and run kb_check before completion.";
2366
+ function interfaceAdvisory(mcpState, workspaceTrusted) {
2367
+ const selectedInterface = resolveKibiInterface2(mcpState, workspaceTrusted);
2368
+ switch (selectedInterface) {
2369
+ case "mcp":
2370
+ return;
2371
+ case "cli":
2372
+ return "Kibi MCP has not been observed in this session. In this explicitly trusted workspace, the project-local CLI is an advisory fallback: use npx --no-install kibi or bunx --no-install kibi. Do not use global or installing runners.";
2373
+ case "setup":
2374
+ return "Kibi MCP has not been observed in this session and workspace trust is unknown. Do not probe or execute a CLI fallback; ask the operator to enable MCP or explicitly approve the trusted project-local CLI workflow.";
2375
+ }
2376
+ }
2377
+ function stopFollowupMessage(state) {
2378
+ if (state.kbMutationTools.length > 0) {
2379
+ const tools = [...new Set(state.kbMutationTools)];
2380
+ return `Kibi KB updated (${tools.join(", ")}).`;
2381
+ }
2382
+ const sourceImpactPaths = state.dirtyPaths.filter(isSourceImpactRelevantPath2);
2383
+ const uncheckedSourcePaths = sourceImpactPaths.filter((sourcePath) => !state.impactCheckedPaths.includes(sourcePath));
2384
+ const freshnessPaths = state.dirtyPaths.filter(isKbFreshnessRelevantPath);
2385
+ if (uncheckedSourcePaths.length > 0) {
2386
+ return impactCheckFollowup(uncheckedSourcePaths);
2387
+ }
2388
+ if (freshnessPaths.length === 0 || state.kbCheckRun) {
2389
+ return;
2390
+ }
2391
+ const fileCount = freshnessPaths.length;
2392
+ const noun = fileCount === 1 ? "file" : "files";
2393
+ return `Kibi: sync or record no-impact after ${fileCount} edited ${noun}.`;
2394
+ }
2395
+ function impactCheckFollowup(sourcePaths) {
2396
+ const fileCount = sourcePaths.length;
2397
+ const noun = fileCount === 1 ? "file" : "files";
2398
+ const sourceFiles = JSON.stringify(sourcePaths.slice(0, 10));
2399
+ return [
2400
+ `Kibi: run impact-enabled kb_check after ${fileCount} edited source ${noun}.`,
2401
+ `Use kb_check({sourceFiles:${sourceFiles}, includeImpactDiagnostics:true, includeWorkingTreeDiff:true}).`,
2402
+ "Review symbol granularity and semantic review of linked requirements/tests before stopping."
2403
+ ].join(`
2404
+ `);
2405
+ }
2406
+
2407
+ // src/guidance.ts
2408
+ var PRE_EDIT_FOLLOWUP = "Kibi pre-edit guidance: read those requirements with kb_query before changing behavior here; if the change contradicts one, resolve that in Kibi before writing the edit.";
2409
+ function withAdvisory(context, guidance) {
2410
+ const advisory = interfaceAdvisory(context.mcpState, context.workspaceTrusted);
2411
+ return (advisory ? [advisory, ...guidance] : guidance).join(`
2412
+ `);
2413
+ }
2414
+ function readGuidance(filePath, context) {
2415
+ if (!context.hasKibi) {
2416
+ return;
2417
+ }
2418
+ const relativePath = toRepoRelativePath(filePath, context.cwd);
2419
+ if (!isMeaningfulTrackedPath2(relativePath)) {
2420
+ return;
2421
+ }
2422
+ if (context.knowledge) {
2423
+ return withAdvisory(context, [context.knowledge]);
2424
+ }
2425
+ const guidance = [
2426
+ "Kibi read guidance: before changing this file, discover linked knowledge through the selected MCP or CLI JSON route.",
2427
+ `Start with kb_search, then kb_query with sourceFile="${relativePath}".`,
2428
+ "Prefer Kibi facts and requirements over long inline comments for durable knowledge."
2429
+ ];
2430
+ return withAdvisory(context, guidance);
2431
+ }
2432
+ function preEditGuidance(filePath, context) {
2433
+ if (!context.hasKibi) {
2434
+ return;
2435
+ }
2436
+ const relativePath = toRepoRelativePath(filePath, context.cwd);
2437
+ if (!isMeaningfulTrackedPath2(relativePath)) {
2438
+ return;
2439
+ }
2440
+ if (isDocumentationTrackedPath2(relativePath)) {
2441
+ return;
2442
+ }
2443
+ if (context.knowledge) {
2444
+ return withAdvisory(context, [context.knowledge, PRE_EDIT_FOLLOWUP]);
2445
+ }
2446
+ const linkedRequirements = context.linkedRequirementIds ?? [];
2447
+ const guidance = linkedRequirements.length > 0 ? [
2448
+ `Kibi pre-edit guidance: ${relativePath} already implements ${linkedRequirements.join(", ")}.`,
2449
+ `Read those requirements with kb_query before changing behavior here, and widen with kb_query({sourceFile:"${relativePath}"}) for scenarios and tests.`,
2450
+ "If the change contradicts an existing requirement, resolve that in Kibi before writing the edit."
2451
+ ] : [
2452
+ `Kibi pre-edit guidance: ${relativePath} has no linked requirement in the symbol manifest.`,
2453
+ `Discover existing constraints with kb_search, then kb_query({sourceFile:"${relativePath}"}), before changing behavior here.`,
2454
+ "Record the requirement this change serves rather than leaving the symbol unowned."
2455
+ ];
2456
+ return withAdvisory(context, guidance);
2457
+ }
2458
+ function writeGuidance(filePath, context) {
2459
+ if (!context.hasKibi) {
2460
+ return;
2461
+ }
2462
+ const relativePath = toRepoRelativePath(filePath, context.cwd);
2463
+ if (!isMeaningfulTrackedPath2(relativePath)) {
2464
+ return;
2465
+ }
2466
+ if (isDocumentationTrackedPath2(relativePath)) {
2467
+ const guidance = [
2468
+ "Kibi write guidance: keep REQ, SCEN, and TEST artifacts separate.",
2469
+ "Query before mutate. Update KB entities through kb_upsert sequentially. Run kb_check before completion.",
2470
+ "Do not read or edit `.kb/` files directly."
2471
+ ];
2472
+ return withAdvisory(context, guidance);
2473
+ }
2474
+ const guidance = [
2475
+ `Kibi impact review: run kb_check({sourceFiles:["${relativePath}"], includeImpactDiagnostics:true, includeWorkingTreeDiff:true}) while this edit is fresh.`,
2476
+ "Check symbol granularity and whether linked requirements, scenarios, and tests still cover the changed behavior.",
2477
+ "Prefer symbol manifest + executable_for or // implements REQ-<area>-<behavior> for traceability."
2478
+ ];
2479
+ return withAdvisory(context, guidance);
2480
+ }
2481
+
2482
+ // src/hook-input.ts
2483
+ function isRecord4(value) {
2484
+ return typeof value === "object" && value !== null && !Array.isArray(value);
2485
+ }
2486
+ function readString2(record, keys) {
2487
+ for (const key of keys) {
2488
+ const value = record[key];
2489
+ if (typeof value === "string") {
2490
+ return value;
2491
+ }
2492
+ }
2493
+ return;
2494
+ }
2495
+ function readStringArray2(record, keys) {
2496
+ for (const key of keys) {
2497
+ const value = record[key];
2498
+ if (!Array.isArray(value)) {
2499
+ continue;
2500
+ }
2501
+ const strings = value.filter((item) => typeof item === "string" && item.length > 0);
2502
+ if (strings.length > 0) {
2503
+ return strings;
2504
+ }
2505
+ }
2506
+ return;
2507
+ }
2508
+ function normalizeEventName(event) {
2509
+ const trimmed = event.trim();
2510
+ return trimmed.charAt(0).toLowerCase() + trimmed.slice(1);
2511
+ }
2512
+ function parseStopStatus(value) {
2513
+ if (value === "completed" || value === "aborted" || value === "error") {
2514
+ return value;
2515
+ }
2516
+ return;
2517
+ }
2518
+ function parseHookInput(input) {
2519
+ if (!isRecord4(input)) {
2520
+ return { event: "" };
2521
+ }
2522
+ const rawEvent = readString2(input, [
2523
+ "event",
2524
+ "hook_event",
2525
+ "hookEvent",
2526
+ "hook_event_name",
2527
+ "name"
2528
+ ]) ?? "";
2529
+ const event = normalizeEventName(rawEvent);
2530
+ const cwd = readString2(input, [
2531
+ "cwd",
2532
+ "current_working_directory",
2533
+ "workspace"
2534
+ ]);
2535
+ const workspaceRoots = readStringArray2(input, [
2536
+ "workspace_roots",
2537
+ "workspaceRoots"
2538
+ ]);
2539
+ const toolName = readString2(input, ["toolName", "tool_name", "tool"]);
2540
+ const toolInput = input.toolInput ?? input.tool_input ?? input.input;
2541
+ const conversationId = readString2(input, [
2542
+ "conversation_id",
2543
+ "conversationId"
2544
+ ]);
2545
+ const filePath = readString2(input, ["file_path", "filePath", "path"]);
2546
+ const status = parseStopStatus(readString2(input, ["status"]));
2547
+ const prompt = readString2(input, ["prompt"]);
2548
+ const parsed = { event };
2549
+ if (cwd !== undefined) {
2550
+ parsed.cwd = cwd;
2551
+ }
2552
+ if (workspaceRoots !== undefined) {
2553
+ parsed.workspaceRoots = workspaceRoots;
2554
+ }
2555
+ if (toolName !== undefined) {
2556
+ parsed.toolName = toolName;
2557
+ }
2558
+ if (toolInput !== undefined) {
2559
+ parsed.toolInput = toolInput;
2560
+ }
2561
+ if (conversationId !== undefined) {
2562
+ parsed.conversationId = conversationId;
2563
+ }
2564
+ if (filePath !== undefined) {
2565
+ parsed.filePath = filePath;
2566
+ }
2567
+ if (prompt !== undefined) {
2568
+ parsed.prompt = prompt;
2569
+ }
2570
+ if (status !== undefined) {
2571
+ parsed.status = status;
2572
+ }
2573
+ return parsed;
2574
+ }
2575
+ async function readStdin() {
2576
+ const chunks = [];
2577
+ for await (const chunk of process.stdin) {
2578
+ chunks.push(Buffer.from(chunk));
2579
+ }
2580
+ return Buffer.concat(chunks).toString("utf8");
2581
+ }
2582
+ function parseStdinJson(rawInput) {
2583
+ const trimmed = rawInput.trim();
2584
+ if (trimmed.length === 0) {
2585
+ return {};
2586
+ }
2587
+ return JSON.parse(trimmed);
2588
+ }
2589
+
2590
+ // src/hook-state-storage.ts
2591
+ import { randomUUID as randomUUID4 } from "node:crypto";
2592
+ import fs6 from "node:fs";
2593
+ import os2 from "node:os";
2594
+ import path7 from "node:path";
2595
+ var stateFileName = "hook-state.json";
2596
+ var lockFileName = "hook-state.lock";
2597
+ var maxDirtyPaths = 50;
2598
+ var maxGuidedPaths = 100;
2599
+ var maxKbMutationTools = 20;
2600
+ var lockRetryCount = 100;
2601
+ var lockRetryDelayMs = 5;
2602
+ var staleLockAgeMs = 30000;
2603
+ function statePath(pluginData) {
2604
+ return path7.join(pluginData, stateFileName);
2605
+ }
2606
+ function lockPath(pluginData) {
2607
+ return path7.join(pluginData, lockFileName);
2608
+ }
2609
+ function isRecord5(value) {
2610
+ return typeof value === "object" && value !== null && !Array.isArray(value);
2611
+ }
2612
+ function normalizePath(dirtyPath) {
2613
+ return dirtyPath.trim().replaceAll("\\", "/");
2614
+ }
2615
+ function sleepSync(milliseconds) {
2616
+ const buffer = new SharedArrayBuffer(4);
2617
+ const view = new Int32Array(buffer);
2618
+ Atomics.wait(view, 0, 0, milliseconds);
2619
+ }
2620
+ function uniqueTempPath(pluginData) {
2621
+ return path7.join(pluginData, `${stateFileName}.${process.pid}.${Date.now()}.${randomUUID4()}.tmp`);
2622
+ }
2623
+ function mergeStringPaths(existingPaths, nextPaths) {
2624
+ const merged = [...existingPaths, ...nextPaths].map(normalizePath).filter((dirtyPath) => dirtyPath.length > 0);
2625
+ return [...new Set(merged)].slice(-maxGuidedPaths);
2626
+ }
2627
+ function emptyHookState() {
2628
+ return {
2629
+ mcpState: "unknown",
2630
+ dirtyPaths: [],
2631
+ guidedReadPaths: [],
2632
+ guidedPreEditPaths: [],
2633
+ guidedWritePaths: [],
2634
+ kbMutationTools: [],
2635
+ kbCheckRun: false,
2636
+ impactCheckRun: false,
2637
+ impactCheckedPaths: [],
2638
+ planDelivered: false
2639
+ };
2640
+ }
2641
+ function acquireLock(pluginData) {
2642
+ const targetLockPath = lockPath(pluginData);
2643
+ for (let attempt = 0;attempt < lockRetryCount; attempt++) {
2644
+ try {
2645
+ return fs6.openSync(targetLockPath, "wx");
2646
+ } catch (error) {
2647
+ if (error instanceof Error && "code" in error && error.code === "EEXIST") {
2648
+ if (removeStaleLock(targetLockPath)) {
2649
+ continue;
2650
+ }
2651
+ sleepSync(lockRetryDelayMs);
2652
+ continue;
2653
+ }
2654
+ return;
2655
+ }
2656
+ }
2657
+ return;
2658
+ }
2659
+ function removeStaleLock(targetLockPath) {
2660
+ let stats;
2661
+ try {
2662
+ stats = fs6.statSync(targetLockPath);
2663
+ } catch {
2664
+ return false;
2665
+ }
2666
+ if (Date.now() - stats.mtimeMs < staleLockAgeMs) {
2667
+ return false;
2668
+ }
2669
+ try {
2670
+ fs6.unlinkSync(targetLockPath);
2671
+ return true;
2672
+ } catch {
2673
+ return false;
2674
+ }
2675
+ }
2676
+ function releaseLock(pluginData, fileDescriptor) {
2677
+ try {
2678
+ fs6.closeSync(fileDescriptor);
2679
+ } catch (error) {
2680
+ if (!(error instanceof Error)) {
2681
+ throw error;
2682
+ }
2683
+ }
2684
+ try {
2685
+ fs6.unlinkSync(lockPath(pluginData));
2686
+ } catch (error) {
2687
+ if (!(error instanceof Error)) {
2688
+ throw error;
2689
+ }
2690
+ }
2691
+ }
2692
+ function coerceHookState(value) {
2693
+ if (!isRecord5(value)) {
2694
+ return emptyHookState();
2695
+ }
2696
+ const strings = (candidate, normalize) => Array.isArray(candidate) ? candidate.filter((entry) => typeof entry === "string").map(normalize).filter((entry) => entry.length > 0) : [];
2697
+ const dirtyPaths = strings(value.dirtyPaths, normalizePath);
2698
+ const guidedReadPaths = strings(value.guidedReadPaths, normalizePath);
2699
+ const guidedPreEditPaths = strings(value.guidedPreEditPaths, normalizePath);
2700
+ const guidedWritePaths = strings(value.guidedWritePaths, normalizePath);
2701
+ const kbMutationTools = strings(value.kbMutationTools, (entry) => entry.trim());
2702
+ const impactCheckedPaths = strings(value.impactCheckedPaths, normalizePath);
2703
+ return {
2704
+ mcpState: value.mcpState === "observed" ? "observed" : "unknown",
2705
+ dirtyPaths: [...new Set(dirtyPaths)].slice(-maxDirtyPaths),
2706
+ guidedReadPaths: [...new Set(guidedReadPaths)].slice(-maxGuidedPaths),
2707
+ guidedPreEditPaths: [...new Set(guidedPreEditPaths)].slice(-maxGuidedPaths),
2708
+ guidedWritePaths: [...new Set(guidedWritePaths)].slice(-maxGuidedPaths),
2709
+ kbMutationTools: [...new Set(kbMutationTools)].slice(-maxKbMutationTools),
2710
+ kbCheckRun: value.kbCheckRun === true,
2711
+ impactCheckRun: value.impactCheckRun === true,
2712
+ impactCheckedPaths: [...new Set(impactCheckedPaths)].slice(-maxGuidedPaths),
2713
+ planDelivered: value.planDelivered === true
2714
+ };
2715
+ }
2716
+ function resolveStateDir(pluginData, conversationId) {
2717
+ if (pluginData) {
2718
+ return pluginData;
2719
+ }
2720
+ if (!conversationId) {
2721
+ return;
2722
+ }
2723
+ return path7.join(os2.tmpdir(), "kibi-cursor-hook-state", conversationId.replaceAll(/[^a-zA-Z0-9._-]/g, "_"));
2724
+ }
2725
+ function loadHookState(stateDir) {
2726
+ if (!stateDir) {
2727
+ return emptyHookState();
2728
+ }
2729
+ try {
2730
+ return coerceHookState(JSON.parse(fs6.readFileSync(statePath(stateDir), "utf8")));
2731
+ } catch {
2732
+ return emptyHookState();
2733
+ }
2734
+ }
2735
+ function saveHookState(stateDir, state) {
2736
+ if (!stateDir) {
2737
+ return;
2738
+ }
2739
+ fs6.mkdirSync(stateDir, { recursive: true });
2740
+ const boundedState = coerceHookState(state);
2741
+ const tempPath = uniqueTempPath(stateDir);
2742
+ fs6.writeFileSync(tempPath, `${JSON.stringify(boundedState)}
2743
+ `);
2744
+ fs6.renameSync(tempPath, statePath(stateDir));
2745
+ }
2746
+ function updateHookState(stateDir, updater) {
2747
+ const initialState = loadHookState(stateDir);
2748
+ if (!stateDir) {
2749
+ return updater(initialState);
2750
+ }
2751
+ fs6.mkdirSync(stateDir, { recursive: true });
2752
+ const lockFileDescriptor = acquireLock(stateDir);
2753
+ if (lockFileDescriptor === undefined) {
2754
+ return updater(initialState);
2755
+ }
2756
+ try {
2757
+ const lockedState = loadHookState(stateDir);
2758
+ const nextState = updater(lockedState);
2759
+ saveHookState(stateDir, nextState);
2760
+ return nextState;
2761
+ } catch {
2762
+ return updater(initialState);
2763
+ } finally {
2764
+ releaseLock(stateDir, lockFileDescriptor);
2765
+ }
2766
+ }
2767
+
2768
+ // src/hook-state.ts
2769
+ function addDirtyPaths(stateDir, dirtyPaths) {
2770
+ return updateHookState(stateDir, (state) => ({
2771
+ ...state,
2772
+ dirtyPaths: mergeStringPaths(state.dirtyPaths, dirtyPaths).slice(-maxDirtyPaths)
2773
+ }));
2774
+ }
2775
+ function rememberGuidedPath(stateDir, kind, guidedPath) {
2776
+ return updateHookState(stateDir, (state) => {
2777
+ const normalized = normalizePath(guidedPath);
2778
+ if (kind === "read") {
2779
+ return {
2780
+ ...state,
2781
+ guidedReadPaths: mergeStringPaths(state.guidedReadPaths, [normalized])
2782
+ };
2783
+ }
2784
+ if (kind === "pre-edit") {
2785
+ return {
2786
+ ...state,
2787
+ guidedPreEditPaths: mergeStringPaths(state.guidedPreEditPaths, [
2788
+ normalized
2789
+ ])
2790
+ };
2791
+ }
2792
+ return {
2793
+ ...state,
2794
+ guidedWritePaths: mergeStringPaths(state.guidedWritePaths, [normalized])
2795
+ };
2796
+ });
2797
+ }
2798
+ function hasGuidedPath(state, kind, guidedPath) {
2799
+ return guidedBucket(state, kind).includes(normalizePath(guidedPath));
2800
+ }
2801
+ function guidedBucket(state, kind) {
2802
+ if (kind === "read")
2803
+ return state.guidedReadPaths;
2804
+ if (kind === "pre-edit")
2805
+ return state.guidedPreEditPaths;
2806
+ return state.guidedWritePaths;
2807
+ }
2808
+ function recordPlanDelivered(stateDir) {
2809
+ return updateHookState(stateDir, (state) => state.planDelivered ? state : { ...state, planDelivered: true });
2810
+ }
2811
+ function recordKbMcpTool(stateDir, toolName, options = {}) {
2812
+ const normalized = toolName.trim();
2813
+ if (normalized.length === 0) {
2814
+ return loadHookState(stateDir);
2815
+ }
2816
+ return updateHookState(stateDir, (state) => {
2817
+ const observedState = { ...state, mcpState: "observed" };
2818
+ if (normalized === "kb_check") {
2819
+ return {
2820
+ ...observedState,
2821
+ kbCheckRun: true,
2822
+ impactCheckRun: state.impactCheckRun || options.impactCheckRun === true,
2823
+ impactCheckedPaths: options.impactCheckRun === true ? mergeStringPaths(state.impactCheckedPaths, options.sourceFiles ?? []) : state.impactCheckedPaths
2824
+ };
2825
+ }
2826
+ if (normalized === "kb_upsert" || normalized === "kb_delete") {
2827
+ return {
2828
+ ...observedState,
2829
+ kbMutationTools: mergeStringPaths(state.kbMutationTools, [
2830
+ normalized
2831
+ ]).slice(-maxKbMutationTools)
2832
+ };
2833
+ }
2834
+ return observedState;
2835
+ });
2836
+ }
2837
+ function clearSessionHookState(stateDir) {
2838
+ const clearedState = emptyHookState();
2839
+ if (!stateDir) {
2840
+ return clearedState;
2841
+ }
2842
+ return updateHookState(stateDir, () => clearedState);
2843
+ }
2844
+
2845
+ // src/source-linked-requirements.ts
2846
+ import path8 from "node:path";
2847
+ var CURSOR_INDEX_OPTIONS = {
2848
+ legacyLinksAsImplements: false,
2849
+ cacheFileName: "cursor-knowledge-index.json"
2850
+ };
2851
+ function toManifestPath(workspaceRoot, filePath) {
2852
+ const absolute = path8.isAbsolute(filePath) ? filePath : path8.resolve(workspaceRoot, filePath);
2853
+ return path8.relative(workspaceRoot, absolute).split(path8.sep).join("/");
2854
+ }
2855
+ function getSourceLinkedSymbols(workspaceRoot, filePath, cacheDir) {
2856
+ const relativePath = toManifestPath(workspaceRoot, filePath);
2857
+ const symbols = loadKnowledgeIndex(workspaceRoot, cacheDir, CURSOR_INDEX_OPTIONS).files[relativePath] ?? [];
2858
+ return { relativePath, symbols };
2859
+ }
2860
+
2861
+ // src/hook-runner.ts
2862
+ function knowledgeFor(workspaceRoot, linked, surface, focus) {
2863
+ if (implementedRequirementIds(linked.symbols).length === 0)
2864
+ return;
2865
+ return fileKnowledgeSnippet({
2866
+ relativePath: linked.relativePath,
2867
+ symbols: linked.symbols,
2868
+ surface,
2869
+ focus,
2870
+ summarize: createEntitySummarizer(workspaceRoot)
2871
+ });
2872
+ }
2873
+ function firmNotice(workspaceRoot, linked, focus, session, env) {
2874
+ if (!confirmFirmEdits(env))
2875
+ return;
2876
+ const summarize = createEntitySummarizer(workspaceRoot);
2877
+ const { ids, symbol } = firmRequirementsTouched({
2878
+ symbols: linked.symbols,
2879
+ focus,
2880
+ wholeFile: focus === undefined,
2881
+ summarize,
2882
+ confirmed: settledFirmRequirements(session)
2883
+ });
2884
+ if (ids.length === 0)
2885
+ return;
2886
+ settleFirmRequirements(session, ids);
2887
+ return firmEditNotice(symbol ? `${symbol.title} in ${linked.relativePath}` : linked.relativePath, ids.map(summarize));
2888
+ }
2889
+ function packageVersion() {
2890
+ return readPackageVersion(path9.join(path9.dirname(fileURLToPath2(import.meta.url)), "..", "package.json"));
2891
+ }
2892
+ function recordToolTelemetry(input, workspaceRoot, stateDir, startedAt, env) {
2893
+ if (!hookTracesWanted(env))
2894
+ return;
2895
+ const kbUsage = kbUsageTrace(input.toolName, input.toolInput);
2896
+ if (!kbUsage && !isKnownEditableTool(input.toolName))
2897
+ return;
2898
+ const traces = kbUsage ? [kbUsage] : editTraces(extractExplicitPathFields2(input.toolInput).map((candidate) => toRepoRelativePath(candidate, workspaceRoot)), (relativePath) => implementedRequirementIds(getSourceLinkedSymbols(workspaceRoot, relativePath, stateDir).symbols));
2899
+ appendHookUsageRows({
2900
+ host: "cursor",
2901
+ packageVersion: packageVersion(),
2902
+ workspaceRoot,
2903
+ event: "postToolUse",
2904
+ sessionId: input.conversationId,
2905
+ hostTool: input.toolName,
2906
+ startedAt
2907
+ }, traces, env);
2908
+ }
2909
+ var editableTools = new Set([
2910
+ "Edit",
2911
+ "MultiEdit",
2912
+ "Write",
2913
+ "StrReplace",
2914
+ "apply_patch",
2915
+ "EditNotebook"
2916
+ ]);
2917
+ var readTools = new Set(["Read", "TabRead"]);
2918
+ var planDeliveryTools = new Set(["CreatePlan", "create_plan", "createPlan"]);
2919
+ function emptyResult() {
2920
+ return {};
2921
+ }
2922
+ function hasKibiConfig(cwd) {
2923
+ if (!cwd) {
2924
+ return false;
2925
+ }
2926
+ return fs7.existsSync(path9.join(cwd, ".kb", "manifest.json"));
2927
+ }
2928
+ function kibiWorkspaceFor(directory) {
2929
+ if (!directory)
2930
+ return;
2931
+ let current = path9.resolve(directory);
2932
+ for (;; ) {
2933
+ if (fs7.existsSync(path9.join(current, ".kb", "manifest.json"))) {
2934
+ return current;
2935
+ }
2936
+ if (fs7.existsSync(path9.join(current, ".git")))
2937
+ return;
2938
+ const parent = path9.dirname(current);
2939
+ if (parent === current)
2940
+ return;
2941
+ current = parent;
2942
+ }
2943
+ }
2944
+ function isEditLikeTool(toolName) {
2945
+ return toolName === undefined || editableTools.has(toolName);
2946
+ }
2947
+ function isKnownEditableTool(toolName) {
2948
+ return toolName !== undefined && editableTools.has(toolName);
2949
+ }
2950
+ function isPlanDeliveryTool(toolName) {
2951
+ return toolName !== undefined && planDeliveryTools.has(toolName);
2952
+ }
2953
+ function isReadLikeTool(toolName) {
2954
+ return toolName === undefined || readTools.has(toolName);
2955
+ }
2956
+ function resolveWorkspaceRoot(input) {
2957
+ return input.workspaceRoots?.[0] ?? input.cwd ?? process.cwd();
2958
+ }
2959
+ function resolvePrimaryPath(input) {
2960
+ if (input.filePath) {
2961
+ return input.filePath;
2962
+ }
2963
+ const explicitPaths = extractExplicitPathFields2(input.toolInput);
2964
+ return explicitPaths[0];
2965
+ }
2966
+ async function runHook(rawInput, environment = {}) {
2967
+ const startedAt = new Date;
2968
+ const input = parseHookInput(rawInput);
2969
+ const pluginData = environment.pluginData ?? process.env.PLUGIN_DATA;
2970
+ const stateDir = resolveStateDir(pluginData, input.conversationId);
2971
+ const cwd = resolveWorkspaceRoot(input);
2972
+ const kibiReady = hasKibiConfig(cwd);
2973
+ const workspaceTrusted = environment.workspaceTrusted === true;
2974
+ const initialState = loadHookState(stateDir);
2975
+ const session = {
2976
+ stateDir,
2977
+ sessionId: input.conversationId
2978
+ };
2979
+ const trace = {};
2980
+ try {
2981
+ switch (input.event) {
2982
+ case "sessionStart":
2983
+ trace.action = "session_start";
2984
+ if (!kibiReady) {
2985
+ const advisory = interfaceAdvisory(initialState.mcpState, workspaceTrusted);
2986
+ return {
2987
+ additional_context: advisory ? `${BOOTSTRAP_REMINDER}
2988
+ ${advisory}` : BOOTSTRAP_REMINDER
2989
+ };
2990
+ }
2991
+ {
2992
+ const advisory = interfaceAdvisory(initialState.mcpState, workspaceTrusted);
2993
+ return advisory ? { additional_context: advisory } : emptyResult();
2994
+ }
2995
+ case "preToolUse": {
2996
+ const stamped = stampKibiWorkspace(input.toolName, input.toolInput, kibiWorkspaceFor(input.cwd ?? input.workspaceRoots?.[0]));
2997
+ if (stamped) {
2998
+ return { updated_input: stamped };
2999
+ }
3000
+ const explicitPaths = extractExplicitPathFields2(input.toolInput);
3001
+ const hasDirectKbEdit = isEditLikeTool(input.toolName) && explicitPaths.some(isDirectKbPath);
3002
+ if (hasDirectKbEdit) {
3003
+ trace.action = "kb_direct_note";
3004
+ trace.pathKind = "kb";
3005
+ return {
3006
+ permission: "allow",
3007
+ agent_message: DIRECT_KB_EDIT_WARNING
3008
+ };
3009
+ }
3010
+ const primaryPath = resolvePrimaryPath(input);
3011
+ if (!primaryPath || !kibiReady || !isKnownEditableTool(input.toolName)) {
3012
+ return emptyResult();
3013
+ }
3014
+ const relativePath = toRepoRelativePath(primaryPath, cwd);
3015
+ const state = loadHookState(stateDir);
3016
+ const linked = getSourceLinkedSymbols(cwd, primaryPath, stateDir);
3017
+ trace.pathKind = classifyPath(relativePath);
3018
+ trace.requirementIds = implementedRequirementIds(linked.symbols);
3019
+ const focus = editFocus(path9.resolve(cwd, primaryPath), input.toolInput);
3020
+ const ruleNote = takeRuleNote(session);
3021
+ const notice = firmNotice(cwd, linked, focus, session, environment.env ?? process.env);
3022
+ const guidance = hasGuidedPath(state, "pre-edit", relativePath) ? undefined : preEditGuidance(primaryPath, {
3023
+ cwd,
3024
+ hasKibi: kibiReady,
3025
+ mcpState: state.mcpState,
3026
+ workspaceTrusted,
3027
+ knowledge: knowledgeFor(cwd, linked, "edit", focus)
3028
+ });
3029
+ if (guidance) {
3030
+ rememberGuidedPath(stateDir, "pre-edit", relativePath);
3031
+ }
3032
+ const parts = [ruleNote, notice, guidance].filter((part) => part !== undefined);
3033
+ trace.action = parts.length > 0 ? "edit_snippet" : "edit_silent";
3034
+ return parts.length > 0 ? { permission: "allow", agent_message: parts.join(`
3035
+
3036
+ `) } : emptyResult();
3037
+ }
3038
+ case "beforeSubmitPrompt": {
3039
+ if (kibiReady && input.prompt) {
3040
+ const rule = recordRequest(session, input.prompt);
3041
+ if (rule !== undefined)
3042
+ holdRuleNote(session, rule);
3043
+ trace.action = rule === undefined ? "prompt_silent" : "prompt_rule_note";
3044
+ }
3045
+ return { continue: true };
3046
+ }
3047
+ case "beforeReadFile": {
3048
+ const primaryPath = resolvePrimaryPath(input);
3049
+ if (!primaryPath || !kibiReady) {
3050
+ return { permission: "allow" };
3051
+ }
3052
+ const relativePath = toRepoRelativePath(primaryPath, cwd);
3053
+ trace.pathKind = classifyPath(relativePath);
3054
+ trace.action = "read_silent";
3055
+ const state = loadHookState(stateDir);
3056
+ if (hasGuidedPath(state, "read", relativePath)) {
3057
+ return { permission: "allow" };
3058
+ }
3059
+ const guidance = readGuidance(primaryPath, {
3060
+ cwd,
3061
+ hasKibi: kibiReady,
3062
+ mcpState: state.mcpState,
3063
+ workspaceTrusted,
3064
+ knowledge: knowledgeFor(cwd, getSourceLinkedSymbols(cwd, primaryPath, stateDir), "read")
3065
+ });
3066
+ if (!guidance) {
3067
+ return { permission: "allow" };
3068
+ }
3069
+ rememberGuidedPath(stateDir, "read", relativePath);
3070
+ trace.action = "read_snippet";
3071
+ return {
3072
+ permission: "allow",
3073
+ agent_message: guidance
3074
+ };
3075
+ }
3076
+ case "postToolUse": {
3077
+ if (kibiReady) {
3078
+ try {
3079
+ recordToolTelemetry(input, cwd, stateDir, startedAt, environment.env);
3080
+ } catch {}
3081
+ }
3082
+ const kbToolCall = extractKbMcpToolCall2(input.toolName, input.toolInput);
3083
+ if (kbToolCall) {
3084
+ recordKbMcpTool(stateDir, kbToolCall.toolName, {
3085
+ impactCheckRun: kbToolCall.impactCheckRun,
3086
+ sourceFiles: kbToolCall.sourceFiles
3087
+ });
3088
+ }
3089
+ if (isPlanDeliveryTool(input.toolName)) {
3090
+ recordPlanDelivered(stateDir);
3091
+ }
3092
+ const explicitPaths = extractExplicitPathFields2(input.toolInput);
3093
+ if (isKnownEditableTool(input.toolName)) {
3094
+ const dirtyPaths = explicitPaths.map((candidate) => toRepoRelativePath(candidate, cwd)).filter((candidate) => isMeaningfulTrackedPath2(candidate) || isKbFreshnessRelevantPath(candidate));
3095
+ if (dirtyPaths.length > 0) {
3096
+ addDirtyPaths(stateDir, dirtyPaths);
3097
+ }
3098
+ if (dirtyPaths.some(isSourceImpactRelevantPath2)) {
3099
+ markDecisionFlag(session, "source-edited");
3100
+ }
3101
+ }
3102
+ if (kbToolCall?.toolName === "kb_upsert" || kbUsageTrace(input.toolName, input.toolInput)?.kbOperation === "kb_upsert") {
3103
+ markDecisionFlag(session, "kb-recorded");
3104
+ if (isRequirementUpsert(input.toolInput)) {
3105
+ markDecisionFlag(session, "requirement-recorded");
3106
+ }
3107
+ }
3108
+ const primaryPath = resolvePrimaryPath(input);
3109
+ if (!primaryPath || !kibiReady) {
3110
+ return emptyResult();
3111
+ }
3112
+ const relativePath = toRepoRelativePath(primaryPath, cwd);
3113
+ const state = loadHookState(stateDir);
3114
+ if (isReadLikeTool(input.toolName)) {
3115
+ if (hasGuidedPath(state, "read", relativePath)) {
3116
+ return emptyResult();
3117
+ }
3118
+ const guidance = readGuidance(primaryPath, {
3119
+ cwd,
3120
+ hasKibi: kibiReady,
3121
+ mcpState: state.mcpState,
3122
+ workspaceTrusted,
3123
+ knowledge: knowledgeFor(cwd, getSourceLinkedSymbols(cwd, primaryPath, stateDir), "read")
3124
+ });
3125
+ if (!guidance) {
3126
+ return emptyResult();
3127
+ }
3128
+ rememberGuidedPath(stateDir, "read", relativePath);
3129
+ return { additional_context: guidance };
3130
+ }
3131
+ if (isEditLikeTool(input.toolName)) {
3132
+ if (hasGuidedPath(state, "write", relativePath)) {
3133
+ return emptyResult();
3134
+ }
3135
+ const guidance = writeGuidance(primaryPath, {
3136
+ cwd,
3137
+ hasKibi: kibiReady,
3138
+ mcpState: state.mcpState,
3139
+ workspaceTrusted
3140
+ });
3141
+ if (!guidance) {
3142
+ return emptyResult();
3143
+ }
3144
+ rememberGuidedPath(stateDir, "write", relativePath);
3145
+ return { additional_context: guidance };
3146
+ }
3147
+ return emptyResult();
3148
+ }
3149
+ case "stop": {
3150
+ const state = loadHookState(stateDir);
3151
+ const shouldClearSession = state.dirtyPaths.length > 0 || state.kbCheckRun || state.planDelivered;
3152
+ if (input.status === "aborted" || input.status === "error") {
3153
+ if (shouldClearSession) {
3154
+ clearSessionHookState(stateDir);
3155
+ }
3156
+ return emptyResult();
3157
+ }
3158
+ const capture = claimCaptureReminder(session) ? captureReminder(hasDecisionFlag(session, "rule-prompt")) : undefined;
3159
+ const signoff = claimSignoffReminder(session, cwd) ? signoffReminder() : undefined;
3160
+ const reminder = stopFollowupMessage(state);
3161
+ const followupMessage = [capture, signoff, reminder].filter((part) => part !== undefined).join(`
3162
+ `);
3163
+ if (followupMessage) {
3164
+ trace.action = capture ? "stop_capture_reminder" : signoff ? "stop_signoff_reminder" : "stop_reminder";
3165
+ clearSessionHookState(stateDir);
3166
+ return { followup_message: followupMessage };
3167
+ }
3168
+ if (shouldClearSession) {
3169
+ clearSessionHookState(stateDir);
3170
+ }
3171
+ return emptyResult();
3172
+ }
3173
+ default:
3174
+ return emptyResult();
3175
+ }
3176
+ } finally {
3177
+ if (kibiReady) {
3178
+ appendHookUsage({
3179
+ host: "cursor",
3180
+ packageVersion: packageVersion(),
3181
+ workspaceRoot: cwd,
3182
+ event: input.event,
3183
+ sessionId: input.conversationId,
3184
+ hostTool: input.toolName,
3185
+ trace,
3186
+ startedAt
3187
+ }, environment.env ?? process.env);
3188
+ }
3189
+ await finishHookTelemetry({
3190
+ event: input.event,
3191
+ workspaceRoot: kibiReady ? cwd : undefined,
3192
+ sessionId: input.conversationId
3193
+ }, environment.env ?? process.env);
3194
+ }
3195
+ }
3196
+ async function main() {
3197
+ const result = await runHook(parseStdinJson(await readStdin()), {
3198
+ workspaceTrusted: process.argv.includes("--trusted-workspace")
3199
+ });
3200
+ process.stdout.write(`${JSON.stringify(result)}
3201
+ `);
3202
+ }
3203
+ function isInvokedAsCli(argv1, moduleUrl) {
3204
+ const invokedPath = argv1 ? pathToFileURL(path9.resolve(argv1)).href : "";
3205
+ return moduleUrl === invokedPath;
3206
+ }
3207
+ async function runHookCli() {
3208
+ try {
3209
+ await main();
3210
+ } catch (error) {
3211
+ const message = error instanceof Error ? error.message : "Unknown hook error";
3212
+ process.stdout.write(`${JSON.stringify({
3213
+ additional_context: `Kibi hook runner error: ${message}`
3214
+ })}
3215
+ `);
3216
+ }
3217
+ }
3218
+ async function runHookCliIfMain(isMain = isInvokedAsCli(process.argv[1], import.meta.url), start = runHookCli) {
3219
+ if (!isMain)
3220
+ return;
3221
+ await start();
3222
+ }
3223
+ runHookCliIfMain();
3224
+ var hookRunnerPath = fileURLToPath2(import.meta.url);
3225
+ export {
3226
+ hookRunnerPath,
3227
+ isInvokedAsCli,
3228
+ kibiWorkspaceFor,
3229
+ main,
3230
+ runHook,
3231
+ runHookCli,
3232
+ runHookCliIfMain
3233
+ };