kibi-cli 2.22.1 → 2.24.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 (159) hide show
  1. package/dist/cli-operation-metadata.d.ts +1 -1
  2. package/dist/cli-operation-metadata.js +1 -1
  3. package/dist/cli-register-foundation.d.ts.map +1 -1
  4. package/dist/cli-register-foundation.js +1 -0
  5. package/dist/commands/check.d.ts.map +1 -1
  6. package/dist/commands/check.js +51 -3
  7. package/dist/commands/doctor.d.ts.map +1 -1
  8. package/dist/commands/doctor.js +28 -0
  9. package/dist/commands/migrate.d.ts +10 -0
  10. package/dist/commands/migrate.d.ts.map +1 -1
  11. package/dist/commands/migrate.js +81 -9
  12. package/dist/commands/sync.d.ts.map +1 -1
  13. package/dist/commands/sync.js +15 -1
  14. package/dist/decision-firmness.d.ts +16 -4
  15. package/dist/decision-firmness.d.ts.map +1 -1
  16. package/dist/decision-firmness.js +29 -6
  17. package/dist/extractors/markdown.d.ts.map +1 -1
  18. package/dist/extractors/markdown.js +39 -36
  19. package/dist/intent-search.d.ts +2 -0
  20. package/dist/intent-search.d.ts.map +1 -1
  21. package/dist/intent-search.js +43 -4
  22. package/dist/operations/bootstrap/activation.d.ts +7 -1
  23. package/dist/operations/bootstrap/activation.d.ts.map +1 -1
  24. package/dist/operations/bootstrap/activation.js +27 -2
  25. package/dist/operations/bootstrap/candidate-helpers.d.ts +6 -0
  26. package/dist/operations/bootstrap/candidate-helpers.d.ts.map +1 -1
  27. package/dist/operations/bootstrap/candidate-helpers.js +19 -0
  28. package/dist/operations/bootstrap/generate.d.ts.map +1 -1
  29. package/dist/operations/bootstrap/generate.js +56 -16
  30. package/dist/operations/bootstrap/guidance-actions.d.ts.map +1 -1
  31. package/dist/operations/bootstrap/guidance-actions.js +14 -2
  32. package/dist/operations/bootstrap/guidance.d.ts.map +1 -1
  33. package/dist/operations/bootstrap/guidance.js +6 -2
  34. package/dist/operations/bootstrap/intent-claims.d.ts.map +1 -1
  35. package/dist/operations/bootstrap/intent-claims.js +2 -2
  36. package/dist/operations/bootstrap/markdown-candidates.d.ts.map +1 -1
  37. package/dist/operations/bootstrap/markdown-candidates.js +2 -2
  38. package/dist/operations/bootstrap/presentation.d.ts.map +1 -1
  39. package/dist/operations/bootstrap/presentation.js +75 -9
  40. package/dist/operations/bootstrap/types.d.ts +6 -0
  41. package/dist/operations/bootstrap/types.d.ts.map +1 -1
  42. package/dist/operations/migration/firm-supersede.d.ts +19 -0
  43. package/dist/operations/migration/firm-supersede.d.ts.map +1 -0
  44. package/dist/operations/migration/firm-supersede.js +60 -0
  45. package/dist/operations/migration/kb-sources.d.ts +8 -5
  46. package/dist/operations/migration/kb-sources.d.ts.map +1 -1
  47. package/dist/operations/migration/kb-sources.js +50 -14
  48. package/dist/operations/migration/schema6.d.ts.map +1 -1
  49. package/dist/operations/migration/schema6.js +52 -2
  50. package/dist/operations/migration/source-paths.d.ts +18 -1
  51. package/dist/operations/migration/source-paths.d.ts.map +1 -1
  52. package/dist/operations/migration/source-paths.js +111 -16
  53. package/dist/operations/modeling/suggest-predicates.d.ts.map +1 -1
  54. package/dist/operations/modeling/suggest-predicates.js +22 -7
  55. package/dist/operations/mutation/requirement-semantics.d.ts +19 -1
  56. package/dist/operations/mutation/requirement-semantics.d.ts.map +1 -1
  57. package/dist/operations/mutation/requirement-semantics.js +63 -2
  58. package/dist/operations/mutation/strict-fact.js +1 -1
  59. package/dist/operations/mutation/upsert.d.ts.map +1 -1
  60. package/dist/operations/mutation/upsert.js +2 -1
  61. package/dist/operations/planning/apply-plan.d.ts.map +1 -1
  62. package/dist/operations/planning/apply-plan.js +22 -2
  63. package/dist/operations/semantic-advisor/analyze-prose.d.ts.map +1 -1
  64. package/dist/operations/semantic-advisor/analyze-prose.js +49 -17
  65. package/dist/public/impact/analyzer.d.ts.map +1 -1
  66. package/dist/public/impact/analyzer.js +7 -1
  67. package/dist/public/impact/diagnostics.d.ts.map +1 -1
  68. package/dist/public/impact/diagnostics.js +16 -3
  69. package/dist/public/impact/types.d.ts +2 -0
  70. package/dist/public/impact/types.d.ts.map +1 -1
  71. package/dist/public/operations/catalog.d.ts +8 -3
  72. package/dist/public/operations/catalog.d.ts.map +1 -1
  73. package/dist/public/operations/check-executor.d.ts.map +1 -1
  74. package/dist/public/operations/check-executor.js +10 -3
  75. package/dist/public/operations/check-helpers.d.ts +10 -0
  76. package/dist/public/operations/check-helpers.d.ts.map +1 -1
  77. package/dist/public/operations/check-helpers.js +54 -0
  78. package/dist/public/operations/contracts.d.ts.map +1 -1
  79. package/dist/public/operations/contracts.js +1 -0
  80. package/dist/public/operations/decision-firmness.d.ts +14 -0
  81. package/dist/public/operations/decision-firmness.d.ts.map +1 -1
  82. package/dist/public/operations/decision-firmness.js +60 -7
  83. package/dist/public/operations/discovery-executors.d.ts +18 -0
  84. package/dist/public/operations/discovery-executors.d.ts.map +1 -1
  85. package/dist/public/operations/discovery-executors.js +80 -8
  86. package/dist/public/operations/schema6-check-actions.d.ts +4 -0
  87. package/dist/public/operations/schema6-check-actions.d.ts.map +1 -1
  88. package/dist/public/operations/schema6-check-actions.js +39 -2
  89. package/dist/public/operations/source-path-dangling.d.ts +1 -1
  90. package/dist/public/operations/source-path-dangling.d.ts.map +1 -1
  91. package/dist/public/operations/source-path-dangling.js +11 -2
  92. package/dist/public/operations/specs/composite.d.ts +1 -1
  93. package/dist/public/operations/specs/discovery.d.ts +5 -0
  94. package/dist/public/operations/specs/discovery.d.ts.map +1 -1
  95. package/dist/public/operations/specs/discovery.js +5 -0
  96. package/dist/public/operations/specs/planning.d.ts +1 -1
  97. package/dist/public/operations/specs/planning.js +1 -1
  98. package/dist/public/operations/specs/reporting.d.ts.map +1 -1
  99. package/dist/public/operations/specs/reporting.js +6 -4
  100. package/dist/public/operations/specs/semantic.d.ts +1 -1
  101. package/dist/public/operations/specs/semantic.d.ts.map +1 -1
  102. package/dist/public/operations/specs/semantic.js +4 -2
  103. package/dist/public/operations/specs/upsert.d.ts +1 -1
  104. package/dist/public/operations/specs/upsert.js +1 -1
  105. package/dist/public/schemas/entity.js +1 -1
  106. package/dist/public/skills/kibi-bootstrap/SKILL.md +5 -4
  107. package/dist/public/skills/kibi-usage/SKILL.md +36 -16
  108. package/dist/public/skills/kibi-usage/resources/workflows.md +3 -1
  109. package/dist/schemas/entity.schema.json +1 -1
  110. package/dist/search-answer.d.ts +14 -0
  111. package/dist/search-answer.d.ts.map +1 -1
  112. package/dist/search-answer.js +51 -5
  113. package/dist/traceability/git-change-snapshot.d.ts +46 -0
  114. package/dist/traceability/git-change-snapshot.d.ts.map +1 -1
  115. package/dist/traceability/git-change-snapshot.js +187 -3
  116. package/dist/traceability/staged-diagnostics.d.ts.map +1 -1
  117. package/dist/traceability/staged-diagnostics.js +5 -6
  118. package/dist/traceability/staged-inventory-drift.d.ts +24 -0
  119. package/dist/traceability/staged-inventory-drift.d.ts.map +1 -0
  120. package/dist/traceability/staged-inventory-drift.js +84 -0
  121. package/dist/traceability/staged-symbols-manifest.d.ts.map +1 -1
  122. package/dist/traceability/staged-symbols-manifest.js +1 -2
  123. package/dist/types/entities.d.ts +2 -2
  124. package/dist/types/entities.d.ts.map +1 -1
  125. package/dist/utils/content-freshness.d.ts +9 -0
  126. package/dist/utils/content-freshness.d.ts.map +1 -1
  127. package/dist/utils/content-freshness.js +33 -0
  128. package/dist/utils/kb-manifest.d.ts +7 -0
  129. package/dist/utils/kb-manifest.d.ts.map +1 -1
  130. package/dist/utils/kb-manifest.js +6 -0
  131. package/dist/utils/merge-driver-setup.d.ts +10 -0
  132. package/dist/utils/merge-driver-setup.d.ts.map +1 -0
  133. package/dist/utils/merge-driver-setup.js +69 -0
  134. package/dist/utils/rule-registry.generated.d.ts +1 -1
  135. package/dist/utils/rule-registry.generated.js +1 -1
  136. package/dist/utils/schema-version.d.ts +1 -1
  137. package/dist/utils/schema-version.d.ts.map +1 -1
  138. package/dist/utils/schema-version.js +3 -1
  139. package/package.json +2 -2
  140. package/src/public/impact/analyzer.ts +13 -1
  141. package/src/public/impact/diagnostics.ts +20 -3
  142. package/src/public/impact/types.ts +2 -0
  143. package/src/public/operations/check-executor.ts +16 -4
  144. package/src/public/operations/check-helpers.ts +65 -0
  145. package/src/public/operations/contracts.ts +1 -0
  146. package/src/public/operations/decision-firmness.ts +83 -10
  147. package/src/public/operations/discovery-executors.ts +124 -7
  148. package/src/public/operations/schema6-check-actions.ts +43 -2
  149. package/src/public/operations/source-path-dangling.ts +12 -2
  150. package/src/public/operations/specs/discovery.ts +6 -0
  151. package/src/public/operations/specs/planning.ts +1 -1
  152. package/src/public/operations/specs/reporting.ts +6 -4
  153. package/src/public/operations/specs/semantic.ts +4 -2
  154. package/src/public/operations/specs/upsert.ts +1 -1
  155. package/src/public/schemas/entity.ts +1 -1
  156. package/src/public/skills/kibi-bootstrap/SKILL.md +5 -4
  157. package/src/public/skills/kibi-usage/SKILL.md +36 -16
  158. package/src/public/skills/kibi-usage/resources/workflows.md +3 -1
  159. package/src/schemas/entity.schema.json +1 -1
