kibi-cli 2.24.0 → 2.25.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/dist/firm-decision-edit.d.ts +30 -0
- package/dist/firm-decision-edit.d.ts.map +1 -0
- package/dist/firm-decision-edit.js +90 -0
- package/dist/operations/mutation/firm-decision-guard.d.ts +10 -0
- package/dist/operations/mutation/firm-decision-guard.d.ts.map +1 -0
- package/dist/operations/mutation/firm-decision-guard.js +71 -0
- package/dist/operations/mutation/upsert.d.ts.map +1 -1
- package/dist/operations/mutation/upsert.js +2 -0
- package/dist/public/operations/catalog.d.ts +2 -2
- package/dist/public/operations/decision-firmness.d.ts +11 -1
- package/dist/public/operations/decision-firmness.d.ts.map +1 -1
- package/dist/public/operations/decision-firmness.js +78 -5
- package/dist/public/operations/specs/check-rules.generated.d.ts +1 -1
- package/dist/public/operations/specs/check-rules.generated.d.ts.map +1 -1
- package/dist/public/operations/specs/check-rules.generated.js +2 -1
- package/dist/public/operations/specs/check.d.ts +1 -1
- package/dist/public/operations/specs/mutation-schemas.d.ts +1 -1
- package/dist/public/operations/specs/mutation-schemas.js +1 -1
- package/dist/public/operations/specs/upsert.d.ts +1 -1
- package/dist/public/skills/kibi-usage/SKILL.md +8 -3
- package/dist/utils/rule-registry.generated.d.ts +5 -0
- package/dist/utils/rule-registry.generated.d.ts.map +1 -1
- package/dist/utils/rule-registry.generated.js +6 -0
- package/package.json +1 -1
- package/src/public/operations/decision-firmness.ts +110 -13
- package/src/public/operations/specs/check-rules.generated.ts +2 -1
- package/src/public/operations/specs/mutation-schemas.ts +1 -1
- package/src/public/skills/kibi-usage/SKILL.md +8 -3
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/** A requirement or ADR as authored: front matter and Markdown body. */
|
|
2
|
+
export type DecisionDocument = Readonly<{
|
|
3
|
+
type: string;
|
|
4
|
+
data: Readonly<Record<string, unknown>>;
|
|
5
|
+
body: string;
|
|
6
|
+
}>;
|
|
7
|
+
/** The parts of a decision's claim an in-place edit may change. */
|
|
8
|
+
export type DecisionClaimField = "title" | "statement" | "body" | "firmness";
|
|
9
|
+
/**
|
|
10
|
+
* The claim fields that differ between two versions of one decision. A
|
|
11
|
+
* requirement's claim is its title, its stated meaning and the statement its
|
|
12
|
+
* body derives (context sections may change freely); an ADR's claim is its
|
|
13
|
+
* title and whole body. Lowering a firm decision's firmness changes it too.
|
|
14
|
+
*/
|
|
15
|
+
export declare function decisionClaimChanges(before: DecisionDocument, after: DecisionDocument): DecisionClaimField[];
|
|
16
|
+
/**
|
|
17
|
+
* Whether `after` cites human words `before` did not: the reference a person
|
|
18
|
+
* gave for this change. Kibi checks that a new reference is present, not that
|
|
19
|
+
* it is genuine.
|
|
20
|
+
*/
|
|
21
|
+
export declare function citesNewHumanWords(before: DecisionDocument, after: DecisionDocument): boolean;
|
|
22
|
+
/**
|
|
23
|
+
* The claim fields an edit changes on a firm decision without citing new
|
|
24
|
+
* human words; empty when the edit is allowed. Only a decision that is firm
|
|
25
|
+
* before the edit is guarded.
|
|
26
|
+
*/
|
|
27
|
+
export declare function unapprovedFirmEdit(before: DecisionDocument, after: DecisionDocument): DecisionClaimField[];
|
|
28
|
+
/** What to do instead of editing a firm decision in place. */
|
|
29
|
+
export declare function firmEditGuidance(type: string): string;
|
|
30
|
+
//# sourceMappingURL=firm-decision-edit.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"firm-decision-edit.d.ts","sourceRoot":"","sources":["../src/firm-decision-edit.ts"],"names":[],"mappings":"AAqBA,wEAAwE;AAExE,MAAM,MAAM,gBAAgB,GAAG,QAAQ,CAAC;IACtC,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;IACxC,IAAI,EAAE,MAAM,CAAC;CACd,CAAC,CAAC;AAEH,mEAAmE;AAEnE,MAAM,MAAM,kBAAkB,GAAG,OAAO,GAAG,WAAW,GAAG,MAAM,GAAG,UAAU,CAAC;AAiB7E;;;;;GAKG;AAEH,wBAAgB,oBAAoB,CAClC,MAAM,EAAE,gBAAgB,EACxB,KAAK,EAAE,gBAAgB,GACtB,kBAAkB,EAAE,CAsBtB;AAED;;;;GAIG;AAEH,wBAAgB,kBAAkB,CAChC,MAAM,EAAE,gBAAgB,EACxB,KAAK,EAAE,gBAAgB,GACtB,OAAO,CAMT;AAED;;;;GAIG;AAEH,wBAAgB,kBAAkB,CAChC,MAAM,EAAE,gBAAgB,EACxB,KAAK,EAAE,gBAAgB,GACtB,kBAAkB,EAAE,CAStB;AAED,8DAA8D;AAE9D,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAErD"}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Kibi — repo-local, per-branch, queryable long-term memory for software projects
|
|
3
|
+
Copyright (C) 2026 Piotr Franczyk
|
|
4
|
+
|
|
5
|
+
This program is free software: you can redistribute it and/or modify
|
|
6
|
+
it under the terms of the GNU Affero General Public License as published by
|
|
7
|
+
the Free Software Foundation, either version 3 of the License, or
|
|
8
|
+
(at your option) any later version.
|
|
9
|
+
|
|
10
|
+
This program is distributed in the hope that it will be useful,
|
|
11
|
+
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
12
|
+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
13
|
+
GNU Affero General Public License for more details.
|
|
14
|
+
|
|
15
|
+
You should have received a copy of the GNU Affero General Public License
|
|
16
|
+
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
17
|
+
*/
|
|
18
|
+
import { effectiveFirmness, firmnessRef } from "./decision-firmness.js";
|
|
19
|
+
import { requirementSemanticText } from "./extractors/markdown.js";
|
|
20
|
+
function normalized(value) {
|
|
21
|
+
return typeof value === "string" ? value.replace(/\s+/g, " ").trim() : "";
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* What a requirement states: its authored `semantic_text`, else the
|
|
25
|
+
* statement its body derives. Context sections are not part of it.
|
|
26
|
+
*/
|
|
27
|
+
function requirementStatement(document) {
|
|
28
|
+
const authored = normalized(document.data.semantic_text);
|
|
29
|
+
return authored !== ""
|
|
30
|
+
? authored
|
|
31
|
+
: normalized(requirementSemanticText(document.body));
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* The claim fields that differ between two versions of one decision. A
|
|
35
|
+
* requirement's claim is its title, its stated meaning and the statement its
|
|
36
|
+
* body derives (context sections may change freely); an ADR's claim is its
|
|
37
|
+
* title and whole body. Lowering a firm decision's firmness changes it too.
|
|
38
|
+
*/
|
|
39
|
+
// implements REQ-kb-firm-decision-edit
|
|
40
|
+
export function decisionClaimChanges(before, after) {
|
|
41
|
+
const changed = [];
|
|
42
|
+
if (normalized(before.data.title) !== normalized(after.data.title))
|
|
43
|
+
changed.push("title");
|
|
44
|
+
if (before.type === "req") {
|
|
45
|
+
if (requirementStatement(before) !== requirementStatement(after) ||
|
|
46
|
+
normalized(requirementSemanticText(before.body)) !==
|
|
47
|
+
normalized(requirementSemanticText(after.body)))
|
|
48
|
+
changed.push("statement");
|
|
49
|
+
}
|
|
50
|
+
else if (normalized(before.body) !== normalized(after.body)) {
|
|
51
|
+
changed.push("body");
|
|
52
|
+
}
|
|
53
|
+
if (effectiveFirmness(before.type, before.data.firmness, before.data.origin) ===
|
|
54
|
+
"firm" &&
|
|
55
|
+
effectiveFirmness(after.type, after.data.firmness, after.data.origin) !==
|
|
56
|
+
"firm")
|
|
57
|
+
changed.push("firmness");
|
|
58
|
+
return changed;
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Whether `after` cites human words `before` did not: the reference a person
|
|
62
|
+
* gave for this change. Kibi checks that a new reference is present, not that
|
|
63
|
+
* it is genuine.
|
|
64
|
+
*/
|
|
65
|
+
// implements REQ-kb-firm-decision-edit
|
|
66
|
+
export function citesNewHumanWords(before, after) {
|
|
67
|
+
const ref = firmnessRef({ type: after.type, ...after.data });
|
|
68
|
+
return (ref !== undefined &&
|
|
69
|
+
ref !== firmnessRef({ type: before.type, ...before.data }));
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* The claim fields an edit changes on a firm decision without citing new
|
|
73
|
+
* human words; empty when the edit is allowed. Only a decision that is firm
|
|
74
|
+
* before the edit is guarded.
|
|
75
|
+
*/
|
|
76
|
+
// implements REQ-kb-firm-decision-edit
|
|
77
|
+
export function unapprovedFirmEdit(before, after) {
|
|
78
|
+
if (effectiveFirmness(before.type, before.data.firmness, before.data.origin) !==
|
|
79
|
+
"firm")
|
|
80
|
+
return [];
|
|
81
|
+
const changed = decisionClaimChanges(before, after);
|
|
82
|
+
if (changed.length === 0 || citesNewHumanWords(before, after))
|
|
83
|
+
return [];
|
|
84
|
+
return changed;
|
|
85
|
+
}
|
|
86
|
+
/** What to do instead of editing a firm decision in place. */
|
|
87
|
+
// implements REQ-kb-firm-decision-edit
|
|
88
|
+
export function firmEditGuidance(type) {
|
|
89
|
+
return `Ask the person who stated it. If they agree, either write the change with ${type === "req" ? "origin.ref or approval_ref" : "origin.ref"} pointing at their words, or supersede it with a new firmness: firm decision that cites them; otherwise leave it and propose the change`;
|
|
90
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { StoredEntityLookup } from "./stored-entity.js";
|
|
2
|
+
import type { UpsertInput } from "./types.js";
|
|
3
|
+
/**
|
|
4
|
+
* Refuse an upsert that changes a current firm requirement's or ADR's claim
|
|
5
|
+
* in place unless it cites new human words for the change. The decision is
|
|
6
|
+
* judged as authored before the write, so dropping its firmness or origin in
|
|
7
|
+
* the same write does not make it editable.
|
|
8
|
+
*/
|
|
9
|
+
export declare function assertFirmDecisionUnchanged(input: UpsertInput, workspaceRoot: string, lookup: StoredEntityLookup): Promise<void>;
|
|
10
|
+
//# sourceMappingURL=firm-decision-guard.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"firm-decision-guard.d.ts","sourceRoot":"","sources":["../../../src/operations/mutation/firm-decision-guard.ts"],"names":[],"mappings":"AA8BA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,oBAAoB,CAAC;AAC7D,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAwB9C;;;;;GAKG;AAEH,wBAAsB,2BAA2B,CAC/C,KAAK,EAAE,WAAW,EAClB,aAAa,EAAE,MAAM,EACrB,MAAM,EAAE,kBAAkB,GACzB,OAAO,CAAC,IAAI,CAAC,CA2Bf"}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Kibi — repo-local, per-branch, queryable long-term memory for software projects
|
|
3
|
+
Copyright (C) 2026 Piotr Franczyk
|
|
4
|
+
|
|
5
|
+
This program is free software: you can redistribute it and/or modify
|
|
6
|
+
it under the terms of the GNU Affero General Public License as published by
|
|
7
|
+
the Free Software Foundation, either version 3 of the License, or
|
|
8
|
+
(at your option) any later version.
|
|
9
|
+
|
|
10
|
+
This program is distributed in the hope that it will be useful,
|
|
11
|
+
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
12
|
+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
13
|
+
GNU Affero General Public License for more details.
|
|
14
|
+
|
|
15
|
+
You should have received a copy of the GNU Affero General Public License
|
|
16
|
+
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
17
|
+
*/
|
|
18
|
+
import { readFileSync } from "node:fs";
|
|
19
|
+
import path from "node:path";
|
|
20
|
+
import { carriesFirmness } from "../../decision-firmness.js";
|
|
21
|
+
import { firmEditGuidance, unapprovedFirmEdit, } from "../../firm-decision-edit.js";
|
|
22
|
+
import { isCurrentAuthoredEntity, readAuthoredEntity, } from "../../public/operations/entity-context.js";
|
|
23
|
+
function readStoredDocument(workspaceRoot, stored) {
|
|
24
|
+
const source = stored.source;
|
|
25
|
+
if (typeof source !== "string" || !source.endsWith(".md"))
|
|
26
|
+
return undefined;
|
|
27
|
+
try {
|
|
28
|
+
const authored = readAuthoredEntity(readFileSync(path.resolve(workspaceRoot, source), "utf8"), source);
|
|
29
|
+
if (authored === null)
|
|
30
|
+
return undefined;
|
|
31
|
+
return {
|
|
32
|
+
type: authored.type,
|
|
33
|
+
data: authored.data,
|
|
34
|
+
body: authored.body,
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
catch {
|
|
38
|
+
return undefined;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Refuse an upsert that changes a current firm requirement's or ADR's claim
|
|
43
|
+
* in place unless it cites new human words for the change. The decision is
|
|
44
|
+
* judged as authored before the write, so dropping its firmness or origin in
|
|
45
|
+
* the same write does not make it editable.
|
|
46
|
+
*/
|
|
47
|
+
// implements REQ-kb-firm-decision-edit
|
|
48
|
+
export async function assertFirmDecisionUnchanged(input, workspaceRoot, lookup) {
|
|
49
|
+
if (!carriesFirmness(input.type))
|
|
50
|
+
return;
|
|
51
|
+
const stored = await lookup.entity();
|
|
52
|
+
if (stored === undefined || stored === null)
|
|
53
|
+
return;
|
|
54
|
+
const authored = readStoredDocument(workspaceRoot, stored);
|
|
55
|
+
const before = authored ?? {
|
|
56
|
+
type: input.type,
|
|
57
|
+
data: stored,
|
|
58
|
+
body: input.document?.body ?? "",
|
|
59
|
+
};
|
|
60
|
+
if (!isCurrentAuthoredEntity({ id: input.id, path: "", ...before, data: { ...before.data } }, new Set()))
|
|
61
|
+
return;
|
|
62
|
+
const after = {
|
|
63
|
+
type: input.type,
|
|
64
|
+
data: { ...before.data, ...input.properties },
|
|
65
|
+
body: input.document?.body ?? before.body,
|
|
66
|
+
};
|
|
67
|
+
const changed = unapprovedFirmEdit(before, after);
|
|
68
|
+
if (changed.length === 0)
|
|
69
|
+
return;
|
|
70
|
+
throw new Error(`Entity validation failed: ${input.id} is a firm decision, so kb_upsert does not change its ${changed.join(", ")} in place. ${firmEditGuidance(input.type)}.`);
|
|
71
|
+
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"upsert.d.ts","sourceRoot":"","sources":["../../../src/operations/mutation/upsert.ts"],"names":[],"mappings":"AAeA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,0CAA0C,CAAC;AACjF,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,kCAAkC,CAAC;AAexE,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,8BAA8B,CAAC;
|
|
1
|
+
{"version":3,"file":"upsert.d.ts","sourceRoot":"","sources":["../../../src/operations/mutation/upsert.ts"],"names":[],"mappings":"AAeA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,0CAA0C,CAAC;AACjF,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,kCAAkC,CAAC;AAexE,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,8BAA8B,CAAC;AA6B3E,OAAO,EACL,KAAK,kBAAkB,EAExB,MAAM,oBAAoB,CAAC;AAQ5B,OAAO,KAAK,EACV,iBAAiB,EACjB,iBAAiB,EACjB,WAAW,EACX,aAAa,EACb,eAAe,EAChB,MAAM,YAAY,CAAC;AAsEpB,wBAAgB,gCAAgC,CAC9C,aAAa,EAAE,MAAM,EACrB,SAAS,EAAE,MAAM,GAChB,IAAI,CAkBN;AAwCD,wBAAsB,+BAA+B,CACnD,MAAM,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,EACzC,OAAO,EAAE,gBAAgB,EACzB,MAAM,CAAC,EAAE,iBAAiB,GACzB,OAAO,CAAC,IAAI,CAAC,CAqBf;AAED,wBAAsB,sBAAsB,CAC1C,KAAK,EAAE,WAAW,EAClB,MAAM,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,EACzC,aAAa,EAAE,SAAS,iBAAiB,EAAE,EAC3C,OAAO,EAAE,gBAAgB,EACzB,MAAM,CAAC,EAAE,iBAAiB,EAC1B,MAAM,CAAC,EAAE,kBAAkB,GAC1B,OAAO,CAAC,SAAS,iBAAiB,EAAE,CAAC,CA8BvC;AAmCD,MAAM,MAAM,uBAAuB,GAAG,QAAQ,CAAC;IAC7C,kEAAkE;IAClE,QAAQ,CAAC,kBAAkB,CAAC,EAAE,OAAO,CAAC;IACtC;;;OAGG;IACH,QAAQ,CAAC,MAAM,CAAC,EAAE,iBAAiB,CAAC;CACrC,CAAC,CAAC;AAGH,uFAAuF;AACvF,MAAM,MAAM,sBAAsB,GAAG,QAAQ,CAAC;IAC5C,QAAQ,CAAC,OAAO,EAAE,sBAAsB,GAAG,IAAI,CAAC;IAChD,QAAQ,CAAC,QAAQ,EAAE,SAAS,MAAM,EAAE,CAAC;CACtC,CAAC,CAAC;AAGH;;;;;;GAMG;AACH,wBAAgB,sBAAsB,CACpC,OAAO,EAAE,WAAW,GAAG;IACrB,QAAQ,CAAC,aAAa,EAAE,SAAS,iBAAiB,EAAE,CAAC;CACtD,GACA,sBAAsB,CASxB;AAGD,MAAM,MAAM,gBAAgB,GAAG,QAAQ,CAAC;IACtC,QAAQ,CAAC,SAAS,EAAE,eAAe,CAAC;IACpC,yEAAyE;IACzE,QAAQ,CAAC,aAAa,EAAE,SAAS,iBAAiB,EAAE,CAAC;IACrD,QAAQ,CAAC,QAAQ,EAAE,sBAAsB,CAAC;CAC3C,CAAC,CAAC;AAGH;;;;;;;;GAQG;AACH,wBAAsB,uBAAuB,CAC3C,OAAO,EAAE,WAAW,EACpB,OAAO,EAAE,gBAAgB,EACzB,OAAO,GAAE,uBAA4B,GACpC,OAAO,CAAC,gBAAgB,CAAC,CAkF3B;AAgDD,MAAM,MAAM,sBAAsB,GAAG,QAAQ,CAAC;IAC5C;;;;;;OAMG;IAEH,QAAQ,CAAC,sBAAsB,CAAC,EAAE,MAAM,CAAC;IACzC;;;;OAIG;IACH,QAAQ,CAAC,kBAAkB,CAAC,EAAE,OAAO,CAAC;IACtC;;;;OAIG;IACH,QAAQ,CAAC,mBAAmB,CAAC,EAAE,OAAO,CAAC;CACxC,CAAC,CAAC;AAyBH,wBAAsB,aAAa,CACjC,KAAK,EAAE,WAAW,EAClB,OAAO,EAAE,gBAAgB,EACzB,OAAO,GAAE,sBAA2B,GACnC,OAAO,CAAC,eAAe,CAAC,aAAa,CAAC,CAAC,CA2hBzC"}
|
|
@@ -15,6 +15,7 @@ import { analyzeSemanticAdvisorInput } from "../semantic-advisor/analyze-prose.j
|
|
|
15
15
|
import { assertLogicalGroundingClaimKeys, assertSemanticInventoryBoundary, } from "../semantic-advisor/ingestion-boundary.js";
|
|
16
16
|
import { entityContextWarnings } from "./context-warning.js";
|
|
17
17
|
import { buildUpsertCommitGoal, formatUpsertError } from "./contradictions.js";
|
|
18
|
+
import { assertFirmDecisionUnchanged } from "./firm-decision-guard.js";
|
|
18
19
|
import { resolveUpsertOrigin } from "./origin.js";
|
|
19
20
|
import { planRecoveryNotes, settlePendingPlanApplyJournals, withPlanRecoveryNotes, } from "./plan-apply-journal.js";
|
|
20
21
|
import { assertPredicateArgumentVocabulary } from "./predicate-vocabulary-guard.js";
|
|
@@ -247,6 +248,7 @@ export async function validateUpsertForCommit(payload, context, options = {}) {
|
|
|
247
248
|
await assertStoredRequirementBoundary(input, relationships, prolog, staged, lookup);
|
|
248
249
|
await assertLogicalGroundingClaimKeys(prolog, { ...input, relationships }, relationships, staged);
|
|
249
250
|
await assertPredicateArgumentVocabulary(prolog, validated.entity, staged);
|
|
251
|
+
await assertFirmDecisionUnchanged(payload, context.workspaceRoot, lookup);
|
|
250
252
|
// The origin the write records is added last: none of the checks above
|
|
251
253
|
// reads it, and a rejected payload never pays for the stored-origin read.
|
|
252
254
|
const recorded = await resolveUpsertOrigin(input, prolog, context.clock(), staged, lookup);
|
|
@@ -1125,7 +1125,7 @@ export declare const OPERATION_CATALOG: readonly [{
|
|
|
1125
1125
|
readonly firmness: {
|
|
1126
1126
|
readonly type: "string";
|
|
1127
1127
|
readonly enum: readonly ["firm", "default", "exploratory"];
|
|
1128
|
-
readonly description: "req/adr only. firm = a human stated it as a rule (needs origin.ref to their words);
|
|
1128
|
+
readonly description: "req/adr only. firm = a human stated it as a rule (needs origin.ref to their words); its title, meaning or firmness changes only with new human words (a new origin.ref) or by supersede. default = delegated or agent choice; exploratory = flagged guess. Absent = default if an agent wrote it, else firm.";
|
|
1129
1129
|
};
|
|
1130
1130
|
readonly rationale: {
|
|
1131
1131
|
readonly type: "string";
|
|
@@ -1839,7 +1839,7 @@ export declare const OPERATION_CATALOG: readonly [{
|
|
|
1839
1839
|
readonly type: "string";
|
|
1840
1840
|
readonly enum: readonly string[];
|
|
1841
1841
|
};
|
|
1842
|
-
readonly description: "Optional rule subset. Allowed: must-priority-coverage, symbol-coverage, symbol-traceability, symbol-granularity, no-dangling-refs, source-relationship-parity, branch-store-not-compiled, source-path-dangling, no-cycles, required-fields, deprecated-adr-no-successor, superseded-requirement-open, scenario-feasibility, scenario-feasibility-unknown, exception-claim-keys, numeric-string-value, rule-key-arguments-missing, domain-contradictions, logic-coverage, rule-safety, rule-verifiability, query-plan-safety, req-status-vocabulary, strict-fact-shape, strict-req-fact-pairing, predicate-verifiability, strict-readiness, semantic-completeness, related-requirement-unmodeled, proof-contract-symbols, entity-id-style, domain-redundancy, domain-implication, subject-key-identity, subject-key-shape, ontology-quality, exception-unapproved, exception-approval-self-attested, agent-requirement-unapproved, requirement-rationale-missing, symbol-owner-superseded, adr-unlinked, adr-proposed, predicate-schema-conformance, entity-context-missing, entity-context-acknowledged, decision-firm-unreferenced, decision-firm-superseded, supersedes-reason-missing, supersedes-reason-acknowledged, policy-ownership, policy-markers. If omitted, server runs canonical and advisory rules plus the full-KB qualityDiagnostics audit scan, including usage telemetry acceptance when evidence exists; migration rules run only when explicitly selected. Advisory and migration findings are non-blocking qualityDiagnostics. If supplied, server preserves scoped validation and skips the full-KB advisory scan.";
|
|
1842
|
+
readonly description: "Optional rule subset. Allowed: must-priority-coverage, symbol-coverage, symbol-traceability, symbol-granularity, no-dangling-refs, source-relationship-parity, branch-store-not-compiled, source-path-dangling, no-cycles, required-fields, deprecated-adr-no-successor, superseded-requirement-open, scenario-feasibility, scenario-feasibility-unknown, exception-claim-keys, numeric-string-value, rule-key-arguments-missing, domain-contradictions, logic-coverage, rule-safety, rule-verifiability, query-plan-safety, req-status-vocabulary, strict-fact-shape, strict-req-fact-pairing, predicate-verifiability, strict-readiness, semantic-completeness, related-requirement-unmodeled, proof-contract-symbols, entity-id-style, domain-redundancy, domain-implication, subject-key-identity, subject-key-shape, ontology-quality, exception-unapproved, exception-approval-self-attested, agent-requirement-unapproved, requirement-rationale-missing, symbol-owner-superseded, adr-unlinked, adr-proposed, predicate-schema-conformance, entity-context-missing, entity-context-acknowledged, decision-firm-unreferenced, decision-firm-superseded, decision-firm-edited, supersedes-reason-missing, supersedes-reason-acknowledged, policy-ownership, policy-markers. If omitted, server runs canonical and advisory rules plus the full-KB qualityDiagnostics audit scan, including usage telemetry acceptance when evidence exists; migration rules run only when explicitly selected. Advisory and migration findings are non-blocking qualityDiagnostics. If supplied, server preserves scoped validation and skips the full-KB advisory scan.";
|
|
1843
1843
|
};
|
|
1844
1844
|
readonly sourceFiles: {
|
|
1845
1845
|
readonly type: "array";
|
|
@@ -4,11 +4,13 @@ import { type AuthoredEntity } from "./entity-context.js";
|
|
|
4
4
|
export declare const DECISION_FIRM_UNREFERENCED_RULE = "decision-firm-unreferenced";
|
|
5
5
|
/** Only a firm decision may supersede a firm decision (blocking). */
|
|
6
6
|
export declare const DECISION_FIRM_SUPERSEDED_RULE = "decision-firm-superseded";
|
|
7
|
+
/** A firm decision's claim must not change in place without new human words (blocking). */
|
|
8
|
+
export declare const DECISION_FIRM_EDITED_RULE = "decision-firm-edited";
|
|
7
9
|
/** A supersede must say why under "Why this changed" (blocking). */
|
|
8
10
|
export declare const SUPERSEDES_REASON_MISSING_RULE = "supersedes-reason-missing";
|
|
9
11
|
/** How many legacy supersedes the schema 9 migration acknowledged (info). */
|
|
10
12
|
export declare const SUPERSEDES_REASON_ACKNOWLEDGED_RULE = "supersedes-reason-acknowledged";
|
|
11
|
-
export declare const DECISION_FIRMNESS_RULES: readonly ["decision-firm-unreferenced", "decision-firm-superseded", "supersedes-reason-missing", "supersedes-reason-acknowledged"];
|
|
13
|
+
export declare const DECISION_FIRMNESS_RULES: readonly ["decision-firm-unreferenced", "decision-firm-superseded", "decision-firm-edited", "supersedes-reason-missing", "supersedes-reason-acknowledged"];
|
|
12
14
|
/**
|
|
13
15
|
* The schema version from which a decision without `firmness` reads its
|
|
14
16
|
* firmness from its origin, so a person's or imported rule is firm.
|
|
@@ -45,6 +47,14 @@ export declare function evaluateDecisionFirmness(entities: readonly AuthoredEnti
|
|
|
45
47
|
/** Edges the schema 10 migration recorded (`FROM -> TO`). */
|
|
46
48
|
firmSupersedeAcknowledged?: ReadonlySet<string>;
|
|
47
49
|
}>): Violation[];
|
|
50
|
+
/** Reads a workspace-relative file as committed at HEAD; null when absent. */
|
|
51
|
+
export type CommittedFileReader = (relativePath: string) => string | null;
|
|
52
|
+
/**
|
|
53
|
+
* Current firm requirements and ADRs whose claim changed since HEAD without
|
|
54
|
+
* citing new human words: an in-place edit that bypassed kb_upsert's guard.
|
|
55
|
+
* A decision new since HEAD, or one that was not firm there, is not judged.
|
|
56
|
+
*/
|
|
57
|
+
export declare function evaluateFirmDecisionEdits(entities: readonly AuthoredEntity[], edges: ReadonlyMap<string, Set<string>>, changed: ReadonlySet<string>, readCommitted: CommittedFileReader): Violation[];
|
|
48
58
|
/** Read the authored decisions once and evaluate the selected rules. */
|
|
49
59
|
export declare function collectDecisionFirmnessViolations(rules: ReadonlySet<string>, workspaceRoot: string): Violation[];
|
|
50
60
|
//# sourceMappingURL=decision-firmness.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"decision-firmness.d.ts","sourceRoot":"","sources":["../../../src/public/operations/decision-firmness.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"decision-firmness.d.ts","sourceRoot":"","sources":["../../../src/public/operations/decision-firmness.ts"],"names":[],"mappings":"AAwCA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,8BAA8B,CAAC;AAC9D,OAAO,EACL,KAAK,cAAc,EAGpB,MAAM,qBAAqB,CAAC;AAE7B,wEAAwE;AAExE,eAAO,MAAM,+BAA+B,+BAA+B,CAAC;AAC5E,qEAAqE;AAErE,eAAO,MAAM,6BAA6B,6BAA6B,CAAC;AACxE,2FAA2F;AAE3F,eAAO,MAAM,yBAAyB,yBAAyB,CAAC;AAChE,oEAAoE;AAEpE,eAAO,MAAM,8BAA8B,8BAA8B,CAAC;AAC1E,6EAA6E;AAE7E,eAAO,MAAM,mCAAmC,mCACd,CAAC;AAGnC,eAAO,MAAM,uBAAuB,4JAM1B,CAAC;AAOX;;;GAGG;AAEH,eAAO,MAAM,mCAAmC,KAAK,CAAC;AAiBtD,0CAA0C;AAE1C,wBAAgB,oBAAoB,CAAC,aAAa,EAAE,MAAM,GAAG,cAAc,EAAE,CAS5E;AAED;;;;;GAKG;AAEH,wBAAgB,kBAAkB,CAChC,aAAa,EAAE,MAAM,EACrB,QAAQ,EAAE,SAAS,cAAc,EAAE,GAClC,GAAG,CAAC,MAAM,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC,CA+B1B;AASD;;;;;GAKG;AAEH,wBAAgB,qBAAqB,CACnC,QAAQ,EAAE,SAAS,cAAc,EAAE,EACnC,KAAK,EAAE,WAAW,CAAC,MAAM,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC,GACtC,cAAc,EAAE,CAQlB;AAED,2EAA2E;AAE3E,wBAAgB,yBAAyB,CACvC,EAAE,EAAE,MAAM,EACV,KAAK,EAAE,WAAW,CAAC,MAAM,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC,GACtC,MAAM,EAAE,CAIV;AAED;;;;;GAKG;AAEH,wBAAgB,sBAAsB,CACpC,QAAQ,EAAE,SAAS,cAAc,EAAE,EACnC,KAAK,EAAE,WAAW,CAAC,MAAM,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC,GACtC,MAAM,EAAE,CAyBV;AAED,uEAAuE;AAEvE,wBAAgB,wBAAwB,CACtC,QAAQ,EAAE,SAAS,cAAc,EAAE,EACnC,KAAK,EAAE,WAAW,CAAC,MAAM,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC,EACvC,KAAK,GAAE,WAAW,CAAC,MAAM,CAAoC,EAC7D,YAAY,GAAE,WAAW,CAAC,MAAM,CAAa,EAC7C,OAAO,GAAE,QAAQ,CAAC;IAChB,aAAa,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC9B,6DAA6D;IAC7D,yBAAyB,CAAC,EAAE,WAAW,CAAC,MAAM,CAAC,CAAC;CACjD,CAAM,GACN,SAAS,EAAE,CA+Gb;AAED,8EAA8E;AAE9E,MAAM,MAAM,mBAAmB,GAAG,CAAC,YAAY,EAAE,MAAM,KAAK,MAAM,GAAG,IAAI,CAAC;AAkC1E;;;;GAIG;AAEH,wBAAgB,yBAAyB,CACvC,QAAQ,EAAE,SAAS,cAAc,EAAE,EACnC,KAAK,EAAE,WAAW,CAAC,MAAM,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC,EACvC,OAAO,EAAE,WAAW,CAAC,MAAM,CAAC,EAC5B,aAAa,EAAE,mBAAmB,GACjC,SAAS,EAAE,CAwBb;AAED,wEAAwE;AAExE,wBAAgB,iCAAiC,CAC/C,KAAK,EAAE,WAAW,CAAC,MAAM,CAAC,EAC1B,aAAa,EAAE,MAAM,GACpB,SAAS,EAAE,CA+Bb"}
|
|
@@ -15,8 +15,10 @@
|
|
|
15
15
|
You should have received a copy of the GNU Affero General Public License
|
|
16
16
|
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
17
17
|
*/
|
|
18
|
+
import { execFileSync } from "node:child_process";
|
|
18
19
|
import path from "node:path";
|
|
19
20
|
import { MIN_WHY_CHANGED_WORDS, WHY_THIS_CHANGED_HEADING, carriesFirmness, effectiveFirmness, firmnessIsImplicit, firmnessRef, supersedeAcknowledgement, whyThisChangedWords, } from "../../decision-firmness.js";
|
|
21
|
+
import { firmEditGuidance, unapprovedFirmEdit, } from "../../firm-decision-edit.js";
|
|
20
22
|
import { listLaneMarkdownFiles, readText, } from "../../operations/migration/kb-sources.js";
|
|
21
23
|
import { readAllShards } from "../../relationships/shards.js";
|
|
22
24
|
import { readKbManifest } from "../../utils/kb-manifest.js";
|
|
@@ -27,6 +29,9 @@ export const DECISION_FIRM_UNREFERENCED_RULE = "decision-firm-unreferenced";
|
|
|
27
29
|
/** Only a firm decision may supersede a firm decision (blocking). */
|
|
28
30
|
// implements REQ-kb-decision-firmness
|
|
29
31
|
export const DECISION_FIRM_SUPERSEDED_RULE = "decision-firm-superseded";
|
|
32
|
+
/** A firm decision's claim must not change in place without new human words (blocking). */
|
|
33
|
+
// implements REQ-kb-firm-decision-edit
|
|
34
|
+
export const DECISION_FIRM_EDITED_RULE = "decision-firm-edited";
|
|
30
35
|
/** A supersede must say why under "Why this changed" (blocking). */
|
|
31
36
|
// implements REQ-kb-decision-firmness
|
|
32
37
|
export const SUPERSEDES_REASON_MISSING_RULE = "supersedes-reason-missing";
|
|
@@ -37,6 +42,7 @@ export const SUPERSEDES_REASON_ACKNOWLEDGED_RULE = "supersedes-reason-acknowledg
|
|
|
37
42
|
export const DECISION_FIRMNESS_RULES = [
|
|
38
43
|
DECISION_FIRM_UNREFERENCED_RULE,
|
|
39
44
|
DECISION_FIRM_SUPERSEDED_RULE,
|
|
45
|
+
DECISION_FIRM_EDITED_RULE,
|
|
40
46
|
SUPERSEDES_REASON_MISSING_RULE,
|
|
41
47
|
SUPERSEDES_REASON_ACKNOWLEDGED_RULE,
|
|
42
48
|
];
|
|
@@ -265,15 +271,82 @@ export function evaluateDecisionFirmness(entities, edges, rules = new Set(DECISI
|
|
|
265
271
|
}
|
|
266
272
|
return findings;
|
|
267
273
|
}
|
|
274
|
+
/**
|
|
275
|
+
* The decision files whose working copy differs from HEAD, with a reader for
|
|
276
|
+
* their committed content; undefined outside a Git checkout or before the
|
|
277
|
+
* first commit, where there is nothing to compare against.
|
|
278
|
+
*/
|
|
279
|
+
function committedDecisionChanges(workspaceRoot) {
|
|
280
|
+
try {
|
|
281
|
+
const listed = execFileSync("git", ["-C", workspaceRoot, "diff", "--name-only", "--relative", "-z", "HEAD"], { encoding: "utf8", maxBuffer: 64 * 1024 * 1024, stdio: "pipe" });
|
|
282
|
+
const changed = new Set(listed.split("\0").filter((name) => name !== ""));
|
|
283
|
+
const read = (relativePath) => {
|
|
284
|
+
try {
|
|
285
|
+
return execFileSync("git", ["-C", workspaceRoot, "show", `HEAD:./${relativePath}`], { encoding: "utf8", maxBuffer: 64 * 1024 * 1024, stdio: "pipe" });
|
|
286
|
+
}
|
|
287
|
+
catch {
|
|
288
|
+
return null;
|
|
289
|
+
}
|
|
290
|
+
};
|
|
291
|
+
return { changed, read };
|
|
292
|
+
}
|
|
293
|
+
catch {
|
|
294
|
+
return undefined;
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
/**
|
|
298
|
+
* Current firm requirements and ADRs whose claim changed since HEAD without
|
|
299
|
+
* citing new human words: an in-place edit that bypassed kb_upsert's guard.
|
|
300
|
+
* A decision new since HEAD, or one that was not firm there, is not judged.
|
|
301
|
+
*/
|
|
302
|
+
// implements REQ-kb-firm-decision-edit
|
|
303
|
+
export function evaluateFirmDecisionEdits(entities, edges, changed, readCommitted) {
|
|
304
|
+
const superseded = supersededIds(edges);
|
|
305
|
+
const findings = [];
|
|
306
|
+
for (const entity of [...entities].sort((left, right) => left.id.localeCompare(right.id))) {
|
|
307
|
+
if (!changed.has(entity.path))
|
|
308
|
+
continue;
|
|
309
|
+
if (!isCurrentAuthoredEntity(entity, superseded))
|
|
310
|
+
continue;
|
|
311
|
+
const committed = readCommitted(entity.path);
|
|
312
|
+
if (committed === null)
|
|
313
|
+
continue;
|
|
314
|
+
const before = readAuthoredEntity(committed, entity.path);
|
|
315
|
+
if (before === null || before.type !== entity.type)
|
|
316
|
+
continue;
|
|
317
|
+
const fields = unapprovedFirmEdit(before, entity);
|
|
318
|
+
if (fields.length === 0)
|
|
319
|
+
continue;
|
|
320
|
+
findings.push({
|
|
321
|
+
rule: DECISION_FIRM_EDITED_RULE,
|
|
322
|
+
entityId: entity.id,
|
|
323
|
+
description: `${entity.id} is a firm decision whose ${fields.join(", ")} changed since HEAD without citing new human words`,
|
|
324
|
+
suggestion: `${firmEditGuidance(entity.type)}. To undo the edit, restore ${entity.path} from HEAD`,
|
|
325
|
+
source: entity.path,
|
|
326
|
+
evidence: { type: entity.type, changed: fields },
|
|
327
|
+
});
|
|
328
|
+
}
|
|
329
|
+
return findings;
|
|
330
|
+
}
|
|
268
331
|
/** Read the authored decisions once and evaluate the selected rules. */
|
|
269
|
-
// implements REQ-kb-decision-firmness
|
|
332
|
+
// implements REQ-kb-decision-firmness, REQ-kb-firm-decision-edit
|
|
270
333
|
export function collectDecisionFirmnessViolations(rules, workspaceRoot) {
|
|
271
334
|
if (!DECISION_FIRMNESS_RULES.some((rule) => rules.has(rule)))
|
|
272
335
|
return [];
|
|
273
336
|
const entities = listDecisionEntities(workspaceRoot);
|
|
274
337
|
const manifest = readKbManifest(workspaceRoot);
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
338
|
+
const edges = authoredSupersedes(workspaceRoot, entities);
|
|
339
|
+
const committed = rules.has(DECISION_FIRM_EDITED_RULE)
|
|
340
|
+
? committedDecisionChanges(workspaceRoot)
|
|
341
|
+
: undefined;
|
|
342
|
+
const edited = committed
|
|
343
|
+
? evaluateFirmDecisionEdits(entities, edges, committed.changed, committed.read)
|
|
344
|
+
: [];
|
|
345
|
+
return [
|
|
346
|
+
...edited,
|
|
347
|
+
...evaluateDecisionFirmness(entities, edges, rules, new Set(manifest?.supersedesReasonAcknowledged ?? []), {
|
|
348
|
+
schemaVersion: manifest?.schemaVersion ?? null,
|
|
349
|
+
firmSupersedeAcknowledged: new Set(manifest?.firmSupersedeAcknowledged ?? []),
|
|
350
|
+
}),
|
|
351
|
+
];
|
|
279
352
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/** Every selectable kb_check rule name, registry order. */
|
|
2
2
|
export declare const SELECTABLE_RULE_NAMES: readonly string[];
|
|
3
3
|
/** Comma-separated "Allowed:" fragment for the kb_check rules description. */
|
|
4
|
-
export declare const SELECTABLE_RULES_ALLOWED = "must-priority-coverage, symbol-coverage, symbol-traceability, symbol-granularity, no-dangling-refs, source-relationship-parity, branch-store-not-compiled, source-path-dangling, no-cycles, required-fields, deprecated-adr-no-successor, superseded-requirement-open, scenario-feasibility, scenario-feasibility-unknown, exception-claim-keys, numeric-string-value, rule-key-arguments-missing, domain-contradictions, logic-coverage, rule-safety, rule-verifiability, query-plan-safety, req-status-vocabulary, strict-fact-shape, strict-req-fact-pairing, predicate-verifiability, strict-readiness, semantic-completeness, related-requirement-unmodeled, proof-contract-symbols, entity-id-style, domain-redundancy, domain-implication, subject-key-identity, subject-key-shape, ontology-quality, exception-unapproved, exception-approval-self-attested, agent-requirement-unapproved, requirement-rationale-missing, symbol-owner-superseded, adr-unlinked, adr-proposed, predicate-schema-conformance, entity-context-missing, entity-context-acknowledged, decision-firm-unreferenced, decision-firm-superseded, supersedes-reason-missing, supersedes-reason-acknowledged, policy-ownership, policy-markers";
|
|
4
|
+
export declare const SELECTABLE_RULES_ALLOWED = "must-priority-coverage, symbol-coverage, symbol-traceability, symbol-granularity, no-dangling-refs, source-relationship-parity, branch-store-not-compiled, source-path-dangling, no-cycles, required-fields, deprecated-adr-no-successor, superseded-requirement-open, scenario-feasibility, scenario-feasibility-unknown, exception-claim-keys, numeric-string-value, rule-key-arguments-missing, domain-contradictions, logic-coverage, rule-safety, rule-verifiability, query-plan-safety, req-status-vocabulary, strict-fact-shape, strict-req-fact-pairing, predicate-verifiability, strict-readiness, semantic-completeness, related-requirement-unmodeled, proof-contract-symbols, entity-id-style, domain-redundancy, domain-implication, subject-key-identity, subject-key-shape, ontology-quality, exception-unapproved, exception-approval-self-attested, agent-requirement-unapproved, requirement-rationale-missing, symbol-owner-superseded, adr-unlinked, adr-proposed, predicate-schema-conformance, entity-context-missing, entity-context-acknowledged, decision-firm-unreferenced, decision-firm-superseded, decision-firm-edited, supersedes-reason-missing, supersedes-reason-acknowledged, policy-ownership, policy-markers";
|
|
5
5
|
//# sourceMappingURL=check-rules.generated.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"check-rules.generated.d.ts","sourceRoot":"","sources":["../../../../src/public/operations/specs/check-rules.generated.ts"],"names":[],"mappings":"AAUA,2DAA2D;AAC3D,eAAO,MAAM,qBAAqB,EAAE,SAAS,MAAM,
|
|
1
|
+
{"version":3,"file":"check-rules.generated.d.ts","sourceRoot":"","sources":["../../../../src/public/operations/specs/check-rules.generated.ts"],"names":[],"mappings":"AAUA,2DAA2D;AAC3D,eAAO,MAAM,qBAAqB,EAAE,SAAS,MAAM,EAsDlD,CAAC;AAGF,8EAA8E;AAC9E,eAAO,MAAM,wBAAwB,srCACgpC,CAAC"}
|
|
@@ -57,6 +57,7 @@ export const SELECTABLE_RULE_NAMES = [
|
|
|
57
57
|
"entity-context-acknowledged",
|
|
58
58
|
"decision-firm-unreferenced",
|
|
59
59
|
"decision-firm-superseded",
|
|
60
|
+
"decision-firm-edited",
|
|
60
61
|
"supersedes-reason-missing",
|
|
61
62
|
"supersedes-reason-acknowledged",
|
|
62
63
|
"policy-ownership",
|
|
@@ -64,4 +65,4 @@ export const SELECTABLE_RULE_NAMES = [
|
|
|
64
65
|
];
|
|
65
66
|
// implements REQ-006
|
|
66
67
|
/** Comma-separated "Allowed:" fragment for the kb_check rules description. */
|
|
67
|
-
export const SELECTABLE_RULES_ALLOWED = "must-priority-coverage, symbol-coverage, symbol-traceability, symbol-granularity, no-dangling-refs, source-relationship-parity, branch-store-not-compiled, source-path-dangling, no-cycles, required-fields, deprecated-adr-no-successor, superseded-requirement-open, scenario-feasibility, scenario-feasibility-unknown, exception-claim-keys, numeric-string-value, rule-key-arguments-missing, domain-contradictions, logic-coverage, rule-safety, rule-verifiability, query-plan-safety, req-status-vocabulary, strict-fact-shape, strict-req-fact-pairing, predicate-verifiability, strict-readiness, semantic-completeness, related-requirement-unmodeled, proof-contract-symbols, entity-id-style, domain-redundancy, domain-implication, subject-key-identity, subject-key-shape, ontology-quality, exception-unapproved, exception-approval-self-attested, agent-requirement-unapproved, requirement-rationale-missing, symbol-owner-superseded, adr-unlinked, adr-proposed, predicate-schema-conformance, entity-context-missing, entity-context-acknowledged, decision-firm-unreferenced, decision-firm-superseded, supersedes-reason-missing, supersedes-reason-acknowledged, policy-ownership, policy-markers";
|
|
68
|
+
export const SELECTABLE_RULES_ALLOWED = "must-priority-coverage, symbol-coverage, symbol-traceability, symbol-granularity, no-dangling-refs, source-relationship-parity, branch-store-not-compiled, source-path-dangling, no-cycles, required-fields, deprecated-adr-no-successor, superseded-requirement-open, scenario-feasibility, scenario-feasibility-unknown, exception-claim-keys, numeric-string-value, rule-key-arguments-missing, domain-contradictions, logic-coverage, rule-safety, rule-verifiability, query-plan-safety, req-status-vocabulary, strict-fact-shape, strict-req-fact-pairing, predicate-verifiability, strict-readiness, semantic-completeness, related-requirement-unmodeled, proof-contract-symbols, entity-id-style, domain-redundancy, domain-implication, subject-key-identity, subject-key-shape, ontology-quality, exception-unapproved, exception-approval-self-attested, agent-requirement-unapproved, requirement-rationale-missing, symbol-owner-superseded, adr-unlinked, adr-proposed, predicate-schema-conformance, entity-context-missing, entity-context-acknowledged, decision-firm-unreferenced, decision-firm-superseded, decision-firm-edited, supersedes-reason-missing, supersedes-reason-acknowledged, policy-ownership, policy-markers";
|
|
@@ -12,7 +12,7 @@ export declare const checkSpec: {
|
|
|
12
12
|
readonly type: "string";
|
|
13
13
|
readonly enum: readonly string[];
|
|
14
14
|
};
|
|
15
|
-
readonly description: "Optional rule subset. Allowed: must-priority-coverage, symbol-coverage, symbol-traceability, symbol-granularity, no-dangling-refs, source-relationship-parity, branch-store-not-compiled, source-path-dangling, no-cycles, required-fields, deprecated-adr-no-successor, superseded-requirement-open, scenario-feasibility, scenario-feasibility-unknown, exception-claim-keys, numeric-string-value, rule-key-arguments-missing, domain-contradictions, logic-coverage, rule-safety, rule-verifiability, query-plan-safety, req-status-vocabulary, strict-fact-shape, strict-req-fact-pairing, predicate-verifiability, strict-readiness, semantic-completeness, related-requirement-unmodeled, proof-contract-symbols, entity-id-style, domain-redundancy, domain-implication, subject-key-identity, subject-key-shape, ontology-quality, exception-unapproved, exception-approval-self-attested, agent-requirement-unapproved, requirement-rationale-missing, symbol-owner-superseded, adr-unlinked, adr-proposed, predicate-schema-conformance, entity-context-missing, entity-context-acknowledged, decision-firm-unreferenced, decision-firm-superseded, supersedes-reason-missing, supersedes-reason-acknowledged, policy-ownership, policy-markers. If omitted, server runs canonical and advisory rules plus the full-KB qualityDiagnostics audit scan, including usage telemetry acceptance when evidence exists; migration rules run only when explicitly selected. Advisory and migration findings are non-blocking qualityDiagnostics. If supplied, server preserves scoped validation and skips the full-KB advisory scan.";
|
|
15
|
+
readonly description: "Optional rule subset. Allowed: must-priority-coverage, symbol-coverage, symbol-traceability, symbol-granularity, no-dangling-refs, source-relationship-parity, branch-store-not-compiled, source-path-dangling, no-cycles, required-fields, deprecated-adr-no-successor, superseded-requirement-open, scenario-feasibility, scenario-feasibility-unknown, exception-claim-keys, numeric-string-value, rule-key-arguments-missing, domain-contradictions, logic-coverage, rule-safety, rule-verifiability, query-plan-safety, req-status-vocabulary, strict-fact-shape, strict-req-fact-pairing, predicate-verifiability, strict-readiness, semantic-completeness, related-requirement-unmodeled, proof-contract-symbols, entity-id-style, domain-redundancy, domain-implication, subject-key-identity, subject-key-shape, ontology-quality, exception-unapproved, exception-approval-self-attested, agent-requirement-unapproved, requirement-rationale-missing, symbol-owner-superseded, adr-unlinked, adr-proposed, predicate-schema-conformance, entity-context-missing, entity-context-acknowledged, decision-firm-unreferenced, decision-firm-superseded, decision-firm-edited, supersedes-reason-missing, supersedes-reason-acknowledged, policy-ownership, policy-markers. If omitted, server runs canonical and advisory rules plus the full-KB qualityDiagnostics audit scan, including usage telemetry acceptance when evidence exists; migration rules run only when explicitly selected. Advisory and migration findings are non-blocking qualityDiagnostics. If supplied, server preserves scoped validation and skips the full-KB advisory scan.";
|
|
16
16
|
};
|
|
17
17
|
readonly sourceFiles: {
|
|
18
18
|
readonly type: "array";
|
|
@@ -121,7 +121,7 @@ export declare const ENTITY_PROPERTIES_SCHEMA: {
|
|
|
121
121
|
readonly firmness: {
|
|
122
122
|
readonly type: "string";
|
|
123
123
|
readonly enum: readonly ["firm", "default", "exploratory"];
|
|
124
|
-
readonly description: "req/adr only. firm = a human stated it as a rule (needs origin.ref to their words);
|
|
124
|
+
readonly description: "req/adr only. firm = a human stated it as a rule (needs origin.ref to their words); its title, meaning or firmness changes only with new human words (a new origin.ref) or by supersede. default = delegated or agent choice; exploratory = flagged guess. Absent = default if an agent wrote it, else firm.";
|
|
125
125
|
};
|
|
126
126
|
readonly rationale: {
|
|
127
127
|
readonly type: "string";
|
|
@@ -105,7 +105,7 @@ export const ENTITY_PROPERTIES_SCHEMA = {
|
|
|
105
105
|
firmness: {
|
|
106
106
|
type: "string",
|
|
107
107
|
enum: ["firm", "default", "exploratory"],
|
|
108
|
-
description: "req/adr only. firm = a human stated it as a rule (needs origin.ref to their words);
|
|
108
|
+
description: "req/adr only. firm = a human stated it as a rule (needs origin.ref to their words); its title, meaning or firmness changes only with new human words (a new origin.ref) or by supersede. default = delegated or agent choice; exploratory = flagged guess. Absent = default if an agent wrote it, else firm.",
|
|
109
109
|
},
|
|
110
110
|
rationale: {
|
|
111
111
|
type: "string",
|
|
@@ -138,7 +138,7 @@ export declare const upsertSpec: {
|
|
|
138
138
|
readonly firmness: {
|
|
139
139
|
readonly type: "string";
|
|
140
140
|
readonly enum: readonly ["firm", "default", "exploratory"];
|
|
141
|
-
readonly description: "req/adr only. firm = a human stated it as a rule (needs origin.ref to their words);
|
|
141
|
+
readonly description: "req/adr only. firm = a human stated it as a rule (needs origin.ref to their words); its title, meaning or firmness changes only with new human words (a new origin.ref) or by supersede. default = delegated or agent choice; exploratory = flagged guess. Absent = default if an agent wrote it, else firm.";
|
|
142
142
|
};
|
|
143
143
|
readonly rationale: {
|
|
144
144
|
readonly type: "string";
|
|
@@ -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.
|
|
5
|
+
version: 2.18.0
|
|
6
6
|
kibiCompatibility: ">=1.0.0"
|
|
7
7
|
tags:
|
|
8
8
|
- kibi
|
|
@@ -368,7 +368,11 @@ Search answers, `kb_query` rows and hook snippets show it (snippets label only
|
|
|
368
368
|
wrote it (`origin.kind: agent`): rules people wrote, imported or carried
|
|
369
369
|
over from before origins existed are theirs. `kb_check` blocks an explicit
|
|
370
370
|
`firmness: firm` without `origin.ref` to their words, and a replacement of
|
|
371
|
-
a firm decision that is not itself firm.
|
|
371
|
+
a firm decision that is not itself firm. Never edit a firm decision's
|
|
372
|
+
title, statement or (ADR) body in place, by `kb_upsert` or on disk:
|
|
373
|
+
`kb_upsert` refuses it and `kb_check` reports `decision-firm-edited`
|
|
374
|
+
unless the edit cites new human words in `origin.ref`. Context sections,
|
|
375
|
+
tags, status and links stay editable.
|
|
372
376
|
- `default`: a delegated or agent choice (`firmness: default`, or an
|
|
373
377
|
agent-written decision without `firmness`). Follow it; supersede it only on
|
|
374
378
|
evidence that it is wrong (a failing test, a measured cost, a constraint it
|
|
@@ -381,7 +385,8 @@ conflict a person has to decide. Name the rule (id and what it requires) and
|
|
|
381
385
|
ask whether to change it; do not implement the request and do not supersede
|
|
382
386
|
the rule. Go ahead only when the person has said to change that rule, in this
|
|
383
387
|
task or in reply: then supersede it with `firmness: firm` and `origin.ref` to
|
|
384
|
-
their words,
|
|
388
|
+
their words (or, for a correction they asked for in place, update it with
|
|
389
|
+
`origin.ref` to those words), and say in the final answer which rule changed.
|
|
385
390
|
|
|
386
391
|
Set `firmness` from what a human actually said: only their stated rule is
|
|
387
392
|
`firm` (with `origin.ref`); "do whatever" or your own pick is `default`; a
|
|
@@ -243,6 +243,11 @@ export declare const GENERATED_RULES: readonly [{
|
|
|
243
243
|
readonly description: "A requirement or ADR that supersedes a firm decision, retired or not, must itself be firm (and so carry a ref to the human who changed the rule); a default or exploratory replacement cannot overrule a firm decision";
|
|
244
244
|
readonly enforcementClass: "canonical";
|
|
245
245
|
readonly category: "lifecycle";
|
|
246
|
+
}, {
|
|
247
|
+
readonly name: "decision-firm-edited";
|
|
248
|
+
readonly description: "A current requirement or ADR that was firm at HEAD must not have its claim (title, stated meaning, the statement its body derives, an ADR's body, or its firmness) changed in place unless the edit cites new human words (a changed origin.ref, or approval_ref on a requirement); supersede it with a firm decision instead";
|
|
249
|
+
readonly enforcementClass: "canonical";
|
|
250
|
+
readonly category: "lifecycle";
|
|
246
251
|
}, {
|
|
247
252
|
readonly name: "supersedes-reason-missing";
|
|
248
253
|
readonly description: "A current requirement or ADR that supersedes another must explain why under a 'Why this changed' heading (at least 12 words of prose naming the evidence and answering the old decision's reasoning); supersede edges recorded before schema 9 are acknowledged in the manifest and exempt";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"rule-registry.generated.d.ts","sourceRoot":"","sources":["../../src/utils/rule-registry.generated.ts"],"names":[],"mappings":"AAUA,eAAO,MAAM,eAAe
|
|
1
|
+
{"version":3,"file":"rule-registry.generated.d.ts","sourceRoot":"","sources":["../../src/utils/rule-registry.generated.ts"],"names":[],"mappings":"AAUA,eAAO,MAAM,eAAe;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAuXlB,CAAC"}
|
|
@@ -301,6 +301,12 @@ export const GENERATED_RULES = [
|
|
|
301
301
|
enforcementClass: "canonical",
|
|
302
302
|
category: "lifecycle",
|
|
303
303
|
},
|
|
304
|
+
{
|
|
305
|
+
name: "decision-firm-edited",
|
|
306
|
+
description: "A current requirement or ADR that was firm at HEAD must not have its claim (title, stated meaning, the statement its body derives, an ADR's body, or its firmness) changed in place unless the edit cites new human words (a changed origin.ref, or approval_ref on a requirement); supersede it with a firm decision instead",
|
|
307
|
+
enforcementClass: "canonical",
|
|
308
|
+
category: "lifecycle",
|
|
309
|
+
},
|
|
304
310
|
{
|
|
305
311
|
name: "supersedes-reason-missing",
|
|
306
312
|
description: "A current requirement or ADR that supersedes another must explain why under a 'Why this changed' heading (at least 12 words of prose naming the evidence and answering the old decision's reasoning); supersede edges recorded before schema 9 are acknowledged in the manifest and exempt",
|
package/package.json
CHANGED
|
@@ -16,6 +16,7 @@
|
|
|
16
16
|
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
17
17
|
*/
|
|
18
18
|
|
|
19
|
+
import { execFileSync } from "node:child_process";
|
|
19
20
|
import path from "node:path";
|
|
20
21
|
import {
|
|
21
22
|
MIN_WHY_CHANGED_WORDS,
|
|
@@ -27,6 +28,10 @@ import {
|
|
|
27
28
|
supersedeAcknowledgement,
|
|
28
29
|
whyThisChangedWords,
|
|
29
30
|
} from "../../decision-firmness.js";
|
|
31
|
+
import {
|
|
32
|
+
firmEditGuidance,
|
|
33
|
+
unapprovedFirmEdit,
|
|
34
|
+
} from "../../firm-decision-edit.js";
|
|
30
35
|
import {
|
|
31
36
|
listLaneMarkdownFiles,
|
|
32
37
|
readText,
|
|
@@ -46,6 +51,9 @@ export const DECISION_FIRM_UNREFERENCED_RULE = "decision-firm-unreferenced";
|
|
|
46
51
|
/** Only a firm decision may supersede a firm decision (blocking). */
|
|
47
52
|
// implements REQ-kb-decision-firmness
|
|
48
53
|
export const DECISION_FIRM_SUPERSEDED_RULE = "decision-firm-superseded";
|
|
54
|
+
/** A firm decision's claim must not change in place without new human words (blocking). */
|
|
55
|
+
// implements REQ-kb-firm-decision-edit
|
|
56
|
+
export const DECISION_FIRM_EDITED_RULE = "decision-firm-edited";
|
|
49
57
|
/** A supersede must say why under "Why this changed" (blocking). */
|
|
50
58
|
// implements REQ-kb-decision-firmness
|
|
51
59
|
export const SUPERSEDES_REASON_MISSING_RULE = "supersedes-reason-missing";
|
|
@@ -58,6 +66,7 @@ export const SUPERSEDES_REASON_ACKNOWLEDGED_RULE =
|
|
|
58
66
|
export const DECISION_FIRMNESS_RULES = [
|
|
59
67
|
DECISION_FIRM_UNREFERENCED_RULE,
|
|
60
68
|
DECISION_FIRM_SUPERSEDED_RULE,
|
|
69
|
+
DECISION_FIRM_EDITED_RULE,
|
|
61
70
|
SUPERSEDES_REASON_MISSING_RULE,
|
|
62
71
|
SUPERSEDES_REASON_ACKNOWLEDGED_RULE,
|
|
63
72
|
] as const;
|
|
@@ -345,8 +354,81 @@ export function evaluateDecisionFirmness(
|
|
|
345
354
|
return findings;
|
|
346
355
|
}
|
|
347
356
|
|
|
357
|
+
/** Reads a workspace-relative file as committed at HEAD; null when absent. */
|
|
358
|
+
// implements REQ-kb-firm-decision-edit
|
|
359
|
+
export type CommittedFileReader = (relativePath: string) => string | null;
|
|
360
|
+
|
|
361
|
+
/**
|
|
362
|
+
* The decision files whose working copy differs from HEAD, with a reader for
|
|
363
|
+
* their committed content; undefined outside a Git checkout or before the
|
|
364
|
+
* first commit, where there is nothing to compare against.
|
|
365
|
+
*/
|
|
366
|
+
function committedDecisionChanges(
|
|
367
|
+
workspaceRoot: string,
|
|
368
|
+
): { changed: Set<string>; read: CommittedFileReader } | undefined {
|
|
369
|
+
try {
|
|
370
|
+
const listed = execFileSync(
|
|
371
|
+
"git",
|
|
372
|
+
["-C", workspaceRoot, "diff", "--name-only", "--relative", "-z", "HEAD"],
|
|
373
|
+
{ encoding: "utf8", maxBuffer: 64 * 1024 * 1024, stdio: "pipe" },
|
|
374
|
+
);
|
|
375
|
+
const changed = new Set(listed.split("\0").filter((name) => name !== ""));
|
|
376
|
+
const read: CommittedFileReader = (relativePath) => {
|
|
377
|
+
try {
|
|
378
|
+
return execFileSync(
|
|
379
|
+
"git",
|
|
380
|
+
["-C", workspaceRoot, "show", `HEAD:./${relativePath}`],
|
|
381
|
+
{ encoding: "utf8", maxBuffer: 64 * 1024 * 1024, stdio: "pipe" },
|
|
382
|
+
);
|
|
383
|
+
} catch {
|
|
384
|
+
return null;
|
|
385
|
+
}
|
|
386
|
+
};
|
|
387
|
+
return { changed, read };
|
|
388
|
+
} catch {
|
|
389
|
+
return undefined;
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
/**
|
|
394
|
+
* Current firm requirements and ADRs whose claim changed since HEAD without
|
|
395
|
+
* citing new human words: an in-place edit that bypassed kb_upsert's guard.
|
|
396
|
+
* A decision new since HEAD, or one that was not firm there, is not judged.
|
|
397
|
+
*/
|
|
398
|
+
// implements REQ-kb-firm-decision-edit
|
|
399
|
+
export function evaluateFirmDecisionEdits(
|
|
400
|
+
entities: readonly AuthoredEntity[],
|
|
401
|
+
edges: ReadonlyMap<string, Set<string>>,
|
|
402
|
+
changed: ReadonlySet<string>,
|
|
403
|
+
readCommitted: CommittedFileReader,
|
|
404
|
+
): Violation[] {
|
|
405
|
+
const superseded = supersededIds(edges);
|
|
406
|
+
const findings: Violation[] = [];
|
|
407
|
+
for (const entity of [...entities].sort((left, right) =>
|
|
408
|
+
left.id.localeCompare(right.id),
|
|
409
|
+
)) {
|
|
410
|
+
if (!changed.has(entity.path)) continue;
|
|
411
|
+
if (!isCurrentAuthoredEntity(entity, superseded)) continue;
|
|
412
|
+
const committed = readCommitted(entity.path);
|
|
413
|
+
if (committed === null) continue;
|
|
414
|
+
const before = readAuthoredEntity(committed, entity.path);
|
|
415
|
+
if (before === null || before.type !== entity.type) continue;
|
|
416
|
+
const fields = unapprovedFirmEdit(before, entity);
|
|
417
|
+
if (fields.length === 0) continue;
|
|
418
|
+
findings.push({
|
|
419
|
+
rule: DECISION_FIRM_EDITED_RULE,
|
|
420
|
+
entityId: entity.id,
|
|
421
|
+
description: `${entity.id} is a firm decision whose ${fields.join(", ")} changed since HEAD without citing new human words`,
|
|
422
|
+
suggestion: `${firmEditGuidance(entity.type)}. To undo the edit, restore ${entity.path} from HEAD`,
|
|
423
|
+
source: entity.path,
|
|
424
|
+
evidence: { type: entity.type, changed: fields },
|
|
425
|
+
});
|
|
426
|
+
}
|
|
427
|
+
return findings;
|
|
428
|
+
}
|
|
429
|
+
|
|
348
430
|
/** Read the authored decisions once and evaluate the selected rules. */
|
|
349
|
-
// implements REQ-kb-decision-firmness
|
|
431
|
+
// implements REQ-kb-decision-firmness, REQ-kb-firm-decision-edit
|
|
350
432
|
export function collectDecisionFirmnessViolations(
|
|
351
433
|
rules: ReadonlySet<string>,
|
|
352
434
|
workspaceRoot: string,
|
|
@@ -354,16 +436,31 @@ export function collectDecisionFirmnessViolations(
|
|
|
354
436
|
if (!DECISION_FIRMNESS_RULES.some((rule) => rules.has(rule))) return [];
|
|
355
437
|
const entities = listDecisionEntities(workspaceRoot);
|
|
356
438
|
const manifest = readKbManifest(workspaceRoot);
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
439
|
+
const edges = authoredSupersedes(workspaceRoot, entities);
|
|
440
|
+
const committed = rules.has(DECISION_FIRM_EDITED_RULE)
|
|
441
|
+
? committedDecisionChanges(workspaceRoot)
|
|
442
|
+
: undefined;
|
|
443
|
+
const edited = committed
|
|
444
|
+
? evaluateFirmDecisionEdits(
|
|
445
|
+
entities,
|
|
446
|
+
edges,
|
|
447
|
+
committed.changed,
|
|
448
|
+
committed.read,
|
|
449
|
+
)
|
|
450
|
+
: [];
|
|
451
|
+
return [
|
|
452
|
+
...edited,
|
|
453
|
+
...evaluateDecisionFirmness(
|
|
454
|
+
entities,
|
|
455
|
+
edges,
|
|
456
|
+
rules,
|
|
457
|
+
new Set(manifest?.supersedesReasonAcknowledged ?? []),
|
|
458
|
+
{
|
|
459
|
+
schemaVersion: manifest?.schemaVersion ?? null,
|
|
460
|
+
firmSupersedeAcknowledged: new Set(
|
|
461
|
+
manifest?.firmSupersedeAcknowledged ?? [],
|
|
462
|
+
),
|
|
463
|
+
},
|
|
464
|
+
),
|
|
465
|
+
];
|
|
369
466
|
}
|
|
@@ -58,6 +58,7 @@ export const SELECTABLE_RULE_NAMES: readonly string[] = [
|
|
|
58
58
|
"entity-context-acknowledged",
|
|
59
59
|
"decision-firm-unreferenced",
|
|
60
60
|
"decision-firm-superseded",
|
|
61
|
+
"decision-firm-edited",
|
|
61
62
|
"supersedes-reason-missing",
|
|
62
63
|
"supersedes-reason-acknowledged",
|
|
63
64
|
"policy-ownership",
|
|
@@ -67,4 +68,4 @@ export const SELECTABLE_RULE_NAMES: readonly string[] = [
|
|
|
67
68
|
// implements REQ-006
|
|
68
69
|
/** Comma-separated "Allowed:" fragment for the kb_check rules description. */
|
|
69
70
|
export const SELECTABLE_RULES_ALLOWED =
|
|
70
|
-
"must-priority-coverage, symbol-coverage, symbol-traceability, symbol-granularity, no-dangling-refs, source-relationship-parity, branch-store-not-compiled, source-path-dangling, no-cycles, required-fields, deprecated-adr-no-successor, superseded-requirement-open, scenario-feasibility, scenario-feasibility-unknown, exception-claim-keys, numeric-string-value, rule-key-arguments-missing, domain-contradictions, logic-coverage, rule-safety, rule-verifiability, query-plan-safety, req-status-vocabulary, strict-fact-shape, strict-req-fact-pairing, predicate-verifiability, strict-readiness, semantic-completeness, related-requirement-unmodeled, proof-contract-symbols, entity-id-style, domain-redundancy, domain-implication, subject-key-identity, subject-key-shape, ontology-quality, exception-unapproved, exception-approval-self-attested, agent-requirement-unapproved, requirement-rationale-missing, symbol-owner-superseded, adr-unlinked, adr-proposed, predicate-schema-conformance, entity-context-missing, entity-context-acknowledged, decision-firm-unreferenced, decision-firm-superseded, supersedes-reason-missing, supersedes-reason-acknowledged, policy-ownership, policy-markers";
|
|
71
|
+
"must-priority-coverage, symbol-coverage, symbol-traceability, symbol-granularity, no-dangling-refs, source-relationship-parity, branch-store-not-compiled, source-path-dangling, no-cycles, required-fields, deprecated-adr-no-successor, superseded-requirement-open, scenario-feasibility, scenario-feasibility-unknown, exception-claim-keys, numeric-string-value, rule-key-arguments-missing, domain-contradictions, logic-coverage, rule-safety, rule-verifiability, query-plan-safety, req-status-vocabulary, strict-fact-shape, strict-req-fact-pairing, predicate-verifiability, strict-readiness, semantic-completeness, related-requirement-unmodeled, proof-contract-symbols, entity-id-style, domain-redundancy, domain-implication, subject-key-identity, subject-key-shape, ontology-quality, exception-unapproved, exception-approval-self-attested, agent-requirement-unapproved, requirement-rationale-missing, symbol-owner-superseded, adr-unlinked, adr-proposed, predicate-schema-conformance, entity-context-missing, entity-context-acknowledged, decision-firm-unreferenced, decision-firm-superseded, decision-firm-edited, supersedes-reason-missing, supersedes-reason-acknowledged, policy-ownership, policy-markers";
|
|
@@ -129,7 +129,7 @@ export const ENTITY_PROPERTIES_SCHEMA = {
|
|
|
129
129
|
type: "string",
|
|
130
130
|
enum: ["firm", "default", "exploratory"],
|
|
131
131
|
description:
|
|
132
|
-
"req/adr only. firm = a human stated it as a rule (needs origin.ref to their words);
|
|
132
|
+
"req/adr only. firm = a human stated it as a rule (needs origin.ref to their words); its title, meaning or firmness changes only with new human words (a new origin.ref) or by supersede. default = delegated or agent choice; exploratory = flagged guess. Absent = default if an agent wrote it, else firm.",
|
|
133
133
|
},
|
|
134
134
|
rationale: {
|
|
135
135
|
type: "string",
|
|
@@ -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.
|
|
5
|
+
version: 2.18.0
|
|
6
6
|
kibiCompatibility: ">=1.0.0"
|
|
7
7
|
tags:
|
|
8
8
|
- kibi
|
|
@@ -368,7 +368,11 @@ Search answers, `kb_query` rows and hook snippets show it (snippets label only
|
|
|
368
368
|
wrote it (`origin.kind: agent`): rules people wrote, imported or carried
|
|
369
369
|
over from before origins existed are theirs. `kb_check` blocks an explicit
|
|
370
370
|
`firmness: firm` without `origin.ref` to their words, and a replacement of
|
|
371
|
-
a firm decision that is not itself firm.
|
|
371
|
+
a firm decision that is not itself firm. Never edit a firm decision's
|
|
372
|
+
title, statement or (ADR) body in place, by `kb_upsert` or on disk:
|
|
373
|
+
`kb_upsert` refuses it and `kb_check` reports `decision-firm-edited`
|
|
374
|
+
unless the edit cites new human words in `origin.ref`. Context sections,
|
|
375
|
+
tags, status and links stay editable.
|
|
372
376
|
- `default`: a delegated or agent choice (`firmness: default`, or an
|
|
373
377
|
agent-written decision without `firmness`). Follow it; supersede it only on
|
|
374
378
|
evidence that it is wrong (a failing test, a measured cost, a constraint it
|
|
@@ -381,7 +385,8 @@ conflict a person has to decide. Name the rule (id and what it requires) and
|
|
|
381
385
|
ask whether to change it; do not implement the request and do not supersede
|
|
382
386
|
the rule. Go ahead only when the person has said to change that rule, in this
|
|
383
387
|
task or in reply: then supersede it with `firmness: firm` and `origin.ref` to
|
|
384
|
-
their words,
|
|
388
|
+
their words (or, for a correction they asked for in place, update it with
|
|
389
|
+
`origin.ref` to those words), and say in the final answer which rule changed.
|
|
385
390
|
|
|
386
391
|
Set `firmness` from what a human actually said: only their stated rule is
|
|
387
392
|
`firm` (with `origin.ref`); "do whatever" or your own pick is `default`; a
|