kibi-cursor 2.10.0 → 2.11.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 +91 -0
- package/README.md +21 -3
- package/agent-plugin/plugin.json +1 -1
- package/agent-plugin/skills/kibi-commit-review/SKILL.md +6 -2
- package/agent-plugin/skills/kibi-decisions/SKILL.md +7 -2
- package/agent-plugin/skills/kibi-decisions/resources/signoff-views.md +3 -1
- package/agent-plugin/skills/kibi-usage/SKILL.md +14 -3
- package/agent-plugin/skills/kibi-usage/resources/prolog-proofs.md +207 -0
- package/agent-plugin/skills/kibi-usage/resources/proof.md +44 -3
- package/bin/hook-runner.mjs +3233 -0
- package/bin/launch-kibi-mcp.mjs +2 -1
- package/dist/.cursor-plugin/plugin.json +1 -1
- package/dist/bin/hook-runner.mjs +3233 -0
- package/dist/bin/launch-kibi-mcp.mjs +2 -1
- package/dist/hook-runner.js +245 -200
- package/dist/hooks/hooks.json +6 -6
- package/dist/skills/.canon-hash.json +6 -5
- package/dist/skills/kibi-commit-review/SKILL.md +6 -2
- package/dist/skills/kibi-decisions/SKILL.md +7 -2
- package/dist/skills/kibi-decisions/resources/signoff-views.md +3 -1
- package/dist/skills/kibi-usage/SKILL.md +14 -3
- package/dist/skills/kibi-usage/resources/prolog-proofs.md +207 -0
- package/dist/skills/kibi-usage/resources/proof.md +44 -3
- package/hooks/hooks.json +6 -6
- package/package.json +5 -3
- package/skills/.canon-hash.json +6 -5
- package/skills/kibi-commit-review/SKILL.md +6 -2
- package/skills/kibi-decisions/SKILL.md +7 -2
- package/skills/kibi-decisions/resources/signoff-views.md +3 -1
- package/skills/kibi-usage/SKILL.md +14 -3
- package/skills/kibi-usage/resources/prolog-proofs.md +207 -0
- package/skills/kibi-usage/resources/proof.md +44 -3
|
@@ -180,7 +180,8 @@ export function resolveProjectLocalMcp(workspaceRoot) {
|
|
|
180
180
|
if (!packageInfo) {
|
|
181
181
|
throw new Error(
|
|
182
182
|
`[KIBI-CURSOR] No project-local ${PACKAGE_NAME} package was found for ${root}. ` +
|
|
183
|
-
`Install it in that workspace (for example: npm install --save-dev ${PACKAGE_NAME}) and reload Cursor
|
|
183
|
+
`Install it in that workspace (for example: npm install --save-dev ${PACKAGE_NAME}) and reload Cursor. ` +
|
|
184
|
+
`The plugin launcher does not use a global ${PACKAGE_NAME}; to use one, add the manual MCP entry from the kibi-cursor README instead.`,
|
|
184
185
|
);
|
|
185
186
|
}
|
|
186
187
|
|
package/dist/hook-runner.js
CHANGED
|
@@ -4,8 +4,9 @@ import fs from "node:fs";
|
|
|
4
4
|
import path from "node:path";
|
|
5
5
|
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
6
6
|
import { captureReminder, claimCaptureReminder, claimSignoffReminder, confirmFirmEdits, firmEditNotice, firmRequirementsTouched, hasDecisionFlag, holdRuleNote, isRequirementUpsert, markDecisionFlag, recordRequest, settleFirmRequirements, settledFirmRequirements, signoffReminder, takeRuleNote, } from "kibi-agent-core/decision-guard";
|
|
7
|
-
import { appendHookUsageRows, editTraces,
|
|
7
|
+
import { appendHookUsage, appendHookUsageRows, editTraces, finishHookTelemetry, hookTracesWanted, kbUsageTrace, readPackageVersion, } from "kibi-agent-core/hook-usage-log";
|
|
8
8
|
import { stampKibiWorkspace } from "kibi-agent-core/kb-mcp-tools";
|
|
9
|
+
import { classifyPath } from "kibi-agent-core/path-policy";
|
|
9
10
|
import { createEntitySummarizer, editFocus, fileKnowledgeSnippet, implementedRequirementIds, } from "kibi-agent-core/snippets";
|
|
10
11
|
import { preEditGuidance, readGuidance, writeGuidance } from "./guidance.js";
|
|
11
12
|
import { parseHookInput, parseStdinJson, readStdin } from "./hook-input.js";
|
|
@@ -54,7 +55,8 @@ function firmNotice(workspaceRoot, linked, focus, session, env) {
|
|
|
54
55
|
return firmEditNotice(symbol ? `${symbol.title} in ${linked.relativePath}` : linked.relativePath, ids.map(summarize));
|
|
55
56
|
}
|
|
56
57
|
function packageVersion() {
|
|
57
|
-
// dist/hook-runner.js
|
|
58
|
+
// bin/hook-runner.mjs and dist/hook-runner.js sit one level below the
|
|
59
|
+
// package root. Each hook
|
|
58
60
|
// event is its own process, so the version is read at most once per event.
|
|
59
61
|
return readPackageVersion(path.join(path.dirname(fileURLToPath(import.meta.url)), "..", "package.json"));
|
|
60
62
|
}
|
|
@@ -68,7 +70,7 @@ function recordToolTelemetry(input, workspaceRoot, stateDir, startedAt, env) {
|
|
|
68
70
|
// rationale: appendHookUsageRows applies the same opt-in; this guard only
|
|
69
71
|
// skips reading the index when telemetry is off.
|
|
70
72
|
// Stryker disable next-line ConditionalExpression
|
|
71
|
-
if (!
|
|
73
|
+
if (!hookTracesWanted(env))
|
|
72
74
|
return;
|
|
73
75
|
const kbUsage = kbUsageTrace(input.toolName, input.toolInput);
|
|
74
76
|
if (!kbUsage && !isKnownEditableTool(input.toolName))
|
|
@@ -166,157 +168,100 @@ export async function runHook(rawInput, environment = {}) {
|
|
|
166
168
|
stateDir,
|
|
167
169
|
sessionId: input.conversationId,
|
|
168
170
|
};
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
return advisory ? { additional_context: advisory } : emptyResult();
|
|
182
|
-
}
|
|
183
|
-
case "preToolUse": {
|
|
184
|
-
// Name the agent's workspace on every Kibi MCP call so the launcher
|
|
185
|
-
// answers from it, also for agents working in a git worktree. The
|
|
186
|
-
// per-call cwd wins over workspace_roots, which can be the window's
|
|
187
|
-
// folders rather than the agent's checkout.
|
|
188
|
-
const stamped = stampKibiWorkspace(input.toolName, input.toolInput, kibiWorkspaceFor(input.cwd ?? input.workspaceRoots?.[0]));
|
|
189
|
-
if (stamped) {
|
|
190
|
-
return { updated_input: stamped };
|
|
191
|
-
}
|
|
192
|
-
const explicitPaths = extractExplicitPathFields(input.toolInput);
|
|
193
|
-
const hasDirectKbEdit = isEditLikeTool(input.toolName) && explicitPaths.some(isDirectKbPath);
|
|
194
|
-
if (hasDirectKbEdit) {
|
|
195
|
-
return {
|
|
196
|
-
permission: "allow",
|
|
197
|
-
agent_message: DIRECT_KB_EDIT_WARNING,
|
|
198
|
-
};
|
|
199
|
-
}
|
|
200
|
-
const primaryPath = resolvePrimaryPath(input);
|
|
201
|
-
if (!primaryPath || !kibiReady || !isKnownEditableTool(input.toolName)) {
|
|
202
|
-
return emptyResult();
|
|
203
|
-
}
|
|
204
|
-
const relativePath = toRepoRelativePath(primaryPath, cwd);
|
|
205
|
-
const state = loadHookState(stateDir);
|
|
206
|
-
const linked = getSourceLinkedSymbols(cwd, primaryPath, stateDir);
|
|
207
|
-
const focus = editFocus(path.resolve(cwd, primaryPath), input.toolInput);
|
|
208
|
-
const ruleNote = takeRuleNote(session);
|
|
209
|
-
const notice = firmNotice(cwd, linked, focus, session, environment.env ?? process.env);
|
|
210
|
-
const guidance = hasGuidedPath(state, "pre-edit", relativePath)
|
|
211
|
-
? undefined
|
|
212
|
-
: preEditGuidance(primaryPath, {
|
|
213
|
-
cwd,
|
|
214
|
-
hasKibi: kibiReady,
|
|
215
|
-
mcpState: state.mcpState,
|
|
216
|
-
workspaceTrusted,
|
|
217
|
-
// A file that implements requirements always gets the shared
|
|
218
|
-
// snippet, so the bare linked-requirement wording is never needed.
|
|
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);
|
|
237
|
-
}
|
|
238
|
-
return { continue: true };
|
|
239
|
-
}
|
|
240
|
-
case "beforeReadFile": {
|
|
241
|
-
const primaryPath = resolvePrimaryPath(input);
|
|
242
|
-
if (!primaryPath || !kibiReady) {
|
|
243
|
-
return { permission: "allow" };
|
|
244
|
-
}
|
|
245
|
-
const relativePath = toRepoRelativePath(primaryPath, cwd);
|
|
246
|
-
const state = loadHookState(stateDir);
|
|
247
|
-
if (hasGuidedPath(state, "read", relativePath)) {
|
|
248
|
-
return { permission: "allow" };
|
|
249
|
-
}
|
|
250
|
-
const guidance = readGuidance(primaryPath, {
|
|
251
|
-
cwd,
|
|
252
|
-
hasKibi: kibiReady,
|
|
253
|
-
mcpState: state.mcpState,
|
|
254
|
-
workspaceTrusted,
|
|
255
|
-
knowledge: knowledgeFor(cwd, getSourceLinkedSymbols(cwd, primaryPath, stateDir),
|
|
256
|
-
// rationale: the snippet only special-cases "edit", so any other
|
|
257
|
-
// surface literal renders the same read snippet.
|
|
258
|
-
// Stryker disable next-line StringLiteral
|
|
259
|
-
"read"),
|
|
260
|
-
});
|
|
261
|
-
if (!guidance) {
|
|
262
|
-
return { permission: "allow" };
|
|
263
|
-
}
|
|
264
|
-
rememberGuidedPath(stateDir, "read", relativePath);
|
|
265
|
-
return {
|
|
266
|
-
permission: "allow",
|
|
267
|
-
agent_message: guidance,
|
|
268
|
-
};
|
|
269
|
-
}
|
|
270
|
-
case "postToolUse": {
|
|
271
|
-
if (kibiReady) {
|
|
272
|
-
try {
|
|
273
|
-
recordToolTelemetry(input, cwd, stateDir, startedAt, environment.env);
|
|
171
|
+
const trace = {};
|
|
172
|
+
try {
|
|
173
|
+
switch (input.event) {
|
|
174
|
+
case "sessionStart":
|
|
175
|
+
trace.action = "session_start";
|
|
176
|
+
if (!kibiReady) {
|
|
177
|
+
const advisory = interfaceAdvisory(initialState.mcpState, workspaceTrusted);
|
|
178
|
+
return {
|
|
179
|
+
additional_context: advisory
|
|
180
|
+
? `${BOOTSTRAP_REMINDER}\n${advisory}`
|
|
181
|
+
: BOOTSTRAP_REMINDER,
|
|
182
|
+
};
|
|
274
183
|
}
|
|
275
|
-
|
|
276
|
-
|
|
184
|
+
{
|
|
185
|
+
const advisory = interfaceAdvisory(initialState.mcpState, workspaceTrusted);
|
|
186
|
+
return advisory ? { additional_context: advisory } : emptyResult();
|
|
277
187
|
}
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
if (isPlanDeliveryTool(input.toolName)) {
|
|
287
|
-
recordPlanDelivered(stateDir);
|
|
288
|
-
}
|
|
289
|
-
const explicitPaths = extractExplicitPathFields(input.toolInput);
|
|
290
|
-
if (isKnownEditableTool(input.toolName)) {
|
|
291
|
-
const dirtyPaths = explicitPaths
|
|
292
|
-
.map((candidate) => toRepoRelativePath(candidate, cwd))
|
|
293
|
-
.filter((candidate) => isMeaningfulTrackedPath(candidate) ||
|
|
294
|
-
isKbFreshnessRelevantPath(candidate));
|
|
295
|
-
if (dirtyPaths.length > 0) {
|
|
296
|
-
addDirtyPaths(stateDir, dirtyPaths);
|
|
188
|
+
case "preToolUse": {
|
|
189
|
+
// Name the agent's workspace on every Kibi MCP call so the launcher
|
|
190
|
+
// answers from it, also for agents working in a git worktree. The
|
|
191
|
+
// per-call cwd wins over workspace_roots, which can be the window's
|
|
192
|
+
// folders rather than the agent's checkout.
|
|
193
|
+
const stamped = stampKibiWorkspace(input.toolName, input.toolInput, kibiWorkspaceFor(input.cwd ?? input.workspaceRoots?.[0]));
|
|
194
|
+
if (stamped) {
|
|
195
|
+
return { updated_input: stamped };
|
|
297
196
|
}
|
|
298
|
-
|
|
299
|
-
|
|
197
|
+
const explicitPaths = extractExplicitPathFields(input.toolInput);
|
|
198
|
+
const hasDirectKbEdit = isEditLikeTool(input.toolName) && explicitPaths.some(isDirectKbPath);
|
|
199
|
+
if (hasDirectKbEdit) {
|
|
200
|
+
trace.action = "kb_direct_note";
|
|
201
|
+
trace.pathKind = "kb";
|
|
202
|
+
return {
|
|
203
|
+
permission: "allow",
|
|
204
|
+
agent_message: DIRECT_KB_EDIT_WARNING,
|
|
205
|
+
};
|
|
300
206
|
}
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
// implements REQ-kibi-signoff-views
|
|
307
|
-
if (isRequirementUpsert(input.toolInput)) {
|
|
308
|
-
markDecisionFlag(session, "requirement-recorded");
|
|
207
|
+
const primaryPath = resolvePrimaryPath(input);
|
|
208
|
+
if (!primaryPath ||
|
|
209
|
+
!kibiReady ||
|
|
210
|
+
!isKnownEditableTool(input.toolName)) {
|
|
211
|
+
return emptyResult();
|
|
309
212
|
}
|
|
213
|
+
const relativePath = toRepoRelativePath(primaryPath, cwd);
|
|
214
|
+
const state = loadHookState(stateDir);
|
|
215
|
+
const linked = getSourceLinkedSymbols(cwd, primaryPath, stateDir);
|
|
216
|
+
trace.pathKind = classifyPath(relativePath);
|
|
217
|
+
trace.requirementIds = implementedRequirementIds(linked.symbols);
|
|
218
|
+
const focus = editFocus(path.resolve(cwd, primaryPath), input.toolInput);
|
|
219
|
+
const ruleNote = takeRuleNote(session);
|
|
220
|
+
const notice = firmNotice(cwd, linked, focus, session, environment.env ?? process.env);
|
|
221
|
+
const guidance = hasGuidedPath(state, "pre-edit", relativePath)
|
|
222
|
+
? undefined
|
|
223
|
+
: preEditGuidance(primaryPath, {
|
|
224
|
+
cwd,
|
|
225
|
+
hasKibi: kibiReady,
|
|
226
|
+
mcpState: state.mcpState,
|
|
227
|
+
workspaceTrusted,
|
|
228
|
+
// A file that implements requirements always gets the shared
|
|
229
|
+
// snippet, so the bare linked-requirement wording is never needed.
|
|
230
|
+
knowledge: knowledgeFor(cwd, linked, "edit", focus),
|
|
231
|
+
});
|
|
232
|
+
if (guidance) {
|
|
233
|
+
rememberGuidedPath(stateDir, "pre-edit", relativePath);
|
|
234
|
+
}
|
|
235
|
+
const parts = [ruleNote, notice, guidance].filter((part) => part !== undefined);
|
|
236
|
+
trace.action = parts.length > 0 ? "edit_snippet" : "edit_silent";
|
|
237
|
+
return parts.length > 0
|
|
238
|
+
? { permission: "allow", agent_message: parts.join("\n\n") }
|
|
239
|
+
: emptyResult();
|
|
310
240
|
}
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
241
|
+
// implements REQ-agent-hosts-decision-guard
|
|
242
|
+
case "beforeSubmitPrompt": {
|
|
243
|
+
// Cursor's prompt hook cannot add context: the request's rule note
|
|
244
|
+
// waits for the next pre-edit message.
|
|
245
|
+
if (kibiReady && input.prompt) {
|
|
246
|
+
const rule = recordRequest(session, input.prompt);
|
|
247
|
+
if (rule !== undefined)
|
|
248
|
+
holdRuleNote(session, rule);
|
|
249
|
+
trace.action =
|
|
250
|
+
rule === undefined ? "prompt_silent" : "prompt_rule_note";
|
|
251
|
+
}
|
|
252
|
+
return { continue: true };
|
|
314
253
|
}
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
254
|
+
case "beforeReadFile": {
|
|
255
|
+
const primaryPath = resolvePrimaryPath(input);
|
|
256
|
+
if (!primaryPath || !kibiReady) {
|
|
257
|
+
return { permission: "allow" };
|
|
258
|
+
}
|
|
259
|
+
const relativePath = toRepoRelativePath(primaryPath, cwd);
|
|
260
|
+
trace.pathKind = classifyPath(relativePath);
|
|
261
|
+
trace.action = "read_silent";
|
|
262
|
+
const state = loadHookState(stateDir);
|
|
318
263
|
if (hasGuidedPath(state, "read", relativePath)) {
|
|
319
|
-
return
|
|
264
|
+
return { permission: "allow" };
|
|
320
265
|
}
|
|
321
266
|
const guidance = readGuidance(primaryPath, {
|
|
322
267
|
cwd,
|
|
@@ -324,77 +269,177 @@ export async function runHook(rawInput, environment = {}) {
|
|
|
324
269
|
mcpState: state.mcpState,
|
|
325
270
|
workspaceTrusted,
|
|
326
271
|
knowledge: knowledgeFor(cwd, getSourceLinkedSymbols(cwd, primaryPath, stateDir),
|
|
327
|
-
// rationale:
|
|
272
|
+
// rationale: the snippet only special-cases "edit", so any other
|
|
273
|
+
// surface literal renders the same read snippet.
|
|
328
274
|
// Stryker disable next-line StringLiteral
|
|
329
275
|
"read"),
|
|
330
276
|
});
|
|
331
277
|
if (!guidance) {
|
|
332
|
-
return
|
|
278
|
+
return { permission: "allow" };
|
|
333
279
|
}
|
|
334
280
|
rememberGuidedPath(stateDir, "read", relativePath);
|
|
335
|
-
|
|
281
|
+
trace.action = "read_snippet";
|
|
282
|
+
return {
|
|
283
|
+
permission: "allow",
|
|
284
|
+
agent_message: guidance,
|
|
285
|
+
};
|
|
336
286
|
}
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
287
|
+
case "postToolUse": {
|
|
288
|
+
if (kibiReady) {
|
|
289
|
+
try {
|
|
290
|
+
recordToolTelemetry(input, cwd, stateDir, startedAt, environment.env);
|
|
291
|
+
}
|
|
292
|
+
catch {
|
|
293
|
+
// Telemetry is best effort and must never change the hook's output.
|
|
294
|
+
}
|
|
343
295
|
}
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
}
|
|
351
|
-
if (
|
|
296
|
+
const kbToolCall = extractKbMcpToolCall(input.toolName, input.toolInput);
|
|
297
|
+
if (kbToolCall) {
|
|
298
|
+
recordKbMcpTool(stateDir, kbToolCall.toolName, {
|
|
299
|
+
impactCheckRun: kbToolCall.impactCheckRun,
|
|
300
|
+
sourceFiles: kbToolCall.sourceFiles,
|
|
301
|
+
});
|
|
302
|
+
}
|
|
303
|
+
if (isPlanDeliveryTool(input.toolName)) {
|
|
304
|
+
recordPlanDelivered(stateDir);
|
|
305
|
+
}
|
|
306
|
+
const explicitPaths = extractExplicitPathFields(input.toolInput);
|
|
307
|
+
if (isKnownEditableTool(input.toolName)) {
|
|
308
|
+
const dirtyPaths = explicitPaths
|
|
309
|
+
.map((candidate) => toRepoRelativePath(candidate, cwd))
|
|
310
|
+
.filter((candidate) => isMeaningfulTrackedPath(candidate) ||
|
|
311
|
+
isKbFreshnessRelevantPath(candidate));
|
|
312
|
+
if (dirtyPaths.length > 0) {
|
|
313
|
+
addDirtyPaths(stateDir, dirtyPaths);
|
|
314
|
+
}
|
|
315
|
+
if (dirtyPaths.some(isSourceImpactRelevantPath)) {
|
|
316
|
+
markDecisionFlag(session, "source-edited");
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
if (kbToolCall?.toolName === "kb_upsert" ||
|
|
320
|
+
kbUsageTrace(input.toolName, input.toolInput)?.kbOperation ===
|
|
321
|
+
"kb_upsert") {
|
|
322
|
+
markDecisionFlag(session, "kb-recorded");
|
|
323
|
+
// implements REQ-kibi-signoff-views
|
|
324
|
+
if (isRequirementUpsert(input.toolInput)) {
|
|
325
|
+
markDecisionFlag(session, "requirement-recorded");
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
const primaryPath = resolvePrimaryPath(input);
|
|
329
|
+
if (!primaryPath || !kibiReady) {
|
|
352
330
|
return emptyResult();
|
|
353
331
|
}
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
332
|
+
const relativePath = toRepoRelativePath(primaryPath, cwd);
|
|
333
|
+
const state = loadHookState(stateDir);
|
|
334
|
+
if (isReadLikeTool(input.toolName)) {
|
|
335
|
+
if (hasGuidedPath(state, "read", relativePath)) {
|
|
336
|
+
return emptyResult();
|
|
337
|
+
}
|
|
338
|
+
const guidance = readGuidance(primaryPath, {
|
|
339
|
+
cwd,
|
|
340
|
+
hasKibi: kibiReady,
|
|
341
|
+
mcpState: state.mcpState,
|
|
342
|
+
workspaceTrusted,
|
|
343
|
+
knowledge: knowledgeFor(cwd, getSourceLinkedSymbols(cwd, primaryPath, stateDir),
|
|
344
|
+
// rationale: as above, only "edit" changes the snippet.
|
|
345
|
+
// Stryker disable next-line StringLiteral
|
|
346
|
+
"read"),
|
|
347
|
+
});
|
|
348
|
+
if (!guidance) {
|
|
349
|
+
return emptyResult();
|
|
350
|
+
}
|
|
351
|
+
rememberGuidedPath(stateDir, "read", relativePath);
|
|
352
|
+
return { additional_context: guidance };
|
|
353
|
+
}
|
|
354
|
+
if (isEditLikeTool(input.toolName)) {
|
|
355
|
+
// rationale: the runner only ever passes "read" or "write"; an empty
|
|
356
|
+
// kind selects the same write bucket.
|
|
357
|
+
// Stryker disable StringLiteral
|
|
358
|
+
if (hasGuidedPath(state, "write", relativePath)) {
|
|
359
|
+
return emptyResult();
|
|
360
|
+
}
|
|
361
|
+
// Stryker restore
|
|
362
|
+
const guidance = writeGuidance(primaryPath, {
|
|
363
|
+
cwd,
|
|
364
|
+
hasKibi: kibiReady,
|
|
365
|
+
mcpState: state.mcpState,
|
|
366
|
+
workspaceTrusted,
|
|
367
|
+
});
|
|
368
|
+
if (!guidance) {
|
|
369
|
+
return emptyResult();
|
|
370
|
+
}
|
|
371
|
+
// rationale: same bucket equivalence as the lookup above.
|
|
372
|
+
// Stryker disable StringLiteral
|
|
373
|
+
rememberGuidedPath(stateDir, "write", relativePath);
|
|
374
|
+
// Stryker restore
|
|
375
|
+
return { additional_context: guidance };
|
|
376
|
+
}
|
|
377
|
+
return emptyResult();
|
|
359
378
|
}
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
379
|
+
case "stop": {
|
|
380
|
+
const state = loadHookState(stateDir);
|
|
381
|
+
const shouldClearSession = state.dirtyPaths.length > 0 ||
|
|
382
|
+
state.kbCheckRun ||
|
|
383
|
+
state.planDelivered;
|
|
384
|
+
if (input.status === "aborted" || input.status === "error") {
|
|
385
|
+
if (shouldClearSession) {
|
|
386
|
+
clearSessionHookState(stateDir);
|
|
387
|
+
}
|
|
388
|
+
return emptyResult();
|
|
389
|
+
}
|
|
390
|
+
// implements REQ-agent-hosts-decision-guard
|
|
391
|
+
// Source changed and nothing was recorded: the follow-up asks for the
|
|
392
|
+
// decision to be recorded, once per conversation.
|
|
393
|
+
const capture = claimCaptureReminder(session)
|
|
394
|
+
? captureReminder(hasDecisionFlag(session, "rule-prompt"))
|
|
395
|
+
: undefined;
|
|
396
|
+
// implements REQ-kibi-signoff-views
|
|
397
|
+
// Requirements were written without a signoff view since: ask once.
|
|
398
|
+
// A recorded requirement also counts as recorded knowledge, so this
|
|
399
|
+
// never coincides with the capture request above.
|
|
400
|
+
const signoff = claimSignoffReminder(session, cwd)
|
|
401
|
+
? signoffReminder()
|
|
402
|
+
: undefined;
|
|
403
|
+
const reminder = stopFollowupMessage(state);
|
|
404
|
+
const followupMessage = [capture, signoff, reminder]
|
|
405
|
+
.filter((part) => part !== undefined)
|
|
406
|
+
.join("\n");
|
|
407
|
+
if (followupMessage) {
|
|
408
|
+
trace.action = capture
|
|
409
|
+
? "stop_capture_reminder"
|
|
410
|
+
: signoff
|
|
411
|
+
? "stop_signoff_reminder"
|
|
412
|
+
: "stop_reminder";
|
|
413
|
+
clearSessionHookState(stateDir);
|
|
414
|
+
return { followup_message: followupMessage };
|
|
415
|
+
}
|
|
366
416
|
if (shouldClearSession) {
|
|
367
417
|
clearSessionHookState(stateDir);
|
|
368
418
|
}
|
|
369
419
|
return emptyResult();
|
|
370
420
|
}
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
:
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
if (followupMessage) {
|
|
388
|
-
clearSessionHookState(stateDir);
|
|
389
|
-
return { followup_message: followupMessage };
|
|
390
|
-
}
|
|
391
|
-
if (shouldClearSession) {
|
|
392
|
-
clearSessionHookState(stateDir);
|
|
393
|
-
}
|
|
394
|
-
return emptyResult();
|
|
421
|
+
default:
|
|
422
|
+
return emptyResult();
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
finally {
|
|
426
|
+
if (kibiReady) {
|
|
427
|
+
appendHookUsage({
|
|
428
|
+
host: "cursor",
|
|
429
|
+
packageVersion: packageVersion(),
|
|
430
|
+
workspaceRoot: cwd,
|
|
431
|
+
event: input.event,
|
|
432
|
+
sessionId: input.conversationId,
|
|
433
|
+
hostTool: input.toolName,
|
|
434
|
+
trace,
|
|
435
|
+
startedAt,
|
|
436
|
+
}, environment.env ?? process.env);
|
|
395
437
|
}
|
|
396
|
-
|
|
397
|
-
|
|
438
|
+
await finishHookTelemetry({
|
|
439
|
+
event: input.event,
|
|
440
|
+
workspaceRoot: kibiReady ? cwd : undefined,
|
|
441
|
+
sessionId: input.conversationId,
|
|
442
|
+
}, environment.env ?? process.env);
|
|
398
443
|
}
|
|
399
444
|
}
|
|
400
445
|
export async function main() {
|
package/dist/hooks/hooks.json
CHANGED
|
@@ -3,32 +3,32 @@
|
|
|
3
3
|
"hooks": {
|
|
4
4
|
"sessionStart": [
|
|
5
5
|
{
|
|
6
|
-
"command": "node
|
|
6
|
+
"command": "node bin/hook-runner.mjs"
|
|
7
7
|
}
|
|
8
8
|
],
|
|
9
9
|
"beforeSubmitPrompt": [
|
|
10
10
|
{
|
|
11
|
-
"command": "node
|
|
11
|
+
"command": "node bin/hook-runner.mjs"
|
|
12
12
|
}
|
|
13
13
|
],
|
|
14
14
|
"preToolUse": [
|
|
15
15
|
{
|
|
16
|
-
"command": "node
|
|
16
|
+
"command": "node bin/hook-runner.mjs"
|
|
17
17
|
}
|
|
18
18
|
],
|
|
19
19
|
"postToolUse": [
|
|
20
20
|
{
|
|
21
|
-
"command": "node
|
|
21
|
+
"command": "node bin/hook-runner.mjs"
|
|
22
22
|
}
|
|
23
23
|
],
|
|
24
24
|
"beforeReadFile": [
|
|
25
25
|
{
|
|
26
|
-
"command": "node
|
|
26
|
+
"command": "node bin/hook-runner.mjs"
|
|
27
27
|
}
|
|
28
28
|
],
|
|
29
29
|
"stop": [
|
|
30
30
|
{
|
|
31
|
-
"command": "node
|
|
31
|
+
"command": "node bin/hook-runner.mjs",
|
|
32
32
|
"loop_limit": 1
|
|
33
33
|
}
|
|
34
34
|
]
|
|
@@ -4,11 +4,11 @@
|
|
|
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-commit-review/SKILL.md": "
|
|
7
|
+
"kibi-commit-review/SKILL.md": "bec768ad3e4cd4e782e9e13360b69bcd40a0964b305909818115d7f9e8d282d9",
|
|
8
8
|
"kibi-commit-review/resources/staged-gate.md": "37f750f327bb9202c1f30989763f4f1205b906cf672318ca50887d7984bc3e08",
|
|
9
|
-
"kibi-decisions/SKILL.md": "
|
|
9
|
+
"kibi-decisions/SKILL.md": "d91f784410251095e9d881bce751728cc4596858a518fb24f5e5d38c940bab50",
|
|
10
10
|
"kibi-decisions/resources/decision-records.md": "b201e619c48daf6ebe5989991a96f6c0573adb6c37abf814b9d34b7a1cd423c6",
|
|
11
|
-
"kibi-decisions/resources/signoff-views.md": "
|
|
11
|
+
"kibi-decisions/resources/signoff-views.md": "84eb2b7123e54047205b84af6fbf60c910ebcaaf98023e5812f3de9b65a4871c",
|
|
12
12
|
"kibi-freshness/SKILL.md": "b5c4f24bebdb5b4e0d04c2fa1176fc33ace35334b0c0b6ca943d6f445dc9b69e",
|
|
13
13
|
"kibi-freshness/resources/branch-lifecycle.md": "ebc01b19fe3dca8df5fc0175978963f6f1204f71f7c805cb2a5fb1b277ac78e4",
|
|
14
14
|
"kibi-freshness/resources/operation-access.md": "4e1db9294ee540cd29b8b3a2532a81e7894d5b4228b39e99ba48b37212f489f3",
|
|
@@ -18,13 +18,14 @@
|
|
|
18
18
|
"kibi-traceability/resources/operation-access.md": "39f7972ab7f4ae7479e78f7072bb50e8e6c4cdf4f857425f69d2522d37db437c",
|
|
19
19
|
"kibi-traceability/resources/source-authoring.md": "408c6ee2bef1b84e0194c0538ef929c26a0a617e631b2234247388140dbbdd08",
|
|
20
20
|
"kibi-traceability/resources/traceability.md": "38c290a0bc71bd1692e115f22694743946e2f69630e134acfd43cee5f613fb88",
|
|
21
|
-
"kibi-usage/SKILL.md": "
|
|
21
|
+
"kibi-usage/SKILL.md": "91639d49eae60a83b01017de1843ce49575db231f3f9cb1b0270c57a20462a88",
|
|
22
22
|
"kibi-usage/resources/branch-lifecycle.md": "f1bf359e9826e83c548f3cd0287f8175a62456527b0132b06b3f9f74aa59b7d6",
|
|
23
23
|
"kibi-usage/resources/fact-lanes.md": "7d487ffb794f6a7c32afabc7756a265a258b072ca83510ddb40d275863d44648",
|
|
24
24
|
"kibi-usage/resources/kb-improvement.md": "a8408fce27342bdce6415a6718599e53fe00bb33aa1fd3dc42353ec6f61adcd7",
|
|
25
25
|
"kibi-usage/resources/logic-ir.md": "0e8ce8aad07433ab4803ec169149ff6c4e1cf8cf010af18fa6fd67862d9be807",
|
|
26
26
|
"kibi-usage/resources/operation-access.md": "eb455796c066787ffd3fae8db0db1d3e87a84265a57f06ae61e30103cd6aed66",
|
|
27
|
-
"kibi-usage/resources/
|
|
27
|
+
"kibi-usage/resources/prolog-proofs.md": "b630f1edf32c4d14dbd096deb15c370f6ce05dede8aa9cda4fbf294d8668f164",
|
|
28
|
+
"kibi-usage/resources/proof.md": "fe5c0ed8117ec724f925c29a95345e2f7b9969a413a3ba56eece23273f91346e",
|
|
28
29
|
"kibi-usage/resources/relationship-directions.md": "0fdd67032c2d15299f772845fc4f807eb9435b09507bd87a5911ab33db2f143d",
|
|
29
30
|
"kibi-usage/resources/source-authoring.md": "cad93f1e0ae08a676f2de7492f9227677880302327e74d159618fea86ca50abe",
|
|
30
31
|
"kibi-usage/resources/ui-requirements.md": "f99c2a9a73bbb7bd92ce1f231db3056d544588be149a5b0d3f7cc6f28bc9eee9",
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
id: kibi-commit-review
|
|
3
3
|
name: kibi-commit-review
|
|
4
4
|
description: "Use when kibi check --staged or the Kibi pre-commit hook blocks a commit (kibi_impact_evidence_missing, symbols_manifest_stale, symbol_granularity_violation, symbol-coverage, kibi check-generated drift, Staged impact review failed), or when preparing a commit or pull-request impact review. Covers reading the per-file Detail lines, authoring .kb/symbols.yaml symbol entries through kb_upsert, staging the tracked Kibi evidence with git add (symbol coordinates are derived and never staged), kb_prepare_impact_review for a staged or diff scope, the kibi signoff requirements summary to show before a commit, and how merge commits and migration renames are judged."
|
|
5
|
-
version: 1.
|
|
5
|
+
version: 1.4.0
|
|
6
6
|
kibiCompatibility: ">=1.0.0"
|
|
7
7
|
tags:
|
|
8
8
|
- kibi
|
|
@@ -91,7 +91,11 @@ apps); never attach the `.html` as a file, which chat apps show as raw source;
|
|
|
91
91
|
past 15 requirements the Markdown stops and only the page lists them all.
|
|
92
92
|
Host details: `kibi-decisions`, `resources/signoff-views.md`. `--no-proof` skips the proof lookup, which reads coverage and is slow
|
|
93
93
|
on a large knowledge base. The summary is not an approval: never set
|
|
94
|
-
`approved_by` or `origin.ref` from it.
|
|
94
|
+
`approved_by` or `origin.ref` from it. Show it before you open or merge the
|
|
95
|
+
pull request. When the person answers, record their answer in the same
|
|
96
|
+
change (their name in `approved_by`, their words in `origin.ref` or
|
|
97
|
+
`approval_ref`) and push it to the same pull request; never move a signoff,
|
|
98
|
+
firmness or waiver approval into a separate pull request.
|
|
95
99
|
|
|
96
100
|
## Impact reviews
|
|
97
101
|
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
id: kibi-decisions
|
|
3
3
|
name: kibi-decisions
|
|
4
4
|
description: "Use when the person states a rule or decision (must, never, always, from now on, we decided), when a Kibi hook asks to confirm or warns about an edit to code that implements a firm requirement, when the Stop hook says source files changed but nothing was recorded, when kb_check quotes requirement_statement_unverified, or when a request conflicts with a recorded requirement or ADR. Covers reading firmness and origin, recording a person's rule as firm with origin.ref, superseding with a Why this changed section, the kb_compile_intent unresolved/reviewCandidates/conflictReviews loop, asking before overriding a firm rule, and putting decisions to the person as one numbered kibi signoff --questions view."
|
|
5
|
-
version: 1.
|
|
5
|
+
version: 1.5.0
|
|
6
6
|
kibiCompatibility: ">=1.0.0"
|
|
7
7
|
tags:
|
|
8
8
|
- kibi
|
|
@@ -115,7 +115,12 @@ again until none is open, then record each rule with their words in
|
|
|
115
115
|
`origin.ref`. Fields and outputs: `resources/signoff-views.md`.
|
|
116
116
|
|
|
117
117
|
Never invent words for `origin.ref`, never set `approved_by` yourself, and
|
|
118
|
-
mark your own picks `default` and guesses `exploratory`.
|
|
118
|
+
mark your own picks `default` and guesses `exploratory`. Once the person has
|
|
119
|
+
answered, recording their answer (their name in `approved_by` or a
|
|
120
|
+
`*_waiver_approved_by` field, their words in `origin.ref` or `approval_ref`)
|
|
121
|
+
is yours to do: put it on the branch of the change it approves and push it to
|
|
122
|
+
that pull request, never to a separate one. Ask before you open or merge the
|
|
123
|
+
pull request, so the answer lands in it. Payload shapes,
|
|
119
124
|
refusal messages and the `kb_compile_intent` review loop are in
|
|
120
125
|
`resources/decision-records.md`.
|
|
121
126
|
|