kibi-mcp 0.22.0 → 2.0.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/bin/kibi-mcp +12 -11
- package/dist/diagnostics.js +68 -8
- package/dist/env.js +5 -1
- package/dist/runtime/mcp-runtime.js +39 -3
- package/dist/server/docs.js +31 -42
- package/dist/server/icons.js +27 -0
- package/dist/server/jobs.js +107 -0
- package/dist/server/json-schema-to-zod.js +33 -9
- package/dist/server/session.js +129 -133
- package/dist/server/tool-registration.js +46 -9
- package/dist/server/tools-runtime.js +10 -10
- package/dist/server/tools.js +143 -7
- package/dist/server.js +16 -1
- package/dist/startup-resolution.js +15 -17
- package/dist/tools/check-impact.js +1 -1
- package/dist/tools/check.js +1 -1
- package/dist/tools/core-module.js +19 -8
- package/dist/tools/coverage.js +1 -1
- package/dist/tools/delete.js +1 -1
- package/dist/tools/discovery-adapter.js +5 -0
- package/dist/tools/entity-query.js +1 -1
- package/dist/tools/find-gaps.js +9 -1
- package/dist/tools/graph.js +9 -1
- package/dist/tools/model-requirement.js +2 -2
- package/dist/tools/mutation-context.js +15 -9
- package/dist/tools/plan-bootstrap.js +21 -0
- package/dist/tools/prolog-list.js +1 -1
- package/dist/tools/query.js +1 -1
- package/dist/tools/relationship-validation.js +1 -1
- package/dist/tools/search.js +1 -1
- package/dist/tools/semantic-advisor.js +1 -1
- package/dist/tools/skills.js +2 -2
- package/dist/tools/sparql.js +1 -1
- package/dist/tools/status.js +1 -1
- package/dist/tools/suggest-predicates.js +1 -1
- package/dist/tools/symbols.js +310 -68
- package/dist/tools/upsert.js +3 -3
- package/dist/tools/validate-upsert.js +1 -1
- package/dist/tools-config.js +77 -3
- package/dist/workspace.js +10 -8
- package/package.json +15 -9
- package/dist/tools/autopilot-candidates.js +0 -554
- package/dist/tools/autopilot-discovery.js +0 -898
- package/dist/tools/autopilot-generate.js +0 -47
- package/dist/tools/autopilot-test-compat.js +0 -88
- package/dist/tools/autopilot-test-parsers.js +0 -120
- package/dist/tools/check-config.js +0 -67
package/dist/workspace.js
CHANGED
|
@@ -17,6 +17,7 @@
|
|
|
17
17
|
*/
|
|
18
18
|
import fs from "node:fs";
|
|
19
19
|
import path from "node:path";
|
|
20
|
+
import { branchStorePath } from "kibi-runtime";
|
|
20
21
|
const WORKSPACE_ENV_KEYS = [
|
|
21
22
|
"KIBI_WORKSPACE",
|
|
22
23
|
"KIBI_PROJECT_ROOT",
|
|
@@ -47,9 +48,9 @@ export function resolveKbPath(workspaceRoot, branch) {
|
|
|
47
48
|
if (isBranchPath(resolved)) {
|
|
48
49
|
return resolved;
|
|
49
50
|
}
|
|
50
|
-
return
|
|
51
|
+
return branchStorePath(resolved, branch);
|
|
51
52
|
}
|
|
52
|
-
return
|
|
53
|
+
return branchStorePath(workspaceRoot, branch);
|
|
53
54
|
}
|
|
54
55
|
// implements REQ-002
|
|
55
56
|
export function resolveEnvFilePath(envFileName, workspaceRoot) {
|
|
@@ -67,19 +68,20 @@ function readFirstEnv(keys) {
|
|
|
67
68
|
}
|
|
68
69
|
return null;
|
|
69
70
|
}
|
|
71
|
+
export function nextAncestorDirectory(current) {
|
|
72
|
+
const parent = path.dirname(current);
|
|
73
|
+
return parent === current ? undefined : parent;
|
|
74
|
+
}
|
|
70
75
|
function findUpwards(startDir, marker) {
|
|
71
76
|
let current = path.resolve(startDir);
|
|
72
|
-
while (
|
|
77
|
+
while (current !== undefined) {
|
|
73
78
|
const candidate = path.join(current, marker);
|
|
74
79
|
if (fs.existsSync(candidate)) {
|
|
75
80
|
return current;
|
|
76
81
|
}
|
|
77
|
-
|
|
78
|
-
if (parent === current) {
|
|
79
|
-
return null;
|
|
80
|
-
}
|
|
81
|
-
current = parent;
|
|
82
|
+
current = nextAncestorDirectory(current);
|
|
82
83
|
}
|
|
84
|
+
return null;
|
|
83
85
|
}
|
|
84
86
|
function isBranchPath(p) {
|
|
85
87
|
const parent = path.basename(path.dirname(p));
|
package/package.json
CHANGED
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "kibi-mcp",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "2.0.0",
|
|
4
4
|
"dependencies": {
|
|
5
|
-
"@modelcontextprotocol/sdk": "^1.
|
|
5
|
+
"@modelcontextprotocol/sdk": "^1.30.0",
|
|
6
6
|
"ajv": "^8.18.0",
|
|
7
7
|
"cli-table3": "^0.6.5",
|
|
8
8
|
"commander": "^11.0.0",
|
|
9
9
|
"fast-glob": "^3.2.12",
|
|
10
10
|
"gray-matter": "^4.0.3",
|
|
11
11
|
"js-yaml": "^4.1.0",
|
|
12
|
-
"kibi-
|
|
13
|
-
"kibi-core": "^0.
|
|
12
|
+
"kibi-runtime": "^2.0.0",
|
|
13
|
+
"kibi-core": "^0.12.0",
|
|
14
14
|
"mcpcat": "^0.1.12",
|
|
15
15
|
"ts-morph": "^23.0.0",
|
|
16
16
|
"zod": "^4.3.6"
|
|
@@ -24,13 +24,17 @@
|
|
|
24
24
|
"scripts": {
|
|
25
25
|
"test": "bun test",
|
|
26
26
|
"dev": "bun run bin/kibi-mcp",
|
|
27
|
-
"
|
|
27
|
+
"clean": "rm -rf dist",
|
|
28
|
+
"build": "npm run clean && tsc -p tsconfig.json",
|
|
28
29
|
"typecheck": "tsc -p tsconfig.json --noEmit",
|
|
29
|
-
"prepack": "npm run build"
|
|
30
|
+
"prepack": "npm run build && node ../../scripts/verify-package-contract.mjs kibi-mcp"
|
|
30
31
|
},
|
|
31
|
-
"files": [
|
|
32
|
+
"files": [
|
|
33
|
+
"dist",
|
|
34
|
+
"bin"
|
|
35
|
+
],
|
|
32
36
|
"engines": {
|
|
33
|
-
"node": ">=
|
|
37
|
+
"node": ">=22"
|
|
34
38
|
},
|
|
35
39
|
"exports": {
|
|
36
40
|
".": "./dist/server.js"
|
|
@@ -38,7 +42,9 @@
|
|
|
38
42
|
"devDependencies": {
|
|
39
43
|
"@types/bun": "latest",
|
|
40
44
|
"@types/node": "latest",
|
|
41
|
-
"
|
|
45
|
+
"semver": "^7.7.4",
|
|
46
|
+
"typescript": "^5.7.0",
|
|
47
|
+
"kibi-cli": "workspace:*"
|
|
42
48
|
},
|
|
43
49
|
"license": "AGPL-3.0-or-later",
|
|
44
50
|
"author": "Piotr Franczyk",
|
|
@@ -1,554 +0,0 @@
|
|
|
1
|
-
// Kibi — autopilot candidate builders
|
|
2
|
-
// Implements candidate assembly from public CLI extractors
|
|
3
|
-
import { extractFromManifest } from "kibi-cli/extractors/manifest";
|
|
4
|
-
import { extractFromMarkdown } from "kibi-cli/extractors/markdown";
|
|
5
|
-
import { buildStrictWriteSet, modelRequirementClaims, } from "kibi-cli/public/check-types";
|
|
6
|
-
import fs from "node:fs";
|
|
7
|
-
import path from "node:path";
|
|
8
|
-
import { createRepoIgnorePolicy } from "kibi-cli/ignore-policy";
|
|
9
|
-
import { estimateNormativeSignalConfidence, extractRequirementClaim, strictWriteSetToApplyPlan, writeSetPrimaryEntityId, } from "./model-requirement.js";
|
|
10
|
-
function slugify(value, maxLength = 80) {
|
|
11
|
-
return value
|
|
12
|
-
.toLowerCase()
|
|
13
|
-
.replace(/[^a-z0-9]+/g, "-")
|
|
14
|
-
.replace(/(^-|-$)/g, "")
|
|
15
|
-
.slice(0, maxLength);
|
|
16
|
-
}
|
|
17
|
-
function sortUniquePaths(paths) {
|
|
18
|
-
return Array.from(new Set(paths)).sort();
|
|
19
|
-
}
|
|
20
|
-
function getEvidenceFilePaths(discoveryResult, kind) {
|
|
21
|
-
return sortUniquePaths((discoveryResult.evidence ?? [])
|
|
22
|
-
.filter((item) => item.kind === kind)
|
|
23
|
-
.map((item) => item.absolutePath ?? "")
|
|
24
|
-
.filter((item) => Boolean(item)));
|
|
25
|
-
}
|
|
26
|
-
function getTypedMarkdownFiles(discoveryResult) {
|
|
27
|
-
const evidenceFiles = getEvidenceFilePaths(discoveryResult, "typed_markdown");
|
|
28
|
-
if (evidenceFiles.length > 0)
|
|
29
|
-
return evidenceFiles;
|
|
30
|
-
return discoveryResult.markdownFiles ?? [];
|
|
31
|
-
}
|
|
32
|
-
function getManifestFiles(discoveryResult) {
|
|
33
|
-
const evidenceFiles = getEvidenceFilePaths(discoveryResult, "symbol_manifest");
|
|
34
|
-
if (evidenceFiles.length > 0)
|
|
35
|
-
return evidenceFiles;
|
|
36
|
-
return discoveryResult.manifestFiles ?? [];
|
|
37
|
-
}
|
|
38
|
-
function getGenericMarkdownFiles(discoveryResult) {
|
|
39
|
-
const evidenceFiles = getEvidenceFilePaths(discoveryResult, "generic_markdown");
|
|
40
|
-
if (evidenceFiles.length > 0)
|
|
41
|
-
return evidenceFiles;
|
|
42
|
-
return discoveryResult.markdownFiles ?? [];
|
|
43
|
-
}
|
|
44
|
-
function hasGenericMarkdownEvidence(discoveryResult) {
|
|
45
|
-
return (discoveryResult.evidence ?? []).some((item) => item.kind === "generic_markdown");
|
|
46
|
-
}
|
|
47
|
-
function getFactEvidence(discoveryResult) {
|
|
48
|
-
return (discoveryResult.evidence ?? []).filter((item) => item.kind === "repo_metadata" ||
|
|
49
|
-
item.kind === "repo_layout" ||
|
|
50
|
-
item.kind === "test_topology" ||
|
|
51
|
-
item.kind === "source_symbols");
|
|
52
|
-
}
|
|
53
|
-
function toConfidenceBand(confidence) {
|
|
54
|
-
if (confidence >= 0.9)
|
|
55
|
-
return "high";
|
|
56
|
-
if (confidence >= 0.8)
|
|
57
|
-
return "medium";
|
|
58
|
-
return "low";
|
|
59
|
-
}
|
|
60
|
-
function resolveCandidatePaths(filePath, workspaceRoot) {
|
|
61
|
-
const absolutePath = path.isAbsolute(filePath)
|
|
62
|
-
? filePath
|
|
63
|
-
: path.resolve(workspaceRoot, filePath);
|
|
64
|
-
const relativePath = (path.relative(workspaceRoot, absolutePath) || filePath)
|
|
65
|
-
.split(path.sep)
|
|
66
|
-
.join("/");
|
|
67
|
-
return { absolutePath, relativePath };
|
|
68
|
-
}
|
|
69
|
-
// Legacy helper removed in favor of the shared ignore policy from kibi-cli/ignore-policy.
|
|
70
|
-
// Use createRepoIgnorePolicy(workspaceRoot).isIgnored(relativePath) in builders.
|
|
71
|
-
function shouldIncludeGenericMarkdown(relativePath, providerScopedMarkdown) {
|
|
72
|
-
const base = path.basename(relativePath).toLowerCase();
|
|
73
|
-
const inDocsDir = /(^|\/)docs\//.test(relativePath);
|
|
74
|
-
if (providerScopedMarkdown)
|
|
75
|
-
return true;
|
|
76
|
-
return base === "readme.md" || base === "architecture.md" || inDocsDir;
|
|
77
|
-
}
|
|
78
|
-
function pushSignal(signals, signal, seen) {
|
|
79
|
-
const key = `${signal.kind}::${signal.sourcePath}::${signal.title}`;
|
|
80
|
-
if (seen.has(key))
|
|
81
|
-
return;
|
|
82
|
-
seen.add(key);
|
|
83
|
-
signals.push(signal);
|
|
84
|
-
}
|
|
85
|
-
function buildUpsertFromExtraction(er, typeOverride) {
|
|
86
|
-
const ent = er.entity;
|
|
87
|
-
const type = typeOverride ?? String(ent.type ?? "");
|
|
88
|
-
const id = String(ent.id ?? "");
|
|
89
|
-
// Copy properties except `type` to avoid delete
|
|
90
|
-
const properties = {};
|
|
91
|
-
for (const [k, v] of Object.entries(ent)) {
|
|
92
|
-
if (k === "type")
|
|
93
|
-
continue;
|
|
94
|
-
properties[k] = v;
|
|
95
|
-
}
|
|
96
|
-
return {
|
|
97
|
-
type,
|
|
98
|
-
id,
|
|
99
|
-
properties,
|
|
100
|
-
relationships: er.relationships ?? [],
|
|
101
|
-
};
|
|
102
|
-
}
|
|
103
|
-
// implements REQ-mcp-init-kibi-autopilot-v1
|
|
104
|
-
export function buildTypedMarkdownCandidates(discoveryResult, existingEntities) {
|
|
105
|
-
const candidates = [];
|
|
106
|
-
const workspaceRoot = existingEntities.workspaceRoot ?? process.cwd();
|
|
107
|
-
const ignorePolicy = createRepoIgnorePolicy(workspaceRoot);
|
|
108
|
-
for (const filePath of getTypedMarkdownFiles(discoveryResult)) {
|
|
109
|
-
try {
|
|
110
|
-
const extraction = extractFromMarkdown(filePath);
|
|
111
|
-
const { entity, relationships } = extraction;
|
|
112
|
-
if (existingEntities.ids.has(entity.id))
|
|
113
|
-
continue;
|
|
114
|
-
const { absolutePath, relativePath } = resolveCandidatePaths(filePath, workspaceRoot);
|
|
115
|
-
if (ignorePolicy.isIgnored(relativePath))
|
|
116
|
-
continue;
|
|
117
|
-
const candidateId = `md:${relativePath}:${entity.id}`;
|
|
118
|
-
const upsert = buildUpsertFromExtraction({ entity, relationships });
|
|
119
|
-
candidates.push({
|
|
120
|
-
candidateId,
|
|
121
|
-
entityType: entity.type,
|
|
122
|
-
title: entity.title,
|
|
123
|
-
sourceKind: "typed_markdown",
|
|
124
|
-
sourcePath: absolutePath,
|
|
125
|
-
confidence: 1.0,
|
|
126
|
-
confidenceBand: "high",
|
|
127
|
-
evidence: [
|
|
128
|
-
`extracted_from_markdown:${relativePath}`,
|
|
129
|
-
`entity_id:${entity.id}`,
|
|
130
|
-
],
|
|
131
|
-
relationships: relationships || [],
|
|
132
|
-
applyPlan: [upsert],
|
|
133
|
-
});
|
|
134
|
-
}
|
|
135
|
-
catch (error) {
|
|
136
|
-
// skip files that fail extraction
|
|
137
|
-
}
|
|
138
|
-
}
|
|
139
|
-
return candidates;
|
|
140
|
-
}
|
|
141
|
-
// implements REQ-mcp-init-kibi-autopilot-v1
|
|
142
|
-
export function buildSymbolManifestCandidates(discoveryResult, existingEntities) {
|
|
143
|
-
const candidates = [];
|
|
144
|
-
const workspaceRoot = existingEntities.workspaceRoot ?? process.cwd();
|
|
145
|
-
for (const filePath of getManifestFiles(discoveryResult)) {
|
|
146
|
-
try {
|
|
147
|
-
const results = extractFromManifest(filePath);
|
|
148
|
-
for (const res of results) {
|
|
149
|
-
const entity = res.entity;
|
|
150
|
-
const relationships = res.relationships || [];
|
|
151
|
-
if (existingEntities.ids.has(entity.id))
|
|
152
|
-
continue;
|
|
153
|
-
const { absolutePath, relativePath } = resolveCandidatePaths(filePath, workspaceRoot);
|
|
154
|
-
const candidateId = `mf:${relativePath}:${entity.id}`;
|
|
155
|
-
const upsert = buildUpsertFromExtraction({ entity, relationships });
|
|
156
|
-
candidates.push({
|
|
157
|
-
candidateId,
|
|
158
|
-
entityType: entity.type,
|
|
159
|
-
title: entity.title,
|
|
160
|
-
sourceKind: "symbol_manifest",
|
|
161
|
-
sourcePath: absolutePath,
|
|
162
|
-
confidence: 0.98,
|
|
163
|
-
confidenceBand: "high",
|
|
164
|
-
evidence: [
|
|
165
|
-
`extracted_from_manifest:${relativePath}`,
|
|
166
|
-
`entity_id:${entity.id}`,
|
|
167
|
-
],
|
|
168
|
-
relationships,
|
|
169
|
-
applyPlan: [upsert],
|
|
170
|
-
});
|
|
171
|
-
}
|
|
172
|
-
}
|
|
173
|
-
catch (error) {
|
|
174
|
-
// skip manifest parse errors
|
|
175
|
-
}
|
|
176
|
-
}
|
|
177
|
-
return candidates;
|
|
178
|
-
}
|
|
179
|
-
/**
|
|
180
|
-
* Conservative generic markdown candidate builder.
|
|
181
|
-
* Scans a small, safe set of top-level markdown files and emits only
|
|
182
|
-
* ADR/FACT candidates when clear heading heuristics match.
|
|
183
|
-
*
|
|
184
|
-
* discoveryResult.markdownFiles is expected to be a list of file paths
|
|
185
|
-
* (absolute or relative). Files under documentation/**, .kb/**, .git/**,
|
|
186
|
-
* node_modules/**, vendor/**, third_party/** are ignored to avoid vendored
|
|
187
|
-
* trees and double-counting typed Kibi docs.
|
|
188
|
-
*/
|
|
189
|
-
// implements REQ-mcp-init-kibi-autopilot-v1
|
|
190
|
-
export function buildGenericMarkdownCandidates(discoveryResult, existingEntities, minConfidence = 0.8) {
|
|
191
|
-
const candidates = [];
|
|
192
|
-
const workspaceRoot = existingEntities.workspaceRoot ?? process.cwd();
|
|
193
|
-
const ignorePolicy = createRepoIgnorePolicy(workspaceRoot);
|
|
194
|
-
const providerScopedMarkdown = hasGenericMarkdownEvidence(discoveryResult);
|
|
195
|
-
const files = getGenericMarkdownFiles(discoveryResult);
|
|
196
|
-
for (const rawPath of files) {
|
|
197
|
-
try {
|
|
198
|
-
const filePath = String(rawPath);
|
|
199
|
-
const { absolutePath, relativePath } = resolveCandidatePaths(filePath, workspaceRoot);
|
|
200
|
-
if (ignorePolicy.isIgnored(relativePath))
|
|
201
|
-
continue;
|
|
202
|
-
// Legacy path-only discovery was conservative. Provider-scoped discovery
|
|
203
|
-
// already filters eligible generic docs, so allow broader repo markdown there.
|
|
204
|
-
if (!shouldIncludeGenericMarkdown(relativePath, providerScopedMarkdown)) {
|
|
205
|
-
continue;
|
|
206
|
-
}
|
|
207
|
-
if (!fs.existsSync(absolutePath))
|
|
208
|
-
continue;
|
|
209
|
-
const content = fs.readFileSync(absolutePath, "utf8");
|
|
210
|
-
const lines = content.split(/\r?\n/);
|
|
211
|
-
for (let i = 0; i < lines.length; i++) {
|
|
212
|
-
const line = lines[i];
|
|
213
|
-
if (line === undefined)
|
|
214
|
-
continue;
|
|
215
|
-
const headingMatch = line.match(/^\s*#+\s*(.+)$/);
|
|
216
|
-
if (!headingMatch)
|
|
217
|
-
continue;
|
|
218
|
-
const headingRaw = headingMatch[1];
|
|
219
|
-
if (!headingRaw)
|
|
220
|
-
continue;
|
|
221
|
-
const heading = headingRaw.trim();
|
|
222
|
-
const headingLower = heading.toLowerCase();
|
|
223
|
-
let type = null;
|
|
224
|
-
let confidence = 0;
|
|
225
|
-
// ADR heuristic: headings that mention ADR or Architectural Decision
|
|
226
|
-
if (/\badr\b/i.test(heading) ||
|
|
227
|
-
/architectur.*decision/i.test(heading)) {
|
|
228
|
-
type = "adr";
|
|
229
|
-
confidence = 0.9;
|
|
230
|
-
}
|
|
231
|
-
// Fact/Observation heuristic
|
|
232
|
-
if (!type && /\b(observations?|facts?|notes?)\b/i.test(heading)) {
|
|
233
|
-
type = "fact";
|
|
234
|
-
confidence = 0.8;
|
|
235
|
-
}
|
|
236
|
-
if (!type)
|
|
237
|
-
continue;
|
|
238
|
-
// Suppress below-threshold candidates early
|
|
239
|
-
if (confidence < minConfidence)
|
|
240
|
-
continue;
|
|
241
|
-
// Build a safe ID for the candidate. Use a repo-relative slug so it's
|
|
242
|
-
// deterministic across runs and comparable to typed candidates.
|
|
243
|
-
const slug = heading
|
|
244
|
-
.toLowerCase()
|
|
245
|
-
.replace(/[^a-z0-9]+/g, "-")
|
|
246
|
-
.replace(/(^-|-$)/g, "")
|
|
247
|
-
.slice(0, 60);
|
|
248
|
-
const idPrefix = type === "adr" ? "ADR" : "FACT";
|
|
249
|
-
const genId = `${idPrefix}-GEN-${slug || path.basename(relativePath).replace(/\.[^.]+$/, "")}`.toUpperCase();
|
|
250
|
-
if (existingEntities.ids.has(genId))
|
|
251
|
-
continue;
|
|
252
|
-
// Entity record for applyPlan; keep properties minimal and safe.
|
|
253
|
-
const entity = {
|
|
254
|
-
type,
|
|
255
|
-
id: genId,
|
|
256
|
-
title: heading,
|
|
257
|
-
status: type === "fact" ? "active" : type === "adr" ? "proposed" : "open",
|
|
258
|
-
source: `autopilot:generic:${relativePath}`,
|
|
259
|
-
text_ref: `${relativePath}#L${i + 1}`,
|
|
260
|
-
};
|
|
261
|
-
if (type === "fact") {
|
|
262
|
-
// Generic facts are observations by policy
|
|
263
|
-
entity.fact_kind = "observation";
|
|
264
|
-
}
|
|
265
|
-
const upsert = buildUpsertFromExtraction({
|
|
266
|
-
entity: entity,
|
|
267
|
-
relationships: [],
|
|
268
|
-
}, type);
|
|
269
|
-
const candidateId = `gen:${relativePath}:${type}:${slug}`;
|
|
270
|
-
const confidenceBand = confidence >= 0.95 ? "high" : "medium";
|
|
271
|
-
candidates.push({
|
|
272
|
-
candidateId,
|
|
273
|
-
entityType: type,
|
|
274
|
-
title: heading,
|
|
275
|
-
sourceKind: "generic_markdown",
|
|
276
|
-
sourcePath: absolutePath,
|
|
277
|
-
confidence,
|
|
278
|
-
confidenceBand,
|
|
279
|
-
evidence: [`generic_heading:${relativePath}#L${i + 1}`],
|
|
280
|
-
relationships: [],
|
|
281
|
-
applyPlan: [upsert],
|
|
282
|
-
});
|
|
283
|
-
}
|
|
284
|
-
}
|
|
285
|
-
catch (error) {
|
|
286
|
-
// skip unreadable files silently
|
|
287
|
-
}
|
|
288
|
-
}
|
|
289
|
-
return candidates;
|
|
290
|
-
}
|
|
291
|
-
// implements REQ-mcp-init-kibi-autopilot-v1
|
|
292
|
-
export function collectSourceOnlyAuthoringSignals(discoveryResult, existingEntities, minConfidence = 0.8) {
|
|
293
|
-
const signals = [];
|
|
294
|
-
const seen = new Set();
|
|
295
|
-
const workspaceRoot = existingEntities.workspaceRoot ?? process.cwd();
|
|
296
|
-
const ignorePolicy = createRepoIgnorePolicy(workspaceRoot);
|
|
297
|
-
const providerScopedMarkdown = hasGenericMarkdownEvidence(discoveryResult);
|
|
298
|
-
for (const rawPath of getGenericMarkdownFiles(discoveryResult)) {
|
|
299
|
-
try {
|
|
300
|
-
const filePath = String(rawPath);
|
|
301
|
-
const { absolutePath, relativePath } = resolveCandidatePaths(filePath, workspaceRoot);
|
|
302
|
-
if (ignorePolicy.isIgnored(relativePath))
|
|
303
|
-
continue;
|
|
304
|
-
if (!shouldIncludeGenericMarkdown(relativePath, providerScopedMarkdown))
|
|
305
|
-
continue;
|
|
306
|
-
if (!fs.existsSync(absolutePath))
|
|
307
|
-
continue;
|
|
308
|
-
const content = fs.readFileSync(absolutePath, "utf8");
|
|
309
|
-
const lines = content.split(/\r?\n/);
|
|
310
|
-
for (let i = 0; i < lines.length; i++) {
|
|
311
|
-
const line = lines[i];
|
|
312
|
-
if (line === undefined)
|
|
313
|
-
continue;
|
|
314
|
-
const headingMatch = line.match(/^\s*#+\s*(.+)$/);
|
|
315
|
-
if (!headingMatch)
|
|
316
|
-
continue;
|
|
317
|
-
const headingRaw = headingMatch[1];
|
|
318
|
-
if (!headingRaw)
|
|
319
|
-
continue;
|
|
320
|
-
const heading = headingRaw.trim();
|
|
321
|
-
const textRef = `${relativePath}#L${i + 1}`;
|
|
322
|
-
if (/\brequirements?\b/i.test(heading) && 0.84 >= minConfidence) {
|
|
323
|
-
pushSignal(signals, {
|
|
324
|
-
kind: "req",
|
|
325
|
-
title: `Author requirements from ${heading}`,
|
|
326
|
-
sourcePath: absolutePath,
|
|
327
|
-
confidence: 0.84,
|
|
328
|
-
evidence: [`generic_heading:${textRef}`],
|
|
329
|
-
}, seen);
|
|
330
|
-
}
|
|
331
|
-
if (/\bscenarios?\b/i.test(heading) && 0.83 >= minConfidence) {
|
|
332
|
-
pushSignal(signals, {
|
|
333
|
-
kind: "scenario",
|
|
334
|
-
title: `Author scenarios from ${heading}`,
|
|
335
|
-
sourcePath: absolutePath,
|
|
336
|
-
confidence: 0.83,
|
|
337
|
-
evidence: [`generic_heading:${textRef}`],
|
|
338
|
-
}, seen);
|
|
339
|
-
}
|
|
340
|
-
if (/\b(tests?|verification)\b/i.test(heading) &&
|
|
341
|
-
0.82 >= minConfidence) {
|
|
342
|
-
pushSignal(signals, {
|
|
343
|
-
kind: "test",
|
|
344
|
-
title: `Author tests from ${heading}`,
|
|
345
|
-
sourcePath: absolutePath,
|
|
346
|
-
confidence: 0.82,
|
|
347
|
-
evidence: [`generic_heading:${textRef}`],
|
|
348
|
-
}, seen);
|
|
349
|
-
}
|
|
350
|
-
}
|
|
351
|
-
}
|
|
352
|
-
catch {
|
|
353
|
-
// ignore unreadable files when deriving authoring signals
|
|
354
|
-
}
|
|
355
|
-
}
|
|
356
|
-
for (const item of discoveryResult.evidence ?? []) {
|
|
357
|
-
const confidence = typeof item.data.confidence === "number" ? item.data.confidence : 0;
|
|
358
|
-
if (item.kind === "test_topology" && confidence >= minConfidence) {
|
|
359
|
-
const sourcePath = item.absolutePath ??
|
|
360
|
-
path.resolve(workspaceRoot, item.relativePath ?? item.label);
|
|
361
|
-
const relativePath = item.relativePath ?? item.label;
|
|
362
|
-
pushSignal(signals, {
|
|
363
|
-
kind: "test",
|
|
364
|
-
title: `Author TEST coverage for ${relativePath}`,
|
|
365
|
-
sourcePath,
|
|
366
|
-
confidence,
|
|
367
|
-
evidence: Array.isArray(item.data.evidence)
|
|
368
|
-
? item.data.evidence.filter((value) => typeof value === "string")
|
|
369
|
-
: [`test_topology:${relativePath}`],
|
|
370
|
-
}, seen);
|
|
371
|
-
}
|
|
372
|
-
}
|
|
373
|
-
return signals.sort((left, right) => {
|
|
374
|
-
if (right.confidence !== left.confidence)
|
|
375
|
-
return right.confidence - left.confidence;
|
|
376
|
-
if (left.kind !== right.kind)
|
|
377
|
-
return left.kind.localeCompare(right.kind);
|
|
378
|
-
return left.sourcePath.localeCompare(right.sourcePath);
|
|
379
|
-
});
|
|
380
|
-
}
|
|
381
|
-
// implements REQ-mcp-init-kibi-autopilot-v1
|
|
382
|
-
export function buildNormativeRequirementCandidates(discoveryResult, existingEntities, minConfidence = 0.8) {
|
|
383
|
-
const candidates = [];
|
|
384
|
-
const seeds = [];
|
|
385
|
-
const workspaceRoot = existingEntities.workspaceRoot ?? process.cwd();
|
|
386
|
-
const providerScopedMarkdown = hasGenericMarkdownEvidence(discoveryResult);
|
|
387
|
-
const ignorePolicy = createRepoIgnorePolicy(workspaceRoot);
|
|
388
|
-
for (const rawPath of getGenericMarkdownFiles(discoveryResult)) {
|
|
389
|
-
try {
|
|
390
|
-
const filePath = String(rawPath);
|
|
391
|
-
const { absolutePath, relativePath } = resolveCandidatePaths(filePath, workspaceRoot);
|
|
392
|
-
if (ignorePolicy.isIgnored(relativePath))
|
|
393
|
-
continue;
|
|
394
|
-
if (!shouldIncludeGenericMarkdown(relativePath, providerScopedMarkdown))
|
|
395
|
-
continue;
|
|
396
|
-
if (!fs.existsSync(absolutePath))
|
|
397
|
-
continue;
|
|
398
|
-
const content = fs.readFileSync(absolutePath, "utf8");
|
|
399
|
-
const lines = content.split(/\r?\n/);
|
|
400
|
-
let activeHeading;
|
|
401
|
-
let activeHeadingLine;
|
|
402
|
-
let inCodeFence = false;
|
|
403
|
-
for (let i = 0; i < lines.length; i++) {
|
|
404
|
-
const line = lines[i];
|
|
405
|
-
if (line === undefined)
|
|
406
|
-
continue;
|
|
407
|
-
if (/^\s*(```|~~~)/.test(line)) {
|
|
408
|
-
inCodeFence = !inCodeFence;
|
|
409
|
-
continue;
|
|
410
|
-
}
|
|
411
|
-
if (inCodeFence)
|
|
412
|
-
continue;
|
|
413
|
-
const headingMatch = line.match(/^\s*#+\s*(.+)$/);
|
|
414
|
-
if (headingMatch?.[1]) {
|
|
415
|
-
activeHeading = headingMatch[1].trim();
|
|
416
|
-
activeHeadingLine = i + 1;
|
|
417
|
-
continue;
|
|
418
|
-
}
|
|
419
|
-
const statement = line
|
|
420
|
-
.replace(/^\s*[-*+]\s+/, "")
|
|
421
|
-
.replace(/^\s*\d+[.)]\s+/, "")
|
|
422
|
-
.trim();
|
|
423
|
-
if (!statement || !/\b(must|shall|should)\b/i.test(statement))
|
|
424
|
-
continue;
|
|
425
|
-
const confidence = estimateNormativeSignalConfidence(statement, activeHeading);
|
|
426
|
-
if (confidence < minConfidence)
|
|
427
|
-
continue;
|
|
428
|
-
const extracted = extractRequirementClaim({
|
|
429
|
-
text: statement,
|
|
430
|
-
source: relativePath,
|
|
431
|
-
confidence,
|
|
432
|
-
provenance: `${relativePath}#L${i + 1}`,
|
|
433
|
-
});
|
|
434
|
-
const writeSet = buildStrictWriteSet({
|
|
435
|
-
claim: extracted.claim,
|
|
436
|
-
statement: extracted.statement,
|
|
437
|
-
});
|
|
438
|
-
if (!writeSet.isStrict)
|
|
439
|
-
continue;
|
|
440
|
-
seeds.push({
|
|
441
|
-
input: {
|
|
442
|
-
claim: extracted.claim,
|
|
443
|
-
statement: extracted.statement,
|
|
444
|
-
},
|
|
445
|
-
writeSet,
|
|
446
|
-
sourcePath: absolutePath,
|
|
447
|
-
evidence: [
|
|
448
|
-
`normative_statement:${relativePath}#L${i + 1}`,
|
|
449
|
-
...(activeHeading && activeHeadingLine
|
|
450
|
-
? [`generic_heading:${relativePath}#L${activeHeadingLine}`]
|
|
451
|
-
: []),
|
|
452
|
-
],
|
|
453
|
-
});
|
|
454
|
-
}
|
|
455
|
-
}
|
|
456
|
-
catch {
|
|
457
|
-
// ignore unreadable files when deriving strict requirement candidates
|
|
458
|
-
}
|
|
459
|
-
}
|
|
460
|
-
const modeledIds = new Set(modelRequirementClaims(seeds.map((seed) => seed.input)).map((writeSet) => writeSetPrimaryEntityId(writeSet)));
|
|
461
|
-
const emittedIds = new Set();
|
|
462
|
-
for (const seed of seeds) {
|
|
463
|
-
const entityId = writeSetPrimaryEntityId(seed.writeSet);
|
|
464
|
-
if (!modeledIds.has(entityId) || emittedIds.has(entityId))
|
|
465
|
-
continue;
|
|
466
|
-
if (existingEntities.ids.has(entityId))
|
|
467
|
-
continue;
|
|
468
|
-
emittedIds.add(entityId);
|
|
469
|
-
candidates.push({
|
|
470
|
-
candidateId: `norm:${entityId.toLowerCase()}`,
|
|
471
|
-
entityType: "req",
|
|
472
|
-
title: seed.input.statement,
|
|
473
|
-
sourceKind: "generic_markdown",
|
|
474
|
-
sourcePath: seed.sourcePath,
|
|
475
|
-
confidence: seed.writeSet.confidence,
|
|
476
|
-
confidenceBand: toConfidenceBand(seed.writeSet.confidence),
|
|
477
|
-
evidence: seed.evidence,
|
|
478
|
-
relationships: seed.writeSet.relationships.map((relationship) => ({
|
|
479
|
-
type: relationship.type,
|
|
480
|
-
from: relationship.from,
|
|
481
|
-
to: relationship.to,
|
|
482
|
-
})),
|
|
483
|
-
applyPlan: strictWriteSetToApplyPlan(seed.writeSet),
|
|
484
|
-
});
|
|
485
|
-
}
|
|
486
|
-
return candidates;
|
|
487
|
-
}
|
|
488
|
-
// implements REQ-mcp-init-kibi-autopilot-v1
|
|
489
|
-
export function buildProviderEvidenceCandidates(discoveryResult, existingEntities, minConfidence = 0.8) {
|
|
490
|
-
const candidates = [];
|
|
491
|
-
const workspaceRoot = existingEntities.workspaceRoot ?? process.cwd();
|
|
492
|
-
for (const item of getFactEvidence(discoveryResult)) {
|
|
493
|
-
const relativePath = item.relativePath ?? item.label;
|
|
494
|
-
const absolutePath = item.absolutePath ?? path.resolve(workspaceRoot, relativePath);
|
|
495
|
-
const confidence = typeof item.data.confidence === "number" ? item.data.confidence : 0.8;
|
|
496
|
-
if (confidence < minConfidence)
|
|
497
|
-
continue;
|
|
498
|
-
const factKind = typeof item.data.factKind === "string" && item.data.factKind.length > 0
|
|
499
|
-
? item.data.factKind
|
|
500
|
-
: item.kind === "repo_metadata"
|
|
501
|
-
? "meta"
|
|
502
|
-
: "observation";
|
|
503
|
-
const title = typeof item.data.title === "string" && item.data.title.length > 0
|
|
504
|
-
? item.data.title
|
|
505
|
-
: `Autopilot evidence from ${relativePath}`;
|
|
506
|
-
const slugSource = `${item.kind}-${relativePath}`;
|
|
507
|
-
const generatedId = `FACT-GEN-${slugify(slugSource, 64) || "evidence"}`.toUpperCase();
|
|
508
|
-
if (existingEntities.ids.has(generatedId))
|
|
509
|
-
continue;
|
|
510
|
-
const textRef = relativePath.includes("#")
|
|
511
|
-
? relativePath
|
|
512
|
-
: `${relativePath}`;
|
|
513
|
-
const evidence = Array.isArray(item.data.evidence)
|
|
514
|
-
? item.data.evidence.filter((value) => typeof value === "string")
|
|
515
|
-
: [];
|
|
516
|
-
candidates.push({
|
|
517
|
-
candidateId: `prov:${item.kind}:${slugify(relativePath, 96) || "evidence"}`,
|
|
518
|
-
entityType: "fact",
|
|
519
|
-
title,
|
|
520
|
-
sourceKind: item.kind,
|
|
521
|
-
sourcePath: absolutePath,
|
|
522
|
-
confidence,
|
|
523
|
-
confidenceBand: toConfidenceBand(confidence),
|
|
524
|
-
evidence: evidence.length > 0
|
|
525
|
-
? evidence
|
|
526
|
-
: [`provider:${item.provider}`, `${item.kind}:${relativePath}`],
|
|
527
|
-
relationships: [],
|
|
528
|
-
applyPlan: [
|
|
529
|
-
{
|
|
530
|
-
type: "fact",
|
|
531
|
-
id: generatedId,
|
|
532
|
-
properties: {
|
|
533
|
-
id: generatedId,
|
|
534
|
-
title,
|
|
535
|
-
status: "active",
|
|
536
|
-
fact_kind: factKind,
|
|
537
|
-
source: `autopilot:${item.provider}:${relativePath}`,
|
|
538
|
-
text_ref: textRef,
|
|
539
|
-
},
|
|
540
|
-
relationships: [],
|
|
541
|
-
},
|
|
542
|
-
],
|
|
543
|
-
});
|
|
544
|
-
}
|
|
545
|
-
return candidates;
|
|
546
|
-
}
|
|
547
|
-
export default {
|
|
548
|
-
buildTypedMarkdownCandidates,
|
|
549
|
-
buildSymbolManifestCandidates,
|
|
550
|
-
buildGenericMarkdownCandidates,
|
|
551
|
-
collectSourceOnlyAuthoringSignals,
|
|
552
|
-
buildNormativeRequirementCandidates,
|
|
553
|
-
buildProviderEvidenceCandidates,
|
|
554
|
-
};
|