@@ -23,6 +23,21 @@ const URL_PATTERN = /^https?:\/\/\S+$/i;
23
23
  const LEGACY_ROOT = "documentation/";
24
24
  const SYMBOL_ID_LINE = /^[ \t]*-?[ \t]*id[ \t]*:[ \t]*(.+?)[ \t]*$/gm;
25
25
  const SOURCE_EDIT_REFUSED = "the source field spans several lines, or editing it would change other frontmatter fields";
26
+ const TEXT_REF_EDIT_REFUSED = "the text_ref field spans several lines, or editing it would change other frontmatter fields";
27
+ const TEXT_REF_UNMAPPED = "no file under .kb/ matches it, so Kibi cannot tell what evidence it meant";
28
+ const TEXT_REF_SEMANTIC_SOURCE = "text_ref is this requirement's semantic source, so changing it changes its semantic inventory hash; re-derive the inventory after editing it";
29
+ /** One workspace file path: a directory, a file name with an extension, no spaces. */
30
+ const FILE_PATH_SHAPE = /^(?:\.\/)?[^\s;,:]+\/[^\s;,:/]+\.[A-Za-z0-9]+(?:#\S*)?$/;
31
+ function editRefusedReason(field) {
32
+ return field === "text_ref" ? TEXT_REF_EDIT_REFUSED : SOURCE_EDIT_REFUSED;
33
+ }
34
+ /** Whether a `text_ref` value reads as one workspace file path. */
35
+ // implements REQ-core-validation-rules, REQ-kibi-kb-lifecycle-integrity
36
+ export function isPathShapedTextRef(value) {
37
+ return (typeof value === "string" &&
38
+ !URL_PATTERN.test(value.trim()) &&
39
+ FILE_PATH_SHAPE.test(value.trim()));
40
+ }
26
41
  function scalarText(value) {
27
42
  if (typeof value === "string")
28
43
  return value.trim();
@@ -58,11 +73,33 @@ export function scanAuthoredSources(workspaceRoot) {
58
73
  path.basename(file.relativePath, ".md");
59
74
  entityIds.add(id);
60
75
  const value = readTopLevelField(slice.text, "source");
61
- if (value === undefined || value === null)
62
- continue;
63
- if (typeof value === "string" && value.trim() === "")
64
- continue;
65
- refs.push({ entityId: id, file: file.relativePath, value });
76
+ if (value !== undefined &&
77
+ value !== null &&
78
+ !(typeof value === "string" && value.trim() === "")) {
79
+ refs.push({
80
+ entityId: id,
81
+ field: "source",
82
+ file: file.relativePath,
83
+ value,
84
+ });
85
+ }
86
+ const textRef = readTopLevelField(slice.text, "text_ref");
87
+ if (isPathShapedTextRef(textRef)) {
88
+ const declared = readTopLevelField(slice.text, "semantic_source_field");
89
+ const requirement = readTopLevelField(slice.text, "type") === "req" ||
90
+ file.relativePath.startsWith(`${KB_PATHS.lanes.requirements}/`);
91
+ const semanticSource = declared === "text_ref" ||
92
+ (requirement &&
93
+ declared === undefined &&
94
+ readTopLevelField(slice.text, "semantic_text") === undefined);
95
+ refs.push({
96
+ entityId: id,
97
+ field: "text_ref",
98
+ ...(semanticSource ? { semanticSource } : {}),
99
+ file: file.relativePath,
100
+ value: textRef,
101
+ });
102
+ }
66
103
  }
67
104
  const symbols = readText(path.join(workspaceRoot, KB_PATHS.symbolsManifest));
68
105
  if (symbols !== null) {
@@ -148,6 +185,34 @@ export function workspacePathExists(workspaceRoot) {
148
185
  };
149
186
  }
150
187
  function repairFor(ref, resolution) {
188
+ if (ref.field === "text_ref") {
189
+ // A text_ref naming its own existing file is evidence, not redundancy.
190
+ if (resolution.kind === "self" && resolution.resolves)
191
+ return undefined;
192
+ if (resolution.kind !== "dangling" && resolution.kind !== "self") {
193
+ return undefined;
194
+ }
195
+ // A pre-canonical spelling of the entity's own file moved with it.
196
+ const rewrite = resolution.kind === "dangling" ? resolution.rewrite : ref.file;
197
+ if (ref.semanticSource === true) {
198
+ return {
199
+ ...ref,
200
+ dangling: true,
201
+ fix: rewrite !== undefined
202
+ ? { kind: "rewrite", to: rewrite }
203
+ : { kind: "remove", reason: "dangling" },
204
+ refused: TEXT_REF_SEMANTIC_SOURCE,
205
+ };
206
+ }
207
+ return rewrite !== undefined
208
+ ? { ...ref, dangling: true, fix: { kind: "rewrite", to: rewrite } }
209
+ : {
210
+ ...ref,
211
+ dangling: true,
212
+ fix: { kind: "remove", reason: "dangling" },
213
+ refused: TEXT_REF_UNMAPPED,
214
+ };
215
+ }
151
216
  if (resolution.kind === "self") {
152
217
  return {
153
218
  ...ref,
@@ -165,10 +230,10 @@ function repairFor(ref, resolution) {
165
230
  : { kind: "remove", reason: "dangling" },
166
231
  };
167
232
  }
168
- function editedContent(content, fix) {
233
+ function editedContent(content, field, fix) {
169
234
  return fix.kind === "rewrite"
170
- ? withTopLevelField(content, "source", fix.to)
171
- : withoutTopLevelField(content, "source");
235
+ ? withTopLevelField(content, field, fix.to)
236
+ : withoutTopLevelField(content, field);
172
237
  }
173
238
  /** Every repairable source value, sorted by entity id. Read-only. */
174
239
  function classifySources(workspaceRoot) {
@@ -186,20 +251,24 @@ function classifySources(workspaceRoot) {
186
251
  repairs.push(repair);
187
252
  }
188
253
  return repairs.sort((left, right) => left.entityId.localeCompare(right.entityId) ||
189
- left.file.localeCompare(right.file));
254
+ left.file.localeCompare(right.file) ||
255
+ left.field.localeCompare(right.field));
190
256
  }
191
257
  /** Mark the repairs whose edit would not read back as exactly that change. */
192
258
  function withEditability(workspaceRoot, repairs) {
193
259
  return repairs.map((repair) => {
260
+ if (repair.refused !== undefined)
261
+ return repair;
194
262
  const content = readText(path.join(workspaceRoot, repair.file));
195
- if (content !== null && editedContent(content, repair.fix) !== null) {
263
+ if (content !== null &&
264
+ editedContent(content, repair.field, repair.fix) !== null) {
196
265
  return repair;
197
266
  }
198
267
  return {
199
268
  ...repair,
200
269
  refused: content === null
201
270
  ? "the entity file cannot be read"
202
- : SOURCE_EDIT_REFUSED,
271
+ : editRefusedReason(repair.field),
203
272
  };
204
273
  });
205
274
  }
@@ -225,6 +294,7 @@ function plannedRepair(repair) {
225
294
  const base = {
226
295
  entityId: repair.entityId,
227
296
  file: repair.file,
297
+ ...(repair.field === "text_ref" ? { field: "text_ref" } : {}),
228
298
  from: shownSourceValue(repair.value),
229
299
  };
230
300
  return repair.fix.kind === "rewrite"
@@ -250,8 +320,18 @@ export function plannedSourcePathRepairs(repairs) {
250
320
  }
251
321
  return { rewrites, removals };
252
322
  }
323
+ function plannedField(repair) {
324
+ return "field" in repair && repair.field === "text_ref"
325
+ ? "text_ref"
326
+ : "source";
327
+ }
328
+ function plannedKey(file, field) {
329
+ return `${field}\0${file}`;
330
+ }
253
331
  function samePlannedRepair(left, right) {
254
- if (left.entityId !== right.entityId || left.from !== right.from) {
332
+ if (left.entityId !== right.entityId ||
333
+ left.from !== right.from ||
334
+ plannedField(left) !== plannedField(right)) {
255
335
  return false;
256
336
  }
257
337
  if ("to" in left)
@@ -271,16 +351,26 @@ export function applySourcePathRepairs(workspaceRoot, planned) {
271
351
  const wanted = planned === undefined
272
352
  ? undefined
273
353
  : new Map([...planned.rewrites, ...planned.removals].map((repair) => [
274
- repair.file,
354
+ plannedKey(repair.file, plannedField(repair)),
275
355
  repair,
276
356
  ]));
277
357
  const rewritten = [];
278
358
  const removed = [];
279
359
  const skipped = [];
280
360
  for (const repair of classifySources(workspaceRoot)) {
361
+ // A text_ref Kibi cannot map needs a person; never remove it.
362
+ if (repair.refused !== undefined) {
363
+ if (wanted === undefined) {
364
+ skipped.push({
365
+ file: repair.file,
366
+ reason: `text_ref '${shownSourceValue(repair.value)}': ${repair.refused}`,
367
+ });
368
+ }
369
+ continue;
370
+ }
281
371
  const current = plannedRepair(repair);
282
372
  if (wanted !== undefined) {
283
- const plan = wanted.get(repair.file);
373
+ const plan = wanted.get(plannedKey(repair.file, repair.field));
284
374
  if (plan === undefined)
285
375
  continue;
286
376
  if (!samePlannedRepair(plan, current)) {
@@ -293,9 +383,14 @@ export function applySourcePathRepairs(workspaceRoot, planned) {
293
383
  }
294
384
  const absolute = path.join(workspaceRoot, repair.file);
295
385
  const content = readText(absolute);
296
- const next = content === null ? null : editedContent(content, repair.fix);
386
+ const next = content === null
387
+ ? null
388
+ : editedContent(content, repair.field, repair.fix);
297
389
  if (next === null) {
298
- skipped.push({ file: repair.file, reason: SOURCE_EDIT_REFUSED });
390
+ skipped.push({
391
+ file: repair.file,
392
+ reason: editRefusedReason(repair.field),
393
+ });
299
394
  continue;
300
395
  }
301
396
  writeFileAtomically(absolute, next);
@@ -1 +1 @@
1
- {"version":3,"file":"suggest-predicates.d.ts","sourceRoot":"","sources":["../../../src/operations/modeling/suggest-predicates.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EACV,gBAAgB,EAChB,UAAU,EACX,MAAM,0CAA0C,CAAC;AA2BlD,OAAO,KAAK,EAIV,mBAAmB,EACnB,qBAAqB,EACrB,uBAAuB,EACxB,MAAM,sBAAsB,CAAC;AAG9B,YAAY,EACV,iBAAiB,EACjB,wBAAwB,EACxB,mBAAmB,EACnB,0BAA0B,EAC1B,qBAAqB,EACrB,uBAAuB,GACxB,MAAM,sBAAsB,CAAC;AA+H9B,wBAAgB,sCAAsC,CACpD,IAAI,EAAE,IAAI,CAAC,mBAAmB,EAAE,qBAAqB,GAAG,gBAAgB,CAAC,EACzE,KAAK,EAAE,IAAI,CAAC,mBAAmB,EAAE,qBAAqB,GAAG,gBAAgB,CAAC,GACzE,MAAM,CAIR;AAED,wBAAgB,2CAA2C,CACzD,IAAI,EAAE,IAAI,CACR,mBAAmB,EACnB,qBAAqB,GAAG,OAAO,GAAG,gBAAgB,CACnD,EACD,KAAK,EAAE,IAAI,CACT,mBAAmB,EACnB,qBAAqB,GAAG,OAAO,GAAG,gBAAgB,CACnD,GACA,MAAM,CAKR;AA+ID,wBAAsB,yBAAyB,CAC7C,MAAM,EAAE,UAAU,GAAG,IAAI,EACzB,IAAI,EAAE,qBAAqB,EAC3B,OAAO,CAAC,EAAE,gBAAgB,GACzB,OAAO,CAAC,uBAAuB,CAAC,CAyYlC;AA+WD,wBAAsB,wBAAwB,CAC5C,IAAI,EAAE,qBAAqB,EAC3B,OAAO,EAAE,gBAAgB,GACxB,OAAO,CAAC,uBAAuB,CAAC,CAElC"}
1
+ {"version":3,"file":"suggest-predicates.d.ts","sourceRoot":"","sources":["../../../src/operations/modeling/suggest-predicates.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EACV,gBAAgB,EAChB,UAAU,EACX,MAAM,0CAA0C,CAAC;AA2BlD,OAAO,KAAK,EAIV,mBAAmB,EACnB,qBAAqB,EACrB,uBAAuB,EACxB,MAAM,sBAAsB,CAAC;AAG9B,YAAY,EACV,iBAAiB,EACjB,wBAAwB,EACxB,mBAAmB,EACnB,0BAA0B,EAC1B,qBAAqB,EACrB,uBAAuB,GACxB,MAAM,sBAAsB,CAAC;AA+H9B,wBAAgB,sCAAsC,CACpD,IAAI,EAAE,IAAI,CAAC,mBAAmB,EAAE,qBAAqB,GAAG,gBAAgB,CAAC,EACzE,KAAK,EAAE,IAAI,CAAC,mBAAmB,EAAE,qBAAqB,GAAG,gBAAgB,CAAC,GACzE,MAAM,CAIR;AAED,wBAAgB,2CAA2C,CACzD,IAAI,EAAE,IAAI,CACR,mBAAmB,EACnB,qBAAqB,GAAG,OAAO,GAAG,gBAAgB,CACnD,EACD,KAAK,EAAE,IAAI,CACT,mBAAmB,EACnB,qBAAqB,GAAG,OAAO,GAAG,gBAAgB,CACnD,GACA,MAAM,CAKR;AA+ID,wBAAsB,yBAAyB,CAC7C,MAAM,EAAE,UAAU,GAAG,IAAI,EACzB,IAAI,EAAE,qBAAqB,EAC3B,OAAO,CAAC,EAAE,gBAAgB,GACzB,OAAO,CAAC,uBAAuB,CAAC,CA8ZlC;AA+WD,wBAAsB,wBAAwB,CAC5C,IAAI,EAAE,qBAAqB,EAC3B,OAAO,EAAE,gBAAgB,GACxB,OAAO,CAAC,uBAAuB,CAAC,CAElC"}
@@ -409,8 +409,19 @@ export async function handleKbSuggestPredicates(prolog, args, context) {
409
409
  ? buildGapApplyPlan(text, args)
410
410
  : [];
411
411
  const plannedFactId = typeof predicatePlan[0]?.id === "string" ? predicatePlan[0].id : null;
412
+ // The manifest the relationship plan calls "merged" starts from what the
413
+ // stored requirement already declares, so an agent that passes no
414
+ // existingLogicClaims is not handed a manifest that drops its other
415
+ // clauses.
416
+ const existingLogicClaims = Array.from(new Set([
417
+ ...(storedRequirement !== null &&
418
+ Array.isArray(storedRequirement.logic_claims)
419
+ ? storedRequirement.logic_claims.filter((claim) => typeof claim === "string")
420
+ : []),
421
+ ...(args.existingLogicClaims ?? []),
422
+ ]));
412
423
  const relationshipPlan = completeCandidate && !alreadyGrounded && !ledgerMissing
413
- ? buildRelationshipPlan(plannedFactId ?? "", args.requirementId, text, args.existingLogicClaims)
424
+ ? buildRelationshipPlan(plannedFactId ?? "", args.requirementId, text, existingLogicClaims)
414
425
  : null;
415
426
  const replacementPlan = alreadyGrounded &&
416
427
  !keepExistingGrounding &&
@@ -471,7 +482,7 @@ export async function handleKbSuggestPredicates(prolog, args, context) {
471
482
  : alreadyGrounded
472
483
  ? "No predicate candidate passed the semantic applicability gate; keep the existing grounding and record the ontology-gap observation in applyPlan so the missing schema stays visible."
473
484
  : "No predicate candidate passed the semantic applicability gate; record an ontology gap and review the generated schema draft instead of silently writing prose.");
474
- const logicClaims = Array.from(new Set([...(args.existingLogicClaims ?? []), claimKey]));
485
+ const logicClaims = Array.from(new Set([...existingLogicClaims, claimKey]));
475
486
  const plan = relationshipPlan !== null && plannedFactId !== null && args.requirementId
476
487
  ? await groundingPlan(prolog, context, {
477
488
  requirementId: args.requirementId,
@@ -486,17 +497,21 @@ export async function handleKbSuggestPredicates(prolog, args, context) {
486
497
  : null;
487
498
  // Without a plan nothing ran the contradiction analysis, so the decision
488
499
  // conflicts a complete candidate would introduce are looked up directly.
489
- const decisionConflicts = plan === null &&
490
- completeCandidate !== undefined &&
491
- !keepExistingGrounding &&
492
- args.requirementId
500
+ // A claim analyzed without a requirement asks the same question ("what
501
+ // would this decision conflict with?"), so it gets the same lookup.
502
+ const decisionConflicts = plan === null && completeCandidate !== undefined && !keepExistingGrounding
493
503
  ? await candidateDecisionConflicts(prolog, completeCandidate, warnings)
494
504
  : [];
495
505
  for (const conflict of decisionConflicts) {
496
506
  warnings.push(`The proposed ${conflict.canonicalKey} (${conflict.proposedEffect}) contradicts ${conflict.factId} (${conflict.effect}) under ${conflict.requirementId}${conflict.requirementId === args.requirementId ? ", the requirement this claim would ground" : ""}: both decide ${completeCandidate?.predicate_name} on the same key arguments. kb_upsert refuses a grounding link that introduces this conflict; supersede or close the conflicting requirement, or correct the decision, before applying.`);
497
507
  }
508
+ const conflictSummary = decisionConflicts.length > 0
509
+ ? ` It would contradict ${decisionConflicts
510
+ .map((conflict) => `${conflict.factId} (${conflict.effect}) under ${conflict.requirementId}`)
511
+ .join(", ")}: see decisionConflicts before applying anything.`
512
+ : "";
498
513
  return {
499
- content: [{ type: "text", text: textSummary }],
514
+ content: [{ type: "text", text: textSummary + conflictSummary }],
500
515
  structuredContent: {
501
516
  text,
502
517
  claimKey,
@@ -1,4 +1,5 @@
1
1
  import type { PrologPort } from "../../public/operations/runtime-types.js";
2
+ import { type SemanticRelationship } from "../semantic-advisor/ingestion-boundary.js";
2
3
  import { type StoredEntityLookup } from "./stored-entity.js";
3
4
  import type { StagedUpsertState, UpsertInput } from "./types.js";
4
5
  /**
@@ -7,10 +8,27 @@ import type { StagedUpsertState, UpsertInput } from "./types.js";
7
8
  * relationship (for example `specified_by`) and carries no `semantic_*` or
8
9
  * `logic_claims` field, and no `title` or `text_ref` that differs from the
9
10
  * stored value, is merged over the stored semantic contract (and the stored
10
- * `text_ref` it omits) before validation. The
11
+ * `text_ref` it omits) before validation. When the stored ledger reads its
12
+ * prose from `semantic_text`, a changed `text_ref` is merged the same way,
13
+ * since it is not the prose. The
11
14
  * proposition-complete check then runs against the merged requirement, which
12
15
  * is also what the write records. A payload that supplies any ledger field or
13
16
  * changes the prose is validated exactly as given.
14
17
  */
15
18
  export declare function withStoredRequirementSemantics(input: UpsertInput, prolog: Pick<PrologPort, "query">, staged?: StagedUpsertState, lookup?: StoredEntityLookup): Promise<UpsertInput>;
19
+ /**
20
+ * An update to a stored requirement is judged as its authored file will read
21
+ * afterwards: the stored status, prose and proposition ledger with the
22
+ * payload's fields on top (the source writer keeps every front matter key the
23
+ * payload leaves out and states the stored `semantic_text`). A payload such
24
+ * as `{status: "closed"}` passes the check on its own fields, yet the file it
25
+ * leaves is a current requirement whose prose has no inventory, which sync
26
+ * then refuses. Such a write is refused here with the error sync would give.
27
+ *
28
+ * A requirement that was already current with the same prose and no complete
29
+ * inventory is legacy content sync keeps accepting, so a write that changes
30
+ * neither keeps working. A write that makes the requirement current, or
31
+ * changes the prose sync checks, must carry the complete inventory.
32
+ */
33
+ export declare function assertStoredRequirementBoundary(input: UpsertInput, relationships: readonly SemanticRelationship[], prolog: Pick<PrologPort, "query">, staged?: StagedUpsertState, lookup?: StoredEntityLookup): Promise<void>;
16
34
  //# sourceMappingURL=requirement-semantics.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"requirement-semantics.d.ts","sourceRoot":"","sources":["../../../src/operations/mutation/requirement-semantics.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,0CAA0C,CAAC;AAC3E,OAAO,EACL,KAAK,kBAAkB,EAExB,MAAM,oBAAoB,CAAC;AAC5B,OAAO,KAAK,EAAE,iBAAiB,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAwCjE;;;;;;;;;;GAUG;AACH,wBAAsB,8BAA8B,CAClD,KAAK,EAAE,WAAW,EAClB,MAAM,EAAE,IAAI,CAAC,UAAU,EAAE,OAAO,CAAC,EACjC,MAAM,CAAC,EAAE,iBAAiB,EAC1B,MAAM,GAAE,kBAAqE,GAC5E,OAAO,CAAC,WAAW,CAAC,CA6BtB"}
1
+ {"version":3,"file":"requirement-semantics.d.ts","sourceRoot":"","sources":["../../../src/operations/mutation/requirement-semantics.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,0CAA0C,CAAC;AAE3E,OAAO,EAEL,KAAK,oBAAoB,EAE1B,MAAM,2CAA2C,CAAC;AAEnD,OAAO,EACL,KAAK,kBAAkB,EAExB,MAAM,oBAAoB,CAAC;AAC5B,OAAO,KAAK,EAAE,iBAAiB,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAwCjE;;;;;;;;;;;;GAYG;AACH,wBAAsB,8BAA8B,CAClD,KAAK,EAAE,WAAW,EAClB,MAAM,EAAE,IAAI,CAAC,UAAU,EAAE,OAAO,CAAC,EACjC,MAAM,CAAC,EAAE,iBAAiB,EAC1B,MAAM,GAAE,kBAAqE,GAC5E,OAAO,CAAC,WAAW,CAAC,CAsCtB;AAuBD;;;;;;;;;;;;;GAaG;AACH,wBAAsB,+BAA+B,CACnD,KAAK,EAAE,WAAW,EAClB,aAAa,EAAE,SAAS,oBAAoB,EAAE,EAC9C,MAAM,EAAE,IAAI,CAAC,UAAU,EAAE,OAAO,CAAC,EACjC,MAAM,CAAC,EAAE,iBAAiB,EAC1B,MAAM,GAAE,kBAAqE,GAC5E,OAAO,CAAC,IAAI,CAAC,CAmBf"}
@@ -1,3 +1,6 @@
1
+ import { analyzeSemanticAdvisorInput } from "../semantic-advisor/analyze-prose.js";
2
+ import { validateSemanticInventoryBoundary, } from "../semantic-advisor/ingestion-boundary.js";
3
+ import { stringValue } from "../semantic-advisor/shared.js";
1
4
  import { storedEntityLookup, } from "./stored-entity.js";
2
5
  /** The fields that make up a requirement's proposition ledger. */
3
6
  const SEMANTIC_CONTRACT_FIELDS = [
@@ -30,7 +33,9 @@ async function storedRequirement(id, lookup, staged) {
30
33
  * relationship (for example `specified_by`) and carries no `semantic_*` or
31
34
  * `logic_claims` field, and no `title` or `text_ref` that differs from the
32
35
  * stored value, is merged over the stored semantic contract (and the stored
33
- * `text_ref` it omits) before validation. The
36
+ * `text_ref` it omits) before validation. When the stored ledger reads its
37
+ * prose from `semantic_text`, a changed `text_ref` is merged the same way,
38
+ * since it is not the prose. The
34
39
  * proposition-complete check then runs against the merged requirement, which
35
40
  * is also what the write records. A payload that supplies any ledger field or
36
41
  * changes the prose is validated exactly as given.
@@ -44,7 +49,15 @@ export async function withStoredRequirementSemantics(input, prolog, staged, look
44
49
  const stored = await storedRequirement(input.id, lookup, staged);
45
50
  if (stored === undefined)
46
51
  return input;
47
- if (PROSE_FIELDS.some((field) => properties[field] !== undefined && properties[field] !== stored[field]))
52
+ // text_ref is prose only while it is the semantic source. Once the stored
53
+ // ledger reads semantic_text, changing it (for example clearing a text_ref
54
+ // whose file is gone) changes no proposition. A changed title stays a
55
+ // prose change: it is what a reader takes the requirement to say.
56
+ const readsSemanticText = stored.semantic_source_field === "semantic_text" &&
57
+ stringValue(stored.semantic_text).length > 0;
58
+ if (PROSE_FIELDS.some((field) => !(field === "text_ref" && readsSemanticText) &&
59
+ properties[field] !== undefined &&
60
+ properties[field] !== stored[field]))
48
61
  return input;
49
62
  const preserved = {};
50
63
  for (const field of [
@@ -62,3 +75,51 @@ export async function withStoredRequirementSemantics(input, prolog, staged, look
62
75
  return input;
63
76
  return { ...input, properties: { ...preserved, ...properties } };
64
77
  }
78
+ /** Stored fields that decide how sync reads a requirement's propositions. */
79
+ const BOUNDARY_FIELDS = [
80
+ "status",
81
+ "title",
82
+ "text_ref",
83
+ ...SEMANTIC_CONTRACT_FIELDS,
84
+ ];
85
+ function boundaryOf(input, properties, relationships) {
86
+ const payload = { type: input.type, id: input.id, properties };
87
+ const { receipt } = analyzeSemanticAdvisorInput({
88
+ payload: { ...payload, relationships },
89
+ });
90
+ return validateSemanticInventoryBoundary(payload, relationships, receipt);
91
+ }
92
+ // implements REQ-kibi-upsert-preserves-requirement-semantics, REQ-kibi-proposition-complete-ingestion
93
+ /**
94
+ * An update to a stored requirement is judged as its authored file will read
95
+ * afterwards: the stored status, prose and proposition ledger with the
96
+ * payload's fields on top (the source writer keeps every front matter key the
97
+ * payload leaves out and states the stored `semantic_text`). A payload such
98
+ * as `{status: "closed"}` passes the check on its own fields, yet the file it
99
+ * leaves is a current requirement whose prose has no inventory, which sync
100
+ * then refuses. Such a write is refused here with the error sync would give.
101
+ *
102
+ * A requirement that was already current with the same prose and no complete
103
+ * inventory is legacy content sync keeps accepting, so a write that changes
104
+ * neither keeps working. A write that makes the requirement current, or
105
+ * changes the prose sync checks, must carry the complete inventory.
106
+ */
107
+ export async function assertStoredRequirementBoundary(input, relationships, prolog, staged, lookup = storedEntityLookup(prolog, input.id, input.type)) {
108
+ if (input.type !== "req")
109
+ return;
110
+ const stored = await storedRequirement(input.id, lookup, staged);
111
+ if (stored === undefined)
112
+ return;
113
+ const before = {};
114
+ for (const field of BOUNDARY_FIELDS) {
115
+ if (stored[field] !== undefined)
116
+ before[field] = stored[field];
117
+ }
118
+ const after = boundaryOf(input, { ...before, ...(input.properties ?? {}) }, relationships);
119
+ if (after.errors.length === 0)
120
+ return;
121
+ const previous = boundaryOf(input, before, relationships);
122
+ if (previous.applicable && previous.sourceHash === after.sourceHash)
123
+ return;
124
+ throw new Error(`Proposition-complete ingestion failed: ${input.id} would be written as a current requirement without a complete proposition inventory (${after.errors.join("; ")}). Run kb_model mode analyze (CLI: semantic-advisor) with id '${input.id}' and its complete semantic_text, then send the returned inventory contract (semantic_inventory, semantic_inventory_version, semantic_source_field, semantic_source_hash, logic_claims) in this kb_upsert, or retire the requirement through a superseding one instead.`);
125
+ }
@@ -90,7 +90,7 @@ export function factKindShapeHints(entity) {
90
90
  if (missing.length !== 0)
91
91
  return [
92
92
  `fact_kind 'predicate' requires ${missing.join(", ")}.`,
93
- "Next action: call kb_suggest_predicates before hand-writing ontology predicate facts.",
93
+ "Next action: call kb_model with mode predicates (CLI: kibi suggest-predicates) and apply its planned fact instead of hand-writing ontology predicate facts.",
94
94
  ];
95
95
  return [];
96
96
  }
@@ -1 +1 @@
1
- {"version":3,"file":"upsert.d.ts","sourceRoot":"","sources":["../../../src/operations/mutation/upsert.ts"],"names":[],"mappings":"AAeA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,0CAA0C,CAAC;AACjF,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,kCAAkC,CAAC;AAexE,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,8BAA8B,CAAC;AAyB3E,OAAO,EACL,KAAK,kBAAkB,EAExB,MAAM,oBAAoB,CAAC;AAQ5B,OAAO,KAAK,EACV,iBAAiB,EACjB,iBAAiB,EACjB,WAAW,EACX,aAAa,EACb,eAAe,EAChB,MAAM,YAAY,CAAC;AAsEpB,wBAAgB,gCAAgC,CAC9C,aAAa,EAAE,MAAM,EACrB,SAAS,EAAE,MAAM,GAChB,IAAI,CAkBN;AAwCD,wBAAsB,+BAA+B,CACnD,MAAM,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,EACzC,OAAO,EAAE,gBAAgB,EACzB,MAAM,CAAC,EAAE,iBAAiB,GACzB,OAAO,CAAC,IAAI,CAAC,CAqBf;AAED,wBAAsB,sBAAsB,CAC1C,KAAK,EAAE,WAAW,EAClB,MAAM,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,EACzC,aAAa,EAAE,SAAS,iBAAiB,EAAE,EAC3C,OAAO,EAAE,gBAAgB,EACzB,MAAM,CAAC,EAAE,iBAAiB,EAC1B,MAAM,CAAC,EAAE,kBAAkB,GAC1B,OAAO,CAAC,SAAS,iBAAiB,EAAE,CAAC,CA8BvC;AAmCD,MAAM,MAAM,uBAAuB,GAAG,QAAQ,CAAC;IAC7C,kEAAkE;IAClE,QAAQ,CAAC,kBAAkB,CAAC,EAAE,OAAO,CAAC;IACtC;;;OAGG;IACH,QAAQ,CAAC,MAAM,CAAC,EAAE,iBAAiB,CAAC;CACrC,CAAC,CAAC;AAGH,uFAAuF;AACvF,MAAM,MAAM,sBAAsB,GAAG,QAAQ,CAAC;IAC5C,QAAQ,CAAC,OAAO,EAAE,sBAAsB,GAAG,IAAI,CAAC;IAChD,QAAQ,CAAC,QAAQ,EAAE,SAAS,MAAM,EAAE,CAAC;CACtC,CAAC,CAAC;AAGH;;;;;;GAMG;AACH,wBAAgB,sBAAsB,CACpC,OAAO,EAAE,WAAW,GAAG;IACrB,QAAQ,CAAC,aAAa,EAAE,SAAS,iBAAiB,EAAE,CAAC;CACtD,GACA,sBAAsB,CASxB;AAGD,MAAM,MAAM,gBAAgB,GAAG,QAAQ,CAAC;IACtC,QAAQ,CAAC,SAAS,EAAE,eAAe,CAAC;IACpC,yEAAyE;IACzE,QAAQ,CAAC,aAAa,EAAE,SAAS,iBAAiB,EAAE,CAAC;IACrD,QAAQ,CAAC,QAAQ,EAAE,sBAAsB,CAAC;CAC3C,CAAC,CAAC;AAGH;;;;;;;;GAQG;AACH,wBAAsB,uBAAuB,CAC3C,OAAO,EAAE,WAAW,EACpB,OAAO,EAAE,gBAAgB,EACzB,OAAO,GAAE,uBAA4B,GACpC,OAAO,CAAC,gBAAgB,CAAC,CA0E3B;AAgDD,MAAM,MAAM,sBAAsB,GAAG,QAAQ,CAAC;IAC5C;;;;;;OAMG;IAEH,QAAQ,CAAC,sBAAsB,CAAC,EAAE,MAAM,CAAC;IACzC;;;;OAIG;IACH,QAAQ,CAAC,kBAAkB,CAAC,EAAE,OAAO,CAAC;IACtC;;;;OAIG;IACH,QAAQ,CAAC,mBAAmB,CAAC,EAAE,OAAO,CAAC;CACxC,CAAC,CAAC;AAyBH,wBAAsB,aAAa,CACjC,KAAK,EAAE,WAAW,EAClB,OAAO,EAAE,gBAAgB,EACzB,OAAO,GAAE,sBAA2B,GACnC,OAAO,CAAC,eAAe,CAAC,aAAa,CAAC,CAAC,CA2hBzC"}
1
+ {"version":3,"file":"upsert.d.ts","sourceRoot":"","sources":["../../../src/operations/mutation/upsert.ts"],"names":[],"mappings":"AAeA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,0CAA0C,CAAC;AACjF,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,kCAAkC,CAAC;AAexE,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,8BAA8B,CAAC;AA4B3E,OAAO,EACL,KAAK,kBAAkB,EAExB,MAAM,oBAAoB,CAAC;AAQ5B,OAAO,KAAK,EACV,iBAAiB,EACjB,iBAAiB,EACjB,WAAW,EACX,aAAa,EACb,eAAe,EAChB,MAAM,YAAY,CAAC;AAsEpB,wBAAgB,gCAAgC,CAC9C,aAAa,EAAE,MAAM,EACrB,SAAS,EAAE,MAAM,GAChB,IAAI,CAkBN;AAwCD,wBAAsB,+BAA+B,CACnD,MAAM,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,EACzC,OAAO,EAAE,gBAAgB,EACzB,MAAM,CAAC,EAAE,iBAAiB,GACzB,OAAO,CAAC,IAAI,CAAC,CAqBf;AAED,wBAAsB,sBAAsB,CAC1C,KAAK,EAAE,WAAW,EAClB,MAAM,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,EACzC,aAAa,EAAE,SAAS,iBAAiB,EAAE,EAC3C,OAAO,EAAE,gBAAgB,EACzB,MAAM,CAAC,EAAE,iBAAiB,EAC1B,MAAM,CAAC,EAAE,kBAAkB,GAC1B,OAAO,CAAC,SAAS,iBAAiB,EAAE,CAAC,CA8BvC;AAmCD,MAAM,MAAM,uBAAuB,GAAG,QAAQ,CAAC;IAC7C,kEAAkE;IAClE,QAAQ,CAAC,kBAAkB,CAAC,EAAE,OAAO,CAAC;IACtC;;;OAGG;IACH,QAAQ,CAAC,MAAM,CAAC,EAAE,iBAAiB,CAAC;CACrC,CAAC,CAAC;AAGH,uFAAuF;AACvF,MAAM,MAAM,sBAAsB,GAAG,QAAQ,CAAC;IAC5C,QAAQ,CAAC,OAAO,EAAE,sBAAsB,GAAG,IAAI,CAAC;IAChD,QAAQ,CAAC,QAAQ,EAAE,SAAS,MAAM,EAAE,CAAC;CACtC,CAAC,CAAC;AAGH;;;;;;GAMG;AACH,wBAAgB,sBAAsB,CACpC,OAAO,EAAE,WAAW,GAAG;IACrB,QAAQ,CAAC,aAAa,EAAE,SAAS,iBAAiB,EAAE,CAAC;CACtD,GACA,sBAAsB,CASxB;AAGD,MAAM,MAAM,gBAAgB,GAAG,QAAQ,CAAC;IACtC,QAAQ,CAAC,SAAS,EAAE,eAAe,CAAC;IACpC,yEAAyE;IACzE,QAAQ,CAAC,aAAa,EAAE,SAAS,iBAAiB,EAAE,CAAC;IACrD,QAAQ,CAAC,QAAQ,EAAE,sBAAsB,CAAC;CAC3C,CAAC,CAAC;AAGH;;;;;;;;GAQG;AACH,wBAAsB,uBAAuB,CAC3C,OAAO,EAAE,WAAW,EACpB,OAAO,EAAE,gBAAgB,EACzB,OAAO,GAAE,uBAA4B,GACpC,OAAO,CAAC,gBAAgB,CAAC,CAiF3B;AAgDD,MAAM,MAAM,sBAAsB,GAAG,QAAQ,CAAC;IAC5C;;;;;;OAMG;IAEH,QAAQ,CAAC,sBAAsB,CAAC,EAAE,MAAM,CAAC;IACzC;;;;OAIG;IACH,QAAQ,CAAC,kBAAkB,CAAC,EAAE,OAAO,CAAC;IACtC;;;;OAIG;IACH,QAAQ,CAAC,mBAAmB,CAAC,EAAE,OAAO,CAAC;CACxC,CAAC,CAAC;AAyBH,wBAAsB,aAAa,CACjC,KAAK,EAAE,WAAW,EAClB,OAAO,EAAE,gBAAgB,EACzB,OAAO,GAAE,sBAA2B,GACnC,OAAO,CAAC,eAAe,CAAC,aAAa,CAAC,CAAC,CA2hBzC"}
@@ -19,7 +19,7 @@ import { resolveUpsertOrigin } from "./origin.js";
19
19
  import { planRecoveryNotes, settlePendingPlanApplyJournals, withPlanRecoveryNotes, } from "./plan-apply-journal.js";
20
20
  import { assertPredicateArgumentVocabulary } from "./predicate-vocabulary-guard.js";
21
21
  import { existingRelationships, validateLiveRelationshipTargets, validateRelationshipSources, validateStrictLanePairing, validateSupersedesSourceHistory, } from "./relationships.js";
22
- import { withStoredRequirementSemantics } from "./requirement-semantics.js";
22
+ import { assertStoredRequirementBoundary, withStoredRequirementSemantics, } from "./requirement-semantics.js";
23
23
  import { MutationRollbackFailureError, MutationSaga } from "./saga.js";
24
24
  import { readFileTimes, restoreFileTimes, writePendingSourceReceipt, writeSourceForUpsert, } from "./source-authoring.js";
25
25
  import { storedEntityLookup, } from "./stored-entity.js";
@@ -244,6 +244,7 @@ export async function validateUpsertForCommit(payload, context, options = {}) {
244
244
  await validateLiveRelationshipTargets(prolog, validated.entity, validated.relationships, staged);
245
245
  await validateSupersedesSourceHistory(prolog, validated.entity, validated.relationships, context.workspaceRoot, undefined, staged);
246
246
  const semantic = analyzeUpsertSemantics({ ...input, relationships });
247
+ await assertStoredRequirementBoundary(input, relationships, prolog, staged, lookup);
247
248
  await assertLogicalGroundingClaimKeys(prolog, { ...input, relationships }, relationships, staged);
248
249
  await assertPredicateArgumentVocabulary(prolog, validated.entity, staged);
249
250
  // The origin the write records is added last: none of the checks above
@@ -1 +1 @@
1
- {"version":3,"file":"apply-plan.d.ts","sourceRoot":"","sources":["../../../src/operations/planning/apply-plan.ts"],"names":[],"mappings":"AA4BA,OAAO,KAAK,EAEV,gBAAgB,EAGjB,MAAM,0CAA0C,CAAC;AASlD,OAAO,EACL,KAAK,eAAe,EAIrB,MAAM,uBAAuB,CAAC;AAiE/B,OAAO,KAAK,EACV,aAAa,EACb,eAAe,EAGhB,MAAM,uBAAuB,CAAC;AAE/B,YAAY,EACV,aAAa,EACb,eAAe,EACf,kBAAkB,GACnB,MAAM,uBAAuB,CAAC;AAG/B,eAAO,MAAM,yBAAyB,EAAG,2BAAoC,CAAC;AA6G9E,wBAAgB,iCAAiC,CAC/C,IAAI,EAAE,MAAM,EACZ,QAAQ,EAAE,MAAM,EAChB,SAAS,EAAE,MAAM,GAChB,IAAI,CAMN;AAED,wBAAgB,mCAAmC,CACjD,SAAS,EAAE,SAAS;IAAE,EAAE,EAAE,MAAM,CAAC;IAAC,SAAS,CAAC,EAAE,SAAS,MAAM,EAAE,CAAA;CAAE,EAAE,EACnE,OAAO,EAAE,WAAW,CAAC,MAAM,CAAC,GAC3B,IAAI,CAaN;AAED,wBAAgB,qBAAqB,CACnC,OAAO,EAAE,SAAS,eAAe,EAAE,EACnC,SAAS,cAAoB,GAC5B,eAAe,EAAE,CA2CnB;AAi1BD;;;;;;GAMG;AAEH,wBAAsB,kBAAkB,CACtC,IAAI,EAAE,aAAa,EACnB,OAAO,EAAE,gBAAgB,GACxB,OAAO,CAAC,IAAI,CAAC,CAiCf;AAyqDD,wBAAsB,gBAAgB,CACpC,IAAI,EAAE,aAAa,EACnB,OAAO,EAAE,gBAAgB;AACzB;;;GAGG;AACH,WAAW,GAAE,MAAM,IAAsB,GACxC,OAAO,CAAC;IACT,OAAO,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IAC/C,iBAAiB,EAAE,eAAe,CAAC;CACpC,CAAC,CAmDD"}
1
+ {"version":3,"file":"apply-plan.d.ts","sourceRoot":"","sources":["../../../src/operations/planning/apply-plan.ts"],"names":[],"mappings":"AA4BA,OAAO,KAAK,EAEV,gBAAgB,EAGjB,MAAM,0CAA0C,CAAC;AASlD,OAAO,EACL,KAAK,eAAe,EAIrB,MAAM,uBAAuB,CAAC;AAiE/B,OAAO,KAAK,EACV,aAAa,EACb,eAAe,EAGhB,MAAM,uBAAuB,CAAC;AAE/B,YAAY,EACV,aAAa,EACb,eAAe,EACf,kBAAkB,GACnB,MAAM,uBAAuB,CAAC;AAG/B,eAAO,MAAM,yBAAyB,EAAG,2BAAoC,CAAC;AAqI9E,wBAAgB,iCAAiC,CAC/C,IAAI,EAAE,MAAM,EACZ,QAAQ,EAAE,MAAM,EAChB,SAAS,EAAE,MAAM,GAChB,IAAI,CAMN;AAED,wBAAgB,mCAAmC,CACjD,SAAS,EAAE,SAAS;IAAE,EAAE,EAAE,MAAM,CAAC;IAAC,SAAS,CAAC,EAAE,SAAS,MAAM,EAAE,CAAA;CAAE,EAAE,EACnE,OAAO,EAAE,WAAW,CAAC,MAAM,CAAC,GAC3B,IAAI,CAaN;AAED,wBAAgB,qBAAqB,CACnC,OAAO,EAAE,SAAS,eAAe,EAAE,EACnC,SAAS,cAAoB,GAC5B,eAAe,EAAE,CA2CnB;AAi1BD;;;;;;GAMG;AAEH,wBAAsB,kBAAkB,CACtC,IAAI,EAAE,aAAa,EACnB,OAAO,EAAE,gBAAgB,GACxB,OAAO,CAAC,IAAI,CAAC,CAiCf;AAyqDD,wBAAsB,gBAAgB,CACpC,IAAI,EAAE,aAAa,EACnB,OAAO,EAAE,gBAAgB;AACzB;;;GAGG;AACH,WAAW,GAAE,MAAM,IAAsB,GACxC,OAAO,CAAC;IACT,OAAO,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IAC/C,iBAAiB,EAAE,eAAe,CAAC;CACpC,CAAC,CAmDD"}
@@ -84,13 +84,33 @@ function asUpsert(step) {
84
84
  ...(document !== undefined ? { document } : {}),
85
85
  };
86
86
  }
87
+ // implements REQ-kibi-change-to-proof-plan-compiler-v2
88
+ /**
89
+ * Why a plan that is not ready cannot be applied, with the remedy for its
90
+ * status. The leading sentence stays the same for every status.
91
+ */
92
+ function notReadyPlanMessage(prefix, status) {
93
+ const base = `${prefix}: only ready plans may be applied; this plan's status is '${String(status)}'.`;
94
+ switch (status) {
95
+ case "needs_resolution":
96
+ return `${base} Resolve its ontology_gap and missing propositions first (model each with kb_model mode requirement or predicates, then call kb_compile_intent again and apply the ready plan it returns), and fix any step its diagnostics name. Or write the requirement with kb_upsert carrying the plan's inventory contract, with the unmodeled propositions kept as status ontology_gap entries, as the kibi-usage skill describes.`;
97
+ case "blocked":
98
+ return prefix === "Apply plan failed"
99
+ ? `${base} Its contradiction analysis found a conflict with a current requirement: resolve the contradiction first (supersede or change the conflicting requirement, or change the intent), then compile again.`
100
+ : `${base} Resolve what its diagnostics name, then plan the bootstrap again.`;
101
+ case "needs_context":
102
+ return `${base} Supply the bootstrap context it asks for, then plan the bootstrap again.`;
103
+ default:
104
+ return `${base} Compile or plan again and apply the plan it returns once its status is 'ready'.`;
105
+ }
106
+ }
87
107
  function validateCompilePlanShape(args) {
88
108
  if (!isRecord(args.plan))
89
109
  throw new Error("Apply plan failed: plan must be an object");
90
110
  if (args.plan.version !== "kibi.compile-plan.v1")
91
111
  throw new Error("Apply plan failed: unsupported plan version");
92
112
  if (args.plan.status !== "ready")
93
- throw new Error("Apply plan failed: only ready plans may be applied");
113
+ throw new Error(notReadyPlanMessage("Apply plan failed", args.plan.status));
94
114
  if (!/^[a-f0-9]{64}$/i.test(args.approvedPlanHash))
95
115
  throw new Error("Apply plan failed: approvedPlanHash must be a SHA-256 hash");
96
116
  if (args.approvedPlanHash !== args.plan.planHash)
@@ -163,7 +183,7 @@ export function orderBootstrapActions(actions, completed = new Set()) {
163
183
  function validateBootstrapPlanShape(args) {
164
184
  const { plan } = args;
165
185
  if (plan.status !== "ready")
166
- throw new Error("Bootstrap apply failed: only ready plans may be applied");
186
+ throw new Error(notReadyPlanMessage("Bootstrap apply failed", plan.status));
167
187
  if (!/^[a-f0-9]{64}$/i.test(args.approvedPlanHash))
168
188
  throw new Error("Bootstrap apply failed: approvedPlanHash must be SHA-256");
169
189
  if (args.approvedPlanHash !== plan.planHash)
@@ -1 +1 @@
1
- {"version":3,"file":"analyze-prose.d.ts","sourceRoot":"","sources":["../../../src/operations/semantic-advisor/analyze-prose.ts"],"names":[],"mappings":"AAsBA,OAAO,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AAiBxD,OAAO,KAAK,EACV,6BAA6B,EAC7B,oBAAoB,EAWrB,MAAM,YAAY,CAAC;AAEpB,eAAO,MAAM,wBAAwB,wBAAwB,CAAC;AAC9D,OAAO,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAEhD,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,EAAE,CAI1D;AAugBD,OAAO,EAAE,eAAe,EAAE,CAAC;AA4V3B,wBAAgB,2BAA2B,CACzC,KAAK,EAAE,oBAAoB,GAC1B,6BAA6B,CA+D/B"}
1
+ {"version":3,"file":"analyze-prose.d.ts","sourceRoot":"","sources":["../../../src/operations/semantic-advisor/analyze-prose.ts"],"names":[],"mappings":"AAuBA,OAAO,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AAiBxD,OAAO,KAAK,EACV,6BAA6B,EAC7B,oBAAoB,EAWrB,MAAM,YAAY,CAAC;AAEpB,eAAO,MAAM,wBAAwB,wBAAwB,CAAC;AAC9D,OAAO,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAEhD,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,EAAE,CAI1D;AA4gBD,OAAO,EAAE,eAAe,EAAE,CAAC;AAwX3B,wBAAgB,2BAA2B,CACzC,KAAK,EAAE,oBAAoB,GAC1B,6BAA6B,CAgE/B"}
@@ -1,4 +1,5 @@
1
1
  import { createBuiltinOntologyPack, detectSignals as detectBuiltinSignals, } from "kibi-plugin-builtin";
2
+ import { InputError } from "../../cli-errors.js";
2
3
  import { utf8Span, validateLogicIr, } from "../../logic/ir.js";
3
4
  import { logicRuleFactId } from "../modeling/logic-modeling.js";
4
5
  import { buildAdvisorResult } from "./analysis-receipt.js";
@@ -15,12 +16,17 @@ export function stringLogicClaims(value) {
15
16
  ? value.filter((entry) => typeof entry === "string")
16
17
  : [];
17
18
  }
19
+ // implements REQ-mcp-semantic-advisor-preflight
18
20
  function proseOf(payload) {
19
21
  const properties = propertiesOf(payload);
22
+ // text_ref is the prose only while semantic_text is unset (the ingestion
23
+ // boundary's fallback); beside semantic_text it is a reference such as
24
+ // "docs/intake.md#batch-4", whose numbers and words are not claims.
25
+ const semanticText = stringValue(properties.semantic_text);
20
26
  return [
21
27
  properties.title,
22
28
  properties.semantic_text,
23
- properties.text_ref,
29
+ semanticText ? undefined : properties.text_ref,
24
30
  properties.description,
25
31
  ]
26
32
  .map(stringValue)
@@ -540,6 +546,16 @@ function shadowAnalysis(text, interpretations) {
540
546
  }
541
547
  function propositionSpan(text, clause, cursor) {
542
548
  const located = locateSourceText(text, clause.text, cursor);
549
+ // A clause the prose does not contain has no source span: it keeps its own
550
+ // text over an empty span (which strict ingestion rejects) instead of an
551
+ // unrelated slice of the prose.
552
+ if (located === null) {
553
+ return {
554
+ span: utf8Span(text, cursor, cursor),
555
+ next: cursor,
556
+ sourceText: clause.text,
557
+ };
558
+ }
543
559
  return {
544
560
  span: utf8Span(text, located.start, located.end),
545
561
  next: Math.max(cursor, located.end),
@@ -552,23 +568,34 @@ function escapeRegExp(value) {
552
568
  /**
553
569
  * Locate canonical claim text while accepting source-only whitespace layout.
554
570
  * The caller receives the authored slice so strict ingestion can compare the
555
- * span with the claim text without rewriting the source document.
571
+ * span with the claim text without rewriting the source document. The search
572
+ * starts at `cursor` and then retries from the start of the prose; null means
573
+ * the prose does not contain the claim text at all.
556
574
  */
557
575
  function locateSourceText(source, claimText, cursor) {
558
- const canonicalPattern = escapeRegExp(claimText).replace(/\s+/g, "\\s+");
559
- const match = new RegExp(canonicalPattern).exec(source.slice(cursor));
560
- if (match?.index !== undefined) {
561
- const start = cursor + match.index;
562
- return { start, end: start + match[0].length };
576
+ const pattern = new RegExp(escapeRegExp(claimText.trim()).replace(/\s+/g, "\\s+"));
577
+ for (const from of cursor > 0 ? [cursor, 0] : [0]) {
578
+ const match = pattern.exec(source.slice(from));
579
+ if (match !== null) {
580
+ const start = from + match.index;
581
+ return { start, end: start + match[0].length };
582
+ }
563
583
  }
564
- const exact = source.indexOf(claimText, cursor);
565
- const start = exact >= 0 ? exact : cursor;
566
- return {
567
- start,
568
- end: exact >= 0
569
- ? exact + claimText.length
570
- : Math.min(source.length, start + claimText.length),
571
- };
584
+ return null;
585
+ }
586
+ /**
587
+ * Caller-supplied clauses split the requirement prose; they never restate
588
+ * it. A clause that does not occur in the prose (apart from whitespace
589
+ * layout, a list marker or closing punctuation) would get a span over
590
+ * unrelated text, so the request is refused naming each such clause.
591
+ */
592
+ function assertVerbatimClauses(statement, clauses) {
593
+ const missing = clauses
594
+ .filter((clause) => locateSourceText(statement, clause.text, 0) === null)
595
+ .map((clause) => JSON.stringify(clause.text));
596
+ if (missing.length === 0)
597
+ return;
598
+ throw new InputError("VALIDATION_FAILED", `Semantic advisor clauses must be verbatim fragments of the requirement prose (the intent or semantic_text); ${missing.length === 1 ? "this clause does" : "these clauses do"} not occur in it: ${missing.join(", ")}. Copy each clause from the prose unchanged (whitespace may differ), or omit clauses to let Kibi split the prose.`);
572
599
  }
573
600
  function interpretationResults(interpretations, clauses, statement) {
574
601
  if (interpretations === undefined)
@@ -592,8 +619,11 @@ function interpretationResults(interpretations, clauses, statement) {
592
619
  errors.push("interpretation span is invalid");
593
620
  if (clause && candidate.span) {
594
621
  const candidateLocation = locateSourceText(statement, candidate.claim_text, 0);
595
- const expectedSpan = utf8Span(statement, candidateLocation.start, candidateLocation.end);
596
- if (candidate.span.start !== expectedSpan.start ||
622
+ const expectedSpan = candidateLocation === null
623
+ ? null
624
+ : utf8Span(statement, candidateLocation.start, candidateLocation.end);
625
+ if (expectedSpan === null ||
626
+ candidate.span.start !== expectedSpan.start ||
597
627
  candidate.span.end !== expectedSpan.end)
598
628
  errors.push(`interpretation span for ${candidate.claim_key} does not match the exact UTF-8 proposition span`);
599
629
  }
@@ -690,6 +720,8 @@ export function analyzeSemanticAdvisorInput(input) {
690
720
  const clauses = statement
691
721
  ? extractSemanticClauses(statement, input.clauses ?? semanticClausesOf(payload))
692
722
  : [];
723
+ if (input.clauses !== undefined)
724
+ assertVerbatimClauses(statement, clauses);
693
725
  const rawDeclaredClaims = propertiesOf(payload).logic_claims;
694
726
  const declaredClaims = Array.isArray(rawDeclaredClaims)
695
727
  ? rawDeclaredClaims.filter((value) => typeof value === "string")
@@ -1 +1 @@
1
- {"version":3,"file":"analyzer.d.ts","sourceRoot":"","sources":["../../../src/public/impact/analyzer.ts"],"names":[],"mappings":"AA6BA,OAAO,KAAK,EACV,wBAAwB,EACxB,uBAAuB,EACxB,MAAM,YAAY,CAAC;AAEpB,wBAAsB,wBAAwB,CAC5C,OAAO,EAAE,wBAAwB,GAChC,OAAO,CAAC,uBAAuB,CAAC,CA2IlC"}
1
+ {"version":3,"file":"analyzer.d.ts","sourceRoot":"","sources":["../../../src/public/impact/analyzer.ts"],"names":[],"mappings":"AA8BA,OAAO,KAAK,EACV,wBAAwB,EACxB,uBAAuB,EACxB,MAAM,YAAY,CAAC;AAEpB,wBAAsB,wBAAwB,CAC5C,OAAO,EAAE,wBAAwB,GAChC,OAAO,CAAC,uBAAuB,CAAC,CAsJlC"}