kibi-cli 2.2.0 → 2.3.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 (183) hide show
  1. package/dist/cli-operation-loader.d.ts.map +1 -1
  2. package/dist/cli-operation-loader.js +3 -0
  3. package/dist/cli-operation-metadata.d.ts +4 -0
  4. package/dist/cli-operation-metadata.d.ts.map +1 -1
  5. package/dist/cli-operation-metadata.js +5 -0
  6. package/dist/cli-register-reporting.d.ts.map +1 -1
  7. package/dist/cli-register-reporting.js +4 -0
  8. package/dist/commands/check-diff.d.ts +5 -0
  9. package/dist/commands/check-diff.d.ts.map +1 -0
  10. package/dist/commands/check-diff.js +62 -0
  11. package/dist/commands/check-generated.d.ts.map +1 -1
  12. package/dist/commands/check-generated.js +41 -18
  13. package/dist/commands/check.d.ts.map +1 -1
  14. package/dist/commands/check.js +210 -73
  15. package/dist/commands/doctor.d.ts.map +1 -1
  16. package/dist/commands/doctor.js +63 -29
  17. package/dist/commands/github-init.d.ts.map +1 -1
  18. package/dist/commands/github-init.js +9 -2
  19. package/dist/commands/sync/manifest.d.ts.map +1 -1
  20. package/dist/commands/sync/manifest.js +3 -1
  21. package/dist/engine.d.ts +15 -1
  22. package/dist/engine.d.ts.map +1 -1
  23. package/dist/engine.js +252 -52
  24. package/dist/extractors/symbols-coordinator.d.ts +7 -0
  25. package/dist/extractors/symbols-coordinator.d.ts.map +1 -1
  26. package/dist/extractors/symbols-coordinator.js +75 -39
  27. package/dist/operations/modeling/predicate-inference-1.js +1 -1
  28. package/dist/operations/mutation/source-authoring.d.ts.map +1 -1
  29. package/dist/operations/mutation/source-authoring.js +175 -53
  30. package/dist/operations/mutation/upsert.d.ts.map +1 -1
  31. package/dist/operations/mutation/upsert.js +5 -7
  32. package/dist/operations/planning/apply-plan.d.ts.map +1 -1
  33. package/dist/operations/planning/apply-plan.js +5 -9
  34. package/dist/operations/proof/ingest-proof.d.ts.map +1 -1
  35. package/dist/operations/proof/ingest-proof.js +1 -0
  36. package/dist/operations/semantic-advisor/analysis-receipt.d.ts.map +1 -1
  37. package/dist/operations/semantic-advisor/analysis-receipt.js +7 -1
  38. package/dist/operations/semantic-advisor/analyze-prose.d.ts.map +1 -1
  39. package/dist/operations/semantic-advisor/analyze-prose.js +26 -4
  40. package/dist/operations/semantic-advisor/clauses.d.ts +1 -0
  41. package/dist/operations/semantic-advisor/clauses.d.ts.map +1 -1
  42. package/dist/operations/semantic-advisor/clauses.js +7 -1
  43. package/dist/performance-trace.d.ts +17 -0
  44. package/dist/performance-trace.d.ts.map +1 -0
  45. package/dist/performance-trace.js +134 -0
  46. package/dist/plugins/approved-source-analyzers.d.ts +13 -0
  47. package/dist/plugins/approved-source-analyzers.d.ts.map +1 -0
  48. package/dist/plugins/approved-source-analyzers.generated.d.ts +71 -0
  49. package/dist/plugins/approved-source-analyzers.generated.d.ts.map +1 -0
  50. package/dist/plugins/approved-source-analyzers.generated.js +77 -0
  51. package/dist/plugins/approved-source-analyzers.js +3 -0
  52. package/dist/plugins/maintenance-source-analysis.d.ts +14 -0
  53. package/dist/plugins/maintenance-source-analysis.d.ts.map +1 -0
  54. package/dist/plugins/maintenance-source-analysis.js +191 -0
  55. package/dist/plugins/registry.d.ts +2 -1
  56. package/dist/plugins/registry.d.ts.map +1 -1
  57. package/dist/plugins/registry.js +6 -1
  58. package/dist/plugins/source-analysis-service.d.ts +36 -1
  59. package/dist/plugins/source-analysis-service.d.ts.map +1 -1
  60. package/dist/plugins/source-analysis-service.js +184 -27
  61. package/dist/plugins/source-change-analysis.d.ts +15 -0
  62. package/dist/plugins/source-change-analysis.d.ts.map +1 -0
  63. package/dist/plugins/source-change-analysis.js +54 -0
  64. package/dist/plugins/source-classification.d.ts +41 -0
  65. package/dist/plugins/source-classification.d.ts.map +1 -0
  66. package/dist/plugins/source-classification.js +490 -0
  67. package/dist/prolog/swipl-resolver.d.ts +88 -0
  68. package/dist/prolog/swipl-resolver.d.ts.map +1 -0
  69. package/dist/prolog/swipl-resolver.js +446 -0
  70. package/dist/prolog.d.ts +15 -1
  71. package/dist/prolog.d.ts.map +1 -1
  72. package/dist/prolog.js +67 -6
  73. package/dist/public/impact/analyzer.d.ts.map +1 -1
  74. package/dist/public/impact/analyzer.js +56 -5
  75. package/dist/public/impact/diagnostics.d.ts.map +1 -1
  76. package/dist/public/impact/diagnostics.js +3 -1
  77. package/dist/public/impact/manifest.d.ts.map +1 -1
  78. package/dist/public/impact/manifest.js +15 -1
  79. package/dist/public/impact/source-changes.d.ts.map +1 -1
  80. package/dist/public/impact/source-changes.js +35 -27
  81. package/dist/public/impact/types.d.ts +2 -0
  82. package/dist/public/impact/types.d.ts.map +1 -1
  83. package/dist/public/operations/catalog.d.ts +44 -0
  84. package/dist/public/operations/catalog.d.ts.map +1 -1
  85. package/dist/public/operations/catalog.js +2 -0
  86. package/dist/public/operations/contracts.d.ts.map +1 -1
  87. package/dist/public/operations/contracts.js +309 -8
  88. package/dist/public/operations/impact-review-preparation.d.ts +14 -0
  89. package/dist/public/operations/impact-review-preparation.d.ts.map +1 -0
  90. package/dist/public/operations/impact-review-preparation.js +379 -0
  91. package/dist/public/operations/index.d.ts +2 -0
  92. package/dist/public/operations/index.d.ts.map +1 -1
  93. package/dist/public/operations/index.js +2 -0
  94. package/dist/public/operations/node-ports.d.ts.map +1 -1
  95. package/dist/public/operations/node-ports.js +1 -35
  96. package/dist/public/operations/proof-receipt-projection.d.ts +5 -0
  97. package/dist/public/operations/proof-receipt-projection.d.ts.map +1 -0
  98. package/dist/public/operations/proof-receipt-projection.js +36 -0
  99. package/dist/public/operations/specs/impact-review-preparation.d.ts +47 -0
  100. package/dist/public/operations/specs/impact-review-preparation.d.ts.map +1 -0
  101. package/dist/public/operations/specs/impact-review-preparation.js +43 -0
  102. package/dist/public/operations/types.d.ts +1 -1
  103. package/dist/public/operations/types.d.ts.map +1 -1
  104. package/dist/public/skills/kibi-usage/resources/operation-access.md +1 -0
  105. package/dist/traceability/analysis-gate.d.ts +39 -0
  106. package/dist/traceability/analysis-gate.d.ts.map +1 -0
  107. package/dist/traceability/analysis-gate.js +51 -0
  108. package/dist/traceability/git-change-snapshot.d.ts +27 -0
  109. package/dist/traceability/git-change-snapshot.d.ts.map +1 -0
  110. package/dist/traceability/git-change-snapshot.js +411 -0
  111. package/dist/traceability/git-staged.d.ts +5 -1
  112. package/dist/traceability/git-staged.d.ts.map +1 -1
  113. package/dist/traceability/git-staged.js +16 -11
  114. package/dist/traceability/impact-evaluator.d.ts +54 -0
  115. package/dist/traceability/impact-evaluator.d.ts.map +1 -0
  116. package/dist/traceability/impact-evaluator.js +270 -0
  117. package/dist/traceability/impact-git-tree.d.ts +12 -0
  118. package/dist/traceability/impact-git-tree.d.ts.map +1 -0
  119. package/dist/traceability/impact-git-tree.js +46 -0
  120. package/dist/traceability/impact-knowledge.d.ts +16 -0
  121. package/dist/traceability/impact-knowledge.d.ts.map +1 -0
  122. package/dist/traceability/impact-knowledge.js +165 -0
  123. package/dist/traceability/impact-policy.d.ts +15 -0
  124. package/dist/traceability/impact-policy.d.ts.map +1 -0
  125. package/dist/traceability/impact-policy.js +48 -0
  126. package/dist/traceability/impact-policy.v1.schema.json +48 -0
  127. package/dist/traceability/impact-pr-diff.d.ts +5 -0
  128. package/dist/traceability/impact-pr-diff.d.ts.map +1 -0
  129. package/dist/traceability/impact-pr-diff.js +83 -0
  130. package/dist/traceability/impact-record-validation.d.ts +9 -0
  131. package/dist/traceability/impact-record-validation.d.ts.map +1 -0
  132. package/dist/traceability/impact-record-validation.js +113 -0
  133. package/dist/traceability/impact-review.d.ts +148 -0
  134. package/dist/traceability/impact-review.d.ts.map +1 -0
  135. package/dist/traceability/impact-review.js +494 -0
  136. package/dist/traceability/impact-review.schema.json +365 -0
  137. package/dist/traceability/impact-side-evidence.d.ts +13 -0
  138. package/dist/traceability/impact-side-evidence.d.ts.map +1 -0
  139. package/dist/traceability/impact-side-evidence.js +240 -0
  140. package/dist/traceability/reviewed-coordinate-refresh.d.ts +10 -0
  141. package/dist/traceability/reviewed-coordinate-refresh.d.ts.map +1 -0
  142. package/dist/traceability/reviewed-coordinate-refresh.js +49 -0
  143. package/dist/traceability/snapshot-knowledge.d.ts +4 -0
  144. package/dist/traceability/snapshot-knowledge.d.ts.map +1 -0
  145. package/dist/traceability/snapshot-knowledge.js +98 -0
  146. package/dist/traceability/staged-file-coverage.d.ts +23 -0
  147. package/dist/traceability/staged-file-coverage.d.ts.map +1 -1
  148. package/dist/traceability/staged-impact-contract.d.ts.map +1 -1
  149. package/dist/traceability/staged-impact-contract.js +2 -16
  150. package/dist/traceability/staged-symbols-manifest.d.ts +4 -0
  151. package/dist/traceability/staged-symbols-manifest.d.ts.map +1 -1
  152. package/dist/traceability/staged-symbols-manifest.js +17 -6
  153. package/dist/traceability/symbol-extract.d.ts +3 -5
  154. package/dist/traceability/symbol-extract.d.ts.map +1 -1
  155. package/dist/traceability/symbol-extract.js +49 -34
  156. package/dist/traceability/temp-kb.d.ts +1 -1
  157. package/dist/traceability/temp-kb.d.ts.map +1 -1
  158. package/dist/traceability/temp-kb.js +49 -13
  159. package/dist/traceability/trusted-pr-event.d.ts +13 -0
  160. package/dist/traceability/trusted-pr-event.d.ts.map +1 -0
  161. package/dist/traceability/trusted-pr-event.js +105 -0
  162. package/dist/utils/canonical-path.d.ts +3 -0
  163. package/dist/utils/canonical-path.d.ts.map +1 -0
  164. package/dist/utils/canonical-path.js +33 -0
  165. package/package.json +14 -5
  166. package/src/public/impact/analyzer.ts +88 -5
  167. package/src/public/impact/diagnostics.ts +7 -5
  168. package/src/public/impact/manifest.ts +14 -1
  169. package/src/public/impact/source-changes.ts +39 -34
  170. package/src/public/impact/types.ts +5 -0
  171. package/src/public/operations/catalog.ts +2 -0
  172. package/src/public/operations/contracts.ts +385 -9
  173. package/src/public/operations/impact-review-preparation.ts +548 -0
  174. package/src/public/operations/index.ts +2 -0
  175. package/src/public/operations/node-ports.ts +1 -39
  176. package/src/public/operations/proof-receipt-projection.ts +39 -0
  177. package/src/public/operations/specs/impact-review-preparation.ts +46 -0
  178. package/src/public/operations/types.ts +1 -0
  179. package/src/public/skills/kibi-usage/resources/operation-access.md +1 -0
  180. package/src/traceability/impact-policy.v1.schema.json +48 -0
  181. package/src/traceability/impact-review.schema.json +365 -0
  182. package/templates/github/kibi-badge.yml +0 -4
  183. package/templates/github/kibi-report.yml +0 -4
