kibi-cli 2.1.0 → 2.2.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 (246) hide show
  1. package/dist/cli-protocol.d.ts.map +1 -1
  2. package/dist/cli-protocol.js +3 -2
  3. package/dist/commands/aggregated-checks.d.ts +7 -0
  4. package/dist/commands/aggregated-checks.d.ts.map +1 -1
  5. package/dist/commands/aggregated-checks.js +25 -1
  6. package/dist/commands/check.d.ts.map +1 -1
  7. package/dist/commands/check.js +21 -13
  8. package/dist/commands/doctor.d.ts.map +1 -1
  9. package/dist/commands/doctor.js +105 -24
  10. package/dist/commands/github-init.d.ts +1 -1
  11. package/dist/commands/github-init.d.ts.map +1 -1
  12. package/dist/commands/github-init.js +1 -1
  13. package/dist/commands/init-helpers.d.ts +17 -2
  14. package/dist/commands/init-helpers.d.ts.map +1 -1
  15. package/dist/commands/init-helpers.js +123 -23
  16. package/dist/commands/init.d.ts.map +1 -1
  17. package/dist/commands/init.js +110 -15
  18. package/dist/commands/prove.d.ts.map +1 -1
  19. package/dist/commands/prove.js +10 -18
  20. package/dist/commands/sync/cache.d.ts +20 -1
  21. package/dist/commands/sync/cache.d.ts.map +1 -1
  22. package/dist/commands/sync/cache.js +51 -2
  23. package/dist/commands/sync/discovery.d.ts.map +1 -1
  24. package/dist/commands/sync/discovery.js +1 -1
  25. package/dist/commands/sync/persistence.d.ts.map +1 -1
  26. package/dist/commands/sync/persistence.js +8 -0
  27. package/dist/commands/sync.d.ts.map +1 -1
  28. package/dist/commands/sync.js +11 -3
  29. package/dist/engine.d.ts +26 -0
  30. package/dist/engine.d.ts.map +1 -1
  31. package/dist/engine.js +126 -29
  32. package/dist/extractors/manifest.d.ts.map +1 -1
  33. package/dist/extractors/manifest.js +66 -28
  34. package/dist/extractors/markdown.d.ts +2 -0
  35. package/dist/extractors/markdown.d.ts.map +1 -1
  36. package/dist/extractors/markdown.js +14 -3
  37. package/dist/extractors/relationships.d.ts.map +1 -1
  38. package/dist/extractors/relationships.js +1 -0
  39. package/dist/intent-search.d.ts.map +1 -1
  40. package/dist/intent-search.js +68 -29
  41. package/dist/operations/bootstrap/generate.d.ts.map +1 -1
  42. package/dist/operations/bootstrap/generate.js +4 -3
  43. package/dist/operations/modeling/model-requirement.d.ts +1 -1
  44. package/dist/operations/modeling/model-requirement.d.ts.map +1 -1
  45. package/dist/operations/modeling/model-requirement.js +176 -10
  46. package/dist/operations/modeling/predicate-applyplan.d.ts.map +1 -1
  47. package/dist/operations/modeling/predicate-applyplan.js +14 -2
  48. package/dist/operations/modeling/predicate-loader.d.ts.map +1 -1
  49. package/dist/operations/modeling/predicate-loader.js +24 -0
  50. package/dist/operations/modeling/predicate-types.d.ts +4 -0
  51. package/dist/operations/modeling/predicate-types.d.ts.map +1 -1
  52. package/dist/operations/modeling/predicate-vocabulary.d.ts +54 -0
  53. package/dist/operations/modeling/predicate-vocabulary.d.ts.map +1 -0
  54. package/dist/operations/modeling/predicate-vocabulary.js +169 -0
  55. package/dist/operations/modeling/requirement-types.d.ts +5 -0
  56. package/dist/operations/modeling/requirement-types.d.ts.map +1 -1
  57. package/dist/operations/modeling/vocabulary-alignment.d.ts +60 -0
  58. package/dist/operations/modeling/vocabulary-alignment.d.ts.map +1 -0
  59. package/dist/operations/modeling/vocabulary-alignment.js +178 -0
  60. package/dist/operations/mutation/contradictions.d.ts +3 -0
  61. package/dist/operations/mutation/contradictions.d.ts.map +1 -1
  62. package/dist/operations/mutation/contradictions.js +29 -0
  63. package/dist/operations/mutation/delete.d.ts.map +1 -1
  64. package/dist/operations/mutation/delete.js +33 -1
  65. package/dist/operations/mutation/predicate-vocabulary-guard.d.ts +8 -0
  66. package/dist/operations/mutation/predicate-vocabulary-guard.d.ts.map +1 -0
  67. package/dist/operations/mutation/predicate-vocabulary-guard.js +64 -0
  68. package/dist/operations/mutation/relationships.d.ts +1 -1
  69. package/dist/operations/mutation/relationships.d.ts.map +1 -1
  70. package/dist/operations/mutation/relationships.js +1 -0
  71. package/dist/operations/mutation/saga.d.ts +5 -0
  72. package/dist/operations/mutation/saga.d.ts.map +1 -1
  73. package/dist/operations/mutation/saga.js +10 -0
  74. package/dist/operations/mutation/serialization.d.ts.map +1 -1
  75. package/dist/operations/mutation/serialization.js +2 -0
  76. package/dist/operations/mutation/source-authoring.d.ts +6 -0
  77. package/dist/operations/mutation/source-authoring.d.ts.map +1 -1
  78. package/dist/operations/mutation/source-authoring.js +65 -26
  79. package/dist/operations/mutation/strict-fact.d.ts.map +1 -1
  80. package/dist/operations/mutation/strict-fact.js +5 -0
  81. package/dist/operations/mutation/types.d.ts +17 -0
  82. package/dist/operations/mutation/types.d.ts.map +1 -1
  83. package/dist/operations/mutation/upsert.d.ts +6 -0
  84. package/dist/operations/mutation/upsert.d.ts.map +1 -1
  85. package/dist/operations/mutation/upsert.js +72 -4
  86. package/dist/operations/mutation/validate-upsert.d.ts.map +1 -1
  87. package/dist/operations/mutation/validate-upsert.js +24 -1
  88. package/dist/operations/mutation/validation.d.ts.map +1 -1
  89. package/dist/operations/mutation/validation.js +16 -3
  90. package/dist/operations/planning/apply-plan.d.ts.map +1 -1
  91. package/dist/operations/planning/apply-plan.js +61 -17
  92. package/dist/operations/planning/compile-intent.d.ts.map +1 -1
  93. package/dist/operations/planning/compile-intent.js +3 -1
  94. package/dist/operations/proof/code-scope.d.ts +17 -0
  95. package/dist/operations/proof/code-scope.d.ts.map +1 -0
  96. package/dist/operations/proof/code-scope.js +60 -0
  97. package/dist/operations/proof/ingest-proof.d.ts +4 -2
  98. package/dist/operations/proof/ingest-proof.d.ts.map +1 -1
  99. package/dist/operations/proof/ingest-proof.js +207 -74
  100. package/dist/operations/proof/migrate-legacy-receipts.d.ts.map +1 -1
  101. package/dist/operations/proof/migrate-legacy-receipts.js +7 -5
  102. package/dist/operations/proof/prune-receipts.d.ts.map +1 -1
  103. package/dist/operations/proof/prune-receipts.js +7 -5
  104. package/dist/plugins/compose-semantic-classifier.d.ts +6 -1
  105. package/dist/plugins/compose-semantic-classifier.d.ts.map +1 -1
  106. package/dist/plugins/compose-semantic-classifier.js +8 -3
  107. package/dist/plugins/compose-vocabulary-alignment.d.ts +23 -0
  108. package/dist/plugins/compose-vocabulary-alignment.d.ts.map +1 -0
  109. package/dist/plugins/compose-vocabulary-alignment.js +141 -0
  110. package/dist/plugins/external-allowlist.d.ts +7 -0
  111. package/dist/plugins/external-allowlist.d.ts.map +1 -1
  112. package/dist/plugins/external-allowlist.js +13 -0
  113. package/dist/plugins/index.d.ts +2 -0
  114. package/dist/plugins/index.d.ts.map +1 -1
  115. package/dist/plugins/index.js +4 -0
  116. package/dist/plugins/registry.d.ts +2 -1
  117. package/dist/plugins/registry.d.ts.map +1 -1
  118. package/dist/plugins/registry.js +22 -1
  119. package/dist/prolog/codec.d.ts.map +1 -1
  120. package/dist/prolog/codec.js +2 -0
  121. package/dist/prolog.d.ts +36 -0
  122. package/dist/prolog.d.ts.map +1 -1
  123. package/dist/prolog.js +78 -9
  124. package/dist/public/diagnostic-usage.d.ts.map +1 -1
  125. package/dist/public/diagnostic-usage.js +7 -25
  126. package/dist/public/impact/full-kb-quality.d.ts.map +1 -1
  127. package/dist/public/impact/full-kb-quality.js +4 -12
  128. package/dist/public/operations/catalog.d.ts +36 -7
  129. package/dist/public/operations/catalog.d.ts.map +1 -1
  130. package/dist/public/operations/check-executor.d.ts.map +1 -1
  131. package/dist/public/operations/check-executor.js +10 -2
  132. package/dist/public/operations/check-helpers.d.ts +6 -0
  133. package/dist/public/operations/check-helpers.d.ts.map +1 -1
  134. package/dist/public/operations/check-helpers.js +21 -1
  135. package/dist/public/operations/contracts.d.ts.map +1 -1
  136. package/dist/public/operations/contracts.js +16 -0
  137. package/dist/public/operations/discovery-entities.d.ts +42 -2
  138. package/dist/public/operations/discovery-entities.d.ts.map +1 -1
  139. package/dist/public/operations/discovery-entities.js +195 -22
  140. package/dist/public/operations/discovery-executors.d.ts +1 -0
  141. package/dist/public/operations/discovery-executors.d.ts.map +1 -1
  142. package/dist/public/operations/discovery-executors.js +75 -46
  143. package/dist/public/operations/migration-plan.d.ts +2 -0
  144. package/dist/public/operations/migration-plan.d.ts.map +1 -1
  145. package/dist/public/operations/migration-plan.js +50 -0
  146. package/dist/public/operations/predicate-schema-conformance.d.ts +41 -0
  147. package/dist/public/operations/predicate-schema-conformance.d.ts.map +1 -0
  148. package/dist/public/operations/predicate-schema-conformance.js +242 -0
  149. package/dist/public/operations/prolog-json.d.ts.map +1 -1
  150. package/dist/public/operations/prolog-json.js +9 -3
  151. package/dist/public/operations/result-envelope.d.ts +34 -1
  152. package/dist/public/operations/result-envelope.d.ts.map +1 -1
  153. package/dist/public/operations/result-envelope.js +78 -2
  154. package/dist/public/operations/runtime-types.d.ts +1 -1
  155. package/dist/public/operations/runtime-types.d.ts.map +1 -1
  156. package/dist/public/operations/specs/check-rules.generated.d.ts +1 -1
  157. package/dist/public/operations/specs/check-rules.generated.d.ts.map +1 -1
  158. package/dist/public/operations/specs/check-rules.generated.js +8 -1
  159. package/dist/public/operations/specs/check.d.ts +1 -1
  160. package/dist/public/operations/specs/discovery.d.ts +7 -1
  161. package/dist/public/operations/specs/discovery.d.ts.map +1 -1
  162. package/dist/public/operations/specs/discovery.js +7 -1
  163. package/dist/public/operations/specs/mutation-schemas.d.ts +27 -4
  164. package/dist/public/operations/specs/mutation-schemas.d.ts.map +1 -1
  165. package/dist/public/operations/specs/mutation-schemas.js +21 -3
  166. package/dist/public/operations/specs/reporting.d.ts +8 -0
  167. package/dist/public/operations/specs/reporting.d.ts.map +1 -1
  168. package/dist/public/operations/specs/reporting.js +46 -15
  169. package/dist/public/operations/specs/upsert.d.ts +28 -5
  170. package/dist/public/operations/specs/upsert.d.ts.map +1 -1
  171. package/dist/public/operations/specs/upsert.js +1 -1
  172. package/dist/public/operations/symbol-repair-plan.d.ts.map +1 -1
  173. package/dist/public/operations/symbol-repair-plan.js +8 -3
  174. package/dist/public/schemas/entity.d.ts.map +1 -1
  175. package/dist/public/schemas/entity.js +22 -0
  176. package/dist/public/schemas/relationship.d.ts.map +1 -1
  177. package/dist/public/schemas/relationship.js +1 -0
  178. package/dist/public/skills/kibi-usage/SKILL.md +7 -3
  179. package/dist/public/skills/kibi-usage/resources/fact-lanes.md +4 -0
  180. package/dist/public/skills/kibi-usage/resources/relationship-directions.md +35 -17
  181. package/dist/public/skills/kibi-usage/resources/source-authoring.md +36 -0
  182. package/dist/public/skills/kibi-usage/resources/workflows.md +52 -1
  183. package/dist/public/telemetry-acceptance.d.ts.map +1 -1
  184. package/dist/public/telemetry-acceptance.js +21 -5
  185. package/dist/relationships/shards.d.ts +6 -0
  186. package/dist/relationships/shards.d.ts.map +1 -1
  187. package/dist/relationships/shards.js +26 -19
  188. package/dist/schemas/entity.schema.json +32 -0
  189. package/dist/traceability/staged-diagnostics.d.ts +1 -1
  190. package/dist/traceability/staged-diagnostics.d.ts.map +1 -1
  191. package/dist/traceability/staged-entity-id-style.d.ts +12 -0
  192. package/dist/traceability/staged-entity-id-style.d.ts.map +1 -0
  193. package/dist/traceability/staged-entity-id-style.js +68 -0
  194. package/dist/traceability/temp-kb.d.ts.map +1 -1
  195. package/dist/traceability/temp-kb.js +8 -0
  196. package/dist/traceability/validate.js +1 -1
  197. package/dist/types/entities.d.ts +2 -0
  198. package/dist/types/entities.d.ts.map +1 -1
  199. package/dist/types/relationships.d.ts +1 -1
  200. package/dist/types/relationships.d.ts.map +1 -1
  201. package/dist/utils/entity-id-style.d.ts +27 -0
  202. package/dist/utils/entity-id-style.d.ts.map +1 -0
  203. package/dist/utils/entity-id-style.js +81 -0
  204. package/dist/utils/git-repository-context.d.ts +50 -0
  205. package/dist/utils/git-repository-context.d.ts.map +1 -0
  206. package/dist/utils/git-repository-context.js +259 -0
  207. package/dist/utils/rule-registry.d.ts +5 -0
  208. package/dist/utils/rule-registry.d.ts.map +1 -1
  209. package/dist/utils/rule-registry.generated.d.ts +37 -0
  210. package/dist/utils/rule-registry.generated.d.ts.map +1 -1
  211. package/dist/utils/rule-registry.generated.js +44 -0
  212. package/dist/utils/strict-modeling.d.ts +7 -0
  213. package/dist/utils/strict-modeling.d.ts.map +1 -1
  214. package/dist/utils/strict-modeling.js +11 -0
  215. package/package.json +4 -4
  216. package/schema/entities.pl +60 -1
  217. package/schema/relationships.pl +17 -4
  218. package/schema/validation.pl +118 -20
  219. package/src/public/diagnostic-usage.ts +16 -24
  220. package/src/public/impact/full-kb-quality.ts +4 -16
  221. package/src/public/operations/check-executor.ts +12 -1
  222. package/src/public/operations/check-helpers.ts +27 -1
  223. package/src/public/operations/contracts.ts +19 -0
  224. package/src/public/operations/discovery-entities.ts +292 -23
  225. package/src/public/operations/discovery-executors.ts +91 -55
  226. package/src/public/operations/migration-plan.ts +57 -0
  227. package/src/public/operations/predicate-schema-conformance.ts +362 -0
  228. package/src/public/operations/prolog-json.ts +9 -3
  229. package/src/public/operations/result-envelope.ts +114 -2
  230. package/src/public/operations/runtime-types.ts +1 -1
  231. package/src/public/operations/specs/check-rules.generated.ts +8 -1
  232. package/src/public/operations/specs/discovery.ts +8 -1
  233. package/src/public/operations/specs/mutation-schemas.ts +23 -3
  234. package/src/public/operations/specs/reporting.ts +69 -22
  235. package/src/public/operations/specs/upsert.ts +1 -1
  236. package/src/public/operations/symbol-repair-plan.ts +13 -3
  237. package/src/public/schemas/entity.ts +22 -0
  238. package/src/public/schemas/relationship.ts +1 -0
  239. package/src/public/skills/kibi-usage/SKILL.md +7 -3
  240. package/src/public/skills/kibi-usage/resources/fact-lanes.md +4 -0
  241. package/src/public/skills/kibi-usage/resources/relationship-directions.md +35 -17
  242. package/src/public/skills/kibi-usage/resources/source-authoring.md +36 -0
  243. package/src/public/skills/kibi-usage/resources/workflows.md +52 -1
  244. package/src/public/telemetry-acceptance.ts +37 -6
  245. package/src/schemas/entity.schema.json +32 -0
  246. package/src/schemas/relationship.schema.json +1 -0
