kibi-cli 0.12.4 → 0.12.7

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 (37) hide show
  1. package/dist/commands/check.d.ts.map +1 -1
  2. package/dist/commands/check.js +12 -16
  3. package/dist/commands/migrate.d.ts.map +1 -1
  4. package/dist/commands/migrate.js +18 -0
  5. package/dist/commands/sync/persistence.d.ts.map +1 -1
  6. package/dist/commands/sync/persistence.js +53 -1
  7. package/dist/extractors/manifest.d.ts.map +1 -1
  8. package/dist/extractors/manifest.js +92 -2
  9. package/dist/extractors/markdown.d.ts.map +1 -1
  10. package/dist/extractors/markdown.js +92 -0
  11. package/dist/extractors/relationships.d.ts.map +1 -1
  12. package/dist/extractors/relationships.js +2 -0
  13. package/dist/prolog.d.ts.map +1 -1
  14. package/dist/prolog.js +33 -3
  15. package/dist/public/schemas/entity.d.ts.map +1 -1
  16. package/dist/public/schemas/entity.js +6 -0
  17. package/dist/public/symbol-granularity.d.ts +23 -0
  18. package/dist/public/symbol-granularity.d.ts.map +1 -0
  19. package/dist/public/symbol-granularity.js +68 -0
  20. package/dist/schemas/entity.schema.json +11 -0
  21. package/dist/traceability/symbol-extract.d.ts +3 -1
  22. package/dist/traceability/symbol-extract.d.ts.map +1 -1
  23. package/dist/traceability/symbol-extract.js +2 -0
  24. package/dist/traceability/temp-kb.d.ts.map +1 -1
  25. package/dist/traceability/temp-kb.js +3 -0
  26. package/dist/types/relationships.d.ts +1 -1
  27. package/dist/types/relationships.d.ts.map +1 -1
  28. package/dist/utils/config.d.ts +1 -0
  29. package/dist/utils/config.d.ts.map +1 -1
  30. package/dist/utils/config.js +6 -0
  31. package/dist/utils/schema-version.d.ts +1 -1
  32. package/dist/utils/schema-version.js +1 -1
  33. package/package.json +13 -3
  34. package/schema/config.json +9 -0
  35. package/src/public/schemas/entity.ts +6 -0
  36. package/src/public/symbol-granularity.ts +125 -0
  37. package/src/schemas/entity.schema.json +11 -0
@@ -1 +1 @@
1
- {"version":3,"file":"check.d.ts","sourceRoot":"","sources":["../../src/commands/check.ts"],"names":[],"mappings":"AA2EA,OAAO,EAGL,KAAK,SAAS,EAEf,MAAM,2BAA2B,CAAC;AAEnC,YAAY,EAAE,SAAS,EAAE,CAAC;AAI1B,MAAM,WAAW,YAAY;IAC3B,GAAG,CAAC,EAAE,OAAO,CAAC;IACd,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,QAAQ,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IAC3B,MAAM,CAAC,EAAE,OAAO,CAAC;CAClB;AAgcD,wBAAsB,YAAY,CAChC,OAAO,EAAE,YAAY,GACpB,OAAO,CAAC;IAAE,QAAQ,EAAE,MAAM,CAAA;CAAE,CAAC,CA0V/B"}
1
+ {"version":3,"file":"check.d.ts","sourceRoot":"","sources":["../../src/commands/check.ts"],"names":[],"mappings":"AAiFA,OAAO,EAGL,KAAK,SAAS,EAEf,MAAM,2BAA2B,CAAC;AAEnC,YAAY,EAAE,SAAS,EAAE,CAAC;AAI1B,MAAM,WAAW,YAAY;IAC3B,GAAG,CAAC,EAAE,OAAO,CAAC;IACd,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,QAAQ,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IAC3B,MAAM,CAAC,EAAE,OAAO,CAAC;CAClB;AA2bD,wBAAsB,YAAY,CAChC,OAAO,EAAE,YAAY,GACpB,OAAO,CAAC;IAAE,QAAQ,EAAE,MAAM,CAAA;CAAE,CAAC,CA0V/B"}
@@ -22,6 +22,7 @@ import { extractFromManifest, extractFromManifestString, } from "../extractors/m
22
22
  import { extractFromMarkdownString, } from "../extractors/markdown.js";
23
23
  import { PrologProcess } from "../prolog.js";
24
24
  import { escapeAtom, parseTriples, parseViolationRows, } from "../prolog/codec.js";
25
+ import { getBehavioralSymbolNames, getNonBehavioralSymbolNames, isAllowedGranularityReason, isTraceabilityRelationshipType, } from "../public/symbol-granularity.js";
25
26
  import { KIBI_NO_IMPACT_DECLARATION, KIBI_SYMBOLS_MANIFEST_PATH, } from "../traceability/evidence-model.js";
26
27
  import { getStagedFiles } from "../traceability/git-staged.js";
27
28
  import { validateStagedMarkdown } from "../traceability/markdown-validate.js";
@@ -133,23 +134,11 @@ function buildManifestLookup(stagedFiles) {
133
134
  stagedAuthoredSymbolResults,
134
135
  };
135
136
  }
136
- const GRANULARITY_RELATIONSHIP_TYPES = new Set([
137
- "implements",
138
- "covered_by",
139
- "executable_for",
140
- ]);
141
- const ALLOWED_GRANULARITY_REASONS = new Set([
142
- "config-artifact",
143
- "module-level-behavior",
144
- "extractor-miss",
145
- "legacy-link",
146
- ]);
147
137
  function hasTraceabilityRelationship(result) {
148
- return result.relationships.some((relationship) => GRANULARITY_RELATIONSHIP_TYPES.has(relationship.type));
138
+ return result.relationships.some((relationship) => isTraceabilityRelationshipType(relationship.type));
149
139
  }
150
140
  function hasValidGranularityReason(result) {
151
- const reason = result.entity.granularity_reason;
152
- return typeof reason === "string" && ALLOWED_GRANULARITY_REASONS.has(reason);
141
+ return isAllowedGranularityReason(result.entity.granularity_reason);
153
142
  }
154
143
  function createSymbolGranularityDiagnostics(options) {
155
144
  const diagnostics = [];
@@ -168,13 +157,20 @@ function createSymbolGranularityDiagnostics(options) {
168
157
  ].sort();
169
158
  if (granularNames.includes(result.entity.title))
170
159
  continue;
160
+ const behavioralNames = getBehavioralSymbolNames(granularSymbols);
161
+ if (behavioralNames.length === 0)
162
+ continue;
163
+ const nonBehavioralNames = getNonBehavioralSymbolNames(granularSymbols);
164
+ const ignoredSymbolsSuggestion = nonBehavioralNames.length > 0
165
+ ? ` Non-behavioral symbols ignored for this decision: ${nonBehavioralNames.join(", ")}.`
166
+ : "";
171
167
  diagnostics.push({
172
168
  id: "symbol_granularity_violation",
173
169
  severity: "error",
174
170
  files: [result.entity.source, result.sourceFile],
175
171
  docs: ["docs/symbol-traceability-taxonomy.md"],
176
- message: `Symbol ${result.entity.id} links ${result.sourceFile} coarsely while granular symbols are available: ${granularNames.join(", ")}`,
177
- suggestion: "Move ownership/coverage/test relationships to the narrow function/class/type symbol, or add granularity_reason with config-artifact, module-level-behavior, extractor-miss, or legacy-link when the coarse symbol is intentional.",
172
+ message: `Symbol ${result.entity.id} links ${result.sourceFile} coarsely while granular symbols are available (behavioral only): ${behavioralNames.join(", ")}`,
173
+ suggestion: `Move ownership/coverage/test relationships to the narrow behavioral symbol, add a manifest behavioral anchor, or add granularity_reason with config-artifact, module-level-behavior, extractor-miss, or legacy-link when the coarse symbol is intentional.${ignoredSymbolsSuggestion}`,
178
174
  });
179
175
  }
180
176
  return diagnostics;
@@ -1 +1 @@
1
- {"version":3,"file":"migrate.d.ts","sourceRoot":"","sources":["../../src/commands/migrate.ts"],"names":[],"mappings":"AAqCA,UAAU,cAAc;IACtB,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,GAAG,CAAC,EAAE,OAAO,CAAC;CACf;AAiWD,wBAAsB,cAAc,CAClC,OAAO,GAAE,cAAmB,GAC3B,OAAO,CAAC;IAAE,QAAQ,EAAE,MAAM,CAAA;CAAE,CAAC,CAgI/B"}
1
+ {"version":3,"file":"migrate.d.ts","sourceRoot":"","sources":["../../src/commands/migrate.ts"],"names":[],"mappings":"AAqCA,UAAU,cAAc;IACtB,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,GAAG,CAAC,EAAE,OAAO,CAAC;CACf;AA8WD,wBAAsB,cAAc,CAClC,OAAO,GAAE,cAAmB,GAC3B,OAAO,CAAC;IAAE,QAAQ,EAAE,MAAM,CAAA;CAAE,CAAC,CA+I/B"}
@@ -110,12 +110,20 @@ function buildMigrationAuditRecord(args) {
110
110
  configPath: args.configPath,
111
111
  fromVersion: args.fromVersion,
112
112
  migratedAt: args.migratedAt,
113
+ semanticAdvisorBackfill: args.semanticAdvisorBackfill,
113
114
  status: "applied",
114
115
  symbolGranularityLegacyLinks: args.symbolGranularityLegacyLinks,
115
116
  toVersion: LATEST_KB_SCHEMA_VERSION,
116
117
  warning: args.warning,
117
118
  };
118
119
  }