@@ -17,42 +17,10 @@
17
17
  */
18
18
  import * as fs from "node:fs";
19
19
  import * as path from "node:path";
20
+ import { createMaintenanceSourceAnalysisService } from "../plugins/maintenance-source-analysis.js";
20
21
  import { createSourceAnalysisService, } from "../plugins/source-analysis-service.js";
22
+ import { extensionLanguage, isTsJsSourcePath, } from "../plugins/source-classification.js";
21
23
  import { createTsMorphSourceAnalysisProvider, enrichSymbolCoordinatesWithTsMorph, } from "./symbols-ts.js";
22
- const TS_JS_EXTENSIONS = new Set([
23
- ".ts",
24
- ".tsx",
25
- ".js",
26
- ".jsx",
27
- ".mts",
28
- ".cts",
29
- ".mjs",
30
- ".cjs",
31
- ]);
32
- const SOURCE_LANGUAGE_EXTENSIONS = {
33
- ".c": "c",
34
- ".cc": "cpp",
35
- ".cjs": "javascript",
36
- ".cpp": "cpp",
37
- ".cs": "csharp",
38
- ".cts": "typescript",
39
- ".go": "go",
40
- ".h": "c",
41
- ".hpp": "cpp",
42
- ".java": "java",
43
- ".js": "javascript",
44
- ".jsx": "javascript",
45
- ".kt": "kotlin",
46
- ".mjs": "javascript",
47
- ".mts": "typescript",
48
- ".php": "php",
49
- ".py": "python",
50
- ".rb": "ruby",
51
- ".rs": "rust",
52
- ".swift": "swift",
53
- ".ts": "typescript",
54
- ".tsx": "typescript",
55
- };
56
24
  const DEFAULT_SOURCE_ANALYSIS_PROVIDERS = [
57
25
  createTsMorphSourceAnalysisProvider(),
58
26
  ];
