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.
- package/dist/commands/check.d.ts.map +1 -1
- package/dist/commands/check.js +12 -16
- package/dist/commands/migrate.d.ts.map +1 -1
- package/dist/commands/migrate.js +18 -0
- package/dist/commands/sync/persistence.d.ts.map +1 -1
- package/dist/commands/sync/persistence.js +53 -1
- package/dist/extractors/manifest.d.ts.map +1 -1
- package/dist/extractors/manifest.js +92 -2
- package/dist/extractors/markdown.d.ts.map +1 -1
- package/dist/extractors/markdown.js +92 -0
- package/dist/extractors/relationships.d.ts.map +1 -1
- package/dist/extractors/relationships.js +2 -0
- package/dist/prolog.d.ts.map +1 -1
- package/dist/prolog.js +33 -3
- package/dist/public/schemas/entity.d.ts.map +1 -1
- package/dist/public/schemas/entity.js +6 -0
- package/dist/public/symbol-granularity.d.ts +23 -0
- package/dist/public/symbol-granularity.d.ts.map +1 -0
- package/dist/public/symbol-granularity.js +68 -0
- package/dist/schemas/entity.schema.json +11 -0
- package/dist/traceability/symbol-extract.d.ts +3 -1
- package/dist/traceability/symbol-extract.d.ts.map +1 -1
- package/dist/traceability/symbol-extract.js +2 -0
- package/dist/traceability/temp-kb.d.ts.map +1 -1
- package/dist/traceability/temp-kb.js +3 -0
- package/dist/types/relationships.d.ts +1 -1
- package/dist/types/relationships.d.ts.map +1 -1
- package/dist/utils/config.d.ts +1 -0
- package/dist/utils/config.d.ts.map +1 -1
- package/dist/utils/config.js +6 -0
- package/dist/utils/schema-version.d.ts +1 -1
- package/dist/utils/schema-version.js +1 -1
- package/package.json +13 -3
- package/schema/config.json +9 -0
- package/src/public/schemas/entity.ts +6 -0
- package/src/public/symbol-granularity.ts +125 -0
- 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":"
|
|
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"}
|
package/dist/commands/check.js
CHANGED
|
@@ -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) =>
|
|
138
|
+
return result.relationships.some((relationship) => isTraceabilityRelationshipType(relationship.type));
|
|
149
139
|
}
|
|
150
140
|
function hasValidGranularityReason(result) {
|
|
151
|
-
|
|
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: ${
|
|
177
|
-
suggestion:
|
|
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;
|
|
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"}
|
package/dist/commands/migrate.js
CHANGED
|
@@ -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;
|
|
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
|
-
|
|
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;
|
|
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":"AAwDA,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;IAC5B,kBAAkB,CAAC,EAAE,MAAM,GAAG,aAAa,GAAG,YAAY,CAAC;IAC3D,wBAAwB,CAAC,EAAE,UAAU,GAAG,UAAU,CAAC;IAEnD,SAAS,CAAC,EAAE,SAAS,GAAG,gBAAgB,GAAG,aAAa,GAAG,MAAM,CAAC;IAClE,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,EAAE,IAAI,GAAG,KAAK,GAAG,IAAI,GAAG,KAAK,GAAG,IAAI,GAAG,KAAK,CAAC;IACtD,UAAU,CAAC,EAAE,QAAQ,GAAG,KAAK,GAAG,QAAQ,GAAG,MAAM,CAAC;IAClD,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,SAAS,GAAG,QAAQ,CAAC;IAChC,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB;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;
|
|
1
|
+
{"version":3,"file":"markdown.d.ts","sourceRoot":"","sources":["../../src/extractors/markdown.ts"],"names":[],"mappings":"AAwDA,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;IAC5B,kBAAkB,CAAC,EAAE,MAAM,GAAG,aAAa,GAAG,YAAY,CAAC;IAC3D,wBAAwB,CAAC,EAAE,UAAU,GAAG,UAAU,CAAC;IAEnD,SAAS,CAAC,EAAE,SAAS,GAAG,gBAAgB,GAAG,aAAa,GAAG,MAAM,CAAC;IAClE,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,EAAE,IAAI,GAAG,KAAK,GAAG,IAAI,GAAG,KAAK,GAAG,IAAI,GAAG,KAAK,CAAC;IACtD,UAAU,CAAC,EAAE,QAAQ,GAAG,KAAK,GAAG,QAAQ,GAAG,MAAM,CAAC;IAClD,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,SAAS,GAAG,QAAQ,CAAC;IAChC,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB;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;AAmKD,qBAAa,gBAAiB,SAAQ,KAAK;IAOhC,QAAQ,EAAE,MAAM;IANlB,cAAc,EAAE,MAAM,CAAC;IACvB,IAAI,EAAE,MAAM,CAAC;IACb,aAAa,CAAC,EAAE,MAAM,CAAC;gBAG5B,OAAO,EAAE,MAAM,EACR,QAAQ,EAAE,MAAM,EACvB,OAAO,CAAC,EAAE;QACR,cAAc,CAAC,EAAE,MAAM,CAAC;QACxB,IAAI,CAAC,EAAE,MAAM,CAAC;QACd,aAAa,CAAC,EAAE,MAAM,CAAC;KACxB;IAYM,QAAQ;CAUlB;AA8DD,wBAAgB,sBAAsB,CAEpC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC7B,UAAU,EAAE,MAAM,GACjB,MAAM,EAAE,CAkDV;AA0RD,wBAAgB,yBAAyB,CACvC,OAAO,EAAE,MAAM,EACf,QAAQ,EAAE,MAAM,GACf,gBAAgB,CAElB;AAGD,wBAAgB,mBAAmB,CAAC,QAAQ,EAAE,MAAM,GAAG,gBAAgB,CAatE;AAED,wBAAgB,iBAAiB,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CASjE;AASD,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,GAAG,SAAS,CAQpE"}
|
|
@@ -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;
|
|
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",
|
package/dist/prolog.d.ts.map
CHANGED
|
@@ -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;
|
|
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:
|
|
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":"
|
|
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:
|
|
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,
|
|
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,
|
|
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"}
|
package/dist/utils/config.d.ts
CHANGED
|
@@ -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,
|
|
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"}
|
package/dist/utils/config.js
CHANGED
|
@@ -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
|
+
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;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "kibi-cli",
|
|
3
|
-
"version": "0.12.
|
|
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": [
|
|
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.
|
|
108
|
+
"kibi-core": "^0.6.2",
|
|
99
109
|
"ts-morph": "^23.0.0",
|
|
100
110
|
"ignore": "^5.3.0"
|
|
101
111
|
},
|
package/schema/config.json
CHANGED
|
@@ -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"]
|