kibi-mcp 0.9.0 → 0.11.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/server/docs.js +29 -30
- package/dist/tools/autopilot-candidates.js +220 -14
- package/dist/tools/autopilot-discovery.js +677 -51
- package/dist/tools/autopilot-generate.js +467 -177
- package/dist/tools/briefing-generate.js +28 -6
- package/dist/tools/delete.js +33 -3
- package/dist/tools/upsert.js +3 -3
- package/dist/tools-config.js +31 -1
- package/package.json +3 -3
|
@@ -6,7 +6,33 @@
|
|
|
6
6
|
*/
|
|
7
7
|
import fs from "node:fs";
|
|
8
8
|
import path from "node:path";
|
|
9
|
+
import fg from "fast-glob";
|
|
10
|
+
import * as cliSymbolCoordinator from "kibi-cli/extractors/symbols-coordinator";
|
|
9
11
|
import { runJsonModuleQuery } from "./core-module.js";
|
|
12
|
+
// implements REQ-001
|
|
13
|
+
export const AUTOPILOT_PROVIDER_ORDER = [
|
|
14
|
+
"typed_kibi_docs",
|
|
15
|
+
"generic_repo_docs",
|
|
16
|
+
"repo_metadata",
|
|
17
|
+
"repo_layout",
|
|
18
|
+
"test_topology",
|
|
19
|
+
"source_symbols",
|
|
20
|
+
];
|
|
21
|
+
const IGNORED_DIRECTORY_NAMES = new Set([
|
|
22
|
+
".git",
|
|
23
|
+
".kb",
|
|
24
|
+
".venv",
|
|
25
|
+
"build",
|
|
26
|
+
"coverage",
|
|
27
|
+
"dist",
|
|
28
|
+
"node_modules",
|
|
29
|
+
"target",
|
|
30
|
+
"third-party",
|
|
31
|
+
"third_party",
|
|
32
|
+
"vendor",
|
|
33
|
+
"vendors",
|
|
34
|
+
"venv",
|
|
35
|
+
]);
|
|
10
36
|
// Minimal copy of the opencode defaults used by other packages. Keep in sync
|
|
11
37
|
// with packages/opencode/src/file-filter.ts DEFAULT_SYNC_PATHS.
|
|
12
38
|
const DEFAULT_SYNC_PATHS = {
|
|
@@ -19,8 +45,51 @@ const DEFAULT_SYNC_PATHS = {
|
|
|
19
45
|
facts: "documentation/facts/**/*.md",
|
|
20
46
|
symbols: "documentation/symbols.yaml",
|
|
21
47
|
};
|
|
48
|
+
const SOURCE_LANGUAGE_EXTENSIONS = {
|
|
49
|
+
".ts": "typescript",
|
|
50
|
+
".tsx": "typescript",
|
|
51
|
+
".mts": "typescript",
|
|
52
|
+
".cts": "typescript",
|
|
53
|
+
".js": "javascript",
|
|
54
|
+
".jsx": "javascript",
|
|
55
|
+
".mjs": "javascript",
|
|
56
|
+
".cjs": "javascript",
|
|
57
|
+
".py": "python",
|
|
58
|
+
".rb": "ruby",
|
|
59
|
+
".go": "go",
|
|
60
|
+
".rs": "rust",
|
|
61
|
+
".java": "java",
|
|
62
|
+
".kt": "kotlin",
|
|
63
|
+
".swift": "swift",
|
|
64
|
+
".php": "php",
|
|
65
|
+
".c": "c",
|
|
66
|
+
".cc": "cpp",
|
|
67
|
+
".cpp": "cpp",
|
|
68
|
+
".h": "c",
|
|
69
|
+
".hpp": "cpp",
|
|
70
|
+
};
|
|
71
|
+
const PROJECT_SIGNAL_FILES = [
|
|
72
|
+
"README.md",
|
|
73
|
+
"README.mdx",
|
|
74
|
+
"package.json",
|
|
75
|
+
"tsconfig.json",
|
|
76
|
+
"pyproject.toml",
|
|
77
|
+
"Cargo.toml",
|
|
78
|
+
"go.mod",
|
|
79
|
+
];
|
|
80
|
+
const PROJECT_SIGNAL_DIRS = [
|
|
81
|
+
"src",
|
|
82
|
+
"app",
|
|
83
|
+
"apps",
|
|
84
|
+
"packages",
|
|
85
|
+
"tests",
|
|
86
|
+
"test",
|
|
87
|
+
"docs",
|
|
88
|
+
"documentation",
|
|
89
|
+
"scripts",
|
|
90
|
+
];
|
|
22
91
|
function findVendoredTrees(cwd) {
|
|
23
|
-
const results =
|
|
92
|
+
const results = new Set();
|
|
24
93
|
const vendoredMarkers = [
|
|
25
94
|
["kibi", "opencode.json"],
|
|
26
95
|
["kibi", "package.json"],
|
|
@@ -30,7 +99,7 @@ function findVendoredTrees(cwd) {
|
|
|
30
99
|
for (const marker of vendoredMarkers) {
|
|
31
100
|
const markerPath = path.join(cwd, ...marker);
|
|
32
101
|
if (fs.existsSync(markerPath)) {
|
|
33
|
-
results.
|
|
102
|
+
results.add(marker[0] ?? "kibi");
|
|
34
103
|
}
|
|
35
104
|
}
|
|
36
105
|
const nodeModules = path.join(cwd, "node_modules");
|
|
@@ -38,7 +107,7 @@ function findVendoredTrees(cwd) {
|
|
|
38
107
|
try {
|
|
39
108
|
for (const entry of fs.readdirSync(nodeModules)) {
|
|
40
109
|
if (entry === "kibi" || entry.startsWith("kibi-")) {
|
|
41
|
-
results.
|
|
110
|
+
results.add(`node_modules/${entry}`);
|
|
42
111
|
}
|
|
43
112
|
}
|
|
44
113
|
}
|
|
@@ -46,11 +115,30 @@ function findVendoredTrees(cwd) {
|
|
|
46
115
|
// ignore
|
|
47
116
|
}
|
|
48
117
|
}
|
|
49
|
-
return Array.from(
|
|
118
|
+
return Array.from(results).sort();
|
|
50
119
|
}
|
|
51
120
|
function rootKbConfigExists(cwd) {
|
|
52
121
|
return fs.existsSync(path.join(cwd, ".kb", "config.json"));
|
|
53
122
|
}
|
|
123
|
+
function hasWorkspaceProjectSignals(cwd, vendoredRoots) {
|
|
124
|
+
const vendoredTopLevel = new Set(vendoredRoots
|
|
125
|
+
.map((item) => item.split("/")[0])
|
|
126
|
+
.filter((item) => Boolean(item)));
|
|
127
|
+
for (const fileName of PROJECT_SIGNAL_FILES) {
|
|
128
|
+
if (fs.existsSync(path.join(cwd, fileName))) {
|
|
129
|
+
return true;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
for (const dirName of PROJECT_SIGNAL_DIRS) {
|
|
133
|
+
if (vendoredTopLevel.has(dirName))
|
|
134
|
+
continue;
|
|
135
|
+
const candidate = path.join(cwd, dirName);
|
|
136
|
+
if (fs.existsSync(candidate) && fs.statSync(candidate).isDirectory()) {
|
|
137
|
+
return true;
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
return false;
|
|
141
|
+
}
|
|
54
142
|
function readRootConfig(cwd) {
|
|
55
143
|
try {
|
|
56
144
|
const raw = fs.readFileSync(path.join(cwd, ".kb", "config.json"), "utf8");
|
|
@@ -80,6 +168,575 @@ function normalizePattern(p) {
|
|
|
80
168
|
return p;
|
|
81
169
|
return `${p.replace(/\/+$/, "")}/**/*.md`;
|
|
82
170
|
}
|
|
171
|
+
function buildSourceSummary(activation, vendored) {
|
|
172
|
+
return {
|
|
173
|
+
activationState: activation.activationState,
|
|
174
|
+
activationMode: activation.activationMode,
|
|
175
|
+
applyBlocked: activation.applyBlocked,
|
|
176
|
+
reason: activation.reason,
|
|
177
|
+
...(activation.handoffMessage
|
|
178
|
+
? { handoffMessage: activation.handoffMessage }
|
|
179
|
+
: {}),
|
|
180
|
+
...(vendored.length > 0 ? { vendored } : {}),
|
|
181
|
+
};
|
|
182
|
+
}
|
|
183
|
+
function createEmptyProviderCounts() {
|
|
184
|
+
return Object.fromEntries(AUTOPILOT_PROVIDER_ORDER.map((provider) => [provider, 0]));
|
|
185
|
+
}
|
|
186
|
+
function sortUnique(values) {
|
|
187
|
+
return Array.from(new Set(values)).filter(Boolean).sort();
|
|
188
|
+
}
|
|
189
|
+
function toRelativePosixPath(workspaceRoot, targetPath) {
|
|
190
|
+
return path.relative(workspaceRoot, targetPath).split(path.sep).join("/");
|
|
191
|
+
}
|
|
192
|
+
function normalizeDiscoveryPaths(cwd) {
|
|
193
|
+
const config = readRootConfig(cwd) || {};
|
|
194
|
+
const configured = config.paths ?? {};
|
|
195
|
+
const readPath = (key) => {
|
|
196
|
+
const configuredValue = configured[key];
|
|
197
|
+
if (typeof configuredValue === "string" && configuredValue.length > 0) {
|
|
198
|
+
return configuredValue;
|
|
199
|
+
}
|
|
200
|
+
const fallbackValue = DEFAULT_SYNC_PATHS[key];
|
|
201
|
+
return typeof fallbackValue === "string" ? fallbackValue : "";
|
|
202
|
+
};
|
|
203
|
+
return {
|
|
204
|
+
requirements: readPath("requirements"),
|
|
205
|
+
scenarios: readPath("scenarios"),
|
|
206
|
+
tests: readPath("tests"),
|
|
207
|
+
adr: readPath("adr"),
|
|
208
|
+
flags: readPath("flags"),
|
|
209
|
+
events: readPath("events"),
|
|
210
|
+
facts: readPath("facts"),
|
|
211
|
+
symbols: readPath("symbols"),
|
|
212
|
+
};
|
|
213
|
+
}
|
|
214
|
+
function buildIgnoredGlobs(vendoredRoots) {
|
|
215
|
+
const ignored = new Set();
|
|
216
|
+
for (const dirName of IGNORED_DIRECTORY_NAMES) {
|
|
217
|
+
ignored.add(`**/${dirName}`);
|
|
218
|
+
ignored.add(`**/${dirName}/**`);
|
|
219
|
+
}
|
|
220
|
+
for (const vendoredRoot of vendoredRoots) {
|
|
221
|
+
const normalized = vendoredRoot.replace(/\\/g, "/").replace(/^\.\//, "");
|
|
222
|
+
if (!normalized)
|
|
223
|
+
continue;
|
|
224
|
+
ignored.add(normalized);
|
|
225
|
+
ignored.add(`${normalized}/**`);
|
|
226
|
+
ignored.add(`**/${normalized}`);
|
|
227
|
+
ignored.add(`**/${normalized}/**`);
|
|
228
|
+
}
|
|
229
|
+
return Array.from(ignored);
|
|
230
|
+
}
|
|
231
|
+
function detectLanguagesFromPaths(paths) {
|
|
232
|
+
const detected = new Set();
|
|
233
|
+
for (const filePath of paths) {
|
|
234
|
+
const language = SOURCE_LANGUAGE_EXTENSIONS[path.extname(filePath).toLowerCase()];
|
|
235
|
+
if (language) {
|
|
236
|
+
detected.add(language);
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
return Array.from(detected);
|
|
240
|
+
}
|
|
241
|
+
function createFileEvidence(provider, kind, workspaceRoot, absolutePath, data = {}) {
|
|
242
|
+
const relativePath = toRelativePosixPath(workspaceRoot, absolutePath);
|
|
243
|
+
return {
|
|
244
|
+
provider,
|
|
245
|
+
kind,
|
|
246
|
+
label: relativePath,
|
|
247
|
+
relativePath,
|
|
248
|
+
absolutePath,
|
|
249
|
+
data,
|
|
250
|
+
};
|
|
251
|
+
}
|
|
252
|
+
function runTypedKibiDocsProvider(workspaceRoot) {
|
|
253
|
+
const discoveryPaths = normalizeDiscoveryPaths(workspaceRoot);
|
|
254
|
+
const markdownPatterns = [
|
|
255
|
+
normalizePattern(discoveryPaths.requirements),
|
|
256
|
+
normalizePattern(discoveryPaths.scenarios),
|
|
257
|
+
normalizePattern(discoveryPaths.tests),
|
|
258
|
+
normalizePattern(discoveryPaths.adr),
|
|
259
|
+
normalizePattern(discoveryPaths.flags),
|
|
260
|
+
normalizePattern(discoveryPaths.events),
|
|
261
|
+
normalizePattern(discoveryPaths.facts),
|
|
262
|
+
].filter((pattern) => Boolean(pattern));
|
|
263
|
+
const markdownFiles = fg.sync(markdownPatterns, {
|
|
264
|
+
cwd: workspaceRoot,
|
|
265
|
+
absolute: true,
|
|
266
|
+
onlyFiles: true,
|
|
267
|
+
unique: true,
|
|
268
|
+
suppressErrors: true,
|
|
269
|
+
});
|
|
270
|
+
const manifestFiles = discoveryPaths.symbols
|
|
271
|
+
? fg.sync(discoveryPaths.symbols, {
|
|
272
|
+
cwd: workspaceRoot,
|
|
273
|
+
absolute: true,
|
|
274
|
+
onlyFiles: true,
|
|
275
|
+
unique: true,
|
|
276
|
+
suppressErrors: true,
|
|
277
|
+
})
|
|
278
|
+
: [];
|
|
279
|
+
const evidence = [
|
|
280
|
+
...sortUnique(markdownFiles).map((absolutePath) => createFileEvidence("typed_kibi_docs", "typed_markdown", workspaceRoot, absolutePath)),
|
|
281
|
+
...sortUnique(manifestFiles).map((absolutePath) => createFileEvidence("typed_kibi_docs", "symbol_manifest", workspaceRoot, absolutePath)),
|
|
282
|
+
];
|
|
283
|
+
return {
|
|
284
|
+
provider: "typed_kibi_docs",
|
|
285
|
+
evidence,
|
|
286
|
+
};
|
|
287
|
+
}
|
|
288
|
+
function runGenericRepoDocsProvider(workspaceRoot, vendoredRoots, typedFilePaths) {
|
|
289
|
+
const markdownFiles = fg.sync("**/*.md", {
|
|
290
|
+
cwd: workspaceRoot,
|
|
291
|
+
absolute: true,
|
|
292
|
+
onlyFiles: true,
|
|
293
|
+
unique: true,
|
|
294
|
+
suppressErrors: true,
|
|
295
|
+
ignore: buildIgnoredGlobs(vendoredRoots),
|
|
296
|
+
});
|
|
297
|
+
const evidence = sortUnique(markdownFiles)
|
|
298
|
+
.map((absolutePath) => createFileEvidence("generic_repo_docs", "generic_markdown", workspaceRoot, absolutePath))
|
|
299
|
+
.filter((item) => !typedFilePaths.has(item.relativePath ?? ""));
|
|
300
|
+
return {
|
|
301
|
+
provider: "generic_repo_docs",
|
|
302
|
+
evidence,
|
|
303
|
+
};
|
|
304
|
+
}
|
|
305
|
+
function detectLanguagesFromPackageJson(packageJson) {
|
|
306
|
+
const detected = new Set();
|
|
307
|
+
const scripts = packageJson.scripts;
|
|
308
|
+
const bin = packageJson.bin;
|
|
309
|
+
if (typeof scripts === "object" && scripts) {
|
|
310
|
+
for (const value of Object.values(scripts)) {
|
|
311
|
+
if (typeof value === "string" && /\.(cts|mts|ts|tsx)\b|\b(tsx|ts-node)\b/i.test(value)) {
|
|
312
|
+
detected.add("typescript");
|
|
313
|
+
}
|
|
314
|
+
if (typeof value === "string" && /\.(cjs|mjs|js|jsx)\b/i.test(value)) {
|
|
315
|
+
detected.add("javascript");
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
if (typeof bin === "string" && /\.(cts|mts|ts|tsx)\b/i.test(bin)) {
|
|
320
|
+
detected.add("typescript");
|
|
321
|
+
}
|
|
322
|
+
if (typeof bin === "object" && bin) {
|
|
323
|
+
for (const value of Object.values(bin)) {
|
|
324
|
+
if (typeof value === "string" && /\.(cts|mts|ts|tsx)\b/i.test(value)) {
|
|
325
|
+
detected.add("typescript");
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
return Array.from(detected);
|
|
330
|
+
}
|
|
331
|
+
function runRepoMetadataProvider(workspaceRoot) {
|
|
332
|
+
const patterns = [
|
|
333
|
+
"package.json",
|
|
334
|
+
"opencode.json",
|
|
335
|
+
"tsconfig.json",
|
|
336
|
+
"tsconfig.*.json",
|
|
337
|
+
"bun.lock",
|
|
338
|
+
"bun.lockb",
|
|
339
|
+
"bunfig.toml",
|
|
340
|
+
"pnpm-workspace.yaml",
|
|
341
|
+
"pnpm-lock.yaml",
|
|
342
|
+
"package-lock.json",
|
|
343
|
+
"yarn.lock",
|
|
344
|
+
"Cargo.toml",
|
|
345
|
+
"go.mod",
|
|
346
|
+
"pyproject.toml",
|
|
347
|
+
"requirements*.txt",
|
|
348
|
+
];
|
|
349
|
+
const metadataFiles = fg.sync(patterns, {
|
|
350
|
+
cwd: workspaceRoot,
|
|
351
|
+
absolute: true,
|
|
352
|
+
onlyFiles: true,
|
|
353
|
+
unique: true,
|
|
354
|
+
suppressErrors: true,
|
|
355
|
+
});
|
|
356
|
+
const detectedLanguages = new Set();
|
|
357
|
+
const scanWarnings = [];
|
|
358
|
+
const evidence = [];
|
|
359
|
+
for (const absolutePath of sortUnique(metadataFiles)) {
|
|
360
|
+
const relativePath = toRelativePosixPath(workspaceRoot, absolutePath);
|
|
361
|
+
const basename = path.basename(relativePath);
|
|
362
|
+
const data = {
|
|
363
|
+
title: `Repository metadata: ${basename}`,
|
|
364
|
+
factKind: "meta",
|
|
365
|
+
confidence: basename.startsWith("tsconfig") ? 0.9 : 0.86,
|
|
366
|
+
evidence: [`repo_metadata:${relativePath}`],
|
|
367
|
+
};
|
|
368
|
+
if (basename.startsWith("tsconfig")) {
|
|
369
|
+
detectedLanguages.add("typescript");
|
|
370
|
+
}
|
|
371
|
+
if (basename === "Cargo.toml") {
|
|
372
|
+
detectedLanguages.add("rust");
|
|
373
|
+
}
|
|
374
|
+
if (basename === "go.mod") {
|
|
375
|
+
detectedLanguages.add("go");
|
|
376
|
+
}
|
|
377
|
+
if (basename === "pyproject.toml") {
|
|
378
|
+
detectedLanguages.add("python");
|
|
379
|
+
}
|
|
380
|
+
if (basename === "package.json") {
|
|
381
|
+
try {
|
|
382
|
+
const parsed = JSON.parse(fs.readFileSync(absolutePath, "utf8"));
|
|
383
|
+
for (const language of detectLanguagesFromPackageJson(parsed)) {
|
|
384
|
+
detectedLanguages.add(language);
|
|
385
|
+
}
|
|
386
|
+
if (typeof parsed.packageManager === "string") {
|
|
387
|
+
data.packageManager = parsed.packageManager;
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
catch (error) {
|
|
391
|
+
scanWarnings.push(`repo_metadata:failed_to_parse:${relativePath}`);
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
evidence.push(createFileEvidence("repo_metadata", "repo_metadata", workspaceRoot, absolutePath, data));
|
|
395
|
+
}
|
|
396
|
+
return {
|
|
397
|
+
provider: "repo_metadata",
|
|
398
|
+
evidence,
|
|
399
|
+
detectedLanguages: Array.from(detectedLanguages),
|
|
400
|
+
scanWarnings,
|
|
401
|
+
};
|
|
402
|
+
}
|
|
403
|
+
function runRepoLayoutProvider(workspaceRoot, vendoredRoots) {
|
|
404
|
+
const layoutRoots = ["src", "app", "apps", "packages", "tests", "test", "docs", "scripts"];
|
|
405
|
+
const evidence = [];
|
|
406
|
+
for (const relativePath of layoutRoots) {
|
|
407
|
+
const absolutePath = path.join(workspaceRoot, relativePath);
|
|
408
|
+
if (!fs.existsSync(absolutePath) || !fs.statSync(absolutePath).isDirectory()) {
|
|
409
|
+
continue;
|
|
410
|
+
}
|
|
411
|
+
evidence.push({
|
|
412
|
+
provider: "repo_layout",
|
|
413
|
+
kind: "repo_layout",
|
|
414
|
+
label: relativePath,
|
|
415
|
+
relativePath,
|
|
416
|
+
absolutePath,
|
|
417
|
+
data: {
|
|
418
|
+
title: `Repository layout: ${relativePath} directory`,
|
|
419
|
+
factKind: "observation",
|
|
420
|
+
confidence: 0.84,
|
|
421
|
+
evidence: [`repo_layout:${relativePath}`],
|
|
422
|
+
},
|
|
423
|
+
});
|
|
424
|
+
}
|
|
425
|
+
const codeFiles = fg.sync([
|
|
426
|
+
"src/**/*.{ts,tsx,mts,cts,js,jsx,mjs,cjs,py,rb,go,rs,java,kt,swift,php,c,cc,cpp,h,hpp}",
|
|
427
|
+
"app/**/*.{ts,tsx,mts,cts,js,jsx,mjs,cjs,py,rb,go,rs,java,kt,swift,php,c,cc,cpp,h,hpp}",
|
|
428
|
+
"apps/**/*.{ts,tsx,mts,cts,js,jsx,mjs,cjs,py,rb,go,rs,java,kt,swift,php,c,cc,cpp,h,hpp}",
|
|
429
|
+
"packages/**/*.{ts,tsx,mts,cts,js,jsx,mjs,cjs,py,rb,go,rs,java,kt,swift,php,c,cc,cpp,h,hpp}",
|
|
430
|
+
"tests/**/*.{ts,tsx,mts,cts,js,jsx,mjs,cjs,py,rb,go,rs,java,kt,swift,php,c,cc,cpp,h,hpp}",
|
|
431
|
+
"test/**/*.{ts,tsx,mts,cts,js,jsx,mjs,cjs,py,rb,go,rs,java,kt,swift,php,c,cc,cpp,h,hpp}",
|
|
432
|
+
], {
|
|
433
|
+
cwd: workspaceRoot,
|
|
434
|
+
absolute: true,
|
|
435
|
+
onlyFiles: true,
|
|
436
|
+
unique: true,
|
|
437
|
+
suppressErrors: true,
|
|
438
|
+
ignore: buildIgnoredGlobs(vendoredRoots),
|
|
439
|
+
});
|
|
440
|
+
return {
|
|
441
|
+
provider: "repo_layout",
|
|
442
|
+
evidence,
|
|
443
|
+
detectedLanguages: detectLanguagesFromPaths(codeFiles),
|
|
444
|
+
};
|
|
445
|
+
}
|
|
446
|
+
function detectTestFrameworksFromContent(content) {
|
|
447
|
+
const frameworks = new Set();
|
|
448
|
+
if (/\bbun:test\b/.test(content))
|
|
449
|
+
frameworks.add("bun:test");
|
|
450
|
+
if (/\bvitest\b/.test(content))
|
|
451
|
+
frameworks.add("vitest");
|
|
452
|
+
if (/\bnode:test\b/.test(content))
|
|
453
|
+
frameworks.add("node:test");
|
|
454
|
+
if (/\bmocha\b/.test(content))
|
|
455
|
+
frameworks.add("mocha");
|
|
456
|
+
if (/\bjest\b|@jest\/globals/.test(content))
|
|
457
|
+
frameworks.add("jest");
|
|
458
|
+
return Array.from(frameworks);
|
|
459
|
+
}
|
|
460
|
+
function runTestTopologyProvider(workspaceRoot, vendoredRoots) {
|
|
461
|
+
const testFiles = fg.sync([
|
|
462
|
+
"**/*.test.{ts,tsx,mts,cts,js,jsx,mjs,cjs}",
|
|
463
|
+
"**/*.spec.{ts,tsx,mts,cts,js,jsx,mjs,cjs}",
|
|
464
|
+
"**/__tests__/**/*.{ts,tsx,mts,cts,js,jsx,mjs,cjs}",
|
|
465
|
+
], {
|
|
466
|
+
cwd: workspaceRoot,
|
|
467
|
+
absolute: true,
|
|
468
|
+
onlyFiles: true,
|
|
469
|
+
unique: true,
|
|
470
|
+
suppressErrors: true,
|
|
471
|
+
ignore: buildIgnoredGlobs(vendoredRoots),
|
|
472
|
+
});
|
|
473
|
+
const detectedFrameworks = new Set();
|
|
474
|
+
const detectedLanguages = new Set();
|
|
475
|
+
const scanWarnings = [];
|
|
476
|
+
const evidence = [];
|
|
477
|
+
for (const absolutePath of sortUnique(testFiles)) {
|
|
478
|
+
const relativePath = toRelativePosixPath(workspaceRoot, absolutePath);
|
|
479
|
+
const frameworks = (() => {
|
|
480
|
+
try {
|
|
481
|
+
return detectTestFrameworksFromContent(fs.readFileSync(absolutePath, "utf8"));
|
|
482
|
+
}
|
|
483
|
+
catch (error) {
|
|
484
|
+
scanWarnings.push(`test_topology:failed_to_read:${relativePath}`);
|
|
485
|
+
return [];
|
|
486
|
+
}
|
|
487
|
+
})();
|
|
488
|
+
for (const framework of frameworks) {
|
|
489
|
+
detectedFrameworks.add(framework);
|
|
490
|
+
}
|
|
491
|
+
for (const language of detectLanguagesFromPaths([absolutePath])) {
|
|
492
|
+
detectedLanguages.add(language);
|
|
493
|
+
}
|
|
494
|
+
evidence.push(createFileEvidence("test_topology", "test_topology", workspaceRoot, absolutePath, {
|
|
495
|
+
title: frameworks.length > 0
|
|
496
|
+
? `Test topology: ${frameworks.join(", ")} in ${relativePath}`
|
|
497
|
+
: `Test topology: ${relativePath}`,
|
|
498
|
+
factKind: "observation",
|
|
499
|
+
confidence: frameworks.length > 0 ? 0.92 : 0.85,
|
|
500
|
+
evidence: [
|
|
501
|
+
`test_topology:${relativePath}`,
|
|
502
|
+
...frameworks.map((framework) => `framework:${framework}`),
|
|
503
|
+
],
|
|
504
|
+
frameworks,
|
|
505
|
+
}));
|
|
506
|
+
}
|
|
507
|
+
return {
|
|
508
|
+
provider: "test_topology",
|
|
509
|
+
evidence,
|
|
510
|
+
detectedLanguages: Array.from(detectedLanguages),
|
|
511
|
+
detectedTestFrameworks: Array.from(detectedFrameworks),
|
|
512
|
+
scanWarnings,
|
|
513
|
+
};
|
|
514
|
+
}
|
|
515
|
+
function runSourceSymbolsProvider(workspaceRoot, vendoredRoots) {
|
|
516
|
+
const analyzeSourceText = cliSymbolCoordinator.analyzeSourceText;
|
|
517
|
+
const sourceFiles = fg.sync([
|
|
518
|
+
"src/**/*.{ts,tsx,mts,cts,js,jsx,mjs,cjs,py,rb,go,rs,java,kt,swift,php,c,cc,cpp,h,hpp}",
|
|
519
|
+
"app/**/*.{ts,tsx,mts,cts,js,jsx,mjs,cjs,py,rb,go,rs,java,kt,swift,php,c,cc,cpp,h,hpp}",
|
|
520
|
+
"apps/**/*.{ts,tsx,mts,cts,js,jsx,mjs,cjs,py,rb,go,rs,java,kt,swift,php,c,cc,cpp,h,hpp}",
|
|
521
|
+
"packages/**/*.{ts,tsx,mts,cts,js,jsx,mjs,cjs,py,rb,go,rs,java,kt,swift,php,c,cc,cpp,h,hpp}",
|
|
522
|
+
], {
|
|
523
|
+
cwd: workspaceRoot,
|
|
524
|
+
absolute: true,
|
|
525
|
+
onlyFiles: true,
|
|
526
|
+
unique: true,
|
|
527
|
+
suppressErrors: true,
|
|
528
|
+
ignore: buildIgnoredGlobs(vendoredRoots),
|
|
529
|
+
});
|
|
530
|
+
const evidence = [];
|
|
531
|
+
const detectedLanguages = new Set();
|
|
532
|
+
const scanWarnings = [];
|
|
533
|
+
for (const absolutePath of sortUnique(sourceFiles)) {
|
|
534
|
+
const relativePath = toRelativePosixPath(workspaceRoot, absolutePath);
|
|
535
|
+
const language = SOURCE_LANGUAGE_EXTENSIONS[path.extname(absolutePath).toLowerCase()] ?? "unknown";
|
|
536
|
+
detectedLanguages.add(language);
|
|
537
|
+
try {
|
|
538
|
+
const content = fs.readFileSync(absolutePath, "utf8");
|
|
539
|
+
const analysis = analyzeSourceText
|
|
540
|
+
? analyzeSourceText(relativePath, content)
|
|
541
|
+
: {
|
|
542
|
+
sourceFile: relativePath,
|
|
543
|
+
language,
|
|
544
|
+
providerId: null,
|
|
545
|
+
module: {
|
|
546
|
+
title: path.basename(relativePath, path.extname(relativePath)) || relativePath,
|
|
547
|
+
analysisMode: "fallback",
|
|
548
|
+
fallbackReason: "provider_unavailable",
|
|
549
|
+
},
|
|
550
|
+
symbols: [],
|
|
551
|
+
};
|
|
552
|
+
if (analysis.symbols.length > 0) {
|
|
553
|
+
evidence.push({
|
|
554
|
+
provider: "source_symbols",
|
|
555
|
+
kind: "source_symbols",
|
|
556
|
+
label: relativePath,
|
|
557
|
+
relativePath,
|
|
558
|
+
absolutePath,
|
|
559
|
+
data: {
|
|
560
|
+
title: `Source symbols: ${analysis.module.title}`,
|
|
561
|
+
factKind: "observation",
|
|
562
|
+
confidence: 0.9,
|
|
563
|
+
evidence: [
|
|
564
|
+
`source_symbols:${relativePath}`,
|
|
565
|
+
`language:${analysis.language}`,
|
|
566
|
+
`provider:${analysis.providerId ?? "fallback"}`,
|
|
567
|
+
...analysis.symbols
|
|
568
|
+
.slice(0, 5)
|
|
569
|
+
.map((symbol) => `symbol:${symbol.kind}:${symbol.name}`),
|
|
570
|
+
],
|
|
571
|
+
analysisMode: analysis.module.analysisMode,
|
|
572
|
+
providerId: analysis.providerId,
|
|
573
|
+
symbolCount: analysis.symbols.length,
|
|
574
|
+
},
|
|
575
|
+
});
|
|
576
|
+
continue;
|
|
577
|
+
}
|
|
578
|
+
evidence.push({
|
|
579
|
+
provider: "source_symbols",
|
|
580
|
+
kind: "source_symbols",
|
|
581
|
+
label: relativePath,
|
|
582
|
+
relativePath,
|
|
583
|
+
absolutePath,
|
|
584
|
+
data: {
|
|
585
|
+
title: `Source module: ${analysis.module.title}`,
|
|
586
|
+
factKind: "observation",
|
|
587
|
+
confidence: 0.82,
|
|
588
|
+
evidence: [
|
|
589
|
+
`source_symbols:${relativePath}`,
|
|
590
|
+
`language:${analysis.language}`,
|
|
591
|
+
`analysis_mode:${analysis.module.analysisMode}`,
|
|
592
|
+
...(analysis.module.fallbackReason
|
|
593
|
+
? [`fallback:${analysis.module.fallbackReason}`]
|
|
594
|
+
: []),
|
|
595
|
+
],
|
|
596
|
+
analysisMode: analysis.module.analysisMode,
|
|
597
|
+
fallbackReason: analysis.module.fallbackReason,
|
|
598
|
+
providerId: analysis.providerId,
|
|
599
|
+
symbolCount: 0,
|
|
600
|
+
},
|
|
601
|
+
});
|
|
602
|
+
}
|
|
603
|
+
catch {
|
|
604
|
+
scanWarnings.push(`source_symbols:failed_to_analyze:${relativePath}`);
|
|
605
|
+
}
|
|
606
|
+
}
|
|
607
|
+
return {
|
|
608
|
+
provider: "source_symbols",
|
|
609
|
+
evidence,
|
|
610
|
+
detectedLanguages: Array.from(detectedLanguages),
|
|
611
|
+
scanWarnings,
|
|
612
|
+
};
|
|
613
|
+
}
|
|
614
|
+
function buildDiscoverySummary(activation, vendored, providerResults) {
|
|
615
|
+
const providerCounts = createEmptyProviderCounts();
|
|
616
|
+
const detectedLanguages = new Set();
|
|
617
|
+
const detectedTestFrameworks = new Set();
|
|
618
|
+
const scanWarnings = [];
|
|
619
|
+
let truncated = false;
|
|
620
|
+
for (const result of providerResults) {
|
|
621
|
+
providerCounts[result.provider] = result.evidence.length;
|
|
622
|
+
for (const language of result.detectedLanguages ?? []) {
|
|
623
|
+
detectedLanguages.add(language);
|
|
624
|
+
}
|
|
625
|
+
for (const framework of result.detectedTestFrameworks ?? []) {
|
|
626
|
+
detectedTestFrameworks.add(framework);
|
|
627
|
+
}
|
|
628
|
+
scanWarnings.push(...(result.scanWarnings ?? []));
|
|
629
|
+
truncated ||= Boolean(result.truncated);
|
|
630
|
+
}
|
|
631
|
+
return {
|
|
632
|
+
...buildSourceSummary(activation, vendored),
|
|
633
|
+
providersRun: providerResults.map((result) => result.provider),
|
|
634
|
+
providerCounts,
|
|
635
|
+
detectedLanguages: Array.from(detectedLanguages).sort(),
|
|
636
|
+
detectedTestFrameworks: Array.from(detectedTestFrameworks).sort(),
|
|
637
|
+
excludedRoots: Array.from(IGNORED_DIRECTORY_NAMES).sort(),
|
|
638
|
+
truncated,
|
|
639
|
+
scanWarnings: sortUnique(scanWarnings),
|
|
640
|
+
};
|
|
641
|
+
}
|
|
642
|
+
// implements REQ-001
|
|
643
|
+
export function discoverProviderEvidence(workspaceRoot, activation) {
|
|
644
|
+
const vendored = findVendoredTrees(workspaceRoot);
|
|
645
|
+
if (!activation.allowCandidateGeneration) {
|
|
646
|
+
return {
|
|
647
|
+
evidence: [],
|
|
648
|
+
providerResults: [],
|
|
649
|
+
summary: {
|
|
650
|
+
...buildSourceSummary(activation, vendored),
|
|
651
|
+
providersRun: [],
|
|
652
|
+
providerCounts: createEmptyProviderCounts(),
|
|
653
|
+
detectedLanguages: [],
|
|
654
|
+
detectedTestFrameworks: [],
|
|
655
|
+
excludedRoots: Array.from(IGNORED_DIRECTORY_NAMES).sort(),
|
|
656
|
+
truncated: false,
|
|
657
|
+
scanWarnings: [],
|
|
658
|
+
},
|
|
659
|
+
};
|
|
660
|
+
}
|
|
661
|
+
const typedKibiDocs = runTypedKibiDocsProvider(workspaceRoot);
|
|
662
|
+
const typedPaths = new Set(typedKibiDocs.evidence
|
|
663
|
+
.map((item) => item.relativePath)
|
|
664
|
+
.filter((item) => Boolean(item)));
|
|
665
|
+
const providerResults = [
|
|
666
|
+
typedKibiDocs,
|
|
667
|
+
runGenericRepoDocsProvider(workspaceRoot, vendored, typedPaths),
|
|
668
|
+
runRepoMetadataProvider(workspaceRoot),
|
|
669
|
+
runRepoLayoutProvider(workspaceRoot, vendored),
|
|
670
|
+
runTestTopologyProvider(workspaceRoot, vendored),
|
|
671
|
+
runSourceSymbolsProvider(workspaceRoot, vendored),
|
|
672
|
+
];
|
|
673
|
+
const evidence = providerResults.flatMap((result) => result.evidence);
|
|
674
|
+
evidence.sort((left, right) => {
|
|
675
|
+
const providerCompare = AUTOPILOT_PROVIDER_ORDER.indexOf(left.provider) -
|
|
676
|
+
AUTOPILOT_PROVIDER_ORDER.indexOf(right.provider);
|
|
677
|
+
if (providerCompare !== 0)
|
|
678
|
+
return providerCompare;
|
|
679
|
+
const leftKey = left.relativePath ?? left.label;
|
|
680
|
+
const rightKey = right.relativePath ?? right.label;
|
|
681
|
+
return leftKey.localeCompare(rightKey);
|
|
682
|
+
});
|
|
683
|
+
return {
|
|
684
|
+
evidence,
|
|
685
|
+
providerResults,
|
|
686
|
+
summary: buildDiscoverySummary(activation, vendored, providerResults),
|
|
687
|
+
};
|
|
688
|
+
}
|
|
689
|
+
function toActivationPolicy(activationState) {
|
|
690
|
+
switch (activationState) {
|
|
691
|
+
case "root_partial":
|
|
692
|
+
return {
|
|
693
|
+
activationState,
|
|
694
|
+
activationMode: "repair_bootstrap",
|
|
695
|
+
applyBlocked: true,
|
|
696
|
+
allowCandidateGeneration: true,
|
|
697
|
+
reason: "Workspace root is only partially configured; run a repair bootstrap scan and keep apply blocked until the root is repaired.",
|
|
698
|
+
};
|
|
699
|
+
case "root_active_thin":
|
|
700
|
+
return {
|
|
701
|
+
activationState,
|
|
702
|
+
activationMode: "attached_thin_handoff",
|
|
703
|
+
applyBlocked: true,
|
|
704
|
+
allowCandidateGeneration: false,
|
|
705
|
+
reason: "Workspace already has an attached but thin KB; bootstrap synthesis is replaced by an explicit thin handoff.",
|
|
706
|
+
handoffMessage: "Attached thin KB detected. Review the sparse KB coverage and continue with a handoff instead of a bootstrap apply plan.",
|
|
707
|
+
};
|
|
708
|
+
case "root_active_seeded":
|
|
709
|
+
return {
|
|
710
|
+
activationState,
|
|
711
|
+
activationMode: "attached_seeded_handoff",
|
|
712
|
+
applyBlocked: true,
|
|
713
|
+
allowCandidateGeneration: false,
|
|
714
|
+
reason: "Workspace already has an attached seeded KB; bootstrap synthesis is replaced by an explicit seeded handoff.",
|
|
715
|
+
handoffMessage: "Attached seeded KB detected. Use the existing KB context instead of generating bootstrap candidates.",
|
|
716
|
+
};
|
|
717
|
+
case "vendored_only":
|
|
718
|
+
return {
|
|
719
|
+
activationState,
|
|
720
|
+
activationMode: "vendored_blocked",
|
|
721
|
+
applyBlocked: true,
|
|
722
|
+
allowCandidateGeneration: false,
|
|
723
|
+
reason: "Workspace appears to contain vendored Kibi sources only; bootstrap generation is blocked in this posture.",
|
|
724
|
+
handoffMessage: "Vendored Kibi posture detected. Move to the real project root before attempting bootstrap.",
|
|
725
|
+
};
|
|
726
|
+
case "root_uninitialized":
|
|
727
|
+
return {
|
|
728
|
+
activationState,
|
|
729
|
+
activationMode: "cold_start_bootstrap",
|
|
730
|
+
applyBlocked: false,
|
|
731
|
+
allowCandidateGeneration: true,
|
|
732
|
+
reason: "Workspace has no attached root KB yet; run a cold-start bootstrap scan across repository evidence.",
|
|
733
|
+
};
|
|
734
|
+
}
|
|
735
|
+
}
|
|
736
|
+
// implements REQ-mcp-init-kibi-autopilot-v1
|
|
737
|
+
export async function resolveActivationPolicy(workspaceRoot, prolog) {
|
|
738
|
+
return toActivationPolicy(await classifyActivationState(workspaceRoot, prolog));
|
|
739
|
+
}
|
|
83
740
|
function rootTargetsAllResolve(cwd) {
|
|
84
741
|
const config = readRootConfig(cwd) || {};
|
|
85
742
|
const paths = config.paths ?? {};
|
|
@@ -116,7 +773,9 @@ function rootTargetsAllResolve(cwd) {
|
|
|
116
773
|
export async function classifyActivationState(workspaceRoot, prolog) {
|
|
117
774
|
const hasRootConfig = rootKbConfigExists(workspaceRoot);
|
|
118
775
|
const vendored = findVendoredTrees(workspaceRoot);
|
|
119
|
-
if (!hasRootConfig &&
|
|
776
|
+
if (!hasRootConfig &&
|
|
777
|
+
vendored.length > 0 &&
|
|
778
|
+
!hasWorkspaceProjectSignals(workspaceRoot, vendored)) {
|
|
120
779
|
return "vendored_only";
|
|
121
780
|
}
|
|
122
781
|
if (!hasRootConfig) {
|
|
@@ -166,11 +825,11 @@ function collectMarkdownFiles(dir, workspaceRoot, vendoredRoots) {
|
|
|
166
825
|
const stat = fs.statSync(dir);
|
|
167
826
|
if (!stat.isDirectory())
|
|
168
827
|
return results;
|
|
169
|
-
const entries = fs.readdirSync(dir);
|
|
828
|
+
const entries = fs.readdirSync(dir).sort();
|
|
170
829
|
for (const entry of entries) {
|
|
171
830
|
const full = path.join(dir, entry);
|
|
172
831
|
// Skip ignores
|
|
173
|
-
if (
|
|
832
|
+
if (IGNORED_DIRECTORY_NAMES.has(entry.toLowerCase()))
|
|
174
833
|
continue;
|
|
175
834
|
// Skip vendored roots
|
|
176
835
|
const rel = path.relative(workspaceRoot, full).split(path.sep).join("/");
|
|
@@ -189,55 +848,22 @@ function collectMarkdownFiles(dir, workspaceRoot, vendoredRoots) {
|
|
|
189
848
|
}
|
|
190
849
|
/** Discover eligible source inputs for autopilot. */
|
|
191
850
|
// implements REQ-mcp-init-kibi-autopilot-v1
|
|
192
|
-
export function discoverSources(workspaceRoot,
|
|
193
|
-
const
|
|
194
|
-
if (activationState === "vendored_only") {
|
|
195
|
-
return { candidates: [], summary: { activationState, vendored } };
|
|
196
|
-
}
|
|
197
|
-
const config = readRootConfig(workspaceRoot) || {};
|
|
198
|
-
const paths = config.paths ??
|
|
199
|
-
DEFAULT_SYNC_PATHS;
|
|
851
|
+
export function discoverSources(workspaceRoot, activation) {
|
|
852
|
+
const discovery = discoverProviderEvidence(workspaceRoot, activation);
|
|
200
853
|
const candidates = new Set();
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
if (fs.existsSync(abs) && fs.statSync(abs).isFile()) {
|
|
210
|
-
candidates.add(path.relative(workspaceRoot, abs).split(path.sep).join("/"));
|
|
854
|
+
for (const item of discovery.evidence) {
|
|
855
|
+
if (item.kind === "typed_markdown" ||
|
|
856
|
+
item.kind === "symbol_manifest" ||
|
|
857
|
+
item.kind === "generic_markdown" ||
|
|
858
|
+
item.kind === "source_symbols") {
|
|
859
|
+
const relativePath = item.relativePath;
|
|
860
|
+
if (relativePath) {
|
|
861
|
+
candidates.add(relativePath);
|
|
211
862
|
}
|
|
212
|
-
continue;
|
|
213
|
-
}
|
|
214
|
-
const pat = normalizePattern(normalized) ?? normalized;
|
|
215
|
-
const root = stripToRoot(pat);
|
|
216
|
-
const absRoot = path.resolve(workspaceRoot, root);
|
|
217
|
-
if (fs.existsSync(absRoot) && fs.statSync(absRoot).isDirectory()) {
|
|
218
|
-
for (const f of collectMarkdownFiles(absRoot, workspaceRoot, vendored)) {
|
|
219
|
-
candidates.add(f);
|
|
220
|
-
}
|
|
221
|
-
}
|
|
222
|
-
}
|
|
223
|
-
// Generic markdown candidates (top-level), but exclude documentation/** which
|
|
224
|
-
// is treated above via configured paths.
|
|
225
|
-
for (const file of ["README.md", "ARCHITECTURE.md"]) {
|
|
226
|
-
const abs = path.resolve(workspaceRoot, file);
|
|
227
|
-
if (fs.existsSync(abs) && fs.statSync(abs).isFile()) {
|
|
228
|
-
const rel = path.relative(workspaceRoot, abs).split(path.sep).join("/");
|
|
229
|
-
if (!rel.startsWith("documentation/"))
|
|
230
|
-
candidates.add(rel);
|
|
231
|
-
}
|
|
232
|
-
}
|
|
233
|
-
const docsRoot = path.resolve(workspaceRoot, "docs");
|
|
234
|
-
if (fs.existsSync(docsRoot) && fs.statSync(docsRoot).isDirectory()) {
|
|
235
|
-
for (const f of collectMarkdownFiles(docsRoot, workspaceRoot, vendored)) {
|
|
236
|
-
candidates.add(f);
|
|
237
863
|
}
|
|
238
864
|
}
|
|
239
865
|
return {
|
|
240
866
|
candidates: Array.from(candidates).sort(),
|
|
241
|
-
summary:
|
|
867
|
+
summary: discovery.summary,
|
|
242
868
|
};
|
|
243
869
|
}
|