@@ -114,10 +82,40 @@ export async function analyzeSourceTextWithRegistry(filePath, content, registry)
114
82
  return analyzeSourceText(filePath, content);
115
83
  }
116
84
  }
85
+ function isCoordinateOnlyDecoratorPartial(analysis) {
86
+ if (analysis.status !== "partial" ||
87
+ analysis.language !== "python" ||
88
+ analysis.module.analysisMode !== "parser" ||
89
+ analysis.providerId !== "kibi-plugin-treesitter.tree-sitter.v2" ||
90
+ analysis.stamp?.pluginId !== "kibi-plugin-treesitter" ||
91
+ analysis.diagnostics.length === 0 ||
92
+ analysis.uncoveredRanges.length !== analysis.diagnostics.length)
93
+ return false;
94
+ return (analysis.diagnostics.every((diagnostic) => {
95
+ if (diagnostic.code !== "TREESITTER_DECORATOR_EXPANSION_UNAVAILABLE" ||
96
+ diagnostic.range === undefined)
97
+ return false;
98
+ const range = diagnostic.range;
99
+ return analysis.uncoveredRanges.some((uncovered) => uncovered.reason === "decorator-may-alter-or-create-declarations" &&
100
+ uncovered.startLine === range.startLine &&
101
+ uncovered.startColumn === range.startColumn &&
102
+ uncovered.endLine === range.endLine &&
103
+ uncovered.endColumn === range.endColumn);
104
+ }) &&
105
+ analysis.uncoveredRanges.every((uncovered) => uncovered.reason === "decorator-may-alter-or-create-declarations" &&
106
+ analysis.diagnostics.some(({ range }) => range !== undefined &&
107
+ uncovered.startLine === range.startLine &&
108
+ uncovered.startColumn === range.startColumn &&
109
+ uncovered.endLine === range.endLine &&
110
+ uncovered.endColumn === range.endColumn)));
111
+ }
117
112
  export async function enrichSymbolCoordinates(entries, workspaceRoot, deps) {
118
113
  // implements REQ-vscode-traceability
119
114
  const enrichTsCoordinates = deps?.enrichTsCoordinates ?? enrichSymbolCoordinatesWithTsMorph;
120
115
  const output = entries.map((entry) => withoutGeneratedCoordinates(entry));
116
+ const service = deps?.sourceAnalysisService ??
117
+ createMaintenanceSourceAnalysisService(workspaceRoot);
118
+ const analyses = new Map();
121
119
  const tsIndices = [];
122
120
  const tsEntries = [];
123
121
  for (let index = 0; index < output.length; index++) {
@@ -125,15 +123,54 @@ export async function enrichSymbolCoordinates(entries, workspaceRoot, deps) {
125
123
  if (!entry)
126
124
  continue;
127
125
  const resolved = resolveSourcePath(entry.sourceFile, workspaceRoot);
128
- if (!resolved)
126
+ if (!resolved || !fs.statSync(resolved.absolutePath).isFile())
129
127
  continue;
130
128
  const ext = path.extname(resolved.absolutePath).toLowerCase();
131
- if (TS_JS_EXTENSIONS.has(ext)) {
129
+ if (isTsJsSourcePath(resolved.absolutePath)) {
132
130
  tsIndices.push(index);
133
131
  tsEntries.push(entry);
134
132
  continue;
135
133
  }
136
- output[index] = enrichWithRegexHeuristic(entry, resolved.absolutePath);
134
+ const logicalPath = path
135
+ .relative(workspaceRoot, resolved.absolutePath)
136
+ .replaceAll("\\", "/");
137
+ let analysis = analyses.get(logicalPath);
138
+ const firstAnalysis = analysis === undefined;
139
+ if (!analysis) {
140
+ analysis = await service.analyzeTextV2(logicalPath, fs.readFileSync(resolved.absolutePath, "utf8"));
141
+ analyses.set(logicalPath, analysis);
142
+ }
143
+ const coordinateOnlyPartial = deps?.allowPythonDecoratorCoordinates === true &&
144
+ isCoordinateOnlyDecoratorPartial(analysis);
145
+ if (analysis.status === "failed" ||
146
+ (analysis.status === "partial" && !coordinateOnlyPartial))
147
+ throw new Error(`Cannot refresh incomplete source analysis for ${logicalPath}: ${analysis.diagnostics.map((d) => d.message).join("; ")}`);
148
+ if (coordinateOnlyPartial && firstAnalysis) {
149
+ await deps?.verifyPythonDecoratorCoordinates?.(logicalPath, analysis);
150
+ console.warn(`[kibi] Coordinate-only refresh for ${logicalPath}; source analysis remains partial: ${analysis.diagnostics.map((diagnostic) => diagnostic.message).join("; ")}`);
151
+ }
152
+ if (analysis.status === "unsupported") {
153
+ // Preserve the legacy coarse heuristic until the file-level migration;
154
+ // it is never exposed as parser-backed symbol evidence.
155
+ output[index] = enrichWithRegexHeuristic(entry, resolved.absolutePath);
156
+ continue;
157
+ }
158
+ const exact = analysis.symbols.filter((symbol) => (symbol.qualifiedName ?? symbol.name) === entry.title);
159
+ const candidates = exact.length
160
+ ? exact
161
+ : analysis.symbols.filter((symbol) => symbol.name === entry.title);
162
+ if (candidates.length !== 1)
163
+ continue;
164
+ const symbol = candidates[0];
165
+ if (!symbol)
166
+ continue;
167
+ output[index] = {
168
+ ...entry,
169
+ sourceLine: symbol.startLine,
170
+ sourceColumn: symbol.startColumn,
171
+ sourceEndLine: symbol.endLine,
172
+ sourceEndColumn: symbol.endColumn,
173
+ };
137
174
  }
138
175
  if (tsEntries.length > 0) {
139
176
  const enrichedTs = await enrichTsCoordinates(tsEntries, workspaceRoot);
@@ -218,8 +255,7 @@ function createFallbackAnalysis(filePath, fallbackReason) {
218
255
  };
219
256
  }
220
257
  function detectSourceLanguage(filePath) {
221
- return (SOURCE_LANGUAGE_EXTENSIONS[path.extname(filePath).toLowerCase()] ??
222
- "unknown");
258
+ return extensionLanguage(filePath);
223
259
  }
224
260
  function inferModuleTitle(filePath) {
225
261
  const extension = path.extname(filePath);
@@ -1,7 +1,7 @@
1
1
  import { normalizePredicateToken, normalizeSubjectKey, singularize, slug, } from "./predicate-utils.js";
2
2
  // implements REQ-mcp-suggest-predicates
3
3
  export function inferPermissionRuleArgs(text) {
4
- const prohibition = text.match(/^(?<actor>[a-z][a-z\s_-]*?)\s+(?:must\s+not|cannot|can't|is\s+forbidden\s+to)\s+(?<action>[a-z][a-z_-]*)\s+(?<resource>.+?)\.?$/i);
4
+ const prohibition = text.match(/^(?<actor>[a-z][a-z\s_-]*?)\s+(?:must\s+not|must\s+never|never|cannot|can't|is\s+forbidden\s+to)\s+(?<action>[a-z][a-z_-]*)\s+(?<resource>.+?)\.?$/i);
5
5
  if (prohibition?.groups) {
6
6
  return [
7
7
  singularize(slug(prohibition.groups.actor ?? "actor")),
@@ -1 +1 @@
1
- {"version":3,"file":"source-authoring.d.ts","sourceRoot":"","sources":["../../../src/operations/mutation/source-authoring.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"source-authoring.d.ts","sourceRoot":"","sources":["../../../src/operations/mutation/source-authoring.ts"],"names":[],"mappings":"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"}
@@ -2,9 +2,11 @@ import { createHash } from "node:crypto";
2
2
  import { randomUUID } from "node:crypto";
3
3
  import fs from "node:fs";
4
4
  import path from "node:path";
5
+ import { isDeepStrictEqual } from "node:util";
5
6
  import { dump as dumpYaml, load as loadYaml } from "js-yaml";
6
7
  import { parseDocument, stringify } from "yaml";
7
8
  import { OperationError } from "../../cli-errors.js";
9
+ import { canonicalFilesystemPath } from "../../utils/canonical-path.js";
8
10
  import { CANONICAL_ENTITY_PATHS, isDerivedKbPath, } from "../../utils/kb-paths.js";
9
11
  function digest(value) {
10
12
  return createHash("sha256").update(value).digest("hex");
@@ -69,21 +71,24 @@ export function resolveContainedSourcePath(workspaceRoot, relative) {
69
71
  throw new OperationError("SOURCE_PATH_INVALID", `document.path cannot target Kibi's derived state under ${workspaceRelative}: ${relative}`);
70
72
  }
71
73
  // A symlinked existing file or parent could otherwise escape the workspace.
72
- let existing = fs.existsSync(absolute) ? absolute : path.dirname(absolute);
73
- while (!fs.existsSync(existing) && path.dirname(existing) !== existing) {
74
- existing = path.dirname(existing);
75
- }
76
- const real = fs.realpathSync.native(existing);
77
- if (real !== root && !real.startsWith(`${root}${path.sep}`)) {
74
+ const realRoot = canonicalFilesystemPath(root);
75
+ const real = canonicalFilesystemPath(absolute);
76
+ if (real !== realRoot && !real.startsWith(`${realRoot}${path.sep}`)) {
78
77
  throw new OperationError("SOURCE_PATH_INVALID", `document.path resolves outside the workspace: ${relative}`);
79
78
  }
80
79
  return absolute;
81
80
  }
82
81
  export function normalizeAuthoredSourcePath(workspaceRoot, source) {
83
82
  const normalized = source.replaceAll("\\", "/");
84
- const relative = path.isAbsolute(normalized)
85
- ? path.relative(path.resolve(workspaceRoot), path.resolve(normalized))
86
- : normalized;
83
+ const root = path.resolve(workspaceRoot);
84
+ let relative = normalized;
85
+ if (path.isAbsolute(normalized)) {
86
+ const absolute = path.resolve(normalized);
87
+ relative =
88
+ absolute === root || absolute.startsWith(`${root}${path.sep}`)
89
+ ? path.relative(root, absolute)
90
+ : path.relative(canonicalFilesystemPath(root), canonicalFilesystemPath(absolute));
91
+ }
87
92
  return path
88
93
  .relative(path.resolve(workspaceRoot), resolveContainedSourcePath(workspaceRoot, relative))
89
94
  .replaceAll(path.sep, "/");
@@ -246,13 +251,97 @@ export function manifestRelationships(item) {
246
251
  : [];
247
252
  });
248
253
  }
249
- function mergeManifestRelationships(existing, incoming, entityId) {
254
+ // implements REQ-source-analysis-v2
255
+ function manifestRecord(item) {
256
+ if (item === null ||
257
+ typeof item !== "object" ||
258
+ !("toJSON" in item) ||
259
+ typeof item.toJSON !== "function") {
260
+ return undefined;
261
+ }
262
+ const value = item.toJSON();
263
+ return value !== null && typeof value === "object" && !Array.isArray(value)
264
+ ? value
265
+ : undefined;
266
+ }
267
+ // implements REQ-source-analysis-v2
268
+ function failDuplicateConflict(entityId, field) {
269
+ throw new OperationError("SOURCE_DUPLICATE_CONFLICT", `Duplicate symbol ${entityId} has conflicting authored field "${field}"; no source was written`, false);
270
+ }
271
+ // implements REQ-source-analysis-v2
272
+ function mergeCompatibleManifestField(target, field, value, entityId, conflictField = field) {
273
+ if (Object.hasOwn(target, field)) {
274
+ if (!isDeepStrictEqual(target[field], value)) {
275
+ failDuplicateConflict(entityId, conflictField);
276
+ }
277
+ return;
278
+ }
279
+ Object.defineProperty(target, field, {
280
+ configurable: true,
281
+ enumerable: true,
282
+ value,
283
+ writable: true,
284
+ });
285
+ }
286
+ // implements REQ-source-analysis-v2
287
+ function mergeDuplicateManifestFields(records, entityId) {
288
+ const merged = Object.create(null);
289
+ for (const record of records) {
290
+ // Links and typed relationships are collections, so merge them separately
291
+ // instead of treating a difference as conflicting scalar metadata.
292
+ const authored = Object.fromEntries(Object.entries(symbolManifestRecord(record)).filter(([key]) => key !== "links"));
293
+ for (const [key, value] of Object.entries(authored)) {
294
+ mergeCompatibleManifestField(merged, key, value, entityId);
295
+ }
296
+ }
297
+ return merged;
298
+ }
299
+ // implements REQ-source-analysis-v2
300
+ function manifestRelationshipRecords(record, entityId) {
301
+ if (record.relationships === undefined)
302
+ return [];
303
+ if (!Array.isArray(record.relationships)) {
304
+ failDuplicateConflict(entityId, "relationships");
305
+ }
306
+ return record.relationships;
307
+ }
308
+ // implements REQ-source-analysis-v2
309
+ function mergeManifestRelationshipRecords(records, incoming, entityId) {
250
310
  const merged = new Map();
251
- for (const relationship of existing) {
252
- merged.set(`${relationship.type}\u0000${relationship.target}`, {
253
- type: relationship.type,
254
- target: relationship.target,
255
- });
311
+ const untyped = [];
312
+ for (const record of records) {
313
+ for (const row of manifestRelationshipRecords(record, entityId)) {
314
+ if (row === null || typeof row !== "object" || Array.isArray(row)) {
315
+ if (!untyped.some((value) => isDeepStrictEqual(value, row))) {
316
+ untyped.push(row);
317
+ }
318
+ continue;
319
+ }
320
+ const candidate = row;
321
+ const type = candidate.type;
322
+ const target = candidate.target ?? candidate.to;
323
+ if (typeof type !== "string" || typeof target !== "string") {
324
+ if (!untyped.some((value) => isDeepStrictEqual(value, row))) {
325
+ untyped.push(row);
326
+ }
327
+ continue;
328
+ }
329
+ const key = `${type}\u0000${target}`;
330
+ const normalized = {
331
+ ...Object.fromEntries(Object.entries(candidate).filter(([key]) => key !== "to")),
332
+ type,
333
+ target,
334
+ };
335
+ const previous = merged.get(key);
336
+ if (previous) {
337
+ for (const [field, value] of Object.entries(normalized)) {
338
+ mergeCompatibleManifestField(previous, field, value, entityId, `relationship ${type} -> ${target}.${field}`);
339
+ }
340
+ }
341
+ else {
342
+ merged.set(key, normalized);
343
+ }
344
+ }
256
345
  }
257
346
  for (const relationship of incoming) {
258
347
  if (relationship.from !== entityId ||
@@ -260,10 +349,45 @@ function mergeManifestRelationships(existing, incoming, entityId) {
260
349
  typeof relationship.to !== "string") {
261
350
  continue;
262
351
  }
263
- const normalized = { type: relationship.type, target: relationship.to };
264
- merged.set(`${normalized.type}\u0000${normalized.target}`, normalized);
352
+ const key = `${relationship.type}\u0000${relationship.to}`;
353
+ const normalized = {
354
+ ...Object.fromEntries(Object.entries(relationship).filter(([key]) => key !== "from" && key !== "to")),
355
+ type: relationship.type,
356
+ target: relationship.to,
357
+ };
358
+ const previous = merged.get(key);
359
+ if (previous) {
360
+ for (const [field, value] of Object.entries(normalized)) {
361
+ mergeCompatibleManifestField(previous, field, value, entityId, `relationship ${relationship.type} -> ${relationship.to}.${field}`);
362
+ }
363
+ }
364
+ else {
365
+ merged.set(key, normalized);
366
+ }
367
+ }
368
+ return [
369
+ ...[...merged.values()].sort((left, right) => `${String(left.type)}\u0000${String(left.target)}`.localeCompare(`${String(right.type)}\u0000${String(right.target)}`)),
370
+ ...untyped,
371
+ ];
372
+ }
373
+ // implements REQ-source-analysis-v2
374
+ function mergeManifestLinks(records, entity) {
375
+ const values = [
376
+ ...records.map((record) => record.links),
377
+ entity.links,
378
+ ].filter((value) => value !== undefined);
379
+ if (values.length === 0)
380
+ return undefined;
381
+ const links = [];
382
+ for (const value of values) {
383
+ const entries = Array.isArray(value) ? value : [value];
384
+ for (const entry of entries) {
385
+ if (!links.some((existing) => isDeepStrictEqual(existing, entry))) {
386
+ links.push(entry);
387
+ }
388
+ }
265
389
  }
266
- return [...merged.values()].sort((left, right) => `${left.type}\u0000${left.target}`.localeCompare(`${right.type}\u0000${right.target}`));
390
+ return links;
267
391
  }
268
392
  /** Indexes of every manifest entry whose `id` equals `id`, in file order. */
269
393
  function symbolManifestIndexes(items, id) {
@@ -276,11 +400,11 @@ function symbolManifestIndexes(items, id) {
276
400
  }
277
401
  function renderSymbolManifest(entity, relationships, existingContent) {
278
402
  if (existingContent === undefined) {
279
- const doc = parseDocument(stringify({
280
- symbols: [
281
- symbolManifestRecord(entity, mergeManifestRelationships([], relationships, String(entity.id))),
282
- ],
283
- }));
403
+ const created = symbolManifestRecord(entity);
404
+ const rows = mergeManifestRelationshipRecords([], relationships, String(entity.id));
405
+ if (rows.length > 0)
406
+ created.relationships = rows;
407
+ const doc = parseDocument(stringify({ symbols: [created] }));
284
408
  return doc.toString(AUTHORED_YAML_OPTIONS);
285
409
  }
286
410
  const doc = parseDocument(existingContent);
@@ -290,36 +414,34 @@ function renderSymbolManifest(entity, relationships, existingContent) {
290
414
  }
291
415
  const items = symbols.items;
292
416
  const id = String(entity.id);
293
- // A rebase that unions both sides of the manifest can leave the same id
294
- // twice with different ownership. Fold every copy into the first one so an
295
- // upsert repairs the duplicate instead of updating one copy and leaving the
296
- // other to shadow it.
297
- const matches = symbolManifestIndexes(items, id);
298
- const index = matches[0] ?? -1;
299
- const existingRelationships = matches.flatMap((match) => manifestRelationships(items[match]));
300
- let next = symbolManifestRecord(entity, mergeManifestRelationships(existingRelationships, relationships, id));
301
- if (index >= 0) {
302
- // Partial symbol payloads (for example a relationship-only upsert) must
303
- // never erase authored provenance from the manifest. Preserve fields the
304
- // payload omits, with incoming values winning on explicit conflicts; the
305
- // first copy wins over later duplicates.
306
- let preserved = {};
307
- for (const match of [...matches].reverse()) {
308
- const item = items[match];
309
- const previous = typeof item?.toJSON === "function" ? item.toJSON() : null;
310
- if (typeof previous === "object" &&
311
- previous !== null &&
312
- !Array.isArray(previous)) {
313
- preserved = {
314
- ...preserved,
315
- ...symbolManifestRecord(previous, []),
316
- };
317
- }
318
- }
319
- next = { ...preserved, ...next };
320
- doc.setIn(["symbols", index], next);
321
- for (const duplicate of matches.slice(1).reverse()) {
322
- doc.deleteIn(["symbols", duplicate]);
417
+ const indices = items.flatMap((item, index) => {
418
+ if (!item || typeof item !== "object" || !("get" in item))
419
+ return [];
420
+ return String(item.get("id")) === id
421
+ ? [index]
422
+ : [];
423
+ });
424
+ const existingRecords = indices
425
+ .map((index) => manifestRecord(items[index]))
426
+ .filter((record) => record !== undefined);
427
+ if (existingRecords.length !== indices.length) {
428
+ failDuplicateConflict(id, "record");
429
+ }
430
+ const previous = mergeDuplicateManifestFields(existingRecords, id);
431
+ const next = { ...previous, ...symbolManifestRecord(entity) };
432
+ const mergedRelationships = mergeManifestRelationshipRecords(existingRecords, relationships, id);
433
+ if (mergedRelationships.length > 0) {
434
+ next.relationships = mergedRelationships;
435
+ }
436
+ const mergedLinks = mergeManifestLinks(existingRecords, entity);
437
+ if (mergedLinks !== undefined)
438
+ next.links = mergedLinks;
439
+ if (indices.length > 0) {
440
+ // Patch one canonical row and remove all redundant copies only after
441
+ // compatible authored fields and all links have been merged successfully.
442
+ doc.setIn(["symbols", indices[0]], next);
443
+ for (const duplicateIndex of indices.slice(1).reverse()) {
444
+ items.splice(duplicateIndex, 1);
323
445
  }
324
446
  }
325
447
  else {
@@ -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;AAoCxE,OAAO,KAAK,EAAE,iBAAiB,EAAE,WAAW,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAuGhF,wBAAsB,+BAA+B,CACnD,MAAM,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,EACzC,OAAO,EAAE,gBAAgB,GACxB,OAAO,CAAC,IAAI,CAAC,CAaf;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,GACxB,OAAO,CAAC,SAAS,iBAAiB,EAAE,CAAC,CAuBvC;AAmCD,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;AAEH,wBAAsB,aAAa,CACjC,KAAK,EAAE,WAAW,EAClB,OAAO,EAAE,gBAAgB,EACzB,OAAO,GAAE,sBAA2B,GACnC,OAAO,CAAC,eAAe,CAAC,aAAa,CAAC,CAAC,CAshBzC"}
1
+ {"version":3,"file":"upsert.d.ts","sourceRoot":"","sources":["../../../src/operations/mutation/upsert.ts"],"names":[],"mappings":"AASA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,0CAA0C,CAAC;AACjF,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,kCAAkC,CAAC;AAqCxE,OAAO,KAAK,EAAE,iBAAiB,EAAE,WAAW,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAoGhF,wBAAsB,+BAA+B,CACnD,MAAM,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,EACzC,OAAO,EAAE,gBAAgB,GACxB,OAAO,CAAC,IAAI,CAAC,CAaf;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,GACxB,OAAO,CAAC,SAAS,iBAAiB,EAAE,CAAC,CAuBvC;AAmCD,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;AAEH,wBAAsB,aAAa,CACjC,KAAK,EAAE,WAAW,EAClB,OAAO,EAAE,gBAAgB,EACzB,OAAO,GAAE,sBAA2B,GACnC,OAAO,CAAC,eAAe,CAAC,aAAa,CAAC,CAAC,CAshBzC"}
@@ -1,5 +1,5 @@
1
1
  import { createHash } from "node:crypto";
2
- import { existsSync, readFileSync, realpathSync, unlinkSync, writeFileSync, } from "node:fs";
2
+ import { existsSync, readFileSync, unlinkSync, writeFileSync } from "node:fs";
3
3
  import path from "node:path";
4
4
  import { OperationError } from "../../cli-errors.js";
5
5
  import { readManifestWithCoordinateOverlay } from "../../extractors/manifest.js";
@@ -9,6 +9,7 @@ import { loadEntities } from "../../public/operations/discovery-entities.js";
9
9
  import { appendOnlyProofReceiptHistoryErrors } from "../../public/proof-receipt.js";
10
10
  import { appendRelationship, computeShardPath, } from "../../relationships/shards.js";
11
11
  import { resolveBranchAttachment } from "../../utils/branch-resolver.js";
12
+ import { canonicalFilesystemPath } from "../../utils/canonical-path.js";
12
13
  import { entityIdStyleWarnings } from "../../utils/entity-id-style.js";
13
14
  import { analyzeSemanticAdvisorInput } from "../semantic-advisor/analyze-prose.js";
14
15
  import { assertLogicalGroundingClaimKeys, assertSemanticInventoryBoundary, } from "../semantic-advisor/ingestion-boundary.js";
@@ -57,12 +58,9 @@ function assertRelationshipShardContained(workspaceRoot, shardPath) {
57
58
  if (!absolute.startsWith(`${relationshipRoot}${path.sep}`)) {
58
59
  throw new OperationError("SOURCE_PATH_INVALID", `Relationship shard escapes the canonical workspace lane: ${shardPath}`);
59
60
  }
60
- let existing = absolute;
61
- while (!existsSync(existing) && path.dirname(existing) !== existing) {
62
- existing = path.dirname(existing);
63
- }
64
- const real = realpathSync.native(existing);
65
- if (real !== root && !real.startsWith(`${root}${path.sep}`)) {
61
+ const realRoot = canonicalFilesystemPath(root);
62
+ const real = canonicalFilesystemPath(absolute);
63
+ if (real !== realRoot && !real.startsWith(`${realRoot}${path.sep}`)) {
66
64
  throw new OperationError("SOURCE_PATH_INVALID", `Relationship shard follows a symlink outside the workspace: ${shardPath}`);
67
65
  }
68
66
  }
@@ -1 +1 @@
1
- {"version":3,"file":"apply-plan.d.ts","sourceRoot":"","sources":["../../../src/operations/planning/apply-plan.ts"],"names":[],"mappings":"AAqBA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,0CAA0C,CAAC;AAGjF,OAAO,EACL,KAAK,eAAe,EAIrB,MAAM,uBAAuB,CAAC;AAwB/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;AAmI9E,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;AAy+CD,wBAAsB,gBAAgB,CACpC,IAAI,EAAE,aAAa,EACnB,OAAO,EAAE,gBAAgB,GACxB,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,CA2BD"}
1
+ {"version":3,"file":"apply-plan.d.ts","sourceRoot":"","sources":["../../../src/operations/planning/apply-plan.ts"],"names":[],"mappings":"AAoBA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,0CAA0C,CAAC;AAIjF,OAAO,EACL,KAAK,eAAe,EAIrB,MAAM,uBAAuB,CAAC;AAwB/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;AAmI9E,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;AAm+CD,wBAAsB,gBAAgB,CACpC,IAAI,EAAE,aAAa,EACnB,OAAO,EAAE,gBAAgB,GACxB,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,CA2BD"}
@@ -1,5 +1,4 @@
1
1
  import { createHash, randomUUID } from "node:crypto";
2
- import { existsSync, realpathSync } from "node:fs";
3
2
  import path from "node:path";
4
3
  import { OperationError } from "../../cli-errors.js";
5
4
  import { branchEnsureCommand, branchMigrateCommand, branchRecoverCommand, } from "../../commands/branch.js";
@@ -10,6 +9,7 @@ import { executeStatus } from "../../public/operations/discovery-executors.js";
10
9
  import { PREDICATE_SCHEMA_ALIGNMENT_CODE, migrationPlanHash, } from "../../public/operations/migration-plan.js";
11
10
  import { readMigrationConfigStatus } from "../../public/operations/migration-plan.js";
12
11
  import { readWorkspaceSnapshot } from "../../public/operations/workspace-snapshot.js";
12
+ import { canonicalFilesystemPath } from "../../utils/canonical-path.js";
13
13
  import { isDerivedKbPath } from "../../utils/kb-paths.js";
14
14
  import { bootstrapEmptyKbSnapshotId, bootstrapPlanHash, } from "../bootstrap/types.js";
15
15
  import { executeDelete } from "../mutation/delete.js";
@@ -427,14 +427,10 @@ async function applySourceWrites(context, writes, planHash, allowReplay = false,
427
427
  if (workspaceRelative === ".kb" || isDerivedKbPath(workspaceRelative)) {
428
428
  throw new Error("Apply plan failed: sourceWrites.path cannot target Kibi's derived .kb runtime trees");
429
429
  }
430
- let existingPath = absolute;
431
- while (!existsSync(existingPath) &&
432
- path.dirname(existingPath) !== existingPath) {
433
- existingPath = path.dirname(existingPath);
434
- }
435
- const realExisting = realpathSync.native(existingPath);
436
- if (realExisting !== root &&
437
- !realExisting.startsWith(`${root}${path.sep}`)) {
430
+ const realRoot = canonicalFilesystemPath(root);
431
+ const realExisting = canonicalFilesystemPath(absolute);
432
+ if (realExisting !== realRoot &&
433
+ !realExisting.startsWith(`${realRoot}${path.sep}`)) {
438
434
  throw new Error(`Apply plan failed: sourceWrites.path follows a symlink outside the workspace: ${write.path}`);
439
435
  }
440
436
  const existing = await context.fs
@@ -1 +1 @@
1
- {"version":3,"file":"ingest-proof.d.ts","sourceRoot":"","sources":["../../../src/operations/proof/ingest-proof.ts"],"names":[],"mappings":"AAMA,OAAO,EACL,KAAK,QAAQ,EAEd,MAAM,yBAAyB,CAAC;AAOjC,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,0CAA0C,CAAC;AAkBjF,OAAO,EAEL,KAAK,YAAY,EAGlB,MAAM,+BAA+B,CAAC;AAqBvC,MAAM,MAAM,eAAe,GAAG,QAAQ,CAAC;IACrC,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;IAC5C,OAAO,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;CAC7B,CAAC,CAAC;AAEH,MAAM,MAAM,qBAAqB,GAAG,QAAQ,CAAC;IAC3C,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,YAAY,CAAC,SAAS,CAAC,CAAC;IACjC,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,OAAO,CAAC;IACjB,SAAS,EAAE,OAAO,CAAC;IACnB,YAAY,EAAE,MAAM,CAAC;IACrB,IAAI,EAAE,SAAS,QAAQ,EAAE,CAAC;CAC3B,CAAC,CAAC;AAGH,MAAM,MAAM,iBAAiB,GAAG,QAAQ,CAAC;IACvC,cAAc,EAAE,MAAM,CAAC;IACvB,eAAe,EAAE,MAAM,CAAC;IACxB,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,SAAS,qBAAqB,EAAE,CAAC;CAC3C,CAAC,CAAC;AAEH,KAAK,iBAAiB,GAAG;IACvB,OAAO,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IAC/C,iBAAiB,EAAE,iBAAiB,CAAC;CACtC,CAAC;AAokBF,wBAAsB,kBAAkB,CACtC,IAAI,EAAE,eAAe,EACrB,OAAO,EAAE,gBAAgB,GACxB,OAAO,CAAC,iBAAiB,CAAC,CAuC5B"}
1
+ {"version":3,"file":"ingest-proof.d.ts","sourceRoot":"","sources":["../../../src/operations/proof/ingest-proof.ts"],"names":[],"mappings":"AAMA,OAAO,EACL,KAAK,QAAQ,EAEd,MAAM,yBAAyB,CAAC;AAOjC,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,0CAA0C,CAAC;AAkBjF,OAAO,EAEL,KAAK,YAAY,EAGlB,MAAM,+BAA+B,CAAC;AAqBvC,MAAM,MAAM,eAAe,GAAG,QAAQ,CAAC;IACrC,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;IAC5C,OAAO,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;CAC7B,CAAC,CAAC;AAEH,MAAM,MAAM,qBAAqB,GAAG,QAAQ,CAAC;IAC3C,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,YAAY,CAAC,SAAS,CAAC,CAAC;IACjC,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,OAAO,CAAC;IACjB,SAAS,EAAE,OAAO,CAAC;IACnB,YAAY,EAAE,MAAM,CAAC;IACrB,IAAI,EAAE,SAAS,QAAQ,EAAE,CAAC;CAC3B,CAAC,CAAC;AAGH,MAAM,MAAM,iBAAiB,GAAG,QAAQ,CAAC;IACvC,cAAc,EAAE,MAAM,CAAC;IACvB,eAAe,EAAE,MAAM,CAAC;IACxB,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,SAAS,qBAAqB,EAAE,CAAC;CAC3C,CAAC,CAAC;AAEH,KAAK,iBAAiB,GAAG;IACvB,OAAO,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IAC/C,iBAAiB,EAAE,iBAAiB,CAAC;CACtC,CAAC;AAqkBF,wBAAsB,kBAAkB,CACtC,IAAI,EAAE,eAAe,EACrB,OAAO,EAAE,gBAAgB,GACxB,OAAO,CAAC,iBAAiB,CAAC,CAuC5B"}
@@ -446,6 +446,7 @@ async function executeIngestProofUnlocked(args, context, lifecycle) {
446
446
  },
447
447
  };
448
448
  }
449
+ // implements REQ-kibi-verification-evidence-contract
449
450
  export async function executeIngestProof(args, context) {
450
451
  if (!context.fs ||
451
452
  context.sourceFirst === false ||
@@ -1 +1 @@
1
- {"version":3,"file":"analysis-receipt.d.ts","sourceRoot":"","sources":["../../../src/operations/semantic-advisor/analysis-receipt.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AACnD,OAAO,EACL,KAAK,OAAO,EAMb,MAAM,aAAa,CAAC;AACrB,OAAO,KAAK,EACV,6BAA6B,EAI7B,4BAA4B,EAC5B,0BAA0B,EAC1B,mBAAmB,EACnB,iBAAiB,EACjB,cAAc,EACf,MAAM,YAAY,CAAC;AAyBpB,wBAAgB,kBAAkB,CAChC,OAAO,EAAE,OAAO,EAChB,OAAO,EAAE,SAAS,cAAc,EAAE,EAClC,OAAO,EAAE,OAAO,EAChB,WAAW,EAAE,SAAS,0BAA0B,EAAE,EAClD,OAAO,EAAE,SAAS,cAAc,EAAE,EAClC,YAAY,EAAE,SAAS,mBAAmB,EAAE,EAC5C,eAAe,EAAE,SAAS,4BAA4B,EAAE,EACxD,cAAc,EAAE,SAAS,iBAAiB,EAAE,GAC3C,6BAA6B,CAkH/B"}
1
+ {"version":3,"file":"analysis-receipt.d.ts","sourceRoot":"","sources":["../../../src/operations/semantic-advisor/analysis-receipt.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AACnD,OAAO,EACL,KAAK,OAAO,EAMb,MAAM,aAAa,CAAC;AACrB,OAAO,KAAK,EACV,6BAA6B,EAI7B,4BAA4B,EAC5B,0BAA0B,EAC1B,mBAAmB,EACnB,iBAAiB,EACjB,cAAc,EACf,MAAM,YAAY,CAAC;AAyBpB,wBAAgB,kBAAkB,CAChC,OAAO,EAAE,OAAO,EAChB,OAAO,EAAE,SAAS,cAAc,EAAE,EAClC,OAAO,EAAE,OAAO,EAChB,WAAW,EAAE,SAAS,0BAA0B,EAAE,EAClD,OAAO,EAAE,SAAS,cAAc,EAAE,EAClC,YAAY,EAAE,SAAS,mBAAmB,EAAE,EAC5C,eAAe,EAAE,SAAS,4BAA4B,EAAE,EACxD,cAAc,EAAE,SAAS,iBAAiB,EAAE,GAC3C,6BAA6B,CA2H/B"}
@@ -27,9 +27,15 @@ export function buildAdvisorResult(payload, signals, modeled, suggestions, claus
27
27
  : suggestions.some(({ kind }) => kind === "predicate")
28
28
  ? "predicate"
29
29
  : null;
30
+ const signalLane = chooseLane(signals);
31
+ const unresolvedNormative = propositions.some(({ role, status }) => role === "normative" &&
32
+ ["missing", "ambiguous", "ontology_gap"].includes(status));
30
33
  const candidateLane = modeled
31
34
  ? "none"
32
- : (suggestionLane ?? chooseLane(signals));
35
+ : (suggestionLane ??
36
+ (signalLane === "none" && unresolvedNormative
37
+ ? "observation_review"
38
+ : signalLane));
33
39
  const readiness = modeled
34
40
  ? "modeled"
35
41
  : candidateLane === "none"
@@ -1 +1 @@
1
- {"version":3,"file":"analyze-prose.d.ts","sourceRoot":"","sources":["../../../src/operations/semantic-advisor/analyze-prose.ts"],"names":[],"mappings":"AAkCA,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;AAg0BD,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":"AAmCA,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;AAk2BD,wBAAgB,2BAA2B,CACzC,KAAK,EAAE,oBAAoB,GAC1B,6BAA6B,CA+D/B"}
@@ -2,7 +2,7 @@ import { createBuiltinOntologyPack, detectSignals as detectBuiltinSignals, } fro
2
2
  import { utf8Span, validateLogicIr, } from "../../logic/ir.js";
3
3
  import { logicRuleFactId } from "../modeling/logic-modeling.js";
4
4
  import { buildAdvisorResult } from "./analysis-receipt.js";
5
- import { extractSemanticClauses, semanticClaimKey, } from "./clauses.js";
5
+ import { extractSemanticClauses, hasNormativeAssertion, semanticClaimKey, } from "./clauses.js";
6
6
  import { observationPlan } from "./observation-plan.js";
7
7
  import { predicateSuggestion as buildPredicateSuggestion, isRecord, payloadHash, propertiesOf, relationship, semanticClausesOf, semanticSourceHash, semanticSourceOf, sourceOf, statementOf, stringValue, } from "./shared.js";
8
8
  import { detectStrictSuggestion } from "./strict-rules.js";
@@ -371,11 +371,33 @@ function unmatchedClauseSuggestion(payload, clause, clauses, expectedClaimKeys)
371
371
  }, clause, clauses, expectedClaimKeys, statementOf(payload), semanticSourceOf(payload).field);
372
372
  }
373
373
  function propositionRole(statement, normative) {
374
- if (/\b(?:for example|e\.g\.|such as|illustrative)\b/i.test(statement))
374
+ // Explicit context labels may quote obligations. Incidental explanatory
375
+ // words within an asserted obligation must not exempt it from grounding.
376
+ const assertedNormative = normative && hasNormativeAssertion(statement);
377
+ // A bare desired impression is subjective, even when phrased with should.
378
+ // Keep concrete actions, quantitative conditions and any independent
379
+ // obligation assertive instead of exempting them through subjective words.
380
+ const impression = /\bshould\s+(?:feel\s+(?:welcoming|comfortable|energetic)(?:\s+and\s+(?:welcoming|comfortable|energetic))*|(?:look|seem)\s+complete)\b/i.exec(statement);
381
+ const impressionTail = impression
382
+ ? statement.slice(impression.index + impression[0].length).trim()
383
+ : "";
384
+ const subjectiveAspiration = impression !== null &&
385
+ (impressionTail.length === 0 ||
386
+ /^(?:to|for)\s+[^,;.!?]+$/i.test(impressionTail)) &&
387
+ !/\b(?:and|or)\b/i.test(impressionTail) &&
388
+ !/\d|\b(?:at least|at most|exactly|within|no more than)\b/i.test(statement) &&
389
+ !hasNormativeAssertion(statement.replace(impression[0], ""));
390
+ if (/^\s*(?:for example\b|e\.g\.|(?:example|illustrative(?: example)?)\s*:)/i.test(statement) ||
391
+ (!assertedNormative &&
392
+ /\b(?:for example|e\.g\.|such as|illustrative)\b/i.test(statement)))
375
393
  return "example";
376
- if (/\b(?:because|so that|in order to| rationale|therefore)\b/i.test(statement))
394
+ if (/^\s*rationale\s*:/i.test(statement) ||
395
+ (!assertedNormative &&
396
+ /\b(?:because|so that|in order to| rationale|therefore)\b/i.test(statement)))
377
397
  return "rationale";
378
- if (/\b(?:feel|comfortable|looks complete|seems complete|subjective|prefer)\b/i.test(statement))
398
+ if (subjectiveAspiration ||
399
+ (!assertedNormative &&
400
+ /\b(?:feel|comfortable|looks complete|seems complete|subjective|prefer)\b/i.test(statement)))
379
401
  return "subjective";
380
402
  if (/\b(?:means|defined as|refers to|is called)\b/i.test(statement))
381
403
  return "definition";