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