120
+ function normalizeSemanticAdvisorBackfill(value) {
121
+ return value === "pending" ||
122
+ value === "completed" ||
123
+ value === "not_applicable"
124
+ ? value
125
+ : null;
126
+ }
119
127
  const TRACEABILITY_RELATIONSHIP_TYPES = new Set([
120
128
  "implements",
121
129
  "covered_by",
@@ -305,12 +313,17 @@ export async function migrateCommand(options = {}) {
305
313
  config,
306
314
  dryRun: options.dryRun || !options.yes,
307
315
  });
316
+ const semanticAdvisorBackfill = normalizeSemanticAdvisorBackfill(config.semanticAdvisorBackfill) ??
317
+ "pending";
308
318
  if (options.dryRun) {
309
319
  console.log(`dry run: would migrate ${configPathRelative} schemaVersion from ${fromVersionLabel} to ${LATEST_KB_SCHEMA_VERSION}.`);
310
320
  console.log(`dry run: would write migration audit metadata to ${auditPathRelative}.`);
311
321
  if (symbolGranularityMigration.count > 0) {
312
322
  console.log(`dry run: would mark ${symbolGranularityMigration.count} legacy coarse symbol link(s) in ${toRelativePath(cwd, symbolGranularityMigration.manifestPath ?? "symbols.yaml")}.`);
313
323
  }
324
+ if (semanticAdvisorBackfill === "pending") {
325
+ console.log(`dry run: would mark semantic advisor backfill as pending in ${configPathRelative}.`);
326
+ }
314
327
  console.log("Re-run with --yes to apply these changes.");
315
328
  return { exitCode: 0 };
316
329
  }
@@ -323,6 +336,7 @@ export async function migrateCommand(options = {}) {
323
336
  const nextConfig = {
324
337
  ...config,
325
338
  schemaVersion: LATEST_KB_SCHEMA_VERSION,
339
+ semanticAdvisorBackfill,
326
340
  };
327
341
  const migratedAt = new Date().toISOString();
328
342
  writeJsonAtomically(configPath, nextConfig);
@@ -331,6 +345,7 @@ export async function migrateCommand(options = {}) {
331
345
  configPath: configPathRelative,
332
346
  fromVersion: configStatus.currentVersion,
333
347
  migratedAt,
348
+ semanticAdvisorBackfill,
334
349
  symbolGranularityLegacyLinks: symbolGranularityMigration.count,
335
350
  warning: migrationWarning,
336
351
  }));
@@ -338,6 +353,9 @@ export async function migrateCommand(options = {}) {
338
353
  if (symbolGranularityMigration.count > 0) {
339
354
  console.log(`Marked ${symbolGranularityMigration.count} existing coarse symbol link(s) as legacy-link.`);
340
355
  }
356
+ if (semanticAdvisorBackfill === "pending") {
357
+ console.log(`Marked semantic advisor backfill as pending in ${configPathRelative}.`);
358
+ }
341
359
  console.log(`Wrote migration audit metadata to ${auditPathRelative}.`);
342
360
  console.log("Migration complete. Future 'kibi migrate' runs will be a no-op.");
343
361
  return { exitCode: 0 };
@@ -1 +1 @@
1
- {"version":3,"file":"persistence.d.ts","sourceRoot":"","sources":["../../../src/commands/sync/persistence.ts"],"names":[],"mappings":"AAmBA,OAAO,KAAK,EAEV,qBAAqB,EACrB,gBAAgB,EACjB,MAAM,8BAA8B,CAAC;AACtC,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AA0ErD,MAAM,WAAW,iBAAiB;IAChC,WAAW,EAAE,MAAM,CAAC;IACpB,iBAAiB,EAAE,MAAM,CAAC;IAC1B,UAAU,EAAE,OAAO,CAAC;CACrB;AAED,wBAAsB,eAAe,CAEnC,MAAM,EAAE,aAAa,EACrB,OAAO,EAAE,gBAAgB,EAAE,EAC3B,SAAS,EAAE,GAAG,CAAC,MAAM,CAAC,GACrB,OAAO,CAAC;IAAE,WAAW,EAAE,MAAM,CAAC;IAAC,UAAU,EAAE,OAAO,CAAA;CAAE,CAAC,CAqEvD;AAED,wBAAsB,oBAAoB,CACxC,MAAM,EAAE,aAAa,EACrB,OAAO,EAAE,gBAAgB,EAAE,EAC3B,kBAAkB,EAAE,qBAAqB,EAAE,GAC1C,OAAO,CAAC;IAAE,iBAAiB,EAAE,MAAM,CAAC;IAAC,UAAU,EAAE,OAAO,CAAA;CAAE,CAAC,CAiI7D"}
1
+ {"version":3,"file":"persistence.d.ts","sourceRoot":"","sources":["../../../src/commands/sync/persistence.ts"],"names":[],"mappings":"AAmBA,OAAO,KAAK,EAEV,qBAAqB,EACrB,gBAAgB,EACjB,MAAM,8BAA8B,CAAC;AACtC,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AA0ErD,MAAM,WAAW,iBAAiB;IAChC,WAAW,EAAE,MAAM,CAAC;IACpB,iBAAiB,EAAE,MAAM,CAAC;IAC1B,UAAU,EAAE,OAAO,CAAC;CACrB;AAgCD,wBAAsB,eAAe,CAEnC,MAAM,EAAE,aAAa,EACrB,OAAO,EAAE,gBAAgB,EAAE,EAC3B,SAAS,EAAE,GAAG,CAAC,MAAM,CAAC,GACrB,OAAO,CAAC;IAAE,WAAW,EAAE,MAAM,CAAC;IAAC,UAAU,EAAE,OAAO,CAAA;CAAE,CAAC,CAqEvD;AAED,wBAAsB,oBAAoB,CACxC,MAAM,EAAE,aAAa,EACrB,OAAO,EAAE,gBAAgB,EAAE,EAC3B,kBAAkB,EAAE,qBAAqB,EAAE,GAC1C,OAAO,CAAC;IAAE,iBAAiB,EAAE,MAAM,CAAC;IAAC,UAAU,EAAE,OAAO,CAAA;CAAE,CAAC,CAwK7D"}
@@ -80,6 +80,29 @@ function serializeTypedFactFields(entity) {
80
80
  }
81
81
  return fields;
82
82
  }
83
+ function isQueryFailedError(error) {
84
+ const lowered = error.toLowerCase();
85
+ // Only match session-corruption errors that might benefit from a Prolog process restart.
86
+ // Data errors (entity missing, invalid relationship) are deterministic and should NOT
87
+ // trigger a restart — the error would persist and the restart would lose kb_attach state.
88
+ return (lowered.includes("query failed") ||
89
+ lowered.includes("query returned false") ||
90
+ lowered.includes("predicate or file not found"));
91
+ }
92
+ async function tryResetPrologProcess(prolog) {
93
+ const maybe = prolog;
94
+ if (typeof maybe.terminate !== "function" || typeof maybe.start !== "function") {
95
+ return false;
96
+ }
97
+ try {
98
+ await maybe.terminate();
99
+ await maybe.start();
100
+ return true;
101
+ }
102
+ catch {
103
+ return false;
104
+ }
105
+ }
83
106
  export async function persistEntities(
84
107
  // implements REQ-009
85
108
  prolog, results, entityIds) {
@@ -216,6 +239,7 @@ export async function persistRelationships(prolog, results, shardRelationships)
216
239
  }
217
240
  // Retry failed relationships
218
241
  const retryCount = 3;
242
+ let resetAttempted = false;
219
243
  for (let pass = 0; pass < retryCount && failedRelationships.length > 0; pass++) {
220
244
  const remainingFailed = [];
221
245
  for (const { rel, fromId, toId } of failedRelationships) {
@@ -242,6 +266,15 @@ export async function persistRelationships(prolog, results, shardRelationships)
242
266
  }
243
267
  failedRelationships.length = 0;
244
268
  failedRelationships.push(...remainingFailed);
269
+ const allLookLikeSessionFailures = remainingFailed.length > 0 &&
270
+ remainingFailed.every(({ error }) => isQueryFailedError(error));
271
+ // If every pending relationship fails with generic query errors, attempt one
272
+ // best-effort Prolog restart before the next retry pass to recover from a
273
+ // potentially poisoned interactive session.
274
+ if (allLookLikeSessionFailures && !resetAttempted && pass + 1 < retryCount) {
275
+ resetAttempted = true;
276
+ await tryResetPrologProcess(prolog);
277
+ }
245
278
  }
246
279
  // Log remaining failures
247
280
  if (failedRelationships.length > 0) {
@@ -255,7 +288,26 @@ export async function persistRelationships(prolog, results, shardRelationships)
255
288
  console.warn(` Error: ${error}`);
256
289
  }
257
290
  }
258
- console.warn("\nTip: Ensure target entities exist before creating relationships.");
291
+ const missingEntities = failedRelationships.filter(({ error }) => error.toLowerCase().includes("entity does not exist"));
292
+ const invalidRels = failedRelationships.filter(({ error }) => error.includes("Invalid relationship"));
293
+ if (missingEntities.length > 0) {
294
+ // Collect unique missing entity IDs from error messages
295
+ const missingIds = new Set();
296
+ for (const { error } of missingEntities) {
297
+ const match = error.match(/does not exist: (.+)$/);
298
+ if (match)
299
+ missingIds.add(match[1].trim());
300
+ }
301
+ const idList = [...missingIds].sort().join(", ");
302
+ console.warn(`\nTip: ${missingEntities.length} relationship(s) reference ${missingIds.size} missing entity/ies: ${idList}.`);
303
+ console.warn(" Create the missing docs (e.g., docs/requirements/REQ-*.md) or remove stale relationships.");
304
+ }
305
+ else if (invalidRels.length > 0) {
306
+ console.warn("\nTip: Check that relationship types and directions match the allowed schema (e.g., implements symbol→req, verified_by req→test).");
307
+ }
308
+ else {
309
+ console.warn("\nTip: Ensure target entities exist before creating relationships.");
310
+ }
259
311
  }
260
312
  return { relationshipCount: relCount, kbModified };
261
313
  }
