kibi-cli 2.16.0 → 2.18.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.
Files changed (83) hide show
  1. package/dist/cli-protocol.d.ts.map +1 -1
  2. package/dist/cli-protocol.js +13 -0
  3. package/dist/cli-register-proof.d.ts +6 -0
  4. package/dist/cli-register-proof.d.ts.map +1 -1
  5. package/dist/cli-register-proof.js +46 -2
  6. package/dist/commands/doctor.d.ts.map +1 -1
  7. package/dist/commands/doctor.js +9 -3
  8. package/dist/commands/sync/untracked-targets.d.ts.map +1 -1
  9. package/dist/commands/sync/untracked-targets.js +39 -9
  10. package/dist/commands/sync.d.ts.map +1 -1
  11. package/dist/commands/sync.js +26 -5
  12. package/dist/engine.d.ts +4 -0
  13. package/dist/engine.d.ts.map +1 -1
  14. package/dist/engine.js +40 -10
  15. package/dist/operations/modeling/predicate-applicability.d.ts.map +1 -1
  16. package/dist/operations/modeling/predicate-applicability.js +12 -3
  17. package/dist/operations/modeling/predicate-catalog-4.d.ts.map +1 -1
  18. package/dist/operations/modeling/predicate-catalog-4.js +4 -0
  19. package/dist/operations/modeling/predicate-loader.d.ts.map +1 -1
  20. package/dist/operations/modeling/predicate-loader.js +3 -0
  21. package/dist/operations/modeling/predicate-score-3.js +1 -1
  22. package/dist/operations/modeling/predicate-types.d.ts +5 -0
  23. package/dist/operations/modeling/predicate-types.d.ts.map +1 -1
  24. package/dist/operations/modeling/predicate-utils.d.ts.map +1 -1
  25. package/dist/operations/modeling/predicate-utils.js +2 -3
  26. package/dist/operations/mutation/source-authoring.d.ts +9 -0
  27. package/dist/operations/mutation/source-authoring.d.ts.map +1 -1
  28. package/dist/operations/mutation/source-authoring.js +38 -2
  29. package/dist/operations/planning/apply-plan.d.ts.map +1 -1
  30. package/dist/operations/planning/apply-plan.js +33 -8
  31. package/dist/operations/planning/migration-refusal.d.ts +14 -0
  32. package/dist/operations/planning/migration-refusal.d.ts.map +1 -0
  33. package/dist/operations/planning/migration-refusal.js +17 -0
  34. package/dist/operations/semantic-advisor/clauses.d.ts.map +1 -1
  35. package/dist/operations/semantic-advisor/clauses.js +7 -1
  36. package/dist/operations/semantic-advisor/shared.d.ts +7 -0
  37. package/dist/operations/semantic-advisor/shared.d.ts.map +1 -1
  38. package/dist/operations/semantic-advisor/shared.js +22 -3
  39. package/dist/prolog/store-lock.d.ts +22 -0
  40. package/dist/prolog/store-lock.d.ts.map +1 -1
  41. package/dist/prolog/store-lock.js +45 -1
  42. package/dist/prolog.d.ts +7 -1
  43. package/dist/prolog.d.ts.map +1 -1
  44. package/dist/prolog.js +21 -3
  45. package/dist/proof/inspect.d.ts +9 -0
  46. package/dist/proof/inspect.d.ts.map +1 -1
  47. package/dist/proof/inspect.js +10 -0
  48. package/dist/proof/integration-plan.d.ts +2 -1
  49. package/dist/proof/integration-plan.d.ts.map +1 -1
  50. package/dist/proof/integration-plan.js +30 -13
  51. package/dist/public/impact/coverage-depth-quality.d.ts.map +1 -1
  52. package/dist/public/impact/coverage-depth-quality.js +29 -1
  53. package/dist/public/operations/contracts.d.ts.map +1 -1
  54. package/dist/public/operations/contracts.js +3 -0
  55. package/dist/public/operations/discovery-executors.d.ts +14 -1
  56. package/dist/public/operations/discovery-executors.d.ts.map +1 -1
  57. package/dist/public/operations/discovery-executors.js +62 -12
  58. package/dist/public/operations/migration-plan.d.ts +2 -0
  59. package/dist/public/operations/migration-plan.d.ts.map +1 -1
  60. package/dist/public/operations/migration-plan.js +31 -0
  61. package/dist/public/operations/result-envelope.d.ts.map +1 -1
  62. package/dist/public/operations/result-envelope.js +23 -3
  63. package/dist/public/operations/source-relationship-parity.d.ts.map +1 -1
  64. package/dist/public/operations/source-relationship-parity.js +55 -1
  65. package/dist/public/skills/kibi-usage/SKILL.md +19 -4
  66. package/dist/public/skills/kibi-usage/resources/fact-lanes.md +3 -3
  67. package/dist/public/skills/kibi-usage/resources/proof.md +44 -3
  68. package/dist/utils/branch-store.d.ts +8 -0
  69. package/dist/utils/branch-store.d.ts.map +1 -1
  70. package/dist/utils/branch-store.js +27 -1
  71. package/dist/utils/content-freshness.d.ts +13 -0
  72. package/dist/utils/content-freshness.d.ts.map +1 -0
  73. package/dist/utils/content-freshness.js +161 -0
  74. package/package.json +2 -2
  75. package/src/public/impact/coverage-depth-quality.ts +46 -1
  76. package/src/public/operations/contracts.ts +3 -0
  77. package/src/public/operations/discovery-executors.ts +89 -11
  78. package/src/public/operations/migration-plan.ts +44 -0
  79. package/src/public/operations/result-envelope.ts +31 -3
  80. package/src/public/operations/source-relationship-parity.ts +60 -3
  81. package/src/public/skills/kibi-usage/SKILL.md +19 -4
  82. package/src/public/skills/kibi-usage/resources/fact-lanes.md +3 -3
  83. package/src/public/skills/kibi-usage/resources/proof.md +44 -3
