kibi-cursor 2.7.6 → 2.8.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 +16 -0
- package/README.md +3 -2
- package/agent-plugin/plugin.json +1 -1
- package/dist/.cursor-plugin/plugin.json +1 -1
- package/dist/hook-input.d.ts +3 -1
- package/dist/hook-input.js +4 -0
- package/dist/hook-runner.d.ts +2 -0
- package/dist/hook-runner.js +77 -17
- package/dist/hooks/hooks.json +5 -0
- package/hooks/hooks.json +5 -0
- package/package.json +2 -2
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "kibi-cursor",
|
|
3
3
|
"displayName": "Kibi",
|
|
4
|
-
"version": "2.
|
|
4
|
+
"version": "2.8.0",
|
|
5
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": {
|
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,21 @@
|
|
|
1
1
|
# kibi-cursor
|
|
2
2
|
|
|
3
|
+
## 2.8.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- 00d2871: The Codex, Cursor and ZCode hooks now act on recorded decisions while the work happens, as the Claude Code hooks do.
|
|
8
|
+
- **Firm requirements before the edit.** When an edit changes code that implements a current firm requirement, the pre-edit hook tells the agent to ask the person before an edit that breaks that rule, naming the rule; once per requirement per session. These hosts cannot put the question to the person from a hook (Codex has no "ask", Cursor does not enforce one, and the ZCode adapter stays advisory), so this is a notice to the agent. Set `KIBI_CONFIRM_FIRM_EDITS=0` to turn it off.
|
|
9
|
+
- **Stated rules are captured.** A prompt that reads as a rule ("must", "never", "from now on") gets the note that recording it with `kb_upsert` is part of the change: through a new `UserPromptSubmit` hook in Codex and ZCode, and in Cursor through a new `beforeSubmitPrompt` hook whose note arrives with the next pre-edit message. When a session changed source files and wrote nothing with `kb_upsert`, the stop hook asks once for the decision to be recorded (Codex continues the turn with it; Cursor adds it to the stop follow-up; ZCode adds it to the Stop context).
|
|
10
|
+
- **Codex unlinked-file candidates** are ranked by the latest prompt's words as well as the path's.
|
|
11
|
+
|
|
12
|
+
`kibi-agent-core`: new `kibi-agent-core/decision-guard` entry with the host-neutral pieces (`firmRequirementsTouched`, `firmEditNotice`, `confirmFirmEdits`, the prompt and stop notes, and per-session markers). `kibi-claude` now imports its firm-edit and capture texts from it; behavior is unchanged.
|
|
13
|
+
|
|
14
|
+
### Patch Changes
|
|
15
|
+
|
|
16
|
+
- Updated dependencies [00d2871]
|
|
17
|
+
- kibi-agent-core@0.8.0
|
|
18
|
+
|
|
3
19
|
## 2.7.6
|
|
4
20
|
|
|
5
21
|
### Patch Changes
|
package/README.md
CHANGED
|
@@ -122,11 +122,12 @@ agent-plugin/
|
|
|
122
122
|
Hooks are warning-only and never replace MCP/CLI behavior:
|
|
123
123
|
|
|
124
124
|
- **sessionStart**: bootstrap reminder when `.kb/manifest.json` is missing.
|
|
125
|
-
- **preToolUse**: warns on explicit direct `.kb/**` edits without blocking. Before the first edit of a file whose symbols implement a requirement, it adds the shared Kibi snippet (from `kibi-agent-core/snippets`, the same builder the Claude Code plugin uses): the requirements and their implementing symbols, what the lead requirement must keep true and the decision behind it, the covering tests, and the symbol the edit lands in. A superseded or retired lead requirement gets no "must keep true" or "Decision" line. Once per path per session.
|
|
125
|
+
- **preToolUse**: warns on explicit direct `.kb/**` edits without blocking. Before the first edit of a file whose symbols implement a requirement, it adds the shared Kibi snippet (from `kibi-agent-core/snippets`, the same builder the Claude Code plugin uses): the requirements and their implementing symbols, what the lead requirement must keep true and the decision behind it, the covering tests, and the symbol the edit lands in. A superseded or retired lead requirement gets no "must keep true" or "Decision" line. Once per path per session. When the edited code implements a firm requirement (a rule the person set), the message first tells the agent to ask the person before an edit that breaks it, once per requirement per conversation (Cursor does not enforce a hook's "ask"; `KIBI_CONFIRM_FIRM_EDITS=0` turns this off).
|
|
126
|
+
- **beforeSubmitPrompt**: never blocks a prompt. A prompt that states a rule ("must not", "from now on") leaves a note, delivered with the next pre-edit message, to record that rule with `kb_upsert` as part of the change.
|
|
126
127
|
- **beforeReadFile** and **postToolUse (Read)**: inject source-linked lookup guidance once per path per session, with the same snippet minus the "must keep true" and "Decision" lines.
|
|
127
128
|
- **postToolUse** (opt-in telemetry): with `KIBI_DIAGNOSTIC_MODE=1`, appends `interface: "hook"` rows with `host: "cursor"` to `.kb/usage.log` for `kb_*` lookups (MCP or `kibi <route>` in Shell) and for edited files with the requirements they implement, keyed by `conversation_id`. `kibi usage-metrics` reads them for the `lookup_before_first_edit` metric. Nothing is written without the variable.
|
|
128
129
|
- **postToolUse (Write/Edit)**: inject traceability and freshness guidance once per path per session, including `kb_check({sourceFiles:[...], includeImpactDiagnostics:true, includeWorkingTreeDiff:true})` for meaningful source edits.
|
|
129
|
-
- **stop**: emits a single freshness or impact-check follow-up when meaningful paths were **edited** during the turn (reads and search do not count). Plan delivery (`CreatePlan`) stays silent unless that same turn also edited source or mutated the KB.
|
|
130
|
+
- **stop**: emits a single freshness or impact-check follow-up when meaningful paths were **edited** during the turn (reads and search do not count). Plan delivery (`CreatePlan`) stays silent unless that same turn also edited source or mutated the KB. When the conversation changed source files and recorded nothing with `kb_upsert`, the follow-up also asks, once, for the decision behind the change to be recorded.
|
|
130
131
|
|
|
131
132
|
### What the plugin does not do
|
|
132
133
|
|
package/agent-plugin/plugin.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$schema": "https://agent-plugins.org/schemas/1.0.0/plugin.schema.json",
|
|
3
3
|
"name": "kibi",
|
|
4
|
-
"version": "2.
|
|
4
|
+
"version": "2.8.0",
|
|
5
5
|
"description": "Repo-local, per-branch, queryable knowledge base. Portable Agent Plugin packaging Kibi's Agent Skills and MCP server.",
|
|
6
6
|
"author": { "name": "Kibi" },
|
|
7
7
|
"homepage": "https://looted.github.io/kibi/",
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "kibi-cursor",
|
|
3
3
|
"displayName": "Kibi",
|
|
4
|
-
"version": "2.
|
|
4
|
+
"version": "2.8.0",
|
|
5
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": {
|
package/dist/hook-input.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export type HookEvent = "sessionStart" | "preToolUse" | "postToolUse" | "beforeReadFile" | "stop";
|
|
1
|
+
export type HookEvent = "sessionStart" | "preToolUse" | "postToolUse" | "beforeReadFile" | "beforeSubmitPrompt" | "stop";
|
|
2
2
|
export type HookStopStatus = "completed" | "aborted" | "error";
|
|
3
3
|
export type HookInput = {
|
|
4
4
|
event: string;
|
|
@@ -9,6 +9,8 @@ export type HookInput = {
|
|
|
9
9
|
conversationId?: string;
|
|
10
10
|
filePath?: string;
|
|
11
11
|
status?: HookStopStatus;
|
|
12
|
+
/** beforeSubmitPrompt: the submitted prompt. */
|
|
13
|
+
prompt?: string;
|
|
12
14
|
};
|
|
13
15
|
export declare function parseHookInput(input: unknown): HookInput;
|
|
14
16
|
export declare function readStdin(): Promise<string>;
|
package/dist/hook-input.js
CHANGED
|
@@ -68,6 +68,7 @@ export function parseHookInput(input) {
|
|
|
68
68
|
]);
|
|
69
69
|
const filePath = readString(input, ["file_path", "filePath", "path"]);
|
|
70
70
|
const status = parseStopStatus(readString(input, ["status"]));
|
|
71
|
+
const prompt = readString(input, ["prompt"]);
|
|
71
72
|
const parsed = { event };
|
|
72
73
|
if (cwd !== undefined) {
|
|
73
74
|
parsed.cwd = cwd;
|
|
@@ -87,6 +88,9 @@ export function parseHookInput(input) {
|
|
|
87
88
|
if (filePath !== undefined) {
|
|
88
89
|
parsed.filePath = filePath;
|
|
89
90
|
}
|
|
91
|
+
if (prompt !== undefined) {
|
|
92
|
+
parsed.prompt = prompt;
|
|
93
|
+
}
|
|
90
94
|
if (status !== undefined) {
|
|
91
95
|
parsed.status = status;
|
|
92
96
|
}
|
package/dist/hook-runner.d.ts
CHANGED
package/dist/hook-runner.js
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
import fs from "node:fs";
|
|
4
4
|
import path from "node:path";
|
|
5
5
|
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
6
|
+
import { captureReminder, claimCaptureReminder, confirmFirmEdits, firmEditNotice, firmRequirementsTouched, hasDecisionFlag, holdRuleNote, markDecisionFlag, recordRequest, settleFirmRequirements, settledFirmRequirements, takeRuleNote, } from "kibi-agent-core/decision-guard";
|
|
6
7
|
import { appendHookUsageRows, editTraces, hookTelemetryEnabled, kbUsageTrace, readPackageVersion, } from "kibi-agent-core/hook-usage-log";
|
|
7
8
|
import { stampKibiWorkspace } from "kibi-agent-core/kb-mcp-tools";
|
|
8
9
|
import { createEntitySummarizer, editFocus, fileKnowledgeSnippet, implementedRequirementIds, } from "kibi-agent-core/snippets";
|
|
@@ -11,7 +12,7 @@ import { parseHookInput, parseStdinJson, readStdin } from "./hook-input.js";
|
|
|
11
12
|
import { addDirtyPaths, clearSessionHookState, hasGuidedPath, loadHookState, recordKbMcpTool, recordPlanDelivered, rememberGuidedPath, resolveStateDir, } from "./hook-state.js";
|
|
12
13
|
import { extractKbMcpToolCall } from "./kb-mcp-tools.js";
|
|
13
14
|
import { BOOTSTRAP_REMINDER, DIRECT_KB_EDIT_WARNING, interfaceAdvisory, stopFollowupMessage, } from "./messages.js";
|
|
14
|
-
import { extractExplicitPathFields, isDirectKbPath, isKbFreshnessRelevantPath, isMeaningfulTrackedPath, toRepoRelativePath, } from "./path-policy.js";
|
|
15
|
+
import { extractExplicitPathFields, isDirectKbPath, isKbFreshnessRelevantPath, isMeaningfulTrackedPath, isSourceImpactRelevantPath, toRepoRelativePath, } from "./path-policy.js";
|
|
15
16
|
import { getSourceLinkedSymbols, } from "./source-linked-requirements.js";
|
|
16
17
|
const MAX_LINKED_REQUIREMENTS = 3;
|
|
17
18
|
/**
|
|
@@ -31,6 +32,28 @@ function knowledgeFor(workspaceRoot, linked, surface, focus) {
|
|
|
31
32
|
summarize: createEntitySummarizer(workspaceRoot),
|
|
32
33
|
});
|
|
33
34
|
}
|
|
35
|
+
// implements REQ-agent-hosts-decision-guard
|
|
36
|
+
/**
|
|
37
|
+
* Cursor does not enforce a hook's "ask", so an edit of code that implements
|
|
38
|
+
* a firm requirement gets a notice telling the agent to ask the person first,
|
|
39
|
+
* once per requirement per conversation.
|
|
40
|
+
*/
|
|
41
|
+
function firmNotice(workspaceRoot, linked, focus, session, env) {
|
|
42
|
+
if (!confirmFirmEdits(env))
|
|
43
|
+
return undefined;
|
|
44
|
+
const summarize = createEntitySummarizer(workspaceRoot);
|
|
45
|
+
const { ids, symbol } = firmRequirementsTouched({
|
|
46
|
+
symbols: linked.symbols,
|
|
47
|
+
focus,
|
|
48
|
+
wholeFile: focus === undefined,
|
|
49
|
+
summarize,
|
|
50
|
+
confirmed: settledFirmRequirements(session),
|
|
51
|
+
});
|
|
52
|
+
if (ids.length === 0)
|
|
53
|
+
return undefined;
|
|
54
|
+
settleFirmRequirements(session, ids);
|
|
55
|
+
return firmEditNotice(symbol ? `${symbol.title} in ${linked.relativePath}` : linked.relativePath, ids.map(summarize));
|
|
56
|
+
}
|
|
34
57
|
let cachedPackageVersion;
|
|
35
58
|
function packageVersion() {
|
|
36
59
|
// dist/hook-runner.js sits one level below the package root.
|
|
@@ -140,6 +163,10 @@ export async function runHook(rawInput, environment = {}) {
|
|
|
140
163
|
const kibiReady = hasKibiConfig(cwd);
|
|
141
164
|
const workspaceTrusted = environment.workspaceTrusted === true;
|
|
142
165
|
const initialState = loadHookState(stateDir);
|
|
166
|
+
const session = {
|
|
167
|
+
stateDir,
|
|
168
|
+
sessionId: input.conversationId,
|
|
169
|
+
};
|
|
143
170
|
switch (input.event) {
|
|
144
171
|
case "sessionStart":
|
|
145
172
|
if (!kibiReady) {
|
|
@@ -177,23 +204,38 @@ export async function runHook(rawInput, environment = {}) {
|
|
|
177
204
|
}
|
|
178
205
|
const relativePath = toRepoRelativePath(primaryPath, cwd);
|
|
179
206
|
const state = loadHookState(stateDir);
|
|
180
|
-
if (hasGuidedPath(state, "pre-edit", relativePath)) {
|
|
181
|
-
return emptyResult();
|
|
182
|
-
}
|
|
183
207
|
const linked = getSourceLinkedSymbols(cwd, primaryPath, stateDir);
|
|
184
|
-
const
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
208
|
+
const focus = editFocus(path.resolve(cwd, primaryPath), input.toolInput);
|
|
209
|
+
const ruleNote = takeRuleNote(session);
|
|
210
|
+
const notice = firmNotice(cwd, linked, focus, session, environment.env ?? process.env);
|
|
211
|
+
const guidance = hasGuidedPath(state, "pre-edit", relativePath)
|
|
212
|
+
? undefined
|
|
213
|
+
: preEditGuidance(primaryPath, {
|
|
214
|
+
cwd,
|
|
215
|
+
hasKibi: kibiReady,
|
|
216
|
+
mcpState: state.mcpState,
|
|
217
|
+
workspaceTrusted,
|
|
218
|
+
linkedRequirementIds: implementedRequirementIds(linked.symbols).slice(0, MAX_LINKED_REQUIREMENTS),
|
|
219
|
+
knowledge: knowledgeFor(cwd, linked, "edit", focus),
|
|
220
|
+
});
|
|
221
|
+
if (guidance) {
|
|
222
|
+
rememberGuidedPath(stateDir, "pre-edit", relativePath);
|
|
223
|
+
}
|
|
224
|
+
const parts = [ruleNote, notice, guidance].filter((part) => part !== undefined);
|
|
225
|
+
return parts.length > 0
|
|
226
|
+
? { permission: "allow", agent_message: parts.join("\n\n") }
|
|
227
|
+
: emptyResult();
|
|
228
|
+
}
|
|
229
|
+
// implements REQ-agent-hosts-decision-guard
|
|
230
|
+
case "beforeSubmitPrompt": {
|
|
231
|
+
// Cursor's prompt hook cannot add context: the request's rule note
|
|
232
|
+
// waits for the next pre-edit message.
|
|
233
|
+
if (kibiReady && input.prompt) {
|
|
234
|
+
const rule = recordRequest(session, input.prompt);
|
|
235
|
+
if (rule !== undefined)
|
|
236
|
+
holdRuleNote(session, rule);
|
|
194
237
|
}
|
|
195
|
-
|
|
196
|
-
return { permission: "allow", agent_message: guidance };
|
|
238
|
+
return { continue: true };
|
|
197
239
|
}
|
|
198
240
|
case "beforeReadFile": {
|
|
199
241
|
const primaryPath = resolvePrimaryPath(input);
|
|
@@ -249,6 +291,14 @@ export async function runHook(rawInput, environment = {}) {
|
|
|
249
291
|
if (dirtyPaths.length > 0) {
|
|
250
292
|
addDirtyPaths(stateDir, dirtyPaths);
|
|
251
293
|
}
|
|
294
|
+
if (dirtyPaths.some(isSourceImpactRelevantPath)) {
|
|
295
|
+
markDecisionFlag(session, "source-edited");
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
if (kbToolCall?.toolName === "kb_upsert" ||
|
|
299
|
+
kbUsageTrace(input.toolName, input.toolInput)?.kbOperation ===
|
|
300
|
+
"kb_upsert") {
|
|
301
|
+
markDecisionFlag(session, "kb-recorded");
|
|
252
302
|
}
|
|
253
303
|
const primaryPath = resolvePrimaryPath(input);
|
|
254
304
|
if (!primaryPath || !kibiReady) {
|
|
@@ -307,7 +357,17 @@ export async function runHook(rawInput, environment = {}) {
|
|
|
307
357
|
}
|
|
308
358
|
return emptyResult();
|
|
309
359
|
}
|
|
310
|
-
|
|
360
|
+
// implements REQ-agent-hosts-decision-guard
|
|
361
|
+
// Source changed and nothing was recorded: the follow-up asks for the
|
|
362
|
+
// decision to be recorded, once per conversation.
|
|
363
|
+
const followupMessage = [
|
|
364
|
+
claimCaptureReminder(session)
|
|
365
|
+
? captureReminder(hasDecisionFlag(session, "rule-prompt"))
|
|
366
|
+
: undefined,
|
|
367
|
+
stopFollowupMessage(state),
|
|
368
|
+
]
|
|
369
|
+
.filter((part) => part !== undefined)
|
|
370
|
+
.join("\n");
|
|
311
371
|
if (followupMessage) {
|
|
312
372
|
clearSessionHookState(stateDir);
|
|
313
373
|
return { followup_message: followupMessage };
|
package/dist/hooks/hooks.json
CHANGED
package/hooks/hooks.json
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "kibi-cursor",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.8.0",
|
|
4
4
|
"description": "Kibi Cursor plugin - thin adapter for Cursor MCP integration and editor hooks",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -59,7 +59,7 @@
|
|
|
59
59
|
}
|
|
60
60
|
},
|
|
61
61
|
"dependencies": {
|
|
62
|
-
"kibi-agent-core": "^0.
|
|
62
|
+
"kibi-agent-core": "^0.8.0"
|
|
63
63
|
},
|
|
64
64
|
"devDependencies": {
|
|
65
65
|
"@types/node": "latest",
|