@@ -1 +1 @@
1
- {"version":3,"file":"manifest.d.ts","sourceRoot":"","sources":["../../src/extractors/manifest.ts"],"names":[],"mappings":"AA6BA,MAAM,WAAW,eAAe;IAC9B,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,MAAM,CAAC;IACnB,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,kBAAkB,CAAC,EAAE,MAAM,CAAC;CAC7B;AAED,MAAM,WAAW,qBAAqB;IACpC,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,EAAE,EAAE,MAAM,CAAC;CACZ;AAED,MAAM,WAAW,gBAAgB;IAC/B,MAAM,EAAE,eAAe,CAAC;IACxB,aAAa,EAAE,qBAAqB,EAAE,CAAC;IACvC,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,qBAAa,aAAc,SAAQ,KAAK;IAG7B,QAAQ,EAAE,MAAM;gBADvB,OAAO,EAAE,MAAM,EACR,QAAQ,EAAE,MAAM;CAK1B;AAED,MAAM,WAAW,oBAAoB;IACnC,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,KAAK,CAAC,EAAE,KAAK,CAAC,MAAM,GAAG;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IACzD,aAAa,CAAC,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IACxD,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,sBAAsB,CAAC,EAAE,MAAM,CAAC;IAChC,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;CACxB;AA2HD,wBAAgB,yBAAyB,CACvC,OAAO,EAAE,MAAM,EACf,QAAQ,EAAE,MAAM,GACf,gBAAgB,EAAE,CAmBpB;AAED,wBAAgB,kCAAkC,CAChD,OAAO,EAAE,MAAM,EACf,QAAQ,EAAE,MAAM,GACf,oBAAoB,EAAE,CAmBxB;AAED,wBAAgB,mBAAmB,CAAC,QAAQ,EAAE,MAAM,GAAG,gBAAgB,EAAE,CAKxE;AAsCD,wBAAgB,iCAAiC,CAC/C,YAAY,EAAE,MAAM,EACpB,eAAe,CAAC,EAAE,MAAM,GACvB,oBAAoB,EAAE,CAWxB"}
1
+ {"version":3,"file":"manifest.d.ts","sourceRoot":"","sources":["../../src/extractors/manifest.ts"],"names":[],"mappings":"AA6BA,MAAM,WAAW,eAAe;IAC9B,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,MAAM,CAAC;IACnB,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,kBAAkB,CAAC,EAAE,MAAM,CAAC;CAC7B;AAED,MAAM,WAAW,qBAAqB;IACpC,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,EAAE,EAAE,MAAM,CAAC;CACZ;AAED,MAAM,WAAW,gBAAgB;IAC/B,MAAM,EAAE,eAAe,CAAC;IACxB,aAAa,EAAE,qBAAqB,EAAE,CAAC;IACvC,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAqID,qBAAa,aAAc,SAAQ,KAAK;IAG7B,QAAQ,EAAE,MAAM;gBADvB,OAAO,EAAE,MAAM,EACR,QAAQ,EAAE,MAAM;CAK1B;AAED,MAAM,WAAW,oBAAoB;IACnC,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,KAAK,CAAC,EAAE,KAAK,CAAC,MAAM,GAAG;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IACzD,aAAa,CAAC,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IACxD,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,sBAAsB,CAAC,EAAE,MAAM,CAAC;IAChC,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;CACxB;AAgID,wBAAgB,yBAAyB,CACvC,OAAO,EAAE,MAAM,EACf,QAAQ,EAAE,MAAM,GACf,gBAAgB,EAAE,CAmBpB;AAED,wBAAgB,kCAAkC,CAChD,OAAO,EAAE,MAAM,EACf,QAAQ,EAAE,MAAM,GACf,oBAAoB,EAAE,CAmBxB;AAED,wBAAgB,mBAAmB,CAAC,QAAQ,EAAE,MAAM,GAAG,gBAAgB,EAAE,CAKxE;AAsCD,wBAAgB,iCAAiC,CAC/C,YAAY,EAAE,MAAM,EACpB,eAAe,CAAC,EAAE,MAAM,GACvB,oBAAoB,EAAE,CAWxB"}
@@ -21,6 +21,92 @@ import * as path from "node:path";
21
21
  import { load as parseYAML } from "js-yaml";
22
22
  import { DEFAULT_COORDINATES_PATH } from "../utils/manifest-paths.js";
23
23
  import { mergeCoordinatesWithManifest, readCoordinateArtifact, } from "./symbol-coordinates.js";