@@ -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.12.0
5
+ version: 2.14.0
6
6
  kibiCompatibility: ">=1.0.0"
7
7
  tags:
8
8
  - kibi
@@ -148,7 +148,13 @@ error `BOOTSTRAP_PLAN_REJECTED` can follow earlier committed actions: inspect
148
148
  `data.actionResults` and `data.applied`, then re-plan from the current state.
149
149
  Its journal is terminal; never recover or replay it. `BOOTSTRAP_PLAN_INVALID`
150
150
  fails preflight before bootstrap writes or a new journal; obtain a corrected
151
- plan. If the status is
151
+ plan. A migration plan whose actions were refused before changing anything
152
+ returns `status: error`, `MIGRATION_PLAN_REFUSED` and `data.outcome:
153
+ refused`: nothing changed, so read the action `detail` and get a new plan.
154
+ `data.outcome: reconciliation_required` (status success) means an action
155
+ failed mid-way: inspect its effect before retrying. `kb_delete` of an
156
+ authored entity returns status success with `deleted: 0`; its `errors` say
157
+ why nothing was deleted. If the status is
152
158
  `committed_with_repairs`, the mutation already committed: inspect failed effects,
153
159
  execute each required repair action in order, and never retry the original
154
160
  operation. Record effect failures, followed actions, and unsafe retries in
@@ -231,7 +237,11 @@ authored. Each one is the order the write path accepts.
231
237
  fact, `requires_predicate` to `relationshipTarget` and `specified_by` to
232
238
  its scenario: `specified_by` (req → scenario) is sent in the
233
239
  requirement's write, never in the scenario's, so create the scenario
