kibi-cursor 2.0.1 → 2.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.cursor-plugin/plugin.json +2 -2
- package/CHANGELOG.md +78 -43
- package/README.md +3 -4
- package/agent-plugin/plugin.json +1 -1
- package/agent-plugin/skills/kibi-usage/SKILL.md +7 -3
- package/agent-plugin/skills/kibi-usage/resources/fact-lanes.md +4 -0
- package/agent-plugin/skills/kibi-usage/resources/operation-access.md +1 -0
- package/agent-plugin/skills/kibi-usage/resources/relationship-directions.md +35 -17
- package/agent-plugin/skills/kibi-usage/resources/source-authoring.md +36 -0
- package/agent-plugin/skills/kibi-usage/resources/workflows.md +52 -1
- package/bin/launch-kibi-mcp.mjs +8 -1
- package/dist/.cursor-plugin/plugin.json +2 -2
- package/dist/bin/launch-kibi-mcp.mjs +8 -1
- package/dist/guidance.d.ts +11 -0
- package/dist/guidance.js +39 -5
- package/dist/hook-runner.js +23 -2
- package/dist/hook-state-storage.js +3 -0
- package/dist/hook-state.d.ts +4 -2
- package/dist/hook-state.js +16 -3
- package/dist/kb-mcp-tools.d.ts +5 -7
- package/dist/kb-mcp-tools.js +6 -146
- package/dist/path-policy.js +17 -188
- package/dist/skills/.canon-hash.json +6 -6
- package/dist/skills/kibi-usage/SKILL.md +7 -3
- package/dist/skills/kibi-usage/resources/fact-lanes.md +4 -0
- package/dist/skills/kibi-usage/resources/operation-access.md +1 -0
- package/dist/skills/kibi-usage/resources/relationship-directions.md +35 -17
- package/dist/skills/kibi-usage/resources/source-authoring.md +36 -0
- package/dist/skills/kibi-usage/resources/workflows.md +52 -1
- package/dist/source-linked-requirements.d.ts +16 -0
- package/dist/source-linked-requirements.js +55 -0
- package/package.json +4 -1
- package/skills/.canon-hash.json +6 -6
- package/skills/kibi-usage/SKILL.md +7 -3
- package/skills/kibi-usage/resources/fact-lanes.md +4 -0
- package/skills/kibi-usage/resources/operation-access.md +1 -0
- package/skills/kibi-usage/resources/relationship-directions.md +35 -17
- package/skills/kibi-usage/resources/source-authoring.md +36 -0
- package/skills/kibi-usage/resources/workflows.md +52 -1
|
@@ -29,3 +29,39 @@ Cancellation follows the same rule: wait for the terminal journal state. A
|
|
|
29
29
|
pre-commit rollback is retryable, a committed source with a failed derivative
|
|
30
30
|
is repairable, and an indeterminate outcome is non-retryable until its recovery
|
|
31
31
|
action resolves the journal.
|
|
32
|
+
|
|
33
|
+
## Naming entities
|
|
34
|
+
|
|
35
|
+
Name every entity by what it governs, the way you would name a source file.
|
|
36
|
+
Never choose "the next number": parallel branches pick the same next number and
|
|
37
|
+
collide even when they touch unrelated behavior.
|
|
38
|
+
|
|
39
|
+
- Use `<TYPE>-<area>-<behavior>` in kebab-case, with the type prefix kept
|
|
40
|
+
uppercase: `REQ-cli-gc`, `SCEN-mcp-search-discovery`,
|
|
41
|
+
`TEST-mcp-search-discovery`, `ADR-capability-plugins-v1`,
|
|
42
|
+
`EVT-exact-branch-store-ensure`, and a flag named after its gate such as
|
|
43
|
+
`FLAG-scip-symbol-extraction`.
|
|
44
|
+
- The filename stem equals the frontmatter `id`
|
|
45
|
+
(`.kb/requirements/REQ-cli-gc.md` has `id: REQ-cli-gc`). Always set `id`
|
|
46
|
+
explicitly; a missing `id` falls back to a path-and-title hash that changes
|
|
47
|
+
when the file is renamed.
|
|
48
|
+
- Before creating an entity, `kb_search` the area. If an existing entity already
|
|
49
|
+
covers the behavior, update it, or create a replacement and link it with
|
|
50
|
+
`supersedes`. A `-v2` suffix is acceptable only for that direct superseding
|
|
51
|
+
replacement.
|
|
52
|
+
- A name collision with another branch usually means the same work was done
|
|
53
|
+
twice. Reconcile the two entities; do not rename one of them to dodge the
|
|
54
|
+
collision.
|
|
55
|
+
- The area prefix is for humans. Whether two requirements are about the same
|
|
56
|
+
thing is decided by their facts: a shared `subject_key` and identical
|
|
57
|
+
predicate or property signatures, which `domain-redundancy` and
|
|
58
|
+
`domain-contradictions` compare deterministically. Reuse existing subject keys
|
|
59
|
+
(dotted `component.aspect[.sub]`, lowercase snake segments such as
|
|
60
|
+
`kibi.cli.check.staged`) instead of deriving a subject from the requirement
|
|
61
|
+
ID.
|
|
62
|
+
|
|
63
|
+
`entity-id-style` reports new purely numeric IDs (`REQ-123`) and IDs whose
|
|
64
|
+
filename stem differs from `id` as non-blocking quality diagnostics. Legacy
|
|
65
|
+
numbered entities created before the naming standard are grandfathered and are
|
|
66
|
+
not renamed, because code comments such as `// implements REQ-003` still
|
|
67
|
+
reference them.
|
|
@@ -33,11 +33,62 @@ surfaces are a release defect: require a new package version and label any
|
|
|
33
33
|
project override temporary. This skill never selects a package manager or
|
|
34
34
|
edits dependency configuration.
|
|
35
35
|
|
|
36
|
+
## Choosing a discovery mode
|
|
37
|
+
|
|
38
|
+
`kb_search` is not one behavior. It selects deterministic intent ranking when
|
|
39
|
+
you pass `rankingMode: "intent-v1"`, `semanticFacets`, or `sourceLocations`,
|
|
40
|
+
and legacy lexical ranking otherwise. Naming the tool is not enough; the inputs
|
|
41
|
+
decide the path.
|
|
42
|
+
|
|
43
|
+
- **Exact identity** — you already know the ID. Use `kb_query`, not search.
|
|
44
|
+
- **Literal token** — a symbol, file, or error string that appears verbatim.
|
|
45
|
+
Use lexical `kb_search` and keep the query close to the literal text.
|
|
46
|
+
- **Conceptual or behavioral** — "how does checkout handle a declined card".
|
|
47
|
+
Use intent mode and ground it in what you actually know:
|
|
48
|
+
|
|
49
|
+
```json
|
|
50
|
+
{
|
|
51
|
+
"query": "declined card during checkout",
|
|
52
|
+
"rankingMode": "intent-v1",
|
|
53
|
+
"semanticFacets": {
|
|
54
|
+
"actors": ["shopper"],
|
|
55
|
+
"actions": ["pay"],
|
|
56
|
+
"objects": ["card"],
|
|
57
|
+
"constraints": ["declined"]
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
- **Before changing code** — pass the file you are about to edit as a source
|
|
63
|
+
location, so results are matched against source-linked symbols and return
|
|
64
|
+
source evidence:
|
|
65
|
+
|
|
66
|
+
```json
|
|
67
|
+
{
|
|
68
|
+
"query": "checkout total rounding",
|
|
69
|
+
"rankingMode": "intent-v1",
|
|
70
|
+
"sourceLocations": [{ "path": "src/checkout.ts", "symbol": "computeTotal" }]
|
|
71
|
+
}
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
Supply facets only from the request, the code, or prior results. Inventing
|
|
75
|
+
facets fabricates evidence and biases ranking. Intent mode abstains below
|
|
76
|
+
`minScore` instead of returning weak matches; an abstention is an explicit
|
|
77
|
+
no-answer, so widen the query or drop to lexical search rather than treating it
|
|
78
|
+
as "nothing exists". There is no target percentage of intent-mode calls — a
|
|
79
|
+
literal identifier lookup is still best served lexically.
|
|
80
|
+
|
|
81
|
+
Results are summaries by default: identifying metadata, score, reasons, and a
|
|
82
|
+
snippet. Read them to pick candidates, then `kb_query` those IDs for full
|
|
83
|
+
bodies, or pass `fields: "full"` when you genuinely need every body. Extract
|
|
84
|
+
the relevant existing constraints and decisions from the response before
|
|
85
|
+
planning a behavior change; a search receipt is not comprehension.
|
|
86
|
+
|
|
36
87
|
## Discovery to Validation Sequence
|
|
37
88
|
|
|
38
89
|
The canonical workflow for any KB operation follows this pattern:
|
|
39
90
|
|
|
40
|
-
1. **Discover**: `kb_search` with focused probes
|
|
91
|
+
1. **Discover**: `kb_search` with focused probes; pick the mode above
|
|
41
92
|
2. **Confirm**: `kb_query` for exact IDs and state
|
|
42
93
|
3. **Inspect**: `kb_status` when freshness matters
|
|
43
94
|
4. **Decompose**: `kb_semantic_advisor` on the complete normative prose; verify or supply every atomic clause
|
package/bin/launch-kibi-mcp.mjs
CHANGED
|
@@ -362,7 +362,14 @@ export function launchKibiMcp(
|
|
|
362
362
|
[projectLocal.binPath, ...childArgs],
|
|
363
363
|
{
|
|
364
364
|
cwd: workspaceRoot,
|
|
365
|
-
env: {
|
|
365
|
+
env: {
|
|
366
|
+
...env,
|
|
367
|
+
KIBI_WORKSPACE: workspaceRoot,
|
|
368
|
+
// Identify the host on usage rows. This never enables telemetry;
|
|
369
|
+
// KIBI_DIAGNOSTIC_MODE stays the operator's explicit opt-in and is
|
|
370
|
+
// passed through untouched.
|
|
371
|
+
KIBI_MCP_HOST: "cursor",
|
|
372
|
+
},
|
|
366
373
|
stdio: ["inherit", "inherit", "inherit"],
|
|
367
374
|
},
|
|
368
375
|
);
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "kibi-cursor",
|
|
3
3
|
"displayName": "Kibi",
|
|
4
|
-
"version": "2.
|
|
5
|
-
"description": "Optional Cursor adapter for Kibi. Requires
|
|
4
|
+
"version": "2.1.1",
|
|
5
|
+
"description": "Optional Cursor adapter for Kibi. Requires project-local kibi-cli, kibi-mcp, and kibi-core (SWI-Prolog is bundled on Linux and macOS) before the bundled MCP server can start.",
|
|
6
6
|
"license": "AGPL-3.0-or-later",
|
|
7
7
|
"author": {
|
|
8
8
|
"name": "Kibi"
|
|
@@ -362,7 +362,14 @@ export function launchKibiMcp(
|
|
|
362
362
|
[projectLocal.binPath, ...childArgs],
|
|
363
363
|
{
|
|
364
364
|
cwd: workspaceRoot,
|
|
365
|
-
env: {
|
|
365
|
+
env: {
|
|
366
|
+
...env,
|
|
367
|
+
KIBI_WORKSPACE: workspaceRoot,
|
|
368
|
+
// Identify the host on usage rows. This never enables telemetry;
|
|
369
|
+
// KIBI_DIAGNOSTIC_MODE stays the operator's explicit opt-in and is
|
|
370
|
+
// passed through untouched.
|
|
371
|
+
KIBI_MCP_HOST: "cursor",
|
|
372
|
+
},
|
|
366
373
|
stdio: ["inherit", "inherit", "inherit"],
|
|
367
374
|
},
|
|
368
375
|
);
|
package/dist/guidance.d.ts
CHANGED
|
@@ -5,6 +5,17 @@ export type GuidanceContext = {
|
|
|
5
5
|
readonly hasKibi: boolean;
|
|
6
6
|
readonly mcpState: McpState;
|
|
7
7
|
readonly workspaceTrusted: boolean;
|
|
8
|
+
/** Requirements the edited file already owns, when the manifest knows them. */
|
|
9
|
+
readonly linkedRequirementIds?: readonly string[];
|
|
8
10
|
};
|
|
9
11
|
export declare function readGuidance(filePath: string, context: GuidanceContext): string | undefined;
|
|
12
|
+
/**
|
|
13
|
+
* Guidance emitted before an edit is written.
|
|
14
|
+
*
|
|
15
|
+
* The post-edit and stop messages can only ask for repair once the code is
|
|
16
|
+
* already changed. This is the one point where retrieval can still inform the
|
|
17
|
+
* change, so it names the requirements the file already implements and asks
|
|
18
|
+
* for them to be read before the edit rather than reconciled after it.
|
|
19
|
+
*/
|
|
20
|
+
export declare function preEditGuidance(filePath: string, context: GuidanceContext): string | undefined;
|
|
10
21
|
export declare function writeGuidance(filePath: string, context: GuidanceContext): string | undefined;
|
package/dist/guidance.js
CHANGED
|
@@ -18,6 +18,40 @@ export function readGuidance(filePath, context) {
|
|
|
18
18
|
const advisory = interfaceAdvisory(context.mcpState, context.workspaceTrusted);
|
|
19
19
|
return advisory ? [advisory, ...guidance].join("\n") : guidance.join("\n");
|
|
20
20
|
}
|
|
21
|
+
/**
|
|
22
|
+
* Guidance emitted before an edit is written.
|
|
23
|
+
*
|
|
24
|
+
* The post-edit and stop messages can only ask for repair once the code is
|
|
25
|
+
* already changed. This is the one point where retrieval can still inform the
|
|
26
|
+
* change, so it names the requirements the file already implements and asks
|
|
27
|
+
* for them to be read before the edit rather than reconciled after it.
|
|
28
|
+
*/
|
|
29
|
+
export function preEditGuidance(filePath, context) {
|
|
30
|
+
if (!context.hasKibi) {
|
|
31
|
+
return undefined;
|
|
32
|
+
}
|
|
33
|
+
const relativePath = toRepoRelativePath(filePath, context.cwd);
|
|
34
|
+
if (!isMeaningfulTrackedPath(relativePath)) {
|
|
35
|
+
return undefined;
|
|
36
|
+
}
|
|
37
|
+
if (isDocumentationTrackedPath(relativePath)) {
|
|
38
|
+
return undefined;
|
|
39
|
+
}
|
|
40
|
+
const linkedRequirements = context.linkedRequirementIds ?? [];
|
|
41
|
+
const guidance = linkedRequirements.length > 0
|
|
42
|
+
? [
|
|
43
|
+
`Kibi pre-edit guidance: ${relativePath} already implements ${linkedRequirements.join(", ")}.`,
|
|
44
|
+
`Read those requirements with kb_query before changing behavior here, and widen with kb_query({sourceFile:"${relativePath}"}) for scenarios and tests.`,
|
|
45
|
+
"If the change contradicts an existing requirement, resolve that in Kibi before writing the edit.",
|
|
46
|
+
]
|
|
47
|
+
: [
|
|
48
|
+
`Kibi pre-edit guidance: ${relativePath} has no linked requirement in the symbol manifest.`,
|
|
49
|
+
`Discover existing constraints with kb_search, then kb_query({sourceFile:"${relativePath}"}), before changing behavior here.`,
|
|
50
|
+
"Record the requirement this change serves rather than leaving the symbol unowned.",
|
|
51
|
+
];
|
|
52
|
+
const advisory = interfaceAdvisory(context.mcpState, context.workspaceTrusted);
|
|
53
|
+
return advisory ? [advisory, ...guidance].join("\n") : guidance.join("\n");
|
|
54
|
+
}
|
|
21
55
|
export function writeGuidance(filePath, context) {
|
|
22
56
|
if (!context.hasKibi) {
|
|
23
57
|
return undefined;
|
|
@@ -35,12 +69,12 @@ export function writeGuidance(filePath, context) {
|
|
|
35
69
|
const advisory = interfaceAdvisory(context.mcpState, context.workspaceTrusted);
|
|
36
70
|
return advisory ? [advisory, ...guidance].join("\n") : guidance.join("\n");
|
|
37
71
|
}
|
|
72
|
+
// Retrieval is asked for before the edit; this message stays scoped to the
|
|
73
|
+
// impact review that only becomes possible once the change exists.
|
|
38
74
|
const guidance = [
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
"Prefer symbol manifest + executable_for or // implements REQ-xxx for traceability.",
|
|
43
|
-
"Do not read or edit `.kb/` files directly. Query before mutate; run kb_upsert sequentially and kb_check before completion.",
|
|
75
|
+
`Kibi impact review: run kb_check({sourceFiles:["${relativePath}"], includeImpactDiagnostics:true, includeWorkingTreeDiff:true}) while this edit is fresh.`,
|
|
76
|
+
"Check symbol granularity and whether linked requirements, scenarios, and tests still cover the changed behavior.",
|
|
77
|
+
"Prefer symbol manifest + executable_for or // implements REQ-<area>-<behavior> for traceability.",
|
|
44
78
|
];
|
|
45
79
|
const advisory = interfaceAdvisory(context.mcpState, context.workspaceTrusted);
|
|
46
80
|
return advisory ? [advisory, ...guidance].join("\n") : guidance.join("\n");
|
package/dist/hook-runner.js
CHANGED
|
@@ -3,12 +3,13 @@
|
|
|
3
3
|
import fs from "node:fs";
|
|
4
4
|
import path from "node:path";
|
|
5
5
|
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
6
|
-
import { readGuidance, writeGuidance } from "./guidance.js";
|
|
6
|
+
import { preEditGuidance, readGuidance, writeGuidance } from "./guidance.js";
|
|
7
7
|
import { parseHookInput, parseStdinJson, readStdin } from "./hook-input.js";
|
|
8
8
|
import { addDirtyPaths, clearSessionHookState, hasGuidedPath, loadHookState, recordKbMcpTool, recordPlanDelivered, rememberGuidedPath, resolveStateDir, } from "./hook-state.js";
|
|
9
9
|
import { extractKbMcpToolCall } from "./kb-mcp-tools.js";
|
|
10
10
|
import { BOOTSTRAP_REMINDER, DIRECT_KB_EDIT_WARNING, interfaceAdvisory, stopFollowupMessage, } from "./messages.js";
|
|
11
11
|
import { extractExplicitPathFields, isDirectKbPath, isKbFreshnessRelevantPath, isMeaningfulTrackedPath, toRepoRelativePath, } from "./path-policy.js";
|
|
12
|
+
import { getSourceLinkedRequirementIds } from "./source-linked-requirements.js";
|
|
12
13
|
const editableTools = new Set([
|
|
13
14
|
"Edit",
|
|
14
15
|
"MultiEdit",
|
|
@@ -86,7 +87,27 @@ export async function runHook(rawInput, environment = {}) {
|
|
|
86
87
|
agent_message: DIRECT_KB_EDIT_WARNING,
|
|
87
88
|
};
|
|
88
89
|
}
|
|
89
|
-
|
|
90
|
+
const primaryPath = resolvePrimaryPath(input);
|
|
91
|
+
if (!primaryPath || !kibiReady || !isKnownEditableTool(input.toolName)) {
|
|
92
|
+
return emptyResult();
|
|
93
|
+
}
|
|
94
|
+
const relativePath = toRepoRelativePath(primaryPath, cwd);
|
|
95
|
+
const state = loadHookState(stateDir);
|
|
96
|
+
if (hasGuidedPath(state, "pre-edit", relativePath)) {
|
|
97
|
+
return emptyResult();
|
|
98
|
+
}
|
|
99
|
+
const guidance = preEditGuidance(primaryPath, {
|
|
100
|
+
cwd,
|
|
101
|
+
hasKibi: kibiReady,
|
|
102
|
+
mcpState: state.mcpState,
|
|
103
|
+
workspaceTrusted,
|
|
104
|
+
linkedRequirementIds: getSourceLinkedRequirementIds(cwd, primaryPath, stateDir),
|
|
105
|
+
});
|
|
106
|
+
if (!guidance) {
|
|
107
|
+
return emptyResult();
|
|
108
|
+
}
|
|
109
|
+
rememberGuidedPath(stateDir, "pre-edit", relativePath);
|
|
110
|
+
return { permission: "allow", agent_message: guidance };
|
|
90
111
|
}
|
|
91
112
|
case "beforeReadFile": {
|
|
92
113
|
const primaryPath = resolvePrimaryPath(input);
|
|
@@ -51,6 +51,7 @@ export function emptyHookState() {
|
|
|
51
51
|
mcpState: "unknown",
|
|
52
52
|
dirtyPaths: [],
|
|
53
53
|
guidedReadPaths: [],
|
|
54
|
+
guidedPreEditPaths: [],
|
|
54
55
|
guidedWritePaths: [],
|
|
55
56
|
kbMutationTools: [],
|
|
56
57
|
kbCheckRun: false,
|
|
@@ -164,6 +165,7 @@ function coerceHookState(value) {
|
|
|
164
165
|
: [];
|
|
165
166
|
const dirtyPaths = strings(value.dirtyPaths, normalizePath);
|
|
166
167
|
const guidedReadPaths = strings(value.guidedReadPaths, normalizePath);
|
|
168
|
+
const guidedPreEditPaths = strings(value.guidedPreEditPaths, normalizePath);
|
|
167
169
|
const guidedWritePaths = strings(value.guidedWritePaths, normalizePath);
|
|
168
170
|
const kbMutationTools = strings(value.kbMutationTools, (entry) => entry.trim());
|
|
169
171
|
const impactCheckedPaths = strings(value.impactCheckedPaths, normalizePath);
|
|
@@ -171,6 +173,7 @@ function coerceHookState(value) {
|
|
|
171
173
|
mcpState: value.mcpState === "observed" ? "observed" : "unknown",
|
|
172
174
|
dirtyPaths: [...new Set(dirtyPaths)].slice(-maxDirtyPaths),
|
|
173
175
|
guidedReadPaths: [...new Set(guidedReadPaths)].slice(-maxGuidedPaths),
|
|
176
|
+
guidedPreEditPaths: [...new Set(guidedPreEditPaths)].slice(-maxGuidedPaths),
|
|
174
177
|
guidedWritePaths: [...new Set(guidedWritePaths)].slice(-maxGuidedPaths),
|
|
175
178
|
kbMutationTools: [...new Set(kbMutationTools)].slice(-maxKbMutationTools),
|
|
176
179
|
kbCheckRun: value.kbCheckRun === true,
|
package/dist/hook-state.d.ts
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
import type { McpState } from "./kb-mcp-tools.js";
|
|
2
2
|
export { loadHookState, resolveStateDir, saveHookState, updateHookState, } from "./hook-state-storage.js";
|
|
3
|
+
export type GuidedPathKind = "read" | "pre-edit" | "write";
|
|
3
4
|
export type HookState = {
|
|
4
5
|
mcpState: McpState;
|
|
5
6
|
dirtyPaths: string[];
|
|
6
7
|
guidedReadPaths: string[];
|
|
8
|
+
guidedPreEditPaths: string[];
|
|
7
9
|
guidedWritePaths: string[];
|
|
8
10
|
kbMutationTools: string[];
|
|
9
11
|
kbCheckRun: boolean;
|
|
@@ -12,8 +14,8 @@ export type HookState = {
|
|
|
12
14
|
planDelivered: boolean;
|
|
13
15
|
};
|
|
14
16
|
export declare function addDirtyPaths(stateDir: string | undefined, dirtyPaths: readonly string[]): HookState;
|
|
15
|
-
export declare function rememberGuidedPath(stateDir: string | undefined, kind:
|
|
16
|
-
export declare function hasGuidedPath(state: HookState, kind:
|
|
17
|
+
export declare function rememberGuidedPath(stateDir: string | undefined, kind: GuidedPathKind, guidedPath: string): HookState;
|
|
18
|
+
export declare function hasGuidedPath(state: HookState, kind: GuidedPathKind, guidedPath: string): boolean;
|
|
17
19
|
export declare function recordPlanDelivered(stateDir: string | undefined): HookState;
|
|
18
20
|
export declare function recordKbMcpTool(stateDir: string | undefined, toolName: string, options?: {
|
|
19
21
|
impactCheckRun?: boolean;
|
package/dist/hook-state.js
CHANGED
|
@@ -15,6 +15,14 @@ export function rememberGuidedPath(stateDir, kind, guidedPath) {
|
|
|
15
15
|
guidedReadPaths: mergeStringPaths(state.guidedReadPaths, [normalized]),
|
|
16
16
|
};
|
|
17
17
|
}
|
|
18
|
+
if (kind === "pre-edit") {
|
|
19
|
+
return {
|
|
20
|
+
...state,
|
|
21
|
+
guidedPreEditPaths: mergeStringPaths(state.guidedPreEditPaths, [
|
|
22
|
+
normalized,
|
|
23
|
+
]),
|
|
24
|
+
};
|
|
25
|
+
}
|
|
18
26
|
return {
|
|
19
27
|
...state,
|
|
20
28
|
guidedWritePaths: mergeStringPaths(state.guidedWritePaths, [normalized]),
|
|
@@ -22,9 +30,14 @@ export function rememberGuidedPath(stateDir, kind, guidedPath) {
|
|
|
22
30
|
});
|
|
23
31
|
}
|
|
24
32
|
export function hasGuidedPath(state, kind, guidedPath) {
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
33
|
+
return guidedBucket(state, kind).includes(normalizePath(guidedPath));
|
|
34
|
+
}
|
|
35
|
+
function guidedBucket(state, kind) {
|
|
36
|
+
if (kind === "read")
|
|
37
|
+
return state.guidedReadPaths;
|
|
38
|
+
if (kind === "pre-edit")
|
|
39
|
+
return state.guidedPreEditPaths;
|
|
40
|
+
return state.guidedWritePaths;
|
|
28
41
|
}
|
|
29
42
|
export function recordPlanDelivered(stateDir) {
|
|
30
43
|
return updateHookState(stateDir, (state) => state.planDelivered ? state : { ...state, planDelivered: true });
|
package/dist/kb-mcp-tools.d.ts
CHANGED
|
@@ -1,10 +1,8 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
export type McpState = "observed" | "unknown";
|
|
7
|
-
export type KibiInterface = "mcp" | "cli" | "setup";
|
|
1
|
+
import type { KbMcpToolCall as SharedKbMcpToolCall, KibiInterface as SharedKibiInterface, McpState as SharedMcpState } from "kibi-agent-core/kb-mcp-tools";
|
|
2
|
+
export type KbMcpToolCall = SharedKbMcpToolCall;
|
|
3
|
+
export type McpState = SharedMcpState;
|
|
4
|
+
export type KibiInterface = SharedKibiInterface;
|
|
5
|
+
export declare function canonicalKbToolName(toolName: string | undefined): string | undefined;
|
|
8
6
|
export declare function resolveKibiInterface(mcpState: McpState, workspaceTrusted: boolean): KibiInterface;
|
|
9
7
|
export declare function extractKbMcpToolCall(toolName: string | undefined, toolInput: unknown): KbMcpToolCall | undefined;
|
|
10
8
|
export declare function extractKbMcpToolName(toolName: string | undefined, toolInput: unknown): string | undefined;
|
package/dist/kb-mcp-tools.js
CHANGED
|
@@ -1,154 +1,14 @@
|
|
|
1
1
|
// implements REQ-cursor-kibi-plugin-v1
|
|
2
|
-
|
|
3
|
-
|
|
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;
|
|
2
|
+
import { canonicalKbToolName as canonicalSharedKbToolName, extractKbMcpToolCall as extractSharedKbMcpToolCall, extractKbMcpToolName as extractSharedKbMcpToolName, resolveKibiInterface as resolveSharedKibiInterface, } from "kibi-agent-core/kb-mcp-tools";
|
|
3
|
+
export function canonicalKbToolName(toolName) {
|
|
4
|
+
return canonicalSharedKbToolName(toolName);
|
|
41
5
|
}
|
|
42
6
|
export function resolveKibiInterface(mcpState, workspaceTrusted) {
|
|
43
|
-
|
|
44
|
-
return "mcp";
|
|
45
|
-
}
|
|
46
|
-
return workspaceTrusted ? "cli" : "setup";
|
|
7
|
+
return resolveSharedKibiInterface(mcpState, workspaceTrusted);
|
|
47
8
|
}
|
|
48
9
|
export function extractKbMcpToolCall(toolName, toolInput) {
|
|
49
|
-
|
|
50
|
-
let normalizedToolName = directToolName?.startsWith("kb_")
|
|
51
|
-
? directToolName
|
|
52
|
-
: undefined;
|
|
53
|
-
if (isRecord(toolInput)) {
|
|
54
|
-
normalizedToolName ??= readString(toolInput, [
|
|
55
|
-
"toolName",
|
|
56
|
-
"tool_name",
|
|
57
|
-
"name",
|
|
58
|
-
]);
|
|
59
|
-
const args = readRecord(toolInput, ["arguments", "args"]);
|
|
60
|
-
const payload = args ?? toolInput;
|
|
61
|
-
const includeImpactDiagnostics = readBoolean(payload, [
|
|
62
|
-
"includeImpactDiagnostics",
|
|
63
|
-
"include_impact_diagnostics",
|
|
64
|
-
]);
|
|
65
|
-
const includeWorkingTreeDiff = readBoolean(payload, [
|
|
66
|
-
"includeWorkingTreeDiff",
|
|
67
|
-
"include_working_tree_diff",
|
|
68
|
-
]);
|
|
69
|
-
const sourceFiles = readStringArray(payload, [
|
|
70
|
-
"sourceFiles",
|
|
71
|
-
"source_files",
|
|
72
|
-
]);
|
|
73
|
-
if (normalizedToolName?.startsWith("kb_")) {
|
|
74
|
-
return {
|
|
75
|
-
toolName: normalizedToolName,
|
|
76
|
-
impactCheckRun: normalizedToolName === "kb_check" &&
|
|
77
|
-
includeImpactDiagnostics === true &&
|
|
78
|
-
includeWorkingTreeDiff === true &&
|
|
79
|
-
sourceFiles.length > 0,
|
|
80
|
-
sourceFiles,
|
|
81
|
-
};
|
|
82
|
-
}
|
|
83
|
-
const nestedArgs = toolInput.arguments ?? toolInput.args;
|
|
84
|
-
if (isRecord(nestedArgs)) {
|
|
85
|
-
const nestedTool = readString(nestedArgs, [
|
|
86
|
-
"toolName",
|
|
87
|
-
"tool_name",
|
|
88
|
-
"name",
|
|
89
|
-
]);
|
|
90
|
-
if (nestedTool?.startsWith("kb_")) {
|
|
91
|
-
return { toolName: nestedTool, impactCheckRun: false, sourceFiles: [] };
|
|
92
|
-
}
|
|
93
|
-
}
|
|
94
|
-
}
|
|
95
|
-
if (normalizedToolName?.startsWith("kb_")) {
|
|
96
|
-
return {
|
|
97
|
-
toolName: normalizedToolName,
|
|
98
|
-
impactCheckRun: false,
|
|
99
|
-
sourceFiles: [],
|
|
100
|
-
};
|
|
101
|
-
}
|
|
102
|
-
return undefined;
|
|
10
|
+
return extractSharedKbMcpToolCall(toolName, toolInput);
|
|
103
11
|
}
|
|
104
12
|
export function extractKbMcpToolName(toolName, toolInput) {
|
|
105
|
-
|
|
106
|
-
// rationale: the fallbacks below re-derive the same name from the same
|
|
107
|
-
// inputs, so dropping the toolCall shortcut is observationally equivalent.
|
|
108
|
-
// Stryker disable next-line ConditionalExpression, BlockStatement
|
|
109
|
-
if (toolCall) {
|
|
110
|
-
return toolCall.toolName;
|
|
111
|
-
}
|
|
112
|
-
// rationale: any tool name that trims to a kb_ prefix already produced a
|
|
113
|
-
// tool call above, so this branch only sees equivalent names.
|
|
114
|
-
// Stryker disable next-line MethodExpression
|
|
115
|
-
if (toolName) {
|
|
116
|
-
// rationale: any name that trims to a kb_ prefix already produced a tool
|
|
117
|
-
// call above, so this trim cannot change the returned name.
|
|
118
|
-
// Stryker disable MethodExpression
|
|
119
|
-
const normalized = toolName.trim();
|
|
120
|
-
if (normalized.startsWith("kb_")) {
|
|
121
|
-
return normalized;
|
|
122
|
-
}
|
|
123
|
-
// Stryker restore
|
|
124
|
-
}
|
|
125
|
-
if (!isRecord(toolInput)) {
|
|
126
|
-
return undefined;
|
|
127
|
-
}
|
|
128
|
-
// rationale: this alias list duplicates the one inside
|
|
129
|
-
// extractKbMcpToolCall, which already returned for every kb_ match.
|
|
130
|
-
// Stryker disable next-line StringLiteral
|
|
131
|
-
const directTool = readString(toolInput, ["toolName", "tool_name", "name"]);
|
|
132
|
-
if (directTool?.startsWith("kb_")) {
|
|
133
|
-
return directTool;
|
|
134
|
-
}
|
|
135
|
-
const nestedArgs = toolInput.arguments ?? toolInput.args;
|
|
136
|
-
if (isRecord(nestedArgs)) {
|
|
137
|
-
// rationale: this alias list duplicates the one inside
|
|
138
|
-
// extractKbMcpToolCall, which already returned for every kb_ match.
|
|
139
|
-
// Stryker disable next-line StringLiteral
|
|
140
|
-
// rationale: duplicate alias list; extractKbMcpToolCall already
|
|
141
|
-
// returned for every kb_ match.
|
|
142
|
-
// Stryker disable StringLiteral
|
|
143
|
-
const nestedTool = readString(nestedArgs, [
|
|
144
|
-
"toolName",
|
|
145
|
-
"tool_name",
|
|
146
|
-
"name",
|
|
147
|
-
]);
|
|
148
|
-
// Stryker restore
|
|
149
|
-
if (nestedTool?.startsWith("kb_")) {
|
|
150
|
-
return nestedTool;
|
|
151
|
-
}
|
|
152
|
-
}
|
|
153
|
-
return undefined;
|
|
13
|
+
return extractSharedKbMcpToolName(toolName, toolInput);
|
|
154
14
|
}
|