kibi-cursor 2.0.0 → 2.0.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 +1 -1
- package/CHANGELOG.md +23 -0
- package/agent-plugin/plugin.json +1 -1
- package/agent-plugin/skills/kibi-usage/SKILL.md +4 -1
- package/agent-plugin/skills/kibi-usage/resources/proof.md +45 -0
- package/dist/.cursor-plugin/plugin.json +1 -1
- package/dist/hook-input.js +7 -4
- package/dist/hook-runner.js +13 -4
- package/dist/hook-state-storage.js +52 -2
- package/dist/hook-state.js +14 -4
- package/dist/kb-mcp-tools.js +20 -0
- package/dist/messages.js +3 -8
- package/dist/path-policy.js +11 -0
- package/dist/skills/.canon-hash.json +2 -2
- package/dist/skills/kibi-usage/SKILL.md +4 -1
- package/dist/skills/kibi-usage/resources/proof.md +45 -0
- package/package.json +1 -1
- package/skills/.canon-hash.json +2 -2
- package/skills/kibi-usage/SKILL.md +4 -1
- package/skills/kibi-usage/resources/proof.md +45 -0
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "kibi-cursor",
|
|
3
3
|
"displayName": "Kibi",
|
|
4
|
-
"version": "2.0.
|
|
4
|
+
"version": "2.0.1",
|
|
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": {
|
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,28 @@
|
|
|
1
1
|
# kibi-cursor
|
|
2
2
|
|
|
3
|
+
## 2.0.1
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- 96a1ebd: Local Cursor MCP builds now compile plugin SDK/builtin dist before the CLI, so a fresh checkout no longer fails with a missing `kibi-plugin-builtin` type package.
|
|
8
|
+
|
|
9
|
+
- `build:cli-stack` runs plugin-sdk + plugin-builtin + CLI
|
|
10
|
+
- Worktree resolver uses that stack instead of `build:cli` alone
|
|
11
|
+
|
|
12
|
+
- 188f875: No user-facing behavior change: `kibi-runtime` now resolves its bundled-skills directory lazily on first use instead of at import time, `isWithinRoot` drops a redundant same-path comparison, `kibi-cursor` drops a provably dead `planDelivered` early return and an always-true stdin branch, and the hook runners' stdin buffering is simplified to an unconditional `Buffer.from`. The reshaping lets the new mutation-testing suite (`docs/mutation-testing.md`, run via `bun run test:mutation`) prove these paths exhaustively — the suite holds a 100% mutation score over `kibi-runtime`, `kibi-codex`, and `kibi-cursor` sources.
|
|
13
|
+
- db07d8f: Proof diagnostics now agree with the Prolog decision: a structural type-shape unit contract is shown as qualifying, `kibi proof impact` compares against the Git HEAD baseline and exits 0 after a successful report, and mixed-role symbols fail the strict proof integrity gate.
|
|
14
|
+
|
|
15
|
+
- Mode-aware candidate evaluation in Prolog; receipt fallback uses `test_receipt_evidence(Context, TestId, Evidence)`.
|
|
16
|
+
- `proof impact` reads `HEAD:proof/baseline.json` with no worktree fallback; diagnostic exit 0.
|
|
17
|
+
- Strict proof workflow and baseline checker include canonical `symbol-traceability`.
|
|
18
|
+
|
|
19
|
+
- f33a665: Proof failures now name the exact requirement, symbol, and why each `covered_by` candidate did not qualify, without changing what counts as proven. Agents can inspect a requirement with `kibi proof explain` and compare current proof state to the committed `proof/baseline.json` snapshot with `kibi proof impact`, instead of reverse-engineering Prolog or guessing from aggregate counts.
|
|
20
|
+
|
|
21
|
+
- Keep `kibi.requirement-proof.v3` and add additive production-symbol `explanations` plus TEST `testResolutions` on the same Proof.
|
|
22
|
+
- Ratchet `proof/baseline.json` to v2 with compact requirement fingerprints; aggregate counts stay the ratchet.
|
|
23
|
+
- Add `kibi proof explain` and `kibi proof impact` as Proof projections, mixed-role leftovers in `symbol-traceability`, and advisory `proof-contract-symbols`.
|
|
24
|
+
- Document the proof-regression workflow in `kibi-usage` 2.1.3.
|
|
25
|
+
|
|
3
26
|
## 2.0.0
|
|
4
27
|
|
|
5
28
|
### Major Changes
|
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.0.
|
|
4
|
+
"version": "2.0.1",
|
|
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://github.com/Looted/kibi/blob/develop/docs/install.md",
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
id: kibi-usage
|
|
3
3
|
name: Kibi Usage
|
|
4
4
|
description: Use Kibi's source-first, exact-Git, migration-aware, proof-aware operations safely across MCP or the trusted local CLI, including partial completion repair.
|
|
5
|
-
version: 2.1.
|
|
5
|
+
version: 2.1.3
|
|
6
6
|
kibiCompatibility: ">=1.0.0"
|
|
7
7
|
tags:
|
|
8
8
|
- kibi
|
|
@@ -133,6 +133,9 @@ safe normative claims; use `observation` or `meta` for bug/workaround notes and
|
|
|
133
133
|
tests, symbol ownership, and fresh proof-bearing receipts before claiming proof.
|
|
134
134
|
For an existing product KB that needs semantic backfill, read
|
|
135
135
|
`resources/kb-improvement.md`, and `resources/proof.md` for the proof workflow.
|
|
136
|
+
When a proof ratchet fails, follow Debugging proof regressions in
|
|
137
|
+
`resources/proof.md`: use `kibi proof explain` and compare current proof state
|
|
138
|
+
to committed `proof/baseline.json` with `kibi proof impact`.
|
|
136
139
|
|
|
137
140
|
## Predicate Ontology Decision Tree
|
|
138
141
|
|
|
@@ -103,3 +103,48 @@ re-run `kibi prove` for the affected selection, then confirm `kb_status` and
|
|
|
103
103
|
`kb_coverage` before handoff.
|
|
104
104
|
|
|
105
105
|
Full reference: `docs/proving-requirements.md`.
|
|
106
|
+
|
|
107
|
+
## Debugging proof regressions
|
|
108
|
+
|
|
109
|
+
When a proof ratchet fails, diagnose first. Do not treat
|
|
110
|
+
`proof/baseline.json` as the thing to edit.
|
|
111
|
+
|
|
112
|
+
1. Do not refresh `proof/baseline.json` as the first fix.
|
|
113
|
+
2. Identify the exact requirement and symbol responsible for the regression.
|
|
114
|
+
3. Use Kibi proof diagnostics rather than reverse-engineering
|
|
115
|
+
`requirement_proof.pl`:
|
|
116
|
+
|
|
117
|
+
```bash
|
|
118
|
+
kibi proof explain REQ-<id>
|
|
119
|
+
kibi proof explain SYM-<id>
|
|
120
|
+
kibi proof impact
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
`kibi proof impact` compares the current Proof projection to
|
|
124
|
+
`HEAD:proof/baseline.json` (the committed ratchet snapshot, not the
|
|
125
|
+
working-tree file). It is diagnostic: fingerprint differences are reported
|
|
126
|
+
and the command exits 0 on successful evaluation. The strict ratchet remains
|
|
127
|
+
`scripts/check-proof-baseline.mjs`.
|
|
128
|
+
4. For `missing_production_symbol_coverage`, inspect symbol role, owning
|
|
129
|
+
requirement, `covered_by`, candidate test scope, the requirement scenario
|
|
130
|
+
chain, and the current proof receipt. `kibi proof explain` prints those as
|
|
131
|
+
three labeled blocks so they cannot be confused:
|
|
132
|
+
`required_proofs` (TEST contract obligations), `executable_for` (test-code
|
|
133
|
+
identity), and `covered_by` (production coverage candidates with primary
|
|
134
|
+
plus optional secondary reasons).
|
|
135
|
+
5. A behavioral production symbol requires qualifying behavioral/E2E evidence
|
|
136
|
+
under the current proof model.
|
|
137
|
+
6. Unit coverage can satisfy structural/type-shape requirements only when the
|
|
138
|
+
symbol genuinely represents structural shape and the proof model allows it.
|
|
139
|
+
7. `executable_for` identifies executable test code. Never use it on
|
|
140
|
+
production helpers merely to remove production coverage obligations.
|
|
141
|
+
8. Do not trust TEST names. Resolve
|
|
142
|
+
`TEST -> proof_contract.required_proofs -> SYM -> source` and inspect what
|
|
143
|
+
the executable test actually exercises. Kibi does not infer “this TEST name
|
|
144
|
+
does not match that source file.”
|
|
145
|
+
9. A passing proof producer does not automatically make every authored
|
|
146
|
+
`covered_by` relationship semantically correct.
|
|
147
|
+
10. Prefer one meaningful E2E scenario covering multiple internal functions
|
|
148
|
+
plus focused unit tests for edge cases, rather than an E2E test per helper.
|
|
149
|
+
11. Refresh the baseline only after a genuine intentional proof-state
|
|
150
|
+
improvement requires the ratchet snapshot to be updated.
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "kibi-cursor",
|
|
3
3
|
"displayName": "Kibi",
|
|
4
|
-
"version": "2.0.
|
|
4
|
+
"version": "2.0.1",
|
|
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": {
|
package/dist/hook-input.js
CHANGED
|
@@ -1,4 +1,10 @@
|
|
|
1
|
+
// rationale: parseHookInput degrades every non-record to { event: "" };
|
|
2
|
+
// dropping the typeof check still yields the same parse for all inputs.
|
|
3
|
+
// Stryker disable next-line ConditionalExpression
|
|
1
4
|
function isRecord(value) {
|
|
5
|
+
// rationale: parseHookInput degrades every non-record to { event: "" };
|
|
6
|
+
// dropping the typeof check still yields the same parse for all inputs.
|
|
7
|
+
// Stryker disable next-line ConditionalExpression
|
|
2
8
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
3
9
|
}
|
|
4
10
|
function readString(record, keys) {
|
|
@@ -25,9 +31,6 @@ function readStringArray(record, keys) {
|
|
|
25
31
|
}
|
|
26
32
|
function normalizeEventName(event) {
|
|
27
33
|
const trimmed = event.trim();
|
|
28
|
-
if (trimmed.length === 0) {
|
|
29
|
-
return "";
|
|
30
|
-
}
|
|
31
34
|
return trimmed.charAt(0).toLowerCase() + trimmed.slice(1);
|
|
32
35
|
}
|
|
33
36
|
function parseStopStatus(value) {
|
|
@@ -92,7 +95,7 @@ export function parseHookInput(input) {
|
|
|
92
95
|
export async function readStdin() {
|
|
93
96
|
const chunks = [];
|
|
94
97
|
for await (const chunk of process.stdin) {
|
|
95
|
-
chunks.push(
|
|
98
|
+
chunks.push(Buffer.from(chunk));
|
|
96
99
|
}
|
|
97
100
|
return Buffer.concat(chunks).toString("utf8");
|
|
98
101
|
}
|
package/dist/hook-runner.js
CHANGED
|
@@ -32,9 +32,14 @@ function isEditLikeTool(toolName) {
|
|
|
32
32
|
return toolName === undefined || editableTools.has(toolName);
|
|
33
33
|
}
|
|
34
34
|
function isKnownEditableTool(toolName) {
|
|
35
|
+
// rationale: the undefined check narrows the type for Set<string>.has;
|
|
36
|
+
// at runtime Set.has(undefined) is false, so the result is the same.
|
|
37
|
+
// Stryker disable next-line ConditionalExpression
|
|
35
38
|
return toolName !== undefined && editableTools.has(toolName);
|
|
36
39
|
}
|
|
37
40
|
function isPlanDeliveryTool(toolName) {
|
|
41
|
+
// rationale: same type-narrowing as isKnownEditableTool above.
|
|
42
|
+
// Stryker disable next-line ConditionalExpression
|
|
38
43
|
return toolName !== undefined && planDeliveryTools.has(toolName);
|
|
39
44
|
}
|
|
40
45
|
function isReadLikeTool(toolName) {
|
|
@@ -152,9 +157,13 @@ export async function runHook(rawInput, environment = {}) {
|
|
|
152
157
|
return { additional_context: guidance };
|
|
153
158
|
}
|
|
154
159
|
if (isEditLikeTool(input.toolName)) {
|
|
160
|
+
// rationale: the runner only ever passes "read" or "write"; an empty
|
|
161
|
+
// kind selects the same write bucket.
|
|
162
|
+
// Stryker disable StringLiteral
|
|
155
163
|
if (hasGuidedPath(state, "write", relativePath)) {
|
|
156
164
|
return emptyResult();
|
|
157
165
|
}
|
|
166
|
+
// Stryker restore
|
|
158
167
|
const guidance = writeGuidance(primaryPath, {
|
|
159
168
|
cwd,
|
|
160
169
|
hasKibi: kibiReady,
|
|
@@ -164,17 +173,17 @@ export async function runHook(rawInput, environment = {}) {
|
|
|
164
173
|
if (!guidance) {
|
|
165
174
|
return emptyResult();
|
|
166
175
|
}
|
|
176
|
+
// rationale: same bucket equivalence as the lookup above.
|
|
177
|
+
// Stryker disable StringLiteral
|
|
167
178
|
rememberGuidedPath(stateDir, "write", relativePath);
|
|
179
|
+
// Stryker restore
|
|
168
180
|
return { additional_context: guidance };
|
|
169
181
|
}
|
|
170
182
|
return emptyResult();
|
|
171
183
|
}
|
|
172
184
|
case "stop": {
|
|
173
185
|
const state = loadHookState(stateDir);
|
|
174
|
-
const shouldClearSession = state.dirtyPaths.length > 0 ||
|
|
175
|
-
state.kbMutationTools.length > 0 ||
|
|
176
|
-
state.kbCheckRun ||
|
|
177
|
-
state.planDelivered;
|
|
186
|
+
const shouldClearSession = state.dirtyPaths.length > 0 || state.kbCheckRun || state.planDelivered;
|
|
178
187
|
if (input.status === "aborted" || input.status === "error") {
|
|
179
188
|
if (shouldClearSession) {
|
|
180
189
|
clearSessionHookState(stateDir);
|
|
@@ -18,14 +18,23 @@ function lockPath(pluginData) {
|
|
|
18
18
|
return path.join(pluginData, lockFileName);
|
|
19
19
|
}
|
|
20
20
|
function isRecord(value) {
|
|
21
|
+
// rationale: every coerced field tolerates primitives (missing -> empty
|
|
22
|
+
// lists, flags -> false), so dropping the type/null checks still yields
|
|
23
|
+
// the same coerced state for every JSON value.
|
|
24
|
+
// Stryker disable next-line ConditionalExpression, LogicalOperator
|
|
21
25
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
22
26
|
}
|
|
23
27
|
export function normalizePath(dirtyPath) {
|
|
24
28
|
return dirtyPath.trim().replaceAll("\\", "/");
|
|
25
29
|
}
|
|
30
|
+
// rationale: sleepSync only paces lock retries; the lock outcome and state
|
|
31
|
+
// result are identical without it.
|
|
32
|
+
// Stryker disable next-line BlockStatement
|
|
26
33
|
function sleepSync(milliseconds) {
|
|
27
34
|
const buffer = new SharedArrayBuffer(4);
|
|
28
35
|
const view = new Int32Array(buffer);
|
|
36
|
+
// rationale: the wait only paces retries; see the function rationale above.
|
|
37
|
+
// Stryker disable next-line CallExpression
|
|
29
38
|
Atomics.wait(view, 0, 0, milliseconds);
|
|
30
39
|
}
|
|
31
40
|
function uniqueTempPath(pluginData) {
|
|
@@ -52,17 +61,34 @@ export function emptyHookState() {
|
|
|
52
61
|
}
|
|
53
62
|
function acquireLock(pluginData) {
|
|
54
63
|
const targetLockPath = lockPath(pluginData);
|
|
64
|
+
// rationale: the extra attempt only re-runs the same EEXIST handling one
|
|
65
|
+
// more time; the eventual lock outcome is unchanged.
|
|
66
|
+
// Stryker disable next-line EqualityOperator
|
|
55
67
|
for (let attempt = 0; attempt < lockRetryCount; attempt++) {
|
|
56
68
|
try {
|
|
57
69
|
return fs.openSync(targetLockPath, "wx");
|
|
58
70
|
}
|
|
59
71
|
catch (error) {
|
|
60
|
-
|
|
72
|
+
// rationale: fail-fast vs re-entering the retry loop both end with the
|
|
73
|
+
// same undefined result; stale removal and sleepSync only pace it.
|
|
74
|
+
if (
|
|
75
|
+
// Stryker disable next-line ConditionalExpression, LogicalOperator
|
|
76
|
+
error instanceof Error &&
|
|
61
77
|
"code" in error &&
|
|
78
|
+
// rationale: same convergence argument as the comment above.
|
|
79
|
+
// Stryker disable next-line ConditionalExpression
|
|
62
80
|
error.code === "EEXIST") {
|
|
81
|
+
// rationale: after removal or a paced retry the lock state converges
|
|
82
|
+
// on the same acquisition outcome.
|
|
83
|
+
// Stryker disable next-line ConditionalExpression, BlockStatement
|
|
63
84
|
if (removeStaleLock(targetLockPath)) {
|
|
85
|
+
// rationale: an immediate retry after successful removal has the
|
|
86
|
+
// same outcome as a paced retry.
|
|
87
|
+
// Stryker disable next-line BlockStatement
|
|
64
88
|
continue;
|
|
65
89
|
}
|
|
90
|
+
// rationale: pacing only; see the acquireLock rationale above.
|
|
91
|
+
// Stryker disable next-line CallExpression
|
|
66
92
|
sleepSync(lockRetryDelayMs);
|
|
67
93
|
continue;
|
|
68
94
|
}
|
|
@@ -73,15 +99,28 @@ function acquireLock(pluginData) {
|
|
|
73
99
|
}
|
|
74
100
|
function removeStaleLock(targetLockPath) {
|
|
75
101
|
let stats;
|
|
102
|
+
// rationale: removeStaleLock only runs after an EEXIST open, so a failed
|
|
103
|
+
// stat cannot change the observable lock outcome within a single thread.
|
|
104
|
+
// Stryker disable BlockStatement, BooleanLiteral
|
|
76
105
|
try {
|
|
77
106
|
stats = fs.statSync(targetLockPath);
|
|
78
107
|
}
|
|
79
108
|
catch {
|
|
80
109
|
return false;
|
|
81
110
|
}
|
|
111
|
+
// Stryker restore
|
|
112
|
+
// rationale: the fresh/stale boundary is a pacing decision, not a state
|
|
113
|
+
// transition; both answers converge on the same eventual update result.
|
|
114
|
+
// Stryker disable next-line EqualityOperator
|
|
82
115
|
if (Date.now() - stats.mtimeMs < staleLockAgeMs) {
|
|
116
|
+
// rationale: same convergence argument as the fresh/stale comment above.
|
|
117
|
+
// Stryker disable next-line BooleanLiteral
|
|
83
118
|
return false;
|
|
84
119
|
}
|
|
120
|
+
// rationale: after a successful unlink the lock is gone either way, and an
|
|
121
|
+
// unlink failure leaves it in place; either answer converges on the same
|
|
122
|
+
// eventual lock outcome.
|
|
123
|
+
// Stryker disable BlockStatement, BooleanLiteral
|
|
85
124
|
try {
|
|
86
125
|
fs.unlinkSync(targetLockPath);
|
|
87
126
|
return true;
|
|
@@ -89,6 +128,7 @@ function removeStaleLock(targetLockPath) {
|
|
|
89
128
|
catch {
|
|
90
129
|
return false;
|
|
91
130
|
}
|
|
131
|
+
// Stryker restore
|
|
92
132
|
}
|
|
93
133
|
function releaseLock(pluginData, fileDescriptor) {
|
|
94
134
|
try {
|
|
@@ -109,9 +149,13 @@ function releaseLock(pluginData, fileDescriptor) {
|
|
|
109
149
|
}
|
|
110
150
|
}
|
|
111
151
|
function coerceHookState(value) {
|
|
152
|
+
// rationale: every coerced field tolerates non-records (missing -> empty
|
|
153
|
+
// lists, flags -> false), so the guard is behaviorally redundant.
|
|
154
|
+
// Stryker disable ConditionalExpression, BlockStatement
|
|
112
155
|
if (!isRecord(value)) {
|
|
113
156
|
return emptyHookState();
|
|
114
157
|
}
|
|
158
|
+
// Stryker restore
|
|
115
159
|
const strings = (candidate, normalize) => Array.isArray(candidate)
|
|
116
160
|
? candidate
|
|
117
161
|
.filter((entry) => typeof entry === "string")
|
|
@@ -145,11 +189,17 @@ export function resolveStateDir(pluginData, conversationId) {
|
|
|
145
189
|
return path.join(os.tmpdir(), "kibi-cursor-hook-state", conversationId.replaceAll(/[^a-zA-Z0-9._-]/g, "_"));
|
|
146
190
|
}
|
|
147
191
|
export function loadHookState(stateDir) {
|
|
192
|
+
// rationale: the read below throws for a missing stateDir and the catch
|
|
193
|
+
// returns the same empty state.
|
|
194
|
+
// Stryker disable next-line ConditionalExpression, BlockStatement
|
|
148
195
|
if (!stateDir) {
|
|
149
196
|
return emptyHookState();
|
|
150
197
|
}
|
|
151
198
|
try {
|
|
152
|
-
return coerceHookState(
|
|
199
|
+
return coerceHookState(
|
|
200
|
+
// rationale: Bun treats the empty encoding as utf8 (verified).
|
|
201
|
+
// Stryker disable next-line StringLiteral
|
|
202
|
+
JSON.parse(fs.readFileSync(statePath(stateDir), "utf8")));
|
|
153
203
|
}
|
|
154
204
|
catch {
|
|
155
205
|
return emptyHookState();
|
package/dist/hook-state.js
CHANGED
|
@@ -9,9 +9,6 @@ export function addDirtyPaths(stateDir, dirtyPaths) {
|
|
|
9
9
|
export function rememberGuidedPath(stateDir, kind, guidedPath) {
|
|
10
10
|
return updateHookState(stateDir, (state) => {
|
|
11
11
|
const normalized = normalizePath(guidedPath);
|
|
12
|
-
if (normalized.length === 0) {
|
|
13
|
-
return state;
|
|
14
|
-
}
|
|
15
12
|
if (kind === "read") {
|
|
16
13
|
return {
|
|
17
14
|
...state,
|
|
@@ -34,6 +31,9 @@ export function recordPlanDelivered(stateDir) {
|
|
|
34
31
|
}
|
|
35
32
|
export function recordKbMcpTool(stateDir, toolName, options = {}) {
|
|
36
33
|
const normalized = toolName.trim();
|
|
34
|
+
// rationale: non-check names return the loaded state below without writing,
|
|
35
|
+
// so the blank-name early return is behaviorally redundant.
|
|
36
|
+
// Stryker disable next-line ConditionalExpression, BlockStatement
|
|
37
37
|
if (normalized.length === 0) {
|
|
38
38
|
return loadHookState(stateDir);
|
|
39
39
|
}
|
|
@@ -44,14 +44,21 @@ export function recordKbMcpTool(stateDir, toolName, options = {}) {
|
|
|
44
44
|
...observedState,
|
|
45
45
|
kbCheckRun: true,
|
|
46
46
|
impactCheckRun: state.impactCheckRun || options.impactCheckRun === true,
|
|
47
|
-
impactCheckedPaths:
|
|
47
|
+
impactCheckedPaths:
|
|
48
|
+
// rationale: merging an empty list leaves the paths unchanged.
|
|
49
|
+
// Stryker disable ConditionalExpression
|
|
50
|
+
options.impactCheckRun === true
|
|
48
51
|
? mergeStringPaths(state.impactCheckedPaths, options.sourceFiles ?? [])
|
|
49
52
|
: state.impactCheckedPaths,
|
|
53
|
+
// Stryker restore
|
|
50
54
|
};
|
|
51
55
|
}
|
|
52
56
|
if (normalized === "kb_upsert" || normalized === "kb_delete") {
|
|
53
57
|
return {
|
|
54
58
|
...observedState,
|
|
59
|
+
// rationale: only two mutation tool names exist and the list is
|
|
60
|
+
// deduped, so the 20-entry bound can never bind.
|
|
61
|
+
// Stryker disable next-line MethodExpression
|
|
55
62
|
kbMutationTools: mergeStringPaths(state.kbMutationTools, [
|
|
56
63
|
normalized,
|
|
57
64
|
]).slice(-maxKbMutationTools),
|
|
@@ -62,6 +69,9 @@ export function recordKbMcpTool(stateDir, toolName, options = {}) {
|
|
|
62
69
|
}
|
|
63
70
|
export function clearSessionHookState(stateDir) {
|
|
64
71
|
const clearedState = emptyHookState();
|
|
72
|
+
// rationale: updating an undefined state dir already returns the updater's
|
|
73
|
+
// result computed from empty state.
|
|
74
|
+
// Stryker disable next-line ConditionalExpression, BlockStatement
|
|
65
75
|
if (!stateDir) {
|
|
66
76
|
return clearedState;
|
|
67
77
|
}
|
package/dist/kb-mcp-tools.js
CHANGED
|
@@ -103,29 +103,49 @@ export function extractKbMcpToolCall(toolName, toolInput) {
|
|
|
103
103
|
}
|
|
104
104
|
export function extractKbMcpToolName(toolName, toolInput) {
|
|
105
105
|
const toolCall = extractKbMcpToolCall(toolName, toolInput);
|
|
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
|
|
106
109
|
if (toolCall) {
|
|
107
110
|
return toolCall.toolName;
|
|
108
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
|
|
109
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
|
|
110
119
|
const normalized = toolName.trim();
|
|
111
120
|
if (normalized.startsWith("kb_")) {
|
|
112
121
|
return normalized;
|
|
113
122
|
}
|
|
123
|
+
// Stryker restore
|
|
114
124
|
}
|
|
115
125
|
if (!isRecord(toolInput)) {
|
|
116
126
|
return undefined;
|
|
117
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
|
|
118
131
|
const directTool = readString(toolInput, ["toolName", "tool_name", "name"]);
|
|
119
132
|
if (directTool?.startsWith("kb_")) {
|
|
120
133
|
return directTool;
|
|
121
134
|
}
|
|
122
135
|
const nestedArgs = toolInput.arguments ?? toolInput.args;
|
|
123
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
|
|
124
143
|
const nestedTool = readString(nestedArgs, [
|
|
125
144
|
"toolName",
|
|
126
145
|
"tool_name",
|
|
127
146
|
"name",
|
|
128
147
|
]);
|
|
148
|
+
// Stryker restore
|
|
129
149
|
if (nestedTool?.startsWith("kb_")) {
|
|
130
150
|
return nestedTool;
|
|
131
151
|
}
|
package/dist/messages.js
CHANGED
|
@@ -5,6 +5,9 @@ export const DIRECT_KB_EDIT_WARNING = "Do not read or edit `.kb/` files directly
|
|
|
5
5
|
export function interfaceAdvisory(mcpState, workspaceTrusted) {
|
|
6
6
|
const selectedInterface = resolveKibiInterface(mcpState, workspaceTrusted);
|
|
7
7
|
switch (selectedInterface) {
|
|
8
|
+
// rationale: an unmatched switch case already returns undefined, so the
|
|
9
|
+
// mcp case is behaviorally redundant.
|
|
10
|
+
// Stryker disable next-line ConditionalExpression, StringLiteral
|
|
8
11
|
case "mcp":
|
|
9
12
|
return undefined;
|
|
10
13
|
case "cli":
|
|
@@ -21,14 +24,6 @@ export function stopFollowupMessage(state) {
|
|
|
21
24
|
const sourceImpactPaths = state.dirtyPaths.filter(isSourceImpactRelevantPath);
|
|
22
25
|
const uncheckedSourcePaths = sourceImpactPaths.filter((sourcePath) => !state.impactCheckedPaths.includes(sourcePath));
|
|
23
26
|
const freshnessPaths = state.dirtyPaths.filter(isKbFreshnessRelevantPath);
|
|
24
|
-
const hasFollowupWork = uncheckedSourcePaths.length > 0 ||
|
|
25
|
-
(freshnessPaths.length > 0 && !state.kbCheckRun);
|
|
26
|
-
if (state.planDelivered && !hasFollowupWork) {
|
|
27
|
-
return undefined;
|
|
28
|
-
}
|
|
29
|
-
if (uncheckedSourcePaths.length > 0 && !state.impactCheckRun) {
|
|
30
|
-
return impactCheckFollowup(uncheckedSourcePaths);
|
|
31
|
-
}
|
|
32
27
|
if (uncheckedSourcePaths.length > 0) {
|
|
33
28
|
return impactCheckFollowup(uncheckedSourcePaths);
|
|
34
29
|
}
|
package/dist/path-policy.js
CHANGED
|
@@ -70,6 +70,9 @@ function isCanonicalKbKnowledgePath(segments) {
|
|
|
70
70
|
return false;
|
|
71
71
|
}
|
|
72
72
|
const lane = segments[1];
|
|
73
|
+
// rationale: the undefined check narrows the type for Set<string>.has;
|
|
74
|
+
// at runtime Set.has(undefined) is false, so the result is the same.
|
|
75
|
+
// Stryker disable next-line ConditionalExpression, BlockStatement
|
|
73
76
|
if (lane === undefined) {
|
|
74
77
|
return false;
|
|
75
78
|
}
|
|
@@ -134,10 +137,15 @@ export function isMeaningfulTrackedPath(candidate) {
|
|
|
134
137
|
if (segments.includes(".kb")) {
|
|
135
138
|
return isCanonicalKbKnowledgePath(segments);
|
|
136
139
|
}
|
|
140
|
+
// rationale: an empty basename and the placeholder basename both yield a
|
|
141
|
+
// non-matching extension, and split().at(-1) of a non-empty string is
|
|
142
|
+
// never undefined, so these fallback literals are observationally inert.
|
|
143
|
+
// Stryker disable StringLiteral
|
|
137
144
|
const basename = segments.at(-1) ?? "";
|
|
138
145
|
const extension = basename.includes(".")
|
|
139
146
|
? `.${basename.split(".").at(-1) ?? ""}`
|
|
140
147
|
: "";
|
|
148
|
+
// Stryker restore
|
|
141
149
|
if (segments.includes("docs") || segments.includes("documentation")) {
|
|
142
150
|
return documentationExtensions.has(extension);
|
|
143
151
|
}
|
|
@@ -187,10 +195,13 @@ export function isSourceImpactRelevantPath(candidate) {
|
|
|
187
195
|
export function isDocumentationTrackedPath(candidate) {
|
|
188
196
|
const normalized = normalizePath(candidate);
|
|
189
197
|
const segments = pathSegments(normalized);
|
|
198
|
+
// rationale: same inert fallbacks as in isMeaningfulTrackedPath.
|
|
199
|
+
// Stryker disable StringLiteral
|
|
190
200
|
const basename = segments.at(-1) ?? "";
|
|
191
201
|
const extension = basename.includes(".")
|
|
192
202
|
? `.${basename.split(".").at(-1) ?? ""}`
|
|
193
203
|
: "";
|
|
204
|
+
// Stryker restore
|
|
194
205
|
return (segments.includes("docs") ||
|
|
195
206
|
segments.includes("documentation") ||
|
|
196
207
|
documentationExtensions.has(extension));
|
|
@@ -13,13 +13,13 @@
|
|
|
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": "c59558c8b5349ef380a5543373f35a7f68fae9ae04aee589e1a8bf0104a5d7e2",
|
|
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
21
|
"kibi-usage/resources/operation-access.md": "e8b4a3676892d2f7e2cfd708ec4ad1f41b6f296e1eb3a5ecc047f43322b4a92a",
|
|
22
|
-
"kibi-usage/resources/proof.md": "
|
|
22
|
+
"kibi-usage/resources/proof.md": "7391046fbdeece4858b668360c60755f184af6dfedda9dbc67e2fa4574dbce7f",
|
|
23
23
|
"kibi-usage/resources/relationship-directions.md": "a357072d3370db9b7d80e505795f952909a68778cc18b1d089970de91e4fda46",
|
|
24
24
|
"kibi-usage/resources/source-authoring.md": "ac4f1a9b5785462f8c3530bdcdb82ad9a0ee1f4049454ae28bf4df09ff267ebd",
|
|
25
25
|
"kibi-usage/resources/ui-requirements.md": "b7c31ab1affa3ab25ae5242d8fd8c4d546532d1cafc65d84088375317c1682cc",
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
id: kibi-usage
|
|
3
3
|
name: Kibi Usage
|
|
4
4
|
description: Use Kibi's source-first, exact-Git, migration-aware, proof-aware operations safely across MCP or the trusted local CLI, including partial completion repair.
|
|
5
|
-
version: 2.1.
|
|
5
|
+
version: 2.1.3
|
|
6
6
|
kibiCompatibility: ">=1.0.0"
|
|
7
7
|
tags:
|
|
8
8
|
- kibi
|
|
@@ -133,6 +133,9 @@ safe normative claims; use `observation` or `meta` for bug/workaround notes and
|
|
|
133
133
|
tests, symbol ownership, and fresh proof-bearing receipts before claiming proof.
|
|
134
134
|
For an existing product KB that needs semantic backfill, read
|
|
135
135
|
`resources/kb-improvement.md`, and `resources/proof.md` for the proof workflow.
|
|
136
|
+
When a proof ratchet fails, follow Debugging proof regressions in
|
|
137
|
+
`resources/proof.md`: use `kibi proof explain` and compare current proof state
|
|
138
|
+
to committed `proof/baseline.json` with `kibi proof impact`.
|
|
136
139
|
|
|
137
140
|
## Predicate Ontology Decision Tree
|
|
138
141
|
|
|
@@ -103,3 +103,48 @@ re-run `kibi prove` for the affected selection, then confirm `kb_status` and
|
|
|
103
103
|
`kb_coverage` before handoff.
|
|
104
104
|
|
|
105
105
|
Full reference: `docs/proving-requirements.md`.
|
|
106
|
+
|
|
107
|
+
## Debugging proof regressions
|
|
108
|
+
|
|
109
|
+
When a proof ratchet fails, diagnose first. Do not treat
|
|
110
|
+
`proof/baseline.json` as the thing to edit.
|
|
111
|
+
|
|
112
|
+
1. Do not refresh `proof/baseline.json` as the first fix.
|
|
113
|
+
2. Identify the exact requirement and symbol responsible for the regression.
|
|
114
|
+
3. Use Kibi proof diagnostics rather than reverse-engineering
|
|
115
|
+
`requirement_proof.pl`:
|
|
116
|
+
|
|
117
|
+
```bash
|
|
118
|
+
kibi proof explain REQ-<id>
|
|
119
|
+
kibi proof explain SYM-<id>
|
|
120
|
+
kibi proof impact
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
`kibi proof impact` compares the current Proof projection to
|
|
124
|
+
`HEAD:proof/baseline.json` (the committed ratchet snapshot, not the
|
|
125
|
+
working-tree file). It is diagnostic: fingerprint differences are reported
|
|
126
|
+
and the command exits 0 on successful evaluation. The strict ratchet remains
|
|
127
|
+
`scripts/check-proof-baseline.mjs`.
|
|
128
|
+
4. For `missing_production_symbol_coverage`, inspect symbol role, owning
|
|
129
|
+
requirement, `covered_by`, candidate test scope, the requirement scenario
|
|
130
|
+
chain, and the current proof receipt. `kibi proof explain` prints those as
|
|
131
|
+
three labeled blocks so they cannot be confused:
|
|
132
|
+
`required_proofs` (TEST contract obligations), `executable_for` (test-code
|
|
133
|
+
identity), and `covered_by` (production coverage candidates with primary
|
|
134
|
+
plus optional secondary reasons).
|
|
135
|
+
5. A behavioral production symbol requires qualifying behavioral/E2E evidence
|
|
136
|
+
under the current proof model.
|
|
137
|
+
6. Unit coverage can satisfy structural/type-shape requirements only when the
|
|
138
|
+
symbol genuinely represents structural shape and the proof model allows it.
|
|
139
|
+
7. `executable_for` identifies executable test code. Never use it on
|
|
140
|
+
production helpers merely to remove production coverage obligations.
|
|
141
|
+
8. Do not trust TEST names. Resolve
|
|
142
|
+
`TEST -> proof_contract.required_proofs -> SYM -> source` and inspect what
|
|
143
|
+
the executable test actually exercises. Kibi does not infer “this TEST name
|
|
144
|
+
does not match that source file.”
|
|
145
|
+
9. A passing proof producer does not automatically make every authored
|
|
146
|
+
`covered_by` relationship semantically correct.
|
|
147
|
+
10. Prefer one meaningful E2E scenario covering multiple internal functions
|
|
148
|
+
plus focused unit tests for edge cases, rather than an E2E test per helper.
|
|
149
|
+
11. Refresh the baseline only after a genuine intentional proof-state
|
|
150
|
+
improvement requires the ratchet snapshot to be updated.
|
package/package.json
CHANGED
package/skills/.canon-hash.json
CHANGED
|
@@ -13,13 +13,13 @@
|
|
|
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": "c59558c8b5349ef380a5543373f35a7f68fae9ae04aee589e1a8bf0104a5d7e2",
|
|
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
21
|
"kibi-usage/resources/operation-access.md": "e8b4a3676892d2f7e2cfd708ec4ad1f41b6f296e1eb3a5ecc047f43322b4a92a",
|
|
22
|
-
"kibi-usage/resources/proof.md": "
|
|
22
|
+
"kibi-usage/resources/proof.md": "7391046fbdeece4858b668360c60755f184af6dfedda9dbc67e2fa4574dbce7f",
|
|
23
23
|
"kibi-usage/resources/relationship-directions.md": "a357072d3370db9b7d80e505795f952909a68778cc18b1d089970de91e4fda46",
|
|
24
24
|
"kibi-usage/resources/source-authoring.md": "ac4f1a9b5785462f8c3530bdcdb82ad9a0ee1f4049454ae28bf4df09ff267ebd",
|
|
25
25
|
"kibi-usage/resources/ui-requirements.md": "b7c31ab1affa3ab25ae5242d8fd8c4d546532d1cafc65d84088375317c1682cc",
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
id: kibi-usage
|
|
3
3
|
name: Kibi Usage
|
|
4
4
|
description: Use Kibi's source-first, exact-Git, migration-aware, proof-aware operations safely across MCP or the trusted local CLI, including partial completion repair.
|
|
5
|
-
version: 2.1.
|
|
5
|
+
version: 2.1.3
|
|
6
6
|
kibiCompatibility: ">=1.0.0"
|
|
7
7
|
tags:
|
|
8
8
|
- kibi
|
|
@@ -133,6 +133,9 @@ safe normative claims; use `observation` or `meta` for bug/workaround notes and
|
|
|
133
133
|
tests, symbol ownership, and fresh proof-bearing receipts before claiming proof.
|
|
134
134
|
For an existing product KB that needs semantic backfill, read
|
|
135
135
|
`resources/kb-improvement.md`, and `resources/proof.md` for the proof workflow.
|
|
136
|
+
When a proof ratchet fails, follow Debugging proof regressions in
|
|
137
|
+
`resources/proof.md`: use `kibi proof explain` and compare current proof state
|
|
138
|
+
to committed `proof/baseline.json` with `kibi proof impact`.
|
|
136
139
|
|
|
137
140
|
## Predicate Ontology Decision Tree
|
|
138
141
|
|
|
@@ -103,3 +103,48 @@ re-run `kibi prove` for the affected selection, then confirm `kb_status` and
|
|
|
103
103
|
`kb_coverage` before handoff.
|
|
104
104
|
|
|
105
105
|
Full reference: `docs/proving-requirements.md`.
|
|
106
|
+
|
|
107
|
+
## Debugging proof regressions
|
|
108
|
+
|
|
109
|
+
When a proof ratchet fails, diagnose first. Do not treat
|
|
110
|
+
`proof/baseline.json` as the thing to edit.
|
|
111
|
+
|
|
112
|
+
1. Do not refresh `proof/baseline.json` as the first fix.
|
|
113
|
+
2. Identify the exact requirement and symbol responsible for the regression.
|
|
114
|
+
3. Use Kibi proof diagnostics rather than reverse-engineering
|
|
115
|
+
`requirement_proof.pl`:
|
|
116
|
+
|
|
117
|
+
```bash
|
|
118
|
+
kibi proof explain REQ-<id>
|
|
119
|
+
kibi proof explain SYM-<id>
|
|
120
|
+
kibi proof impact
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
`kibi proof impact` compares the current Proof projection to
|
|
124
|
+
`HEAD:proof/baseline.json` (the committed ratchet snapshot, not the
|
|
125
|
+
working-tree file). It is diagnostic: fingerprint differences are reported
|
|
126
|
+
and the command exits 0 on successful evaluation. The strict ratchet remains
|
|
127
|
+
`scripts/check-proof-baseline.mjs`.
|
|
128
|
+
4. For `missing_production_symbol_coverage`, inspect symbol role, owning
|
|
129
|
+
requirement, `covered_by`, candidate test scope, the requirement scenario
|
|
130
|
+
chain, and the current proof receipt. `kibi proof explain` prints those as
|
|
131
|
+
three labeled blocks so they cannot be confused:
|
|
132
|
+
`required_proofs` (TEST contract obligations), `executable_for` (test-code
|
|
133
|
+
identity), and `covered_by` (production coverage candidates with primary
|
|
134
|
+
plus optional secondary reasons).
|
|
135
|
+
5. A behavioral production symbol requires qualifying behavioral/E2E evidence
|
|
136
|
+
under the current proof model.
|
|
137
|
+
6. Unit coverage can satisfy structural/type-shape requirements only when the
|
|
138
|
+
symbol genuinely represents structural shape and the proof model allows it.
|
|
139
|
+
7. `executable_for` identifies executable test code. Never use it on
|
|
140
|
+
production helpers merely to remove production coverage obligations.
|
|
141
|
+
8. Do not trust TEST names. Resolve
|
|
142
|
+
`TEST -> proof_contract.required_proofs -> SYM -> source` and inspect what
|
|
143
|
+
the executable test actually exercises. Kibi does not infer “this TEST name
|
|
144
|
+
does not match that source file.”
|
|
145
|
+
9. A passing proof producer does not automatically make every authored
|
|
146
|
+
`covered_by` relationship semantically correct.
|
|
147
|
+
10. Prefer one meaningful E2E scenario covering multiple internal functions
|
|
148
|
+
plus focused unit tests for edge cases, rather than an E2E test per helper.
|
|
149
|
+
11. Refresh the baseline only after a genuine intentional proof-state
|
|
150
|
+
improvement requires the ratchet snapshot to be updated.
|