kibi-cli 0.2.3 → 0.2.6
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/dist/cli.js +3 -28
- package/dist/commands/aggregated-checks.d.ts +4 -1
- package/dist/commands/aggregated-checks.d.ts.map +1 -1
- package/dist/commands/aggregated-checks.js +13 -3
- package/dist/commands/branch.d.ts.map +1 -1
- package/dist/commands/branch.js +3 -41
- package/dist/commands/check.d.ts.map +1 -1
- package/dist/commands/check.js +55 -45
- package/dist/commands/doctor.d.ts.map +1 -1
- package/dist/commands/doctor.js +0 -27
- package/dist/commands/gc.d.ts.map +1 -1
- package/dist/commands/gc.js +2 -51
- package/dist/commands/init-helpers.d.ts.map +1 -1
- package/dist/commands/init-helpers.js +23 -36
- package/dist/commands/init.d.ts.map +1 -1
- package/dist/commands/init.js +0 -27
- package/dist/commands/query.d.ts.map +1 -1
- package/dist/commands/query.js +7 -288
- package/dist/commands/sync/cache.d.ts +13 -0
- package/dist/commands/sync/cache.d.ts.map +1 -0
- package/dist/commands/sync/cache.js +76 -0
- package/dist/commands/sync/discovery.d.ts +8 -0
- package/dist/commands/sync/discovery.d.ts.map +1 -0
- package/dist/commands/sync/discovery.js +50 -0
- package/dist/commands/sync/extraction.d.ts +11 -0
- package/dist/commands/sync/extraction.d.ts.map +1 -0
- package/dist/commands/sync/extraction.js +69 -0
- package/dist/commands/sync/manifest.d.ts +5 -0
- package/dist/commands/sync/manifest.d.ts.map +1 -0
- package/dist/commands/sync/manifest.js +118 -0
- package/dist/commands/sync/persistence.d.ts +16 -0
- package/dist/commands/sync/persistence.d.ts.map +1 -0
- package/dist/commands/sync/persistence.js +188 -0
- package/dist/commands/sync/staging.d.ts +4 -0
- package/dist/commands/sync/staging.d.ts.map +1 -0
- package/dist/commands/sync/staging.js +86 -0
- package/dist/commands/sync.d.ts +2 -1
- package/dist/commands/sync.d.ts.map +1 -1
- package/dist/commands/sync.js +69 -501
- package/dist/extractors/manifest.d.ts +0 -1
- package/dist/extractors/manifest.d.ts.map +1 -1
- package/dist/extractors/manifest.js +41 -49
- package/dist/extractors/markdown.d.ts +0 -1
- package/dist/extractors/markdown.d.ts.map +1 -1
- package/dist/extractors/markdown.js +28 -50
- package/dist/extractors/relationships.d.ts +39 -0
- package/dist/extractors/relationships.d.ts.map +1 -0
- package/dist/extractors/relationships.js +137 -0
- package/dist/extractors/symbols-coordinator.d.ts.map +1 -1
- package/dist/extractors/symbols-coordinator.js +0 -27
- package/dist/extractors/symbols-ts.d.ts.map +1 -1
- package/dist/extractors/symbols-ts.js +0 -27
- package/dist/kb/target-resolver.d.ts +80 -0
- package/dist/kb/target-resolver.d.ts.map +1 -0
- package/dist/kb/target-resolver.js +313 -0
- package/dist/prolog/codec.d.ts +63 -0
- package/dist/prolog/codec.d.ts.map +1 -0
- package/dist/prolog/codec.js +434 -0
- package/dist/prolog.d.ts.map +1 -1
- package/dist/prolog.js +0 -27
- package/dist/public/extractors/symbols-coordinator.d.ts.map +1 -1
- package/dist/public/extractors/symbols-coordinator.js +0 -27
- package/dist/public/prolog/index.d.ts.map +1 -1
- package/dist/public/prolog/index.js +0 -27
- package/dist/public/schemas/entity.d.ts.map +1 -1
- package/dist/public/schemas/entity.js +10 -27
- package/dist/public/schemas/relationship.d.ts.map +1 -1
- package/dist/public/schemas/relationship.js +0 -27
- package/dist/query/service.d.ts +35 -0
- package/dist/query/service.d.ts.map +1 -0
- package/dist/query/service.js +149 -0
- package/dist/relationships/shards.d.ts +68 -0
- package/dist/relationships/shards.d.ts.map +1 -0
- package/dist/relationships/shards.js +263 -0
- package/dist/traceability/git-staged.d.ts +4 -1
- package/dist/traceability/git-staged.d.ts.map +1 -1
- package/dist/traceability/git-staged.js +24 -11
- package/dist/types/changeset.d.ts.map +1 -1
- package/dist/types/entities.d.ts.map +1 -1
- package/dist/types/relationships.d.ts.map +1 -1
- package/dist/utils/branch-resolver.d.ts +4 -0
- package/dist/utils/branch-resolver.d.ts.map +1 -1
- package/dist/utils/branch-resolver.js +4 -0
- package/dist/utils/config.d.ts +10 -1
- package/dist/utils/config.d.ts.map +1 -1
- package/dist/utils/config.js +27 -1
- package/dist/utils/rule-registry.d.ts +47 -0
- package/dist/utils/rule-registry.d.ts.map +1 -0
- package/dist/utils/rule-registry.js +139 -0
- package/package.json +6 -2
- package/schema/config.json +161 -0
- package/src/public/extractors/symbols-coordinator.ts +0 -27
- package/src/public/prolog/index.ts +0 -27
- package/src/public/schemas/entity.ts +10 -27
- package/src/public/schemas/relationship.ts +0 -27
- package/src/schemas/entity.schema.json +11 -1
package/dist/commands/query.js
CHANGED
|
@@ -15,37 +15,12 @@
|
|
|
15
15
|
You should have received a copy of the GNU Affero General Public License
|
|
16
16
|
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
17
17
|
*/
|
|
18
|
-
/*
|
|
19
|
-
How to apply this header to source files (examples)
|
|
20
|
-
|
|
21
|
-
1) Prepend header to a single file (POSIX shells):
|
|
22
|
-
|
|
23
|
-
cat LICENSE_HEADER.txt "$FILE" > "$FILE".with-header && mv "$FILE".with-header "$FILE"
|
|
24
|
-
|
|
25
|
-
2) Apply to multiple files (example: the project's main entry files):
|
|
26
|
-
|
|
27
|
-
for f in packages/cli/bin/kibi packages/mcp/bin/kibi-mcp packages/cli/src/*.ts packages/mcp/src/*.ts; do
|
|
28
|
-
if [ -f "$f" ]; then
|
|
29
|
-
cp "$f" "$f".bak
|
|
30
|
-
(cat LICENSE_HEADER.txt; echo; cat "$f" ) > "$f".new && mv "$f".new "$f"
|
|
31
|
-
fi
|
|
32
|
-
done
|
|
33
|
-
|
|
34
|
-
3) Avoid duplicating the header: run a quick guard to only add if missing
|
|
35
|
-
|
|
36
|
-
for f in packages/cli/bin/kibi packages/mcp/bin/kibi-mcp; do
|
|
37
|
-
if [ -f "$f" ]; then
|
|
38
|
-
if ! head -n 5 "$f" | grep -q "Copyright (C) 2026 Piotr Franczyk"; then
|
|
39
|
-
cp "$f" "$f".bak
|
|
40
|
-
(cat LICENSE_HEADER.txt; echo; cat "$f" ) > "$f".new && mv "$f".new "$f"
|
|
41
|
-
fi
|
|
42
|
-
fi
|
|
43
|
-
done
|
|
44
|
-
*/
|
|
45
18
|
import * as path from "node:path";
|
|
46
19
|
import Table from "cli-table3";
|
|
47
20
|
import { PrologProcess } from "../prolog.js";
|
|
21
|
+
import { parseEntityFromBinding, parseEntityFromList, parseListOfLists, parsePrologValue, } from "../prolog/codec.js";
|
|
48
22
|
import relationshipSchema from "../public/schemas/relationship.js";
|
|
23
|
+
import { VALID_ENTITY_TYPES } from "../query/service.js";
|
|
49
24
|
import { resolveActiveBranch } from "../utils/branch-resolver.js";
|
|
50
25
|
const REL_TYPES = relationshipSchema.properties.type.enum;
|
|
51
26
|
export async function queryCommand(type, options) {
|
|
@@ -109,23 +84,11 @@ export async function queryCommand(type, options) {
|
|
|
109
84
|
// Query entities mode
|
|
110
85
|
else if (type || options.source) {
|
|
111
86
|
// Validate type if provided
|
|
112
|
-
if (type) {
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
"adr",
|
|
118
|
-
"flag",
|
|
119
|
-
"event",
|
|
120
|
-
"symbol",
|
|
121
|
-
"fact",
|
|
122
|
-
];
|
|
123
|
-
if (!validTypes.includes(type)) {
|
|
124
|
-
await prolog.query("kb_detach");
|
|
125
|
-
await prolog.terminate();
|
|
126
|
-
console.error(`Error: Invalid type '${type}'. Valid types: ${validTypes.join(", ")}`);
|
|
127
|
-
process.exit(1);
|
|
128
|
-
}
|
|
87
|
+
if (type && !VALID_ENTITY_TYPES.includes(type)) {
|
|
88
|
+
await prolog.query("kb_detach");
|
|
89
|
+
await prolog.terminate();
|
|
90
|
+
console.error(`Error: Invalid type '${type}'. Valid types: ${VALID_ENTITY_TYPES.join(", ")}`);
|
|
91
|
+
process.exit(1);
|
|
129
92
|
}
|
|
130
93
|
let goal;
|
|
131
94
|
if (options.source) {
|
|
@@ -206,250 +169,6 @@ export async function queryCommand(type, options) {
|
|
|
206
169
|
process.exit(1);
|
|
207
170
|
}
|
|
208
171
|
}
|
|
209
|
-
/**
|
|
210
|
-
* Parse a Prolog list of lists into a JavaScript array.
|
|
211
|
-
* Input: "[[a,b,c],[d,e,f]]"
|
|
212
|
-
* Output: [["a", "b", "c"], ["d", "e", "f"]]
|
|
213
|
-
*/
|
|
214
|
-
function parseListOfLists(listStr) {
|
|
215
|
-
// Clean input
|
|
216
|
-
const cleaned = listStr.trim().replace(/^\[/, "").replace(/\]$/, "");
|
|
217
|
-
if (cleaned === "") {
|
|
218
|
-
return [];
|
|
219
|
-
}
|
|
220
|
-
const results = [];
|
|
221
|
-
let depth = 0;
|
|
222
|
-
let inQuotes = false;
|
|
223
|
-
let current = "";
|
|
224
|
-
let currentList = [];
|
|
225
|
-
for (let i = 0; i < cleaned.length; i++) {
|
|
226
|
-
const char = cleaned[i];
|
|
227
|
-
const prevChar = i > 0 ? cleaned[i - 1] : "";
|
|
228
|
-
if (char === '"' && prevChar !== "\\") {
|
|
229
|
-
inQuotes = !inQuotes;
|
|
230
|
-
current += char;
|
|
231
|
-
continue;
|
|
232
|
-
}
|
|
233
|
-
if (inQuotes) {
|
|
234
|
-
current += char;
|
|
235
|
-
continue;
|
|
236
|
-
}
|
|
237
|
-
if (char === "[") {
|
|
238
|
-
depth++;
|
|
239
|
-
if (depth > 1)
|
|
240
|
-
current += char;
|
|
241
|
-
}
|
|
242
|
-
else if (char === "]") {
|
|
243
|
-
depth--;
|
|
244
|
-
if (depth === 0) {
|
|
245
|
-
if (current) {
|
|
246
|
-
currentList.push(current.trim());
|
|
247
|
-
current = "";
|
|
248
|
-
}
|
|
249
|
-
if (currentList.length > 0) {
|
|
250
|
-
results.push(currentList);
|
|
251
|
-
currentList = [];
|
|
252
|
-
}
|
|
253
|
-
}
|
|
254
|
-
else {
|
|
255
|
-
current += char;
|
|
256
|
-
}
|
|
257
|
-
}
|
|
258
|
-
else if (char === "," && depth === 1) {
|
|
259
|
-
if (current) {
|
|
260
|
-
currentList.push(current.trim());
|
|
261
|
-
current = "";
|
|
262
|
-
}
|
|
263
|
-
}
|
|
264
|
-
else if (char === "," && depth === 0) {
|
|
265
|
-
// Skip comma between lists
|
|
266
|
-
}
|
|
267
|
-
else {
|
|
268
|
-
current += char;
|
|
269
|
-
}
|
|
270
|
-
}
|
|
271
|
-
return results;
|
|
272
|
-
}
|
|
273
|
-
/**
|
|
274
|
-
* Parse a single entity from Prolog binding format.
|
|
275
|
-
* Input: "[abc123, req, [id=abc123, title=\"Test\", ...]]"
|
|
276
|
-
*/
|
|
277
|
-
function parseEntityFromBinding(bindingStr) {
|
|
278
|
-
const cleaned = bindingStr.trim().replace(/^\[/, "").replace(/\]$/, "");
|
|
279
|
-
const parts = splitTopLevel(cleaned, ",");
|
|
280
|
-
if (parts.length < 3) {
|
|
281
|
-
return {};
|
|
282
|
-
}
|
|
283
|
-
const id = parts[0].trim();
|
|
284
|
-
const type = parts[1].trim();
|
|
285
|
-
const propsStr = parts.slice(2).join(",").trim();
|
|
286
|
-
const props = parsePropertyList(propsStr);
|
|
287
|
-
return { id, type, ...props };
|
|
288
|
-
}
|
|
289
|
-
/**
|
|
290
|
-
* Parse entity from array returned by parseListOfLists.
|
|
291
|
-
* Input: ["abc123", "req", "[id=abc123, title=\"Test\", ...]"]
|
|
292
|
-
*/
|
|
293
|
-
function parseEntityFromList(data) {
|
|
294
|
-
if (data.length < 3) {
|
|
295
|
-
return {};
|
|
296
|
-
}
|
|
297
|
-
const id = data[0].trim();
|
|
298
|
-
const type = data[1].trim();
|
|
299
|
-
const propsStr = data[2].trim();
|
|
300
|
-
const props = parsePropertyList(propsStr);
|
|
301
|
-
return { id, type, ...props };
|
|
302
|
-
}
|
|
303
|
-
/**
|
|
304
|
-
* Parse Prolog property list into JavaScript object.
|
|
305
|
-
* Input: "[id=abc123, title=^^(\"User Auth\", xsd:string), status='file:///path/approved', tags=^^(\"[security,auth]\", xsd:string)]"
|
|
306
|
-
* Output: { id: "abc123", title: "User Auth", status: "approved", tags: ["security", "auth"] }
|
|
307
|
-
*/
|
|
308
|
-
function parsePropertyList(propsStr) {
|
|
309
|
-
const props = {};
|
|
310
|
-
// Remove outer brackets
|
|
311
|
-
let cleaned = propsStr.trim();
|
|
312
|
-
if (cleaned.startsWith("[")) {
|
|
313
|
-
cleaned = cleaned.substring(1);
|
|
314
|
-
}
|
|
315
|
-
if (cleaned.endsWith("]")) {
|
|
316
|
-
cleaned = cleaned.substring(0, cleaned.length - 1);
|
|
317
|
-
}
|
|
318
|
-
// Split by top-level commas
|
|
319
|
-
const pairs = splitTopLevel(cleaned, ",");
|
|
320
|
-
for (const pair of pairs) {
|
|
321
|
-
const eqIndex = pair.indexOf("=");
|
|
322
|
-
if (eqIndex === -1)
|
|
323
|
-
continue;
|
|
324
|
-
const key = pair.substring(0, eqIndex).trim();
|
|
325
|
-
const value = pair.substring(eqIndex + 1).trim();
|
|
326
|
-
if (key === "..." || value === "..." || value === "...|...") {
|
|
327
|
-
continue;
|
|
328
|
-
}
|
|
329
|
-
const parsed = parsePrologValue(value);
|
|
330
|
-
props[key] = parsed;
|
|
331
|
-
}
|
|
332
|
-
return props;
|
|
333
|
-
}
|
|
334
|
-
/**
|
|
335
|
-
* Parse a single Prolog value, handling typed literals and URIs.
|
|
336
|
-
* Examples:
|
|
337
|
-
* - ^^("value", 'http://...#string') -> "value"
|
|
338
|
-
* - 'file:///path/to/id' -> "id" (extract last segment)
|
|
339
|
-
* - "string" -> "string"
|
|
340
|
-
* - atom -> "atom"
|
|
341
|
-
* - [a,b,c] -> ["a", "b", "c"]
|
|
342
|
-
*/
|
|
343
|
-
function parsePrologValue(value) {
|
|
344
|
-
value = value.trim();
|
|
345
|
-
if (value.startsWith("^^(")) {
|
|
346
|
-
const innerStart = value.indexOf("(") + 1;
|
|
347
|
-
let depth = 1;
|
|
348
|
-
let innerEnd = innerStart;
|
|
349
|
-
for (let i = innerStart; i < value.length; i++) {
|
|
350
|
-
if (value[i] === "(")
|
|
351
|
-
depth++;
|
|
352
|
-
if (value[i] === ")") {
|
|
353
|
-
depth--;
|
|
354
|
-
if (depth === 0) {
|
|
355
|
-
innerEnd = i;
|
|
356
|
-
break;
|
|
357
|
-
}
|
|
358
|
-
}
|
|
359
|
-
}
|
|
360
|
-
const innerContent = value.substring(innerStart, innerEnd);
|
|
361
|
-
const parts = splitTopLevel(innerContent, ",");
|
|
362
|
-
if (parts.length >= 2) {
|
|
363
|
-
let literalValue = parts[0].trim();
|
|
364
|
-
if (literalValue.startsWith('"') && literalValue.endsWith('"')) {
|
|
365
|
-
literalValue = literalValue.substring(1, literalValue.length - 1);
|
|
366
|
-
}
|
|
367
|
-
if (literalValue.startsWith("[") && literalValue.endsWith("]")) {
|
|
368
|
-
const listContent = literalValue.substring(1, literalValue.length - 1);
|
|
369
|
-
if (listContent === "") {
|
|
370
|
-
return [];
|
|
371
|
-
}
|
|
372
|
-
return listContent.split(",").map((item) => item.trim());
|
|
373
|
-
}
|
|
374
|
-
return literalValue;
|
|
375
|
-
}
|
|
376
|
-
}
|
|
377
|
-
if (value.startsWith("file:///")) {
|
|
378
|
-
const cleaned = value;
|
|
379
|
-
const lastSlash = cleaned.lastIndexOf("/");
|
|
380
|
-
if (lastSlash !== -1) {
|
|
381
|
-
return cleaned.substring(lastSlash + 1);
|
|
382
|
-
}
|
|
383
|
-
return cleaned;
|
|
384
|
-
}
|
|
385
|
-
// Handle quoted string
|
|
386
|
-
if (value.startsWith('"') && value.endsWith('"')) {
|
|
387
|
-
return value.substring(1, value.length - 1);
|
|
388
|
-
}
|
|
389
|
-
// Handle quoted atom (may contain file URLs that need extraction)
|
|
390
|
-
if (value.startsWith("'") && value.endsWith("'")) {
|
|
391
|
-
const unquoted = value.substring(1, value.length - 1);
|
|
392
|
-
// Check if unquoted value is a file URL
|
|
393
|
-
if (unquoted.startsWith("file:///")) {
|
|
394
|
-
const lastSlash = unquoted.lastIndexOf("/");
|
|
395
|
-
if (lastSlash !== -1) {
|
|
396
|
-
return unquoted.substring(lastSlash + 1);
|
|
397
|
-
}
|
|
398
|
-
}
|
|
399
|
-
return unquoted;
|
|
400
|
-
}
|
|
401
|
-
// Handle list
|
|
402
|
-
if (value.startsWith("[") && value.endsWith("]")) {
|
|
403
|
-
const listContent = value.substring(1, value.length - 1);
|
|
404
|
-
if (listContent === "") {
|
|
405
|
-
return [];
|
|
406
|
-
}
|
|
407
|
-
const items = listContent.split(",").map((item) => {
|
|
408
|
-
return parsePrologValue(item.trim());
|
|
409
|
-
});
|
|
410
|
-
return items;
|
|
411
|
-
}
|
|
412
|
-
// Return as-is
|
|
413
|
-
return value;
|
|
414
|
-
}
|
|
415
|
-
/**
|
|
416
|
-
* Split a string by delimiter at the top level (not inside brackets or quotes).
|
|
417
|
-
*/
|
|
418
|
-
function splitTopLevel(str, delimiter) {
|
|
419
|
-
const results = [];
|
|
420
|
-
let current = "";
|
|
421
|
-
let depth = 0;
|
|
422
|
-
let inQuotes = false;
|
|
423
|
-
for (let i = 0; i < str.length; i++) {
|
|
424
|
-
const char = str[i];
|
|
425
|
-
const prevChar = i > 0 ? str[i - 1] : "";
|
|
426
|
-
if (char === '"' && prevChar !== "\\") {
|
|
427
|
-
inQuotes = !inQuotes;
|
|
428
|
-
current += char;
|
|
429
|
-
}
|
|
430
|
-
else if (!inQuotes && (char === "[" || char === "(")) {
|
|
431
|
-
depth++;
|
|
432
|
-
current += char;
|
|
433
|
-
}
|
|
434
|
-
else if (!inQuotes && (char === "]" || char === ")")) {
|
|
435
|
-
depth--;
|
|
436
|
-
current += char;
|
|
437
|
-
}
|
|
438
|
-
else if (!inQuotes && depth === 0 && char === delimiter) {
|
|
439
|
-
if (current) {
|
|
440
|
-
results.push(current);
|
|
441
|
-
current = "";
|
|
442
|
-
}
|
|
443
|
-
}
|
|
444
|
-
else {
|
|
445
|
-
current += char;
|
|
446
|
-
}
|
|
447
|
-
}
|
|
448
|
-
if (current) {
|
|
449
|
-
results.push(current);
|
|
450
|
-
}
|
|
451
|
-
return results;
|
|
452
|
-
}
|
|
453
172
|
/**
|
|
454
173
|
* Output results as a formatted table.
|
|
455
174
|
*/
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export type SyncCache = {
|
|
2
|
+
version: number;
|
|
3
|
+
hashes: Record<string, string>;
|
|
4
|
+
seenAt: Record<string, string>;
|
|
5
|
+
};
|
|
6
|
+
export declare const SYNC_CACHE_VERSION = 1;
|
|
7
|
+
export declare const SYNC_CACHE_TTL_MS: number;
|
|
8
|
+
export declare function toCacheKey(filePath: string): string;
|
|
9
|
+
export declare function hashFile(filePath: string): string;
|
|
10
|
+
export declare function readSyncCache(cachePath: string): SyncCache;
|
|
11
|
+
export declare function writeSyncCache(cachePath: string, cache: SyncCache): void;
|
|
12
|
+
export declare function copySyncCache(livePath: string, stagingPath: string): void;
|
|
13
|
+
//# sourceMappingURL=cache.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cache.d.ts","sourceRoot":"","sources":["../../../src/commands/sync/cache.ts"],"names":[],"mappings":"AAsBA,MAAM,MAAM,SAAS,GAAG;IACtB,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC/B,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CAChC,CAAC;AAEF,eAAO,MAAM,kBAAkB,IAAI,CAAC;AACpC,eAAO,MAAM,iBAAiB,QAA2B,CAAC;AAE1D,wBAAgB,UAAU,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAEnD;AAED,wBAAgB,QAAQ,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAGjD;AAED,wBAAgB,aAAa,CAAC,SAAS,EAAE,MAAM,GAAG,SAAS,CAiC1D;AAED,wBAAgB,cAAc,CAAC,SAAS,EAAE,MAAM,EAAE,KAAK,EAAE,SAAS,GAAG,IAAI,CAYxE;AAED,wBAAgB,aAAa,CAAC,QAAQ,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,GAAG,IAAI,CAQzE"}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Kibi — repo-local, per-branch, queryable long-term memory for software projects
|
|
3
|
+
Copyright (C) 2026 Piotr Franczyk
|
|
4
|
+
|
|
5
|
+
This program is free software: you can redistribute it and/or modify
|
|
6
|
+
it under the terms of the GNU Affero General Public License as published by
|
|
7
|
+
the Free Software Foundation, either version 3 of the License, or
|
|
8
|
+
(at your option) any later version.
|
|
9
|
+
|
|
10
|
+
This program is distributed in the hope that it will be useful,
|
|
11
|
+
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
12
|
+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
13
|
+
GNU Affero General Public License for more details.
|
|
14
|
+
|
|
15
|
+
You should have received a copy of the GNU Affero General Public License
|
|
16
|
+
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
17
|
+
*/
|
|
18
|
+
import { createHash } from "node:crypto";
|
|
19
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
20
|
+
import * as path from "node:path";
|
|
21
|
+
export const SYNC_CACHE_VERSION = 1;
|
|
22
|
+
export const SYNC_CACHE_TTL_MS = 30 * 24 * 60 * 60 * 1000;
|
|
23
|
+
export function toCacheKey(filePath) {
|
|
24
|
+
return path.relative(process.cwd(), filePath).split(path.sep).join("/");
|
|
25
|
+
}
|
|
26
|
+
export function hashFile(filePath) {
|
|
27
|
+
const content = readFileSync(filePath);
|
|
28
|
+
return createHash("sha256").update(content).digest("hex");
|
|
29
|
+
}
|
|
30
|
+
export function readSyncCache(cachePath) {
|
|
31
|
+
if (!existsSync(cachePath)) {
|
|
32
|
+
return {
|
|
33
|
+
version: SYNC_CACHE_VERSION,
|
|
34
|
+
hashes: {},
|
|
35
|
+
seenAt: {},
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
try {
|
|
39
|
+
const parsed = JSON.parse(readFileSync(cachePath, "utf8"));
|
|
40
|
+
if (parsed.version !== SYNC_CACHE_VERSION) {
|
|
41
|
+
return {
|
|
42
|
+
version: SYNC_CACHE_VERSION,
|
|
43
|
+
hashes: {},
|
|
44
|
+
seenAt: {},
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
return {
|
|
48
|
+
version: SYNC_CACHE_VERSION,
|
|
49
|
+
hashes: parsed.hashes ?? {},
|
|
50
|
+
seenAt: parsed.seenAt ?? {},
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
catch {
|
|
54
|
+
return {
|
|
55
|
+
version: SYNC_CACHE_VERSION,
|
|
56
|
+
hashes: {},
|
|
57
|
+
seenAt: {},
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
export function writeSyncCache(cachePath, cache) {
|
|
62
|
+
const cacheDir = path.dirname(cachePath);
|
|
63
|
+
if (!existsSync(cacheDir)) {
|
|
64
|
+
mkdirSync(cacheDir, { recursive: true });
|
|
65
|
+
}
|
|
66
|
+
writeFileSync(cachePath, `${JSON.stringify(cache, null, 2)}
|
|
67
|
+
`, "utf8");
|
|
68
|
+
}
|
|
69
|
+
export function copySyncCache(livePath, stagingPath) {
|
|
70
|
+
const liveCachePath = path.join(livePath, "sync-cache.json");
|
|
71
|
+
const stagingCachePath = path.join(stagingPath, "sync-cache.json");
|
|
72
|
+
if (existsSync(liveCachePath)) {
|
|
73
|
+
const cacheContent = readFileSync(liveCachePath, "utf8");
|
|
74
|
+
writeFileSync(stagingCachePath, cacheContent, "utf8");
|
|
75
|
+
}
|
|
76
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { KbConfigPaths } from "../../utils/config.js";
|
|
2
|
+
export declare function normalizeMarkdownPath(pattern: string | undefined): string | null;
|
|
3
|
+
export declare function discoverSourceFiles(cwd: string, paths: KbConfigPaths): Promise<{
|
|
4
|
+
markdownFiles: string[];
|
|
5
|
+
manifestFiles: string[];
|
|
6
|
+
relationshipsDir: string;
|
|
7
|
+
}>;
|
|
8
|
+
//# sourceMappingURL=discovery.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"discovery.d.ts","sourceRoot":"","sources":["../../../src/commands/sync/discovery.ts"],"names":[],"mappings":"AAqBA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC;AAE3D,wBAAgB,qBAAqB,CACnC,OAAO,EAAE,MAAM,GAAG,SAAS,GAC1B,MAAM,GAAG,IAAI,CAIf;AAED,wBAAsB,mBAAmB,CACvC,GAAG,EAAE,MAAM,EACX,KAAK,EAAE,aAAa,GACnB,OAAO,CAAC;IACT,aAAa,EAAE,MAAM,EAAE,CAAC;IACxB,aAAa,EAAE,MAAM,EAAE,CAAC;IACxB,gBAAgB,EAAE,MAAM,CAAC;CAC1B,CAAC,CA0BD"}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Kibi — repo-local, per-branch, queryable long-term memory for software projects
|
|
3
|
+
Copyright (C) 2026 Piotr Franczyk
|
|
4
|
+
|
|
5
|
+
This program is free software: you can redistribute it and/or modify
|
|
6
|
+
it under the terms of the GNU Affero General Public License as published by
|
|
7
|
+
the Free Software Foundation, either version 3 of the License, or
|
|
8
|
+
(at your option) any later version.
|
|
9
|
+
|
|
10
|
+
This program is distributed in the hope that it will be useful,
|
|
11
|
+
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
12
|
+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
13
|
+
GNU Affero General Public License for more details.
|
|
14
|
+
|
|
15
|
+
You should have received a copy of the GNU Affero General Public License
|
|
16
|
+
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
17
|
+
*/
|
|
18
|
+
import * as path from "node:path";
|
|
19
|
+
import fg from "fast-glob";
|
|
20
|
+
import { getRelationshipsDir } from "../../extractors/relationships.js";
|
|
21
|
+
export function normalizeMarkdownPath(pattern) {
|
|
22
|
+
if (!pattern)
|
|
23
|
+
return null;
|
|
24
|
+
if (pattern.includes("*"))
|
|
25
|
+
return pattern;
|
|
26
|
+
return `${pattern}/**/*.md`;
|
|
27
|
+
}
|
|
28
|
+
export async function discoverSourceFiles(cwd, paths) {
|
|
29
|
+
const markdownPatterns = [
|
|
30
|
+
normalizeMarkdownPath(paths.requirements),
|
|
31
|
+
normalizeMarkdownPath(paths.scenarios),
|
|
32
|
+
normalizeMarkdownPath(paths.tests),
|
|
33
|
+
normalizeMarkdownPath(paths.adr),
|
|
34
|
+
normalizeMarkdownPath(paths.flags),
|
|
35
|
+
normalizeMarkdownPath(paths.events),
|
|
36
|
+
normalizeMarkdownPath(paths.facts),
|
|
37
|
+
].filter((p) => Boolean(p));
|
|
38
|
+
const markdownFiles = await fg(markdownPatterns, {
|
|
39
|
+
cwd,
|
|
40
|
+
absolute: true,
|
|
41
|
+
});
|
|
42
|
+
const manifestFiles = paths.symbols
|
|
43
|
+
? await fg(paths.symbols, {
|
|
44
|
+
cwd,
|
|
45
|
+
absolute: true,
|
|
46
|
+
})
|
|
47
|
+
: [];
|
|
48
|
+
const relationshipsDir = getRelationshipsDir(path.join(cwd, ".kb"));
|
|
49
|
+
return { markdownFiles, manifestFiles, relationshipsDir };
|
|
50
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { type ExtractionResult } from "../../extractors/markdown.js";
|
|
2
|
+
export interface ExtractionOutput {
|
|
3
|
+
results: ExtractionResult[];
|
|
4
|
+
failedCacheKeys: Set<string>;
|
|
5
|
+
errors: {
|
|
6
|
+
file: string;
|
|
7
|
+
message: string;
|
|
8
|
+
}[];
|
|
9
|
+
}
|
|
10
|
+
export declare function processExtractions(changedMarkdownFiles: string[], changedManifestFiles: string[], validateOnly: boolean): Promise<ExtractionOutput>;
|
|
11
|
+
//# sourceMappingURL=extraction.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"extraction.d.ts","sourceRoot":"","sources":["../../../src/commands/sync/extraction.ts"],"names":[],"mappings":"AAmBA,OAAO,EACL,KAAK,gBAAgB,EAGtB,MAAM,8BAA8B,CAAC;AAGtC,MAAM,WAAW,gBAAgB;IAC/B,OAAO,EAAE,gBAAgB,EAAE,CAAC;IAC5B,eAAe,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;IAC7B,MAAM,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC;CAC7C;AAED,wBAAsB,kBAAkB,CACtC,oBAAoB,EAAE,MAAM,EAAE,EAC9B,oBAAoB,EAAE,MAAM,EAAE,EAC9B,YAAY,EAAE,OAAO,GACpB,OAAO,CAAC,gBAAgB,CAAC,CAoD3B"}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Kibi — repo-local, per-branch, queryable long-term memory for software projects
|
|
3
|
+
Copyright (C) 2026 Piotr Franczyk
|
|
4
|
+
|
|
5
|
+
This program is free software: you can redistribute it and/or modify
|
|
6
|
+
it under the terms of the GNU Affero General Public License as published by
|
|
7
|
+
the Free Software Foundation, either version 3 of the License, or
|
|
8
|
+
(at your option) any later version.
|
|
9
|
+
|
|
10
|
+
This program is distributed in the hope that it will be useful,
|
|
11
|
+
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
12
|
+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
13
|
+
GNU Affero General Public License for more details.
|
|
14
|
+
|
|
15
|
+
You should have received a copy of the GNU Affero General Public License
|
|
16
|
+
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
17
|
+
*/
|
|
18
|
+
import { extractFromManifest } from "../../extractors/manifest.js";
|
|
19
|
+
import { FrontmatterError, extractFromMarkdown, } from "../../extractors/markdown.js";
|
|
20
|
+
import { toCacheKey } from "./cache.js";
|
|
21
|
+
export async function processExtractions(changedMarkdownFiles, changedManifestFiles, validateOnly) {
|
|
22
|
+
const results = [];
|
|
23
|
+
const failedCacheKeys = new Set();
|
|
24
|
+
const errors = [];
|
|
25
|
+
for (const file of changedMarkdownFiles) {
|
|
26
|
+
try {
|
|
27
|
+
results.push(extractFromMarkdown(file));
|
|
28
|
+
}
|
|
29
|
+
catch (error) {
|
|
30
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
31
|
+
// Handle INVALID_AUTHORING diagnostics for embedded entities
|
|
32
|
+
if (error instanceof FrontmatterError &&
|
|
33
|
+
error.classification === "Embedded Entity Violation") {
|
|
34
|
+
const embeddedTypes = message.includes("scenario") && message.includes("test")
|
|
35
|
+
? ["scenario", "test"]
|
|
36
|
+
: message.includes("scenario")
|
|
37
|
+
? ["scenario"]
|
|
38
|
+
: message.includes("test")
|
|
39
|
+
? ["test"]
|
|
40
|
+
: ["entity"];
|
|
41
|
+
// Note: diagnostics are created by the caller
|
|
42
|
+
}
|
|
43
|
+
if (validateOnly) {
|
|
44
|
+
errors.push({ file, message });
|
|
45
|
+
}
|
|
46
|
+
else {
|
|
47
|
+
console.warn(`Warning: Failed to extract from ${file}: ${message}`);
|
|
48
|
+
}
|
|
49
|
+
failedCacheKeys.add(toCacheKey(file));
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
for (const file of changedManifestFiles) {
|
|
53
|
+
try {
|
|
54
|
+
const manifestResults = extractFromManifest(file);
|
|
55
|
+
results.push(...manifestResults);
|
|
56
|
+
}
|
|
57
|
+
catch (error) {
|
|
58
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
59
|
+
if (validateOnly) {
|
|
60
|
+
errors.push({ file, message });
|
|
61
|
+
}
|
|
62
|
+
else {
|
|
63
|
+
console.warn(`Warning: Failed to extract from ${file}: ${message}`);
|
|
64
|
+
}
|
|
65
|
+
failedCacheKeys.add(toCacheKey(file));
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
return { results, failedCacheKeys, errors };
|
|
69
|
+
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import { type ManifestSymbolEntry } from "../../extractors/symbols-coordinator.js";
|
|
2
|
+
export declare function refreshManifestCoordinates(manifestPath: string, workspaceRoot: string): Promise<void>;
|
|
3
|
+
export declare function hasAllGeneratedCoordinates(entry: ManifestSymbolEntry): boolean;
|
|
4
|
+
export declare function isEligibleForCoordinateRefresh(sourceFile: string | undefined, workspaceRoot: string): boolean;
|
|
5
|
+
//# sourceMappingURL=manifest.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"manifest.d.ts","sourceRoot":"","sources":["../../../src/commands/sync/manifest.ts"],"names":[],"mappings":"AAqBA,OAAO,EACL,KAAK,mBAAmB,EAEzB,MAAM,yCAAyC,CAAC;AA6BjD,wBAAsB,0BAA0B,CAC9C,YAAY,EAAE,MAAM,EACpB,aAAa,EAAE,MAAM,GACpB,OAAO,CAAC,IAAI,CAAC,CAyEf;AAED,wBAAgB,0BAA0B,CACxC,KAAK,EAAE,mBAAmB,GACzB,OAAO,CAST;AAED,wBAAgB,8BAA8B,CAC5C,UAAU,EAAE,MAAM,GAAG,SAAS,EAC9B,aAAa,EAAE,MAAM,GACpB,OAAO,CAST"}
|