@@ -0,0 +1,169 @@
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
+ function isRecord(value) {
19
+ return value !== null && typeof value === "object" && !Array.isArray(value);
20
+ }
21
+ /** Structured fields arrive as objects from Markdown or as JSON strings from Prolog. */
22
+ function structuredField(value) {
23
+ if (typeof value !== "string")
24
+ return value;
25
+ try {
26
+ return JSON.parse(value);
27
+ }
28
+ catch {
29
+ return value;
30
+ }
31
+ }
32
+ /**
33
+ * Read a schema's vocabulary. Malformed entries are ignored here; use
34
+ * {@link predicateVocabularyErrors} to report them.
35
+ */
36
+ // implements REQ-kibi-predicate-argument-constants
37
+ export function predicateVocabularyFromEntity(entity) {
38
+ const rawConstants = structuredField(entity.argument_constants);
39
+ const rawAliases = structuredField(entity.argument_aliases);
40
+ const constants = {};
41
+ if (isRecord(rawConstants)) {
42
+ for (const [name, values] of Object.entries(rawConstants)) {
43
+ if (!Array.isArray(values))
44
+ continue;
45
+ const strings = values.filter((value) => typeof value === "string" && value.length > 0);
46
+ if (strings.length > 0)
47
+ constants[name] = [...new Set(strings)];
48
+ }
49
+ }
50
+ const aliases = {};
51
+ if (isRecord(rawAliases)) {
52
+ for (const [name, mapping] of Object.entries(rawAliases)) {
53
+ if (!isRecord(mapping))
54
+ continue;
55
+ const entries = Object.entries(mapping).filter((entry) => typeof entry[1] === "string" && entry[1].length > 0);
56
+ if (entries.length > 0)
57
+ aliases[name] = Object.fromEntries(entries);
58
+ }
59
+ }
60
+ return { constants, aliases };
61
+ }
62
+ // implements REQ-kibi-predicate-argument-constants
63
+ export function hasPredicateVocabulary(vocabulary) {
64
+ return (Object.keys(vocabulary.constants).length > 0 ||
65
+ Object.keys(vocabulary.aliases).length > 0);
66
+ }
67
+ /**
68
+ * Cross-field errors in a predicate schema's vocabulary: every key must be an
69
+ * argument name, aliases need declared constants for their argument, alias
70
+ * targets must be declared, and an alias may not shadow a constant.
71
+ */
72
+ // implements REQ-kibi-predicate-argument-constants
73
+ export function predicateVocabularyErrors(entity) {
74
+ const errors = [];
75
+ const rawConstants = structuredField(entity.argument_constants);
76
+ const rawAliases = structuredField(entity.argument_aliases);
77
+ if (rawConstants === undefined && rawAliases === undefined)
78
+ return errors;
79
+ if (entity.fact_kind !== "predicate_schema") {
80
+ errors.push("argument_constants and argument_aliases are only valid on fact_kind predicate_schema");
81
+ return errors;
82
+ }
83
+ const argumentNames = Array.isArray(entity.argument_names)
84
+ ? entity.argument_names.filter((name) => typeof name === "string")
85
+ : [];
86
+ if (rawConstants !== undefined && !isRecord(rawConstants)) {
87
+ errors.push("argument_constants must map argument names to constant lists");
88
+ }
89
+ if (rawAliases !== undefined && !isRecord(rawAliases)) {
90
+ errors.push("argument_aliases must map argument names to alias-to-constant objects");
91
+ }
92
+ const vocabulary = predicateVocabularyFromEntity(entity);
93
+ if (isRecord(rawConstants)) {
94
+ for (const [name, values] of Object.entries(rawConstants)) {
95
+ if (!argumentNames.includes(name)) {
96
+ errors.push(`argument_constants.${name} is not one of argument_names (${argumentNames.join(", ")})`);
97
+ }
98
+ if (!Array.isArray(values) ||
99
+ values.length === 0 ||
100
+ values.some((value) => typeof value !== "string" || value === "")) {
101
+ errors.push(`argument_constants.${name} must be a non-empty list of non-empty strings`);
102
+ }
103
+ }
104
+ }
105
+ if (isRecord(rawAliases)) {
106
+ for (const [name, mapping] of Object.entries(rawAliases)) {
107
+ if (!argumentNames.includes(name)) {
108
+ errors.push(`argument_aliases.${name} is not one of argument_names (${argumentNames.join(", ")})`);
109
+ continue;
110
+ }
111
+ const declared = vocabulary.constants[name];
112
+ if (declared === undefined) {
113
+ errors.push(`argument_aliases.${name} requires argument_constants.${name} to declare the canonical constants`);
114
+ continue;
115
+ }
116
+ if (!isRecord(mapping)) {
117
+ errors.push(`argument_aliases.${name} must map alias spellings to constants`);
118
+ continue;
119
+ }
120
+ for (const [alias, target] of Object.entries(mapping)) {
121
+ if (typeof target !== "string" || !declared.includes(target)) {
122
+ errors.push(`argument_aliases.${name}.${alias} must name a declared constant of ${name}`);
123
+ }
124
+ if (declared.includes(alias)) {
125
+ errors.push(`argument_aliases.${name}.${alias} shadows a declared constant; remove the alias`);
126
+ }
127
+ }
128
+ }
129
+ }
130
+ return errors;
131
+ }
132
+ /**
133
+ * Compare predicate arguments with a schema vocabulary. Open arguments always
134
+ * conform; an alias is reported as a rewrite, anything else not declared is
135
+ * reported as undeclared.
136
+ */
137
+ // implements REQ-kibi-predicate-argument-constants
138
+ export function predicateArgumentConformance(argumentNames, args, vocabulary) {
139
+ const rewrites = [];
140
+ const undeclared = [];
141
+ args.forEach((value, index) => {
142
+ const argumentName = argumentNames[index];
143
+ if (argumentName === undefined)
144
+ return;
145
+ const allowed = vocabulary.constants[argumentName];
146
+ if (allowed === undefined || allowed.includes(value))
147
+ return;
148
+ const canonical = vocabulary.aliases[argumentName]?.[value];
149
+ if (canonical !== undefined && allowed.includes(canonical)) {
150
+ rewrites.push({ index, argumentName, from: value, to: canonical });
151
+ return;
152
+ }
153
+ undeclared.push({ index, argumentName, value, allowed });
154
+ });
155
+ return { rewrites, undeclared };
156
+ }
157
+ /** Arguments with every alias replaced by its declared constant. */
158
+ // implements REQ-kibi-predicate-argument-constants
159
+ export function applyArgumentRewrites(args, rewrites) {
160
+ const next = [...args];
161
+ for (const rewrite of rewrites)
162
+ next[rewrite.index] = rewrite.to;
163
+ return next;
164
+ }
165
+ /** Canonical key convention shared with predicate apply plans. */
166
+ // implements REQ-kibi-predicate-argument-constants
167
+ export function predicateCanonicalKey(predicateName, args) {
168
+ return `${predicateName}(${args.join(",")})`;
169
+ }
@@ -1,5 +1,6 @@
1
1
  import type { LogicRuleIR } from "../../logic/ir.js";
