kibi-cli 2.22.0 → 2.23.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 (142) hide show
  1. package/dist/cli-register-foundation.d.ts.map +1 -1
  2. package/dist/cli-register-foundation.js +1 -0
  3. package/dist/commands/check.d.ts.map +1 -1
  4. package/dist/commands/check.js +51 -3
  5. package/dist/commands/doctor.d.ts.map +1 -1
  6. package/dist/commands/doctor.js +40 -0
  7. package/dist/commands/init-helpers.d.ts +2 -1
  8. package/dist/commands/init-helpers.d.ts.map +1 -1
  9. package/dist/commands/init-helpers.js +3 -0
  10. package/dist/commands/migrate.d.ts +47 -0
  11. package/dist/commands/migrate.d.ts.map +1 -1
  12. package/dist/commands/migrate.js +82 -15
  13. package/dist/commands/sync.d.ts.map +1 -1
  14. package/dist/commands/sync.js +15 -1
  15. package/dist/extractors/markdown.d.ts.map +1 -1
  16. package/dist/extractors/markdown.js +37 -35
  17. package/dist/intent-search.d.ts.map +1 -1
  18. package/dist/intent-search.js +32 -4
  19. package/dist/operations/bootstrap/activation.d.ts +7 -1
  20. package/dist/operations/bootstrap/activation.d.ts.map +1 -1
  21. package/dist/operations/bootstrap/activation.js +27 -2
  22. package/dist/operations/bootstrap/generate.d.ts.map +1 -1
  23. package/dist/operations/bootstrap/generate.js +56 -16
  24. package/dist/operations/bootstrap/guidance-actions.d.ts.map +1 -1
  25. package/dist/operations/bootstrap/guidance-actions.js +14 -2
  26. package/dist/operations/bootstrap/guidance.d.ts.map +1 -1
  27. package/dist/operations/bootstrap/guidance.js +6 -2
  28. package/dist/operations/bootstrap/presentation.d.ts.map +1 -1
  29. package/dist/operations/bootstrap/presentation.js +75 -9
  30. package/dist/operations/bootstrap/types.d.ts +6 -0
  31. package/dist/operations/bootstrap/types.d.ts.map +1 -1
  32. package/dist/operations/migration/kb-sources.d.ts +8 -5
  33. package/dist/operations/migration/kb-sources.d.ts.map +1 -1
  34. package/dist/operations/migration/kb-sources.js +50 -14
  35. package/dist/operations/migration/schema6.d.ts.map +1 -1
  36. package/dist/operations/migration/schema6.js +11 -2
  37. package/dist/operations/migration/source-paths.d.ts +18 -1
  38. package/dist/operations/migration/source-paths.d.ts.map +1 -1
  39. package/dist/operations/migration/source-paths.js +111 -16
  40. package/dist/operations/modeling/suggest-predicates.d.ts.map +1 -1
  41. package/dist/operations/modeling/suggest-predicates.js +22 -7
  42. package/dist/operations/mutation/requirement-semantics.d.ts +16 -0
  43. package/dist/operations/mutation/requirement-semantics.d.ts.map +1 -1
  44. package/dist/operations/mutation/requirement-semantics.js +50 -0
  45. package/dist/operations/mutation/strict-fact.js +1 -1
  46. package/dist/operations/mutation/upsert.d.ts.map +1 -1
  47. package/dist/operations/mutation/upsert.js +2 -1
  48. package/dist/operations/mutation/validation.d.ts.map +1 -1
  49. package/dist/operations/mutation/validation.js +43 -1
  50. package/dist/operations/planning/apply-plan.d.ts.map +1 -1
  51. package/dist/operations/planning/apply-plan.js +38 -9
  52. package/dist/operations/semantic-advisor/analyze-prose.d.ts.map +1 -1
  53. package/dist/operations/semantic-advisor/analyze-prose.js +49 -17
  54. package/dist/operations/semantic-advisor/ingestion-boundary.d.ts.map +1 -1
  55. package/dist/operations/semantic-advisor/ingestion-boundary.js +25 -1
  56. package/dist/operations/semantic-advisor/stored-ledger-drift.d.ts +19 -0
  57. package/dist/operations/semantic-advisor/stored-ledger-drift.d.ts.map +1 -0
  58. package/dist/operations/semantic-advisor/stored-ledger-drift.js +78 -0
  59. package/dist/public/impact/analyzer.d.ts.map +1 -1
  60. package/dist/public/impact/analyzer.js +7 -1
  61. package/dist/public/impact/diagnostics.d.ts.map +1 -1
  62. package/dist/public/impact/diagnostics.js +16 -3
  63. package/dist/public/impact/types.d.ts +2 -0
  64. package/dist/public/impact/types.d.ts.map +1 -1
  65. package/dist/public/operations/catalog.d.ts +7 -2
  66. package/dist/public/operations/catalog.d.ts.map +1 -1
  67. package/dist/public/operations/check-executor.d.ts.map +1 -1
  68. package/dist/public/operations/check-executor.js +10 -3
  69. package/dist/public/operations/check-helpers.d.ts +10 -0
  70. package/dist/public/operations/check-helpers.d.ts.map +1 -1
  71. package/dist/public/operations/check-helpers.js +54 -0
  72. package/dist/public/operations/contracts.d.ts.map +1 -1
  73. package/dist/public/operations/contracts.js +1 -0
  74. package/dist/public/operations/decision-firmness.d.ts +3 -1
  75. package/dist/public/operations/decision-firmness.d.ts.map +1 -1
  76. package/dist/public/operations/decision-firmness.js +13 -3
  77. package/dist/public/operations/discovery-executors.d.ts +18 -0
  78. package/dist/public/operations/discovery-executors.d.ts.map +1 -1
  79. package/dist/public/operations/discovery-executors.js +77 -3
  80. package/dist/public/operations/migration-plan.d.ts +13 -0
  81. package/dist/public/operations/migration-plan.d.ts.map +1 -1
  82. package/dist/public/operations/migration-plan.js +29 -1
  83. package/dist/public/operations/schema6-check-actions.d.ts +4 -0
  84. package/dist/public/operations/schema6-check-actions.d.ts.map +1 -1
  85. package/dist/public/operations/schema6-check-actions.js +39 -2
  86. package/dist/public/operations/source-path-dangling.d.ts +1 -1
  87. package/dist/public/operations/source-path-dangling.d.ts.map +1 -1
  88. package/dist/public/operations/source-path-dangling.js +11 -2
  89. package/dist/public/operations/specs/composite.d.ts +1 -1
  90. package/dist/public/operations/specs/discovery.d.ts +5 -0
  91. package/dist/public/operations/specs/discovery.d.ts.map +1 -1
  92. package/dist/public/operations/specs/discovery.js +5 -0
  93. package/dist/public/operations/specs/planning.d.ts +1 -1
  94. package/dist/public/operations/specs/planning.js +1 -1
  95. package/dist/public/operations/specs/reporting.d.ts.map +1 -1
  96. package/dist/public/operations/specs/reporting.js +6 -4
  97. package/dist/public/operations/specs/semantic.d.ts +1 -1
  98. package/dist/public/operations/specs/semantic.d.ts.map +1 -1
  99. package/dist/public/operations/specs/semantic.js +30 -3
  100. package/dist/public/skills/kibi-bootstrap/SKILL.md +3 -2
  101. package/dist/public/skills/kibi-usage/SKILL.md +13 -5
  102. package/dist/public/skills/kibi-usage/resources/workflows.md +3 -1
  103. package/dist/search-answer.d.ts +14 -0
  104. package/dist/search-answer.d.ts.map +1 -1
  105. package/dist/search-answer.js +42 -4
  106. package/dist/traceability/git-change-snapshot.d.ts +46 -0
  107. package/dist/traceability/git-change-snapshot.d.ts.map +1 -1
  108. package/dist/traceability/git-change-snapshot.js +187 -3
  109. package/dist/traceability/staged-diagnostics.d.ts.map +1 -1
  110. package/dist/traceability/staged-diagnostics.js +5 -6
  111. package/dist/traceability/staged-inventory-drift.d.ts +24 -0
  112. package/dist/traceability/staged-inventory-drift.d.ts.map +1 -0
  113. package/dist/traceability/staged-inventory-drift.js +84 -0
  114. package/dist/traceability/staged-symbols-manifest.d.ts.map +1 -1
  115. package/dist/traceability/staged-symbols-manifest.js +1 -2
  116. package/dist/utils/content-freshness.d.ts +9 -0
  117. package/dist/utils/content-freshness.d.ts.map +1 -1
  118. package/dist/utils/content-freshness.js +33 -0
  119. package/dist/utils/merge-driver-setup.d.ts +10 -0
  120. package/dist/utils/merge-driver-setup.d.ts.map +1 -0
  121. package/dist/utils/merge-driver-setup.js +69 -0
  122. package/dist/utils/rule-registry.generated.d.ts +1 -1
  123. package/dist/utils/rule-registry.generated.js +1 -1
  124. package/package.json +2 -2
  125. package/src/public/impact/analyzer.ts +13 -1
  126. package/src/public/impact/diagnostics.ts +20 -3
  127. package/src/public/impact/types.ts +2 -0
  128. package/src/public/operations/check-executor.ts +16 -4
  129. package/src/public/operations/check-helpers.ts +65 -0
  130. package/src/public/operations/contracts.ts +1 -0
  131. package/src/public/operations/decision-firmness.ts +18 -2
  132. package/src/public/operations/discovery-executors.ts +113 -3
  133. package/src/public/operations/migration-plan.ts +47 -1
  134. package/src/public/operations/schema6-check-actions.ts +43 -2
  135. package/src/public/operations/source-path-dangling.ts +12 -2
  136. package/src/public/operations/specs/discovery.ts +6 -0
  137. package/src/public/operations/specs/planning.ts +1 -1
  138. package/src/public/operations/specs/reporting.ts +6 -4
  139. package/src/public/operations/specs/semantic.ts +41 -4
  140. package/src/public/skills/kibi-bootstrap/SKILL.md +3 -2
  141. package/src/public/skills/kibi-usage/SKILL.md +13 -5
  142. package/src/public/skills/kibi-usage/resources/workflows.md +3 -1
