kibi-cli 0.16.1 → 0.17.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 (117) hide show
  1. package/dist/commands/check.d.ts.map +1 -1
  2. package/dist/commands/check.js +1 -1
  3. package/dist/commands/sync/manifest.d.ts +12 -1
  4. package/dist/commands/sync/manifest.d.ts.map +1 -1
  5. package/dist/commands/sync/manifest.js +22 -5
  6. package/dist/commands/sync/persistence.d.ts.map +1 -1
  7. package/dist/commands/sync/persistence.js +18 -1
  8. package/dist/extractors/markdown.d.ts +9 -1
  9. package/dist/extractors/markdown.d.ts.map +1 -1
  10. package/dist/extractors/markdown.js +12 -0
  11. package/dist/logic/ir.d.ts +112 -0
  12. package/dist/logic/ir.d.ts.map +1 -0
  13. package/dist/logic/ir.js +729 -0
  14. package/dist/operations/modeling/logic-modeling.d.ts +22 -0
  15. package/dist/operations/modeling/logic-modeling.d.ts.map +1 -0
  16. package/dist/operations/modeling/logic-modeling.js +111 -0
  17. package/dist/operations/modeling/model-requirement.d.ts.map +1 -1
  18. package/dist/operations/modeling/model-requirement.js +56 -0
  19. package/dist/operations/modeling/predicate-applyplan.d.ts.map +1 -1
  20. package/dist/operations/modeling/predicate-applyplan.js +15 -2
  21. package/dist/operations/modeling/predicate-inference.d.ts.map +1 -1
  22. package/dist/operations/modeling/predicate-inference.js +17 -2
  23. package/dist/operations/modeling/predicate-loader.d.ts.map +1 -1
  24. package/dist/operations/modeling/predicate-loader.js +6 -0
  25. package/dist/operations/modeling/predicate-ranker.d.ts.map +1 -1
  26. package/dist/operations/modeling/predicate-ranker.js +40 -2
  27. package/dist/operations/modeling/predicate-types.d.ts +3 -0
  28. package/dist/operations/modeling/predicate-types.d.ts.map +1 -1
  29. package/dist/operations/modeling/requirement-types.d.ts +12 -0
  30. package/dist/operations/modeling/requirement-types.d.ts.map +1 -1
  31. package/dist/operations/mutation/relationships.d.ts.map +1 -1
  32. package/dist/operations/mutation/relationships.js +8 -0
  33. package/dist/operations/mutation/serialization.d.ts.map +1 -1
  34. package/dist/operations/mutation/serialization.js +3 -0
  35. package/dist/operations/mutation/strict-fact.d.ts.map +1 -1
  36. package/dist/operations/mutation/strict-fact.js +68 -3
  37. package/dist/operations/semantic-advisor/analysis-receipt.d.ts +2 -2
  38. package/dist/operations/semantic-advisor/analysis-receipt.d.ts.map +1 -1
  39. package/dist/operations/semantic-advisor/analysis-receipt.js +23 -15
  40. package/dist/operations/semantic-advisor/analyze-prose.d.ts +1 -1
  41. package/dist/operations/semantic-advisor/analyze-prose.d.ts.map +1 -1
  42. package/dist/operations/semantic-advisor/analyze-prose.js +435 -13
  43. package/dist/operations/semantic-advisor/observation-plan.d.ts.map +1 -1
  44. package/dist/operations/semantic-advisor/observation-plan.js +15 -3
  45. package/dist/operations/semantic-advisor/shared.d.ts +1 -0
  46. package/dist/operations/semantic-advisor/shared.d.ts.map +1 -1
  47. package/dist/operations/semantic-advisor/shared.js +1 -1
  48. package/dist/operations/semantic-advisor/types.d.ts +61 -1
  49. package/dist/operations/semantic-advisor/types.d.ts.map +1 -1
  50. package/dist/prolog/codec.d.ts.map +1 -1
  51. package/dist/prolog/codec.js +11 -1
  52. package/dist/prolog.d.ts.map +1 -1
  53. package/dist/prolog.js +7 -2
  54. package/dist/public/operations/catalog.d.ts +85 -6
  55. package/dist/public/operations/catalog.d.ts.map +1 -1
  56. package/dist/public/operations/index.d.ts +1 -0
  57. package/dist/public/operations/index.d.ts.map +1 -1
  58. package/dist/public/operations/index.js +1 -0
  59. package/dist/public/operations/specs/check.d.ts +2 -2
  60. package/dist/public/operations/specs/check.d.ts.map +1 -1
  61. package/dist/public/operations/specs/check.js +4 -1
  62. package/dist/public/operations/specs/modeling.d.ts +16 -0
  63. package/dist/public/operations/specs/modeling.d.ts.map +1 -1
  64. package/dist/public/operations/specs/modeling.js +16 -0
  65. package/dist/public/operations/specs/mutation-schemas.d.ts +37 -2
  66. package/dist/public/operations/specs/mutation-schemas.d.ts.map +1 -1
  67. package/dist/public/operations/specs/mutation-schemas.js +27 -0
  68. package/dist/public/operations/specs/semantic.d.ts +30 -2
  69. package/dist/public/operations/specs/semantic.d.ts.map +1 -1
  70. package/dist/public/operations/specs/semantic.js +34 -2
  71. package/dist/public/operations/specs/upsert.d.ts +37 -2
  72. package/dist/public/operations/specs/upsert.d.ts.map +1 -1
  73. package/dist/public/schemas/entity.d.ts.map +1 -1
  74. package/dist/public/schemas/entity.js +290 -0
  75. package/dist/public/schemas/relationship.d.ts.map +1 -1
  76. package/dist/public/schemas/relationship.js +1 -0
  77. package/dist/public/skills/kibi-usage/SKILL.md +35 -9
  78. package/dist/public/skills/kibi-usage/resources/fact-lanes.md +11 -1
  79. package/dist/public/skills/kibi-usage/resources/logic-ir.md +43 -0
  80. package/dist/public/skills/kibi-usage/resources/operation-access.md +19 -19
  81. package/dist/public/skills/kibi-usage/resources/ui-requirements.md +96 -0
  82. package/dist/public/skills/kibi-usage/resources/workflows.md +21 -6
  83. package/dist/public/symbol-granularity.d.ts +9 -0
  84. package/dist/public/symbol-granularity.d.ts.map +1 -1
  85. package/dist/public/symbol-granularity.js +16 -0
  86. package/dist/schemas/entity.schema.json +306 -3
  87. package/dist/traceability/staged-diagnostics.d.ts +1 -1
  88. package/dist/traceability/staged-diagnostics.d.ts.map +1 -1
  89. package/dist/traceability/staged-diagnostics.js +17 -10
  90. package/dist/traceability/staged-impact-contract.js +1 -1
  91. package/dist/traceability/staged-symbols-manifest.d.ts.map +1 -1
  92. package/dist/traceability/staged-symbols-manifest.js +28 -10
  93. package/dist/traceability/temp-kb.d.ts.map +1 -1
  94. package/dist/traceability/temp-kb.js +13 -0
  95. package/dist/types/relationships.d.ts +1 -1
  96. package/dist/types/relationships.d.ts.map +1 -1
  97. package/dist/utils/rule-registry.d.ts.map +1 -1
  98. package/dist/utils/rule-registry.js +18 -0
  99. package/dist/utils/schema-version.d.ts +1 -1
  100. package/dist/utils/schema-version.js +1 -1
  101. package/package.json +6 -2
  102. package/src/public/operations/index.ts +1 -0
  103. package/src/public/operations/specs/check.ts +4 -1
  104. package/src/public/operations/specs/modeling.ts +20 -0
  105. package/src/public/operations/specs/mutation-schemas.ts +30 -0
  106. package/src/public/operations/specs/semantic.ts +41 -2
  107. package/src/public/schemas/entity.ts +297 -1
  108. package/src/public/schemas/relationship.ts +1 -0
  109. package/src/public/skills/kibi-usage/SKILL.md +35 -9
  110. package/src/public/skills/kibi-usage/resources/fact-lanes.md +11 -1
  111. package/src/public/skills/kibi-usage/resources/logic-ir.md +43 -0
  112. package/src/public/skills/kibi-usage/resources/operation-access.md +19 -19
  113. package/src/public/skills/kibi-usage/resources/ui-requirements.md +96 -0
  114. package/src/public/skills/kibi-usage/resources/workflows.md +21 -6
  115. package/src/public/symbol-granularity.ts +25 -0
  116. package/src/schemas/entity.schema.json +306 -3
  117. package/src/schemas/relationship.schema.json +1 -0
