kibi-cursor 1.0.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/.cursor-plugin/plugin.json +1 -1
- package/CHANGELOG.md +50 -0
- package/README.md +25 -4
- package/agent-plugin/plugin.json +1 -1
- package/agent-plugin/skills/kibi-freshness/SKILL.md +1 -1
- package/agent-plugin/skills/kibi-usage/SKILL.md +41 -12
- package/agent-plugin/skills/kibi-usage/resources/operation-access.md +1 -1
- package/agent-plugin/skills/kibi-usage/resources/proof.md +105 -0
- package/agent-plugin/skills/kibi-usage/resources/workflows.md +6 -6
- package/bin/launch-kibi-mcp.d.mts +38 -0
- package/bin/launch-kibi-mcp.mjs +66 -33
- package/dist/.cursor-plugin/plugin.json +1 -1
- package/dist/bin/launch-kibi-mcp.d.mts +38 -0
- package/dist/bin/launch-kibi-mcp.mjs +66 -33
- package/dist/hook-runner.d.ts +4 -0
- package/dist/hook-runner.js +19 -7
- package/dist/mcp.json +4 -1
- package/dist/path-policy.d.ts +1 -0
- package/dist/path-policy.js +12 -10
- package/dist/rules/kibi-workflow.mdc +16 -1
- package/dist/skills/.canon-hash.json +5 -4
- package/dist/skills/kibi-freshness/SKILL.md +1 -1
- package/dist/skills/kibi-usage/SKILL.md +41 -12
- package/dist/skills/kibi-usage/resources/operation-access.md +1 -1
- package/dist/skills/kibi-usage/resources/proof.md +105 -0
- package/dist/skills/kibi-usage/resources/workflows.md +6 -6
- package/mcp.json +4 -1
- package/package.json +1 -1
- package/rules/kibi-workflow.mdc +16 -1
- package/skills/.canon-hash.json +5 -4
- package/skills/kibi-freshness/SKILL.md +1 -1
- package/skills/kibi-usage/SKILL.md +41 -12
- package/skills/kibi-usage/resources/operation-access.md +1 -1
- package/skills/kibi-usage/resources/proof.md +105 -0
- package/skills/kibi-usage/resources/workflows.md +6 -6
package/bin/launch-kibi-mcp.mjs
CHANGED
|
@@ -7,14 +7,17 @@
|
|
|
7
7
|
/**
|
|
8
8
|
* Launch the kibi-mcp package installed by the consumer project.
|
|
9
9
|
*
|
|
10
|
-
* Cursor
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
10
|
+
* Cursor resolves this command from the plugin-root manifest, but the process
|
|
11
|
+
* cwd is host-controlled and is not a reliable consumer workspace (it may be
|
|
12
|
+
* the plugin directory or another host directory). This adapter deliberately
|
|
13
|
+
* knows nothing about the Kibi source tree: it resolves the unrelated consumer
|
|
14
|
+
* workspace from Cursor's workspace-folder data or explicit environment,
|
|
15
|
+
* finds that workspace's kibi-mcp package, and runs its declared bin with the
|
|
16
|
+
* consumer as both cwd and KIBI_WORKSPACE.
|
|
14
17
|
*/
|
|
15
18
|
|
|
16
19
|
import { spawn } from "node:child_process";
|
|
17
|
-
import
|
|
20
|
+
import * as fs from "node:fs";
|
|
18
21
|
import { createRequire } from "node:module";
|
|
19
22
|
import { dirname, isAbsolute, join, relative, resolve } from "node:path";
|
|
20
23
|
import { fileURLToPath } from "node:url";
|
|
@@ -29,12 +32,12 @@ const SIGNAL_EXIT_CODES = {
|
|
|
29
32
|
};
|
|
30
33
|
|
|
31
34
|
function readJson(path) {
|
|
32
|
-
return JSON.parse(readFileSync(path, "utf8"));
|
|
35
|
+
return JSON.parse(fs.readFileSync(path, "utf8"));
|
|
33
36
|
}
|
|
34
37
|
|
|
35
38
|
function isDirectory(path) {
|
|
36
39
|
try {
|
|
37
|
-
return statSync(path).isDirectory();
|
|
40
|
+
return fs.statSync(path).isDirectory();
|
|
38
41
|
} catch {
|
|
39
42
|
return false;
|
|
40
43
|
}
|
|
@@ -86,17 +89,22 @@ export function parseWorkspaceFolderPaths(value) {
|
|
|
86
89
|
.filter(Boolean);
|
|
87
90
|
}
|
|
88
91
|
|
|
89
|
-
function
|
|
92
|
+
export function nextAncestorDirectory(current) {
|
|
93
|
+
const parent = dirname(current);
|
|
94
|
+
return parent === current ? undefined : parent;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
export function packageJsonForResolvedFile(startPath) {
|
|
90
98
|
let current = resolve(startPath);
|
|
91
99
|
try {
|
|
92
|
-
if (!statSync(current).isDirectory()) current = dirname(current);
|
|
100
|
+
if (!fs.statSync(current).isDirectory()) current = dirname(current);
|
|
93
101
|
} catch {
|
|
94
102
|
current = dirname(current);
|
|
95
103
|
}
|
|
96
104
|
|
|
97
|
-
while (
|
|
105
|
+
while (current !== undefined) {
|
|
98
106
|
const packageJsonPath = join(current, "package.json");
|
|
99
|
-
if (existsSync(packageJsonPath)) {
|
|
107
|
+
if (fs.existsSync(packageJsonPath)) {
|
|
100
108
|
try {
|
|
101
109
|
const packageJson = readJson(packageJsonPath);
|
|
102
110
|
if (packageJson.name === PACKAGE_NAME) {
|
|
@@ -106,10 +114,9 @@ function packageJsonForResolvedFile(startPath) {
|
|
|
106
114
|
// Keep walking if a parent package manifest is malformed.
|
|
107
115
|
}
|
|
108
116
|
}
|
|
109
|
-
|
|
110
|
-
if (parent === current) return null;
|
|
111
|
-
current = parent;
|
|
117
|
+
current = nextAncestorDirectory(current);
|
|
112
118
|
}
|
|
119
|
+
return null;
|
|
113
120
|
}
|
|
114
121
|
|
|
115
122
|
function isWithinRoot(rootPath, candidatePath) {
|
|
@@ -120,10 +127,10 @@ function isWithinRoot(rootPath, candidatePath) {
|
|
|
120
127
|
);
|
|
121
128
|
}
|
|
122
129
|
|
|
123
|
-
function hasConsumerNodeModulesLink(workspaceRoot, packageRoot) {
|
|
130
|
+
export function hasConsumerNodeModulesLink(workspaceRoot, packageRoot) {
|
|
124
131
|
try {
|
|
125
132
|
const linkPath = join(workspaceRoot, "node_modules", PACKAGE_NAME);
|
|
126
|
-
const linkedRoot = realpathSync(linkPath);
|
|
133
|
+
const linkedRoot = fs.realpathSync(linkPath);
|
|
127
134
|
return (
|
|
128
135
|
isWithinRoot(linkedRoot, packageRoot) ||
|
|
129
136
|
isWithinRoot(packageRoot, linkedRoot)
|
|
@@ -133,7 +140,7 @@ function hasConsumerNodeModulesLink(workspaceRoot, packageRoot) {
|
|
|
133
140
|
}
|
|
134
141
|
}
|
|
135
142
|
|
|
136
|
-
function isProjectScopedPackage(workspaceRoot, packageRoot) {
|
|
143
|
+
export function isProjectScopedPackage(workspaceRoot, packageRoot) {
|
|
137
144
|
if (isWithinRoot(workspaceRoot, packageRoot)) return true;
|
|
138
145
|
|
|
139
146
|
// pnpm can expose a package through a symlink whose realpath is outside the
|
|
@@ -197,7 +204,7 @@ export function resolveProjectLocalMcp(workspaceRoot) {
|
|
|
197
204
|
}
|
|
198
205
|
|
|
199
206
|
const binPath = resolve(packageInfo.packageRoot, binEntry);
|
|
200
|
-
if (!existsSync(binPath)) {
|
|
207
|
+
if (!fs.existsSync(binPath)) {
|
|
201
208
|
throw new Error(
|
|
202
209
|
`[KIBI-CURSOR] Project-local ${PACKAGE_NAME} declares a missing executable: ${binPath}. Reinstall the workspace dependency and reload Cursor.`,
|
|
203
210
|
);
|
|
@@ -211,9 +218,9 @@ export function resolveProjectLocalMcp(workspaceRoot) {
|
|
|
211
218
|
};
|
|
212
219
|
}
|
|
213
220
|
|
|
214
|
-
function hasDeclaredProjectDependency(workspaceRoot) {
|
|
221
|
+
export function hasDeclaredProjectDependency(workspaceRoot) {
|
|
215
222
|
const packageJsonPath = join(workspaceRoot, "package.json");
|
|
216
|
-
if (!existsSync(packageJsonPath)) return false;
|
|
223
|
+
if (!fs.existsSync(packageJsonPath)) return false;
|
|
217
224
|
try {
|
|
218
225
|
const packageJson = readJson(packageJsonPath);
|
|
219
226
|
return [
|
|
@@ -235,8 +242,8 @@ function hasDeclaredProjectDependency(workspaceRoot) {
|
|
|
235
242
|
|
|
236
243
|
function isDemonstrablyProjectWorkspace(workspaceRoot) {
|
|
237
244
|
return (
|
|
238
|
-
existsSync(join(workspaceRoot, ".git")) ||
|
|
239
|
-
existsSync(join(workspaceRoot, ".kb")) ||
|
|
245
|
+
fs.existsSync(join(workspaceRoot, ".git")) ||
|
|
246
|
+
fs.existsSync(join(workspaceRoot, ".kb")) ||
|
|
240
247
|
hasDeclaredProjectDependency(workspaceRoot)
|
|
241
248
|
);
|
|
242
249
|
}
|
|
@@ -311,12 +318,30 @@ export function resolveWorkspaceRoot(explicitWorkspace, options = {}) {
|
|
|
311
318
|
);
|
|
312
319
|
}
|
|
313
320
|
|
|
314
|
-
function signalExitCode(signal) {
|
|
321
|
+
export function signalExitCode(signal) {
|
|
315
322
|
return SIGNAL_EXIT_CODES[signal] ?? 1;
|
|
316
323
|
}
|
|
317
324
|
|
|
325
|
+
export function isLaunchEntrypoint(argv1, moduleUrl) {
|
|
326
|
+
const entrypoint = argv1 ? resolve(argv1) : undefined;
|
|
327
|
+
return entrypoint === resolve(fileURLToPath(moduleUrl));
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
export async function runLaunchEntrypoint(
|
|
331
|
+
argv = process.argv.slice(2),
|
|
332
|
+
env = process.env,
|
|
333
|
+
) {
|
|
334
|
+
const exitCode = await launchKibiMcp(argv, env);
|
|
335
|
+
process.exitCode = exitCode;
|
|
336
|
+
return exitCode;
|
|
337
|
+
}
|
|
338
|
+
|
|
318
339
|
/** Spawn the project-local MCP server and mirror its transport and exit state. */
|
|
319
|
-
export function launchKibiMcp(
|
|
340
|
+
export function launchKibiMcp(
|
|
341
|
+
argv = process.argv.slice(2),
|
|
342
|
+
env = process.env,
|
|
343
|
+
spawnImpl = spawn,
|
|
344
|
+
) {
|
|
320
345
|
const [explicitWorkspace, ...childArgs] = argv;
|
|
321
346
|
let workspaceRoot;
|
|
322
347
|
let projectLocal;
|
|
@@ -332,11 +357,15 @@ export function launchKibiMcp(argv = process.argv.slice(2), env = process.env) {
|
|
|
332
357
|
return Promise.resolve(1);
|
|
333
358
|
}
|
|
334
359
|
|
|
335
|
-
const child =
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
360
|
+
const child = spawnImpl(
|
|
361
|
+
process.execPath,
|
|
362
|
+
[projectLocal.binPath, ...childArgs],
|
|
363
|
+
{
|
|
364
|
+
cwd: workspaceRoot,
|
|
365
|
+
env: { ...env, KIBI_WORKSPACE: workspaceRoot },
|
|
366
|
+
stdio: ["inherit", "inherit", "inherit"],
|
|
367
|
+
},
|
|
368
|
+
);
|
|
340
369
|
|
|
341
370
|
return new Promise((resolveExit) => {
|
|
342
371
|
let finished = false;
|
|
@@ -371,8 +400,12 @@ export function launchKibiMcp(argv = process.argv.slice(2), env = process.env) {
|
|
|
371
400
|
});
|
|
372
401
|
}
|
|
373
402
|
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
403
|
+
export async function runLaunchIfEntrypoint(
|
|
404
|
+
isEntrypoint = isLaunchEntrypoint(process.argv[1], import.meta.url),
|
|
405
|
+
start = runLaunchEntrypoint,
|
|
406
|
+
) {
|
|
407
|
+
if (!isEntrypoint) return;
|
|
408
|
+
await start();
|
|
378
409
|
}
|
|
410
|
+
|
|
411
|
+
await runLaunchIfEntrypoint();
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "kibi-cursor",
|
|
3
3
|
"displayName": "Kibi",
|
|
4
|
-
"version": "
|
|
4
|
+
"version": "2.0.0",
|
|
5
5
|
"description": "Optional Cursor adapter for Kibi. Requires SWI-Prolog 9+ and project-local kibi-cli, kibi-mcp, and kibi-core before the bundled MCP server can start.",
|
|
6
6
|
"license": "AGPL-3.0-or-later",
|
|
7
7
|
"author": {
|
|
@@ -1,5 +1,23 @@
|
|
|
1
1
|
export function parseWorkspaceFolderPaths(value: unknown): string[];
|
|
2
2
|
|
|
3
|
+
export function nextAncestorDirectory(current: string): string | undefined;
|
|
4
|
+
|
|
5
|
+
export function packageJsonForResolvedFile(startPath: string): {
|
|
6
|
+
packageJsonPath: string;
|
|
7
|
+
packageRoot: string;
|
|
8
|
+
packageJson: Record<string, unknown>;
|
|
9
|
+
} | null;
|
|
10
|
+
|
|
11
|
+
export function hasConsumerNodeModulesLink(
|
|
12
|
+
workspaceRoot: string,
|
|
13
|
+
packageRoot: string,
|
|
14
|
+
): boolean;
|
|
15
|
+
|
|
16
|
+
export function isProjectScopedPackage(
|
|
17
|
+
workspaceRoot: string,
|
|
18
|
+
packageRoot: string,
|
|
19
|
+
): boolean;
|
|
20
|
+
|
|
3
21
|
export function resolveProjectLocalMcp(workspaceRoot: string): {
|
|
4
22
|
packageRoot: string;
|
|
5
23
|
packageJsonPath: string;
|
|
@@ -7,12 +25,32 @@ export function resolveProjectLocalMcp(workspaceRoot: string): {
|
|
|
7
25
|
binPath: string;
|
|
8
26
|
};
|
|
9
27
|
|
|
28
|
+
export function hasDeclaredProjectDependency(workspaceRoot: string): boolean;
|
|
29
|
+
|
|
10
30
|
export function resolveWorkspaceRoot(
|
|
11
31
|
explicitWorkspace?: string,
|
|
12
32
|
options?: { cwd?: string; env?: NodeJS.ProcessEnv },
|
|
13
33
|
): string;
|
|
14
34
|
|
|
35
|
+
export function signalExitCode(signal: NodeJS.Signals | string): number;
|
|
36
|
+
|
|
37
|
+
export function isLaunchEntrypoint(
|
|
38
|
+
argv1: string | undefined,
|
|
39
|
+
moduleUrl: string,
|
|
40
|
+
): boolean;
|
|
41
|
+
|
|
42
|
+
export function runLaunchEntrypoint(
|
|
43
|
+
argv?: string[],
|
|
44
|
+
env?: NodeJS.ProcessEnv,
|
|
45
|
+
): Promise<number>;
|
|
46
|
+
|
|
15
47
|
export function launchKibiMcp(
|
|
16
48
|
argv?: string[],
|
|
17
49
|
env?: NodeJS.ProcessEnv,
|
|
50
|
+
spawnImpl?: typeof import("node:child_process").spawn,
|
|
18
51
|
): Promise<number>;
|
|
52
|
+
|
|
53
|
+
export function runLaunchIfEntrypoint(
|
|
54
|
+
isEntrypoint?: boolean,
|
|
55
|
+
start?: () => Promise<number> | Promise<void>,
|
|
56
|
+
): Promise<void>;
|
|
@@ -7,14 +7,17 @@
|
|
|
7
7
|
/**
|
|
8
8
|
* Launch the kibi-mcp package installed by the consumer project.
|
|
9
9
|
*
|
|
10
|
-
* Cursor
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
10
|
+
* Cursor resolves this command from the plugin-root manifest, but the process
|
|
11
|
+
* cwd is host-controlled and is not a reliable consumer workspace (it may be
|
|
12
|
+
* the plugin directory or another host directory). This adapter deliberately
|
|
13
|
+
* knows nothing about the Kibi source tree: it resolves the unrelated consumer
|
|
14
|
+
* workspace from Cursor's workspace-folder data or explicit environment,
|
|
15
|
+
* finds that workspace's kibi-mcp package, and runs its declared bin with the
|
|
16
|
+
* consumer as both cwd and KIBI_WORKSPACE.
|
|
14
17
|
*/
|
|
15
18
|
|
|
16
19
|
import { spawn } from "node:child_process";
|
|
17
|
-
import
|
|
20
|
+
import * as fs from "node:fs";
|
|
18
21
|
import { createRequire } from "node:module";
|
|
19
22
|
import { dirname, isAbsolute, join, relative, resolve } from "node:path";
|
|
20
23
|
import { fileURLToPath } from "node:url";
|
|
@@ -29,12 +32,12 @@ const SIGNAL_EXIT_CODES = {
|
|
|
29
32
|
};
|
|
30
33
|
|
|
31
34
|
function readJson(path) {
|
|
32
|
-
return JSON.parse(readFileSync(path, "utf8"));
|
|
35
|
+
return JSON.parse(fs.readFileSync(path, "utf8"));
|
|
33
36
|
}
|
|
34
37
|
|
|
35
38
|
function isDirectory(path) {
|
|
36
39
|
try {
|
|
37
|
-
return statSync(path).isDirectory();
|
|
40
|
+
return fs.statSync(path).isDirectory();
|
|
38
41
|
} catch {
|
|
39
42
|
return false;
|
|
40
43
|
}
|
|
@@ -86,17 +89,22 @@ export function parseWorkspaceFolderPaths(value) {
|
|
|
86
89
|
.filter(Boolean);
|
|
87
90
|
}
|
|
88
91
|
|
|
89
|
-
function
|
|
92
|
+
export function nextAncestorDirectory(current) {
|
|
93
|
+
const parent = dirname(current);
|
|
94
|
+
return parent === current ? undefined : parent;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
export function packageJsonForResolvedFile(startPath) {
|
|
90
98
|
let current = resolve(startPath);
|
|
91
99
|
try {
|
|
92
|
-
if (!statSync(current).isDirectory()) current = dirname(current);
|
|
100
|
+
if (!fs.statSync(current).isDirectory()) current = dirname(current);
|
|
93
101
|
} catch {
|
|
94
102
|
current = dirname(current);
|
|
95
103
|
}
|
|
96
104
|
|
|
97
|
-
while (
|
|
105
|
+
while (current !== undefined) {
|
|
98
106
|
const packageJsonPath = join(current, "package.json");
|
|
99
|
-
if (existsSync(packageJsonPath)) {
|
|
107
|
+
if (fs.existsSync(packageJsonPath)) {
|
|
100
108
|
try {
|
|
101
109
|
const packageJson = readJson(packageJsonPath);
|
|
102
110
|
if (packageJson.name === PACKAGE_NAME) {
|
|
@@ -106,10 +114,9 @@ function packageJsonForResolvedFile(startPath) {
|
|
|
106
114
|
// Keep walking if a parent package manifest is malformed.
|
|
107
115
|
}
|
|
108
116
|
}
|
|
109
|
-
|
|
110
|
-
if (parent === current) return null;
|
|
111
|
-
current = parent;
|
|
117
|
+
current = nextAncestorDirectory(current);
|
|
112
118
|
}
|
|
119
|
+
return null;
|
|
113
120
|
}
|
|
114
121
|
|
|
115
122
|
function isWithinRoot(rootPath, candidatePath) {
|
|
@@ -120,10 +127,10 @@ function isWithinRoot(rootPath, candidatePath) {
|
|
|
120
127
|
);
|
|
121
128
|
}
|
|
122
129
|
|
|
123
|
-
function hasConsumerNodeModulesLink(workspaceRoot, packageRoot) {
|
|
130
|
+
export function hasConsumerNodeModulesLink(workspaceRoot, packageRoot) {
|
|
124
131
|
try {
|
|
125
132
|
const linkPath = join(workspaceRoot, "node_modules", PACKAGE_NAME);
|
|
126
|
-
const linkedRoot = realpathSync(linkPath);
|
|
133
|
+
const linkedRoot = fs.realpathSync(linkPath);
|
|
127
134
|
return (
|
|
128
135
|
isWithinRoot(linkedRoot, packageRoot) ||
|
|
129
136
|
isWithinRoot(packageRoot, linkedRoot)
|
|
@@ -133,7 +140,7 @@ function hasConsumerNodeModulesLink(workspaceRoot, packageRoot) {
|
|
|
133
140
|
}
|
|
134
141
|
}
|
|
135
142
|
|
|
136
|
-
function isProjectScopedPackage(workspaceRoot, packageRoot) {
|
|
143
|
+
export function isProjectScopedPackage(workspaceRoot, packageRoot) {
|
|
137
144
|
if (isWithinRoot(workspaceRoot, packageRoot)) return true;
|
|
138
145
|
|
|
139
146
|
// pnpm can expose a package through a symlink whose realpath is outside the
|
|
@@ -197,7 +204,7 @@ export function resolveProjectLocalMcp(workspaceRoot) {
|
|
|
197
204
|
}
|
|
198
205
|
|
|
199
206
|
const binPath = resolve(packageInfo.packageRoot, binEntry);
|
|
200
|
-
if (!existsSync(binPath)) {
|
|
207
|
+
if (!fs.existsSync(binPath)) {
|
|
201
208
|
throw new Error(
|
|
202
209
|
`[KIBI-CURSOR] Project-local ${PACKAGE_NAME} declares a missing executable: ${binPath}. Reinstall the workspace dependency and reload Cursor.`,
|
|
203
210
|
);
|
|
@@ -211,9 +218,9 @@ export function resolveProjectLocalMcp(workspaceRoot) {
|
|
|
211
218
|
};
|
|
212
219
|
}
|
|
213
220
|
|
|
214
|
-
function hasDeclaredProjectDependency(workspaceRoot) {
|
|
221
|
+
export function hasDeclaredProjectDependency(workspaceRoot) {
|
|
215
222
|
const packageJsonPath = join(workspaceRoot, "package.json");
|
|
216
|
-
if (!existsSync(packageJsonPath)) return false;
|
|
223
|
+
if (!fs.existsSync(packageJsonPath)) return false;
|
|
217
224
|
try {
|
|
218
225
|
const packageJson = readJson(packageJsonPath);
|
|
219
226
|
return [
|
|
@@ -235,8 +242,8 @@ function hasDeclaredProjectDependency(workspaceRoot) {
|
|
|
235
242
|
|
|
236
243
|
function isDemonstrablyProjectWorkspace(workspaceRoot) {
|
|
237
244
|
return (
|
|
238
|
-
existsSync(join(workspaceRoot, ".git")) ||
|
|
239
|
-
existsSync(join(workspaceRoot, ".kb")) ||
|
|
245
|
+
fs.existsSync(join(workspaceRoot, ".git")) ||
|
|
246
|
+
fs.existsSync(join(workspaceRoot, ".kb")) ||
|
|
240
247
|
hasDeclaredProjectDependency(workspaceRoot)
|
|
241
248
|
);
|
|
242
249
|
}
|
|
@@ -311,12 +318,30 @@ export function resolveWorkspaceRoot(explicitWorkspace, options = {}) {
|
|
|
311
318
|
);
|
|
312
319
|
}
|
|
313
320
|
|
|
314
|
-
function signalExitCode(signal) {
|
|
321
|
+
export function signalExitCode(signal) {
|
|
315
322
|
return SIGNAL_EXIT_CODES[signal] ?? 1;
|
|
316
323
|
}
|
|
317
324
|
|
|
325
|
+
export function isLaunchEntrypoint(argv1, moduleUrl) {
|
|
326
|
+
const entrypoint = argv1 ? resolve(argv1) : undefined;
|
|
327
|
+
return entrypoint === resolve(fileURLToPath(moduleUrl));
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
export async function runLaunchEntrypoint(
|
|
331
|
+
argv = process.argv.slice(2),
|
|
332
|
+
env = process.env,
|
|
333
|
+
) {
|
|
334
|
+
const exitCode = await launchKibiMcp(argv, env);
|
|
335
|
+
process.exitCode = exitCode;
|
|
336
|
+
return exitCode;
|
|
337
|
+
}
|
|
338
|
+
|
|
318
339
|
/** Spawn the project-local MCP server and mirror its transport and exit state. */
|
|
319
|
-
export function launchKibiMcp(
|
|
340
|
+
export function launchKibiMcp(
|
|
341
|
+
argv = process.argv.slice(2),
|
|
342
|
+
env = process.env,
|
|
343
|
+
spawnImpl = spawn,
|
|
344
|
+
) {
|
|
320
345
|
const [explicitWorkspace, ...childArgs] = argv;
|
|
321
346
|
let workspaceRoot;
|
|
322
347
|
let projectLocal;
|
|
@@ -332,11 +357,15 @@ export function launchKibiMcp(argv = process.argv.slice(2), env = process.env) {
|
|
|
332
357
|
return Promise.resolve(1);
|
|
333
358
|
}
|
|
334
359
|
|
|
335
|
-
const child =
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
360
|
+
const child = spawnImpl(
|
|
361
|
+
process.execPath,
|
|
362
|
+
[projectLocal.binPath, ...childArgs],
|
|
363
|
+
{
|
|
364
|
+
cwd: workspaceRoot,
|
|
365
|
+
env: { ...env, KIBI_WORKSPACE: workspaceRoot },
|
|
366
|
+
stdio: ["inherit", "inherit", "inherit"],
|
|
367
|
+
},
|
|
368
|
+
);
|
|
340
369
|
|
|
341
370
|
return new Promise((resolveExit) => {
|
|
342
371
|
let finished = false;
|
|
@@ -371,8 +400,12 @@ export function launchKibiMcp(argv = process.argv.slice(2), env = process.env) {
|
|
|
371
400
|
});
|
|
372
401
|
}
|
|
373
402
|
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
403
|
+
export async function runLaunchIfEntrypoint(
|
|
404
|
+
isEntrypoint = isLaunchEntrypoint(process.argv[1], import.meta.url),
|
|
405
|
+
start = runLaunchEntrypoint,
|
|
406
|
+
) {
|
|
407
|
+
if (!isEntrypoint) return;
|
|
408
|
+
await start();
|
|
378
409
|
}
|
|
410
|
+
|
|
411
|
+
await runLaunchIfEntrypoint();
|
package/dist/hook-runner.d.ts
CHANGED
|
@@ -11,4 +11,8 @@ export type HookEnvironment = {
|
|
|
11
11
|
workspaceTrusted?: boolean;
|
|
12
12
|
};
|
|
13
13
|
export declare function runHook(rawInput: unknown, environment?: HookEnvironment): Promise<CursorHookResult>;
|
|
14
|
+
export declare function main(): Promise<void>;
|
|
15
|
+
export declare function isInvokedAsCli(argv1: string | undefined, moduleUrl: string): boolean;
|
|
16
|
+
export declare function runHookCli(): Promise<void>;
|
|
17
|
+
export declare function runHookCliIfMain(isMain?: boolean, start?: typeof runHookCli): Promise<void>;
|
|
14
18
|
export declare const hookRunnerPath: string;
|
package/dist/hook-runner.js
CHANGED
|
@@ -195,21 +195,33 @@ export async function runHook(rawInput, environment = {}) {
|
|
|
195
195
|
return emptyResult();
|
|
196
196
|
}
|
|
197
197
|
}
|
|
198
|
-
async function main() {
|
|
198
|
+
export async function main() {
|
|
199
199
|
const result = await runHook(parseStdinJson(await readStdin()), {
|
|
200
200
|
workspaceTrusted: process.argv.includes("--trusted-workspace"),
|
|
201
201
|
});
|
|
202
202
|
process.stdout.write(`${JSON.stringify(result)}\n`);
|
|
203
203
|
}
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
204
|
+
// implements REQ-cursor-kibi-plugin-v1
|
|
205
|
+
// covered_by TEST-cursor-kibi-plugin-v1
|
|
206
|
+
export function isInvokedAsCli(argv1, moduleUrl) {
|
|
207
|
+
const invokedPath = argv1 ? pathToFileURL(path.resolve(argv1)).href : "";
|
|
208
|
+
return moduleUrl === invokedPath;
|
|
209
|
+
}
|
|
210
|
+
export async function runHookCli() {
|
|
211
|
+
try {
|
|
212
|
+
await main();
|
|
213
|
+
}
|
|
214
|
+
catch (error) {
|
|
209
215
|
const message = error instanceof Error ? error.message : "Unknown hook error";
|
|
210
216
|
process.stdout.write(`${JSON.stringify({
|
|
211
217
|
additional_context: `Kibi hook runner error: ${message}`,
|
|
212
218
|
})}\n`);
|
|
213
|
-
}
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
export async function runHookCliIfMain(isMain = isInvokedAsCli(process.argv[1], import.meta.url), start = runHookCli) {
|
|
222
|
+
if (!isMain)
|
|
223
|
+
return;
|
|
224
|
+
await start();
|
|
214
225
|
}
|
|
226
|
+
void runHookCliIfMain();
|
|
215
227
|
export const hookRunnerPath = fileURLToPath(import.meta.url);
|
package/dist/mcp.json
CHANGED
package/dist/path-policy.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
export declare function isFreshnessLane(lane: string | undefined): boolean;
|
|
1
2
|
export declare function extractExplicitPathFields(input: unknown): string[];
|
|
2
3
|
export declare function isDirectKbPath(candidate: string): boolean;
|
|
3
4
|
export declare function isMeaningfulTrackedPath(candidate: string): boolean;
|
package/dist/path-policy.js
CHANGED
|
@@ -1,3 +1,14 @@
|
|
|
1
|
+
export function isFreshnessLane(lane) {
|
|
2
|
+
return (lane === "requirements" ||
|
|
3
|
+
lane === "scenarios" ||
|
|
4
|
+
lane === "tests" ||
|
|
5
|
+
lane === "facts" ||
|
|
6
|
+
lane === "adr" ||
|
|
7
|
+
lane === "flags" ||
|
|
8
|
+
lane === "events" ||
|
|
9
|
+
lane === "symbols.yaml" ||
|
|
10
|
+
lane === "symbol-coordinates.yaml");
|
|
11
|
+
}
|
|
1
12
|
// implements REQ-cursor-kibi-plugin-v1
|
|
2
13
|
const explicitPathKeys = new Set([
|
|
3
14
|
"absolute_path",
|
|
@@ -145,17 +156,8 @@ export function isKbFreshnessRelevantPath(candidate) {
|
|
|
145
156
|
const segments = pathSegments(normalizePath(candidate));
|
|
146
157
|
if (segments[0] === ".kb") {
|
|
147
158
|
const lane = segments[1];
|
|
148
|
-
if (lane
|
|
149
|
-
lane === "scenarios" ||
|
|
150
|
-
lane === "tests" ||
|
|
151
|
-
lane === "facts" ||
|
|
152
|
-
lane === "adr" ||
|
|
153
|
-
lane === "flags" ||
|
|
154
|
-
lane === "events" ||
|
|
155
|
-
lane === "symbols.yaml" ||
|
|
156
|
-
lane === "symbol-coordinates.yaml") {
|
|
159
|
+
if (isFreshnessLane(lane))
|
|
157
160
|
return true;
|
|
158
|
-
}
|
|
159
161
|
}
|
|
160
162
|
// Legacy layout during migration
|
|
161
163
|
if (segments.includes("documentation")) {
|
|
@@ -3,7 +3,22 @@ description: Kibi capability-based discovery-first workflow and KB mutation disc
|
|
|
3
3
|
alwaysApply: true
|
|
4
4
|
---
|
|
5
5
|
|
|
6
|
-
Use Kibi via MCP tools when available, or via CLI JSON routes when MCP is unavailable.
|
|
6
|
+
Use Kibi via MCP tools when available, or via CLI JSON routes when MCP is unavailable.
|
|
7
|
+
|
|
8
|
+
Interface selection:
|
|
9
|
+
1. If any `kb_*` or `kibi_kb_*` tool is in the tool list, use MCP only. Do not use the CLI path.
|
|
10
|
+
2. Else if Shell (or equivalent) is available and the project-local CLI is trusted, run the non-installing JSON recipe below. Missing MCP tools does not mean Kibi is unavailable.
|
|
11
|
+
3. Else stop and tell the operator. Do not probe the CLI, do not install packages, and do not infer MCP availability from config file existence.
|
|
12
|
+
4. Shell is in-policy only for the trusted project-local `npx --no-install kibi ...` (or `bunx --no-install kibi ...`) command when step 2 applies.
|
|
13
|
+
5. Never use a global fallback, an installing runner, or an unapproved route. Do not read or edit `.kb/` files directly.
|
|
14
|
+
|
|
15
|
+
CLI JSON recipe (stdin is one UTF-8 JSON object):
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
printf '%s\n' '{"query":"checkout","limit":10}' | npx --no-install kibi search --input -
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
Map canonical MCP names to CLI routes by dropping the `kb_` prefix and replacing `_` with `-` (`kb_search` → `search`, `kb_find_gaps` → `find-gaps`, `kb_query` → `query`, `kb_status` → `status`, `kb_check` → `check`, `kb_upsert` → `upsert`).
|
|
7
22
|
|
|
8
23
|
Discovery workflow:
|
|
9
24
|
- Start with `kb_search` for broad discovery.
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
"kibi-bootstrap/resources/branch-lifecycle.md": "c854264ce888439140883a3fc636db5e522f020dd27b30a5c1ecc4105b64ed13",
|
|
5
5
|
"kibi-bootstrap/resources/operation-access.md": "b5a6a59e72d800c376e7fad79132897623c32a0ce152a1e8d73f9628b444db51",
|
|
6
6
|
"kibi-bootstrap/resources/source-authoring.md": "bf296e62f69474e01797d6cb9469bf624551dca834dd188abb6e705bd96fa586",
|
|
7
|
-
"kibi-freshness/SKILL.md": "
|
|
7
|
+
"kibi-freshness/SKILL.md": "fa90d34663ddd6f03468008fb26e4417211f7b7d46ffde082291747743464c54",
|
|
8
8
|
"kibi-freshness/resources/branch-lifecycle.md": "ebc01b19fe3dca8df5fc0175978963f6f1204f71f7c805cb2a5fb1b277ac78e4",
|
|
9
9
|
"kibi-freshness/resources/operation-access.md": "4e1db9294ee540cd29b8b3a2532a81e7894d5b4228b39e99ba48b37212f489f3",
|
|
10
10
|
"kibi-freshness/resources/source-authoring.md": "41a0547ce4d2a112a0f3671d36720f07c447864db3b473e94eb88a8ce2d371ff",
|
|
@@ -13,14 +13,15 @@
|
|
|
13
13
|
"kibi-traceability/resources/operation-access.md": "39f7972ab7f4ae7479e78f7072bb50e8e6c4cdf4f857425f69d2522d37db437c",
|
|
14
14
|
"kibi-traceability/resources/source-authoring.md": "408c6ee2bef1b84e0194c0538ef929c26a0a617e631b2234247388140dbbdd08",
|
|
15
15
|
"kibi-traceability/resources/traceability.md": "38c290a0bc71bd1692e115f22694743946e2f69630e134acfd43cee5f613fb88",
|
|
16
|
-
"kibi-usage/SKILL.md": "
|
|
16
|
+
"kibi-usage/SKILL.md": "47a76e74fa1439e3745afe4c883b9cb1383df7696b1a0d695c02fe4e34e3205d",
|
|
17
17
|
"kibi-usage/resources/branch-lifecycle.md": "f1bf359e9826e83c548f3cd0287f8175a62456527b0132b06b3f9f74aa59b7d6",
|
|
18
18
|
"kibi-usage/resources/fact-lanes.md": "41b28023a57593009867ea0ebc8e9f3f05c3bd837144772d12844423f922e763",
|
|
19
19
|
"kibi-usage/resources/kb-improvement.md": "17e8b6cf6a0dd43b443a2bfd92e7205d099647a5f1c75fe85413a6cea63d86c6",
|
|
20
20
|
"kibi-usage/resources/logic-ir.md": "151675d0ffa813cb9e271084102a3af42de369b8f9a0c64866af0cdc4d924063",
|
|
21
|
-
"kibi-usage/resources/operation-access.md": "
|
|
21
|
+
"kibi-usage/resources/operation-access.md": "e8b4a3676892d2f7e2cfd708ec4ad1f41b6f296e1eb3a5ecc047f43322b4a92a",
|
|
22
|
+
"kibi-usage/resources/proof.md": "e1a44ea4bf342afebb0aaa7800221067a511f6607c687ac38db6392b7e3d9f9a",
|
|
22
23
|
"kibi-usage/resources/relationship-directions.md": "a357072d3370db9b7d80e505795f952909a68778cc18b1d089970de91e4fda46",
|
|
23
24
|
"kibi-usage/resources/source-authoring.md": "ac4f1a9b5785462f8c3530bdcdb82ad9a0ee1f4049454ae28bf4df09ff267ebd",
|
|
24
25
|
"kibi-usage/resources/ui-requirements.md": "b7c31ab1affa3ab25ae5242d8fd8c4d546532d1cafc65d84088375317c1682cc",
|
|
25
|
-
"kibi-usage/resources/workflows.md": "
|
|
26
|
+
"kibi-usage/resources/workflows.md": "776b97a794325e7012a230d2b9fc32fc89b2a51df45dfd8498cad49170bdc43d"
|
|
26
27
|
}
|
|
@@ -39,7 +39,7 @@ echo '{}' | npx --no-install kibi status --input -
|
|
|
39
39
|
matching `branch.json`; a `legacy_compat` attachment is read-only until an
|
|
40
40
|
explicit old/new migration is approved.
|
|
41
41
|
- Inspect bounded `staleReasons` (indexed source missing/newer or documentation
|
|
42
|
-
source newer) and `
|
|
42
|
+
source newer) and `proofSnapshotChanges`. Kibi deliberately reports
|
|
43
43
|
editor/config paths such as editor dot-directories; do not silently ignore them.
|
|
44
44
|
- Use `kb_query`, or `query --input`, with `sourceFile` and text references for source-linked discovery.
|
|
45
45
|
- Re-run `kb_search`, or `search --input`, after sync gaps or stale context are detected.
|