kibi-cli 2.31.1 → 2.32.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/cli-register-proof.d.ts.map +1 -1
- package/dist/cli-register-proof.js +19 -7
- package/dist/cli-register-reporting.d.ts.map +1 -1
- package/dist/cli-register-reporting.js +2 -0
- package/dist/commands/check.d.ts +1 -0
- package/dist/commands/check.d.ts.map +1 -1
- package/dist/commands/check.js +14 -5
- package/dist/commands/discovery-table.d.ts.map +1 -1
- package/dist/commands/discovery-table.js +51 -4
- package/dist/commands/doctor.js +19 -4
- package/dist/commands/migrate.d.ts.map +1 -1
- package/dist/commands/migrate.js +89 -3
- package/dist/commands/prove.d.ts +48 -1
- package/dist/commands/prove.d.ts.map +1 -1
- package/dist/commands/prove.js +293 -8
- package/dist/commands/report.d.ts +1 -0
- package/dist/commands/report.d.ts.map +1 -1
- package/dist/commands/report.js +70 -5
- package/dist/commands/signoff.d.ts.map +1 -1
- package/dist/commands/signoff.js +20 -1
- package/dist/commands/sync/manifest.d.ts.map +1 -1
- package/dist/commands/sync/manifest.js +20 -4
- package/dist/commands/sync/persistence.d.ts.map +1 -1
- package/dist/commands/sync/persistence.js +16 -5
- package/dist/diagnostics.js +1 -1
- package/dist/engine.js +1 -1
- package/dist/extractors/markdown.d.ts +3 -1
- package/dist/extractors/markdown.d.ts.map +1 -1
- package/dist/extractors/markdown.js +17 -0
- package/dist/operations/bootstrap/candidate-helpers.d.ts +3 -0
- package/dist/operations/bootstrap/candidate-helpers.d.ts.map +1 -1
- package/dist/operations/bootstrap/candidate-helpers.js +4 -0
- package/dist/operations/migration/evidence-floor.d.ts +41 -0
- package/dist/operations/migration/evidence-floor.d.ts.map +1 -0
- package/dist/operations/migration/evidence-floor.js +230 -0
- package/dist/operations/migration/proof-contract-claims.d.ts +59 -0
- package/dist/operations/migration/proof-contract-claims.d.ts.map +1 -0
- package/dist/operations/migration/proof-contract-claims.js +182 -0
- package/dist/operations/migration/schema6.d.ts +6 -0
- package/dist/operations/migration/schema6.d.ts.map +1 -1
- package/dist/operations/migration/schema6.js +94 -0
- package/dist/operations/mutation/omitted-properties.d.ts +11 -0
- package/dist/operations/mutation/omitted-properties.d.ts.map +1 -0
- package/dist/operations/mutation/omitted-properties.js +76 -0
- package/dist/operations/mutation/serialization.d.ts.map +1 -1
- package/dist/operations/mutation/serialization.js +1 -0
- package/dist/operations/mutation/strict-fact.d.ts.map +1 -1
- package/dist/operations/mutation/strict-fact.js +6 -2
- package/dist/operations/mutation/symbol-granularity.d.ts.map +1 -1
- package/dist/operations/mutation/symbol-granularity.js +5 -3
- package/dist/operations/mutation/upsert.d.ts.map +1 -1
- package/dist/operations/mutation/upsert.js +7 -1
- package/dist/operations/mutation/validation.d.ts +2 -0
- package/dist/operations/mutation/validation.d.ts.map +1 -1
- package/dist/operations/mutation/validation.js +4 -0
- package/dist/operations/proof/claim-bindings.d.ts +23 -0
- package/dist/operations/proof/claim-bindings.d.ts.map +1 -0
- package/dist/operations/proof/claim-bindings.js +211 -0
- package/dist/operations/proof/code-certificates.d.ts +135 -0
- package/dist/operations/proof/code-certificates.d.ts.map +1 -0
- package/dist/operations/proof/code-certificates.js +785 -0
- package/dist/operations/proof/code-scope.d.ts +11 -1
- package/dist/operations/proof/code-scope.d.ts.map +1 -1
- package/dist/operations/proof/code-scope.js +20 -2
- package/dist/operations/proof/compact-receipts.d.ts.map +1 -1
- package/dist/operations/proof/compact-receipts.js +4 -3
- package/dist/operations/proof/explain.d.ts +32 -0
- package/dist/operations/proof/explain.d.ts.map +1 -1
- package/dist/operations/proof/explain.js +91 -0
- package/dist/operations/proof/ingest-proof.d.ts +9 -1
- package/dist/operations/proof/ingest-proof.d.ts.map +1 -1
- package/dist/operations/proof/ingest-proof.js +41 -2
- package/dist/operations/proof/receipt-compaction.d.ts +2 -0
- package/dist/operations/proof/receipt-compaction.d.ts.map +1 -1
- package/dist/operations/proof/receipt-compaction.js +6 -2
- package/dist/operations/semantic-advisor/shared.d.ts.map +1 -1
- package/dist/operations/semantic-advisor/shared.js +4 -0
- package/dist/operations/semantic-advisor/strict-rules.js +3 -3
- package/dist/operations/semantic-advisor/types.d.ts +2 -1
- package/dist/operations/semantic-advisor/types.d.ts.map +1 -1
- package/dist/plugins/approved-source-analyzers.generated.d.ts +6 -6
- package/dist/plugins/approved-source-analyzers.generated.js +6 -6
- package/dist/proof/command-test-report.d.ts +4 -3
- package/dist/proof/command-test-report.d.ts.map +1 -1
- package/dist/proof/command-test-report.js +4 -10
- package/dist/proof/evaluate.d.ts.map +1 -1
- package/dist/proof/evaluate.js +18 -2
- package/dist/proof/inspect.d.ts.map +1 -1
- package/dist/proof/inspect.js +58 -0
- package/dist/proof/producers/playwright-reporter.d.ts +3 -0
- package/dist/proof/producers/playwright-reporter.d.ts.map +1 -1
- package/dist/proof/producers/playwright-reporter.js +25 -2
- package/dist/proof/step-runner.d.ts +36 -0
- package/dist/proof/step-runner.d.ts.map +1 -0
- package/dist/proof/step-runner.js +234 -0
- package/dist/public/evidence-levels.d.ts +12 -0
- package/dist/public/evidence-levels.d.ts.map +1 -0
- package/dist/public/evidence-levels.js +19 -0
- package/dist/public/impact/coverage-depth-quality.d.ts +7 -0
- package/dist/public/impact/coverage-depth-quality.d.ts.map +1 -1
- package/dist/public/impact/coverage-depth-quality.js +59 -0
- package/dist/public/impact/full-kb-quality.d.ts.map +1 -1
- package/dist/public/impact/full-kb-quality.js +3 -1
- package/dist/public/operations/catalog.d.ts +167 -6
- package/dist/public/operations/catalog.d.ts.map +1 -1
- package/dist/public/operations/check-executor.d.ts +2 -0
- package/dist/public/operations/check-executor.d.ts.map +1 -1
- package/dist/public/operations/check-executor.js +6 -0
- package/dist/public/operations/evidence-floor.d.ts +48 -0
- package/dist/public/operations/evidence-floor.d.ts.map +1 -0
- package/dist/public/operations/evidence-floor.js +143 -0
- package/dist/public/operations/migration-plan.d.ts +19 -5
- package/dist/public/operations/migration-plan.d.ts.map +1 -1
- package/dist/public/operations/migration-plan.js +22 -5
- package/dist/public/operations/node-ports.d.ts.map +1 -1
- package/dist/public/operations/node-ports.js +4 -1
- package/dist/public/operations/proof-contract-claims.d.ts +26 -0
- package/dist/public/operations/proof-contract-claims.d.ts.map +1 -0
- package/dist/public/operations/proof-contract-claims.js +71 -0
- 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 +3 -1
- package/dist/public/operations/specs/check.d.ts +1 -1
- package/dist/public/operations/specs/mutation-schemas.d.ts +157 -5
- package/dist/public/operations/specs/mutation-schemas.d.ts.map +1 -1
- package/dist/public/operations/specs/mutation-schemas.js +15 -2
- package/dist/public/operations/specs/proof.d.ts +9 -0
- package/dist/public/operations/specs/proof.d.ts.map +1 -1
- package/dist/public/operations/specs/reporting.d.ts.map +1 -1
- package/dist/public/operations/specs/reporting.js +19 -10
- package/dist/public/operations/specs/upsert.d.ts +157 -5
- package/dist/public/operations/specs/upsert.d.ts.map +1 -1
- package/dist/public/proof-certificates.d.ts +89 -0
- package/dist/public/proof-certificates.d.ts.map +1 -0
- package/dist/public/proof-certificates.js +207 -0
- package/dist/public/proof-protocol.d.ts +126 -3
- package/dist/public/proof-protocol.d.ts.map +1 -1
- package/dist/public/proof-protocol.js +239 -3
- package/dist/public/proof-receipt.d.ts +89 -6
- package/dist/public/proof-receipt.d.ts.map +1 -1
- package/dist/public/proof-receipt.js +83 -7
- package/dist/public/schemas/entity.d.ts.map +1 -1
- package/dist/public/schemas/entity.js +18 -1
- package/dist/public/skills/kibi-bootstrap/SKILL.md +2 -2
- package/dist/public/skills/kibi-commit-review/SKILL.md +1 -1
- package/dist/public/skills/kibi-commit-review/resources/staged-gate.md +6 -1
- package/dist/public/skills/kibi-decisions/SKILL.md +10 -2
- package/dist/public/skills/kibi-decisions/resources/decision-records.md +6 -1
- package/dist/public/skills/kibi-freshness/SKILL.md +5 -1
- package/dist/public/skills/kibi-traceability/SKILL.md +11 -1
- package/dist/public/skills/kibi-usage/SKILL.md +6 -4
- package/dist/public/skills/kibi-usage/resources/proof.md +124 -19
- package/dist/public/skills/kibi-usage/resources/workflows.md +4 -4
- package/dist/report/html-report.css +38 -1
- package/dist/report/html-report.d.ts +8 -0
- package/dist/report/html-report.d.ts.map +1 -1
- package/dist/report/html-report.js +141 -6
- package/dist/report/report-view.d.ts +1 -1
- package/dist/report/report-view.d.ts.map +1 -1
- package/dist/report/report-view.js +7 -0
- package/dist/schemas/entity.schema.json +212 -4
- package/dist/search-answer.d.ts.map +1 -1
- package/dist/search-answer.js +1 -0
- package/dist/signoff/html.d.ts.map +1 -1
- package/dist/signoff/html.js +3 -0
- package/dist/signoff/markdown.d.ts.map +1 -1
- package/dist/signoff/markdown.js +3 -0
- package/dist/signoff/model.d.ts +20 -0
- package/dist/signoff/model.d.ts.map +1 -1
- package/dist/signoff/model.js +20 -1
- package/dist/traceability/impact-knowledge.d.ts.map +1 -1
- package/dist/traceability/impact-knowledge.js +1 -0
- package/dist/traceability/temp-kb.d.ts.map +1 -1
- package/dist/traceability/temp-kb.js +1 -0
- package/dist/types/entities.d.ts +4 -1
- package/dist/types/entities.d.ts.map +1 -1
- package/dist/utils/merge-driver-setup.d.ts +8 -1
- package/dist/utils/merge-driver-setup.d.ts.map +1 -1
- package/dist/utils/merge-driver-setup.js +23 -2
- package/dist/utils/rule-registry.generated.d.ts +10 -0
- package/dist/utils/rule-registry.generated.d.ts.map +1 -1
- package/dist/utils/rule-registry.generated.js +12 -0
- package/dist/utils/schema-version.d.ts +1 -1
- package/dist/utils/schema-version.d.ts.map +1 -1
- package/dist/utils/schema-version.js +5 -1
- package/package.json +6 -2
- package/schema/entities.pl +6 -0
- package/schema/validation.pl +4 -1
- package/src/public/evidence-levels.ts +23 -0
- package/src/public/impact/coverage-depth-quality.ts +73 -0
- package/src/public/impact/full-kb-quality.ts +6 -1
- package/src/public/operations/check-executor.ts +13 -0
- package/src/public/operations/evidence-floor.ts +203 -0
- package/src/public/operations/migration-plan.ts +39 -6
- package/src/public/operations/node-ports.ts +3 -1
- package/src/public/operations/proof-contract-claims.ts +103 -0
- package/src/public/operations/specs/check-rules.generated.ts +3 -1
- package/src/public/operations/specs/mutation-schemas.ts +17 -2
- package/src/public/operations/specs/reporting.ts +20 -10
- package/src/public/proof-certificates.ts +271 -0
- package/src/public/proof-protocol-types.d.ts +17 -1
- package/src/public/proof-protocol.ts +318 -3
- package/src/public/proof-receipt.ts +127 -7
- package/src/public/schemas/entity.ts +19 -1
- package/src/public/skills/kibi-bootstrap/SKILL.md +2 -2
- package/src/public/skills/kibi-commit-review/SKILL.md +1 -1
- package/src/public/skills/kibi-commit-review/resources/staged-gate.md +6 -1
- package/src/public/skills/kibi-decisions/SKILL.md +10 -2
- package/src/public/skills/kibi-decisions/resources/decision-records.md +6 -1
- package/src/public/skills/kibi-freshness/SKILL.md +5 -1
- package/src/public/skills/kibi-traceability/SKILL.md +11 -1
- package/src/public/skills/kibi-usage/SKILL.md +6 -4
- package/src/public/skills/kibi-usage/resources/proof.md +124 -19
- package/src/public/skills/kibi-usage/resources/workflows.md +4 -4
- package/src/schemas/entity.schema.json +212 -4
- package/dist/commands/proof-impact.d.ts +0 -7
- package/dist/commands/proof-impact.d.ts.map +0 -1
- package/dist/commands/proof-impact.js +0 -39
- package/dist/operations/proof/baseline-diff.d.ts +0 -41
- package/dist/operations/proof/baseline-diff.d.ts.map +0 -1
- package/dist/operations/proof/baseline-diff.js +0 -153
- package/dist/operations/proof/impact.d.ts +0 -51
- package/dist/operations/proof/impact.d.ts.map +0 -1
- package/dist/operations/proof/impact.js +0 -138
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "kibi-cli",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.32.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Kibi CLI for knowledge base management",
|
|
6
6
|
"engines": {
|
|
@@ -92,6 +92,10 @@
|
|
|
92
92
|
"types": "./dist/public/extractors/markdown.d.ts",
|
|
93
93
|
"default": "./dist/public/extractors/markdown.js"
|
|
94
94
|
},
|
|
95
|
+
"./proof-certificates": {
|
|
96
|
+
"types": "./dist/public/proof-certificates.d.ts",
|
|
97
|
+
"default": "./dist/public/proof-certificates.js"
|
|
98
|
+
},
|
|
95
99
|
"./schemas/entity": {
|
|
96
100
|
"types": "./dist/public/schemas/entity.d.ts",
|
|
97
101
|
"default": "./dist/public/schemas/entity.js"
|
|
@@ -235,7 +239,7 @@
|
|
|
235
239
|
"fast-glob": "^3.2.12",
|
|
236
240
|
"gray-matter": "^4.0.3",
|
|
237
241
|
"js-yaml": "^4.1.0",
|
|
238
|
-
"kibi-core": "^0.
|
|
242
|
+
"kibi-core": "^0.24.0",
|
|
239
243
|
"kibi-plugin-builtin": "^0.5.3",
|
|
240
244
|
"kibi-plugin-sdk": "^0.5.0",
|
|
241
245
|
"kibi-swipl": "^1.0.1",
|
package/schema/entities.pl
CHANGED
|
@@ -56,6 +56,9 @@ entity_property(req, exempts_claims, list).
|
|
|
56
56
|
% Absent reads as default.
|
|
57
57
|
entity_property(req, firmness, atom).
|
|
58
58
|
entity_property(adr, firmness, atom).
|
|
59
|
+
% The weakest evidence level a current requirement accepts: proved, tested,
|
|
60
|
+
% asserted or unverified. Absent: tested when firm, otherwise unverified.
|
|
61
|
+
entity_property(req, required_evidence, atom).
|
|
59
62
|
% Intended outcome of a scenario: success, rejection or error.
|
|
60
63
|
entity_property(scenario, expects, atom).
|
|
61
64
|
|
|
@@ -69,6 +72,8 @@ entity_property(fact, value_string, string).
|
|
|
69
72
|
entity_property(fact, value_int, integer).
|
|
70
73
|
entity_property(fact, value_number, number).
|
|
71
74
|
entity_property(fact, value_bool, boolean).
|
|
75
|
+
% The exact members of a set fact (value_type set).
|
|
76
|
+
entity_property(fact, value_set, list).
|
|
72
77
|
entity_property(fact, unit, string).
|
|
73
78
|
entity_property(fact, scope, string).
|
|
74
79
|
entity_property(fact, polarity, atom).
|
|
@@ -150,6 +155,7 @@ optional_property(req, rationale).
|
|
|
150
155
|
optional_property(req, exempts_claims).
|
|
151
156
|
optional_property(req, firmness).
|
|
152
157
|
optional_property(adr, firmness).
|
|
158
|
+
optional_property(req, required_evidence).
|
|
153
159
|
optional_property(test, verification_scope).
|
|
154
160
|
optional_property(test, verification_perspective).
|
|
155
161
|
optional_property(test, proof_contract).
|
package/schema/validation.pl
CHANGED
|
@@ -108,6 +108,7 @@ is_fact_only_field(value_string).
|
|
|
108
108
|
is_fact_only_field(value_int).
|
|
109
109
|
is_fact_only_field(value_number).
|
|
110
110
|
is_fact_only_field(value_bool).
|
|
111
|
+
is_fact_only_field(value_set).
|
|
111
112
|
is_fact_only_field(unit).
|
|
112
113
|
is_fact_only_field(scope).
|
|
113
114
|
is_fact_only_field(polarity).
|
|
@@ -227,7 +228,7 @@ text_atom(Value, Atom) :- string(Value), atom_string(Atom, Value).
|
|
|
227
228
|
valid_operator(Op) :- memberchk(Op, [eq, neq, lt, lte, gt, gte]), !.
|
|
228
229
|
|
|
229
230
|
% valid_value_type(+VT)
|
|
230
|
-
valid_value_type(ValueType) :- memberchk(ValueType, [string, int, number, bool]), !.
|
|
231
|
+
valid_value_type(ValueType) :- memberchk(ValueType, [string, int, number, bool, set]), !.
|
|
231
232
|
|
|
232
233
|
% valid_polarity_in_props(+Props)
|
|
233
234
|
% Validates polarity if present; succeeds if no polarity in props
|
|
@@ -299,6 +300,7 @@ is_value_field(value_string).
|
|
|
299
300
|
is_value_field(value_int).
|
|
300
301
|
is_value_field(value_number).
|
|
301
302
|
is_value_field(value_bool).
|
|
303
|
+
is_value_field(value_set).
|
|
302
304
|
|
|
303
305
|
% value_type_matches_field(+ValueType, +Props)
|
|
304
306
|
% Ensures that value_type matches the actual value field present
|
|
@@ -306,6 +308,7 @@ value_type_matches_field(string, Props) :- memberchk(value_string=_, Props), !.
|
|
|
306
308
|
value_type_matches_field(int, Props) :- memberchk(value_int=_, Props), !.
|
|
307
309
|
value_type_matches_field(number, Props) :- memberchk(value_number=_, Props), !.
|
|
308
310
|
value_type_matches_field(bool, Props) :- memberchk(value_bool=_, Props), !.
|
|
311
|
+
value_type_matches_field(set, Props) :- memberchk(value_set=_, Props), !.
|
|
309
312
|
|
|
310
313
|
% valid_origin_in_props(+Props)
|
|
311
314
|
% Entity provenance travels as a JSON object string (or a dict). When present
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* How strong the evidence behind a requirement is, strongest first. proved: a
|
|
3
|
+
* re-checkable certificate covers every case. tested: fresh passing E2E
|
|
4
|
+
* receipts cover every linked scenario. asserted: a person stands behind it.
|
|
5
|
+
* unverified: an agent wrote it and no person approved it. The same list is
|
|
6
|
+
* the vocabulary of required_evidence, a requirement's evidence floor.
|
|
7
|
+
*/
|
|
8
|
+
// implements REQ-kibi-evidence-levels, REQ-kibi-evidence-floors
|
|
9
|
+
export const EVIDENCE_LEVELS = [
|
|
10
|
+
"proved",
|
|
11
|
+
"tested",
|
|
12
|
+
"asserted",
|
|
13
|
+
"unverified",
|
|
14
|
+
] as const;
|
|
15
|
+
|
|
16
|
+
// implements REQ-kibi-evidence-levels
|
|
17
|
+
export type EvidenceLevel = (typeof EVIDENCE_LEVELS)[number];
|
|
18
|
+
|
|
19
|
+
/** Higher is stronger: unverified 0 through proved 3. */
|
|
20
|
+
// implements REQ-kibi-evidence-floors
|
|
21
|
+
export function evidenceRank(level: EvidenceLevel): number {
|
|
22
|
+
return EVIDENCE_LEVELS.length - 1 - EVIDENCE_LEVELS.indexOf(level);
|
|
23
|
+
}
|
|
@@ -2,6 +2,7 @@ import type {
|
|
|
2
2
|
ExtractedEntity,
|
|
3
3
|
ExtractionResult,
|
|
4
4
|
} from "../../extractors/markdown.js";
|
|
5
|
+
import { COARSE_GRANULARITY_REASONS } from "../symbol-granularity.generated.js";
|
|
5
6
|
import type { QualityDiagnostic } from "./types.js";
|
|
6
7
|
|
|
7
8
|
type ReviewableCoverageDepth =
|
|
@@ -347,3 +348,75 @@ export function createCoverageDepthQualityDiagnostics(
|
|
|
347
348
|
(left.entityId ?? "").localeCompare(right.entityId ?? ""),
|
|
348
349
|
);
|
|
349
350
|
}
|
|
351
|
+
|
|
352
|
+
const COARSE_REASONS: ReadonlySet<string> = new Set(COARSE_GRANULARITY_REASONS);
|
|
353
|
+
|
|
354
|
+
/**
|
|
355
|
+
* Requirements whose every implementing production symbol is a whole-file
|
|
356
|
+
* anchor (a coarse granularity_reason). Coverage then only says some test
|
|
357
|
+
* touches the file, so `tested` evidence is cheap; narrower behavioral
|
|
358
|
+
* symbols would tie it to the code that implements the claim.
|
|
359
|
+
*/
|
|
360
|
+
// implements REQ-audit-quality-diagnostics-v1
|
|
361
|
+
export function createCoarseAnchorQualityDiagnostics(
|
|
362
|
+
manifestResults: readonly ExtractionResult[],
|
|
363
|
+
): readonly QualityDiagnostic[] {
|
|
364
|
+
const superseded = supersededRequirementIds(manifestResults);
|
|
365
|
+
const anchorsByRequirement = new Map<string, ExtractedEntity[]>();
|
|
366
|
+
for (const result of manifestResults) {
|
|
367
|
+
if (result.entity.type !== "symbol") continue;
|
|
368
|
+
// Executable test code is not a production symbol.
|
|
369
|
+
if (result.relationships.some((rel) => rel.type === "executable_for"))
|
|
370
|
+
continue;
|
|
371
|
+
for (const relationship of result.relationships) {
|
|
372
|
+
if (relationship.type !== "implements") continue;
|
|
373
|
+
const list = anchorsByRequirement.get(relationship.to) ?? [];
|
|
374
|
+
list.push(result.entity);
|
|
375
|
+
anchorsByRequirement.set(relationship.to, list);
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
return manifestResults
|
|
379
|
+
.filter(
|
|
380
|
+
(result) =>
|
|
381
|
+
result.entity.type === "req" &&
|
|
382
|
+
isReviewableRequirement(result.entity, superseded),
|
|
383
|
+
)
|
|
384
|
+
.flatMap((result): QualityDiagnostic[] => {
|
|
385
|
+
const symbols = uniqueEntities(
|
|
386
|
+
anchorsByRequirement.get(result.entity.id) ?? [],
|
|
387
|
+
);
|
|
388
|
+
if (
|
|
389
|
+
symbols.length === 0 ||
|
|
390
|
+
!symbols.every((symbol) =>
|
|
391
|
+
COARSE_REASONS.has(symbol.granularity_reason ?? ""),
|
|
392
|
+
)
|
|
393
|
+
)
|
|
394
|
+
return [];
|
|
395
|
+
const requirement = result.entity;
|
|
396
|
+
return [
|
|
397
|
+
{
|
|
398
|
+
id: "coarse_anchor_only_review",
|
|
399
|
+
severity: "review",
|
|
400
|
+
blocking: false,
|
|
401
|
+
category: "coverage",
|
|
402
|
+
entityId: requirement.id,
|
|
403
|
+
source: requirement.source,
|
|
404
|
+
files: [requirement.source],
|
|
405
|
+
message: `Requirement ${requirement.id} (${requirement.title}) is implemented only by whole-file anchors, so its coverage says a test touches those files, not the code behind the claim.`,
|
|
406
|
+
suggestion:
|
|
407
|
+
"Link the functions, methods or types that implement the requirement with implements (and covered_by to their tests); keep the file anchor only for genuinely module-level behavior.",
|
|
408
|
+
evidence: {
|
|
409
|
+
requirementId: requirement.id,
|
|
410
|
+
title: requirement.title,
|
|
411
|
+
symbols: symbols.map((symbol) => ({
|
|
412
|
+
id: symbol.id,
|
|
413
|
+
granularityReason: symbol.granularity_reason ?? "",
|
|
414
|
+
})),
|
|
415
|
+
},
|
|
416
|
+
},
|
|
417
|
+
];
|
|
418
|
+
})
|
|
419
|
+
.toSorted((left, right) =>
|
|
420
|
+
(left.entityId ?? "").localeCompare(right.entityId ?? ""),
|
|
421
|
+
);
|
|
422
|
+
}
|
|
@@ -24,7 +24,10 @@ import {
|
|
|
24
24
|
createTelemetryAcceptanceDiagnostics,
|
|
25
25
|
parseTelemetryUsageLog,
|
|
26
26
|
} from "../telemetry-acceptance.js";
|
|
27
|
-
import {
|
|
27
|
+
import {
|
|
28
|
+
createCoarseAnchorQualityDiagnostics,
|
|
29
|
+
createCoverageDepthQualityDiagnostics,
|
|
30
|
+
} from "./coverage-depth-quality.js";
|
|
28
31
|
import { createRequirementQualityDiagnostics } from "./requirement-quality.js";
|
|
29
32
|
import { createSymbolQualityDiagnostics } from "./symbol-quality.js";
|
|
30
33
|
import type { QualityDiagnostic } from "./types.js";
|
|
@@ -297,6 +300,7 @@ function toExtractedEntity(entity: Record<string, unknown>): ExtractedEntity {
|
|
|
297
300
|
"aliases",
|
|
298
301
|
"examples",
|
|
299
302
|
"predicate_args",
|
|
303
|
+
"value_set",
|
|
300
304
|
] as const) {
|
|
301
305
|
const values = optionalStringArrayField(entity, field);
|
|
302
306
|
if (values !== undefined) extracted[field] = values;
|
|
@@ -786,6 +790,7 @@ export async function collectFullKbQualityDiagnostics(
|
|
|
786
790
|
: {}),
|
|
787
791
|
}),
|
|
788
792
|
...coverageDepthDiagnostics,
|
|
793
|
+
...createCoarseAnchorQualityDiagnostics(manifestResults),
|
|
789
794
|
...createSymbolQualityDiagnostics({
|
|
790
795
|
manifestResults,
|
|
791
796
|
symbolsByFile: new Map(),
|
|
@@ -46,6 +46,7 @@ import { collectCheckPolicyViolations } from "./check-policy-rules.js";
|
|
|
46
46
|
import { collectDecisionFirmnessViolations } from "./decision-firmness.js";
|
|
47
47
|
import { executeStatus } from "./discovery-executors.js";
|
|
48
48
|
import { collectEntityContextViolations } from "./entity-context.js";
|
|
49
|
+
import { collectEvidenceFloorViolations } from "./evidence-floor.js";
|
|
49
50
|
import {
|
|
50
51
|
buildActionsFromCheck,
|
|
51
52
|
buildMigrationPlan,
|
|
@@ -53,6 +54,7 @@ import {
|
|
|
53
54
|
} from "./migration-plan.js";
|
|
54
55
|
import { collectOriginReviewViolations } from "./origin-review.js";
|
|
55
56
|
import { collectPredicateSchemaConformanceViolations } from "./predicate-schema-conformance.js";
|
|
57
|
+
import { collectProofContractClaimViolations } from "./proof-contract-claims.js";
|
|
56
58
|
import type { OperationContext, PrologPort } from "./runtime-types.js";
|
|
57
59
|
import {
|
|
58
60
|
SOURCE_PATH_DANGLING_RULE,
|
|
@@ -72,6 +74,8 @@ export type CheckInput = {
|
|
|
72
74
|
readonly includeWorkingTreeDiff?: boolean;
|
|
73
75
|
readonly includeImpactDiagnostics?: boolean;
|
|
74
76
|
readonly maxDiagnostics?: number;
|
|
77
|
+
/** evidence-floor judges receipts as they are and needs a clean snapshot. */
|
|
78
|
+
readonly freshEvidence?: boolean;
|
|
75
79
|
};
|
|
76
80
|
|
|
77
81
|
export type CheckPayload = CheckStructuredContent;
|
|
@@ -393,6 +397,13 @@ export async function executeCheck(
|
|
|
393
397
|
rulesAllowlist,
|
|
394
398
|
workspaceRoot,
|
|
395
399
|
);
|
|
400
|
+
const evidenceFloorFindings = await collectEvidenceFloorViolations(
|
|
401
|
+
context,
|
|
402
|
+
rulesAllowlist,
|
|
403
|
+
{ freshEvidence: args.freshEvidence === true },
|
|
404
|
+
);
|
|
405
|
+
const proofContractClaimFindings =
|
|
406
|
+
await collectProofContractClaimViolations(context, rulesAllowlist);
|
|
396
407
|
const partitioned = partitionCheckFindings([
|
|
397
408
|
...aggregatedFindings,
|
|
398
409
|
...queryPlanViolations,
|
|
@@ -404,6 +415,8 @@ export async function executeCheck(
|
|
|
404
415
|
...entityContextFindings,
|
|
405
416
|
...decisionFirmnessFindings,
|
|
406
417
|
...checkPolicyFindings,
|
|
418
|
+
...evidenceFloorFindings,
|
|
419
|
+
...proofContractClaimFindings,
|
|
407
420
|
]);
|
|
408
421
|
const violations: Violation[] = partitioned.violations;
|
|
409
422
|
const evaluated = await describeEvaluation(prolog, rulesAllowlist);
|
|
@@ -0,0 +1,203 @@
|
|
|
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
|
+
|
|
19
|
+
import { readKbManifest } from "../../utils/kb-manifest.js";
|
|
20
|
+
import type { Violation } from "../../utils/rule-registry.js";
|
|
21
|
+
import { PROOF_RECEIPT_MAX_AGE_SECONDS } from "../proof-receipt.js";
|
|
22
|
+
import { runOperationJsonQuery, toPrologAtom } from "./prolog-json.js";
|
|
23
|
+
import type { OperationContext } from "./runtime-types.js";
|
|
24
|
+
import { readWorkspaceSnapshot } from "./workspace-snapshot.js";
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* The schema version from which a current requirement's evidence level must
|
|
28
|
+
* reach its floor; older KBs get the floor with their schema upgrade, whose
|
|
29
|
+
* migration records the floor of firm requirements without a proof path.
|
|
30
|
+
*/
|
|
31
|
+
// implements REQ-kibi-evidence-floors
|
|
32
|
+
export const EVIDENCE_FLOOR_SCHEMA_VERSION = 11;
|
|
33
|
+
|
|
34
|
+
/** A current requirement's evidence level must reach its floor. */
|
|
35
|
+
// implements REQ-kibi-evidence-floors
|
|
36
|
+
export const EVIDENCE_FLOOR_RULE = "evidence-floor";
|
|
37
|
+
|
|
38
|
+
// implements REQ-kibi-evidence-floors
|
|
39
|
+
export type EvidenceFloorRow = Readonly<{
|
|
40
|
+
id: string;
|
|
41
|
+
evidenceLevel: string;
|
|
42
|
+
requiredEvidence: string;
|
|
43
|
+
requiredEvidenceSource: string;
|
|
44
|
+
status: "below" | "pending_reproof";
|
|
45
|
+
proofStatus: string;
|
|
46
|
+
proofGaps: readonly string[];
|
|
47
|
+
/** The tests and symbols behind the gaps; empty lists are left out. */
|
|
48
|
+
gapDetails?: Readonly<Record<string, readonly string[]>>;
|
|
49
|
+
}>;
|
|
50
|
+
|
|
51
|
+
type EvidenceFloorPayload = {
|
|
52
|
+
readonly evaluated?: number;
|
|
53
|
+
readonly rows?: readonly EvidenceFloorRow[];
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
export type EvidenceFloorOptions = Readonly<{
|
|
57
|
+
/**
|
|
58
|
+
* Judge receipts as they are: stale receipts no longer count as pending
|
|
59
|
+
* re-proof, and the proof snapshot must be clean. CI uses it right after
|
|
60
|
+
* `kibi prove --all`.
|
|
61
|
+
*/
|
|
62
|
+
freshEvidence?: boolean;
|
|
63
|
+
}>;
|
|
64
|
+
|
|
65
|
+
function floorSource(row: EvidenceFloorRow): string {
|
|
66
|
+
return row.requiredEvidenceSource === "explicit"
|
|
67
|
+
? "its required_evidence"
|
|
68
|
+
: row.requiredEvidence === "tested"
|
|
69
|
+
? "the floor of a firm requirement"
|
|
70
|
+
: "its floor";
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const GAP_DETAIL_LABELS: Readonly<Record<string, string>> = {
|
|
74
|
+
failedReceiptTests: "failing receipts",
|
|
75
|
+
missingReceiptTests: "no receipt",
|
|
76
|
+
staleReceiptTests: "stale receipts",
|
|
77
|
+
invalidReceiptTests: "invalid receipts",
|
|
78
|
+
contractMismatchReceiptTests: "receipts for an older contract",
|
|
79
|
+
uncoveredSymbols: "production symbols without qualifying coverage",
|
|
80
|
+
missingCoordinateSymbols: "symbols without source coordinates",
|
|
81
|
+
unprovedClaims: "claims no certificate derives",
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
/** The entities behind the gaps, for a person reading kibi check's text. */
|
|
85
|
+
// implements REQ-kibi-evidence-floors
|
|
86
|
+
export function gapDetailLines(row: EvidenceFloorRow): string[] {
|
|
87
|
+
return Object.entries(row.gapDetails ?? {}).flatMap(([key, ids]) =>
|
|
88
|
+
ids.length === 0
|
|
89
|
+
? []
|
|
90
|
+
: [
|
|
91
|
+
`${GAP_DETAIL_LABELS[key] ?? key}: ${ids.slice(0, 8).join(", ")}${ids.length > 8 ? ` (+${ids.length - 8} more)` : ""}`,
|
|
92
|
+
],
|
|
93
|
+
);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function suggestionFor(row: EvidenceFloorRow): string {
|
|
97
|
+
if (row.status === "pending_reproof")
|
|
98
|
+
return `Run kibi prove for the tests behind ${row.id} so its receipts match the current code, then rerun kibi check --fresh-evidence.`;
|
|
99
|
+
switch (row.requiredEvidence) {
|
|
100
|
+
case "proved":
|
|
101
|
+
return `Prove every applicable claim of ${row.id}${(row.gapDetails?.unprovedClaims ?? []).length > 0 ? ` (unproved: ${(row.gapDetails?.unprovedClaims ?? []).join(", ")})` : ""}: model each as a set fact (prove it with exhaustive or typecheck) or a numeric fact (arithmetic or smt), name it in a certificate test's proof_contract.proves, and run kibi prove; kibi proof explain ${row.id} shows each claim's bindings. A claim with no such fact can only be attested, so lower required_evidence to tested with the reason in the requirement if that is enough.`;
|
|
102
|
+
case "tested":
|
|
103
|
+
return `Link ${row.id} to a scenario with an end-to-end test that has a proof contract, run kibi prove, and fix its proof gaps (${row.proofGaps.join(", ") || "none listed"}${gapDetailLines(row).length > 0 ? `; ${gapDetailLines(row).join("; ")}` : ""}); kibi proof explain ${row.id} shows each one. Or lower required_evidence if a person decides it does not need tests.`;
|
|
104
|
+
default:
|
|
105
|
+
return `Have a person approve ${row.id} (origin.approved_by) or state it as firm with origin.ref to their words, or lower required_evidence.`;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/** One finding per current requirement whose evidence is below its floor. */
|
|
110
|
+
// implements REQ-kibi-evidence-floors
|
|
111
|
+
export function evidenceFloorViolations(
|
|
112
|
+
rows: readonly EvidenceFloorRow[],
|
|
113
|
+
options: EvidenceFloorOptions = {},
|
|
114
|
+
): Violation[] {
|
|
115
|
+
return rows
|
|
116
|
+
.filter((row) => options.freshEvidence === true || row.status === "below")
|
|
117
|
+
.map((row) => ({
|
|
118
|
+
rule: EVIDENCE_FLOOR_RULE,
|
|
119
|
+
entityId: row.id,
|
|
120
|
+
description:
|
|
121
|
+
row.status === "pending_reproof"
|
|
122
|
+
? `${row.id} is ${row.evidenceLevel} but needs ${row.requiredEvidence} (${floorSource(row)}); its receipts are older than the code`
|
|
123
|
+
: `${row.id} is ${row.evidenceLevel} but needs ${row.requiredEvidence} (${floorSource(row)})`,
|
|
124
|
+
suggestion: suggestionFor(row),
|
|
125
|
+
evidence: {
|
|
126
|
+
evidenceLevel: row.evidenceLevel,
|
|
127
|
+
requiredEvidence: row.requiredEvidence,
|
|
128
|
+
requiredEvidenceSource: row.requiredEvidenceSource,
|
|
129
|
+
status: row.status,
|
|
130
|
+
proofStatus: row.proofStatus,
|
|
131
|
+
proofGaps: row.proofGaps,
|
|
132
|
+
...(row.gapDetails === undefined ? {} : { gapDetails: row.gapDetails }),
|
|
133
|
+
},
|
|
134
|
+
}));
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* Every current requirement whose evidence is below its floor (status below
|
|
139
|
+
* or pending_reproof), judged on the attached store with the same
|
|
140
|
+
* per-contract receipt binding kb_coverage uses. Unlike the check rule this
|
|
141
|
+
* ignores the schema version, so a migration can read the floor the ladder
|
|
142
|
+
* will apply before it records the new schema.
|
|
143
|
+
*/
|
|
144
|
+
// implements REQ-kibi-evidence-floors
|
|
145
|
+
export async function readEvidenceFloorRows(
|
|
146
|
+
context: OperationContext,
|
|
147
|
+
snapshotEvidence?: Awaited<ReturnType<typeof readWorkspaceSnapshot>>,
|
|
148
|
+
): Promise<readonly EvidenceFloorRow[]> {
|
|
149
|
+
const prolog = context.prolog ?? (await context.ensureProlog?.());
|
|
150
|
+
if (prolog === undefined) return [];
|
|
151
|
+
const evidence = snapshotEvidence ?? (await readWorkspaceSnapshot(context));
|
|
152
|
+
const snapshot = evidence.available ? evidence.snapshot.hash : "unknown";
|
|
153
|
+
const { perContractTestBindings } = await import("./specs/reporting.js");
|
|
154
|
+
const bindings = await perContractTestBindings({ ...context, prolog });
|
|
155
|
+
const checkedAt = context.clock().toISOString();
|
|
156
|
+
const goal = `discovery:evidence_floor_json(${bindings === null ? "strict_snapshot, _{}" : `per_contract, ${bindings}`}, ${toPrologAtom(snapshot)}, ${toPrologAtom(checkedAt)}, ${PROOF_RECEIPT_MAX_AGE_SECONDS}, JsonString)`;
|
|
157
|
+
const payload = await runOperationJsonQuery<EvidenceFloorPayload>(
|
|
158
|
+
prolog,
|
|
159
|
+
"discovery.pl",
|
|
160
|
+
goal,
|
|
161
|
+
"Evidence floor evaluation",
|
|
162
|
+
);
|
|
163
|
+
return payload.rows ?? [];
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* Evaluate the evidence-floor rule against the attached store, with the same
|
|
168
|
+
* per-contract receipt binding kb_coverage uses.
|
|
169
|
+
*/
|
|
170
|
+
// implements REQ-kibi-evidence-floors
|
|
171
|
+
export async function collectEvidenceFloorViolations(
|
|
172
|
+
context: OperationContext,
|
|
173
|
+
rules: ReadonlySet<string>,
|
|
174
|
+
options: EvidenceFloorOptions = {},
|
|
175
|
+
): Promise<Violation[]> {
|
|
176
|
+
if (!rules.has(EVIDENCE_FLOOR_RULE) || context.prolog === undefined)
|
|
177
|
+
return [];
|
|
178
|
+
// A store without a manifest predates schema 11 as much as an old one.
|
|
179
|
+
const schemaVersion = readKbManifest(context.workspaceRoot)?.schemaVersion;
|
|
180
|
+
if (
|
|
181
|
+
schemaVersion === undefined ||
|
|
182
|
+
schemaVersion < EVIDENCE_FLOOR_SCHEMA_VERSION
|
|
183
|
+
)
|
|
184
|
+
return [];
|
|
185
|
+
const snapshotEvidence = await readWorkspaceSnapshot(context);
|
|
186
|
+
const rows = await readEvidenceFloorRows(context, snapshotEvidence);
|
|
187
|
+
const violations = evidenceFloorViolations(rows, options);
|
|
188
|
+
if (options.freshEvidence === true) {
|
|
189
|
+
const clean =
|
|
190
|
+
snapshotEvidence.available && snapshotEvidence.snapshot.dirty === false;
|
|
191
|
+
if (!clean)
|
|
192
|
+
violations.unshift({
|
|
193
|
+
rule: EVIDENCE_FLOOR_RULE,
|
|
194
|
+
entityId: "proof-snapshot",
|
|
195
|
+
description: snapshotEvidence.available
|
|
196
|
+
? "--fresh-evidence needs a clean proof snapshot, but the working tree has uncommitted changes"
|
|
197
|
+
: `--fresh-evidence needs a proof snapshot, but none is available: ${snapshotEvidence.error}`,
|
|
198
|
+
suggestion:
|
|
199
|
+
"Commit or stash working-tree changes (or run inside a Git checkout), run kibi prove, then rerun kibi check --fresh-evidence.",
|
|
200
|
+
});
|
|
201
|
+
}
|
|
202
|
+
return violations;
|
|
203
|
+
}
|
|
@@ -297,8 +297,11 @@ export function mergeMigrationPlans(
|
|
|
297
297
|
});
|
|
298
298
|
}
|
|
299
299
|
|
|
300
|
+
/** How many automatic actions a plan digest lists by id. */
|
|
301
|
+
const DIGEST_AUTOMATIC_ACTIONS = 20;
|
|
302
|
+
|
|
300
303
|
export const MIGRATION_PLAN_DIGEST_VERSION =
|
|
301
|
-
"kibi.migration-plan-digest.
|
|
304
|
+
"kibi.migration-plan-digest.v2" as const;
|
|
302
305
|
|
|
303
306
|
export type MigrationPlanDigest = Readonly<{
|
|
304
307
|
version: typeof MIGRATION_PLAN_DIGEST_VERSION;
|
|
@@ -307,7 +310,20 @@ export type MigrationPlanDigest = Readonly<{
|
|
|
307
310
|
expected: MigrationPlan["expected"];
|
|
308
311
|
scope: MigrationPlan["scope"];
|
|
309
312
|
summary: MigrationPlan["summary"];
|
|
310
|
-
|
|
313
|
+
/** The first automatic actions, by id; automaticSummary counts them all. */
|
|
314
|
+
automaticActions: readonly Readonly<{
|
|
315
|
+
id: string;
|
|
316
|
+
code: string;
|
|
317
|
+
state: MigrationActionState;
|
|
318
|
+
autoApplicable: boolean;
|
|
319
|
+
dependsOn: readonly string[];
|
|
320
|
+
}>[];
|
|
321
|
+
automaticSummary: Readonly<{
|
|
322
|
+
total: number;
|
|
323
|
+
ready: number;
|
|
324
|
+
blocked: number;
|
|
325
|
+
omitted: number;
|
|
326
|
+
}>;
|
|
311
327
|
actionGroups: readonly Readonly<{
|
|
312
328
|
code: string;
|
|
313
329
|
safety: MigrationSafety;
|
|
@@ -320,9 +336,10 @@ export type MigrationPlanDigest = Readonly<{
|
|
|
320
336
|
|
|
321
337
|
/**
|
|
322
338
|
* A bounded summary of a plan for responses that report what is left rather
|
|
323
|
-
* than hand over a plan to apply: automatic actions
|
|
324
|
-
* action is counted by code. The full
|
|
325
|
-
* coverage
|
|
339
|
+
* than hand over a plan to apply: the first automatic actions are listed by
|
|
340
|
+
* id and all of them counted, every other action is counted by code. The full
|
|
341
|
+
* plan comes from status, check and coverage, so a response stays small even
|
|
342
|
+
* when hundreds of actions remain.
|
|
326
343
|
*/
|
|
327
344
|
// implements REQ-agent-guided-migration-orchestration
|
|
328
345
|
export function digestMigrationPlan(plan: MigrationPlan): MigrationPlanDigest {
|
|
@@ -365,7 +382,23 @@ export function digestMigrationPlan(plan: MigrationPlan): MigrationPlanDigest {
|
|
|
365
382
|
expected: plan.expected,
|
|
366
383
|
scope: plan.scope,
|
|
367
384
|
summary: plan.summary,
|
|
368
|
-
automaticActions
|
|
385
|
+
automaticActions: automaticActions
|
|
386
|
+
.slice(0, DIGEST_AUTOMATIC_ACTIONS)
|
|
387
|
+
.map((action) => ({
|
|
388
|
+
id: action.id,
|
|
389
|
+
code: action.code,
|
|
390
|
+
state: action.state,
|
|
391
|
+
autoApplicable: action.autoApplicable,
|
|
392
|
+
dependsOn: action.dependsOn,
|
|
393
|
+
})),
|
|
394
|
+
automaticSummary: {
|
|
395
|
+
total: automaticActions.length,
|
|
396
|
+
ready: automaticActions.filter(
|
|
397
|
+
(action) => action.state === "ready" && action.autoApplicable,
|
|
398
|
+
).length,
|
|
399
|
+
blocked: blockedAutomatic,
|
|
400
|
+
omitted: Math.max(0, automaticActions.length - DIGEST_AUTOMATIC_ACTIONS),
|
|
401
|
+
},
|
|
369
402
|
actionGroups: [...groups.values()].sort(
|
|
370
403
|
(left, right) =>
|
|
371
404
|
right.count - left.count ||
|
|
@@ -79,7 +79,9 @@ function isReceiptOnlyMarkdownChange(
|
|
|
79
79
|
const committed = execFileSync(
|
|
80
80
|
"git",
|
|
81
81
|
["-C", workspaceRoot, "show", `HEAD:${relativePath}`],
|
|
82
|
-
|
|
82
|
+
// A file staged but not committed yet has no HEAD blob; git's error is
|
|
83
|
+
// expected here and must not reach the terminal.
|
|
84
|
+
{ encoding: "buffer", maxBuffer: 64 * 1024 * 1024, stdio: "pipe" },
|
|
83
85
|
) as Buffer;
|
|
84
86
|
return working.equals(snapshotFileContent(relativePath, committed));
|
|
85
87
|
} catch {
|
|
@@ -0,0 +1,103 @@
|
|
|
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
|
+
|
|
19
|
+
import { readKbManifest } from "../../utils/kb-manifest.js";
|
|
20
|
+
import type { Violation } from "../../utils/rule-registry.js";
|
|
21
|
+
import { runOperationJsonQuery } from "./prolog-json.js";
|
|
22
|
+
import type { OperationContext } from "./runtime-types.js";
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* The schema version from which every certificate-kind proof contract must
|
|
26
|
+
* name the claims it proves; older KBs get the rule with their schema
|
|
27
|
+
* upgrade, whose migration pins proves where the claim is unambiguous.
|
|
28
|
+
*/
|
|
29
|
+
// implements REQ-kibi-claim-bound-proofs
|
|
30
|
+
export const CLAIM_BINDING_SCHEMA_VERSION = 12;
|
|
31
|
+
|
|
32
|
+
/** A certificate-kind proof contract must name provable claims in proves. */
|
|
33
|
+
// implements REQ-kibi-claim-bound-proofs
|
|
34
|
+
export const PROOF_CONTRACT_CLAIMS_RULE = "proof-contract-claims";
|
|
35
|
+
|
|
36
|
+
// implements REQ-kibi-claim-bound-proofs
|
|
37
|
+
export type ProofContractClaimIssue = Readonly<{
|
|
38
|
+
test_id: string;
|
|
39
|
+
claim_key?: string;
|
|
40
|
+
reason: string;
|
|
41
|
+
}>;
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Every certificate-kind proof contract that names no claims, and every
|
|
45
|
+
* named claim no certificate of its kind could prove. Ignores the schema
|
|
46
|
+
* version, so a migration can read what the rule will report.
|
|
47
|
+
*/
|
|
48
|
+
// implements REQ-kibi-claim-bound-proofs
|
|
49
|
+
export async function readProofContractClaimIssues(
|
|
50
|
+
context: OperationContext,
|
|
51
|
+
): Promise<readonly ProofContractClaimIssue[]> {
|
|
52
|
+
const prolog = context.prolog ?? (await context.ensureProlog?.());
|
|
53
|
+
if (prolog === undefined) return [];
|
|
54
|
+
return runOperationJsonQuery<ProofContractClaimIssue[]>(
|
|
55
|
+
prolog,
|
|
56
|
+
"claim_obligations.pl",
|
|
57
|
+
"claim_obligations:proof_contract_claim_issues_json(JsonString)",
|
|
58
|
+
"Proof contract claims",
|
|
59
|
+
);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/** One finding per contract without proves and per unprovable claim key. */
|
|
63
|
+
// implements REQ-kibi-claim-bound-proofs
|
|
64
|
+
export function proofContractClaimViolations(
|
|
65
|
+
issues: readonly ProofContractClaimIssue[],
|
|
66
|
+
): Violation[] {
|
|
67
|
+
return issues.map((issue) => ({
|
|
68
|
+
rule: PROOF_CONTRACT_CLAIMS_RULE,
|
|
69
|
+
entityId: issue.test_id,
|
|
70
|
+
description:
|
|
71
|
+
issue.claim_key === undefined
|
|
72
|
+
? `${issue.test_id}: ${issue.reason}`
|
|
73
|
+
: `${issue.test_id} proves ${issue.claim_key}: ${issue.reason}`,
|
|
74
|
+
suggestion:
|
|
75
|
+
issue.claim_key === undefined
|
|
76
|
+
? `Add proof_contract.proves to ${issue.test_id} with the claim keys (CLAIM-...) of the requirement its certificate establishes; kibi proof explain on the requirement lists its claims.`
|
|
77
|
+
: `Name a modeled claim of a requirement ${issue.test_id} reaches through its scenarios, grounded by a fact this proof kind can express (a set fact for exhaustive or typecheck, a numeric fact for arithmetic or smt), or remove ${issue.claim_key} from proves.`,
|
|
78
|
+
evidence: {
|
|
79
|
+
testId: issue.test_id,
|
|
80
|
+
...(issue.claim_key === undefined ? {} : { claimKey: issue.claim_key }),
|
|
81
|
+
reason: issue.reason,
|
|
82
|
+
},
|
|
83
|
+
}));
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/** Evaluate the proof-contract-claims rule from schema 12. */
|
|
87
|
+
// implements REQ-kibi-claim-bound-proofs
|
|
88
|
+
export async function collectProofContractClaimViolations(
|
|
89
|
+
context: OperationContext,
|
|
90
|
+
rules: ReadonlySet<string>,
|
|
91
|
+
): Promise<Violation[]> {
|
|
92
|
+
if (!rules.has(PROOF_CONTRACT_CLAIMS_RULE) || context.prolog === undefined)
|
|
93
|
+
return [];
|
|
94
|
+
const schemaVersion = readKbManifest(context.workspaceRoot)?.schemaVersion;
|
|
95
|
+
if (
|
|
96
|
+
schemaVersion === undefined ||
|
|
97
|
+
schemaVersion < CLAIM_BINDING_SCHEMA_VERSION
|
|
98
|
+
)
|
|
99
|
+
return [];
|
|
100
|
+
return proofContractClaimViolations(
|
|
101
|
+
await readProofContractClaimIssues(context),
|
|
102
|
+
);
|
|
103
|
+
}
|