@@ -19,15 +19,19 @@ function summary(readiness, lane) {
19
19
  return "Requirement prose appears to contain scalar, threshold, or cardinality logic that should be modeled with strict facts.";
20
20
  if (lane === "predicate")
21
21
  return "Requirement prose appears to contain relational or behavioral logic that should be modeled with ontology predicates.";
22
+ if (lane === "rule")
23
+ return "Requirement prose has a typed interpretation candidate; validate and persist it as a safe kibi.logic.v1 rule.";
22
24
  return "Requirement prose appears normative but needs review before it can participate in logic checks.";
23
25
  }
24
26
  // implements REQ-mcp-semantic-advisor-preflight
25
- export function buildAdvisorResult(payload, signals, modeled, suggestions, clauses) {
27
+ export function buildAdvisorResult(payload, signals, modeled, suggestions, clauses, propositions, interpretations, shadowAnalysis) {
26
28
  const suggestionLane = suggestions.some(({ kind }) => kind === "strict_property")
27
29
  ? "strict_property"
28
- : suggestions.some(({ kind }) => kind === "predicate")
29
- ? "predicate"
30
- : null;
30
+ : suggestions.some(({ kind }) => kind === "rule")
31
+ ? "rule"
32
+ : suggestions.some(({ kind }) => kind === "predicate")
33
+ ? "predicate"
34
+ : null;
31
35
  const candidateLane = modeled
32
36
  ? "none"
33
37
  : (suggestionLane ?? chooseLane(signals));
@@ -42,21 +46,22 @@ export function buildAdvisorResult(payload, signals, modeled, suggestions, claus
42
46
  ? ["kb_model_requirement"]
43
47
  : candidateLane === "predicate"
44
48
  ? ["kb_suggest_predicates"]
45
- : candidateLane === "observation_review"
46
- ? ["kb_model_requirement", "kb_suggest_predicates"]
47
- : [];
48
- const expectedClaimKeys = clauses
49
- .filter((clause) => clause.normative)
50
- .map((clause) => clause.claim_key);
49
+ : candidateLane === "rule"
50
+ ? ["kb_model_requirement"]
51
+ : candidateLane === "observation_review"
52
+ ? ["kb_model_requirement", "kb_suggest_predicates"]
53
+ : [];
54
+ const expectedClaimKeys = propositions
55
+ .filter(({ role }) => !["rationale", "example", "subjective"].includes(role))
56
+ .map(({ claim_key }) => claim_key);
51
57
  const rawDeclaredClaimKeys = propertiesOf(payload).logic_claims;
52
58
  const declaredClaimKeys = Array.isArray(rawDeclaredClaimKeys)
53
59
  ? rawDeclaredClaimKeys.filter((value) => typeof value === "string")
54
60
  : [];
55
61
  const missingClaimKeys = expectedClaimKeys.filter((claimKey) => !declaredClaimKeys.includes(claimKey));
56
- const unresolvedClaimKeys = suggestions
57
- .filter((suggestion) => suggestion.kind === "ambiguity_observation" ||
58
- suggestion.kind === "ontology_gap")
59
- .map((suggestion) => suggestion.claim_key);
62
+ const unresolvedClaimKeys = propositions
63
+ .filter(({ status }) => ["ambiguous", "ontology_gap", "missing"].includes(status))
64
+ .map(({ claim_key }) => claim_key);
60
65
  const coverageStatus = expectedClaimKeys.length === 0
61
66
  ? "not_applicable"
62
67
  : modeled
@@ -65,7 +70,7 @@ export function buildAdvisorResult(payload, signals, modeled, suggestions, claus
65
70
  ? "unverified"
66
71
  : "partial";
67
72
  const receipt = {
68
- version: "semantic-advisor-v1",
73
+ version: "semantic-advisor-v2",
69
74
  payload_hash: payloadHash(payload),
70
75
  logic_readiness: readiness,
71
76
  candidate_lane: candidateLane,
@@ -86,6 +91,9 @@ export function buildAdvisorResult(payload, signals, modeled, suggestions, claus
86
91
  ],
87
92
  message: "Numeric cardinality prose can mean an exact count, an upper/lower bound, named membership, or an example; model it explicitly before relying on contradiction checks.",
88
93
  })),
94
+ propositions,
95
+ interpretations,
96
+ shadow_analysis: shadowAnalysis,
89
97
  suggestions,
90
98
  clauses: clauses.map((clause) => ({
91
99
  ...clause,
@@ -1,5 +1,5 @@
1
1
  import type { SemanticAdvisorAnalysisResult, SemanticAdvisorInput } from "./types.js";
2
- export declare const SEMANTIC_ADVISOR_VERSION = "semantic-advisor-v1";
2
+ export declare const SEMANTIC_ADVISOR_VERSION = "semantic-advisor-v2";
3
3
  export { semanticClaimKey } from "./clauses.js";
4
4
  export declare function analyzeSemanticAdvisorInput(input: SemanticAdvisorInput): SemanticAdvisorAnalysisResult;
5
5
  //# sourceMappingURL=analyze-prose.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"analyze-prose.d.ts","sourceRoot":"","sources":["../../../src/operations/semantic-advisor/analyze-prose.ts"],"names":[],"mappings":"AAgBA,OAAO,KAAK,EACV,6BAA6B,EAC7B,oBAAoB,EAKrB,MAAM,YAAY,CAAC;AAEpB,eAAO,MAAM,wBAAwB,wBAAwB,CAAC;AAC9D,OAAO,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAsThD,wBAAgB,2BAA2B,CACzC,KAAK,EAAE,oBAAoB,GAC1B,6BAA6B,CAuB/B"}
1
+ {"version":3,"file":"analyze-prose.d.ts","sourceRoot":"","sources":["../../../src/operations/semantic-advisor/analyze-prose.ts"],"names":[],"mappings":"AA8BA,OAAO,KAAK,EACV,6BAA6B,EAC7B,oBAAoB,EAWrB,MAAM,YAAY,CAAC;AAEpB,eAAO,MAAM,wBAAwB,wBAAwB,CAAC;AAC9D,OAAO,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AA6zBhD,wBAAgB,2BAA2B,CACzC,KAAK,EAAE,oBAAoB,GAC1B,6BAA6B,CAuD/B"}
@@ -1,14 +1,16 @@
1
+ import { utf8Span, validateLogicIr, } from "../../logic/ir.js";
2
+ import { logicRuleFactId } from "../modeling/logic-modeling.js";
1
3
  import { buildAdvisorResult } from "./analysis-receipt.js";
2
- import { extractSemanticClauses } from "./clauses.js";
4
+ import { extractSemanticClauses, semanticClaimKey, } from "./clauses.js";
3
5
  import { observationPlan } from "./observation-plan.js";
4
6
  import { detectPredicateRules } from "./predicate-rule.js";
5
7
  import { CORE_PREDICATE_RULES } from "./predicate-rules-core.js";
6
8
  import { POLICY_PREDICATE_RULES } from "./predicate-rules-policy.js";
7
9
  import { PRODUCT_TAIL_PREDICATE_RULES } from "./predicate-rules-product-tail.js";
8
10
  import { PRODUCT_PREDICATE_RULES } from "./predicate-rules-product.js";
9
- import { isRecord, propertiesOf, statementOf, stringValue, } from "./shared.js";
11
+ import { isRecord, payloadHash, propertiesOf, relationship, sourceOf, statementOf, stringValue, } from "./shared.js";
10
12
  import { detectStrictSuggestion } from "./strict-rules.js";
11
- export const SEMANTIC_ADVISOR_VERSION = "semantic-advisor-v1";
13
+ export const SEMANTIC_ADVISOR_VERSION = "semantic-advisor-v2";
12
14
  export { semanticClaimKey } from "./clauses.js";
13
15
  const SIGNAL_PATTERNS = [
14
16
  {
@@ -65,7 +67,7 @@ function logicalGroundingSlots(payload) {
65
67
  ? payload.relationships
66
68
  : [];
67
69
  return relationships.filter((relationship) => isRecord(relationship) &&
68
- ["requires_property", "requires_predicate"].includes(stringValue(relationship.type))).length;
70
+ ["requires_property", "requires_predicate", "requires_rule"].includes(stringValue(relationship.type))).length;
69
71
  }
70
72
  function isModeled(payload, expectedClaimCount) {
71
73
  if (expectedClaimCount === 0)
@@ -79,7 +81,8 @@ function isModeled(payload, expectedClaimCount) {
79
81
  .filter(Boolean));
80
82
  return (logicalGroundingSlots(payload) >= expectedClaimCount &&
81
83
  ((types.has("constrains") && types.has("requires_property")) ||
82
- types.has("requires_predicate")));
84
+ types.has("requires_predicate") ||
85
+ types.has("requires_rule")));
83
86
  }
84
87
  function detectSignals(prose) {
85
88
  const seen = new Set();
@@ -149,7 +152,80 @@ function predicateSuggestion(payload, statement) {
149
152
  detectPredicateRules(payload, statement, PRODUCT_PREDICATE_RULES) ??
150
153
  detectPredicateRules(payload, statement, PRODUCT_TAIL_PREDICATE_RULES));
151
154
  }
152
- function modelingSuggestions(payload, modeled, clauses) {
155
+ function ruleSuggestion(payload, candidate) {
156
+ const validation = validateLogicIr(candidate.ir);
157
+ if (!validation.valid ||
158
+ !validation.normalized ||
159
+ !validation.semanticKey ||
160
+ !validation.renderedProlog)
161
+ return null;
162
+ const reqId = stringValue(payload.id);
163
+ const semanticKey = validation.semanticKey;
164
+ const schemaId = candidate.ir.ruleSchemaId ?? "FACT-RULE-SCHEMA-LOGIC-V1";
165
+ const factId = logicRuleFactId(semanticKey);
166
+ const schemaPlan = candidate.ir.ruleSchemaId
167
+ ? []
168
+ : [
169
+ {
170
+ type: "fact",
171
+ id: schemaId,
172
+ properties: {
173
+ title: "kibi.logic.v1 rule schema",
174
+ status: "active",
175
+ source: sourceOf(payload),
176
+ fact_kind: "rule_schema",
177
+ rule_name: "kibi.logic.v1",
178
+ argument_names: ["rule_ir"],
179
+ argument_types: ["logic_ir"],
180
+ aliases: ["conditional rule", "constraint", "policy rule"],
181
+ examples: [validation.renderedProlog],
182
+ },
183
+ relationships: [],
184
+ },
185
+ ];
186
+ return {
187
+ kind: "rule",
188
+ claim_key: candidate.claim_key,
189
+ claim_text: candidate.claim_text,
190
+ confidence: candidate.confidence ?? 0.75,
191
+ evidence: candidate.claim_text,
192
+ rationale: "The host supplied a typed kibi.logic.v1 interpretation. Kibi validated its safety, canonicalized it, and will persist it as data-backed rule evidence.",
193
+ suggested_next_tool: "kb_model_requirement",
194
+ rule: validation.normalized,
195
+ semantic_key: semanticKey,
196
+ rendered_prolog: validation.renderedProlog,
197
+ rejected_alternatives: ["raw_prolog", "observation_review"],
198
+ applyPlan: [
199
+ ...schemaPlan,
200
+ {
201
+ type: "fact",
202
+ id: factId,
203
+ properties: {
204
+ title: `${candidate.ir.kind} rule ${semanticKey}`,
205
+ status: "active",
206
+ source: sourceOf(payload),
207
+ fact_kind: "rule",
208
+ rule_ir: validation.normalized,
209
+ rule_hash: validation.ruleHash,
210
+ semantic_key: semanticKey,
211
+ rule_schema_id: schemaId,
212
+ rule_name: validation.normalized.ruleSchemaId ?? "kibi.logic.v1",
213
+ canonical_key: semanticKey,
214
+ tags: ["lane:logic", "semantic-advisor-suggestion"],
215
+ },
216
+ relationships: [],
217
+ },
218
+ ],
219
+ relationshipPlan: reqId
220
+ ? {
221
+ applyAfter: factId,
222
+ requiresExistingReq: reqId,
223
+ relationship: relationship(reqId, factId, "requires_rule"),
224
+ }
225
+ : null,
226
+ };
227
+ }
228
+ function modelingSuggestions(payload, modeled, clauses, interpretations) {
153
229
  if (!isRequirement(payload) || modeled)
154
230
  return [];
155
231
  const suggestions = [];
@@ -169,7 +245,7 @@ function modelingSuggestions(payload, modeled, clauses) {
169
245
  ambiguitySuggestion(payload, candidate) ??
170
246
  detectStrictSuggestion(payload, candidate);
171
247
  const suggestion = rawSuggestion
172
- ? withClauseProvenance(rawSuggestion, clause, mergedClaimKeys)
248
+ ? withClauseProvenance(rawSuggestion, clause, mergedClaimKeys, statementOf(payload))
173
249
  : clause.normative
174
250
  ? unmatchedClauseSuggestion(payload, clause, mergedClaimKeys)
175
251
  : null;
@@ -181,9 +257,27 @@ function modelingSuggestions(payload, modeled, clauses) {
181
257
  suggestions.push(suggestion);
182
258
  }
183
259
  }
260
+ for (const interpretation of interpretations ?? []) {
261
+ const clause = clauses.find((entry) => entry.claim_key === interpretation.claim_key);
262
+ if (!clause)
263
+ continue;
264
+ const suggestion = ruleSuggestion(payload, interpretation);
265
+ if (!suggestion)
266
+ continue;
267
+ const withProvenance = withClauseProvenance(suggestion, clause, mergedClaimKeys, statementOf(payload));
268
+ const key = withProvenance.kind === "rule"
269
+ ? `${withProvenance.claim_key}:rule:${withProvenance.semantic_key}`
270
+ : `${withProvenance.claim_key}:${withProvenance.kind}`;
271
+ if (!seen.has(key)) {
272
+ seen.add(key);
273
+ suggestions.push(withProvenance);
274
+ }
275
+ }
184
276
  return suggestions;
185
277
  }
186
- function withClauseProvenance(suggestion, clause, expectedClaimKeys) {
278
+ function withClauseProvenance(suggestion, clause, expectedClaimKeys, statement) {
279
+ const clauseOffset = statement.indexOf(clause.text);
280
+ const clauseSpan = utf8Span(statement || clause.text, clauseOffset >= 0 ? clauseOffset : 0, clauseOffset >= 0 ? clauseOffset + clause.text.length : clause.text.length);
187
281
  const applyPlan = suggestion.applyPlan.map((step) => {
188
282
  if (!isRecord(step))
189
283
  return step;
@@ -194,11 +288,32 @@ function withClauseProvenance(suggestion, clause, expectedClaimKeys) {
194
288
  const existing = Array.isArray(properties.logic_claims)
195
289
  ? properties.logic_claims.filter((value) => typeof value === "string")
196
290
  : [];
291
+ const inventory = Array.isArray(properties.semantic_inventory)
292
+ ? properties.semantic_inventory.filter(isRecord)
293
+ : [];
294
+ const status = suggestion.kind === "ambiguity_observation"
295
+ ? "ambiguous"
296
+ : suggestion.kind === "ontology_gap"
297
+ ? "ontology_gap"
298
+ : "modeled";
197
299
  return {
198
300
  ...step,
199
301
  properties: {
200
302
  ...properties,
201
303
  logic_claims: Array.from(new Set([...existing, ...expectedClaimKeys])),
304
+ semantic_inventory: [
305
+ ...inventory.filter((entry) => entry.claim_key !== clause.claim_key),
306
+ {
307
+ claim_key: clause.claim_key,
308
+ claim_text: clause.text,
309
+ role: clause.normative ? "normative" : "descriptive",
310
+ status,
311
+ span: clauseSpan,
312
+ reason: status === "modeled"
313
+ ? "Draft apply plan contains a typed grounding for this proposition."
314
+ : suggestion.rationale,
315
+ },
316
+ ],
202
317
  },
203
318
  };
204
319
  }
@@ -210,6 +325,8 @@ function withClauseProvenance(suggestion, clause, expectedClaimKeys) {
210
325
  ...properties,
211
326
  claim_key: clause.claim_key,
212
327
  claim_text: clause.text,
328
+ claim_span_start: clauseSpan.start,
329
+ claim_span_end: clauseSpan.end,
213
330
  },
214
331
  };
215
332
  });
@@ -225,10 +342,41 @@ function withClauseProvenance(suggestion, clause, expectedClaimKeys) {
225
342
  claimKey: clause.claim_key,
226
343
  claimText: clause.text,
227
344
  logicClaims: expectedClaimKeys,
345
+ semanticInventory: [
346
+ {
347
+ claim_key: clause.claim_key,
348
+ claim_text: clause.text,
349
+ role: clause.normative ? "normative" : "descriptive",
350
+ status: "modeled",
351
+ span: clauseSpan,
352
+ reason: "The returned typed fact grounds this proposition.",
353
+ },
354
+ ],
228
355
  instructions: "Create the predicate fact, merge the returned logicClaims into the requirement logic_claims manifest, then add requires_predicate without overwriting other requirement metadata.",
229
356
  },
230
357
  }
231
358
  : {}),
359
+ ...(suggestion.kind === "rule" && suggestion.relationshipPlan !== null
360
+ ? {
361
+ relationshipPlan: {
362
+ ...suggestion.relationshipPlan,
363
+ claimKey: clause.claim_key,
364
+ claimText: clause.text,
365
+ logicClaims: expectedClaimKeys,
366
+ semanticInventory: [
367
+ {
368
+ claim_key: clause.claim_key,
369
+ claim_text: clause.text,
370
+ role: clause.normative ? "normative" : "descriptive",
371
+ status: "modeled",
372
+ span: clauseSpan,
373
+ reason: "The returned typed rule grounds this proposition.",
374
+ },
375
+ ],
376
+ instructions: "Create the rule_schema and rule facts, merge the returned logicClaims into the requirement logic_claims manifest, then add requires_rule without overwriting other requirement metadata.",
377
+ },
378
+ }
379
+ : {}),
232
380
  };
233
381
  }
234
382
  function unmatchedClauseSuggestion(payload, clause, expectedClaimKeys) {
@@ -246,14 +394,281 @@ function unmatchedClauseSuggestion(payload, clause, expectedClaimKeys) {
246
394
  "review:ontology-gap",
247
395
  "needs_schema_extension",
248
396
  ]),
249
- }, clause, expectedClaimKeys);
397
+ }, clause, expectedClaimKeys, statementOf(payload));
398
+ }
399
+ function propositionRole(statement, normative) {
400
+ if (/\b(?:for example|e\.g\.|such as|illustrative)\b/i.test(statement))
401
+ return "example";
402
+ if (/\b(?:because|so that|in order to| rationale|therefore)\b/i.test(statement))
403
+ return "rationale";
404
+ if (/\b(?:feel|comfortable|looks complete|seems complete|subjective|prefer)\b/i.test(statement))
405
+ return "subjective";
406
+ if (/\b(?:means|defined as|refers to|is called)\b/i.test(statement))
407
+ return "definition";
408
+ if (/^\s*(?:if|when|whenever|provided that|only if)\b/i.test(statement))
409
+ return "condition";
410
+ if (/\b(?:unless|except|exempt|apart from)\b/i.test(statement))
411
+ return "exception";
412
+ if (normative)
413
+ return "normative";
414
+ return "descriptive";
415
+ }
416
+ function shadowAnalysis(text, interpretations) {
417
+ const valid = interpretations.filter((interpretation) => interpretation.valid);
418
+ const models = valid.flatMap(({ normalized_ir: ir }) => (ir ? [ir] : []));
419
+ const containsExpressionKind = (kind) => {
420
+ const visit = (value) => {
421
+ if (!isRecord(value))
422
+ return false;
423
+ if (value.kind === kind)
424
+ return true;
425
+ return Object.values(value).some((child) => Array.isArray(child) ? child.some(visit) : visit(child));
426
+ };
427
+ return models.some((model) => visit(model));
428
+ };
429
+ const hasModality = (modalities) => models.some((model) => modalities.includes(model.modality));
430
+ const hasNegativeAtom = (() => {
431
+ const visit = (value) => {
432
+ if (!isRecord(value))
433
+ return false;
434
+ if (value.kind === "not" || value.polarity === "negative")
435
+ return true;
436
+ return Object.values(value).some((child) => Array.isArray(child) ? child.some(visit) : visit(child));
437
+ };
438
+ return models.some((model) => visit(model));
439
+ })();
440
+ const cues = [
441
+ {
442
+ kind: "modal",
443
+ pattern: /\b(?:must|shall|should|may|can)\b/i,
444
+ evidence: "deontic modal",
445
+ represented: models.length > 0,
446
+ },
447
+ {
448
+ kind: "polarity",
449
+ pattern: /\b(?:assert|deny|forbid|allowed|permitted|prohibited)\b/i,
450
+ evidence: "polarity cue",
451
+ represented: hasModality(["deny", "forbid", "permit"]) || hasNegativeAtom,
452
+ },
453
+ {
454
+ kind: "passive_voice",
455
+ pattern: /\b(?:is|are|be|was|were)\s+[a-z]+ed\b/i,
456
+ evidence: "passive construction",
457
+ represented: false,
458
+ },
459
+ {
460
+ kind: "nominalization",
461
+ pattern: /\b[a-z]+(?:tion|ment|ity|ance|ence)\b/i,
462
+ evidence: "nominalized domain term",
463
+ represented: false,
464
+ },
465
+ {
466
+ kind: "conditional",
467
+ pattern: /\b(?:if|when|whenever|provided that)\b/i,
468
+ evidence: "conditional",
469
+ represented: models.some(({ kind, body }) => kind === "rule" && body !== undefined),
470
+ },
471
+ {
472
+ kind: "causal",
473
+ pattern: /\b(?:because|so that|therefore|as a result)\b/i,
474
+ evidence: "causal relation",
475
+ represented: false,
476
+ },
477
+ {
478
+ kind: "prerequisite",
479
+ pattern: /\b(?:before|only after|requires?\s+.+?\s+before)\b/i,
480
+ evidence: "prerequisite/order relation",
481
+ represented: containsExpressionKind("temporal"),
482
+ },
483
+ {
484
+ kind: "exception",
485
+ pattern: /\b(?:unless|except|exempt|apart from)\b/i,
486
+ evidence: "exception",
487
+ represented: models.some(({ exceptions }) => (exceptions?.length ?? 0) > 0) ||
488
+ containsExpressionKind("not"),
489
+ },
490
+ {
491
+ kind: "temporal",
492
+ pattern: /\b(?:before|after|during|until|within|expires?|retained for)\b/i,
493
+ evidence: "temporal qualifier",
494
+ represented: containsExpressionKind("temporal") ||
495
+ models.some(({ validFrom, validTo }) => validFrom !== undefined || validTo !== undefined) ||
496
+ containsExpressionKind("duration"),
497
+ },
498
+ {
499
+ kind: "quantifier",
500
+ pattern: /\b(?:each|every|all|any|some|only|at least|at most|exactly|no more than)\b/i,
501
+ evidence: "quantifier/cardinality",
502
+ represented: models.some(({ variables }) => (variables?.length ?? 0) > 0) ||
503
+ containsExpressionKind("count") ||
504
+ containsExpressionKind("compare") ||
505
+ containsExpressionKind("all") ||
506
+ containsExpressionKind("any"),
507
+ },
508
+ {
509
+ kind: "numeric",
510
+ pattern: /\b\d+(?:\.\d+)?\b|\b(?:zero|one|two|three|four|five|six|seven|eight|nine|ten)\b/i,
511
+ evidence: "numeric or cardinality value",
512
+ represented: containsExpressionKind("number") ||
513
+ containsExpressionKind("count") ||
514
+ containsExpressionKind("compare"),
515
+ },
516
+ {
517
+ kind: "negation_scope",
518
+ pattern: /\b(?:not|never|no|without|cannot|can't|must not)\b/i,
519
+ evidence: "negation scope",
520
+ represented: hasModality(["deny", "forbid"]) || hasNegativeAtom,
521
+ },
522
+ {
523
+ kind: "directionality",
524
+ pattern: /\b(?:only if|if and only if|unless|when)\b/i,
525
+ evidence: "condition direction",
526
+ represented: models.some(({ kind, body }) => kind === "rule" && body !== undefined),
527
+ },
528
+ ];
529
+ return cues
530
+ .filter((cue) => cue.pattern.test(text))
531
+ .map(({ kind, evidence, represented }) => ({
532
+ kind,
533
+ evidence,
534
+ represented,
535
+ }));
536
+ }
537
+ function propositionSpan(text, clause, cursor) {
538
+ const exact = text.indexOf(clause.text, cursor);
539
+ const start = exact >= 0 ? exact : cursor;
540
+ const end = exact >= 0
541
+ ? exact + clause.text.length
542
+ : Math.min(text.length, start + clause.text.length);
543
+ return { span: utf8Span(text, start, end), next: Math.max(cursor, end) };
544
+ }
545
+ function interpretationResults(interpretations, clauses, statement) {
546
+ if (interpretations === undefined)
547
+ return { results: [], warnings: [] };
548
+ const known = new Set(clauses.map((clause) => clause.claim_key));
549
+ const semanticKeys = new Map();
550
+ const warnings = [];
551
+ const results = interpretations.slice(0, 3).map((candidate) => {
552
+ const confidence = Math.max(0, Math.min(1, candidate.confidence ?? 0.5));
553
+ const validation = validateLogicIr(candidate.ir);
554
+ const errors = [...validation.errors];
555
+ const clause = clauses.find((entry) => entry.claim_key === candidate.claim_key);
556
+ if (!known.has(candidate.claim_key))
557
+ errors.push(`claim_key ${candidate.claim_key} is not present in the advisor proposition inventory`);
558
+ if (clause &&
559
+ candidate.claim_text !== clause.text &&
560
+ semanticClaimKey(candidate.claim_text) !== clause.claim_key)
561
+ errors.push(`claim_text for ${candidate.claim_key} must match the proposition text (apart from normalized punctuation)`);
562
+ if (candidate.span &&
563
+ (candidate.span.start < 0 || candidate.span.end < candidate.span.start))
564
+ errors.push("interpretation span is invalid");
565
+ if (clause && candidate.span) {
566
+ const candidateStart = statement.indexOf(candidate.claim_text);
567
+ const clauseStart = statement.indexOf(clause.text);
568
+ const sourceStart = candidateStart >= 0 ? candidateStart : clauseStart;
569
+ const sourceEnd = sourceStart >= 0
570
+ ? sourceStart +
571
+ (candidateStart >= 0
572
+ ? candidate.claim_text.length
573
+ : clause.text.length)
574
+ : 0;
575
+ const expectedSpan = utf8Span(statement, Math.max(0, sourceStart), Math.max(0, sourceEnd));
576
+ if (candidate.span.start !== expectedSpan.start ||
577
+ candidate.span.end !== expectedSpan.end)
578
+ errors.push(`interpretation span for ${candidate.claim_key} does not match the exact UTF-8 proposition span`);
579
+ }
580
+ const semanticKey = validation.semanticKey;
581
+ if (semanticKey !== undefined) {
582
+ const prior = semanticKeys.get(candidate.claim_key);
583
+ if (prior !== undefined && prior !== semanticKey)
584
+ warnings.push(`Claim ${candidate.claim_key} has materially different interpretations; it remains unresolved until one is selected.`);
585
+ semanticKeys.set(candidate.claim_key, semanticKey);
586
+ }
587
+ return {
588
+ claim_key: candidate.claim_key,
589
+ ...(semanticKey ? { semantic_key: semanticKey } : {}),
590
+ ...(validation.normalized
591
+ ? { normalized_ir: validation.normalized }
592
+ : {}),
593
+ valid: errors.length === 0 && validation.valid,
594
+ confidence,
595
+ errors,
596
+ warnings: validation.warnings,
597
+ ...(validation.renderedProlog
598
+ ? { rendered_prolog: validation.renderedProlog }
599
+ : {}),
600
+ };
601
+ });
602
+ if (interpretations.length > 3)
603
+ warnings.push("Only the first three interpretations are considered; submit materially distinct alternatives explicitly and resolve them before writing.");
604
+ return { results, warnings };
605
+ }
606
+ function propositionInventory(statement, clauses, suggestions, interpretations, inputPayloadHash) {
607
+ const interpretationKeys = new Map();
608
+ for (const interpretation of interpretations) {
609
+ if (!interpretation.valid || !interpretation.semantic_key)
610
+ continue;
611
+ const keys = interpretationKeys.get(interpretation.claim_key) ?? new Set();
612
+ keys.add(interpretation.semantic_key);
613
+ interpretationKeys.set(interpretation.claim_key, keys);
614
+ }
615
+ let cursor = 0;
616
+ return clauses.map((clause) => {
617
+ const located = propositionSpan(statement, clause, cursor);
618
+ cursor = located.next;
619
+ const role = propositionRole(clause.text, clause.normative);
620
+ const suggestion = suggestions.find((candidate) => candidate.claim_key === clause.claim_key);
621
+ let status;
622
+ let reason;
623
+ const keys = interpretationKeys.get(clause.claim_key);
624
+ const semanticKey = keys?.size === 1 ? [...keys][0] : undefined;
625
+ if (keys && keys.size > 1) {
626
+ status = "ambiguous";
627
+ reason =
628
+ "Materially different valid interpretations were submitted; confidence cannot choose between them.";
629
+ }
630
+ else if (semanticKey) {
631
+ status = "modeled";
632
+ }
633
+ else if (role === "rationale" ||
634
+ role === "example" ||
635
+ role === "subjective") {
636
+ status = "nonlogical";
637
+ reason =
638
+ "Prose is retained for human context but does not assert a verifiable domain proposition.";
639
+ }
640
+ else if (suggestion?.kind === "ambiguity_observation") {
641
+ status = "ambiguous";
642
+ reason = suggestion.rationale;
643
+ }
644
+ else if (suggestion?.kind === "ontology_gap") {
645
+ status = "ontology_gap";
646
+ reason = suggestion.rationale;
647
+ }
648
+ else {
649
+ status = "missing";
650
+ reason =
651
+ "No accepted typed interpretation grounds this assertive proposition.";
652
+ }
653
+ return {
654
+ claim_key: clause.claim_key,
655
+ claim_text: clause.text,
656
+ role,
657
+ status,
658
+ span: located.span,
659
+ payload_hash: inputPayloadHash,
660
+ ...(semanticKey ? { semantic_key: semanticKey } : {}),
661
+ ...(reason ? { reason } : {}),
662
+ };
663
+ });
250
664
  }
251
665
  export function analyzeSemanticAdvisorInput(input) {
252
666
  const payload = input.payload;
253
667
  const requirement = isRequirement(payload);
668
+ const statement = statementOf(payload) || proseOf(payload);
254
669
  const signals = requirement ? detectSignals(proseOf(payload)) : [];
255
- const clauses = requirement
256
- ? extractSemanticClauses(statementOf(payload), input.clauses)
670
+ const clauses = statement
671
+ ? extractSemanticClauses(statement, input.clauses)
257
672
  : [];
258
673
  const rawDeclaredClaims = propertiesOf(payload).logic_claims;
259
674
  const declaredClaims = Array.isArray(rawDeclaredClaims)
@@ -266,6 +681,13 @@ export function analyzeSemanticAdvisorInput(input) {
266
681
  isModeled(payload, expectedClaims.length) &&
267
682
  expectedClaims.length > 0 &&
268
683
  expectedClaims.every((claimKey) => declaredClaims.includes(claimKey));
269
- const suggestions = modelingSuggestions(payload, modeled, clauses);
270
- return buildAdvisorResult(payload, signals, modeled, suggestions, clauses);
684
+ const suggestions = modelingSuggestions(payload, modeled, clauses, input.interpretations);
685
+ const interpreted = interpretationResults(input.interpretations, clauses, statement);
686
+ const propositions = propositionInventory(statement, clauses, suggestions, interpreted.results, payloadHash(payload));
687
+ const shadow = shadowAnalysis(statement, interpreted.results);
688
+ const result = buildAdvisorResult(payload, signals, modeled, suggestions, clauses, propositions, interpreted.results, shadow);
689
+ return {
690
+ receipt: result.receipt,
691
+ warnings: [...result.warnings, ...interpreted.warnings],
692
+ };
271
693
  }
@@ -1 +1 @@
1
- {"version":3,"file":"observation-plan.d.ts","sourceRoot":"","sources":["../../../src/operations/semantic-advisor/observation-plan.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,KAAK,OAAO,EAKb,MAAM,aAAa,CAAC;AAGrB,wBAAgB,eAAe,CAC7B,OAAO,EAAE,OAAO,EAChB,KAAK,EAAE,MAAM,EACb,IAAI,EAAE,SAAS,MAAM,EAAE,GACtB,SAAS,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,EAAE,CAgB9C"}
1
+ {"version":3,"file":"observation-plan.d.ts","sourceRoot":"","sources":["../../../src/operations/semantic-advisor/observation-plan.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,KAAK,OAAO,EAMb,MAAM,aAAa,CAAC;AAGrB,wBAAgB,eAAe,CAC7B,OAAO,EAAE,OAAO,EAChB,KAAK,EAAE,MAAM,EACb,IAAI,EAAE,SAAS,MAAM,EAAE,GACtB,SAAS,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,EAAE,CA6B9C"}
@@ -1,10 +1,20 @@
1
- import { shortHash, sourceOf, statementOf, stringValue, } from "./shared.js";
1
+ import { relationship, shortHash, sourceOf, statementOf, stringValue, } from "./shared.js";
2
2
  // implements REQ-mcp-semantic-advisor-preflight
3
3
  export function observationPlan(payload, title, tags) {
4
+ const factId = `FACT-OBS-${shortHash(`${stringValue(payload.id)}.${title}.${statementOf(payload)}`)}`;
5
+ const target = tags.includes("review:ambiguity")
6
+ ? "review:ambiguous-claim"
7
+ : tags.includes("review:keyword-false-positive")
8
+ ? "review:keyword-false-positive"
9
+ : tags.includes("review:ontology-gap")
10
+ ? "review:ontology-gap"
11
+ : tags.includes("review:nonlogical")
12
+ ? "review:nonlogical"
13
+ : undefined;
4
14
  return [
5
15
  {
6
16
  type: "fact",
7
- id: `FACT-OBS-${shortHash(`${stringValue(payload.id)}.${title}.${statementOf(payload)}`)}`,
17
+ id: factId,
8
18
  properties: {
9
19
  title,
10
20
  status: "active",
@@ -13,7 +23,9 @@ export function observationPlan(payload, title, tags) {
13
23
  text_ref: statementOf(payload),
14
24
  tags,
15
25
  },
16
- relationships: [],
26
+ relationships: target === undefined
27
+ ? []
28
+ : [relationship(factId, target, "relates_to")],
17
29
  },
18
30
  ];
19
31
  }