2
2
  import type { SemanticClaim, StrictWriteSet } from "../../public/check-types.js";
3
+ import type { VocabularyAlignmentOutcome } from "./vocabulary-alignment.js";
3
4
  export type ModelRequirementArgs = Readonly<Record<string, unknown>> & {
4
5
  readonly text: string;
5
6
  readonly source?: string;
@@ -46,6 +47,10 @@ export interface ModelRequirementResult {
46
47
  nextAction: string;
47
48
  }>;
48
49
  migrationWarning: string | null;
50
+ /** Subject reuse decision and possible-duplicate review candidates. */
51
+ vocabularyAlignment?: VocabularyAlignmentOutcome & {
52
+ readonly reviewPlan: Array<Record<string, unknown>>;
53
+ };
49
54
  logic?: {
50
55
  readonly semanticKey: string;
51
56
  readonly claimKey: string;
@@ -1 +1 @@
1
- {"version":3,"file":"requirement-types.d.ts","sourceRoot":"","sources":["../../../src/operations/modeling/requirement-types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AACrD,OAAO,KAAK,EACV,aAAa,EACb,cAAc,EACf,MAAM,6BAA6B,CAAC;AAGrC,MAAM,MAAM,oBAAoB,GAAG,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,GAAG;IACrE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,EAAE,CAAC;IAChC,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAC;IAC9B,QAAQ,CAAC,QAAQ,CAAC,EAAE,aAAa,CAAC,UAAU,CAAC,CAAC;IAC9C,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,OAAO,CAAC;IAC3C,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAC;IAC7B,iFAAiF;IACjF,QAAQ,CAAC,mBAAmB,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IACjD,QAAQ,CAAC,KAAK,CAAC,EAAE,WAAW,CAAC;IAC7B,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,aAAa,CAAC,EAAE,MAAM,CAAC;CACjC,CAAC;AAGF,MAAM,WAAW,yBAAyB;IACxC,KAAK,EAAE,aAAa,CAAC;IACrB,cAAc,EAAE,UAAU,GAAG,WAAW,GAAG,UAAU,CAAC;IACtD,kBAAkB,EAAE,MAAM,EAAE,CAAC;CAC9B;AAGD,MAAM,WAAW,sBAAsB;IACrC,OAAO,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IAC/C,iBAAiB,EAAE;QACjB,SAAS,EAAE,MAAM,CAAC;QAClB,QAAQ,EAAE,MAAM,CAAC;QACjB,WAAW,EAAE,MAAM,EAAE,CAAC;QACtB,MAAM,EAAE,MAAM,CAAC;QACf,WAAW,EAAE,MAAM,EAAE,CAAC;QACtB,KAAK,EAAE,aAAa,CAAC;QACrB,QAAQ,EAAE,cAAc,CAAC;QACzB,SAAS,EAAE,KAAK,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;QAC1C,QAAQ,EAAE,OAAO,CAAC;QAClB,UAAU,EAAE,MAAM,CAAC;QACnB,cAAc,EAAE,UAAU,GAAG,WAAW,GAAG,UAAU,CAAC;QACtD,kBAAkB,EAAE,MAAM,EAAE,CAAC;QAC7B,QAAQ,EAAE,KAAK,CAAC;YACd,IAAI,EAAE,MAAM,CAAC;YACb,OAAO,EAAE,MAAM,CAAC;YAChB,UAAU,EAAE,MAAM,CAAC;SACpB,CAAC,CAAC;QACH,gBAAgB,EAAE,MAAM,GAAG,IAAI,CAAC;QAChC,KAAK,CAAC,EAAE;YACN,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;YAC7B,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;YAC1B,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;YAC3B,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAC;YAChC,QAAQ,CAAC,UAAU,EAAE,WAAW,CAAC;SAClC,CAAC;KACH,CAAC;IACF,SAAS,EAAE,KAAK,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;IAC1C,QAAQ,EAAE,cAAc,CAAC;IACzB,gBAAgB,EAAE,MAAM,GAAG,IAAI,CAAC;CACjC"}
1
+ {"version":3,"file":"requirement-types.d.ts","sourceRoot":"","sources":["../../../src/operations/modeling/requirement-types.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,60 @@
1
+ import { type SubjectVocabularyEntry } from "kibi-plugin-builtin";
2
+ import { type SubjectCandidate } from "kibi-plugin-sdk";
3
+ import { publicCapabilityStamp } from "../../plugins/compose-semantic-classifier.js";
4
+ import { type VocabularyAlignmentDiagnostic } from "../../plugins/compose-vocabulary-alignment.js";
5
+ import type { OperationContext, PrologPort } from "../../public/operations/runtime-types.js";
6
+ export type SubjectResolution = Readonly<{
7
+ /** reuse_existing: the plan links the existing subject fact. */
8
+ decision: "reuse_existing" | "declare_new";
9
+ subjectKey: string;
10
+ /** Existing subject fact linked via constrains when reusing. */
11
+ existingFactId: string | null;
12
+ confidence: number;
13
+ candidates: readonly SubjectCandidate[];
14
+ }>;
15
+ export type RedundancyCandidate = Readonly<{
16
+ factId: string;
17
+ subjectKey: string;
18
+ requirementIds: readonly string[];
19
+ claimText: string;
20
+ signature: string;
21
+ confidence: number;
22
+ }>;
23
+ export type VocabularyAlignmentOutcome = Readonly<{
24
+ subject: SubjectResolution;
25
+ redundancyCandidates: readonly RedundancyCandidate[];
26
+ stamps: readonly ReturnType<typeof publicCapabilityStamp>[];
27
+ fallbackUsed: boolean;
28
+ diagnostics: readonly VocabularyAlignmentDiagnostic[];
29
+ }>;
30
+ type SubjectClaim = Readonly<{
31
+ factId: string;
32
+ subjectKey: string;
33
+ signature: string;
34
+ claimText?: string;
35
+ title?: string;
36
+ requirements?: readonly string[];
37
+ }>;
38
+ type VocabularyEntryWithFact = SubjectVocabularyEntry & {
39
+ factId: string;
40
+ /** Rejected by subject-key-identity; never proposed for reuse. */
41
+ reqDerived?: boolean;
42
+ };
43
+ export declare function readSubjectVocabulary(prolog: PrologPort): Promise<VocabularyEntryWithFact[]>;
44
+ export declare function readSubjectClaims(prolog: PrologPort, subjectKeys: readonly string[]): Promise<SubjectClaim[]>;
45
+ /**
46
+ * Rank existing subjects for one modeled clause and look for possible
47
+ * paraphrase duplicates among claims already on the chosen subject.
48
+ *
49
+ * The builtin ranker always runs; a configured kibi.vocabulary-alignment.v1
50
+ * provider may refine the choice (replace / augment / shadow). The outcome is
51
+ * modeling advice: exact duplicate detection stays with the deterministic
52
+ * Prolog `domain-redundancy` check.
53
+ */
54
+ export declare function alignRequirementVocabulary(context: OperationContext, clause: Readonly<{
55
+ claimKey: string;
56
+ statement: string;
57
+ proposedSubjectKey: string;
58
+ }>): Promise<VocabularyAlignmentOutcome | null>;
59
+ export {};
60
+ //# sourceMappingURL=vocabulary-alignment.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"vocabulary-alignment.d.ts","sourceRoot":"","sources":["../../../src/operations/modeling/vocabulary-alignment.ts"],"names":[],"mappings":"AAmBA,OAAO,EACL,KAAK,sBAAsB,EAE5B,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EAGL,KAAK,gBAAgB,EACtB,MAAM,iBAAiB,CAAC;AACzB,OAAO,EAAE,qBAAqB,EAAE,MAAM,8CAA8C,CAAC;AACrF,OAAO,EACL,KAAK,6BAA6B,EAGnC,MAAM,+CAA+C,CAAC;AAOvD,OAAO,KAAK,EACV,gBAAgB,EAChB,UAAU,EACX,MAAM,0CAA0C,CAAC;AAMlD,MAAM,MAAM,iBAAiB,GAAG,QAAQ,CAAC;IACvC,gEAAgE;IAChE,QAAQ,EAAE,gBAAgB,GAAG,aAAa,CAAC;IAC3C,UAAU,EAAE,MAAM,CAAC;IACnB,gEAAgE;IAChE,cAAc,EAAE,MAAM,GAAG,IAAI,CAAC;IAC9B,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,SAAS,gBAAgB,EAAE,CAAC;CACzC,CAAC,CAAC;AAGH,MAAM,MAAM,mBAAmB,GAAG,QAAQ,CAAC;IACzC,MAAM,EAAE,MAAM,CAAC;IACf,UAAU,EAAE,MAAM,CAAC;IACnB,cAAc,EAAE,SAAS,MAAM,EAAE,CAAC;IAClC,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,MAAM,CAAC;CACpB,CAAC,CAAC;AAGH,MAAM,MAAM,0BAA0B,GAAG,QAAQ,CAAC;IAChD,OAAO,EAAE,iBAAiB,CAAC;IAC3B,oBAAoB,EAAE,SAAS,mBAAmB,EAAE,CAAC;IACrD,MAAM,EAAE,SAAS,UAAU,CAAC,OAAO,qBAAqB,CAAC,EAAE,CAAC;IAC5D,YAAY,EAAE,OAAO,CAAC;IACtB,WAAW,EAAE,SAAS,6BAA6B,EAAE,CAAC;CACvD,CAAC,CAAC;AAEH,KAAK,YAAY,GAAG,QAAQ,CAAC;IAC3B,MAAM,EAAE,MAAM,CAAC;IACf,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,YAAY,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;CAClC,CAAC,CAAC;AAEH,KAAK,uBAAuB,GAAG,sBAAsB,GAAG;IACtD,MAAM,EAAE,MAAM,CAAC;IACf,kEAAkE;IAClE,UAAU,CAAC,EAAE,OAAO,CAAC;CACtB,CAAC;AA6BF,wBAAsB,qBAAqB,CACzC,MAAM,EAAE,UAAU,GACjB,OAAO,CAAC,uBAAuB,EAAE,CAAC,CAMpC;AAGD,wBAAsB,iBAAiB,CACrC,MAAM,EAAE,UAAU,EAClB,WAAW,EAAE,SAAS,MAAM,EAAE,GAC7B,OAAO,CAAC,YAAY,EAAE,CAAC,CAQzB;AAwCD;;;;;;;;GAQG;AAEH,wBAAsB,0BAA0B,CAC9C,OAAO,EAAE,gBAAgB,EACzB,MAAM,EAAE,QAAQ,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,kBAAkB,EAAE,MAAM,CAAC;CAC5B,CAAC,GACD,OAAO,CAAC,0BAA0B,GAAG,IAAI,CAAC,CAgG5C"}
@@ -0,0 +1,178 @@
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 path from "node:path";
19
+ import { rankSubjectCandidates, } from "kibi-plugin-builtin";
20
+ import { NEW_SUBJECT_CHOICE, } from "kibi-plugin-sdk";
21
+ import { publicCapabilityStamp } from "../../plugins/compose-semantic-classifier.js";
22
+ import { composeClaimComparison, composeSubjectRanking, } from "../../plugins/compose-vocabulary-alignment.js";
23
+ import { createCapabilityRegistry, } from "../../plugins/registry.js";
24
+ import { resolveKbPlPath } from "../../prolog.js";
25
+ import { escapeAtom, toPrologAtom } from "../../prolog/codec.js";
26
+ /** Operation name used for the external-provider allowlist. */
27
+ const OPERATION_NAME = "kb_model_requirement";
28
+ function semanticQualityModulePath() {
29
+ return path.join(path.dirname(resolveKbPlPath()), "semantic_quality.pl");
30
+ }
31
+ function parseJsonBinding(raw) {
32
+ if (raw === undefined || raw === "")
33
+ return [];
34
+ let parsed = JSON.parse(raw);
35
+ if (typeof parsed === "string")
36
+ parsed = JSON.parse(parsed);
37
+ return parsed;
38
+ }
39
+ async function queryJson(prolog, goal) {
40
+ const modulePath = escapeAtom(semanticQualityModulePath());
41
+ // call/1 defers module resolution until use_module has loaded the module;
42
+ // a literal qualified goal is checked when the query is read.
43
+ const result = await prolog.query(`(use_module('${modulePath}'), call(${goal}))`);
44
+ if (!result.success) {
45
+ throw new Error(`Vocabulary query failed: ${result.error ?? "unknown error"}`);
46
+ }
47
+ return parseJsonBinding(result.bindings.Json);
48
+ }
49
+ // implements REQ-kibi-subject-vocabulary
50
+ export async function readSubjectVocabulary(prolog) {
51
+ const parsed = await queryJson(prolog, "semantic_quality:subject_vocabulary_json(Json)");
52
+ return Array.isArray(parsed) ? parsed : [];
53
+ }
54
+ // implements REQ-kibi-subject-vocabulary
55
+ export async function readSubjectClaims(prolog, subjectKeys) {
56
+ if (subjectKeys.length === 0)
57
+ return [];
58
+ const list = subjectKeys.map((key) => toPrologAtom(key)).join(",");
59
+ const parsed = await queryJson(prolog, `semantic_quality:subject_claims_json([${list}], Json)`);
60
+ return Array.isArray(parsed) ? parsed : [];
61
+ }
62
+ /**
63
+ * Keep an existing subject whose key equals the proposed key among the
64
+ * candidates even when it ranks outside the top k, so the plan reuses it
65
+ * instead of declaring a second subject fact with the same key.
66
+ */
67
+ function withExactSubject(candidates, vocabulary, proposedSubjectKey) {
68
+ if (candidates.some((c) => c.subjectKey === proposedSubjectKey)) {
69
+ return [...candidates];
70
+ }
71
+ const exact = vocabulary.find((entry) => entry.subjectKey === proposedSubjectKey);
72
+ if (exact === undefined)
73
+ return [...candidates];
74
+ return [
75
+ ...candidates,
76
+ {
77
+ subjectKey: exact.subjectKey,
78
+ score: 0,
79
+ ...(exact.title !== undefined ? { title: exact.title } : {}),
80
+ },
81
+ ];
82
+ }
83
+ async function resolveRegistry(context) {
84
+ if (context.ensurePlugins)
85
+ return context.ensurePlugins();
86
+ // Hosts without a plugin runtime get the builtin provider only.
87
+ return createCapabilityRegistry({
88
+ workspaceRoot: context.workspaceRoot,
89
+ projectConfig: {},
90
+ });
91
+ }
92
+ /**
93
+ * Rank existing subjects for one modeled clause and look for possible
94
+ * paraphrase duplicates among claims already on the chosen subject.
95
+ *
96
+ * The builtin ranker always runs; a configured kibi.vocabulary-alignment.v1
97
+ * provider may refine the choice (replace / augment / shadow). The outcome is
98
+ * modeling advice: exact duplicate detection stays with the deterministic
99
+ * Prolog `domain-redundancy` check.
100
+ */
101
+ // implements REQ-kibi-subject-vocabulary, REQ-kibi-vocabulary-alignment-capability
102
+ export async function alignRequirementVocabulary(context, clause) {
103
+ const prolog = context.prolog;
104
+ if (!prolog)
105
+ return null;
106
+ const vocabulary = (await readSubjectVocabulary(prolog)).filter((entry) => entry.reqDerived !== true);
107
+ const candidates = withExactSubject(rankSubjectCandidates(clause.statement, vocabulary), vocabulary, clause.proposedSubjectKey);
108
+ const registry = await resolveRegistry(context);
109
+ const resolution = await registry.resolveVocabularyAlignment();
110
+ const ranking = await composeSubjectRanking(resolution, {
111
+ clauses: [
112
+ {
113
+ claimKey: clause.claimKey,
114
+ text: clause.statement,
115
+ proposedSubjectKey: clause.proposedSubjectKey,
116
+ candidates,
117
+ },
118
+ ],
119
+ }, { operationName: OPERATION_NAME });
120
+ const decision = ranking.results[0];
121
+ const chosen = decision !== undefined && decision.choice !== NEW_SUBJECT_CHOICE
122
+ ? vocabulary.find((entry) => entry.subjectKey === decision.choice)
123
+ : undefined;
124
+ const subject = chosen
125
+ ? {
126
+ decision: "reuse_existing",
127
+ subjectKey: chosen.subjectKey,
128
+ existingFactId: chosen.factId,
129
+ confidence: decision?.confidence ?? 0,
130
+ candidates,
131
+ }
132
+ : {
133
+ decision: "declare_new",
134
+ subjectKey: clause.proposedSubjectKey,
135
+ existingFactId: null,
136
+ confidence: decision?.confidence ?? 1,
137
+ candidates,
138
+ };
139
+ let redundancyCandidates = [];
140
+ let comparison = null;
141
+ if (subject.decision === "reuse_existing") {
142
+ const claims = await readSubjectClaims(prolog, [subject.subjectKey]);
143
+ const pairs = claims.map((claim) => ({
144
+ pairKey: claim.factId,
145
+ sharedKey: subject.subjectKey,
146
+ left: { claimText: clause.statement },
147
+ right: {
148
+ claimText: claim.claimText || claim.title || claim.factId,
149
+ signature: claim.signature,
150
+ factId: claim.factId,
151
+ },
152
+ }));
153
+ comparison = await composeClaimComparison(resolution, { pairs }, { operationName: OPERATION_NAME });
154
+ const claimsById = new Map(claims.map((claim) => [claim.factId, claim]));
155
+ redundancyCandidates = comparison.results.flatMap((judgment) => {
156
+ const claim = claimsById.get(judgment.pairKey);
157
+ if (!judgment.sameObligation || claim === undefined)
158
+ return [];
159
+ return [
160
+ {
161
+ factId: claim.factId,
162
+ subjectKey: claim.subjectKey,
163
+ requirementIds: claim.requirements ?? [],
164
+ claimText: claim.claimText || claim.title || "",
165
+ signature: claim.signature,
166
+ confidence: judgment.confidence,
167
+ },
168
+ ];
169
+ });
170
+ }
171
+ return {
172
+ subject,
173
+ redundancyCandidates,
174
+ stamps: [...ranking.stamps, ...(comparison?.stamps ?? [])].map(publicCapabilityStamp),
175
+ fallbackUsed: ranking.fallbackUsed || comparison?.fallbackUsed === true,
176
+ diagnostics: [...ranking.diagnostics, ...(comparison?.diagnostics ?? [])],
177
+ };
178
+ }
@@ -6,6 +6,9 @@ type TransactionInput = {
6
6
  readonly skipContradictionCheck: boolean;
7
7
  };
8
8
  export declare function buildUpsertCommitGoal(input: TransactionInput): string;
9
+ /** One transaction and one journal flush for a receipt campaign. */
10
+ export declare function buildUpsertBatchCommitGoal(inputs: readonly TransactionInput[]): string;
11
+ export declare function parseUpsertChangeKinds(bindings: Readonly<Record<string, string>>, count: number): Array<"created" | "updated">;
9
12
  export declare function formatUpsertError(entityId: string, raw?: string, record?: PrologErrorRecord): string;
10
13
  export {};
11
14
  //# sourceMappingURL=contradictions.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"contradictions.d.ts","sourceRoot":"","sources":["../../../src/operations/mutation/contradictions.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,6BAA6B,CAAC;AASrE,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAC;AAEpD,KAAK,gBAAgB,GAAG;IACtB,QAAQ,CAAC,MAAM,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;IACnD,QAAQ,CAAC,aAAa,EAAE,SAAS,iBAAiB,EAAE,CAAC;IACrD,QAAQ,CAAC,sBAAsB,EAAE,OAAO,CAAC;CAC1C,CAAC;AAGF,wBAAgB,qBAAqB,CAAC,KAAK,EAAE,gBAAgB,GAAG,MAAM,CAOrE;AAGD,wBAAgB,iBAAiB,CAC/B,QAAQ,EAAE,MAAM,EAChB,GAAG,CAAC,EAAE,MAAM,EACZ,MAAM,CAAC,EAAE,iBAAiB,GACzB,MAAM,CAOR"}
1
+ {"version":3,"file":"contradictions.d.ts","sourceRoot":"","sources":["../../../src/operations/mutation/contradictions.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,6BAA6B,CAAC;AASrE,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAC;AAEpD,KAAK,gBAAgB,GAAG;IACtB,QAAQ,CAAC,MAAM,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;IACnD,QAAQ,CAAC,aAAa,EAAE,SAAS,iBAAiB,EAAE,CAAC;IACrD,QAAQ,CAAC,sBAAsB,EAAE,OAAO,CAAC;CAC1C,CAAC;AAGF,wBAAgB,qBAAqB,CAAC,KAAK,EAAE,gBAAgB,GAAG,MAAM,CAOrE;AAWD,oEAAoE;AAEpE,wBAAgB,0BAA0B,CACxC,MAAM,EAAE,SAAS,gBAAgB,EAAE,GAClC,MAAM,CAKR;AAGD,wBAAgB,sBAAsB,CACpC,QAAQ,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,EAC1C,KAAK,EAAE,MAAM,GACZ,KAAK,CAAC,SAAS,GAAG,SAAS,CAAC,CAe9B;AAGD,wBAAgB,iBAAiB,CAC/B,QAAQ,EAAE,MAAM,EAChB,GAAG,CAAC,EAAE,MAAM,EACZ,MAAM,CAAC,EAAE,iBAAiB,GACzB,MAAM,CAOR"}
@@ -7,6 +7,35 @@ export function buildUpsertCommitGoal(input) {
7
7
  const relationships = input.relationships.map((relationship) => `rel(${String(relationship.type)}, '${escapeAtom(String(relationship.from))}', '${escapeAtom(String(relationship.to))}', ${buildRelationshipMetadata(relationship)})`);
8
8
  return `kb_commit_upsert(${type}, ${buildPropertyList(input.entity)}, [${relationships.join(", ")}], ${input.skipContradictionCheck ? "true" : "false"}, ChangeKind)`;
9
9
  }
10
+ function upsertBatchEntry(input) {
11
+ const type = String(input.entity.type);
12
+ const relationships = input.relationships.map((relationship) => `rel(${String(relationship.type)}, '${escapeAtom(String(relationship.from))}', '${escapeAtom(String(relationship.to))}', ${buildRelationshipMetadata(relationship)})`);
13
+ return `upsert(${type}, ${buildPropertyList(input.entity)}, [${relationships.join(", ")}], ${input.skipContradictionCheck ? "true" : "false"})`;
14
+ }
15
+ /** One transaction and one journal flush for a receipt campaign. */
16
+ // implements REQ-core-atomic-upsert-persistence
17
+ export function buildUpsertBatchCommitGoal(inputs) {
18
+ const first = inputs[0];
19
+ if (!first)
20
+ throw new Error("Upsert batch requires at least one entity");
21
+ if (inputs.length === 1)
22
+ return buildUpsertCommitGoal(first);
23
+ return `kb_commit_upsert_batch([${inputs.map(upsertBatchEntry).join(", ")}], ChangeKinds)`;
24
+ }
25
+ // implements REQ-core-atomic-upsert-persistence
26
+ export function parseUpsertChangeKinds(bindings, count) {
27
+ const listed = bindings.ChangeKinds;
28
+ if (typeof listed === "string") {
29
+ const kinds = [...listed.matchAll(/\b(created|updated)\b/g)].map((match) => match[1]);
30
+ if (kinds.length === count)
31
+ return kinds;
32
+ }
33
+ const one = bindings.ChangeKind;
34
+ if ((one === "created" || one === "updated") && count >= 1) {
35
+ return Array.from({ length: count }, () => one);
36
+ }
37
+ throw new Error(`Upsert batch completed without ${count} created/updated result(s)`);
38
+ }
10
39
  // implements REQ-kibi-operation-interface-parity
11
40
  export function formatUpsertError(entityId, raw, record) {
12
41
  const stage = raw?.match(/\(stage=([^)]+)\)/)?.[1];
@@ -1 +1 @@
1
- {"version":3,"file":"delete.d.ts","sourceRoot":"","sources":["../../../src/operations/mutation/delete.ts"],"names":[],"mappings":"AASA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,0CAA0C,CAAC;AACjF,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,kCAAkC,CAAC;AACxE,OAAO,EACL,KAAK,oBAAoB,EAI1B,MAAM,+BAA+B,CAAC;AAgBvC,OAAO,KAAK,EAAE,WAAW,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAc7D,wBAAgB,8BAA8B,CAC5C,EAAE,EAAE,gBAAgB,CAAC,IAAI,CAAC,GACzB,OAAO,CAAC,EAAE,IAAI,WAAW,CAAC,gBAAgB,CAAC,IAAI,CAAC,CAAC,CAMnD;AAuWD,wBAAsB,yBAAyB,CAC7C,SAAS,EAAE,SAAS,oBAAoB,EAAE,EAC1C,OAAO,EAAE,gBAAgB,EACzB,WAAW,GAAE,MAAM,IAAsB,GACxC,OAAO,CAAC,aAAa,CAAC,CA4BxB;AAED,wBAAsB,aAAa,CACjC,KAAK,EAAE,WAAW,EAClB,OAAO,EAAE,gBAAgB,GACxB,OAAO,CAAC,eAAe,CAAC,aAAa,CAAC,CAAC,CA6OzC"}
1
+ {"version":3,"file":"delete.d.ts","sourceRoot":"","sources":["../../../src/operations/mutation/delete.ts"],"names":[],"mappings":"AASA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,0CAA0C,CAAC;AACjF,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,kCAAkC,CAAC;AACxE,OAAO,EACL,KAAK,oBAAoB,EAM1B,MAAM,+BAA+B,CAAC;AAgBvC,OAAO,KAAK,EAAE,WAAW,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAc7D,wBAAgB,8BAA8B,CAC5C,EAAE,EAAE,gBAAgB,CAAC,IAAI,CAAC,GACzB,OAAO,CAAC,EAAE,IAAI,WAAW,CAAC,gBAAgB,CAAC,IAAI,CAAC,CAAC,CAMnD;AAuWD,wBAAsB,yBAAyB,CAC7C,SAAS,EAAE,SAAS,oBAAoB,EAAE,EAC1C,OAAO,EAAE,gBAAgB,EACzB,WAAW,GAAE,MAAM,IAAsB,GACxC,OAAO,CAAC,aAAa,CAAC,CA4BxB;AAED,wBAAsB,aAAa,CACjC,KAAK,EAAE,WAAW,EAClB,OAAO,EAAE,gBAAgB,GACxB,OAAO,CAAC,eAAe,CAAC,aAAa,CAAC,CAAC,CA8QzC"}
@@ -3,7 +3,7 @@ import { existsSync, readFileSync, writeFileSync } from "node:fs";
3
3
  import path from "node:path";
4
4
  import { InputError, OperationError } from "../../cli-errors.js";
5
5
  import { parseEntityFromList, parseListOfLists, toPrologAtom, } from "../../prolog/codec.js";
6
- import { listShards, readAllShards, removeRelationshipsFromShards, } from "../../relationships/shards.js";
6
+ import { listShards, readAllShards, readShard, removeRelationshipsFromShards, renderShardWithout, } from "../../relationships/shards.js";
7
7
  import { resolveBranchAttachment } from "../../utils/branch-resolver.js";
8
8
  import { buildEntityDeleteAuditGoal } from "./audit.js";
9
9
  import { RELATIONSHIP_TYPES, dependentRelationshipsGoal, } from "./relationships.js";
@@ -447,6 +447,36 @@ export async function executeDelete(input, context) {
447
447
  goals.push(buildEntityDeleteAuditGoal(entity));
448
448
  }
449
449
  if (authoredIds.length > 0 && context.sourcePlanApplication !== true) {
450
+ if (context.fs) {
451
+ // A deleted entity's outgoing relationships live in canonical shards,
452
+ // not in its own document. Incoming edges already block deletion, so
453
+ // the plan removes the outgoing rows too; otherwise the compiled
454
+ // store drops them while the shards keep them.
455
+ const deleted = new Set(authoredIds);
456
+ for (const shardPath of listShards(path.join(context.workspaceRoot, ".kb"))) {
457
+ const selectors = readShard(shardPath)
458
+ .filter((record) => deleted.has(record.from))
459
+ .map(({ type, from, to }) => ({ type, from, to }));
460
+ if (selectors.length === 0)
461
+ continue;
462
+ const relativeShard = path
463
+ .relative(context.workspaceRoot, shardPath)
464
+ .replaceAll("\\", "/");
465
+ const original = readFileSync(shardPath, "utf8");
466
+ const body = renderShardWithout(original, selectors, shardPath);
467
+ const beforeHash = createHash("sha256")
468
+ .update(original)
469
+ .digest("hex");
470
+ sourceHashes[relativeShard] = beforeHash;
471
+ sourcePlansByPath.set(relativeShard, {
472
+ path: relativeShard,
473
+ mode: "write",
474
+ beforeHash,
475
+ afterHash: createHash("sha256").update(body).digest("hex"),
476
+ body,
477
+ });
478
+ }
479
+ }
450
480
  const supersessionRequired = authoredRequirementIds.length > 0;
451
481
  const sourcePlans = [...sourcePlansByPath.values()];
452
482
  const planBody = {
@@ -468,6 +498,8 @@ export async function executeDelete(input, context) {
468
498
  : "Authored entity deletion returns a hash-bound plan for kb_apply_plan.",
469
499
  ],
470
500
  deletionPlan: { ...planBody, planHash },
501
+ // Only kb_apply_plan writes; this call returns the plan.
502
+ skippedEffects: ["kb-write", "workspace-write"],
471
503
  };
472
504
  return {
473
505
  content: [
@@ -0,0 +1,8 @@
1
+ import type { PrologPort } from "../../public/operations/runtime-types.js";
2
+ /**
3
+ * Reject a predicate fact whose arguments fall outside the constants its
4
+ * project-local schema declares. An alias is rejected too, with the declared
5
+ * constant to use instead, so new facts never need migrating.
6
+ */
7
+ export declare function assertPredicateArgumentVocabulary(prolog: Pick<PrologPort, "query">, entity: Readonly<Record<string, unknown>>): Promise<void>;
8
+ //# sourceMappingURL=predicate-vocabulary-guard.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"predicate-vocabulary-guard.d.ts","sourceRoot":"","sources":["../../../src/operations/mutation/predicate-vocabulary-guard.ts"],"names":[],"mappings":"AAuBA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,0CAA0C,CAAC;AAY3E;;;;GAIG;AAEH,wBAAsB,iCAAiC,CACrD,MAAM,EAAE,IAAI,CAAC,UAAU,EAAE,OAAO,CAAC,EACjC,MAAM,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,GACxC,OAAO,CAAC,IAAI,CAAC,CA+Cf"}
@@ -0,0 +1,64 @@
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 { parseEntityFromList, parseListOfLists, toPrologAtom, } from "../../prolog/codec.js";
19
+ import { predicateArgumentConformance, predicateVocabularyFromEntity, } from "../modeling/predicate-vocabulary.js";
20
+ function text(value) {
21
+ return typeof value === "string" && value.trim() !== ""
22
+ ? value.trim()
23
+ : undefined;
24
+ }
25
+ /**
26
+ * Reject a predicate fact whose arguments fall outside the constants its
27
+ * project-local schema declares. An alias is rejected too, with the declared
28
+ * constant to use instead, so new facts never need migrating.
29
+ */
30
+ // implements REQ-kibi-predicate-argument-constants
31
+ export async function assertPredicateArgumentVocabulary(prolog, entity) {
32
+ if (entity.type !== "fact" || entity.fact_kind !== "predicate")
33
+ return;
34
+ const name = text(entity.predicate_name);
35
+ const args = Array.isArray(entity.predicate_args)
36
+ ? entity.predicate_args.filter((value) => typeof value === "string")
37
+ : [];
38
+ if (name === undefined || args.length === 0)
39
+ return;
40
+ const namespace = text(entity.predicate_namespace) ?? "default";
41
+ const result = await prolog.query(`findall([Id,'fact',Props], (kb:predicate_schema(Id, Namespace, Name, Arity, _Names, _Types), Name == ${toPrologAtom(name)}, Namespace == ${toPrologAtom(namespace)}, Arity =:= ${args.length}, kb_entity(Id, 'fact', Props)), Results)`);
42
+ if (!result.success) {
43
+ throw new Error(`Predicate schema lookup failed: ${result.error ?? "unknown error"}`);
44
+ }
45
+ const schemas = result.bindings.Results
46
+ ? parseListOfLists(result.bindings.Results).map(parseEntityFromList)
47
+ : [];
48
+ const problems = [];
49
+ for (const schema of schemas) {
50
+ const argumentNames = Array.isArray(schema.argument_names)
51
+ ? schema.argument_names.map(String)
52
+ : [];
53
+ const conformance = predicateArgumentConformance(argumentNames, args, predicateVocabularyFromEntity(schema));
54
+ for (const rewrite of conformance.rewrites) {
55
+ problems.push(`argument ${rewrite.argumentName} uses alias ${rewrite.from}; use the declared constant ${rewrite.to} (${String(schema.id)})`);
56
+ }
57
+ for (const value of conformance.undeclared) {
58
+ problems.push(`argument ${value.argumentName} value ${value.value} is not declared by ${String(schema.id)}; allowed: ${value.allowed.join(", ")}`);
59
+ }
60
+ }
61
+ if (problems.length > 0) {
62
+ throw new Error(`Entity validation failed: ${problems.join("; ")}. Use a declared constant, or extend the schema's argument_constants when the value is genuinely new.`);
63
+ }
64
+ }
@@ -1,6 +1,6 @@
1
1
  import type { PrologPort } from "../../public/operations/runtime-types.js";
2
2
  import type { RelationshipInput } from "./types.js";
3
- export declare const RELATIONSHIP_TYPES: readonly ["depends_on", "specified_by", "verified_by", "validates", "implements", "covered_by", "executable_for", "constrained_by", "constrains", "requires_property", "requires_predicate", "requires_rule", "guards", "publishes", "consumes", "supersedes", "relates_to"];
3
+ export declare const RELATIONSHIP_TYPES: readonly ["depends_on", "specified_by", "verified_by", "validates", "implements", "covered_by", "executable_for", "constrained_by", "constrains", "requires_property", "requires_predicate", "requires_rule", "guards", "publishes", "consumes", "supersedes", "restates", "relates_to"];
4
4
  export type RelationshipType = (typeof RELATIONSHIP_TYPES)[number];
5
5
  export declare function wrongKindRelationshipError(type: string, target: string, wrongKind: string): Error;
6
6
  export declare function dependentRelationshipsGoal(entityId: string): string;