kibi-cursor 2.11.0 → 2.13.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.cursor-plugin/plugin.json +1 -1
- package/CHANGELOG.md +71 -0
- package/README.md +1 -1
- package/agent-plugin/plugin.json +1 -1
- package/agent-plugin/skills/kibi-bootstrap/SKILL.md +2 -2
- package/agent-plugin/skills/kibi-provable-code/SKILL.md +203 -0
- package/agent-plugin/skills/kibi-provable-code/resources/provable-patterns.md +172 -0
- package/agent-plugin/skills/kibi-usage/SKILL.md +4 -1
- package/agent-plugin/skills/kibi-usage/resources/proof.md +31 -10
- package/bin/hook-runner.mjs +632 -568
- package/dist/.cursor-plugin/plugin.json +1 -1
- package/dist/bin/hook-runner.mjs +632 -568
- package/dist/hook-runner.js +12 -4
- package/dist/skills/.canon-hash.json +5 -3
- package/dist/skills/kibi-bootstrap/SKILL.md +2 -2
- package/dist/skills/kibi-provable-code/SKILL.md +203 -0
- package/dist/skills/kibi-provable-code/resources/provable-patterns.md +172 -0
- package/dist/skills/kibi-usage/SKILL.md +4 -1
- package/dist/skills/kibi-usage/resources/proof.md +31 -10
- package/package.json +2 -2
- package/skills/.canon-hash.json +5 -3
- package/skills/kibi-bootstrap/SKILL.md +2 -2
- package/skills/kibi-provable-code/SKILL.md +203 -0
- package/skills/kibi-provable-code/resources/provable-patterns.md +172 -0
- package/skills/kibi-usage/SKILL.md +4 -1
- package/skills/kibi-usage/resources/proof.md +31 -10
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);
|
|
672
633
|
} catch {}
|
|
673
634
|
}
|
|
635
|
+
function readCachedIndex(cachePath, signature) {
|
|
636
|
+
try {
|
|
637
|
+
const parsed = JSON.parse(String(fs2.readFileSync(cachePath)));
|
|
638
|
+
if (parsed.signature === signature && parsed.index)
|
|
639
|
+
return parsed.index;
|
|
640
|
+
} catch {}
|
|
641
|
+
return;
|
|
642
|
+
}
|
|
643
|
+
function scanKnowledgeIndex(symbolsPath, coordinatesPath, options) {
|
|
644
|
+
const symbolsText = readText(symbolsPath);
|
|
645
|
+
const coordinatesText = readText(coordinatesPath);
|
|
646
|
+
const records = symbolsText === undefined ? [] : scanSymbolsManifest(symbolsText);
|
|
647
|
+
return buildKnowledgeIndex(records, coordinatesText === undefined ? {} : scanSymbolCoordinates(coordinatesText), options, manifestStateOf(symbolsText, records.length));
|
|
648
|
+
}
|
|
674
649
|
function loadKnowledgeIndex(workspaceRoot, cacheDir, options = {}) {
|
|
675
|
-
const symbolsPath =
|
|
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);
|
|
1211
|
+
}
|
|
1212
|
+
function writeMarker(file, content) {
|
|
1213
|
+
fs4.mkdirSync(path4.dirname(file), { recursive: true });
|
|
1214
|
+
fs4.writeFileSync(file, content);
|
|
1215
|
+
}
|
|
1216
|
+
function touchMarker(file, flag) {
|
|
1217
|
+
fs4.mkdirSync(path4.dirname(file), { recursive: true });
|
|
1218
|
+
fs4.closeSync(fs4.openSync(file, flag));
|
|
1269
1219
|
}
|
|
1270
|
-
function
|
|
1271
|
-
|
|
1272
|
-
|
|
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,30 +1893,34 @@ function sandboxedHome(env) {
|
|
|
1848
1893
|
return false;
|
|
1849
1894
|
const temp = realDirectory(os.tmpdir());
|
|
1850
1895
|
const resolved = realDirectory(home);
|
|
1851
|
-
return resolved
|
|
1896
|
+
return resolved.startsWith(`${temp}${path7.sep}`);
|
|
1852
1897
|
}
|
|
1853
1898
|
var KIBI_REPOSITORY_PACKAGE = "kibi";
|
|
1854
|
-
function
|
|
1899
|
+
function moduleFileFromUrl(readUrl) {
|
|
1900
|
+
let file;
|
|
1855
1901
|
try {
|
|
1856
|
-
const url =
|
|
1857
|
-
|
|
1858
|
-
|
|
1859
|
-
|
|
1860
|
-
|
|
1902
|
+
const url = readUrl();
|
|
1903
|
+
if (typeof url === "string")
|
|
1904
|
+
file = fileURLToPath(url);
|
|
1905
|
+
} catch {}
|
|
1906
|
+
return file;
|
|
1907
|
+
}
|
|
1908
|
+
function thisModuleFile() {
|
|
1909
|
+
return moduleFileFromUrl(() => import.meta.url);
|
|
1861
1910
|
}
|
|
1862
1911
|
function insideKibiCheckout(file) {
|
|
1863
1912
|
const resolved = realDirectory(file);
|
|
1864
1913
|
if (resolved.split(/[\\/]/).includes("node_modules"))
|
|
1865
1914
|
return false;
|
|
1866
|
-
let current =
|
|
1915
|
+
let current = path7.dirname(resolved);
|
|
1867
1916
|
for (;; ) {
|
|
1868
1917
|
try {
|
|
1869
|
-
const manifest = JSON.parse(
|
|
1870
|
-
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"))) {
|
|
1871
1920
|
return true;
|
|
1872
1921
|
}
|
|
1873
1922
|
} catch {}
|
|
1874
|
-
const parent =
|
|
1923
|
+
const parent = path7.dirname(current);
|
|
1875
1924
|
if (parent === current)
|
|
1876
1925
|
return false;
|
|
1877
1926
|
current = parent;
|
|
@@ -1885,7 +1934,7 @@ function telemetryChannel(env = process.env, moduleFile = thisModuleFile() ?? nu
|
|
|
1885
1934
|
const explicit = env.KIBI_TELEMETRY_CHANNEL?.trim();
|
|
1886
1935
|
if (explicit)
|
|
1887
1936
|
return {
|
|
1888
|
-
channel: telemetryCategory(explicit)
|
|
1937
|
+
channel: telemetryCategory(explicit),
|
|
1889
1938
|
source: "KIBI_TELEMETRY_CHANNEL"
|
|
1890
1939
|
};
|
|
1891
1940
|
if (!moduleFile)
|
|
@@ -1902,14 +1951,14 @@ function telemetryDirectory(env = process.env) {
|
|
|
1902
1951
|
if (explicit)
|
|
1903
1952
|
return explicit;
|
|
1904
1953
|
if (process.platform === "win32" && env.LOCALAPPDATA) {
|
|
1905
|
-
return
|
|
1954
|
+
return path7.join(env.LOCALAPPDATA, "kibi");
|
|
1906
1955
|
}
|
|
1907
1956
|
const stateHome = env.XDG_STATE_HOME?.trim();
|
|
1908
|
-
return
|
|
1957
|
+
return path7.join(stateHome || path7.join(env.HOME || os.homedir(), ".local", "state"), "kibi");
|
|
1909
1958
|
}
|
|
1910
1959
|
function readTelemetryConfig(env = process.env) {
|
|
1911
1960
|
try {
|
|
1912
|
-
const parsed = JSON.parse(
|
|
1961
|
+
const parsed = JSON.parse(String(fs6.readFileSync(path7.join(telemetryDirectory(env), CONFIG_FILE))));
|
|
1913
1962
|
return typeof parsed === "object" && parsed !== null ? parsed : {};
|
|
1914
1963
|
} catch {
|
|
1915
1964
|
return {};
|
|
@@ -1917,9 +1966,9 @@ function readTelemetryConfig(env = process.env) {
|
|
|
1917
1966
|
}
|
|
1918
1967
|
function writeTelemetryConfig(config, env) {
|
|
1919
1968
|
const directory = telemetryDirectory(env);
|
|
1920
|
-
|
|
1921
|
-
|
|
1922
|
-
|
|
1969
|
+
fs6.mkdirSync(directory, { recursive: true });
|
|
1970
|
+
fs6.writeFileSync(path7.join(directory, CONFIG_FILE), `${JSON.stringify(config, null, 2)}
|
|
1971
|
+
`);
|
|
1923
1972
|
}
|
|
1924
1973
|
function telemetryStatus(env = process.env) {
|
|
1925
1974
|
const directory = telemetryDirectory(env);
|
|
@@ -1975,31 +2024,31 @@ function installId(env) {
|
|
|
1975
2024
|
if (typeof config.installId === "string" && config.installId !== "") {
|
|
1976
2025
|
return config.installId;
|
|
1977
2026
|
}
|
|
1978
|
-
const created =
|
|
2027
|
+
const created = randomUUID3();
|
|
1979
2028
|
try {
|
|
1980
2029
|
writeTelemetryConfig({ ...config, installId: created }, env);
|
|
1981
2030
|
} catch {}
|
|
1982
2031
|
return created;
|
|
1983
2032
|
}
|
|
1984
2033
|
function hashTelemetryId(value) {
|
|
1985
|
-
return
|
|
2034
|
+
return createHash4("sha256").update("kibi-telemetry\x00").update(value).digest("hex").slice(0, 16);
|
|
1986
2035
|
}
|
|
1987
2036
|
function runtimeName() {
|
|
1988
2037
|
return typeof globalThis.Bun === "undefined" ? "node" : "bun";
|
|
1989
2038
|
}
|
|
1990
2039
|
function sessionMarkersPath(env) {
|
|
1991
|
-
return
|
|
2040
|
+
return path7.join(telemetryDirectory(env), SESSIONS_FILE);
|
|
1992
2041
|
}
|
|
1993
2042
|
function readSessionMarkers(env) {
|
|
1994
2043
|
try {
|
|
1995
|
-
const parsed = JSON.parse(
|
|
1996
|
-
return
|
|
2044
|
+
const parsed = JSON.parse(String(fs6.readFileSync(sessionMarkersPath(env))));
|
|
2045
|
+
return typeof parsed === "object" && parsed !== null ? parsed : {};
|
|
1997
2046
|
} catch {
|
|
1998
2047
|
return {};
|
|
1999
2048
|
}
|
|
2000
2049
|
}
|
|
2001
2050
|
function workspaceKey(workspaceRoot) {
|
|
2002
|
-
return hashTelemetryId(`workspace\x00${
|
|
2051
|
+
return hashTelemetryId(`workspace\x00${path7.resolve(workspaceRoot)}`);
|
|
2003
2052
|
}
|
|
2004
2053
|
function rememberHookSession(workspaceRoot, sessionId, env = process.env) {
|
|
2005
2054
|
if (!sessionId.trim() || !telemetryEnabled(env))
|
|
@@ -2013,22 +2062,22 @@ function rememberHookSession(workspaceRoot, sessionId, env = process.env) {
|
|
|
2013
2062
|
const kept = Object.entries(markers).filter(([, marker]) => typeof marker?.at === "number" && now - marker.at <= SESSION_MARKER_MAX_AGE_MS).sort(([, left], [, right]) => right.at - left.at).slice(0, MAX_SESSION_MARKERS);
|
|
2014
2063
|
try {
|
|
2015
2064
|
const file = sessionMarkersPath(env);
|
|
2016
|
-
|
|
2017
|
-
const temporary = `${file}.${process.pid}.${
|
|
2018
|
-
|
|
2019
|
-
|
|
2065
|
+
fs6.mkdirSync(path7.dirname(file), { recursive: true });
|
|
2066
|
+
const temporary = `${file}.${process.pid}.${randomUUID3()}.tmp`;
|
|
2067
|
+
fs6.writeFileSync(temporary, JSON.stringify(Object.fromEntries(kept)));
|
|
2068
|
+
fs6.renameSync(temporary, file);
|
|
2020
2069
|
} catch {}
|
|
2021
2070
|
}
|
|
2022
2071
|
function workspaceSessionHash(startDirectory, env = process.env) {
|
|
2023
2072
|
const markers = readSessionMarkers(env);
|
|
2024
2073
|
const now = Date.now();
|
|
2025
|
-
let current =
|
|
2074
|
+
let current = path7.resolve(startDirectory);
|
|
2026
2075
|
for (;; ) {
|
|
2027
2076
|
const marker = markers[workspaceKey(current)];
|
|
2028
2077
|
if (marker && typeof marker.session === "string" && typeof marker.at === "number" && now - marker.at <= SESSION_MARKER_MAX_AGE_MS) {
|
|
2029
2078
|
return marker.session;
|
|
2030
2079
|
}
|
|
2031
|
-
const parent =
|
|
2080
|
+
const parent = path7.dirname(current);
|
|
2032
2081
|
if (parent === current)
|
|
2033
2082
|
return;
|
|
2034
2083
|
current = parent;
|
|
@@ -2046,31 +2095,32 @@ function baseProperties(source, env) {
|
|
|
2046
2095
|
$process_person_profile: false,
|
|
2047
2096
|
$geoip_disable: true,
|
|
2048
2097
|
$lib: "kibi",
|
|
2098
|
+
telemetry_version: TELEMETRY_EVENT_VERSION,
|
|
2049
2099
|
package: source.packageName,
|
|
2050
2100
|
package_version: source.packageVersion ?? null,
|
|
2051
|
-
host: source.host
|
|
2101
|
+
host: source.host && hostKey(source.host) || detectTelemetryHost(env) || "unknown",
|
|
2052
2102
|
remote: remoteEnvironment(env),
|
|
2053
2103
|
channel: detectTelemetryChannel(env),
|
|
2054
2104
|
session: sessionProperty(source, env),
|
|
2055
2105
|
os: process.platform,
|
|
2056
2106
|
arch: process.arch,
|
|
2057
2107
|
runtime: runtimeName(),
|
|
2058
|
-
runtime_version: process.versions.node.split(".")[0]
|
|
2108
|
+
runtime_version: process.versions.node.split(".")[0]
|
|
2059
2109
|
};
|
|
2060
2110
|
}
|
|
2061
2111
|
function queuePath(env) {
|
|
2062
|
-
return
|
|
2112
|
+
return path7.join(telemetryDirectory(env), QUEUE_FILE);
|
|
2063
2113
|
}
|
|
2064
2114
|
function appendEvent(event, env) {
|
|
2065
2115
|
try {
|
|
2066
2116
|
const file = queuePath(env);
|
|
2067
|
-
|
|
2117
|
+
fs6.mkdirSync(path7.dirname(file), { recursive: true });
|
|
2068
2118
|
try {
|
|
2069
|
-
if (
|
|
2119
|
+
if (fs6.statSync(file).size > MAX_QUEUE_BYTES)
|
|
2070
2120
|
return;
|
|
2071
2121
|
} catch {}
|
|
2072
|
-
|
|
2073
|
-
|
|
2122
|
+
fs6.appendFileSync(file, `${JSON.stringify(event)}
|
|
2123
|
+
`);
|
|
2074
2124
|
} catch {}
|
|
2075
2125
|
}
|
|
2076
2126
|
function telemetryCategory(value) {
|
|
@@ -2079,6 +2129,11 @@ function telemetryCategory(value) {
|
|
|
2079
2129
|
return null;
|
|
2080
2130
|
return /^[A-Za-z0-9_.:+-]{1,160}$/.test(trimmed) ? trimmed.toLowerCase() : "unclassified";
|
|
2081
2131
|
}
|
|
2132
|
+
function rememberTelemetrySkillLoad(workspaceRoot, env = process.env) {
|
|
2133
|
+
if (!telemetryEnabled(env))
|
|
2134
|
+
return;
|
|
2135
|
+
rememberSkillLoad(telemetryDirectory(env), workspaceKey(workspaceRoot));
|
|
2136
|
+
}
|
|
2082
2137
|
function recordHookTelemetry(source, hook, env = process.env) {
|
|
2083
2138
|
if (!telemetryEnabled(env))
|
|
2084
2139
|
return;
|
|
@@ -2092,7 +2147,8 @@ function recordHookTelemetry(source, hook, env = process.env) {
|
|
|
2092
2147
|
path_kind: hook.pathKind ?? null,
|
|
2093
2148
|
kb_operation: hook.kbOperation ?? null,
|
|
2094
2149
|
kb_used_before: hook.kbUsedBefore ?? null,
|
|
2095
|
-
requirement_count: hook.requirementCount ?? null
|
|
2150
|
+
requirement_count: hook.requirementCount ?? null,
|
|
2151
|
+
skill: hook.skill ?? null
|
|
2096
2152
|
}
|
|
2097
2153
|
}, env);
|
|
2098
2154
|
}
|
|
@@ -2100,8 +2156,6 @@ function parseQueued(text, now) {
|
|
|
2100
2156
|
const events = [];
|
|
2101
2157
|
for (const line of text.split(`
|
|
2102
2158
|
`)) {
|
|
2103
|
-
if (line.trim() === "")
|
|
2104
|
-
continue;
|
|
2105
2159
|
try {
|
|
2106
2160
|
const event = JSON.parse(line);
|
|
2107
2161
|
const age = now - Date.parse(event.timestamp);
|
|
@@ -2114,7 +2168,7 @@ function parseQueued(text, now) {
|
|
|
2114
2168
|
function flushDue(file, now) {
|
|
2115
2169
|
let text;
|
|
2116
2170
|
try {
|
|
2117
|
-
text =
|
|
2171
|
+
text = fs6.readFileSync(file, "utf8");
|
|
2118
2172
|
} catch {
|
|
2119
2173
|
return false;
|
|
2120
2174
|
}
|
|
@@ -2125,7 +2179,7 @@ function flushDue(file, now) {
|
|
|
2125
2179
|
if (lines.length >= FLUSH_BATCH_EVENTS)
|
|
2126
2180
|
return true;
|
|
2127
2181
|
try {
|
|
2128
|
-
const first = JSON.parse(lines[0]
|
|
2182
|
+
const first = JSON.parse(lines[0]);
|
|
2129
2183
|
return now - Date.parse(first.timestamp) >= FLUSH_MAX_AGE_MS;
|
|
2130
2184
|
} catch {
|
|
2131
2185
|
return true;
|
|
@@ -2135,26 +2189,26 @@ function claimBatches(directory, now) {
|
|
|
2135
2189
|
const claimed = [];
|
|
2136
2190
|
let names;
|
|
2137
2191
|
try {
|
|
2138
|
-
names =
|
|
2192
|
+
names = fs6.readdirSync(directory);
|
|
2139
2193
|
} catch {
|
|
2140
2194
|
return claimed;
|
|
2141
2195
|
}
|
|
2142
2196
|
for (const name of names) {
|
|
2143
2197
|
if (!name.endsWith(SENDING_SUFFIX))
|
|
2144
2198
|
continue;
|
|
2145
|
-
const file =
|
|
2199
|
+
const file = path7.join(directory, name);
|
|
2146
2200
|
try {
|
|
2147
|
-
if (now -
|
|
2201
|
+
if (now - fs6.statSync(file).mtimeMs < STALE_CLAIM_MS)
|
|
2148
2202
|
continue;
|
|
2149
|
-
const reclaimed = `${file}.${
|
|
2150
|
-
|
|
2203
|
+
const reclaimed = `${file}.${randomUUID3()}${SENDING_SUFFIX}`;
|
|
2204
|
+
fs6.renameSync(file, reclaimed);
|
|
2151
2205
|
claimed.push(reclaimed);
|
|
2152
2206
|
} catch {}
|
|
2153
2207
|
}
|
|
2154
|
-
const queue =
|
|
2155
|
-
const target =
|
|
2208
|
+
const queue = path7.join(directory, QUEUE_FILE);
|
|
2209
|
+
const target = path7.join(directory, `${QUEUE_FILE}.${process.pid}.${randomUUID3()}${SENDING_SUFFIX}`);
|
|
2156
2210
|
try {
|
|
2157
|
-
|
|
2211
|
+
fs6.renameSync(queue, target);
|
|
2158
2212
|
claimed.push(target);
|
|
2159
2213
|
} catch {}
|
|
2160
2214
|
return claimed;
|
|
@@ -2167,17 +2221,15 @@ async function flushTelemetry(options = {}, env = process.env) {
|
|
|
2167
2221
|
if (!options.force && !flushDue(queuePath(env), now))
|
|
2168
2222
|
return 0;
|
|
2169
2223
|
const claimed = claimBatches(directory, now);
|
|
2170
|
-
if (claimed.length === 0)
|
|
2171
|
-
return 0;
|
|
2172
2224
|
const events = [];
|
|
2173
2225
|
for (const file of claimed) {
|
|
2174
2226
|
try {
|
|
2175
|
-
events.push(...parseQueued(
|
|
2227
|
+
events.push(...parseQueued(fs6.readFileSync(file, "utf8"), now));
|
|
2176
2228
|
} catch {}
|
|
2177
2229
|
}
|
|
2178
2230
|
const release = () => {
|
|
2179
2231
|
for (const file of claimed)
|
|
2180
|
-
|
|
2232
|
+
fs6.rmSync(file, { force: true });
|
|
2181
2233
|
};
|
|
2182
2234
|
if (events.length === 0) {
|
|
2183
2235
|
release();
|
|
@@ -2186,7 +2238,7 @@ async function flushTelemetry(options = {}, env = process.env) {
|
|
|
2186
2238
|
const fetchImpl = options.fetchImpl ?? globalThis.fetch;
|
|
2187
2239
|
const controller = new AbortController;
|
|
2188
2240
|
const timer = setTimeout(() => controller.abort(), options.timeoutMs ?? DEFAULT_FLUSH_TIMEOUT_MS);
|
|
2189
|
-
timer.unref
|
|
2241
|
+
timer.unref();
|
|
2190
2242
|
try {
|
|
2191
2243
|
const response = await fetchImpl(`${telemetryHost(env)}/batch/`, {
|
|
2192
2244
|
method: "POST",
|
|
@@ -2199,7 +2251,7 @@ async function flushTelemetry(options = {}, env = process.env) {
|
|
|
2199
2251
|
signal: controller.signal
|
|
2200
2252
|
});
|
|
2201
2253
|
if (!response.ok && response.status >= 500)
|
|
2202
|
-
throw new Error
|
|
2254
|
+
throw new Error;
|
|
2203
2255
|
release();
|
|
2204
2256
|
return response.ok ? events.length : 0;
|
|
2205
2257
|
} catch {
|
|
@@ -2215,6 +2267,7 @@ async function flushTelemetry(options = {}, env = process.env) {
|
|
|
2215
2267
|
// ../agent-core/dist/hook-usage-log.js
|
|
2216
2268
|
var HOOK_ACTION_EDITED = "edited";
|
|
2217
2269
|
var HOOK_ACTION_KB_USAGE = "kb_usage";
|
|
2270
|
+
var HOOK_ACTION_SKILL_LOADED = "skill_loaded";
|
|
2218
2271
|
function hookTelemetryEnabled(env = process.env) {
|
|
2219
2272
|
const value = env.KIBI_DIAGNOSTIC_MODE?.trim().toLowerCase();
|
|
2220
2273
|
return value === "1" || value === "true";
|
|
@@ -2239,15 +2292,19 @@ function appendHookUsage(row, env = process.env) {
|
|
|
2239
2292
|
pathKind: row.trace.pathKind,
|
|
2240
2293
|
kbOperation: row.trace.kbOperation,
|
|
2241
2294
|
kbUsedBefore: row.trace.kbUsedBefore,
|
|
2242
|
-
requirementCount: row.trace.requirementIds?.length
|
|
2295
|
+
requirementCount: row.trace.requirementIds?.length,
|
|
2296
|
+
skill: row.trace.skill
|
|
2243
2297
|
}, env);
|
|
2298
|
+
if (row.trace.action === HOOK_ACTION_SKILL_LOADED) {
|
|
2299
|
+
rememberTelemetrySkillLoad(row.workspaceRoot, env);
|
|
2300
|
+
}
|
|
2244
2301
|
if (!hookTelemetryEnabled(env))
|
|
2245
2302
|
return;
|
|
2246
2303
|
const finishedAt = new Date;
|
|
2247
2304
|
const { trace } = row;
|
|
2248
2305
|
const record = {
|
|
2249
2306
|
timestamp: finishedAt.toISOString(),
|
|
2250
|
-
request_id: `hook-${
|
|
2307
|
+
request_id: `hook-${randomUUID4()}`,
|
|
2251
2308
|
tool: `hook_${row.event}`,
|
|
2252
2309
|
interface: "hook",
|
|
2253
2310
|
host: row.host,
|
|
@@ -2266,15 +2323,21 @@ function appendHookUsage(row, env = process.env) {
|
|
|
2266
2323
|
duration_ms: finishedAt.getTime() - row.startedAt.getTime()
|
|
2267
2324
|
};
|
|
2268
2325
|
try {
|
|
2269
|
-
const logPath =
|
|
2270
|
-
|
|
2271
|
-
|
|
2272
|
-
|
|
2326
|
+
const logPath = path8.join(row.workspaceRoot, ".kb", "usage.log");
|
|
2327
|
+
fs7.mkdirSync(path8.dirname(logPath), { recursive: true });
|
|
2328
|
+
fs7.appendFileSync(logPath, `${JSON.stringify(record)}
|
|
2329
|
+
`);
|
|
2273
2330
|
} catch {}
|
|
2274
2331
|
}
|
|
2275
2332
|
function kbUsageTrace(toolName, toolInput) {
|
|
2276
2333
|
const kbOperation = hostKbOperation(toolName, toolInput);
|
|
2277
|
-
|
|
2334
|
+
if (kbOperation)
|
|
2335
|
+
return { action: HOOK_ACTION_KB_USAGE, kbOperation };
|
|
2336
|
+
return skillLoadTrace(toolName, toolInput);
|
|
2337
|
+
}
|
|
2338
|
+
function skillLoadTrace(toolName, toolInput) {
|
|
2339
|
+
const skill = kibiSkillLoad(toolName, toolInput);
|
|
2340
|
+
return skill ? { action: HOOK_ACTION_SKILL_LOADED, skill } : undefined;
|
|
2278
2341
|
}
|
|
2279
2342
|
function editTraces(relativePaths, requirementIdsFor) {
|
|
2280
2343
|
const traces = [];
|
|
@@ -2292,8 +2355,6 @@ function editTraces(relativePaths, requirementIdsFor) {
|
|
|
2292
2355
|
return traces;
|
|
2293
2356
|
}
|
|
2294
2357
|
function appendHookUsageRows(row, traces, env = process.env) {
|
|
2295
|
-
if (!hookTracesWanted(env))
|
|
2296
|
-
return;
|
|
2297
2358
|
for (const trace of traces)
|
|
2298
2359
|
appendHookUsage({ ...row, trace }, env);
|
|
2299
2360
|
}
|
|
@@ -2309,10 +2370,10 @@ async function finishHookTelemetry(input, env = process.env) {
|
|
|
2309
2370
|
}
|
|
2310
2371
|
function readPackageVersion(packageJsonPath) {
|
|
2311
2372
|
try {
|
|
2312
|
-
const parsed = JSON.parse(
|
|
2313
|
-
|
|
2314
|
-
|
|
2315
|
-
|
|
2373
|
+
const parsed = JSON.parse(String(fs7.readFileSync(packageJsonPath)));
|
|
2374
|
+
const version = parsed.version;
|
|
2375
|
+
if (typeof version === "string")
|
|
2376
|
+
return version;
|
|
2316
2377
|
} catch {}
|
|
2317
2378
|
return null;
|
|
2318
2379
|
}
|
|
@@ -2588,10 +2649,10 @@ function parseStdinJson(rawInput) {
|
|
|
2588
2649
|
}
|
|
2589
2650
|
|
|
2590
2651
|
// src/hook-state-storage.ts
|
|
2591
|
-
import { randomUUID as
|
|
2592
|
-
import
|
|
2652
|
+
import { randomUUID as randomUUID5 } from "node:crypto";
|
|
2653
|
+
import fs8 from "node:fs";
|
|
2593
2654
|
import os2 from "node:os";
|
|
2594
|
-
import
|
|
2655
|
+
import path9 from "node:path";
|
|
2595
2656
|
var stateFileName = "hook-state.json";
|
|
2596
2657
|
var lockFileName = "hook-state.lock";
|
|
2597
2658
|
var maxDirtyPaths = 50;
|
|
@@ -2601,10 +2662,10 @@ var lockRetryCount = 100;
|
|
|
2601
2662
|
var lockRetryDelayMs = 5;
|
|
2602
2663
|
var staleLockAgeMs = 30000;
|
|
2603
2664
|
function statePath(pluginData) {
|
|
2604
|
-
return
|
|
2665
|
+
return path9.join(pluginData, stateFileName);
|
|
2605
2666
|
}
|
|
2606
2667
|
function lockPath(pluginData) {
|
|
2607
|
-
return
|
|
2668
|
+
return path9.join(pluginData, lockFileName);
|
|
2608
2669
|
}
|
|
2609
2670
|
function isRecord5(value) {
|
|
2610
2671
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
@@ -2618,7 +2679,7 @@ function sleepSync(milliseconds) {
|
|
|
2618
2679
|
Atomics.wait(view, 0, 0, milliseconds);
|
|
2619
2680
|
}
|
|
2620
2681
|
function uniqueTempPath(pluginData) {
|
|
2621
|
-
return
|
|
2682
|
+
return path9.join(pluginData, `${stateFileName}.${process.pid}.${Date.now()}.${randomUUID5()}.tmp`);
|
|
2622
2683
|
}
|
|
2623
2684
|
function mergeStringPaths(existingPaths, nextPaths) {
|
|
2624
2685
|
const merged = [...existingPaths, ...nextPaths].map(normalizePath).filter((dirtyPath) => dirtyPath.length > 0);
|
|
@@ -2642,7 +2703,7 @@ function acquireLock(pluginData) {
|
|
|
2642
2703
|
const targetLockPath = lockPath(pluginData);
|
|
2643
2704
|
for (let attempt = 0;attempt < lockRetryCount; attempt++) {
|
|
2644
2705
|
try {
|
|
2645
|
-
return
|
|
2706
|
+
return fs8.openSync(targetLockPath, "wx");
|
|
2646
2707
|
} catch (error) {
|
|
2647
2708
|
if (error instanceof Error && "code" in error && error.code === "EEXIST") {
|
|
2648
2709
|
if (removeStaleLock(targetLockPath)) {
|
|
@@ -2659,7 +2720,7 @@ function acquireLock(pluginData) {
|
|
|
2659
2720
|
function removeStaleLock(targetLockPath) {
|
|
2660
2721
|
let stats;
|
|
2661
2722
|
try {
|
|
2662
|
-
stats =
|
|
2723
|
+
stats = fs8.statSync(targetLockPath);
|
|
2663
2724
|
} catch {
|
|
2664
2725
|
return false;
|
|
2665
2726
|
}
|
|
@@ -2667,7 +2728,7 @@ function removeStaleLock(targetLockPath) {
|
|
|
2667
2728
|
return false;
|
|
2668
2729
|
}
|
|
2669
2730
|
try {
|
|
2670
|
-
|
|
2731
|
+
fs8.unlinkSync(targetLockPath);
|
|
2671
2732
|
return true;
|
|
2672
2733
|
} catch {
|
|
2673
2734
|
return false;
|
|
@@ -2675,14 +2736,14 @@ function removeStaleLock(targetLockPath) {
|
|
|
2675
2736
|
}
|
|
2676
2737
|
function releaseLock(pluginData, fileDescriptor) {
|
|
2677
2738
|
try {
|
|
2678
|
-
|
|
2739
|
+
fs8.closeSync(fileDescriptor);
|
|
2679
2740
|
} catch (error) {
|
|
2680
2741
|
if (!(error instanceof Error)) {
|
|
2681
2742
|
throw error;
|
|
2682
2743
|
}
|
|
2683
2744
|
}
|
|
2684
2745
|
try {
|
|
2685
|
-
|
|
2746
|
+
fs8.unlinkSync(lockPath(pluginData));
|
|
2686
2747
|
} catch (error) {
|
|
2687
2748
|
if (!(error instanceof Error)) {
|
|
2688
2749
|
throw error;
|
|
@@ -2720,14 +2781,14 @@ function resolveStateDir(pluginData, conversationId) {
|
|
|
2720
2781
|
if (!conversationId) {
|
|
2721
2782
|
return;
|
|
2722
2783
|
}
|
|
2723
|
-
return
|
|
2784
|
+
return path9.join(os2.tmpdir(), "kibi-cursor-hook-state", conversationId.replaceAll(/[^a-zA-Z0-9._-]/g, "_"));
|
|
2724
2785
|
}
|
|
2725
2786
|
function loadHookState(stateDir) {
|
|
2726
2787
|
if (!stateDir) {
|
|
2727
2788
|
return emptyHookState();
|
|
2728
2789
|
}
|
|
2729
2790
|
try {
|
|
2730
|
-
return coerceHookState(JSON.parse(
|
|
2791
|
+
return coerceHookState(JSON.parse(fs8.readFileSync(statePath(stateDir), "utf8")));
|
|
2731
2792
|
} catch {
|
|
2732
2793
|
return emptyHookState();
|
|
2733
2794
|
}
|
|
@@ -2736,19 +2797,19 @@ function saveHookState(stateDir, state) {
|
|
|
2736
2797
|
if (!stateDir) {
|
|
2737
2798
|
return;
|
|
2738
2799
|
}
|
|
2739
|
-
|
|
2800
|
+
fs8.mkdirSync(stateDir, { recursive: true });
|
|
2740
2801
|
const boundedState = coerceHookState(state);
|
|
2741
2802
|
const tempPath = uniqueTempPath(stateDir);
|
|
2742
|
-
|
|
2803
|
+
fs8.writeFileSync(tempPath, `${JSON.stringify(boundedState)}
|
|
2743
2804
|
`);
|
|
2744
|
-
|
|
2805
|
+
fs8.renameSync(tempPath, statePath(stateDir));
|
|
2745
2806
|
}
|
|
2746
2807
|
function updateHookState(stateDir, updater) {
|
|
2747
2808
|
const initialState = loadHookState(stateDir);
|
|
2748
2809
|
if (!stateDir) {
|
|
2749
2810
|
return updater(initialState);
|
|
2750
2811
|
}
|
|
2751
|
-
|
|
2812
|
+
fs8.mkdirSync(stateDir, { recursive: true });
|
|
2752
2813
|
const lockFileDescriptor = acquireLock(stateDir);
|
|
2753
2814
|
if (lockFileDescriptor === undefined) {
|
|
2754
2815
|
return updater(initialState);
|
|
@@ -2843,14 +2904,14 @@ function clearSessionHookState(stateDir) {
|
|
|
2843
2904
|
}
|
|
2844
2905
|
|
|
2845
2906
|
// src/source-linked-requirements.ts
|
|
2846
|
-
import
|
|
2907
|
+
import path10 from "node:path";
|
|
2847
2908
|
var CURSOR_INDEX_OPTIONS = {
|
|
2848
2909
|
legacyLinksAsImplements: false,
|
|
2849
2910
|
cacheFileName: "cursor-knowledge-index.json"
|
|
2850
2911
|
};
|
|
2851
2912
|
function toManifestPath(workspaceRoot, filePath) {
|
|
2852
|
-
const absolute =
|
|
2853
|
-
return
|
|
2913
|
+
const absolute = path10.isAbsolute(filePath) ? filePath : path10.resolve(workspaceRoot, filePath);
|
|
2914
|
+
return path10.relative(workspaceRoot, absolute).split(path10.sep).join("/");
|
|
2854
2915
|
}
|
|
2855
2916
|
function getSourceLinkedSymbols(workspaceRoot, filePath, cacheDir) {
|
|
2856
2917
|
const relativePath = toManifestPath(workspaceRoot, filePath);
|
|
@@ -2887,7 +2948,7 @@ function firmNotice(workspaceRoot, linked, focus, session, env) {
|
|
|
2887
2948
|
return firmEditNotice(symbol ? `${symbol.title} in ${linked.relativePath}` : linked.relativePath, ids.map(summarize));
|
|
2888
2949
|
}
|
|
2889
2950
|
function packageVersion() {
|
|
2890
|
-
return readPackageVersion(
|
|
2951
|
+
return readPackageVersion(path11.join(path11.dirname(fileURLToPath2(import.meta.url)), "..", "package.json"));
|
|
2891
2952
|
}
|
|
2892
2953
|
function recordToolTelemetry(input, workspaceRoot, stateDir, startedAt, env) {
|
|
2893
2954
|
if (!hookTracesWanted(env))
|
|
@@ -2923,19 +2984,19 @@ function hasKibiConfig(cwd) {
|
|
|
2923
2984
|
if (!cwd) {
|
|
2924
2985
|
return false;
|
|
2925
2986
|
}
|
|
2926
|
-
return
|
|
2987
|
+
return fs9.existsSync(path11.join(cwd, ".kb", "manifest.json"));
|
|
2927
2988
|
}
|
|
2928
2989
|
function kibiWorkspaceFor(directory) {
|
|
2929
2990
|
if (!directory)
|
|
2930
2991
|
return;
|
|
2931
|
-
let current =
|
|
2992
|
+
let current = path11.resolve(directory);
|
|
2932
2993
|
for (;; ) {
|
|
2933
|
-
if (
|
|
2994
|
+
if (fs9.existsSync(path11.join(current, ".kb", "manifest.json"))) {
|
|
2934
2995
|
return current;
|
|
2935
2996
|
}
|
|
2936
|
-
if (
|
|
2997
|
+
if (fs9.existsSync(path11.join(current, ".git")))
|
|
2937
2998
|
return;
|
|
2938
|
-
const parent =
|
|
2999
|
+
const parent = path11.dirname(current);
|
|
2939
3000
|
if (parent === current)
|
|
2940
3001
|
return;
|
|
2941
3002
|
current = parent;
|
|
@@ -2990,7 +3051,9 @@ ${advisory}` : BOOTSTRAP_REMINDER
|
|
|
2990
3051
|
}
|
|
2991
3052
|
{
|
|
2992
3053
|
const advisory = interfaceAdvisory(initialState.mcpState, workspaceTrusted);
|
|
2993
|
-
|
|
3054
|
+
const context = [advisory, missingHooksNote(cwd)].filter((line) => Boolean(line)).join(`
|
|
3055
|
+
`);
|
|
3056
|
+
return context ? { additional_context: context } : emptyResult();
|
|
2994
3057
|
}
|
|
2995
3058
|
case "preToolUse": {
|
|
2996
3059
|
const stamped = stampKibiWorkspace(input.toolName, input.toolInput, kibiWorkspaceFor(input.cwd ?? input.workspaceRoots?.[0]));
|
|
@@ -3016,7 +3079,7 @@ ${advisory}` : BOOTSTRAP_REMINDER
|
|
|
3016
3079
|
const linked = getSourceLinkedSymbols(cwd, primaryPath, stateDir);
|
|
3017
3080
|
trace.pathKind = classifyPath(relativePath);
|
|
3018
3081
|
trace.requirementIds = implementedRequirementIds(linked.symbols);
|
|
3019
|
-
const focus = editFocus(
|
|
3082
|
+
const focus = editFocus(path11.resolve(cwd, primaryPath), input.toolInput);
|
|
3020
3083
|
const ruleNote = takeRuleNote(session);
|
|
3021
3084
|
const notice = firmNotice(cwd, linked, focus, session, environment.env ?? process.env);
|
|
3022
3085
|
const guidance = hasGuidedPath(state, "pre-edit", relativePath) ? undefined : preEditGuidance(primaryPath, {
|
|
@@ -3099,7 +3162,8 @@ ${advisory}` : BOOTSTRAP_REMINDER
|
|
|
3099
3162
|
markDecisionFlag(session, "source-edited");
|
|
3100
3163
|
}
|
|
3101
3164
|
}
|
|
3102
|
-
|
|
3165
|
+
const kbOperation = kbToolCall ? kbToolCall.toolName : kbUsageTrace(input.toolName, input.toolInput)?.kbOperation;
|
|
3166
|
+
if (kbOperation === "kb_upsert") {
|
|
3103
3167
|
markDecisionFlag(session, "kb-recorded");
|
|
3104
3168
|
if (isRequirementUpsert(input.toolInput)) {
|
|
3105
3169
|
markDecisionFlag(session, "requirement-recorded");
|
|
@@ -3201,7 +3265,7 @@ async function main() {
|
|
|
3201
3265
|
`);
|
|
3202
3266
|
}
|
|
3203
3267
|
function isInvokedAsCli(argv1, moduleUrl) {
|
|
3204
|
-
const invokedPath = argv1 ? pathToFileURL(
|
|
3268
|
+
const invokedPath = argv1 ? pathToFileURL(path11.resolve(argv1)).href : "";
|
|
3205
3269
|
return moduleUrl === invokedPath;
|
|
3206
3270
|
}
|
|
3207
3271
|
async function runHookCli() {
|