234
- first. A
240
+ first, with `assumes` links (scenario → `property_value` fact, sent in
241
+ the scenario's own write) to the values its outcome relies on. Set
242
+ `expects` only on a scenario that has `assumes` links; `expects: success`
243
+ without them only yields `scenario-feasibility-unknown` ("assumes
244
+ nothing"). A
235
245
  `modeled` entry and its grounding link must arrive in the same write
236
246
  (`modeled semantic_inventory entries (n) must equal logical grounding
237
247
  relationships (m)`); a clause not grounded yet stays `status: missing`
@@ -243,7 +253,12 @@ authored. Each one is the order the write path accepts.
243
253
  claim_keys must match logical grounding target claim_keys exactly`.
244
254
  Prefer supersession for a change of meaning: analyze the new prose,
245
255
  write its facts, write the new requirement (`-v2`) with `supersedes` to
246
- the old one, then set the old one to `status: closed`. For a wording fix
256
+ the old one, then set the old one to `status: closed`. Its scenario
257
+ follows the same rule as in recipe 1: create the new scenario (with
258
+ `assumes` to the `property_value` facts it relies on, and `expects` only
259
+ when it has them) before the `-v2` write that sends `specified_by`, then
260
+ re-point the old scenario (its `assumes` go in its own write) or close
261
+ it. For a wording fix
247
262
  that keeps the meaning, re-ground in place: analyze the new prose,
248
263
  `kb_upsert` each grounding fact with the new `claim_key`/`claim_text`
249
264
  (same fact id), then `kb_upsert` the requirement with the new ledger,
@@ -61,16 +61,16 @@ A schema can close an argument with `argument_constants` (allowed values keyed b
61
61
 
62
62
  ### Deny predicate
63
63
 
64
- Claim: “Suspended users must not publish articles.” When `permission_rule` fits, preserve its positive schema name and encode prohibition as polarity:
64
+ Claim: “Suspended users must not publish articles.” When `permission_rule` fits, preserve its positive schema name and encode prohibition as polarity. `kb_model` (`mode: "predicates"`) plans this fact for the claim; the built-in schema's fourth argument, `decision`, repeats the polarity:
65
65
 
66
66
  ```yaml
67
67
  id: FACT-SUSPENDED-PUBLISH-DENIED
68
68
  status: active
69
69
  fact_kind: predicate
70
70
  predicate_name: permission_rule
71
- predicate_args: [suspended_user, publish, article]
71
+ predicate_args: [suspended_user, publish, articles, deny]
72
72
  polarity: deny
73
- canonical_key: permission_rule(suspended_user,publish,article)
73
+ canonical_key: permission_rule(suspended_user,publish,articles,deny)
74
74
  claim_key: <advisor-issued key for this exact clause>
75
75
  claim_text: Suspended users must not publish articles.
76
76
  relationship: { type: requires_predicate, from: REQ-SUSPENDED-PUBLISH-DENIED, to: FACT-SUSPENDED-PUBLISH-DENIED }
@@ -51,6 +51,15 @@ proof_bindings: # optional provenance metadata
51
51
 
52
52
  Provenance lives in `proof_bindings`; the contract stays semantic.
53
53
 
54
+ - Every `required_proofs[].symbol_id` must name an existing `symbol` entity,
55
+ or `kb_check` reports `proof-contract-symbols`. Create the symbols first
56
+ (`kb_upsert`, narrow method-level granularity, never a `type-shape`
57
+ symbol): a production symbol `implements` the requirement and is
58
+ `covered_by` the test; an executable test symbol is `executable_for` it.
59
+ - `proof_bindings[].source_file`, when set, must equal the named symbol's
60
+ `sourceFile`: for a production symbol that is the production file, not the
61
+ spec file that exercises it. Omit it when unsure.
62
+
54
63
  ## Integration configuration
55
64
 
56
65
  `.kb/proof/integrations.json` (`kibi.proof-integration.v1`) is tracked,
@@ -62,12 +71,21 @@ returns `integrationPlan`, a hash-bound `kibi.migration-plan.v2`, plus the
62
71
  proposal to the human, then apply the plan with `kb_apply_plan` (or
63
72
  `kibi apply-plan --input -`) passing `plan`, `approvedPlanHash` (the plan's
64
73
  `planHash`) and `approvedActionIds`. Once the file exists, a create plan is
65
- refused; `kibi proof inspect --update <id> --json` plans adding or replacing
66
- one named integration.
74
+ refused (`status: error`, `MIGRATION_PLAN_REFUSED`, `data.outcome: refused`;
75
+ nothing is written); `kibi proof inspect --update <id> --json` plans adding or
76
+ replacing one named integration.
67
77
 
68
78
  - `producer: command` — Kibi synthesizes the envelope from the process
69
79
  outcome; obligations are bound with `aggregate_run` provenance and the
70
- single process invocation counts as the known first attempt.
80
+ single process invocation counts as the known first attempt. The run is
81
+ judged as a whole: any failing test in it fails every obligation of every
82
+ test that names the integration, so a suite with unrelated failures proves
83
+ nothing. Narrow the command to the proof-bearing tests with
84
+ `kibi proof inspect --update <id> --command "<command>" --json` (without
85
+ `--command` the proposal is the `package.json` `test` script or the
86
+ detected runner again), or make the run attributable per test: have the
87
+ command write a `kibi.proof-test-report.v1` to the path in
88
+ `KIBI_PROOF_TEST_REPORT` so each test is judged by its own steps.
71
89
  - `producer: playwright` (or custom) — the child emits `kibi.proof-run.v1`
72
90
  itself (bundled reporter: `kibi-cli/playwright-reporter`).
73
91
  - `producer: junit` / `tap` — Kibi converts the native report at `artifact`
@@ -79,6 +97,29 @@ Cosmetic edits (descriptions, labels) never stale proof. Execution-relevant
79
97
  edits (command, artifact, targets, options, bindings, contract) change the
80
98
  effective fingerprint and do.
81
99
 
100
+ ## What counts toward proofProven
101
+
102
+ A requirement is proven only by its scenarios' tests, and a scenario test
103
+ counts only when it is an **end-to-end** test with a valid, fresh, passing
104
+ receipt for the current snapshot.
105
+
106
+ - The scope comes from the test entity's `verification_scope`
107
+ (`end_to_end`, `integration`, `unit`), never from the integration id or the
108
+ producer. Without `verification_scope`, a test tagged `e2e` (or whose
109
+ `source` path contains `e2e`) counts as end-to-end; anything else is
110
+ `unknown` scope.
111
+ - A `unit` or `integration` receipt is still recorded on the test entity, but
112
+ it does not move `proofProven` for a behavioural requirement: the
113
+ requirement keeps the `missing_passing_e2e` gap in `proofGaps`. The one
114
+ exception is a scenario whose specifying requirements are all grounded only
115
+ in component-scope UI predicates (`ui_pattern`, `same_pattern`,
116
+ `pattern_marker`); a unit or integration test that renders the component
117
+ may prove it.
118
+ - `coverage_depth` (`kb_coverage`, `kb_find_gaps`) follows the test's
119
+ authored `status` (`passing`) and scope, not its receipts: a passing
120
+ receipt does not change it, and a `passing` status without a fresh receipt
121
+ proves nothing.
122
+
82
123
  ## Attempt semantics (factual, never optimistic)
83
124
 
84
125
  - `attempts: {status: "complete", entries: [...]}` — known history; the
@@ -21,6 +21,14 @@ export declare function inspectBranchStore(workspaceRoot: string, branch: string
21
21
  * auto-heal; the reason exists so operators see the state before it bites.
22
22
  */
23
23
  export declare function storeLockJournalReason(storePath: string): Record<string, unknown> | null;
24
+ /**
25
+ * An informational notice that this branch store's lock was taken over from
26
+ * a dead holder. The takeover itself is safe and automatic; it is reported
27
+ * because it means an engine or prove run crashed or was killed while it
28
+ * held the store, which nothing else shows once the new holder rewrites the
29
+ * ownership journal. Visible for STORE_LOCK_TAKEOVER_VISIBLE_MS.
30
+ */
31
+ export declare function storeLockTakeoverReason(storePath: string, now?: Date): Record<string, unknown> | null;
24
32
  export declare function branchStoreReason(inspection: BranchStoreInspection): Record<string, unknown> | null;
25
33
  /**
26
34
  * The number of authored source files under `.kb/`: entity Markdown in the
@@ -1 +1 @@
1
- {"version":3,"file":"branch-store.d.ts","sourceRoot":"","sources":["../../src/utils/branch-store.ts"],"names":[],"mappings":"AAwBA,mEAAmE;AACnE,eAAO,MAAM,yBAAyB,8BAA8B,CAAC;AACrE,yCAAyC;AACzC,eAAO,MAAM,8BAA8B,8BAA8B,CAAC;AAE1E,MAAM,MAAM,gBAAgB,GACxB,SAAS,GACT,SAAS,GACT,YAAY,GACZ,YAAY,CAAC;AAEjB,MAAM,MAAM,qBAAqB,GAAG,QAAQ,CAAC;IAC3C,KAAK,EAAE,gBAAgB,CAAC;IACxB,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,gBAAgB,EAAE,OAAO,CAAC;CAC3B,CAAC,CAAC;AAEH;;;GAGG;AACH,wBAAgB,kBAAkB,CAChC,aAAa,EAAE,MAAM,EACrB,MAAM,EAAE,MAAM,GACb,qBAAqB,CA0GvB;AAED;;;;GAIG;AAEH,wBAAgB,sBAAsB,CACpC,SAAS,EAAE,MAAM,GAChB,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAiBhC;AAED,wBAAgB,iBAAiB,CAC/B,UAAU,EAAE,qBAAqB,GAChC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAWhC;AA6BD;;;;GAIG;AAEH,wBAAgB,sBAAsB,CAAC,aAAa,EAAE,MAAM,GAAG,MAAM,CAOpE;AAED,MAAM,MAAM,sBAAsB,GAAG,QAAQ,CAAC;IAC5C,oEAAoE;IACpE,QAAQ,EAAE,OAAO,CAAC;IAClB,0EAA0E;IAC1E,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;CAC3B,CAAC,CAAC;AAEH;;;;;;GAMG;AAEH,wBAAgB,sBAAsB,CACpC,UAAU,EAAE,qBAAqB,GAChC,sBAAsB,CAwBxB;AAkBD;;;;;;GAMG;AAEH,wBAAgB,2BAA2B,CACzC,aAAa,EAAE,MAAM,EACrB,UAAU,EAAE,qBAAqB,EACjC,MAAM,EAAE,MAAM,GACb,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAwBhC"}
1
+ {"version":3,"file":"branch-store.d.ts","sourceRoot":"","sources":["../../src/utils/branch-store.ts"],"names":[],"mappings":"AA0BA,mEAAmE;AACnE,eAAO,MAAM,yBAAyB,8BAA8B,CAAC;AACrE,yCAAyC;AACzC,eAAO,MAAM,8BAA8B,8BAA8B,CAAC;AAE1E,MAAM,MAAM,gBAAgB,GACxB,SAAS,GACT,SAAS,GACT,YAAY,GACZ,YAAY,CAAC;AAEjB,MAAM,MAAM,qBAAqB,GAAG,QAAQ,CAAC;IAC3C,KAAK,EAAE,gBAAgB,CAAC;IACxB,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,gBAAgB,EAAE,OAAO,CAAC;CAC3B,CAAC,CAAC;AAEH;;;GAGG;AACH,wBAAgB,kBAAkB,CAChC,aAAa,EAAE,MAAM,EACrB,MAAM,EAAE,MAAM,GACb,qBAAqB,CA0GvB;AAED;;;;GAIG;AAEH,wBAAgB,sBAAsB,CACpC,SAAS,EAAE,MAAM,GAChB,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAiBhC;AAED;;;;;;GAMG;AAEH,wBAAgB,uBAAuB,CACrC,SAAS,EAAE,MAAM,EACjB,GAAG,GAAE,IAAiB,GACrB,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAehC;AAED,wBAAgB,iBAAiB,CAC/B,UAAU,EAAE,qBAAqB,GAChC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAWhC;AA6BD;;;;GAIG;AAEH,wBAAgB,sBAAsB,CAAC,aAAa,EAAE,MAAM,GAAG,MAAM,CAOpE;AAED,MAAM,MAAM,sBAAsB,GAAG,QAAQ,CAAC;IAC5C,oEAAoE;IACpE,QAAQ,EAAE,OAAO,CAAC;IAClB,0EAA0E;IAC1E,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;CAC3B,CAAC,CAAC;AAEH;;;;;;GAMG;AAEH,wBAAgB,sBAAsB,CACpC,UAAU,EAAE,qBAAqB,GAChC,sBAAsB,CAwBxB;AAkBD;;;;;;GAMG;AAEH,wBAAgB,2BAA2B,CACzC,aAAa,EAAE,MAAM,EACrB,UAAU,EAAE,qBAAqB,EACjC,MAAM,EAAE,MAAM,GACb,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAwBhC"}
@@ -4,7 +4,7 @@
4
4
  */
5
5
  import { existsSync, readFileSync, readdirSync, statSync, } from "node:fs";
6
6
  import * as path from "node:path";
7
- import { classifyStoreLockHolder, readStoreLockOwner, } from "../prolog/store-lock.js";
7
+ import { STORE_LOCK_TAKEOVER_VISIBLE_MS, classifyStoreLockHolder, readStoreLockOwner, readStoreLockTakeover, } from "../prolog/store-lock.js";
8
8
  import { branchStoreManifestMatches, branchStorePath, legacyBranchStorePath, } from "./branch-store-locator.js";
9
9
  import { ENTITY_LANES, KB_ROOT } from "./kb-paths.js";
10
10
  /** Stale-reason code of a branch store that was never compiled. */
@@ -147,6 +147,32 @@ export function storeLockJournalReason(storePath) {
147
147
  },
148
148
  };
149
149
  }
150
+ /**
151
+ * An informational notice that this branch store's lock was taken over from
152
+ * a dead holder. The takeover itself is safe and automatic; it is reported
153
+ * because it means an engine or prove run crashed or was killed while it
154
+ * held the store, which nothing else shows once the new holder rewrites the
155
+ * ownership journal. Visible for STORE_LOCK_TAKEOVER_VISIBLE_MS.
156
+ */
157
+ // implements REQ-core-journaled-engine-persistence
158
+ export function storeLockTakeoverReason(storePath, now = new Date()) {
159
+ const record = readStoreLockTakeover(storePath);
160
+ if (record === null)
161
+ return null;
162
+ const age = now.getTime() - Date.parse(record.at);
163
+ if (age < 0 || age > STORE_LOCK_TAKEOVER_VISIBLE_MS)
164
+ return null;
165
+ return {
166
+ code: "store_lock_taken_over",
167
+ path: record.path,
168
+ entityIds: [],
169
+ blocking: false,
170
+ brokenPid: record.brokenPid,
171
+ reason: record.reason,
172
+ at: record.at,
173
+ detail: `Kibi broke a stale branch-store lock at ${record.at} (${record.reason || "holder no longer running"}) and reattached. Nothing needs repair; a holder that died while it held the store usually means an engine or kibi prove run crashed or was killed.`,
174
+ };
175
+ }
150
176
  export function branchStoreReason(inspection) {
151
177
  if (inspection.state === "healthy")
152
178
  return null;
@@ -0,0 +1,13 @@
1
+ /** Stale-reason codes that compare file times only. */
2
+ export declare const MTIME_SOURCE_REASON_CODES: ReadonlySet<string>;
3
+ type Reason = Readonly<Record<string, unknown>>;
4
+ /**
5
+ * Drop mtime-only stale reasons whose file content provably matches what the
6
+ * store compiled. Reasons without such evidence are kept.
7
+ */
8
+ export declare function confirmSourceNewerReasons(workspaceRoot: string, storePath: string, reasons: readonly Reason[]): {
9
+ kept: Reason[];
10
+ dropped: Reason[];
11
+ };
12
+ export {};
13
+ //# sourceMappingURL=content-freshness.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"content-freshness.d.ts","sourceRoot":"","sources":["../../src/utils/content-freshness.ts"],"names":[],"mappings":"AAYA,uDAAuD;AACvD,eAAO,MAAM,yBAAyB,EAAE,WAAW,CAAC,MAAM,CAIxD,CAAC;AAEH,KAAK,MAAM,GAAG,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;AA6IhD;;;GAGG;AAEH,wBAAgB,yBAAyB,CACvC,aAAa,EAAE,MAAM,EACrB,SAAS,EAAE,MAAM,EACjB,OAAO,EAAE,SAAS,MAAM,EAAE,GACzB;IAAE,IAAI,EAAE,MAAM,EAAE,CAAC;IAAC,OAAO,EAAE,MAAM,EAAE,CAAA;CAAE,CAkBvC"}
@@ -0,0 +1,161 @@
1
+ /*
2
+ Kibi — repo-local, per-branch, queryable long-term memory for software projects
3
+ Copyright (C) 2026 Piotr Franczyk
4
+ */
5
+ import { createHash } from "node:crypto";
6
+ import { existsSync, readFileSync } from "node:fs";
7
+ import * as path from "node:path";
8
+ import { load as parseYAML } from "js-yaml";
9
+ import { hashManifestWithCoordinates } from "../commands/sync/cache.js";
10
+ import { isSymbolsManifestPath } from "./kb-paths.js";
11
+ /** Stale-reason codes that compare file times only. */
12
+ export const MTIME_SOURCE_REASON_CODES = new Set([
13
+ "indexed_source_newer",
14
+ "knowledge_source_newer",
15
+ "documentation_source_newer",
16
+ ]);
17
+ function sha256(bytes) {
18
+ return createHash("sha256").update(bytes).digest("hex");
19
+ }
20
+ function readJson(file) {
21
+ try {
22
+ return JSON.parse(readFileSync(file, "utf8"));
23
+ }
24
+ catch {
25
+ return null;
26
+ }
27
+ }
28
+ function record(value) {
29
+ return value !== null && typeof value === "object" && !Array.isArray(value)
30
+ ? value
31
+ : null;
32
+ }
33
+ /**
34
+ * Content evidence recorded by the last compilation: the store's sync-cache
35
+ * source hashes, and the per-symbol `sourceHash` of the generated coordinate
36
+ * artifact. A file whose bytes match that evidence has not changed since it
37
+ * was compiled, however new its mtime (a fresh clone, a checkout, a touch).
38
+ */
39
+ class CompiledContentEvidence {
40
+ workspaceRoot;
41
+ hashes;
42
+ coordinateHashes = null;
43
+ manifestState = new Map();
44
+ constructor(workspaceRoot, storePath) {
45
+ this.workspaceRoot = workspaceRoot;
46
+ const cache = record(readJson(path.join(storePath, "sync-cache.json")));
47
+ const hashes = record(cache?.hashes) ?? {};
48
+ this.hashes = Object.fromEntries(Object.entries(hashes).filter((entry) => typeof entry[1] === "string"));
49
+ }
50
+ fileHash(relative) {
51
+ try {
52
+ return sha256(readFileSync(path.resolve(this.workspaceRoot, relative)));
53
+ }
54
+ catch {
55
+ return null;
56
+ }
57
+ }
58
+ manifest(key) {
59
+ const cached = this.manifestState.get(key);
60
+ if (cached)
61
+ return cached;
62
+ const absolute = path.resolve(this.workspaceRoot, key);
63
+ let state = { matches: false, sources: new Set() };
64
+ try {
65
+ const matches = hashManifestWithCoordinates(this.workspaceRoot, absolute, path.join(path.dirname(absolute), "symbol-coordinates.yaml")) === this.hashes[key];
66
+ const parsed = record(parseYAML(readFileSync(absolute, "utf8")));
67
+ const sources = new Set();
68
+ for (const symbol of Array.isArray(parsed?.symbols)
69
+ ? parsed.symbols
70
+ : []) {
71
+ const row = record(symbol);
72
+ const source = row?.sourceFile ?? row?.source;
73
+ if (typeof source === "string" && source.length > 0)
74
+ sources.add(source.replaceAll("\\", "/"));
75
+ }
76
+ state = { matches, sources };
77
+ }
78
+ catch {
79
+ // An unreadable manifest proves nothing; keep the reason.
80
+ }
81
+ this.manifestState.set(key, state);
82
+ return state;
83
+ }
84
+ coordinates() {
85
+ if (this.coordinateHashes)
86
+ return this.coordinateHashes;
87
+ const byFile = new Map();
88
+ for (const key of Object.keys(this.hashes)) {
89
+ if (!isSymbolsManifestPath(key) || !key.endsWith("symbols.yaml"))
90
+ continue;
91
+ const artifact = path.resolve(this.workspaceRoot, path.dirname(key), "symbol-coordinates.yaml");
92
+ if (!existsSync(artifact))
93
+ continue;
94
+ try {
95
+ const parsed = record(parseYAML(readFileSync(artifact, "utf8")));
96
+ for (const value of Object.values(record(parsed?.coordinates) ?? {})) {
97
+ const row = record(value);
98
+ if (typeof row?.sourceFile !== "string" ||
99
+ typeof row.sourceHash !== "string")
100
+ continue;
101
+ const hashes = byFile.get(row.sourceFile) ?? new Set();
102
+ hashes.add(row.sourceHash);
103
+ byFile.set(row.sourceFile, hashes);
104
+ }
105
+ }
106
+ catch {
107
+ // A malformed artifact proves nothing.
108
+ }
109
+ }
110
+ this.coordinateHashes = byFile;
111
+ return byFile;
112
+ }
113
+ /** True only when recorded evidence proves the file's bytes are unchanged. */
114
+ unchanged(relative) {
115
+ const key = relative.replaceAll("\\", "/");
116
+ if (isSymbolsManifestPath(key) && this.hashes[key] !== undefined)
117
+ return this.manifest(key).matches;
118
+ const current = this.fileHash(key);
119
+ if (current === null)
120
+ return false;
121
+ const cached = this.hashes[key];
122
+ if (cached !== undefined)
123
+ return cached === current;
124
+ const coordinate = this.coordinates().get(key);
125
+ if (coordinate !== undefined && coordinate.size > 0)
126
+ return [...coordinate].every((hash) => hash === current);
127
+ // A code file that is only referenced from a symbol manifest is covered
128
+ // by that manifest's composite hash, which includes every referenced
129
+ // source's bytes.
130
+ return Object.keys(this.hashes).some((manifestKey) => {
131
+ if (!isSymbolsManifestPath(manifestKey))
132
+ return false;
133
+ const manifest = this.manifest(manifestKey);
134
+ return manifest.sources.has(key) && manifest.matches;
135
+ });
136
+ }
137
+ }
138
+ /**
139
+ * Drop mtime-only stale reasons whose file content provably matches what the
140
+ * store compiled. Reasons without such evidence are kept.
141
+ */
142
+ // implements REQ-cli-status-pre-first-sync
143
+ export function confirmSourceNewerReasons(workspaceRoot, storePath, reasons) {
144
+ const kept = [];
145
+ const dropped = [];
146
+ let evidence = null;
147
+ for (const reason of reasons) {
148
+ if (typeof reason.code !== "string" ||
149
+ !MTIME_SOURCE_REASON_CODES.has(reason.code) ||
150
+ typeof reason.path !== "string") {
151
+ kept.push(reason);
152
+ continue;
153
+ }
154
+ evidence ??= new CompiledContentEvidence(workspaceRoot, storePath);
155
+ if (evidence.unchanged(reason.path))
156
+ dropped.push(reason);
157
+ else
158
+ kept.push(reason);
159
+ }
160
+ return { kept, dropped };
161
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "kibi-cli",
3
- "version": "2.16.0",
3
+ "version": "2.18.0",
4
4
  "type": "module",
5
5
  "description": "Kibi CLI for knowledge base management",
6
6
  "engines": {
@@ -235,7 +235,7 @@
235
235
  "fast-glob": "^3.2.12",
236
236
  "gray-matter": "^4.0.3",
237
237
  "js-yaml": "^4.1.0",
238
- "kibi-core": "^0.18.1",
238
+ "kibi-core": "^0.19.0",
239
239
  "kibi-plugin-builtin": "^0.5.0",
240
240
  "kibi-plugin-sdk": "^0.5.0",
241
241
  "kibi-swipl": "^1.0.1",
@@ -276,6 +276,46 @@ function coverageForRequirement(
276
276
  };
277
277
  }
278
278
 
279
+ /**
280
+ * Requirement statuses that keep a requirement in the coverage-depth review.
281
+ * `closed` is reviewed only when tagged `implemented` (the documented
282
+ * "done, with evidence" shape); a plain closed, deprecated or superseded
283
+ * requirement is retired history, not a coverage gap.
284
+ */
285
+ const REVIEWED_REQUIREMENT_STATUSES = new Set([
286
+ "open",
287
+ "in_progress",
288
+ "active",
289
+ "approved",
290
+ ]);
291
+
292
+ function supersededRequirementIds(
293
+ manifestResults: readonly ExtractionResult[],
294
+ ): ReadonlySet<string> {
295
+ return new Set(
296
+ manifestResults.flatMap((result) =>
297
+ result.entity.type === "req"
298
+ ? result.relationships.flatMap((relationship) =>
299
+ relationship.type === "supersedes" ? [relationship.to] : [],
300
+ )
301
+ : [],
302
+ ),
303
+ );
304
+ }
305
+
306
+ // implements REQ-audit-quality-diagnostics-v1
307
+ function isReviewableRequirement(
308
+ requirement: ExtractedEntity,
309
+ superseded: ReadonlySet<string>,
310
+ ): boolean {
311
+ if (superseded.has(requirement.id)) return false;
312
+ const status = requirement.status.trim().toLowerCase();
313
+ if (REVIEWED_REQUIREMENT_STATUSES.has(status)) return true;
314
+ return (
315
+ status === "closed" && (requirement.tags ?? []).includes("implemented")
316
+ );
317
+ }
318
+
279
319
  export function createCoverageDepthQualityDiagnostics(
280
320
  manifestResults: readonly ExtractionResult[],
281
321
  proofByRequirement: ReadonlyMap<
@@ -289,8 +329,13 @@ export function createCoverageDepthQualityDiagnostics(
289
329
  > = new Map(),
290
330
  ): readonly QualityDiagnostic[] {
291
331
  const entities = entitiesById(manifestResults);
332
+ const superseded = supersededRequirementIds(manifestResults);
292
333
  return manifestResults
293
- .filter((result) => result.entity.type === "req")
334
+ .filter(
335
+ (result) =>
336
+ result.entity.type === "req" &&
337
+ isReviewableRequirement(result.entity, superseded),
338
+ )
294
339
  .flatMap((result) => {
295
340
  const diagnostic = createDiagnostic(
296
341
  coverageForRequirement(result, manifestResults, entities),
@@ -452,6 +452,8 @@ const BASE_DATA_SCHEMAS: Readonly<
452
452
  truncated: booleanValue,
453
453
  queryAnalysis: recordValue,
454
454
  answer: recordValue,
455
+ branch: { type: ["string", "null"] },
456
+ branchStore: recordValue,
455
457
  },
456
458
  ["results", "count"],
457
459
  ),
@@ -476,6 +478,7 @@ const BASE_DATA_SCHEMAS: Readonly<
476
478
  staleReasons: recordArray,
477
479
  staleReasonCount: integerValue,
478
480
  staleReasonsTruncated: booleanValue,
481
+ notices: recordArray,
479
482
  branchAttachment: recordValue,
480
483
  proofSnapshotChanges: recordArray,
481
484
  proofSnapshotChangeCount: integerValue,