24
+ const VALID_RELATIONSHIP_TYPES = new Set([
25
+ "depends_on",
26
+ "executable_for",
27
+ "specified_by",
28
+ "verified_by",
29
+ "validates",
30
+ "implements",
31
+ "covered_by",
32
+ "constrained_by",
33
+ "constrains",
34
+ "requires_property",
35
+ "requires_predicate",
36
+ "guards",
37
+ "publishes",
38
+ "consumes",
39
+ "supersedes",
40
+ "relates_to",
41
+ ]);
42
+ const VALID_RELATIONSHIP_DIRECTIONS = [
43
+ { type: "depends_on", from: "req", to: "req" },
44
+ { type: "executable_for", from: "symbol", to: "test" },
45
+ { type: "specified_by", from: "req", to: "scenario" },
46
+ { type: "verified_by", from: "req", to: "test" },
47
+ { type: "verified_by", from: "scenario", to: "test" },
48
+ { type: "validates", from: "test", to: "req" },
49
+ { type: "validates", from: "test", to: "scenario" },
50
+ { type: "implements", from: "symbol", to: "req" },
51
+ { type: "covered_by", from: "symbol", to: "test" },
52
+ { type: "constrained_by", from: "symbol", to: "adr" },
53
+ { type: "constrains", from: "req", to: "fact" },
54
+ { type: "requires_property", from: "req", to: "fact" },
55
+ { type: "requires_predicate", from: "req", to: "fact" },
56
+ { type: "guards", from: "flag", to: "symbol" },
57
+ { type: "guards", from: "flag", to: "event" },
58
+ { type: "guards", from: "flag", to: "req" },
59
+ { type: "publishes", from: "symbol", to: "event" },
60
+ { type: "consumes", from: "symbol", to: "event" },
61
+ { type: "supersedes", from: "adr", to: "adr" },
62
+ { type: "supersedes", from: "req", to: "req" },
63
+ ];
64
+ const RELATIONSHIP_TYPE_DISPLAY_LIST = Array.from(VALID_RELATIONSHIP_TYPES)
65
+ .sort()
66
+ .join(", ");
67
+ function inferEntityTypeFromId(id) {
68
+ const upper = id.toUpperCase();
69
+ if (upper.startsWith("REQ-"))
70
+ return "req";
71
+ if (upper.startsWith("SCEN-"))
72
+ return "scenario";
73
+ if (upper.startsWith("TEST-"))
74
+ return "test";
75
+ if (upper.startsWith("ADR-"))
76
+ return "adr";
77
+ if (upper.startsWith("FLAG-"))
78
+ return "flag";
79
+ if (upper.startsWith("EVENT-"))
80
+ return "event";
81
+ if (upper.startsWith("SYM-"))
82
+ return "symbol";
83
+ if (upper.startsWith("FACT-"))
84
+ return "fact";
85
+ return null;
86
+ }
87
+ function validateRelationshipType(type, filePath) {
88
+ if (!VALID_RELATIONSHIP_TYPES.has(type)) {
89
+ throw new ManifestError(`Invalid relationship type \"${type}\". Allowed types: ${RELATIONSHIP_TYPE_DISPLAY_LIST}`, filePath);
90
+ }
91
+ }
92
+ function validateRelationshipDirection(type, from, to, filePath) {
93
+ if (type === "relates_to") {
94
+ return;
95
+ }
96
+ const fromType = inferEntityTypeFromId(from);
97
+ const toType = inferEntityTypeFromId(to);
98
+ // If we cannot infer one side from ID prefix, defer to downstream existence/type checks.
99
+ if (!fromType || !toType) {
100
+ return;
101
+ }
102
+ const valid = VALID_RELATIONSHIP_DIRECTIONS.some((rule) => rule.type === type && rule.from === fromType && rule.to === toType);
103
+ if (!valid) {
104
+ const allowed = VALID_RELATIONSHIP_DIRECTIONS.filter((rule) => rule.type === type)
105
+ .map((rule) => `${rule.from} -> ${rule.to}`)
106
+ .join(", ");
107
+ throw new ManifestError(`Invalid relationship direction for \"${type}\": ${fromType} -> ${toType}. Allowed: ${allowed}`, filePath);
108
+ }
109
+ }
24
110
  export class ManifestError extends Error {
25
111
  filePath;
26
112
  constructor(message, filePath) {
@@ -35,7 +121,7 @@ function getManifestSymbols(manifest, filePath) {
35
121
  }
36
122
  return manifest.symbols;
37
123
  }
38
- function extractRelationships(id, symbol) {
124
+ function extractRelationships(id, symbol, filePath) {
39
125
  const relationships = [];
40
126
  if (Array.isArray(symbol.links)) {
41
127
  for (const link of symbol.links) {
@@ -50,6 +136,8 @@ function extractRelationships(id, symbol) {
50
136
  const typedLink = link;
51
137
  if (typeof typedLink.type === "string" &&
52
138
  typeof typedLink.target === "string") {
139
+ validateRelationshipType(typedLink.type, filePath);
140
+ validateRelationshipDirection(typedLink.type, id, typedLink.target, filePath);
53
141
  relationships.push({
54
142
  type: typedLink.type,
55
143
  from: id,
@@ -64,6 +152,8 @@ function extractRelationships(id, symbol) {
64
152
  if (rel &&
65
153
  typeof rel.type === "string" &&
66
154
  typeof rel.target === "string") {
155
+ validateRelationshipType(rel.type, filePath);
156
+ validateRelationshipDirection(rel.type, id, rel.target, filePath);
67
157
  relationships.push({
68
158
  type: rel.type,
69
159
  from: id,
@@ -103,7 +193,7 @@ function extractFromManifestSymbolRecords(manifestSymbols, filePath) {
103
193
  const sourceFile = symbol.sourceFile ?? symbol.source;
104
194
  return {
105
195
  entity,
106
- relationships: extractRelationships(id, symbol),
196
+ relationships: extractRelationships(id, symbol, filePath),
107
197
  ...(sourceFile !== undefined ? { sourceFile } : {}),
108
198
  };
109
199
  });
@@ -1 +1 @@
1
- {"version":3,"file":"markdown.d.ts","sourceRoot":"","sources":["../../src/extractors/markdown.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"markdown.d.ts","sourceRoot":"","sources":["../../src/extractors/markdown.ts"],"names":[],"mappings":"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"}
@@ -58,6 +58,96 @@ const DEFAULT_STATUS_BY_TYPE = {
58
58
  symbol: "active",
59
59
  fact: "active",
60
60
  };
61
+ const VALID_RELATIONSHIP_TYPES = new Set([
62
+ "depends_on",
63
+ "executable_for",
64
+ "specified_by",
65
+ "verified_by",
66
+ "validates",
67
+ "implements",
68
+ "covered_by",
69
+ "constrained_by",
70
+ "constrains",
71
+ "requires_property",
72
+ "requires_predicate",
73
+ "guards",
74
+ "publishes",
75
+ "consumes",
76
+ "supersedes",
77
+ "relates_to",
78
+ ]);
79
+ const VALID_RELATIONSHIP_DIRECTIONS = [
80
+ { type: "depends_on", from: "req", to: "req" },
81
+ { type: "executable_for", from: "symbol", to: "test" },
82
+ { type: "specified_by", from: "req", to: "scenario" },
83
+ { type: "verified_by", from: "req", to: "test" },
84
+ { type: "verified_by", from: "scenario", to: "test" },
85
+ { type: "validates", from: "test", to: "req" },
86
+ { type: "validates", from: "test", to: "scenario" },
87
+ { type: "implements", from: "symbol", to: "req" },
88
+ { type: "covered_by", from: "symbol", to: "test" },
89
+ { type: "constrained_by", from: "symbol", to: "adr" },
90
+ { type: "constrains", from: "req", to: "fact" },
91
+ { type: "requires_property", from: "req", to: "fact" },
92
+ { type: "requires_predicate", from: "req", to: "fact" },
93
+ { type: "guards", from: "flag", to: "symbol" },
94
+ { type: "guards", from: "flag", to: "event" },
95
+ { type: "guards", from: "flag", to: "req" },
96
+ { type: "publishes", from: "symbol", to: "event" },
97
+ { type: "consumes", from: "symbol", to: "event" },
98
+ { type: "supersedes", from: "adr", to: "adr" },
99
+ { type: "supersedes", from: "req", to: "req" },
100
+ ];
101
+ const RELATIONSHIP_TYPE_DISPLAY_LIST = Array.from(VALID_RELATIONSHIP_TYPES)
102
+ .sort()
103
+ .join(", ");
104
+ function inferEntityTypeFromId(id) {
105
+ const upper = id.toUpperCase();
106
+ if (upper.startsWith("REQ-"))
107
+ return "req";
108
+ if (upper.startsWith("SCEN-"))
109
+ return "scenario";
110
+ if (upper.startsWith("TEST-"))
111
+ return "test";
112
+ if (upper.startsWith("ADR-"))
113
+ return "adr";
114
+ if (upper.startsWith("FLAG-"))
115
+ return "flag";
116
+ if (upper.startsWith("EVENT-"))
117
+ return "event";
118
+ if (upper.startsWith("SYM-"))
119
+ return "symbol";
120
+ if (upper.startsWith("FACT-"))
121
+ return "fact";
122
+ return null;
123
+ }
124
+ function validateRelationshipType(type, filePath) {
125
+ if (!VALID_RELATIONSHIP_TYPES.has(type)) {
126
+ throw new FrontmatterError(`Invalid relationship type \"${type}\". Allowed types: ${RELATIONSHIP_TYPE_DISPLAY_LIST}`, filePath, {
127
+ classification: "Invalid Relationship Type",
128
+ hint: "Use one of the supported relationship types from relationship.schema.json.",
129
+ });
130
+ }
131
+ }
132
+ function validateRelationshipDirection(type, fromType, toId, filePath) {
133
+ if (type === "relates_to") {
134
+ return;
135
+ }
136
+ const toType = inferEntityTypeFromId(toId);
137
+ if (!toType) {
138
+ return;
139
+ }
140
+ const valid = VALID_RELATIONSHIP_DIRECTIONS.some((rule) => rule.type === type && rule.from === fromType && rule.to === toType);
141
+ if (!valid) {
142
+ const allowed = VALID_RELATIONSHIP_DIRECTIONS.filter((rule) => rule.type === type)
143
+ .map((rule) => `${rule.from} -> ${rule.to}`)
144
+ .join(", ");
145
+ throw new FrontmatterError(`Invalid relationship direction for \"${type}\": ${fromType} -> ${toType}. Allowed: ${allowed}`, filePath, {
146
+ classification: "Invalid Relationship Direction",
147
+ hint: "Reverse the link direction or use the correct relationship type for these entity kinds.",
148
+ });
149
+ }
150
+ }
61
151
  export class FrontmatterError extends Error {
62
152
  filePath;
63
153
  classification;
@@ -224,6 +314,8 @@ function extractFromMarkdownContent(content, filePath) {
224
314
  typeof link === "object" &&
225
315
  typeof link.type === "string" &&
226
316
  typeof link.target === "string") {
317
+ validateRelationshipType(link.type, filePath);
318
+ validateRelationshipDirection(link.type, type, link.target, filePath);
227
319
  relationships.push({
228
320
  type: link.type,
229
321
  from: id,
@@ -1 +1 @@
1
- {"version":3,"file":"relationships.d.ts","sourceRoot":"","sources":["../../src/extractors/relationships.ts"],"names":[],"mappings":"AAsBA,MAAM,WAAW,qBAAqB;IACpC,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,EAAE,EAAE,MAAM,CAAC;IACX,QAAQ,CAAC,EAAE;QACT,UAAU,CAAC,EAAE,MAAM,CAAC;QACpB,UAAU,CAAC,EAAE,MAAM,CAAC;QACpB,MAAM,CAAC,EAAE,MAAM,CAAC;QAChB,UAAU,CAAC,EAAE,MAAM,CAAC;KACrB,CAAC;CACH;AAED,MAAM,WAAW,qBAAqB;IACpC,SAAS,EAAE,MAAM,CAAC;IAClB,aAAa,EAAE,qBAAqB,EAAE,CAAC;CACxC;AAsBD;;;;;GAKG;AACH,wBAAgB,6BAA6B,CAC3C,gBAAgB,EAAE,MAAM,GACvB,qBAAqB,EAAE,CAyCzB;AAmDD;;GAEG;AACH,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAE1D;AAED;;GAEG;AACH,wBAAgB,oBAAoB,CAClC,OAAO,EAAE,qBAAqB,EAAE,GAC/B,qBAAqB,EAAE,CAEzB;AAED;;;GAGG;AACH,wBAAgB,qBAAqB,CACnC,aAAa,EAAE,qBAAqB,EAAE,EACtC,cAAc,EAAE,GAAG,CAAC,MAAM,CAAC,GAC1B,KAAK,CAAC;IACP,YAAY,EAAE,qBAAqB,CAAC;IACpC,KAAK,EAAE,cAAc,GAAG,YAAY,CAAC;CACtC,CAAC,CAgBD"}
1
+ {"version":3,"file":"relationships.d.ts","sourceRoot":"","sources":["../../src/extractors/relationships.ts"],"names":[],"mappings":"AAsBA,MAAM,WAAW,qBAAqB;IACpC,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,EAAE,EAAE,MAAM,CAAC;IACX,QAAQ,CAAC,EAAE;QACT,UAAU,CAAC,EAAE,MAAM,CAAC;QACpB,UAAU,CAAC,EAAE,MAAM,CAAC;QACpB,MAAM,CAAC,EAAE,MAAM,CAAC;QAChB,UAAU,CAAC,EAAE,MAAM,CAAC;KACrB,CAAC;CACH;AAED,MAAM,WAAW,qBAAqB;IACpC,SAAS,EAAE,MAAM,CAAC;IAClB,aAAa,EAAE,qBAAqB,EAAE,CAAC;CACxC;AAwBD;;;;;GAKG;AACH,wBAAgB,6BAA6B,CAC3C,gBAAgB,EAAE,MAAM,GACvB,qBAAqB,EAAE,CAyCzB;AAmDD;;GAEG;AACH,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAE1D;AAED;;GAEG;AACH,wBAAgB,oBAAoB,CAClC,OAAO,EAAE,qBAAqB,EAAE,GAC/B,qBAAqB,EAAE,CAEzB;AAED;;;GAGG;AACH,wBAAgB,qBAAqB,CACnC,aAAa,EAAE,qBAAqB,EAAE,EACtC,cAAc,EAAE,GAAG,CAAC,MAAM,CAAC,GAC1B,KAAK,CAAC;IACP,YAAY,EAAE,qBAAqB,CAAC;IACpC,KAAK,EAAE,cAAc,GAAG,YAAY,CAAC;CACtC,CAAC,CAgBD"}
@@ -23,6 +23,7 @@ import { readShard } from "../relationships/shards.js";
23
23
  */
24
24
  const VALID_RELATIONSHIP_TYPES = new Set([
25
25
  "depends_on",
26
+ "executable_for",
26
27
  "specified_by",
27
28
  "verified_by",
28
29
  "validates",
@@ -31,6 +32,7 @@ const VALID_RELATIONSHIP_TYPES = new Set([
31
32
  "constrained_by",
32
33
  "constrains",
33
34
  "requires_property",
35
+ "requires_predicate",
34
36
  "guards",
35
37
  "publishes",
36
38
  "consumes",
@@ -1 +1 @@
1
- {"version":3,"file":"prolog.d.ts","sourceRoot":"","sources":["../src/prolog.ts"],"names":[],"mappings":"AA4BA,wBAAgB,eAAe,IAAI,MAAM,CAsCxC;AACD,MAAM,WAAW,aAAa;IAC5B,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,WAAW;IAC1B,OAAO,EAAE,OAAO,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACjC,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,qBAAa,aAAa;IACxB,OAAO,CAAC,OAAO,CAA6B;IAC5C,OAAO,CAAC,SAAS,CAAS;IAC1B,OAAO,CAAC,OAAO,CAAS;IACxB,OAAO,CAAC,YAAY,CAAM;IAC1B,OAAO,CAAC,WAAW,CAAM;IACzB,OAAO,CAAC,KAAK,CAAuC;IACpD,OAAO,CAAC,oBAAoB,CAAoC;IAChE,OAAO,CAAC,kBAAkB,CAA8B;IACxD,OAAO,CAAC,cAAc,CACyC;IAC/D,OAAO,CAAC,cAAc,CAAuB;IAC7C,OAAO,CAAC,aAAa,CAA6B;gBAEtC,OAAO,GAAE,aAAkB;IAKjC,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;YAuCd,YAAY;IA0CpB,KAAK,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,EAAE,GAAG,OAAO,CAAC,WAAW,CAAC;IAmK1D,eAAe,IAAI,IAAI;IAIvB,OAAO,CAAC,eAAe;IAavB,OAAO,CAAC,cAAc;IAStB,OAAO,CAAC,kBAAkB;YAIZ,YAAY;IA2C1B,OAAO,CAAC,WAAW;IAsGnB,OAAO,CAAC,aAAa;IAIrB,OAAO,CAAC,iBAAiB;IAMzB,OAAO,CAAC,eAAe;IAQvB,OAAO,CAAC,eAAe;IAiBvB,OAAO,CAAC,cAAc;IA+BtB,SAAS,IAAI,OAAO;IAMpB,MAAM,IAAI,MAAM;IAIV,SAAS,IAAI,OAAO,CAAC,IAAI,CAAC;CAwCjC"}
1
+ {"version":3,"file":"prolog.d.ts","sourceRoot":"","sources":["../src/prolog.ts"],"names":[],"mappings":"AA4BA,wBAAgB,eAAe,IAAI,MAAM,CAsCxC;AACD,MAAM,WAAW,aAAa;IAC5B,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,WAAW;IAC1B,OAAO,EAAE,OAAO,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACjC,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,qBAAa,aAAa;IACxB,OAAO,CAAC,OAAO,CAA6B;IAC5C,OAAO,CAAC,SAAS,CAAS;IAC1B,OAAO,CAAC,OAAO,CAAS;IACxB,OAAO,CAAC,YAAY,CAAM;IAC1B,OAAO,CAAC,WAAW,CAAM;IACzB,OAAO,CAAC,KAAK,CAAuC;IACpD,OAAO,CAAC,oBAAoB,CAAoC;IAChE,OAAO,CAAC,kBAAkB,CAA8B;IACxD,OAAO,CAAC,cAAc,CACyC;IAC/D,OAAO,CAAC,cAAc,CAAuB;IAC7C,OAAO,CAAC,aAAa,CAA6B;gBAEtC,OAAO,GAAE,aAAkB;IAKjC,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;YAuCd,YAAY;IA0CpB,KAAK,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,EAAE,GAAG,OAAO,CAAC,WAAW,CAAC;IA0K1D,eAAe,IAAI,IAAI;IAIvB,OAAO,CAAC,eAAe;IAavB,OAAO,CAAC,cAAc;IAStB,OAAO,CAAC,kBAAkB;YAIZ,YAAY;IA2C1B,OAAO,CAAC,WAAW;IAsGnB,OAAO,CAAC,aAAa;IAIrB,OAAO,CAAC,iBAAiB;IAMzB,OAAO,CAAC,eAAe;IAQvB,OAAO,CAAC,eAAe;IAiBvB,OAAO,CAAC,cAAc;IAqDtB,SAAS,IAAI,OAAO;IAMpB,MAAM,IAAI,MAAM;IAIV,SAAS,IAAI,OAAO,CAAC,IAAI,CAAC;CAwCjC"}
package/dist/prolog.js CHANGED
@@ -165,8 +165,8 @@ export class PrologProcess {
165
165
  const debug = isPrologDebugEnabled();
166
166
  const normalizedGoal = this.normalizeGoal(goal);
167
167
  const wrappedGoal = /^once\s*\(/.test(normalizedGoal)
168
- ? normalizedGoal
169
- : `once((${normalizedGoal}))`;
168
+ ? `catch(${normalizedGoal}, _E, (print_message(error, _E), fail))`
169
+ : `catch(once((${normalizedGoal})), _E, (print_message(error, _E), fail))`;
170
170
  const start = Date.now();
171
171
  if (debug) {
172
172
  console.error(`[prolog debug] start query: ${normalizedGoal}`);
@@ -240,10 +240,16 @@ export class PrologProcess {
240
240
  if (debug) {
241
241
  console.error(`[prolog debug] query failed (false): ${normalizedGoal}`);
242
242
  }
243
+ // Check errorBuffer first — Prolog catch/3 writes ERROR to stderr
244
+ // then fail to stdout. If stderr has the real error, surface it.
245
+ const errorMessage = this.errorBuffer.length > 0 &&
246
+ this.errorBuffer.includes("ERROR")
247
+ ? this.translateError(this.errorBuffer)
248
+ : "Query failed";
243
249
  resolve({
244
250
  success: false,
245
251
  bindings: {},
246
- error: "Query failed",
252
+ error: errorMessage,
247
253
  });
248
254
  return;
249
255
  }
@@ -436,6 +442,30 @@ export class PrologProcess {
436
442
  return bindings;
437
443
  }
438
444
  translateError(errorText) {
445
+ // SWI-Prolog print_message/2 formats errors as human-readable messages,
446
+ // not raw Prolog terms. Match the actual output format.
447
+ if (errorText.includes('does not exist') && /entity [`'"].+?[`'"]/.test(errorText)) {
448
+ // SWI-Prolog doubles single quotes in formatted messages: ''REQ-TEST''
449
+ const entityIdMatch = errorText.match(/entity [`'"]+(.+?)[`'"]+ does not exist/);
450
+ const entityId = entityIdMatch
451
+ ? entityIdMatch[1].replace(/^`?'+|'+`?$/g, "")
452
+ : "unknown";
453
+ if (errorText.includes("Target entity does not exist")) {
454
+ return `Target entity does not exist: ${entityId}`;
455
+ }
456
+ if (errorText.includes("Source entity does not exist")) {
457
+ return `Source entity does not exist: ${entityId}`;
458
+ }
459
+ return `Entity does not exist: ${entityId}`;
460
+ }
461
+ if (errorText.includes("Type error: `relationship' expected")) {
462
+ const relMatch = errorText.match(/\(Invalid relationship: ([^)]+)\)/);
463
+ if (relMatch) {
464
+ return `Invalid relationship: ${relMatch[1].trim()}`;
465
+ }
466
+ return "Invalid relationship type or direction";
467
+ }
468
+ // Fallback: check for raw Prolog error terms (used by other tools)
439
469
  if (errorText.includes("existence_error") ||
440
470
  errorText.includes("Unknown procedure")) {
441
471
  return "Predicate or file not found";
@@ -1 +1 @@
1
- {"version":3,"file":"entity.d.ts","sourceRoot":"","sources":["../../../src/public/schemas/entity.ts"],"names":[],"mappings":"AA+EA,QAAA,MAAM,YAAY,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CA2JzC,CAAC;AAEF,eAAe,YAAY,CAAC"}
1
+ {"version":3,"file":"entity.d.ts","sourceRoot":"","sources":["../../../src/public/schemas/entity.ts"],"names":[],"mappings":"AAiFA,QAAA,MAAM,YAAY,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CA+JzC,CAAC;AAEF,eAAe,YAAY,CAAC"}
@@ -15,6 +15,8 @@
15
15
  You should have received a copy of the GNU Affero General Public License
16
16
  along with this program. If not, see <https://www.gnu.org/licenses/>.
17
17
  */
18
+ // Typed fact field enums per proposal
19
+ import { SYMBOL_ROLES } from "../symbol-granularity.js";
18
20
  const factConditionals = JSON.parse(`[
19
21
  {
20
22
  "if": {
@@ -113,6 +115,10 @@ const entitySchema = {
113
115
  "legacy-link",
114
116
  ],
115
117
  },
118
+ symbol_role: {
119
+ type: "string",
120
+ enum: [...SYMBOL_ROLES],
121
+ },
116
122
  type: {
117
123
  type: "string",
118
124
  enum: [
@@ -0,0 +1,23 @@
1
+ export declare const SYMBOL_ROLES: readonly ["behavioral", "structural", "type-shape", "config", "module", "unknown"];
2
+ export type SymbolRole = (typeof SYMBOL_ROLES)[number];
3
+ export declare const TRACEABILITY_RELATIONSHIP_TYPES: readonly ["implements", "covered_by", "executable_for"];
4
+ export type TraceabilityRelationshipType = (typeof TRACEABILITY_RELATIONSHIP_TYPES)[number];
5
+ export declare const ALLOWED_GRANULARITY_REASONS: readonly ["config-artifact", "module-level-behavior", "extractor-miss", "legacy-link"];
6
+ export type GranularityReason = (typeof ALLOWED_GRANULARITY_REASONS)[number];
7
+ export type SymbolKind = "function" | "class" | "method" | "interface" | "type" | "variable" | "enum" | "unknown";
8
+ export type SourceSymbolKind = SymbolKind;
9
+ export interface GranularSymbolCandidate {
10
+ name: string;
11
+ kind?: SymbolKind;
12
+ role?: SymbolRole;
13
+ }
14
+ export declare function inferSymbolRole(kind: SymbolKind): SymbolRole;
15
+ export declare const inferSymbolRoleFromKind: typeof inferSymbolRole;
16
+ export declare function isSymbolRole(value: unknown): value is SymbolRole;
17
+ export declare function isTraceabilityRelationshipType(value: unknown): value is TraceabilityRelationshipType;
18
+ export declare function isAllowedGranularityReason(value: unknown): value is GranularityReason;
19
+ export declare function getSymbolRole(candidate: GranularSymbolCandidate): SymbolRole;
20
+ export declare function isBehavioralSymbol(candidate: GranularSymbolCandidate): boolean;
21
+ export declare function getBehavioralSymbolNames(candidates: GranularSymbolCandidate[]): string[];
22
+ export declare function getNonBehavioralSymbolNames(candidates: GranularSymbolCandidate[]): string[];
23
+ //# sourceMappingURL=symbol-granularity.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"symbol-granularity.d.ts","sourceRoot":"","sources":["../../src/public/symbol-granularity.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,YAAY,oFAOf,CAAC;AAEX,MAAM,MAAM,UAAU,GAAG,CAAC,OAAO,YAAY,CAAC,CAAC,MAAM,CAAC,CAAC;AAEvD,eAAO,MAAM,+BAA+B,yDAIlC,CAAC;AAEX,MAAM,MAAM,4BAA4B,GACtC,CAAC,OAAO,+BAA+B,CAAC,CAAC,MAAM,CAAC,CAAC;AAEnD,eAAO,MAAM,2BAA2B,wFAK9B,CAAC;AAEX,MAAM,MAAM,iBAAiB,GAAG,CAAC,OAAO,2BAA2B,CAAC,CAAC,MAAM,CAAC,CAAC;AAE7E,MAAM,MAAM,UAAU,GAClB,UAAU,GACV,OAAO,GACP,QAAQ,GACR,WAAW,GACX,MAAM,GACN,UAAU,GACV,MAAM,GACN,SAAS,CAAC;AAEd,MAAM,MAAM,gBAAgB,GAAG,UAAU,CAAC;AAE1C,MAAM,WAAW,uBAAuB;IACtC,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,CAAC,EAAE,UAAU,CAAC;IAClB,IAAI,CAAC,EAAE,UAAU,CAAC;CACnB;AASD,wBAAgB,eAAe,CAAC,IAAI,EAAE,UAAU,GAAG,UAAU,CAc5D;AAED,eAAO,MAAM,uBAAuB,wBAAkB,CAAC;AAEvD,wBAAgB,YAAY,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,UAAU,CAIhE;AAED,wBAAgB,8BAA8B,CAC5C,KAAK,EAAE,OAAO,GACb,KAAK,IAAI,4BAA4B,CAIvC;AAED,wBAAgB,0BAA0B,CACxC,KAAK,EAAE,OAAO,GACb,KAAK,IAAI,iBAAiB,CAE5B;AAED,wBAAgB,aAAa,CAAC,SAAS,EAAE,uBAAuB,GAAG,UAAU,CAI5E;AAED,wBAAgB,kBAAkB,CAChC,SAAS,EAAE,uBAAuB,GACjC,OAAO,CAET;AAED,wBAAgB,wBAAwB,CACtC,UAAU,EAAE,uBAAuB,EAAE,GACpC,MAAM,EAAE,CAMV;AAED,wBAAgB,2BAA2B,CACzC,UAAU,EAAE,uBAAuB,EAAE,GACpC,MAAM,EAAE,CAQV"}
@@ -0,0 +1,68 @@
1
+ export const SYMBOL_ROLES = [
2
+ "behavioral",
3
+ "structural",
4
+ "type-shape",
5
+ "config",
6
+ "module",
7
+ "unknown",
8
+ ];
9
+ export const TRACEABILITY_RELATIONSHIP_TYPES = [
10
+ "implements",
11
+ "covered_by",
12
+ "executable_for",
13
+ ];
14
+ export const ALLOWED_GRANULARITY_REASONS = [
15
+ "config-artifact",
16
+ "module-level-behavior",
17
+ "extractor-miss",
18
+ "legacy-link",
19
+ ];
20
+ const traceabilityRelationshipTypeSet = new Set(TRACEABILITY_RELATIONSHIP_TYPES);
21
+ const allowedGranularityReasonSet = new Set(ALLOWED_GRANULARITY_REASONS);
22
+ export function inferSymbolRole(kind) {
23
+ switch (kind) {
24
+ case "function":
25
+ case "class":
26
+ case "method":
27
+ return "behavioral";
28
+ case "interface":
29
+ case "type":
30
+ case "enum":
31
+ return "type-shape";
32
+ case "variable":
33
+ case "unknown":
34
+ return "unknown";
35
+ }
36
+ }
37
+ export const inferSymbolRoleFromKind = inferSymbolRole;
38
+ export function isSymbolRole(value) {
39
+ return (typeof value === "string" && SYMBOL_ROLES.some((role) => role === value));
40
+ }
41
+ export function isTraceabilityRelationshipType(value) {
42
+ return (typeof value === "string" && traceabilityRelationshipTypeSet.has(value));
43
+ }
44
+ export function isAllowedGranularityReason(value) {
45
+ return typeof value === "string" && allowedGranularityReasonSet.has(value);
46
+ }
47
+ export function getSymbolRole(candidate) {
48
+ if (candidate.role)
49
+ return candidate.role;
50
+ if (candidate.kind)
51
+ return inferSymbolRole(candidate.kind);
52
+ return "unknown";
53
+ }
54
+ export function isBehavioralSymbol(candidate) {
55
+ return getSymbolRole(candidate) === "behavioral";
56
+ }
57
+ export function getBehavioralSymbolNames(candidates) {
58
+ return [
59
+ ...new Set(candidates.filter(isBehavioralSymbol).map((candidate) => candidate.name)),
60
+ ].sort();
61
+ }
62
+ export function getNonBehavioralSymbolNames(candidates) {
63
+ return [
64
+ ...new Set(candidates
65
+ .filter((candidate) => !isBehavioralSymbol(candidate))
66
+ .map((candidate) => candidate.name)),
67
+ ].sort();
68
+ }
@@ -48,6 +48,17 @@
48
48
  "legacy-link"
49
49
  ]
50
50
  },
51
+ "symbol_role": {
52
+ "type": "string",
53
+ "enum": [
54
+ "behavioral",
55
+ "structural",
56
+ "type-shape",
57
+ "config",
58
+ "module",
59
+ "unknown"
60
+ ]
61
+ },
51
62
  "verification_scope": {
52
63
  "type": "string",
53
64
  "enum": ["unit", "integration", "end_to_end"]
@@ -1,3 +1,4 @@
1
+ import { type SymbolKind, type SymbolRole } from "../public/symbol-granularity.js";
1
2
  import type { HunkRange, StagedFile } from "./git-staged.js";
2
3
  type TraceabilityRelationship = {
3
4
  type: string;
@@ -6,7 +7,8 @@ type TraceabilityRelationship = {
6
7
  export interface ExtractedSymbol {
7
8
  id: string;
8
9
  name: string;
9
- kind: "function" | "class" | "method" | "interface" | "type" | "variable" | "enum" | "unknown";
10
+ kind: SymbolKind;
11
+ role: SymbolRole;
10
12
  location: {
11
13
  file: string;
12
14
  startLine: number;
@@ -1 +1 @@
1
- {"version":3,"file":"symbol-extract.d.ts","sourceRoot":"","sources":["../../src/traceability/symbol-extract.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,SAAS,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAE7D,KAAK,wBAAwB,GAAG;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,EAAE,EAAE,MAAM,CAAA;CAAE,CAAC;AAU7D,MAAM,WAAW,eAAe;IAC9B,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EACA,UAAU,GACV,OAAO,GACP,QAAQ,GACR,WAAW,GACX,MAAM,GACN,UAAU,GACV,MAAM,GACN,SAAS,CAAC;IACd,QAAQ,EAAE;QACR,IAAI,EAAE,MAAM,CAAC;QACb,SAAS,EAAE,MAAM,CAAC;QAClB,OAAO,EAAE,MAAM,CAAC;KACjB,CAAC;IACF,UAAU,EAAE,SAAS,EAAE,CAAC;IACxB,QAAQ,EAAE,MAAM,EAAE,CAAC;IACnB,aAAa,CAAC,EAAE,wBAAwB,EAAE,CAAC;CAC5C;AAED,MAAM,WAAW,mBAAmB;IAClC,EAAE,EAAE,MAAM,CAAC;IACX,aAAa,CAAC,EAAE,wBAAwB,EAAE,CAAC;CAC5C;AAED,MAAM,MAAM,cAAc,GAAG,GAAG,CAAC,MAAM,EAAE,mBAAmB,CAAC,CAAC;AAE9D,wBAAgB,+BAA+B,CAAC,YAAY,EAAE,MAAM,GAAG,MAAM,CAG5E;AAkOD,wBAAgB,4BAA4B,CAE1C,UAAU,EAAE,UAAU,EACtB,cAAc,CAAC,EAAE,cAAc,GAC9B,eAAe,EAAE,CAyOnB"}
1
+ {"version":3,"file":"symbol-extract.d.ts","sourceRoot":"","sources":["../../src/traceability/symbol-extract.ts"],"names":[],"mappings":"AAGA,OAAO,EACL,KAAK,UAAU,EACf,KAAK,UAAU,EAEhB,MAAM,iCAAiC,CAAC;AACzC,OAAO,KAAK,EAAE,SAAS,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAE7D,KAAK,wBAAwB,GAAG;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,EAAE,EAAE,MAAM,CAAA;CAAE,CAAC;AAU7D,MAAM,WAAW,eAAe;IAC9B,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,UAAU,CAAC;IACjB,IAAI,EAAE,UAAU,CAAC;IACjB,QAAQ,EAAE;QACR,IAAI,EAAE,MAAM,CAAC;QACb,SAAS,EAAE,MAAM,CAAC;QAClB,OAAO,EAAE,MAAM,CAAC;KACjB,CAAC;IACF,UAAU,EAAE,SAAS,EAAE,CAAC;IACxB,QAAQ,EAAE,MAAM,EAAE,CAAC;IACnB,aAAa,CAAC,EAAE,wBAAwB,EAAE,CAAC;CAC5C;AAED,MAAM,WAAW,mBAAmB;IAClC,EAAE,EAAE,MAAM,CAAC;IACX,aAAa,CAAC,EAAE,wBAAwB,EAAE,CAAC;CAC5C;AAED,MAAM,MAAM,cAAc,GAAG,GAAG,CAAC,MAAM,EAAE,mBAAmB,CAAC,CAAC;AAE9D,wBAAgB,+BAA+B,CAAC,YAAY,EAAE,MAAM,GAAG,MAAM,CAG5E;AAmOD,wBAAgB,4BAA4B,CAE1C,UAAU,EAAE,UAAU,EACtB,cAAc,CAAC,EAAE,cAAc,GAC9B,eAAe,EAAE,CAyOnB"}
@@ -1,6 +1,7 @@
1
1
  import { createHash } from "node:crypto";
2
2
  import { Project, ScriptKind } from "ts-morph";
3
3
  import { readManifestWithCoordinateOverlay } from "../extractors/manifest.js";
4
+ import { inferSymbolRole, } from "../public/symbol-granularity.js";
4
5
  const TRACEABILITY_RELATIONSHIP_TYPES = new Set([
5
6
  "implements",
6
7
  "covered_by",
@@ -109,6 +110,7 @@ function buildSymbolResult(stagedFile, name, kind, span, inlineReqLinks, manifes
109
110
  id,
110
111
  name,
111
112
  kind,
113
+ role: inferSymbolRole(kind),
112
114
  location: {
113
115
  file: stagedFile.path,
114
116
  startLine: span.startLine,
@@ -1 +1 @@
1
- {"version":3,"file":"temp-kb.d.ts","sourceRoot":"","sources":["../../src/traceability/temp-kb.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAGV,gBAAgB,EACjB,MAAM,2BAA2B,CAAC;AACnC,OAAO,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAE7C,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,kBAAkB,CAAC;AAExD,MAAM,WAAW,aAAa;IAC5B,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;IACf,WAAW,EAAE,MAAM,CAAC;IACpB,MAAM,EAAE,aAAa,CAAC;CACvB;AAMD;;;GAGG;AACH,wBAAgB,gBAAgB,IAAI,IAAI,CASvC;AAOD;;;GAGG;AACH,wBAAgB,iBAAiB,CAE/B,OAAO,EAAE,CAAC,IAAI,EAAE;IAAE,OAAO,EAAE,MAAM,CAAA;CAAE,KAAK,aAAa,GACpD,IAAI,CAEN;AA4ID,iBAAe,cAAc,CAAC,GAAG,EAAE,aAAa,GAAG,OAAO,CAAC,IAAI,CAAC,CAgB/D;AAED,OAAO,EAAE,cAAc,EAAE,CAAC;AAG1B,wBAAsB,qBAAqB,CACzC,MAAM,EAAE,aAAa,EACrB,OAAO,EAAE,gBAAgB,EAAE,GAC1B,OAAO,CAAC,IAAI,CAAC,CAwBf;AAED,wBAAsB,YAAY,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,aAAa,CAAC,CA6C7E;AAGD,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,eAAe,EAAE,GAAG,MAAM,CAkBrE;AAED,wBAAsB,aAAa,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CA2BlE"}
1
+ {"version":3,"file":"temp-kb.d.ts","sourceRoot":"","sources":["../../src/traceability/temp-kb.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAGV,gBAAgB,EACjB,MAAM,2BAA2B,CAAC;AACnC,OAAO,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAE7C,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,kBAAkB,CAAC;AAExD,MAAM,WAAW,aAAa;IAC5B,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;IACf,WAAW,EAAE,MAAM,CAAC;IACpB,MAAM,EAAE,aAAa,CAAC;CACvB;AAMD;;;GAGG;AACH,wBAAgB,gBAAgB,IAAI,IAAI,CASvC;AAOD;;;GAGG;AACH,wBAAgB,iBAAiB,CAE/B,OAAO,EAAE,CAAC,IAAI,EAAE;IAAE,OAAO,EAAE,MAAM,CAAA;CAAE,KAAK,aAAa,GACpD,IAAI,CAEN;AA4ID,iBAAe,cAAc,CAAC,GAAG,EAAE,aAAa,GAAG,OAAO,CAAC,IAAI,CAAC,CAgB/D;AAED,OAAO,EAAE,cAAc,EAAE,CAAC;AAG1B,wBAAsB,qBAAqB,CACzC,MAAM,EAAE,aAAa,EACrB,OAAO,EAAE,gBAAgB,EAAE,GAC1B,OAAO,CAAC,IAAI,CAAC,CA6Bf;AAED,wBAAsB,YAAY,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,aAAa,CAAC,CA6C7E;AAGD,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,eAAe,EAAE,GAAG,MAAM,CAkBrE;AAED,wBAAsB,aAAa,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CA2BlE"}
@@ -170,6 +170,9 @@ export { consultOverlay };
170
170
  // implements REQ-014
171
171
  export async function projectStagedEntities(prolog, results) {
172
172
  for (const { entity } of results) {
173
+ // Retract stale relationships before re-asserting entity
174
+ // (kb_assert_entity_no_audit preserves relationships on upsert)
175
+ await prolog.query(`kb_retract_entity_relationships('${entity.id}')`);
173
176
  const assertEntityResult = await prolog.query(buildEntityAssertionGoal(entity));
174
177
  if (!assertEntityResult.success) {
175
178
  throw new Error(`Failed to assert staged entity ${entity.id}: ${assertEntityResult.error || "unknown error"}`);
@@ -1,5 +1,5 @@
1
1
  export interface BaseRelationship {
2
- type: "depends_on" | "specified_by" | "verified_by" | "implements" | "covered_by" | "constrained_by" | "constrains" | "requires_property" | "requires_predicate" | "guards" | "publishes" | "consumes" | "supersedes" | "relates_to";
2
+ type: "depends_on" | "specified_by" | "verified_by" | "validates" | "implements" | "covered_by" | "executable_for" | "constrained_by" | "constrains" | "requires_property" | "requires_predicate" | "guards" | "publishes" | "consumes" | "supersedes" | "relates_to";
3
3
  from: string;
4
4
  to: string;
5
5
  created_at?: string;
@@ -1 +1 @@
1
- {"version":3,"file":"relationships.d.ts","sourceRoot":"","sources":["../../src/types/relationships.ts"],"names":[],"mappings":"AAkBA,MAAM,WAAW,gBAAgB;IAC/B,IAAI,EACA,YAAY,GACZ,cAAc,GACd,aAAa,GACb,YAAY,GACZ,YAAY,GACZ,gBAAgB,GAChB,YAAY,GACZ,mBAAmB,GACnB,oBAAoB,GACpB,QAAQ,GACR,WAAW,GACX,UAAU,GACV,YAAY,GACZ,YAAY,CAAC;IACjB,IAAI,EAAE,MAAM,CAAC;IACb,EAAE,EAAE,MAAM,CAAC;IACX,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,eAAe,gBAAgB,CAAC"}
1
+ {"version":3,"file":"relationships.d.ts","sourceRoot":"","sources":["../../src/types/relationships.ts"],"names":[],"mappings":"AAkBA,MAAM,WAAW,gBAAgB;IAC/B,IAAI,EACA,YAAY,GACZ,cAAc,GACd,aAAa,GACb,WAAW,GACX,YAAY,GACZ,YAAY,GACZ,gBAAgB,GAChB,gBAAgB,GAChB,YAAY,GACZ,mBAAmB,GACnB,oBAAoB,GACpB,QAAQ,GACR,WAAW,GACX,UAAU,GACV,YAAY,GACZ,YAAY,CAAC;IACjB,IAAI,EAAE,MAAM,CAAC;IACb,EAAE,EAAE,MAAM,CAAC;IACX,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,eAAe,gBAAgB,CAAC"}
@@ -19,6 +19,7 @@ export interface KbConfigPaths {
19
19
  export interface KbConfig {
20
20
  paths: KbConfigPaths;
21
21
  schemaVersion?: number | string;
22
+ semanticAdvisorBackfill?: "pending" | "completed" | "not_applicable";
22
23
  /**
23
24
  * @deprecated defaultBranch is deprecated. Branch lifecycle now follows git naturally
24
25
  * without requiring a configured default. This field is ignored but kept for compatibility.
@@ -1 +1 @@
1
- {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../src/utils/config.ts"],"names":[],"mappings":"AAoBA,OAAO,EACL,KAAK,YAAY,EAEjB,KAAK,yBAAyB,EAC/B,MAAM,oBAAoB,CAAC;AAG5B;;GAEG;AACH,MAAM,WAAW,aAAa;IAC5B,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED;;;GAGG;AACH,MAAM,WAAW,QAAQ;IACvB,KAAK,EAAE,aAAa,CAAC;IACrB,aAAa,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IAChC;;;OAGG;IACH,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,MAAM,CAAC,EAAE,YAAY,CAAC;CACvB;AAED,YAAY,EAAE,YAAY,EAAE,yBAAyB,EAAE,CAAC;AAExD;;GAEG;AAEH,eAAO,MAAM,cAAc,EAAE,QAAQ,GAAG;IAAE,OAAO,EAAE,MAAM,CAAA;CAgBxD,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,kBAAkB,EAAE,aAUhC,CAAC;AA4BF;;;;;;GAMG;AACH,wBAAgB,UAAU,CAAC,GAAG,GAAE,MAAsB,GAAG,QAAQ,CAgChE;AAED;;;;;;;GAOG;AACH,wBAAgB,cAAc,CAAC,GAAG,GAAE,MAAsB,GAAG,QAAQ,CAgCpE"}
1
+ {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../src/utils/config.ts"],"names":[],"mappings":"AAoBA,OAAO,EACL,KAAK,YAAY,EAEjB,KAAK,yBAAyB,EAC/B,MAAM,oBAAoB,CAAC;AAG5B;;GAEG;AACH,MAAM,WAAW,aAAa;IAC5B,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED;;;GAGG;AACH,MAAM,WAAW,QAAQ;IACvB,KAAK,EAAE,aAAa,CAAC;IACrB,aAAa,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IAChC,uBAAuB,CAAC,EAAE,SAAS,GAAG,WAAW,GAAG,gBAAgB,CAAC;IACrE;;;OAGG;IACH,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,MAAM,CAAC,EAAE,YAAY,CAAC;CACvB;AAED,YAAY,EAAE,YAAY,EAAE,yBAAyB,EAAE,CAAC;AAExD;;GAEG;AAEH,eAAO,MAAM,cAAc,EAAE,QAAQ,GAAG;IAAE,OAAO,EAAE,MAAM,CAAA;CAgBxD,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,kBAAkB,EAAE,aAUhC,CAAC;AA4BF;;;;;;GAMG;AACH,wBAAgB,UAAU,CAAC,GAAG,GAAE,MAAsB,GAAG,QAAQ,CAmChE;AAED;;;;;;;GAOG;AACH,wBAAgB,cAAc,CAAC,GAAG,GAAE,MAAsB,GAAG,QAAQ,CAmCpE"}
@@ -98,6 +98,9 @@ export function loadConfig(cwd = process.cwd()) {
98
98
  ...(userConfig.defaultBranch !== undefined
99
99
  ? { defaultBranch: userConfig.defaultBranch }
100
100
  : {}),
101
+ ...(userConfig.semanticAdvisorBackfill !== undefined
102
+ ? { semanticAdvisorBackfill: userConfig.semanticAdvisorBackfill }
103
+ : {}),
101
104
  checks: userConfig.checks
102
105
  ? {
103
106
  rules: {
@@ -137,6 +140,9 @@ export function loadSyncConfig(cwd = process.cwd()) {
137
140
  ...(userConfig.defaultBranch !== undefined
138
141
  ? { defaultBranch: userConfig.defaultBranch }
139
142
  : {}),
143
+ ...(userConfig.semanticAdvisorBackfill !== undefined
144
+ ? { semanticAdvisorBackfill: userConfig.semanticAdvisorBackfill }
145
+ : {}),
140
146
  checks: userConfig.checks
141
147
  ? {
142
148
  rules: {
@@ -1,5 +1,5 @@
1
1
  import type { KbConfig } from "./config.js";
2
- export declare const LATEST_KB_SCHEMA_VERSION = 2;
2
+ export declare const LATEST_KB_SCHEMA_VERSION = 3;
3
3
  export interface SchemaVersionStatus {
4
4
  status: "missing" | "invalid" | "older" | "current" | "newer";
5
5
  currentVersion: number | null;
@@ -1,5 +1,5 @@
1
1
  // implements REQ-003
2
- export const LATEST_KB_SCHEMA_VERSION = 2;
2
+ export const LATEST_KB_SCHEMA_VERSION = 3;
3
3
  // implements REQ-003
4
4
  export function normalizeSchemaVersion(schemaVersion) {
5
5
  if (schemaVersion === undefined || schemaVersion === null) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "kibi-cli",
3
- "version": "0.12.4",
3
+ "version": "0.12.7",
4
4
  "type": "module",
5
5
  "description": "Kibi CLI for knowledge base management",
6
6
  "engines": {
@@ -11,7 +11,13 @@
11
11
  "bin": {
12
12
  "kibi": "bin/kibi"
13
13
  },
14
- "files": ["dist", "bin", "schema", "src/schemas", "src/public"],
14
+ "files": [
15
+ "dist",
16
+ "bin",
17
+ "schema",
18
+ "src/schemas",
19
+ "src/public"
20
+ ],
15
21
  "scripts": {
16
22
  "build": "tsc -p tsconfig.json && mkdir -p dist/public/skills && cp -r src/public/skills/. dist/public/skills/",
17
23
  "prepack": "npm run build"
@@ -69,6 +75,10 @@
69
75
  "types": "./dist/public/check-types.d.ts",
70
76
  "default": "./dist/public/check-types.js"
71
77
  },
78
+ "./public/symbol-granularity": {
79
+ "types": "./dist/public/symbol-granularity.d.ts",
80
+ "default": "./dist/public/symbol-granularity.js"
81
+ },
72
82
  "./schema-version": {
73
83
  "types": "./dist/public/schema-version.d.ts",
74
84
  "default": "./dist/public/schema-version.js"
@@ -95,7 +105,7 @@
95
105
  "fast-glob": "^3.2.12",
96
106
  "gray-matter": "^4.0.3",
97
107
  "js-yaml": "^4.1.0",
98
- "kibi-core": "^0.6.0",
108
+ "kibi-core": "^0.6.2",
99
109
  "ts-morph": "^23.0.0",
100
110
  "ignore": "^5.3.0"
101
111
  },
@@ -61,6 +61,15 @@
61
61
  "description": "[DEPRECATED] No longer used. Branch lifecycle now follows git naturally without requiring a configured default. This field is ignored but kept for backward compatibility.",
62
62
  "deprecated": true
63
63
  },
64
+ "schemaVersion": {
65
+ "type": ["integer", "string"],
66
+ "description": "Kibi KB schema version used by the migration command"
67
+ },
68
+ "semanticAdvisorBackfill": {
69
+ "type": "string",
70
+ "enum": ["pending", "completed", "not_applicable"],
71
+ "description": "Review state for retroactive semantic-advisor modeling of existing requirements"
72
+ },
64
73
  "checks": {
65
74
  "type": "object",
66
75
  "description": "Configuration for KB validation rules",
@@ -17,6 +17,8 @@
17
17
  */
18
18
 
19
19
  // Typed fact field enums per proposal
20
+ import { SYMBOL_ROLES, type SymbolRole } from "../symbol-granularity.js";
21
+
20
22
  type FactKind =
21
23
  | "subject"
22
24
  | "property_value"
@@ -127,6 +129,10 @@ const entitySchema: Record<string, unknown> = {
127
129
  "legacy-link",
128
130
  ],
129
131
  },
132
+ symbol_role: {
133
+ type: "string",
134
+ enum: [...SYMBOL_ROLES] satisfies SymbolRole[],
135
+ },
130
136
  type: {
131
137
  type: "string",
132
138
  enum: [
@@ -0,0 +1,125 @@
1
+ export const SYMBOL_ROLES = [
2
+ "behavioral",
3
+ "structural",
4
+ "type-shape",
5
+ "config",
6
+ "module",
7
+ "unknown",
8
+ ] as const;
9
+
10
+ export type SymbolRole = (typeof SYMBOL_ROLES)[number];
11
+
12
+ export const TRACEABILITY_RELATIONSHIP_TYPES = [
13
+ "implements",
14
+ "covered_by",
15
+ "executable_for",
16
+ ] as const;
17
+
18
+ export type TraceabilityRelationshipType =
19
+ (typeof TRACEABILITY_RELATIONSHIP_TYPES)[number];
20
+
21
+ export const ALLOWED_GRANULARITY_REASONS = [
22
+ "config-artifact",
23
+ "module-level-behavior",
24
+ "extractor-miss",
25
+ "legacy-link",
26
+ ] as const;
27
+
28
+ export type GranularityReason = (typeof ALLOWED_GRANULARITY_REASONS)[number];
29
+
30
+ export type SymbolKind =
31
+ | "function"
32
+ | "class"
33
+ | "method"
34
+ | "interface"
35
+ | "type"
36
+ | "variable"
37
+ | "enum"
38
+ | "unknown";
39
+
40
+ export type SourceSymbolKind = SymbolKind;
41
+
42
+ export interface GranularSymbolCandidate {
43
+ name: string;
44
+ kind?: SymbolKind;
45
+ role?: SymbolRole;
46
+ }
47
+
48
+ const traceabilityRelationshipTypeSet: ReadonlySet<string> = new Set(
49
+ TRACEABILITY_RELATIONSHIP_TYPES,
50
+ );
51
+ const allowedGranularityReasonSet: ReadonlySet<string> = new Set(
52
+ ALLOWED_GRANULARITY_REASONS,
53
+ );
54
+
55
+ export function inferSymbolRole(kind: SymbolKind): SymbolRole {
56
+ switch (kind) {
57
+ case "function":
58
+ case "class":
59
+ case "method":
60
+ return "behavioral";
61
+ case "interface":
62
+ case "type":
63
+ case "enum":
64
+ return "type-shape";
65
+ case "variable":
66
+ case "unknown":
67
+ return "unknown";
68
+ }
69
+ }
70
+
71
+ export const inferSymbolRoleFromKind = inferSymbolRole;
72
+
73
+ export function isSymbolRole(value: unknown): value is SymbolRole {
74
+ return (
75
+ typeof value === "string" && SYMBOL_ROLES.some((role) => role === value)
76
+ );
77
+ }
78
+
79
+ export function isTraceabilityRelationshipType(
80
+ value: unknown,
81
+ ): value is TraceabilityRelationshipType {
82
+ return (
83
+ typeof value === "string" && traceabilityRelationshipTypeSet.has(value)
84
+ );
85
+ }
86
+
87
+ export function isAllowedGranularityReason(
88
+ value: unknown,
89
+ ): value is GranularityReason {
90
+ return typeof value === "string" && allowedGranularityReasonSet.has(value);
91
+ }
92
+
93
+ export function getSymbolRole(candidate: GranularSymbolCandidate): SymbolRole {
94
+ if (candidate.role) return candidate.role;
95
+ if (candidate.kind) return inferSymbolRole(candidate.kind);
96
+ return "unknown";
97
+ }
98
+
99
+ export function isBehavioralSymbol(
100
+ candidate: GranularSymbolCandidate,
101
+ ): boolean {
102
+ return getSymbolRole(candidate) === "behavioral";
103
+ }
104
+
105
+ export function getBehavioralSymbolNames(
106
+ candidates: GranularSymbolCandidate[],
107
+ ): string[] {
108
+ return [
109
+ ...new Set(
110
+ candidates.filter(isBehavioralSymbol).map((candidate) => candidate.name),
111
+ ),
112
+ ].sort();
113
+ }
114
+
115
+ export function getNonBehavioralSymbolNames(
116
+ candidates: GranularSymbolCandidate[],
117
+ ): string[] {
118
+ return [
119
+ ...new Set(
120
+ candidates
121
+ .filter((candidate) => !isBehavioralSymbol(candidate))
122
+ .map((candidate) => candidate.name),
123
+ ),
124
+ ].sort();
125
+ }
@@ -48,6 +48,17 @@
48
48
  "legacy-link"
49
49
  ]
50
50
  },
51
+ "symbol_role": {
52
+ "type": "string",
53
+ "enum": [
54
+ "behavioral",
55
+ "structural",
56
+ "type-shape",
57
+ "config",
58
+ "module",
59
+ "unknown"
60
+ ]
61
+ },
51
62
  "verification_scope": {
52
63
  "type": "string",
53
64
  "enum": ["unit", "integration", "end_to_end"]