@@ -1,3 +1,5 @@
1
+ import { analyzeSemanticAdvisorInput } from "../semantic-advisor/analyze-prose.js";
2
+ import { validateSemanticInventoryBoundary, } from "../semantic-advisor/ingestion-boundary.js";
1
3
  import { storedEntityLookup, } from "./stored-entity.js";
2
4
  /** The fields that make up a requirement's proposition ledger. */
3
5
  const SEMANTIC_CONTRACT_FIELDS = [
@@ -62,3 +64,51 @@ export async function withStoredRequirementSemantics(input, prolog, staged, look
62
64
  return input;
63
65
  return { ...input, properties: { ...preserved, ...properties } };
64
66
  }
67
+ /** Stored fields that decide how sync reads a requirement's propositions. */
68
+ const BOUNDARY_FIELDS = [
69
+ "status",
70
+ "title",
71
+ "text_ref",
72
+ ...SEMANTIC_CONTRACT_FIELDS,
73
+ ];
74
+ function boundaryOf(input, properties, relationships) {
75
+ const payload = { type: input.type, id: input.id, properties };
76
+ const { receipt } = analyzeSemanticAdvisorInput({
77
+ payload: { ...payload, relationships },
78
+ });
79
+ return validateSemanticInventoryBoundary(payload, relationships, receipt);
80
+ }
81
+ // implements REQ-kibi-upsert-preserves-requirement-semantics, REQ-kibi-proposition-complete-ingestion
82
+ /**
83
+ * An update to a stored requirement is judged as its authored file will read
84
+ * afterwards: the stored status, prose and proposition ledger with the
85
+ * payload's fields on top (the source writer keeps every front matter key the
86
+ * payload leaves out and states the stored `semantic_text`). A payload such
87
+ * as `{status: "closed"}` passes the check on its own fields, yet the file it
88
+ * leaves is a current requirement whose prose has no inventory, which sync
89
+ * then refuses. Such a write is refused here with the error sync would give.
90
+ *
91
+ * A requirement that was already current with the same prose and no complete
92
+ * inventory is legacy content sync keeps accepting, so a write that changes
93
+ * neither keeps working. A write that makes the requirement current, or
94
+ * changes the prose sync checks, must carry the complete inventory.
95
+ */
96
+ export async function assertStoredRequirementBoundary(input, relationships, prolog, staged, lookup = storedEntityLookup(prolog, input.id, input.type)) {
97
+ if (input.type !== "req")
98
+ return;
99
+ const stored = await storedRequirement(input.id, lookup, staged);
100
+ if (stored === undefined)
101
+ return;
102
+ const before = {};
103
+ for (const field of BOUNDARY_FIELDS) {
104
+ if (stored[field] !== undefined)
105
+ before[field] = stored[field];
106
+ }
107
+ const after = boundaryOf(input, { ...before, ...(input.properties ?? {}) }, relationships);
108
+ if (after.errors.length === 0)
109
+ return;
110
+ const previous = boundaryOf(input, before, relationships);
111
+ if (previous.applicable && previous.sourceHash === after.sourceHash)
112
+ return;
113
+ 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.`);
114
+ }
@@ -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":"validation.d.ts","sourceRoot":"","sources":["../../../src/operations/mutation/validation.ts"],"names":[],"mappings":"AAKA,OAAO,EAEL,0BAA0B,EAC3B,MAAM,oCAAoC,CAAC;AAU5C,OAAO,KAAK,EAAE,WAAW,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAqI/D,wBAAgB,mBAAmB,CACjC,KAAK,EAAE,WAAW,EAClB,GAAG,EAAE,IAAI,GACR,eAAe,CA6DjB;AAED,OAAO,EAAE,0BAA0B,EAAE,CAAC"}
1
+ {"version":3,"file":"validation.d.ts","sourceRoot":"","sources":["../../../src/operations/mutation/validation.ts"],"names":[],"mappings":"AAKA,OAAO,EAEL,0BAA0B,EAC3B,MAAM,oCAAoC,CAAC;AAU5C,OAAO,KAAK,EAAE,WAAW,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AA8L/D,wBAAgB,mBAAmB,CACjC,KAAK,EAAE,WAAW,EAClB,GAAG,EAAE,IAAI,GACR,eAAe,CA6DjB;AAED,OAAO,EAAE,0BAA0B,EAAE,CAAC"}
@@ -64,9 +64,51 @@ function unknownEntityProperties(entity) {
64
64
  const known = upsertEntitySchema.properties;
65
65
  return Object.keys(entity).filter((key) => !Object.hasOwn(known, key));
66
66
  }
67
+ /**
68
+ * The fields a type-conditional `else: { not: ... }` branch of the entity
69
+ * schema forbids, and the types that may carry them; null for any other
70
+ * schema path.
71
+ */
72
+ function typeRestrictedFields(schemaPath) {
73
+ const match = /^#\/allOf\/(\d+)\/else\/not$/.exec(schemaPath);
74
+ if (match === null)
75
+ return null;
76
+ const allOf = upsertEntitySchema.allOf;
77
+ const branch = Array.isArray(allOf) ? allOf[Number(match[1])] : undefined;
78
+ if (branch === null || typeof branch !== "object")
79
+ return null;
80
+ const record = branch;
81
+ const typeRule = record.if?.properties?.type;
82
+ const types = Array.isArray(typeRule?.enum)
83
+ ? typeRule.enum.map(String)
84
+ : typeof typeRule?.const === "string"
85
+ ? [typeRule.const]
86
+ : [];
87
+ const not = record.else?.not;
88
+ const requiredLists = [
89
+ not?.required,
90
+ ...(Array.isArray(not?.anyOf)
91
+ ? not.anyOf.map((entry) => entry?.required)
92
+ : []),
93
+ ];
94
+ const fields = requiredLists.flatMap((list) => Array.isArray(list) ? list.map(String) : []);
95
+ return types.length > 0 && fields.length > 0 ? { fields, types } : null;
96
+ }
67
97
  // implements REQ-upsert-unknown-field-guidance
68
98
  function formatEntityErrors(entity, errors) {
69
- const messages = errors.map((error) => {
99
+ // A field only some entity types carry fails a type-conditional `not`
100
+ // branch; AJV then reports "must NOT be valid" plus the "else" it chose.
101
+ const restricted = new Set(errors.flatMap((error) => error.keyword === "not" && typeRestrictedFields(error.schemaPath)
102
+ ? [error.schemaPath.replace(/\/else\/not$/, "/if")]
103
+ : []));
104
+ const visible = errors.filter((error) => !(error.keyword === "if" && restricted.has(error.schemaPath)));
105
+ const messages = visible.map((error) => {
106
+ const restriction = error.keyword === "not" ? typeRestrictedFields(error.schemaPath) : null;
107
+ if (restriction !== null) {
108
+ const present = restriction.fields.filter((field) => Object.hasOwn(entity, field));
109
+ const named = present.length > 0 ? present : restriction.fields;
110
+ return `${named.join(", ")} ${named.length === 1 ? "is" : "are"} only valid on ${restriction.types.join(" and ")} entities; remove ${named.length === 1 ? "it" : "them"} from this ${String(entity.type ?? "entity")}`;
111
+ }
70
112
  const params = error.params;
71
113
  const path = error.instancePath || "root";
72
114
  if (error.keyword === "additionalProperties" && params.additionalProperty) {
@@ -1 +1 @@
1
- {"version":3,"file":"apply-plan.d.ts","sourceRoot":"","sources":["../../../src/operations/planning/apply-plan.ts"],"names":[],"mappings":"AAyBA,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"}
@@ -2,7 +2,7 @@ import { createHash, randomUUID } from "node:crypto";
2
2
  import path from "node:path";
3
3
  import { OperationError } from "../../cli-errors.js";
4
4
  import { branchEnsureCommand, branchMigrateCommand, branchRecoverCommand, } from "../../commands/branch.js";
5
- import { migrateCommand } from "../../commands/migrate.js";
5
+ import { describeMigrationRun, migrateCommand, } from "../../commands/migrate.js";
6
6
  import { syncCommand } from "../../commands/sync.js";
7
7
  import { PROOF_INTEGRATION_CONFIGURE, applyProofIntegrationAction, } from "../../proof/integration-plan.js";
8
8
  import { loadEntities } from "../../public/operations/discovery-entities.js";
@@ -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)
@@ -2155,12 +2175,12 @@ async function applyMigrationPlan(args, context, onCommitted = () => undefined)
2155
2175
  continue;
2156
2176
  }
2157
2177
  try {
2158
- await applyMigrationAction(action, context);
2178
+ const detail = await applyMigrationAction(action, context);
2159
2179
  onCommitted();
2160
2180
  results.push({
2161
2181
  actionId: action.id,
2162
2182
  outcome: "applied",
2163
- detail: `Applied ${action.code}.`,
2183
+ detail: detail ?? `Applied ${action.code}.`,
2164
2184
  });
2165
2185
  }
2166
2186
  catch (error) {
@@ -2292,6 +2312,10 @@ async function applyMigrationPlan(args, context, onCommitted = () => undefined)
2292
2312
  structuredContent: payload,
2293
2313
  };
2294
2314
  }
2315
+ /**
2316
+ * Run one approved migration action. Returns a detail line for the action
2317
+ * result when the executor reports what it changed.
2318
+ */
2295
2319
  async function applyMigrationAction(action, context) {
2296
2320
  switch (action.code) {
2297
2321
  case "legacy_branch_storage":
@@ -2342,14 +2366,19 @@ async function applyMigrationAction(action, context) {
2342
2366
  return;
2343
2367
  case "schema_version_upgrade":
2344
2368
  case "invalid_schema_version":
2345
- case "legacy_storage_migration":
2346
- if ((await migrateCommand({
2369
+ case "legacy_storage_migration": {
2370
+ // implements REQ-agent-guided-migration-orchestration
2371
+ const run = await migrateCommand({
2347
2372
  yes: true,
2348
2373
  workspaceRoot: context.workspaceRoot,
2349
2374
  initializeMissingConfig: true,
2350
- })).exitCode !== 0)
2375
+ });
2376
+ if (run.exitCode !== 0)
2351
2377
  throw new Error("Schema migration did not complete successfully.");
2352
- return;
2378
+ return run.summary !== undefined
2379
+ ? describeMigrationRun(run.summary)
2380
+ : undefined;
2381
+ }
2353
2382
  case "symbol_refresh_coordinates":
2354
2383
  case "coverage_source_coordinates": {
2355
2384
  const result = await syncCommand({
@@ -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":"ingestion-boundary.d.ts","sourceRoot":"","sources":["../../../src/operations/semantic-advisor/ingestion-boundary.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,0CAA0C,CAAC;AAG3E,OAAO,EACL,KAAK,OAAO,EAOb,MAAM,aAAa,CAAC;AACrB,OAAO,KAAK,EAAE,sBAAsB,EAAuB,MAAM,YAAY,CAAC;AAgB9E,MAAM,WAAW,+BAA+B;IAC9C,QAAQ,CAAC,UAAU,EAAE,OAAO,CAAC;IAC7B,QAAQ,CAAC,MAAM,EAAE,SAAS,MAAM,EAAE,CAAC;IACnC,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;CAC7B;AAED,MAAM,MAAM,oBAAoB,GAAG,QAAQ,CAAC;IAC1C,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,EAAE,CAAC,EAAE,OAAO,CAAC;CACd,CAAC,CAAC;AAuEH,wBAAgB,iCAAiC,CAC/C,OAAO,EAAE,OAAO,EAChB,aAAa,EAAE,SAAS,oBAAoB,EAAE,EAC9C,OAAO,EAAE,sBAAsB,GAC9B,+BAA+B,CA0IjC;AAED,wBAAgB,+BAA+B,CAC7C,OAAO,EAAE,OAAO,EAChB,aAAa,EAAE,SAAS,oBAAoB,EAAE,EAC9C,OAAO,EAAE,sBAAsB,GAC9B,IAAI,CAWN;AAoDD;;;;GAIG;AAEH,wBAAgB,2BAA2B,CACzC,OAAO,EAAE,OAAO,EAChB,aAAa,EAAE,SAAS,oBAAoB,EAAE,EAC9C,OAAO,EAAE,WAAW,CAAC,MAAM,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC,GAC9D,MAAM,GAAG,IAAI,CAUf;AAGD,wBAAsB,+BAA+B,CACnD,MAAM,EAAE,UAAU,EAClB,OAAO,EAAE,OAAO,EAChB,aAAa,EAAE,SAAS,oBAAoB,EAAE,EAC9C,MAAM,CAAC,EAAE,QAAQ,CAAC;IAChB,QAAQ,EAAE,WAAW,CAAC,MAAM,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC,CAAC;CAClE,CAAC,GACD,OAAO,CAAC,IAAI,CAAC,CAwBf"}
1
+ {"version":3,"file":"ingestion-boundary.d.ts","sourceRoot":"","sources":["../../../src/operations/semantic-advisor/ingestion-boundary.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,0CAA0C,CAAC;AAG3E,OAAO,EACL,KAAK,OAAO,EAOb,MAAM,aAAa,CAAC;AACrB,OAAO,KAAK,EAAE,sBAAsB,EAAuB,MAAM,YAAY,CAAC;AAgB9E,MAAM,WAAW,+BAA+B;IAC9C,QAAQ,CAAC,UAAU,EAAE,OAAO,CAAC;IAC7B,QAAQ,CAAC,MAAM,EAAE,SAAS,MAAM,EAAE,CAAC;IACnC,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;CAC7B;AAED,MAAM,MAAM,oBAAoB,GAAG,QAAQ,CAAC;IAC1C,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,EAAE,CAAC,EAAE,OAAO,CAAC;CACd,CAAC,CAAC;AAuEH,wBAAgB,iCAAiC,CAC/C,OAAO,EAAE,OAAO,EAChB,aAAa,EAAE,SAAS,oBAAoB,EAAE,EAC9C,OAAO,EAAE,sBAAsB,GAC9B,+BAA+B,CA0IjC;AAED,wBAAgB,+BAA+B,CAC7C,OAAO,EAAE,OAAO,EAChB,aAAa,EAAE,SAAS,oBAAoB,EAAE,EAC9C,OAAO,EAAE,sBAAsB,GAC9B,IAAI,CAWN;AAoFD;;;;GAIG;AAEH,wBAAgB,2BAA2B,CACzC,OAAO,EAAE,OAAO,EAChB,aAAa,EAAE,SAAS,oBAAoB,EAAE,EAC9C,OAAO,EAAE,WAAW,CAAC,MAAM,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC,GAC9D,MAAM,GAAG,IAAI,CAUf;AAGD,wBAAsB,+BAA+B,CACnD,MAAM,EAAE,UAAU,EAClB,OAAO,EAAE,OAAO,EAChB,aAAa,EAAE,SAAS,oBAAoB,EAAE,EAC9C,MAAM,CAAC,EAAE,QAAQ,CAAC;IAChB,QAAQ,EAAE,WAAW,CAAC,MAAM,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC,CAAC;CAClE,CAAC,GACD,OAAO,CAAC,IAAI,CAAC,CAwBf"}
@@ -192,9 +192,33 @@ function groundingClaimKeyError(modeledClaimKeys, grounded) {
192
192
  return `Proposition-complete ingestion failed: claim '${duplicate}' has more than one logical grounding relationship.`;
193
193
  if (groundedClaimKeys.length !== modeledClaimKeys.length ||
194
194
  modeledClaimKeys.some((claimKey) => !groundedClaimKeys.includes(claimKey)))
195
- return "Proposition-complete ingestion failed: modeled proposition claim_keys must match logical grounding target claim_keys exactly.";
195
+ return `Proposition-complete ingestion failed: modeled proposition claim_keys must match logical grounding target claim_keys exactly. ${claimKeyMismatchDetail(modeledClaimKeys, grounded)}`;
196
196
  return null;
197
197
  }
198
+ /**
199
+ * Why the mismatch names both sides: a claim_key is derived from the clause
200
+ * text, so a requirement whose prose was reworded (or analyzed from another
201
+ * field than the stored semantic_text) carries keys none of its grounding
202
+ * facts declare. Listing the unmatched modeled keys and the facts with the
203
+ * keys they still declare lets the agent see the drift and pick the remedy
204
+ * without diffing the KB by hand.
205
+ */
206
+ // implements REQ-kibi-proposition-complete-ingestion
207
+ function claimKeyMismatchDetail(modeledClaimKeys, grounded) {
208
+ const groundedClaimKeys = grounded.map(([, claimKey]) => claimKey);
209
+ const ungrounded = modeledClaimKeys.filter((claimKey) => !groundedClaimKeys.includes(claimKey));
210
+ const stale = grounded.filter(([, claimKey]) => !modeledClaimKeys.includes(claimKey));
211
+ const parts = [
212
+ ungrounded.length > 0
213
+ ? `Modeled claims with no grounding fact declaring their claim_key: ${ungrounded.map((claimKey) => `'${claimKey}'`).join(", ")}.`
214
+ : "",
215
+ stale.length > 0
216
+ ? `Grounding facts whose claim_key the inventory no longer models: ${stale.map(([target, claimKey]) => `${target} ('${claimKey}')`).join(", ")}.`
217
+ : "",
218
+ "A claim_key is derived from the clause text, so reworded prose (or prose analyzed from a field other than the stored semantic_text) yields new keys. Keep the stored semantic_text wording, or kb_upsert each grounding fact with the new claim_key and claim_text (same fact id) before this write; for a change of meaning, supersede the requirement instead.",
219
+ ];
220
+ return parts.filter(Boolean).join(" ");
221
+ }
198
222
  /**
199
223
  * The write-time claim_key check, run against planned writes only. Returns
200
224
  * null when it passes or when a target is outside the plan (the write-time
@@ -0,0 +1,19 @@
1
+ import type { PrologPort } from "../../public/operations/runtime-types.js";
2
+ import type { SemanticAdvisorReceipt } from "./types.js";
3
+ /**
4
+ * Why `kb_model mode: analyze` with the `id` of a stored requirement compares
5
+ * its ledger with the stored one: a claim_key is derived from the clause
6
+ * text, so analyzing reworded prose, or the title instead of the stored
7
+ * `semantic_text`, yields keys that none of the requirement's grounding
8
+ * facts declare. The write that follows such a receipt is refused by the
9
+ * proposition-complete check, long after the agent picked the wording. The
10
+ * receipt therefore warns at analysis time, naming the stored modeled
11
+ * clauses the analysis no longer states, the facts that ground them, and the
12
+ * clauses the analysis adds, so the agent can keep the stored wording or
13
+ * re-ground the facts before writing.
14
+ *
15
+ * Returns null when the requirement is not stored, carries no recorded
16
+ * ledger, or every stored modeled clause is still among the analyzed ones.
17
+ */
18
+ export declare function storedLedgerDriftWarning(prolog: Pick<PrologPort, "query">, id: string, text: string, receipt: SemanticAdvisorReceipt): Promise<string | null>;
19
+ //# sourceMappingURL=stored-ledger-drift.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"stored-ledger-drift.d.ts","sourceRoot":"","sources":["../../../src/operations/semantic-advisor/stored-ledger-drift.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,0CAA0C,CAAC;AAE3E,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,YAAY,CAAC;AAwCzD;;;;;;;;;;;;;;GAcG;AAEH,wBAAsB,wBAAwB,CAC5C,MAAM,EAAE,IAAI,CAAC,UAAU,EAAE,OAAO,CAAC,EACjC,EAAE,EAAE,MAAM,EACV,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,sBAAsB,GAC9B,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAkDxB"}
@@ -0,0 +1,78 @@
1
+ import { escapeAtom, parseListOfLists, parsePrologValue, } from "../../prolog/codec.js";
2
+ import { loadEntities } from "../../public/operations/discovery-entities.js";
3
+ import { SEMANTIC_INVENTORY_VERSION, isRecord, stringValue } from "./shared.js";
4
+ const LOGICAL_RELATIONSHIPS = [
5
+ "requires_property",
6
+ "requires_predicate",
7
+ "requires_rule",
8
+ ];
9
+ function atom(value) {
10
+ return stringValue(parsePrologValue(value ?? "")).replace(/^['"]|['"]$/g, "");
11
+ }
12
+ function quoted(text) {
13
+ return text ? ` ("${text}")` : "";
14
+ }
15
+ async function groundingFactsOf(prolog, id) {
16
+ const result = await prolog.query(`findall([Type,To,ClaimKey], (member(Type, [${LOGICAL_RELATIONSHIPS.join(",")}]), kb_relationship(Type, '${escapeAtom(id)}', To), ((kb_entity(To, fact, _DriftProps), memberchk(claim_key=_DriftRaw, _DriftProps), normalize_term_atom(_DriftRaw, ClaimKey)) -> true ; ClaimKey = '')), Rows)`);
17
+ if (!result.success)
18
+ return [];
19
+ return parseListOfLists(result.bindings.Rows ?? "[]").map(([type, target, claimKey]) => ({
20
+ type: atom(type),
21
+ target: atom(target),
22
+ claimKey: atom(claimKey),
23
+ }));
24
+ }
25
+ /**
26
+ * Why `kb_model mode: analyze` with the `id` of a stored requirement compares
27
+ * its ledger with the stored one: a claim_key is derived from the clause
28
+ * text, so analyzing reworded prose, or the title instead of the stored
29
+ * `semantic_text`, yields keys that none of the requirement's grounding
30
+ * facts declare. The write that follows such a receipt is refused by the
31
+ * proposition-complete check, long after the agent picked the wording. The
32
+ * receipt therefore warns at analysis time, naming the stored modeled
33
+ * clauses the analysis no longer states, the facts that ground them, and the
34
+ * clauses the analysis adds, so the agent can keep the stored wording or
35
+ * re-ground the facts before writing.
36
+ *
37
+ * Returns null when the requirement is not stored, carries no recorded
38
+ * ledger, or every stored modeled clause is still among the analyzed ones.
39
+ */
40
+ // implements REQ-kibi-proposition-complete-ingestion
41
+ export async function storedLedgerDriftWarning(prolog, id, text, receipt) {
42
+ const [stored] = await loadEntities(prolog, { id, type: "req" });
43
+ if (!stored ||
44
+ stored.semantic_inventory_version !== SEMANTIC_INVENTORY_VERSION ||
45
+ !Array.isArray(stored.semantic_inventory))
46
+ return null;
47
+ const storedInventory = stored.semantic_inventory.filter(isRecord);
48
+ const storedKeys = new Set(storedInventory.map((entry) => stringValue(entry.claim_key)));
49
+ const analyzedKeys = new Set(receipt.propositions.map(({ claim_key }) => claim_key));
50
+ const drifted = storedInventory
51
+ .filter((entry) => stringValue(entry.status) === "modeled" &&
52
+ !analyzedKeys.has(stringValue(entry.claim_key)))
53
+ .map((entry) => ({
54
+ claimKey: stringValue(entry.claim_key),
55
+ claimText: stringValue(entry.claim_text),
56
+ }));
57
+ if (drifted.length === 0)
58
+ return null;
59
+ const facts = await groundingFactsOf(prolog, id);
60
+ const groundedBy = (claimKey) => {
61
+ const matches = facts.filter((fact) => fact.claimKey === claimKey);
62
+ return matches.length > 0
63
+ ? `, grounded by ${matches.map((fact) => `${fact.target} via ${fact.type}`).join(" and ")}`
64
+ : "";
65
+ };
66
+ const missing = drifted
67
+ .map(({ claimKey, claimText }) => `'${claimKey}'${quoted(claimText)}${groundedBy(claimKey)}`)
68
+ .join("; ");
69
+ const added = receipt.propositions
70
+ .filter(({ claim_key }) => !storedKeys.has(claim_key))
71
+ .map(({ claim_key, claim_text }) => `'${claim_key}'${quoted(claim_text)}`)
72
+ .join("; ");
73
+ const storedText = stringValue(stored.semantic_text);
74
+ const cause = storedText && storedText !== text
75
+ ? `The analyzed prose differs from the stored semantic_text of ${id}.`
76
+ : "The stored clause keys were derived from different prose.";
77
+ return `Claim-key drift against the stored ledger of ${id}: stored modeled clause(s) not stated by this analysis: ${missing}.${added ? ` Clause(s) this analysis adds without a stored grounding: ${added}.` : ""} ${cause} A kb_upsert of ${id} with this ledger fails the proposition-complete claim_key check unless each affected grounding fact is first re-written with the new claim_key and claim_text (same fact id). Keep the stored semantic_text wording to keep the keys, or supersede the requirement for a change of meaning.`;
78
+ }
@@ -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"}
@@ -4,6 +4,7 @@ import { analyzeSourceChanges, assertSourceAnalysisGate, } from "../../plugins/s
4
4
  import { captureStagedSnapshot } from "../../traceability/git-change-snapshot.js";
5
5
  import { readSnapshotKnowledge } from "../../traceability/snapshot-knowledge.js";
6
6
  import { extractSymbolsFromStagedFileAsync } from "../../traceability/symbol-extract.js";
7
+ import { isTraceabilityRelationshipType } from "../symbol-granularity.js";
7
8
  import { createSemanticReviewDiagnostics, createSymbolGranularityDiagnostics, } from "./diagnostics.js";
8
9
  import { createImpactManifestLookup, readImpactManifestResults, } from "./manifest.js";
9
10
  import { normalizeSourceFile } from "./source-changes.js";
@@ -90,7 +91,12 @@ export async function analyzeChangedFileImpact(options) {
90
91
  manifestResults: activeManifestResults,
91
92
  symbolsByFile,
92
93
  }),
93
- ...createSemanticReviewDiagnostics({ symbolsByFile }),
94
+ ...createSemanticReviewDiagnostics({
95
+ symbolsByFile,
96
+ linkedSourceFiles: new Set(activeManifestResults
97
+ .filter((result) => result.relationships.some((relationship) => isTraceabilityRelationshipType(relationship.type)))
98
+ .map((result) => result.sourceFile)),
99
+ }),
94
100
  ];
95
101
  const maxDiagnostics = options.maxDiagnostics;
96
102
  const cappedDiagnostics = maxDiagnostics !== undefined && maxDiagnostics >= 0
@@ -1 +1 @@
1
- {"version":3,"file":"diagnostics.d.ts","sourceRoot":"","sources":["../../../src/public/impact/diagnostics.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,0CAA0C,CAAC;AAarF,OAAO,KAAK,EACV,iBAAiB,EACjB,gCAAgC,EAChC,mCAAmC,EACpC,MAAM,YAAY,CAAC;AAoFpB,wBAAgB,kCAAkC,CAChD,OAAO,EAAE,mCAAmC,GAC3C,oBAAoB,EAAE,CAmExB;AAED,wBAAgB,+BAA+B,CAC7C,OAAO,EAAE,gCAAgC,GACxC,oBAAoB,EAAE,CAsBxB;AAED,wBAAgB,4BAA4B,CAC1C,WAAW,EAAE,SAAS,oBAAoB,EAAE,GAC3C,OAAO,CAET;AAED,wBAAgB,6BAA6B,CAC3C,WAAW,EAAE,SAAS,iBAAiB,EAAE,GACxC,OAAO,CAIT"}
1
+ {"version":3,"file":"diagnostics.d.ts","sourceRoot":"","sources":["../../../src/public/impact/diagnostics.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,0CAA0C,CAAC;AAarF,OAAO,KAAK,EACV,iBAAiB,EACjB,gCAAgC,EAChC,mCAAmC,EACpC,MAAM,YAAY,CAAC;AA0FpB,wBAAgB,kCAAkC,CAChD,OAAO,EAAE,mCAAmC,GAC3C,oBAAoB,EAAE,CAmExB;AAED,wBAAgB,+BAA+B,CAC7C,OAAO,EAAE,gCAAgC,GACxC,oBAAoB,EAAE,CAiCxB;AAED,wBAAgB,4BAA4B,CAC1C,WAAW,EAAE,SAAS,oBAAoB,EAAE,GAC3C,OAAO,CAET;AAED,wBAAgB,6BAA6B,CAC3C,WAAW,EAAE,SAAS,iBAAiB,EAAE,GACxC,OAAO,CAIT"}
@@ -40,6 +40,9 @@ function getGranularSymbolsForSourceFile(sourceFile, options) {
40
40
  sourceSymbols: sourceSymbols.length > 0 ? sourceSymbols : (changedSymbols ?? []),
41
41
  };
42
42
  }
43
+ function hasTraceabilityLinks(symbol) {
44
+ return (symbol.relationships ?? []).some((relationship) => isTraceabilityRelationshipType(relationship.type));
45
+ }
43
46
  function formatRelationshipTargets(symbol) {
44
47
  const targets = [
45
48
  ...new Set((symbol.relationships ?? [])
@@ -116,15 +119,25 @@ export function createSemanticReviewDiagnostics(options) {
116
119
  if (!isChangedBehavioralReviewCandidate(symbol))
117
120
  continue;
118
121
  const linkedTargets = formatRelationshipTargets(symbol);
122
+ const file = symbol.location.file;
123
+ // In a file no requirement owns at all, the changed behavior has no
124
+ // recorded intent, so the review is "record it", not "compare it with
125
+ // its links". Elsewhere a coarser symbol of the file may still own it.
126
+ const unlinked = !hasTraceabilityLinks(symbol) &&
127
+ !(options.linkedSourceFiles?.has(file) ?? false);
119
128
  diagnostics.push({
120
129
  id: "symbol_semantic_review_needed",
121
130
  severity: "warning",
122
131
  blocking: false,
123
132
  category: "symbol",
124
- files: [symbol.location.file],
133
+ files: [file],
125
134
  docs: ["docs/symbol-traceability-taxonomy.md"],
126
- message: `Changed behavioral symbol ${symbol.name} needs semantic review against linked Kibi coverage: ${linkedTargets}`,
127
- suggestion: `Use kb_search and kb_query for ${symbol.location.file}, then verify that ${linkedTargets} actually describe and test the changed behavior or UI copy. Update Kibi links or requirements/tests while the edit context is fresh.`,
135
+ message: unlinked
136
+ ? `Changed behavioral symbol ${symbol.name} is in a file no requirement owns, so no recorded intent covers the behavior it has after this change`
137
+ : `Changed behavioral symbol ${symbol.name} needs semantic review against linked Kibi coverage: ${linkedTargets}`,
138
+ suggestion: unlinked
139
+ ? `If the change adds or alters behavior, record it with the change: kb_search({query:"<the behavior>", sourceLocations:[{path:"${file}"}]}) for a requirement that already describes it and link ${symbol.name} to it; if none does, kb_upsert a requirement with a scenario and link the symbol with implements. If behavior is unchanged (a refactor, renaming), say so in the final report instead.`
140
+ : `Use kb_search and kb_query for ${file}, then verify that ${linkedTargets} actually describe and test the changed behavior or UI copy. Update Kibi links or requirements/tests while the edit context is fresh.`,
128
141
  });
129
142
  }
130
143
  }
@@ -32,6 +32,8 @@ export type SymbolGranularityDiagnosticsOptions = {
32
32
  };
33
33
  export type SemanticReviewDiagnosticsOptions = {
34
34
  readonly symbolsByFile: SymbolsByFile;
35
+ /** Source files where some manifest symbol carries a traceability link. */
36
+ readonly linkedSourceFiles?: ReadonlySet<string>;
35
37
  };
36
38
  export type SymbolQualityDiagnosticsOptions = {
37
39
  readonly manifestResults: readonly ExtractionResult[];