kibi-agent-core 0.0.0-stage → 0.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/CHANGELOG.md +5 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +3 -0
- package/dist/kb-mcp-tools.d.ts +16 -0
- package/dist/kb-mcp-tools.js +164 -0
- package/dist/knowledge-index.d.ts +50 -0
- package/dist/knowledge-index.js +388 -0
- package/dist/path-policy.d.ts +39 -0
- package/dist/path-policy.js +221 -0
- package/package.json +48 -4
- package/README.md +0 -3
package/CHANGELOG.md
ADDED
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Canonical Kibi operation name for a host tool name. Hosts prefix MCP tools
|
|
3
|
+
* (`mcp__kibi__kb_check`, `mcp__plugin_<plugin>_kibi__kb_check`,
|
|
4
|
+
* `MCP:kb_check`, `kibi_kb_check`); an unprefixed `kb_check` passes through.
|
|
5
|
+
*/
|
|
6
|
+
export declare function canonicalKbToolName(toolName: string | undefined): string | undefined;
|
|
7
|
+
export type KbMcpToolCall = {
|
|
8
|
+
toolName: string;
|
|
9
|
+
impactCheckRun: boolean;
|
|
10
|
+
sourceFiles: string[];
|
|
11
|
+
};
|
|
12
|
+
export type McpState = "observed" | "unknown";
|
|
13
|
+
export type KibiInterface = "mcp" | "cli" | "setup";
|
|
14
|
+
export declare function resolveKibiInterface(mcpState: McpState, workspaceTrusted: boolean): KibiInterface;
|
|
15
|
+
export declare function extractKbMcpToolCall(toolName: string | undefined, toolInput: unknown): KbMcpToolCall | undefined;
|
|
16
|
+
export declare function extractKbMcpToolName(toolName: string | undefined, toolInput: unknown): string | undefined;
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
// implements REQ-cursor-kibi-plugin-v1
|
|
2
|
+
function isRecord(value) {
|
|
3
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
4
|
+
}
|
|
5
|
+
function readString(record, keys) {
|
|
6
|
+
for (const key of keys) {
|
|
7
|
+
const value = record[key];
|
|
8
|
+
if (typeof value === "string") {
|
|
9
|
+
return value;
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
return undefined;
|
|
13
|
+
}
|
|
14
|
+
function readBoolean(record, keys) {
|
|
15
|
+
for (const key of keys) {
|
|
16
|
+
const value = record[key];
|
|
17
|
+
if (typeof value === "boolean") {
|
|
18
|
+
return value;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
return undefined;
|
|
22
|
+
}
|
|
23
|
+
function readStringArray(record, keys) {
|
|
24
|
+
for (const key of keys) {
|
|
25
|
+
const value = record[key];
|
|
26
|
+
if (!Array.isArray(value)) {
|
|
27
|
+
continue;
|
|
28
|
+
}
|
|
29
|
+
return value.filter((item) => typeof item === "string" && item.length > 0);
|
|
30
|
+
}
|
|
31
|
+
return [];
|
|
32
|
+
}
|
|
33
|
+
function readRecord(record, keys) {
|
|
34
|
+
for (const key of keys) {
|
|
35
|
+
const value = record[key];
|
|
36
|
+
if (isRecord(value)) {
|
|
37
|
+
return value;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
return undefined;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Canonical Kibi operation name for a host tool name. Hosts prefix MCP tools
|
|
44
|
+
* (`mcp__kibi__kb_check`, `mcp__plugin_<plugin>_kibi__kb_check`,
|
|
45
|
+
* `MCP:kb_check`, `kibi_kb_check`); an unprefixed `kb_check` passes through.
|
|
46
|
+
*/
|
|
47
|
+
export function canonicalKbToolName(toolName) {
|
|
48
|
+
const trimmed = toolName?.trim() ?? "";
|
|
49
|
+
const lastSegment = trimmed.includes("__")
|
|
50
|
+
? (trimmed.split("__").at(-1) ?? "")
|
|
51
|
+
: trimmed.replace(/^MCP:/i, "");
|
|
52
|
+
const operation = lastSegment.replace(/^kibi_/, "");
|
|
53
|
+
return operation.startsWith("kb_") ? operation : undefined;
|
|
54
|
+
}
|
|
55
|
+
export function resolveKibiInterface(mcpState, workspaceTrusted) {
|
|
56
|
+
if (mcpState === "observed") {
|
|
57
|
+
return "mcp";
|
|
58
|
+
}
|
|
59
|
+
return workspaceTrusted ? "cli" : "setup";
|
|
60
|
+
}
|
|
61
|
+
export function extractKbMcpToolCall(toolName, toolInput) {
|
|
62
|
+
let normalizedToolName = canonicalKbToolName(toolName);
|
|
63
|
+
if (isRecord(toolInput)) {
|
|
64
|
+
normalizedToolName ??= readString(toolInput, [
|
|
65
|
+
"toolName",
|
|
66
|
+
"tool_name",
|
|
67
|
+
"name",
|
|
68
|
+
]);
|
|
69
|
+
const args = readRecord(toolInput, ["arguments", "args"]);
|
|
70
|
+
const payload = args ?? toolInput;
|
|
71
|
+
const includeImpactDiagnostics = readBoolean(payload, [
|
|
72
|
+
"includeImpactDiagnostics",
|
|
73
|
+
"include_impact_diagnostics",
|
|
74
|
+
]);
|
|
75
|
+
const includeWorkingTreeDiff = readBoolean(payload, [
|
|
76
|
+
"includeWorkingTreeDiff",
|
|
77
|
+
"include_working_tree_diff",
|
|
78
|
+
]);
|
|
79
|
+
const sourceFiles = readStringArray(payload, [
|
|
80
|
+
"sourceFiles",
|
|
81
|
+
"source_files",
|
|
82
|
+
]);
|
|
83
|
+
if (normalizedToolName?.startsWith("kb_")) {
|
|
84
|
+
return {
|
|
85
|
+
toolName: normalizedToolName,
|
|
86
|
+
impactCheckRun: normalizedToolName === "kb_check" &&
|
|
87
|
+
includeImpactDiagnostics === true &&
|
|
88
|
+
includeWorkingTreeDiff === true &&
|
|
89
|
+
sourceFiles.length > 0,
|
|
90
|
+
sourceFiles,
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
const nestedArgs = toolInput.arguments ?? toolInput.args;
|
|
94
|
+
if (isRecord(nestedArgs)) {
|
|
95
|
+
const nestedTool = readString(nestedArgs, [
|
|
96
|
+
"toolName",
|
|
97
|
+
"tool_name",
|
|
98
|
+
"name",
|
|
99
|
+
]);
|
|
100
|
+
if (nestedTool?.startsWith("kb_")) {
|
|
101
|
+
return { toolName: nestedTool, impactCheckRun: false, sourceFiles: [] };
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
if (normalizedToolName?.startsWith("kb_")) {
|
|
106
|
+
return {
|
|
107
|
+
toolName: normalizedToolName,
|
|
108
|
+
impactCheckRun: false,
|
|
109
|
+
sourceFiles: [],
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
return undefined;
|
|
113
|
+
}
|
|
114
|
+
export function extractKbMcpToolName(toolName, toolInput) {
|
|
115
|
+
const toolCall = extractKbMcpToolCall(toolName, toolInput);
|
|
116
|
+
// rationale: the fallbacks below re-derive the same name from the same
|
|
117
|
+
// inputs, so dropping the toolCall shortcut is observationally equivalent.
|
|
118
|
+
// Stryker disable next-line ConditionalExpression, BlockStatement
|
|
119
|
+
if (toolCall) {
|
|
120
|
+
return toolCall.toolName;
|
|
121
|
+
}
|
|
122
|
+
// rationale: any tool name that trims to a kb_ prefix already produced a
|
|
123
|
+
// tool call above, so this branch only sees equivalent names.
|
|
124
|
+
// Stryker disable next-line MethodExpression
|
|
125
|
+
if (toolName) {
|
|
126
|
+
// rationale: any name that trims to a kb_ prefix already produced a tool
|
|
127
|
+
// call above, so this trim cannot change the returned name.
|
|
128
|
+
// Stryker disable MethodExpression
|
|
129
|
+
const normalized = toolName.trim();
|
|
130
|
+
if (normalized.startsWith("kb_")) {
|
|
131
|
+
return normalized;
|
|
132
|
+
}
|
|
133
|
+
// Stryker restore
|
|
134
|
+
}
|
|
135
|
+
if (!isRecord(toolInput)) {
|
|
136
|
+
return undefined;
|
|
137
|
+
}
|
|
138
|
+
// rationale: this alias list duplicates the one inside
|
|
139
|
+
// extractKbMcpToolCall, which already returned for every kb_ match.
|
|
140
|
+
// Stryker disable next-line StringLiteral
|
|
141
|
+
const directTool = readString(toolInput, ["toolName", "tool_name", "name"]);
|
|
142
|
+
if (directTool?.startsWith("kb_")) {
|
|
143
|
+
return directTool;
|
|
144
|
+
}
|
|
145
|
+
const nestedArgs = toolInput.arguments ?? toolInput.args;
|
|
146
|
+
if (isRecord(nestedArgs)) {
|
|
147
|
+
// rationale: this alias list duplicates the one inside
|
|
148
|
+
// extractKbMcpToolCall, which already returned for every kb_ match.
|
|
149
|
+
// Stryker disable next-line StringLiteral
|
|
150
|
+
// rationale: duplicate alias list; extractKbMcpToolCall already
|
|
151
|
+
// returned for every kb_ match.
|
|
152
|
+
// Stryker disable StringLiteral
|
|
153
|
+
const nestedTool = readString(nestedArgs, [
|
|
154
|
+
"toolName",
|
|
155
|
+
"tool_name",
|
|
156
|
+
"name",
|
|
157
|
+
]);
|
|
158
|
+
// Stryker restore
|
|
159
|
+
if (nestedTool?.startsWith("kb_")) {
|
|
160
|
+
return nestedTool;
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
return undefined;
|
|
164
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
export type KnowledgeIndexOptions = {
|
|
2
|
+
/** Legacy string links mean requirement ownership only in adapters that opt in. */
|
|
3
|
+
legacyLinksAsImplements?: boolean;
|
|
4
|
+
/** Lets multiple adapter indexes coexist in one host-owned cache directory. */
|
|
5
|
+
cacheFileName?: string;
|
|
6
|
+
};
|
|
7
|
+
export type IndexedSymbol = {
|
|
8
|
+
id: string;
|
|
9
|
+
title: string;
|
|
10
|
+
line?: number;
|
|
11
|
+
endLine?: number;
|
|
12
|
+
implements: string[];
|
|
13
|
+
coveredBy: string[];
|
|
14
|
+
executableFor: string[];
|
|
15
|
+
};
|
|
16
|
+
export type KnowledgeIndex = {
|
|
17
|
+
/** Workspace-relative source file -> symbols with at least one relationship. */
|
|
18
|
+
files: Record<string, IndexedSymbol[]>;
|
|
19
|
+
symbolCount: number;
|
|
20
|
+
};
|
|
21
|
+
type RawRecord = Record<string, unknown>;
|
|
22
|
+
/**
|
|
23
|
+
* Scan the symbols manifest into raw records. Accepts both the canonical
|
|
24
|
+
* `symbols:` mapping and a bare top-level sequence.
|
|
25
|
+
*/
|
|
26
|
+
export declare function scanSymbolsManifest(text: string): RawRecord[];
|
|
27
|
+
export type Coordinates = Record<string, {
|
|
28
|
+
line?: number;
|
|
29
|
+
endLine?: number;
|
|
30
|
+
}>;
|
|
31
|
+
/** Scan `coordinates:` -> `<symbol id>:` -> `sourceLine` / `sourceEndLine`. */
|
|
32
|
+
export declare function scanSymbolCoordinates(text: string): Coordinates;
|
|
33
|
+
/** Collapse raw manifest records and coordinates into the file-keyed index. */
|
|
34
|
+
export declare function buildKnowledgeIndex(records: readonly RawRecord[], coordinates?: Coordinates, options?: KnowledgeIndexOptions): KnowledgeIndex;
|
|
35
|
+
/**
|
|
36
|
+
* Load the index for a workspace, reusing the cache in `cacheDir` while both
|
|
37
|
+
* manifests keep their size and mtime.
|
|
38
|
+
*/
|
|
39
|
+
export declare function loadKnowledgeIndex(workspaceRoot: string, cacheDir?: string, options?: KnowledgeIndexOptions): KnowledgeIndex;
|
|
40
|
+
export type EntitySummary = {
|
|
41
|
+
id: string;
|
|
42
|
+
title?: string;
|
|
43
|
+
status?: string;
|
|
44
|
+
};
|
|
45
|
+
/**
|
|
46
|
+
* Read an entity's title and status from its canonical authored document
|
|
47
|
+
* (`.kb/<lane>/<ID>.md`). Only the frontmatter head is read.
|
|
48
|
+
*/
|
|
49
|
+
export declare function readEntitySummary(workspaceRoot: string, entityId: string): EntitySummary;
|
|
50
|
+
export {};
|
|
@@ -0,0 +1,388 @@
|
|
|
1
|
+
// implements REQ-claude-code-kibi-plugin-v1
|
|
2
|
+
import { randomUUID } from "node:crypto";
|
|
3
|
+
import fs from "node:fs";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
/**
|
|
6
|
+
* File-keyed view of the Kibi symbol manifest, built for hook latency.
|
|
7
|
+
*
|
|
8
|
+
* Hooks run before every read and edit, so they cannot afford a KB query
|
|
9
|
+
* (a CLI round trip costs seconds) or a full YAML parse of a large manifest.
|
|
10
|
+
* This module line-scans `.kb/symbols.yaml` and `.kb/symbol-coordinates.yaml`
|
|
11
|
+
* once, keeps only symbols that carry relationships, and caches the result as
|
|
12
|
+
* JSON keyed by both files' size and mtime. Every later hook pays one stat per
|
|
13
|
+
* file plus a JSON parse.
|
|
14
|
+
*
|
|
15
|
+
* The scanner understands the subset the Kibi writer emits (block mappings and
|
|
16
|
+
* sequences, quoted scalars, no line folding) plus hand-authored flow lists
|
|
17
|
+
* such as `links: [REQ-001]`. The manifest is a hint for progressive
|
|
18
|
+
* disclosure, not the source of truth: snippets always point at kb_query for
|
|
19
|
+
* the authoritative view.
|
|
20
|
+
*/
|
|
21
|
+
const SYMBOLS_MANIFEST = ".kb/symbols.yaml";
|
|
22
|
+
const SYMBOL_COORDINATES = ".kb/symbol-coordinates.yaml";
|
|
23
|
+
const INDEX_FORMAT = "kibi-agent-core.knowledge-index.v1";
|
|
24
|
+
const CACHE_FILE = "knowledge-index.json";
|
|
25
|
+
function unquote(raw) {
|
|
26
|
+
const value = raw.trim();
|
|
27
|
+
if (value.startsWith('"') && value.endsWith('"') && value.length >= 2) {
|
|
28
|
+
try {
|
|
29
|
+
return JSON.parse(value);
|
|
30
|
+
}
|
|
31
|
+
catch {
|
|
32
|
+
return value.slice(1, -1);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
if (value.startsWith("'") && value.endsWith("'") && value.length >= 2) {
|
|
36
|
+
return value.slice(1, -1).replaceAll("''", "'");
|
|
37
|
+
}
|
|
38
|
+
// Plain scalars may carry a trailing comment.
|
|
39
|
+
const comment = value.search(/\s#/);
|
|
40
|
+
return comment >= 0 ? value.slice(0, comment).trimEnd() : value;
|
|
41
|
+
}
|
|
42
|
+
function parseFlowList(raw) {
|
|
43
|
+
const value = raw.trim();
|
|
44
|
+
if (!value.startsWith("[") || !value.endsWith("]"))
|
|
45
|
+
return undefined;
|
|
46
|
+
const inner = value.slice(1, -1).trim();
|
|
47
|
+
if (inner.length === 0)
|
|
48
|
+
return [];
|
|
49
|
+
return inner
|
|
50
|
+
.split(",")
|
|
51
|
+
.map(unquote)
|
|
52
|
+
.filter((item) => item.length > 0);
|
|
53
|
+
}
|
|
54
|
+
const keyValue = /^([A-Za-z0-9_"'-][^:]*?):(?:\s+(.*))?$/;
|
|
55
|
+
function parseScalarOrFlow(raw) {
|
|
56
|
+
if (raw === undefined || raw.trim().length === 0)
|
|
57
|
+
return undefined;
|
|
58
|
+
return parseFlowList(raw) ?? unquote(raw);
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Scan the symbols manifest into raw records. Accepts both the canonical
|
|
62
|
+
* `symbols:` mapping and a bare top-level sequence.
|
|
63
|
+
*/
|
|
64
|
+
export function scanSymbolsManifest(text) {
|
|
65
|
+
const records = [];
|
|
66
|
+
let itemIndent;
|
|
67
|
+
let keyIndent = 0;
|
|
68
|
+
let current;
|
|
69
|
+
let listKey;
|
|
70
|
+
let listItem;
|
|
71
|
+
// Top-level section: symbols are the `symbols:` sequence or a bare root
|
|
72
|
+
// sequence; sequences under any other top-level key are ignored.
|
|
73
|
+
let section = "root";
|
|
74
|
+
for (const rawLine of text.split(/\r?\n/)) {
|
|
75
|
+
const content = rawLine.trim();
|
|
76
|
+
if (content.length === 0 || content.startsWith("#"))
|
|
77
|
+
continue;
|
|
78
|
+
const indent = rawLine.length - rawLine.trimStart().length;
|
|
79
|
+
const dash = /^(\s*)-(\s+)(.*)$/.exec(rawLine);
|
|
80
|
+
if (indent === 0 && !dash) {
|
|
81
|
+
const pair = keyValue.exec(content);
|
|
82
|
+
section =
|
|
83
|
+
pair?.[1] !== undefined && unquote(pair[1]) === "symbols"
|
|
84
|
+
? "symbols"
|
|
85
|
+
: "other";
|
|
86
|
+
current = undefined;
|
|
87
|
+
itemIndent = undefined;
|
|
88
|
+
continue;
|
|
89
|
+
}
|
|
90
|
+
if (section === "other")
|
|
91
|
+
continue;
|
|
92
|
+
if (dash && (itemIndent === undefined || indent === itemIndent)) {
|
|
93
|
+
itemIndent = indent;
|
|
94
|
+
keyIndent = (dash[1]?.length ?? 0) + 1 + (dash[2]?.length ?? 1);
|
|
95
|
+
current = {};
|
|
96
|
+
records.push(current);
|
|
97
|
+
listKey = undefined;
|
|
98
|
+
listItem = undefined;
|
|
99
|
+
const pair = keyValue.exec(dash[3] ?? "");
|
|
100
|
+
if (pair?.[1] !== undefined) {
|
|
101
|
+
const value = parseScalarOrFlow(pair[2]);
|
|
102
|
+
if (value !== undefined)
|
|
103
|
+
current[unquote(pair[1])] = value;
|
|
104
|
+
else
|
|
105
|
+
listKey = unquote(pair[1]);
|
|
106
|
+
}
|
|
107
|
+
continue;
|
|
108
|
+
}
|
|
109
|
+
if (current === undefined || itemIndent === undefined)
|
|
110
|
+
continue;
|
|
111
|
+
if (indent <= itemIndent)
|
|
112
|
+
continue;
|
|
113
|
+
if (dash) {
|
|
114
|
+
if (listKey === undefined)
|
|
115
|
+
continue;
|
|
116
|
+
const list = Array.isArray(current[listKey])
|
|
117
|
+
? current[listKey]
|
|
118
|
+
: [];
|
|
119
|
+
current[listKey] = list;
|
|
120
|
+
const pair = keyValue.exec(dash[3] ?? "");
|
|
121
|
+
if (pair?.[1] !== undefined && pair[2] !== undefined) {
|
|
122
|
+
listItem = { [unquote(pair[1])]: unquote(pair[2]) };
|
|
123
|
+
list.push(listItem);
|
|
124
|
+
}
|
|
125
|
+
else {
|
|
126
|
+
listItem = undefined;
|
|
127
|
+
list.push(unquote(dash[3] ?? ""));
|
|
128
|
+
}
|
|
129
|
+
continue;
|
|
130
|
+
}
|
|
131
|
+
const pair = keyValue.exec(content);
|
|
132
|
+
if (pair?.[1] === undefined)
|
|
133
|
+
continue;
|
|
134
|
+
const key = unquote(pair[1]);
|
|
135
|
+
if (indent === keyIndent) {
|
|
136
|
+
listItem = undefined;
|
|
137
|
+
const value = parseScalarOrFlow(pair[2]);
|
|
138
|
+
if (value === undefined) {
|
|
139
|
+
listKey = key;
|
|
140
|
+
}
|
|
141
|
+
else {
|
|
142
|
+
current[key] = value;
|
|
143
|
+
listKey = undefined;
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
else if (listItem !== undefined && pair[2] !== undefined) {
|
|
147
|
+
listItem[key] = unquote(pair[2]);
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
return records;
|
|
151
|
+
}
|
|
152
|
+
/** Scan `coordinates:` -> `<symbol id>:` -> `sourceLine` / `sourceEndLine`. */
|
|
153
|
+
export function scanSymbolCoordinates(text) {
|
|
154
|
+
const coordinates = {};
|
|
155
|
+
let inCoordinates = false;
|
|
156
|
+
let idIndent;
|
|
157
|
+
let currentId;
|
|
158
|
+
for (const rawLine of text.split(/\r?\n/)) {
|
|
159
|
+
const content = rawLine.trim();
|
|
160
|
+
if (content.length === 0 || content.startsWith("#"))
|
|
161
|
+
continue;
|
|
162
|
+
const indent = rawLine.length - rawLine.trimStart().length;
|
|
163
|
+
const pair = keyValue.exec(content);
|
|
164
|
+
if (pair?.[1] === undefined)
|
|
165
|
+
continue;
|
|
166
|
+
const key = unquote(pair[1]);
|
|
167
|
+
if (indent === 0) {
|
|
168
|
+
inCoordinates = key === "coordinates";
|
|
169
|
+
idIndent = undefined;
|
|
170
|
+
currentId = undefined;
|
|
171
|
+
continue;
|
|
172
|
+
}
|
|
173
|
+
if (!inCoordinates)
|
|
174
|
+
continue;
|
|
175
|
+
if (idIndent === undefined || indent === idIndent) {
|
|
176
|
+
idIndent = indent;
|
|
177
|
+
currentId = key;
|
|
178
|
+
coordinates[currentId] ??= {};
|
|
179
|
+
continue;
|
|
180
|
+
}
|
|
181
|
+
if (currentId === undefined || indent < idIndent)
|
|
182
|
+
continue;
|
|
183
|
+
const entry = coordinates[currentId] ?? {};
|
|
184
|
+
const numeric = Number.parseInt(unquote(pair[2] ?? ""), 10);
|
|
185
|
+
if (!Number.isFinite(numeric))
|
|
186
|
+
continue;
|
|
187
|
+
if (key === "sourceLine")
|
|
188
|
+
entry.line = numeric;
|
|
189
|
+
if (key === "sourceEndLine")
|
|
190
|
+
entry.endLine = numeric;
|
|
191
|
+
coordinates[currentId] = entry;
|
|
192
|
+
}
|
|
193
|
+
return coordinates;
|
|
194
|
+
}
|
|
195
|
+
function toPositiveInt(value) {
|
|
196
|
+
const parsed = typeof value === "number" ? value : Number.parseInt(String(value), 10);
|
|
197
|
+
return Number.isFinite(parsed) && parsed > 0 ? parsed : undefined;
|
|
198
|
+
}
|
|
199
|
+
function pushUnique(list, value) {
|
|
200
|
+
if (!list.includes(value))
|
|
201
|
+
list.push(value);
|
|
202
|
+
}
|
|
203
|
+
/** Collapse raw manifest records and coordinates into the file-keyed index. */
|
|
204
|
+
export function buildKnowledgeIndex(records, coordinates = {}, options = {}) {
|
|
205
|
+
const files = {};
|
|
206
|
+
let symbolCount = 0;
|
|
207
|
+
for (const record of records) {
|
|
208
|
+
const id = typeof record.id === "string" ? record.id : undefined;
|
|
209
|
+
const sourceFile = typeof record.sourceFile === "string"
|
|
210
|
+
? record.sourceFile.replaceAll("\\", "/").replace(/^\.\//, "")
|
|
211
|
+
: undefined;
|
|
212
|
+
if (!id || !sourceFile)
|
|
213
|
+
continue;
|
|
214
|
+
symbolCount += 1;
|
|
215
|
+
const symbol = {
|
|
216
|
+
id,
|
|
217
|
+
title: typeof record.title === "string" ? record.title : id,
|
|
218
|
+
implements: [],
|
|
219
|
+
coveredBy: [],
|
|
220
|
+
executableFor: [],
|
|
221
|
+
};
|
|
222
|
+
const relationships = [];
|
|
223
|
+
if (options.legacyLinksAsImplements !== false) {
|
|
224
|
+
let links = [];
|
|
225
|
+
if (typeof record.links === "string")
|
|
226
|
+
links = [record.links];
|
|
227
|
+
else if (Array.isArray(record.links))
|
|
228
|
+
links = record.links;
|
|
229
|
+
for (const link of links) {
|
|
230
|
+
if (typeof link === "string") {
|
|
231
|
+
relationships.push({ type: "implements", target: link });
|
|
232
|
+
}
|
|
233
|
+
else if (link !== null && typeof link === "object") {
|
|
234
|
+
const typed = link;
|
|
235
|
+
if (typeof typed.type === "string" &&
|
|
236
|
+
typeof typed.target === "string") {
|
|
237
|
+
relationships.push({ type: typed.type, target: typed.target });
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
for (const relation of Array.isArray(record.relationships)
|
|
243
|
+
? record.relationships
|
|
244
|
+
: []) {
|
|
245
|
+
if (relation === null || typeof relation !== "object")
|
|
246
|
+
continue;
|
|
247
|
+
const typed = relation;
|
|
248
|
+
if (typeof typed.type === "string" && typeof typed.target === "string")
|
|
249
|
+
relationships.push({ type: typed.type, target: typed.target });
|
|
250
|
+
}
|
|
251
|
+
for (const { type, target } of relationships) {
|
|
252
|
+
if (type === "implements")
|
|
253
|
+
pushUnique(symbol.implements, target);
|
|
254
|
+
else if (type === "covered_by")
|
|
255
|
+
pushUnique(symbol.coveredBy, target);
|
|
256
|
+
else if (type === "executable_for")
|
|
257
|
+
pushUnique(symbol.executableFor, target);
|
|
258
|
+
}
|
|
259
|
+
if (symbol.implements.length +
|
|
260
|
+
symbol.coveredBy.length +
|
|
261
|
+
symbol.executableFor.length ===
|
|
262
|
+
0) {
|
|
263
|
+
continue;
|
|
264
|
+
}
|
|
265
|
+
const line = toPositiveInt(record.sourceLine) ?? coordinates[id]?.line ?? undefined;
|
|
266
|
+
const endLine = toPositiveInt(record.sourceEndLine) ??
|
|
267
|
+
coordinates[id]?.endLine ??
|
|
268
|
+
undefined;
|
|
269
|
+
if (line !== undefined)
|
|
270
|
+
symbol.line = line;
|
|
271
|
+
if (endLine !== undefined)
|
|
272
|
+
symbol.endLine = endLine;
|
|
273
|
+
const bucket = files[sourceFile] ?? [];
|
|
274
|
+
bucket.push(symbol);
|
|
275
|
+
files[sourceFile] = bucket;
|
|
276
|
+
}
|
|
277
|
+
return { files, symbolCount };
|
|
278
|
+
}
|
|
279
|
+
function fileSignature(filePath) {
|
|
280
|
+
try {
|
|
281
|
+
const stats = fs.statSync(filePath);
|
|
282
|
+
return `${stats.size}:${stats.mtimeMs}`;
|
|
283
|
+
}
|
|
284
|
+
catch {
|
|
285
|
+
return "missing";
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
function readText(filePath) {
|
|
289
|
+
try {
|
|
290
|
+
return fs.readFileSync(filePath, "utf8");
|
|
291
|
+
}
|
|
292
|
+
catch {
|
|
293
|
+
return undefined;
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
function writeCacheAtomically(cachePath, payload) {
|
|
297
|
+
try {
|
|
298
|
+
fs.mkdirSync(path.dirname(cachePath), { recursive: true });
|
|
299
|
+
const temporary = `${cachePath}.${process.pid}.${randomUUID()}.tmp`;
|
|
300
|
+
fs.writeFileSync(temporary, payload);
|
|
301
|
+
fs.renameSync(temporary, cachePath);
|
|
302
|
+
}
|
|
303
|
+
catch {
|
|
304
|
+
// The cache is an optimization; a failed write only costs the next hook a rescan.
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
/**
|
|
308
|
+
* Load the index for a workspace, reusing the cache in `cacheDir` while both
|
|
309
|
+
* manifests keep their size and mtime.
|
|
310
|
+
*/
|
|
311
|
+
export function loadKnowledgeIndex(workspaceRoot, cacheDir, options = {}) {
|
|
312
|
+
const symbolsPath = path.join(workspaceRoot, SYMBOLS_MANIFEST);
|
|
313
|
+
const coordinatesPath = path.join(workspaceRoot, SYMBOL_COORDINATES);
|
|
314
|
+
const legacyMode = options.legacyLinksAsImplements === false ? "typed" : "legacy";
|
|
315
|
+
const signature = `${INDEX_FORMAT}|${legacyMode}|${fileSignature(symbolsPath)}|${fileSignature(coordinatesPath)}`;
|
|
316
|
+
const cachePath = cacheDir
|
|
317
|
+
? path.join(cacheDir, options.cacheFileName ?? CACHE_FILE)
|
|
318
|
+
: undefined;
|
|
319
|
+
if (cachePath) {
|
|
320
|
+
const cached = readText(cachePath);
|
|
321
|
+
if (cached !== undefined) {
|
|
322
|
+
try {
|
|
323
|
+
const parsed = JSON.parse(cached);
|
|
324
|
+
if (parsed.signature === signature && parsed.index)
|
|
325
|
+
return parsed.index;
|
|
326
|
+
}
|
|
327
|
+
catch {
|
|
328
|
+
// Corrupt cache: rebuild below.
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
const symbolsText = readText(symbolsPath);
|
|
333
|
+
const index = symbolsText === undefined
|
|
334
|
+
? { files: {}, symbolCount: 0 }
|
|
335
|
+
: buildKnowledgeIndex(scanSymbolsManifest(symbolsText), scanSymbolCoordinates(readText(coordinatesPath) ?? ""), options);
|
|
336
|
+
if (cachePath) {
|
|
337
|
+
writeCacheAtomically(cachePath, JSON.stringify({ signature, index }));
|
|
338
|
+
}
|
|
339
|
+
return index;
|
|
340
|
+
}
|
|
341
|
+
const ENTITY_LANES = [
|
|
342
|
+
["REQ-", "requirements"],
|
|
343
|
+
["SCEN-", "scenarios"],
|
|
344
|
+
["TEST-", "tests"],
|
|
345
|
+
["ADR-", "adr"],
|
|
346
|
+
["FACT-", "facts"],
|
|
347
|
+
["FLAG-", "flags"],
|
|
348
|
+
["EVT-", "events"],
|
|
349
|
+
];
|
|
350
|
+
const SAFE_ENTITY_ID = /^[A-Za-z0-9][A-Za-z0-9._-]*$/;
|
|
351
|
+
/**
|
|
352
|
+
* Read an entity's title and status from its canonical authored document
|
|
353
|
+
* (`.kb/<lane>/<ID>.md`). Only the frontmatter head is read.
|
|
354
|
+
*/
|
|
355
|
+
export function readEntitySummary(workspaceRoot, entityId) {
|
|
356
|
+
const lane = ENTITY_LANES.find(([prefix]) => entityId.startsWith(prefix));
|
|
357
|
+
if (!lane || !SAFE_ENTITY_ID.test(entityId))
|
|
358
|
+
return { id: entityId };
|
|
359
|
+
let head;
|
|
360
|
+
try {
|
|
361
|
+
const descriptor = fs.openSync(path.join(workspaceRoot, ".kb", lane[1], `${entityId}.md`), "r");
|
|
362
|
+
try {
|
|
363
|
+
const buffer = Buffer.alloc(4096);
|
|
364
|
+
const bytes = fs.readSync(descriptor, buffer, 0, buffer.length, 0);
|
|
365
|
+
head = buffer.subarray(0, bytes).toString("utf8");
|
|
366
|
+
}
|
|
367
|
+
finally {
|
|
368
|
+
fs.closeSync(descriptor);
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
catch {
|
|
372
|
+
return { id: entityId };
|
|
373
|
+
}
|
|
374
|
+
const lines = head.split(/\r?\n/);
|
|
375
|
+
if (lines[0]?.trim() !== "---")
|
|
376
|
+
return { id: entityId };
|
|
377
|
+
const summary = { id: entityId };
|
|
378
|
+
for (const line of lines.slice(1)) {
|
|
379
|
+
if (line.trim() === "---")
|
|
380
|
+
break;
|
|
381
|
+
const match = /^(title|status):\s*(.*)$/.exec(line);
|
|
382
|
+
if (match?.[1] === "title" && match[2])
|
|
383
|
+
summary.title = unquote(match[2]);
|
|
384
|
+
if (match?.[1] === "status" && match[2])
|
|
385
|
+
summary.status = unquote(match[2]);
|
|
386
|
+
}
|
|
387
|
+
return summary;
|
|
388
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What a workspace path means for Kibi guidance.
|
|
3
|
+
*
|
|
4
|
+
* - `source`: production code whose behavior requirements may own.
|
|
5
|
+
* - `test`: test code (owned through TEST entities / executable_for).
|
|
6
|
+
* - `kb`: Kibi-managed knowledge under `.kb/`.
|
|
7
|
+
* - `other`: docs, config, generated output, dependencies — no snippets.
|
|
8
|
+
*
|
|
9
|
+
* Source detection deliberately does not require a `src/` segment: `lib/`,
|
|
10
|
+
* `app/`, and root-level packages are source too.
|
|
11
|
+
*/
|
|
12
|
+
export type PathKind = "source" | "test" | "kb" | "other";
|
|
13
|
+
export type WorkspacePath = {
|
|
14
|
+
/** Path relative to the workspace root, forward slashes. */
|
|
15
|
+
relative: string;
|
|
16
|
+
absolute: string;
|
|
17
|
+
};
|
|
18
|
+
export type CanonicalWorkspacePath = {
|
|
19
|
+
workspaceRelative: string;
|
|
20
|
+
absolute: string;
|
|
21
|
+
};
|
|
22
|
+
export declare function normalizeWorkspacePath(candidate: string): string;
|
|
23
|
+
export declare function extractExplicitPathFields(input: unknown): string[];
|
|
24
|
+
export declare function canonicalizeWorkspacePath(workspaceRoot: string, options: {
|
|
25
|
+
eventCwd?: string | undefined;
|
|
26
|
+
base?: string | undefined;
|
|
27
|
+
rawPath: string;
|
|
28
|
+
}): CanonicalWorkspacePath | undefined;
|
|
29
|
+
/**
|
|
30
|
+
* Resolve a tool-call path against the event cwd and express it relative to
|
|
31
|
+
* the Kibi workspace root. Paths outside the workspace return undefined.
|
|
32
|
+
*/
|
|
33
|
+
export declare function toWorkspacePath(workspaceRoot: string, rawPath: string, eventCwd?: string): WorkspacePath | undefined;
|
|
34
|
+
export declare function classifyPath(relativePath: string): PathKind;
|
|
35
|
+
export declare function isKbPath(candidate: string): boolean;
|
|
36
|
+
export declare function isRootKbPath(candidate: string): boolean;
|
|
37
|
+
export declare function isMeaningfulTrackedPath(candidate: string): boolean;
|
|
38
|
+
export declare function isSourceImpactRelevantPath(candidate: string): boolean;
|
|
39
|
+
export declare function isDocumentationTrackedPath(candidate: string): boolean;
|
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
// implements REQ-claude-code-kibi-plugin-v1
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
const codeExtensions = new Set([
|
|
4
|
+
".c",
|
|
5
|
+
".cc",
|
|
6
|
+
".cjs",
|
|
7
|
+
".cpp",
|
|
8
|
+
".cs",
|
|
9
|
+
".css",
|
|
10
|
+
".go",
|
|
11
|
+
".h",
|
|
12
|
+
".hpp",
|
|
13
|
+
".html",
|
|
14
|
+
".java",
|
|
15
|
+
".js",
|
|
16
|
+
".jsx",
|
|
17
|
+
".kt",
|
|
18
|
+
".lua",
|
|
19
|
+
".mjs",
|
|
20
|
+
".mts",
|
|
21
|
+
".cts",
|
|
22
|
+
".php",
|
|
23
|
+
".pl",
|
|
24
|
+
".py",
|
|
25
|
+
".rb",
|
|
26
|
+
".rs",
|
|
27
|
+
".scala",
|
|
28
|
+
".sh",
|
|
29
|
+
".swift",
|
|
30
|
+
".ts",
|
|
31
|
+
".tsx",
|
|
32
|
+
".vue",
|
|
33
|
+
".svelte",
|
|
34
|
+
]);
|
|
35
|
+
/** Directories whose contents are generated, vendored, or tool state. */
|
|
36
|
+
const ignoredSegments = new Set([
|
|
37
|
+
"node_modules",
|
|
38
|
+
"dist",
|
|
39
|
+
"build",
|
|
40
|
+
"out",
|
|
41
|
+
"coverage",
|
|
42
|
+
"vendor",
|
|
43
|
+
"target",
|
|
44
|
+
".git",
|
|
45
|
+
".next",
|
|
46
|
+
".turbo",
|
|
47
|
+
".cache",
|
|
48
|
+
"__pycache__",
|
|
49
|
+
]);
|
|
50
|
+
const testSegments = new Set([
|
|
51
|
+
"test",
|
|
52
|
+
"tests",
|
|
53
|
+
"__tests__",
|
|
54
|
+
"spec",
|
|
55
|
+
"specs",
|
|
56
|
+
"e2e",
|
|
57
|
+
"__mocks__",
|
|
58
|
+
"fixtures",
|
|
59
|
+
]);
|
|
60
|
+
const testBasename = /(\.|_)(test|spec|e2e)\.[^.]+$|^test_[^/]+\.py$/;
|
|
61
|
+
const documentationExtensions = new Set([".md", ".mdx", ".rst", ".txt"]);
|
|
62
|
+
const documentationSegments = new Set(["docs", "documentation"]);
|
|
63
|
+
const canonicalKbKnowledgeLanes = new Set([
|
|
64
|
+
"requirements",
|
|
65
|
+
"scenarios",
|
|
66
|
+
"tests",
|
|
67
|
+
"facts",
|
|
68
|
+
"adr",
|
|
69
|
+
"flags",
|
|
70
|
+
"events",
|
|
71
|
+
]);
|
|
72
|
+
const canonicalKbKnowledgeFiles = new Set([
|
|
73
|
+
"symbols.yaml",
|
|
74
|
+
"symbol-coordinates.yaml",
|
|
75
|
+
]);
|
|
76
|
+
const explicitPathKeys = new Set([
|
|
77
|
+
"absolute_path",
|
|
78
|
+
"file",
|
|
79
|
+
"file_path",
|
|
80
|
+
"filepath",
|
|
81
|
+
"new_path",
|
|
82
|
+
"old_path",
|
|
83
|
+
"path",
|
|
84
|
+
"paths",
|
|
85
|
+
"relative_path",
|
|
86
|
+
"target_path",
|
|
87
|
+
]);
|
|
88
|
+
function isRecord(value) {
|
|
89
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
90
|
+
}
|
|
91
|
+
export function normalizeWorkspacePath(candidate) {
|
|
92
|
+
return candidate.trim().replaceAll("\\", "/");
|
|
93
|
+
}
|
|
94
|
+
function pathSegments(candidate) {
|
|
95
|
+
return normalizeWorkspacePath(candidate).split("/").filter(Boolean);
|
|
96
|
+
}
|
|
97
|
+
function collectPathValues(value, output) {
|
|
98
|
+
if (typeof value === "string") {
|
|
99
|
+
const normalized = normalizeWorkspacePath(value);
|
|
100
|
+
if (normalized.length > 0)
|
|
101
|
+
output.push(normalized);
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
if (!Array.isArray(value))
|
|
105
|
+
return;
|
|
106
|
+
for (const item of value)
|
|
107
|
+
collectPathValues(item, output);
|
|
108
|
+
}
|
|
109
|
+
function visitExplicitPathFields(value, output) {
|
|
110
|
+
if (Array.isArray(value)) {
|
|
111
|
+
for (const item of value)
|
|
112
|
+
visitExplicitPathFields(item, output);
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
115
|
+
if (!isRecord(value))
|
|
116
|
+
return;
|
|
117
|
+
for (const [key, child] of Object.entries(value)) {
|
|
118
|
+
if (explicitPathKeys.has(key.toLowerCase()))
|
|
119
|
+
collectPathValues(child, output);
|
|
120
|
+
visitExplicitPathFields(child, output);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
export function extractExplicitPathFields(input) {
|
|
124
|
+
const paths = [];
|
|
125
|
+
visitExplicitPathFields(input, paths);
|
|
126
|
+
return [...new Set(paths)];
|
|
127
|
+
}
|
|
128
|
+
export function canonicalizeWorkspacePath(workspaceRoot, options) {
|
|
129
|
+
const trimmed = normalizeWorkspacePath(options.rawPath);
|
|
130
|
+
if (trimmed.length === 0)
|
|
131
|
+
return undefined;
|
|
132
|
+
const base = options.base ?? options.eventCwd ?? workspaceRoot;
|
|
133
|
+
const absolute = path.isAbsolute(trimmed)
|
|
134
|
+
? path.resolve(trimmed)
|
|
135
|
+
: path.resolve(base, trimmed);
|
|
136
|
+
const workspaceRelative = path
|
|
137
|
+
.relative(workspaceRoot, absolute)
|
|
138
|
+
.replaceAll("\\", "/");
|
|
139
|
+
if (workspaceRelative.length === 0 ||
|
|
140
|
+
workspaceRelative === ".." ||
|
|
141
|
+
workspaceRelative.startsWith("../") ||
|
|
142
|
+
path.isAbsolute(workspaceRelative)) {
|
|
143
|
+
return undefined;
|
|
144
|
+
}
|
|
145
|
+
return { workspaceRelative, absolute };
|
|
146
|
+
}
|
|
147
|
+
/**
|
|
148
|
+
* Resolve a tool-call path against the event cwd and express it relative to
|
|
149
|
+
* the Kibi workspace root. Paths outside the workspace return undefined.
|
|
150
|
+
*/
|
|
151
|
+
export function toWorkspacePath(workspaceRoot, rawPath, eventCwd) {
|
|
152
|
+
const canonical = canonicalizeWorkspacePath(workspaceRoot, {
|
|
153
|
+
rawPath,
|
|
154
|
+
eventCwd,
|
|
155
|
+
});
|
|
156
|
+
return canonical
|
|
157
|
+
? { relative: canonical.workspaceRelative, absolute: canonical.absolute }
|
|
158
|
+
: undefined;
|
|
159
|
+
}
|
|
160
|
+
export function classifyPath(relativePath) {
|
|
161
|
+
const segments = pathSegments(relativePath);
|
|
162
|
+
if (segments[0] === ".kb")
|
|
163
|
+
return "kb";
|
|
164
|
+
if (segments.some((segment) => ignoredSegments.has(segment)))
|
|
165
|
+
return "other";
|
|
166
|
+
if (segments.some((segment) => documentationSegments.has(segment))) {
|
|
167
|
+
return "other";
|
|
168
|
+
}
|
|
169
|
+
const basename = segments.at(-1) ?? "";
|
|
170
|
+
const extension = path.extname(basename).toLowerCase();
|
|
171
|
+
if (!codeExtensions.has(extension))
|
|
172
|
+
return "other";
|
|
173
|
+
if (testBasename.test(basename) ||
|
|
174
|
+
segments.slice(0, -1).some((segment) => testSegments.has(segment))) {
|
|
175
|
+
return "test";
|
|
176
|
+
}
|
|
177
|
+
return "source";
|
|
178
|
+
}
|
|
179
|
+
export function isKbPath(candidate) {
|
|
180
|
+
return pathSegments(candidate).includes(".kb");
|
|
181
|
+
}
|
|
182
|
+
export function isRootKbPath(candidate) {
|
|
183
|
+
return pathSegments(candidate)[0] === ".kb";
|
|
184
|
+
}
|
|
185
|
+
function isCanonicalKbKnowledgePath(segments) {
|
|
186
|
+
const kbIndex = segments.indexOf(".kb");
|
|
187
|
+
if (kbIndex < 0)
|
|
188
|
+
return false;
|
|
189
|
+
const lane = segments[kbIndex + 1];
|
|
190
|
+
return (lane !== undefined &&
|
|
191
|
+
(canonicalKbKnowledgeFiles.has(lane) || canonicalKbKnowledgeLanes.has(lane)));
|
|
192
|
+
}
|
|
193
|
+
export function isMeaningfulTrackedPath(candidate) {
|
|
194
|
+
const segments = pathSegments(candidate);
|
|
195
|
+
if (segments.some((segment) => ignoredSegments.has(segment)))
|
|
196
|
+
return false;
|
|
197
|
+
if (segments.includes(".kb"))
|
|
198
|
+
return isCanonicalKbKnowledgePath(segments);
|
|
199
|
+
const basename = segments.at(-1) ?? "";
|
|
200
|
+
const extension = path.extname(basename).toLowerCase();
|
|
201
|
+
if (basename === "README.md")
|
|
202
|
+
return true;
|
|
203
|
+
if (segments.some((segment) => documentationSegments.has(segment))) {
|
|
204
|
+
return documentationExtensions.has(extension);
|
|
205
|
+
}
|
|
206
|
+
if (testBasename.test(basename) ||
|
|
207
|
+
segments.slice(0, -1).some((segment) => testSegments.has(segment))) {
|
|
208
|
+
return (codeExtensions.has(extension) || documentationExtensions.has(extension));
|
|
209
|
+
}
|
|
210
|
+
return codeExtensions.has(extension);
|
|
211
|
+
}
|
|
212
|
+
export function isSourceImpactRelevantPath(candidate) {
|
|
213
|
+
return classifyPath(normalizeWorkspacePath(candidate)) === "source";
|
|
214
|
+
}
|
|
215
|
+
export function isDocumentationTrackedPath(candidate) {
|
|
216
|
+
const segments = pathSegments(candidate);
|
|
217
|
+
const basename = segments.at(-1) ?? "";
|
|
218
|
+
return (basename === "README.md" ||
|
|
219
|
+
segments.some((segment) => documentationSegments.has(segment)) ||
|
|
220
|
+
documentationExtensions.has(path.extname(basename).toLowerCase()));
|
|
221
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,50 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "kibi-agent-core",
|
|
3
|
-
"version": "0.0.0
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
|
|
3
|
+
"version": "0.0.0",
|
|
4
|
+
"description": "Shared low-latency hook helpers for Kibi agent adapters",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./dist/index.js",
|
|
7
|
+
"types": "./dist/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"types": "./dist/index.d.ts",
|
|
11
|
+
"default": "./dist/index.js"
|
|
12
|
+
},
|
|
13
|
+
"./kb-mcp-tools": {
|
|
14
|
+
"types": "./dist/kb-mcp-tools.d.ts",
|
|
15
|
+
"default": "./dist/kb-mcp-tools.js"
|
|
16
|
+
},
|
|
17
|
+
"./knowledge-index": {
|
|
18
|
+
"types": "./dist/knowledge-index.d.ts",
|
|
19
|
+
"default": "./dist/knowledge-index.js"
|
|
20
|
+
},
|
|
21
|
+
"./path-policy": {
|
|
22
|
+
"types": "./dist/path-policy.d.ts",
|
|
23
|
+
"default": "./dist/path-policy.js"
|
|
24
|
+
}
|
|
25
|
+
},
|
|
26
|
+
"files": ["dist", "CHANGELOG.md"],
|
|
27
|
+
"scripts": {
|
|
28
|
+
"build": "tsc -p tsconfig.json",
|
|
29
|
+
"typecheck": "tsc -p tsconfig.json --noEmit",
|
|
30
|
+
"prepack": "npm run build && node ../../scripts/verify-package-contract.mjs kibi-agent-core"
|
|
31
|
+
},
|
|
32
|
+
"engines": {
|
|
33
|
+
"node": ">=18"
|
|
34
|
+
},
|
|
35
|
+
"license": "AGPL-3.0-or-later",
|
|
36
|
+
"author": "Piotr Franczyk",
|
|
37
|
+
"repository": {
|
|
38
|
+
"type": "git",
|
|
39
|
+
"url": "https://github.com/Looted/kibi.git"
|
|
40
|
+
},
|
|
41
|
+
"publishConfig": {
|
|
42
|
+
"access": "public"
|
|
43
|
+
},
|
|
44
|
+
"devDependencies": {
|
|
45
|
+
"@types/node": "^25.9.8",
|
|
46
|
+
"semver": "^7.8.5",
|
|
47
|
+
"typescript": "^5.7.0",
|
|
48
|
+
"yaml": "^2.9.0"
|
|
49
|
+
}
|
|
50
|
+
}
|
package/README.md
DELETED