@weftlet/toolkit 0.6.1 → 0.7.1

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 (196) hide show
  1. package/README.md +191 -18
  2. package/THIRD_PARTY_NOTICES.md +3 -1
  3. package/dist/application/application.d.ts +10 -0
  4. package/dist/application/application.js +20 -0
  5. package/dist/application/document-code-bindings.d.ts +11 -0
  6. package/dist/application/document-code-bindings.js +25 -0
  7. package/dist/application/document-obligations.d.ts +8 -0
  8. package/dist/application/document-obligations.js +11 -0
  9. package/dist/application/draft-facts.d.ts +3 -0
  10. package/dist/application/draft-facts.js +37 -0
  11. package/dist/application/extraction-identity.d.ts +2 -2
  12. package/dist/application/extraction-identity.js +7 -1
  13. package/dist/application/extractor-results.d.ts +6 -0
  14. package/dist/application/extractor-results.js +6 -0
  15. package/dist/application/hydrator.js +38 -18
  16. package/dist/application/mapping-proposals.d.ts +108 -0
  17. package/dist/application/mapping-proposals.js +142 -0
  18. package/dist/application/mapping-service.d.ts +2 -0
  19. package/dist/application/mapping-service.js +5 -0
  20. package/dist/application/overlay.js +43 -29
  21. package/dist/application/saved-code-coverage.d.ts +40 -0
  22. package/dist/application/saved-code-coverage.js +20 -0
  23. package/dist/application/saved-compatibility.d.ts +38 -0
  24. package/dist/application/saved-compatibility.js +20 -0
  25. package/dist/application/saved-evidence.d.ts +17 -11
  26. package/dist/application/saved-evidence.js +44 -40
  27. package/dist/application/saved-exploration.d.ts +57 -0
  28. package/dist/application/saved-exploration.js +21 -0
  29. package/dist/application/saved-paths.d.ts +58 -0
  30. package/dist/application/saved-paths.js +20 -0
  31. package/dist/application/semantic-run.d.ts +1 -1
  32. package/dist/extraction/call-syntax.d.ts +18 -0
  33. package/dist/extraction/call-syntax.js +61 -0
  34. package/dist/extraction/canonical-document-links.d.ts +5 -1
  35. package/dist/extraction/canonical-document-links.js +4 -4
  36. package/dist/extraction/code-inventory.d.ts +6 -0
  37. package/dist/extraction/code-inventory.js +26 -0
  38. package/dist/extraction/document-code-bindings.d.ts +14 -0
  39. package/dist/extraction/document-code-bindings.js +121 -0
  40. package/dist/extraction/document-obligations.d.ts +12 -0
  41. package/dist/extraction/document-obligations.js +112 -0
  42. package/dist/extraction/mapping-candidates.d.ts +13 -0
  43. package/dist/extraction/mapping-candidates.js +46 -0
  44. package/dist/extraction/mapping-output.d.ts +75 -0
  45. package/dist/extraction/mapping-output.js +53 -0
  46. package/dist/extraction/mapping-plan.d.ts +7 -0
  47. package/dist/extraction/mapping-plan.js +117 -0
  48. package/dist/extraction/mapping-prompt.d.ts +2 -0
  49. package/dist/extraction/mapping-prompt.js +17 -0
  50. package/dist/extraction/mapping-provider.d.ts +15 -0
  51. package/dist/extraction/mapping-provider.js +60 -0
  52. package/dist/extraction/mapping-request.d.ts +3 -0
  53. package/dist/extraction/mapping-request.js +32 -0
  54. package/dist/extraction/ontology.d.ts +2 -0
  55. package/dist/extraction/ontology.js +28 -13
  56. package/dist/extraction/routing.js +5 -20
  57. package/dist/extraction/scip-occurrences.d.ts +3 -0
  58. package/dist/extraction/scip-occurrences.js +102 -0
  59. package/dist/extraction/scip-ranges.d.ts +6 -0
  60. package/dist/extraction/scip-ranges.js +31 -0
  61. package/dist/extraction/scip-runtime.d.ts +39 -0
  62. package/dist/extraction/scip-runtime.js +8 -0
  63. package/dist/extraction/scip-symbols.d.ts +21 -0
  64. package/dist/extraction/scip-symbols.js +91 -0
  65. package/dist/extraction/semantic.d.ts +1 -1
  66. package/dist/extraction/semantic.js +5 -17
  67. package/dist/extraction/static-calls.d.ts +10 -0
  68. package/dist/extraction/static-calls.js +133 -0
  69. package/dist/extraction/static.d.ts +3 -20
  70. package/dist/extraction/static.js +8 -84
  71. package/dist/extraction/symbol-resolution.d.ts +12 -0
  72. package/dist/extraction/symbol-resolution.js +51 -0
  73. package/dist/extraction/symbol-selectors.d.ts +11 -0
  74. package/dist/extraction/symbol-selectors.js +51 -0
  75. package/dist/graph/binding-rdf.d.ts +7 -0
  76. package/dist/graph/binding-rdf.js +109 -0
  77. package/dist/graph/call-rdf.d.ts +4 -0
  78. package/dist/graph/call-rdf.js +94 -0
  79. package/dist/graph/call-shapes.d.ts +2 -0
  80. package/dist/graph/call-shapes.js +28 -0
  81. package/dist/graph/code-coverage-projection.d.ts +9 -0
  82. package/dist/graph/code-coverage-projection.js +117 -0
  83. package/dist/graph/code-coverage-renderer.d.ts +2 -0
  84. package/dist/graph/code-coverage-renderer.js +15 -0
  85. package/dist/graph/code-coverage-request.d.ts +6 -0
  86. package/dist/graph/code-coverage-request.js +19 -0
  87. package/dist/graph/code-coverage.d.ts +6 -0
  88. package/dist/graph/code-coverage.js +54 -0
  89. package/dist/graph/code-description.d.ts +4 -0
  90. package/dist/graph/code-description.js +24 -0
  91. package/dist/graph/code-rdf.d.ts +6 -0
  92. package/dist/graph/code-rdf.js +79 -0
  93. package/dist/graph/compatibility-context.d.ts +2 -0
  94. package/dist/graph/compatibility-context.js +18 -0
  95. package/dist/graph/compatibility-renderer.d.ts +2 -0
  96. package/dist/graph/compatibility-renderer.js +45 -0
  97. package/dist/graph/compatibility-request.d.ts +3 -0
  98. package/dist/graph/compatibility-request.js +18 -0
  99. package/dist/graph/compatibility.d.ts +7 -0
  100. package/dist/graph/compatibility.js +67 -0
  101. package/dist/graph/constraint-comparison.d.ts +7 -0
  102. package/dist/graph/constraint-comparison.js +49 -0
  103. package/dist/graph/coverage.d.ts +2 -2
  104. package/dist/graph/coverage.js +8 -3
  105. package/dist/graph/diagnostics.d.ts +1 -1
  106. package/dist/graph/diagnostics.js +10 -6
  107. package/dist/graph/evidence-description.d.ts +3 -0
  108. package/dist/graph/evidence-description.js +13 -0
  109. package/dist/graph/identity-preflight.js +1 -1
  110. package/dist/graph/neighborhood-renderer.d.ts +2 -0
  111. package/dist/graph/neighborhood-renderer.js +38 -0
  112. package/dist/graph/neighborhood-request.d.ts +9 -0
  113. package/dist/graph/neighborhood-request.js +37 -0
  114. package/dist/graph/neighborhood.d.ts +8 -0
  115. package/dist/graph/neighborhood.js +100 -0
  116. package/dist/graph/obligation-rdf.d.ts +5 -0
  117. package/dist/graph/obligation-rdf.js +67 -0
  118. package/dist/graph/obligation-shapes.d.ts +2 -0
  119. package/dist/graph/obligation-shapes.js +32 -0
  120. package/dist/graph/partitioned-shacl.d.ts +14 -0
  121. package/dist/graph/partitioned-shacl.js +66 -0
  122. package/dist/graph/path-request.d.ts +11 -0
  123. package/dist/graph/path-request.js +24 -0
  124. package/dist/graph/path-search.d.ts +45 -0
  125. package/dist/graph/path-search.js +101 -0
  126. package/dist/graph/paths-renderer.d.ts +2 -0
  127. package/dist/graph/paths-renderer.js +36 -0
  128. package/dist/graph/paths.d.ts +7 -0
  129. package/dist/graph/paths.js +74 -0
  130. package/dist/graph/query.js +23 -6
  131. package/dist/graph/rdf-partitions.d.ts +24 -0
  132. package/dist/graph/rdf-partitions.js +170 -0
  133. package/dist/graph/rdf-projection.d.ts +7 -0
  134. package/dist/graph/rdf-projection.js +27 -0
  135. package/dist/graph/rdf-records.d.ts +12 -0
  136. package/dist/graph/rdf-records.js +90 -0
  137. package/dist/graph/rdf.d.ts +10 -10
  138. package/dist/graph/rdf.js +84 -89
  139. package/dist/graph/shacl-shapes.js +70 -0
  140. package/dist/graph/snapshot-context.d.ts +3 -0
  141. package/dist/graph/snapshot-context.js +8 -0
  142. package/dist/graph/validation-progress.d.ts +4 -1
  143. package/dist/index.d.ts +23 -0
  144. package/dist/index.js +22 -0
  145. package/dist/infrastructure/json-digest.d.ts +3 -0
  146. package/dist/infrastructure/json-digest.js +3 -0
  147. package/dist/infrastructure/unique-json.d.ts +2 -0
  148. package/dist/infrastructure/unique-json.js +20 -0
  149. package/dist/interfaces/cli.js +33 -1
  150. package/dist/interfaces/code-coverage.d.ts +46 -0
  151. package/dist/interfaces/code-coverage.js +18 -0
  152. package/dist/interfaces/graph-compatibility.d.ts +35 -0
  153. package/dist/interfaces/graph-compatibility.js +21 -0
  154. package/dist/interfaces/graph-exploration.d.ts +59 -0
  155. package/dist/interfaces/graph-exploration.js +27 -0
  156. package/dist/interfaces/graph-paths.d.ts +77 -0
  157. package/dist/interfaces/graph-paths.js +31 -0
  158. package/dist/interfaces/graph-query-args.d.ts +2 -0
  159. package/dist/interfaces/graph-query-args.js +25 -0
  160. package/dist/interfaces/mapping-proposals.d.ts +204 -0
  161. package/dist/interfaces/mapping-proposals.js +74 -0
  162. package/dist/interfaces/mcp.js +31 -3
  163. package/dist/storage/mapping-proposals.d.ts +9 -0
  164. package/dist/storage/mapping-proposals.js +42 -0
  165. package/dist/storage/storage.d.ts +3 -0
  166. package/dist/storage/storage.js +37 -13
  167. package/dist/storage/streaming-json.d.ts +5 -0
  168. package/dist/storage/streaming-json.js +45 -0
  169. package/dist/visualization/evidence-sources.d.ts +6 -0
  170. package/dist/visualization/evidence-sources.js +19 -0
  171. package/dist/visualization/viewer-interaction.d.ts +5 -8
  172. package/dist/visualization/viewer-interaction.js +46 -44
  173. package/dist/visualization/visualization.js +14 -5
  174. package/docs/1.0-WORK-QUEUE.md +48 -0
  175. package/docs/ADR-029-exact-graph-neighborhoods.md +70 -0
  176. package/docs/ADR-030-bounded-evidence-paths.md +97 -0
  177. package/docs/ADR-031-code-extractor-selection.md +91 -0
  178. package/docs/ADR-032-scip-code-evidence.md +73 -0
  179. package/docs/ADR-033-document-symbol-bindings.md +83 -0
  180. package/docs/ADR-034-mapping-gaps.md +80 -0
  181. package/docs/ADR-035-static-call-evidence.md +78 -0
  182. package/docs/ADR-036-contract-obligation-declarations.md +124 -0
  183. package/docs/ADR-037-selected-contract-compatibility.md +93 -0
  184. package/docs/ADR-038-monorepo-code-coverage.md +105 -0
  185. package/docs/ADR-039-mapping-proposals.md +119 -0
  186. package/docs/ADR-040-bounded-graph-io.md +90 -0
  187. package/docs/GRAPHIFY-EVALUATION.md +21 -4
  188. package/docs/PRE-1.0-BASELINE.md +49 -2
  189. package/docs/RELEASE-1.0-PLAN.md +14 -2
  190. package/docs/RELEASING.md +3 -3
  191. package/docs/ROADMAP.md +27 -6
  192. package/docs/VALIDATION.md +521 -14
  193. package/docs/benchmarks/large-graph-2026-11-02/result.json +156 -0
  194. package/docs/extraction-manifest.json +266 -23
  195. package/package.json +10 -4
  196. package/templates/context-graph-development/SKILL.md +30 -1
@@ -0,0 +1,133 @@
1
+ import ts from 'typescript';
2
+ import { repositoryFileNode } from '@weftlet/core';
3
+ import { sha256, stableJson } from '../infrastructure/util.js';
4
+ import { calleeToken, callableName, factsAt, isAmbient, isCallable, parseCallSource, syntaxSpan } from './call-syntax.js';
5
+ export const staticCallIdentity = { id: 'typescript-call-extractor', version: '1.0.0' };
6
+ const proof = (fact) => ({ factId: fact.id, provenance: fact.provenance });
7
+ function targetResolution(syntax, defs, kinds) {
8
+ if (!defs.length)
9
+ return 'external';
10
+ if (syntax === 'construct' && kinds?.has('class'))
11
+ return 'indexed-class';
12
+ if (kinds?.has('body'))
13
+ return 'indexed-body';
14
+ if (kinds?.has('declaration'))
15
+ return 'declaration-only';
16
+ return undefined;
17
+ }
18
+ /** Parse only the exact committed documents already indexed by SCIP. No second resolver or name guessing. */
19
+ export function extractStaticCalls(sources, codeFacts, context) {
20
+ const parsed = [], kinds = new Map();
21
+ const definitions = new Map(), local = new Map();
22
+ for (const fact of codeFacts.filter(f => f.codeOccurrence)) {
23
+ const group = local.get(fact.provenance.path) ?? [];
24
+ group.push(fact);
25
+ local.set(fact.provenance.path, group);
26
+ if (fact.codeOccurrence.role === 'definition') {
27
+ const defs = definitions.get(fact.subject.id) ?? [];
28
+ defs.push(fact);
29
+ definitions.set(fact.subject.id, defs);
30
+ }
31
+ }
32
+ for (const [path, text] of sources) {
33
+ const digest = sha256(text), facts = local.get(path) ?? [];
34
+ if (facts.some(f => f.provenance.commitSha !== context.commitSha || f.provenance.digest !== digest)) {
35
+ throw new Error('Call analysis requires matching committed SCIP source bytes');
36
+ }
37
+ const file = parseCallSource(path, text, digest, facts);
38
+ parsed.push(file);
39
+ if (file.parseErrors.length)
40
+ continue;
41
+ const classify = (node) => {
42
+ const name = isCallable(node) ? callableName(node) : ts.isClassDeclaration(node) || ts.isClassExpression(node) ? node.name :
43
+ ts.isMethodSignature(node) ? node.name : undefined;
44
+ const kind = isCallable(node) ? node.body && !isAmbient(node) ? 'body' : 'declaration' :
45
+ ts.isClassDeclaration(node) || ts.isClassExpression(node) ? isAmbient(node) ? 'declaration' : 'class' : ts.isMethodSignature(node) ? 'declaration' : undefined;
46
+ if (kind)
47
+ for (const fact of factsAt(name, file, context.commitSha, 'definition')) {
48
+ const values = kinds.get(fact.subject.id) ?? new Set();
49
+ values.add(kind);
50
+ kinds.set(fact.subject.id, values);
51
+ }
52
+ ts.forEachChild(node, classify);
53
+ };
54
+ classify(file.source);
55
+ }
56
+ const facts = [], diagnostics = [];
57
+ for (const file of parsed) {
58
+ const physical = repositoryFileNode(file.source.fileName);
59
+ const gap = (node, caller, reason, targets = []) => {
60
+ const record = { kind: 'implementationGaps', reason: `Static call: ${reason}`,
61
+ provenance: [syntaxSpan(node, file.source, file.digest, context.commitSha)],
62
+ relatedNodeIds: [...new Set([physical.id, caller.node.id, ...targets.map(n => n.id)])].sort(), evidenceScope: 'source' };
63
+ diagnostics.push({ ...record, id: sha256(stableJson({ ...record, extractor: staticCallIdentity })) });
64
+ };
65
+ if (file.parseErrors.length) {
66
+ gap(file.source, { node: physical, definitions: [] }, 'TypeScript syntax errors prevent call analysis in this indexed document.');
67
+ continue;
68
+ }
69
+ const emit = (node, caller) => {
70
+ const token = calleeToken(node.expression), selected = factsAt(token, file, context.commitSha, 'reference');
71
+ const targets = [...new Map(selected.map(f => [f.object.id, f.object])).values()];
72
+ if (caller.unresolved) {
73
+ gap(node, caller, 'Enclosing anonymous callable or initializer has no exact indexed callable identity.', targets);
74
+ return;
75
+ }
76
+ if (targets.length !== 1) {
77
+ gap(node, caller, 'Callee expression has no single exact indexed target; dynamic dispatch is unresolved.', targets);
78
+ return;
79
+ }
80
+ const target = targets[0], defs = definitions.get(target.id) ?? [], targetKinds = kinds.get(target.id);
81
+ const syntax = ts.isNewExpression(node) ? 'construct' : 'call';
82
+ const resolution = targetResolution(syntax, defs, targetKinds);
83
+ if (!resolution) {
84
+ gap(node, caller, 'Indexed value is not a known callable body or declaration; value flow, callbacks and dispatch are unresolved.', [target]);
85
+ return;
86
+ }
87
+ const call = { schemaVersion: 1, method: 'typescript-ast+scip', syntax,
88
+ dispatch: syntax === 'construct' ? 'construct' : token && (ts.isPropertyAccessExpression(token.parent) || ts.isElementAccessExpression(token.parent)) ? 'member' : 'lexical',
89
+ optional: Boolean(node.flags & ts.NodeFlags.OptionalChain), caller: caller.definitions.length ? 'indexed-callable' : 'file-scope',
90
+ target: resolution, runtime: 'not-observed', callerDefinitions: caller.definitions.map(proof),
91
+ targetOccurrences: selected.map(proof), targetDefinitions: defs.map(proof) };
92
+ const fact = { subject: caller.node, predicate: 'calls', object: target,
93
+ provenance: syntaxSpan(node, file.source, file.digest, context.commitSha), codeCall: call,
94
+ extractor: staticCallIdentity, authority: 'implemented', extractionReview: 'deterministic', confidence: 1 };
95
+ facts.push({ ...fact, id: sha256(stableJson(fact)), snapshotId: context.snapshotId });
96
+ if (resolution === 'external' || resolution === 'declaration-only')
97
+ gap(node, caller, 'The selected declaration has no indexed callable body; runtime implementation remains unknown.', [target]);
98
+ };
99
+ const walk = (node, caller) => {
100
+ // Ambient declarations describe types; decorators/default syntax there is not executed.
101
+ if (ts.canHaveModifiers(node) && ts.getModifiers(node)?.some(modifier => modifier.kind === ts.SyntaxKind.DeclareKeyword))
102
+ return;
103
+ if (isCallable(node)) {
104
+ const defs = factsAt(callableName(node), file, context.commitSha, 'definition');
105
+ const ids = new Set(defs.map(f => f.subject.id));
106
+ const inner = ids.size === 1 ? { node: defs[0].subject, definitions: defs }
107
+ : { node: caller.node, definitions: [], unresolved: true };
108
+ // Decorators/computed names execute in the outer context, not in the function body.
109
+ ts.forEachChild(node, child => {
110
+ if (ts.isParameter(child))
111
+ ts.forEachChild(child, part => walk(part, part === child.initializer ? inner : caller));
112
+ else
113
+ walk(child, child === node.body ? inner : caller);
114
+ });
115
+ return;
116
+ }
117
+ if (ts.isPropertyDeclaration(node) && node.initializer) {
118
+ ts.forEachChild(node, child => walk(child, child === node.initializer ? { node: caller.node, definitions: [], unresolved: true } : caller));
119
+ return;
120
+ }
121
+ if (ts.isClassStaticBlockDeclaration(node)) {
122
+ ts.forEachChild(node, child => walk(child, { node: caller.node, definitions: [], unresolved: true }));
123
+ return;
124
+ }
125
+ if (ts.isCallExpression(node) || ts.isNewExpression(node))
126
+ emit(node, caller);
127
+ ts.forEachChild(node, child => walk(child, caller));
128
+ };
129
+ if (!file.source.isDeclarationFile)
130
+ walk(file.source, { node: physical, definitions: [] });
131
+ }
132
+ return { facts: facts.sort((a, b) => a.id.localeCompare(b.id)), diagnostics: diagnostics.sort((a, b) => a.id.localeCompare(b.id)) };
133
+ }
@@ -1,12 +1,8 @@
1
1
  import type { ContextGraphConfig } from "../configuration/config.js";
2
2
  import { type ExtractionContext, type FactExtractor, type GraphFact } from "@weftlet/core";
3
3
  import { GitRepository } from "../repository/git.js";
4
- export interface ScipIndexResult {
5
- readonly path: string;
6
- readonly digest: string;
7
- readonly mode: 'full' | 'package';
8
- readonly version: string;
9
- }
4
+ import type { ScipIndexResult } from './scip-runtime.js';
5
+ export type { ScipIndexResult } from './scip-runtime.js';
10
6
  export interface StaticIndexer {
11
7
  index(context: ExtractionContext): Promise<ScipIndexResult>;
12
8
  }
@@ -17,20 +13,7 @@ export declare class ScipTypeScriptIndexer implements StaticIndexer {
17
13
  index(context: ExtractionContext): Promise<ScipIndexResult>;
18
14
  }
19
15
  export declare function normalizeScipCheckout(bytes: Uint8Array, checkoutRoot: string): Uint8Array;
20
- export declare const scipSymbolExtractorIdentity: {
21
- readonly id: "scip-symbol-extractor";
22
- readonly version: "1.1.0";
23
- };
24
- export declare class ScipSymbolExtractor implements FactExtractor {
25
- private readonly repository;
26
- private readonly index;
27
- readonly identity: {
28
- readonly id: "scip-symbol-extractor";
29
- readonly version: "1.1.0";
30
- };
31
- constructor(repository: GitRepository, index: ScipIndexResult);
32
- extract(context: ExtractionContext): Promise<readonly GraphFact[]>;
33
- }
16
+ export { ScipSymbolExtractor, scipSymbolExtractorIdentity } from './scip-symbols.js';
34
17
  export declare const repositoryFileExtractorIdentity: {
35
18
  readonly id: "repository-file-extractor";
36
19
  readonly version: "2.0.0";
@@ -2,7 +2,7 @@
2
2
  import { withCommittedTree } from "../repository/committed-tree.js";
3
3
  import { copyFile, mkdir, readFile, rename, rm, stat, writeFile } from 'node:fs/promises';
4
4
  import { createRequire } from 'node:module';
5
- import { dirname, join } from 'node:path';
5
+ import { join } from 'node:path';
6
6
  import { pathToFileURL } from 'node:url';
7
7
  import { repositoryFileNode } from "@weftlet/core";
8
8
  import { GitRepository } from "../repository/git.js";
@@ -12,6 +12,7 @@ import { moduleRequestNode } from './module-request.js';
12
12
  import { matchesRepositoryGlob } from "../repository/glob.js";
13
13
  import { runProcess } from "../infrastructure/process.js";
14
14
  import { sha256, stableJson } from "../infrastructure/util.js";
15
+ import { loadScipRuntime } from './scip-runtime.js';
15
16
  import { physicalArtifactKey, temporaryArtifactPath } from '../infrastructure/artifact-paths.js';
16
17
  export class ScipTypeScriptIndexer {
17
18
  config;
@@ -60,19 +61,18 @@ export class ScipTypeScriptIndexer {
60
61
  finally {
61
62
  await rm(temporary, { force: true });
62
63
  }
64
+ const bytes = await readFile(destination);
65
+ const indexedPaths = [...new Set(loadScipRuntime().scip.Index.deserializeBinary(bytes).documents
66
+ .map(document => document.relative_path.replaceAll('\\', '/')))].sort();
63
67
  return {
64
68
  path: destination,
65
- digest: sha256(await readFile(destination)),
69
+ digest: sha256(bytes),
66
70
  mode: 'full',
67
71
  version: this.config.version,
72
+ indexedPaths,
68
73
  };
69
74
  }
70
75
  }
71
- function loadScipRuntime() {
72
- const require = createRequire(import.meta.url);
73
- const main = require.resolve('@sourcegraph/scip-typescript');
74
- return require(join(dirname(main), 'scip.js'));
75
- }
76
76
  export function normalizeScipCheckout(bytes, checkoutRoot) {
77
77
  const runtime = loadScipRuntime();
78
78
  const roots = [pathToFileURL(checkoutRoot).href, checkoutRoot.replace(/\\/g, '/'), checkoutRoot];
@@ -96,83 +96,7 @@ export function normalizeScipCheckout(bytes, checkoutRoot) {
96
96
  }
97
97
  return index.serializeBinary();
98
98
  }
99
- export const scipSymbolExtractorIdentity = { id: 'scip-symbol-extractor', version: '1.1.0' };
100
- export class ScipSymbolExtractor {
101
- repository;
102
- index;
103
- identity = scipSymbolExtractorIdentity;
104
- constructor(repository, index) {
105
- this.repository = repository;
106
- this.index = index;
107
- }
108
- async extract(context) {
109
- const parsed = loadScipRuntime().scip.Index.deserializeBinary(await readFile(this.index.path));
110
- const symbols = new Map();
111
- for (const document of parsed.documents) {
112
- const path = document.relative_path.replace(/\\/g, '/');
113
- const content = await this.repository.readFileAt(context.commitSha, path);
114
- const digest = sha256(content);
115
- const component = repositoryFileNode(path);
116
- for (const occurrence of document.occurrences) {
117
- if (!occurrence.symbol || occurrence.symbol.startsWith('local '))
118
- continue;
119
- const definition = (occurrence.symbol_roles & 1) === 1;
120
- const sites = symbols.get(occurrence.symbol) ?? {
121
- definitions: new Map(),
122
- references: new Map(),
123
- };
124
- symbols.set(occurrence.symbol, sites);
125
- const target = definition ? sites.definitions : sites.references;
126
- if (target.has(path))
127
- continue;
128
- target.set(path, {
129
- path,
130
- line: (occurrence.range[0] ?? 0) + 1,
131
- digest,
132
- component,
133
- });
134
- }
135
- }
136
- const facts = [];
137
- for (const [symbolText, sites] of [...symbols.entries()].sort(([left], [right]) => left.localeCompare(right))) {
138
- const definitionPaths = new Set(sites.definitions.keys());
139
- const downstream = [...sites.references.values()].filter((site) => !definitionPaths.has(site.path));
140
- if (sites.definitions.size === 0 || downstream.length === 0)
141
- continue;
142
- const symbol = {
143
- id: `symbol:scip:${sha256(symbolText)}`,
144
- kind: 'Contract',
145
- label: symbolText,
146
- attributes: { scipIndexDigest: this.index.digest },
147
- };
148
- const relationships = [
149
- ...[...sites.definitions.values()].map((site) => ({ site, definition: true })),
150
- ...downstream.map((site) => ({ site, definition: false })),
151
- ];
152
- for (const { site, definition } of relationships) {
153
- const relation = definition ? 'definition' : 'reference';
154
- const provenance = {
155
- path: site.path,
156
- startLine: site.line,
157
- endLine: site.line,
158
- digest: site.digest,
159
- commitSha: context.commitSha,
160
- };
161
- facts.push({
162
- id: sha256(stableJson({ relation, path: site.path, symbol: symbolText, provenance })),
163
- subject: site.component,
164
- predicate: definition ? 'contains' : 'consumes',
165
- object: symbol,
166
- provenance,
167
- extractor: this.identity,
168
- confidence: 1,
169
- snapshotId: context.snapshotId,
170
- });
171
- }
172
- }
173
- return facts;
174
- }
175
- }
99
+ export { ScipSymbolExtractor, scipSymbolExtractorIdentity } from './scip-symbols.js';
176
100
  export const repositoryFileExtractorIdentity = { id: 'repository-file-extractor', version: '2.0.0' };
177
101
  export class RepositoryFileExtractor {
178
102
  repository;
@@ -0,0 +1,12 @@
1
+ import type { DocumentCodeBinding, GraphFact, GraphNode } from '@weftlet/core';
2
+ type Selector = DocumentCodeBinding['selector'];
3
+ export type SymbolResolution = {
4
+ readonly node: GraphNode;
5
+ readonly selected: readonly GraphFact[];
6
+ readonly definitions: readonly GraphFact[];
7
+ } | {
8
+ readonly problem: string;
9
+ readonly candidates: readonly string[];
10
+ };
11
+ export declare function resolveSymbolSelector(selector: Selector, facts: readonly GraphFact[], sourceText: string): SymbolResolution;
12
+ export {};
@@ -0,0 +1,51 @@
1
+ import { stableJson } from '../infrastructure/util.js';
2
+ function occurrenceNode(fact) {
3
+ return fact.codeOccurrence?.role === 'definition' ? fact.subject : fact.object;
4
+ }
5
+ function textAt(span, text) {
6
+ const lines = text.split('\n');
7
+ if (!span.startColumn || !span.endColumn || span.columnEncoding !== 'utf-16' || span.startLine !== span.endLine)
8
+ return undefined;
9
+ return lines[span.startLine - 1]?.slice(span.startColumn - 1, span.endColumn - 1);
10
+ }
11
+ /** Qualification follows indexed lexical parents only; absent or conflicting parents are never invented. */
12
+ function qualifiedName(fact, localDefinitions, seen = new Set()) {
13
+ const node = occurrenceNode(fact), parentId = fact.codeOccurrence?.enclosingSymbolId;
14
+ if (seen.has(node.id) || seen.size >= 64)
15
+ return undefined;
16
+ if (!parentId)
17
+ return node.label;
18
+ const parents = localDefinitions.get(parentId) ?? [];
19
+ if (!parents.length)
20
+ return undefined;
21
+ const named = parents.filter(candidate => candidate.codeOccurrence?.locationKind !== 'zero-width-anchor');
22
+ if (!named.length)
23
+ return node.label;
24
+ const names = new Set(named.map(parent => qualifiedName(parent, localDefinitions, new Set([...seen, node.id]))));
25
+ return names.size === 1 && !names.has(undefined) ? `${[...names][0]}.${node.label}` : undefined;
26
+ }
27
+ export function resolveSymbolSelector(selector, facts, sourceText) {
28
+ const local = facts.filter(fact => fact.codeOccurrence && fact.provenance.path === selector.path);
29
+ const definitions = local.filter(fact => fact.codeOccurrence.role === 'definition' && fact.codeOccurrence.locationKind === 'name');
30
+ const parents = new Map();
31
+ for (const fact of local.filter(item => item.codeOccurrence.role === 'definition')) {
32
+ const entries = parents.get(fact.subject.id) ?? [];
33
+ entries.push(fact);
34
+ parents.set(fact.subject.id, entries);
35
+ }
36
+ const selected = selector.line !== undefined
37
+ ? local.filter(fact => fact.provenance.startLine === selector.line && fact.provenance.startColumn === selector.column &&
38
+ (occurrenceNode(fact).id === selector.symbol || textAt(fact.provenance, sourceText) === selector.symbol))
39
+ : definitions.filter(fact => fact.subject.id === selector.symbol || fact.subject.label === selector.symbol ||
40
+ qualifiedName(fact, parents) === selector.symbol);
41
+ const identities = [...new Set(selected.map(fact => occurrenceNode(fact).id))].sort();
42
+ if (identities.length !== 1)
43
+ return { problem: identities.length ? 'Ambiguous indexed symbol; use a qualified name or exact occurrence.'
44
+ : 'No indexed symbol matches this exact selector; no name, path, alias or line was guessed.', candidates: identities };
45
+ const target = occurrenceNode(selected[0]);
46
+ const resolved = facts.filter(fact => fact.codeOccurrence?.role === 'definition' && fact.subject.id === target.id);
47
+ if (!resolved.length)
48
+ return { problem: 'The occurrence has no indexed definition; implementation is unresolved in this saved scope.', candidates: identities };
49
+ const sorted = (items) => [...items].sort((a, b) => stableJson(a.provenance).localeCompare(stableJson(b.provenance)) || a.id.localeCompare(b.id));
50
+ return { node: target, selected: sorted(selected), definitions: sorted(resolved) };
51
+ }
@@ -0,0 +1,11 @@
1
+ import type { CanonicalDocumentLink } from './canonical-document-links.js';
2
+ import type { DocumentCodeBinding } from '@weftlet/core';
3
+ export interface SymbolLink {
4
+ readonly link: CanonicalDocumentLink;
5
+ readonly selector?: DocumentCodeBinding['selector'];
6
+ readonly subjectId?: string;
7
+ readonly predicate: 'references' | 'implementedBy' | 'verifiedBy';
8
+ readonly problem?: string;
9
+ }
10
+ /** Only the explicit weftlet-symbol fragment opts a link in; ordinary headings keep their existing meaning. */
11
+ export declare function documentSymbolLinks(sourcePath: string, content: string): readonly SymbolLink[];
@@ -0,0 +1,51 @@
1
+ import { canonicalDocumentLinks, resolveCanonicalDocumentLink } from './canonical-document-links.js';
2
+ const keys = new Set(['weftlet-symbol', 'weftlet-line', 'weftlet-column']);
3
+ const positive = (value) => value !== null && /^[1-9][0-9]*$/.test(value) && Number.isSafeInteger(Number(value));
4
+ /** Only the explicit weftlet-symbol fragment opts a link in; ordinary headings keep their existing meaning. */
5
+ export function documentSymbolLinks(sourcePath, content) {
6
+ const result = [];
7
+ for (const link of canonicalDocumentLinks(content, { includeTitles: true })) {
8
+ const targets = link.kind === 'ambiguous-reference' ? link.definitions?.map(definition => definition.target) ?? [] : [link.target ?? ''];
9
+ if (!targets.some(target => /#weftlet-symbol(?:=|&|$)/.test(target)))
10
+ continue;
11
+ const fail = (problem) => result.push({ link, predicate: 'references', problem });
12
+ if (link.kind === 'ambiguous-reference') {
13
+ fail('Conflicting reference definitions; no symbol binding selected.');
14
+ continue;
15
+ }
16
+ const target = link.target;
17
+ const path = resolveCanonicalDocumentLink(sourcePath, target);
18
+ if (path.kind !== 'path' || path.directoryHint || target.split('#')[0].includes('?')) {
19
+ fail('Symbol link must name an exact repository file.');
20
+ continue;
21
+ }
22
+ const parsed = selectorFragment(target);
23
+ if ('problem' in parsed) {
24
+ fail(parsed.problem);
25
+ continue;
26
+ }
27
+ const annotation = link.title;
28
+ const match = annotation?.match(/^weftlet:(implementedBy|verifiedBy) ([^\s]+)$/);
29
+ if (annotation?.startsWith('weftlet:') && !match) {
30
+ fail('Invalid mapping annotation; expected weftlet:implementedBy or weftlet:verifiedBy and an exact subject ID.');
31
+ continue;
32
+ }
33
+ result.push({ link, selector: { path: path.path, ...parsed },
34
+ predicate: match ? match[1] : 'references', ...(match ? { subjectId: match[2] } : {}) });
35
+ }
36
+ return result;
37
+ }
38
+ function selectorFragment(target) {
39
+ const fragment = target.slice(target.indexOf('#') + 1);
40
+ if (/%(?![a-f0-9]{2})/i.test(fragment))
41
+ return { problem: 'Malformed percent encoding in symbol selector.' };
42
+ const params = new URLSearchParams(fragment);
43
+ const symbol = params.get('weftlet-symbol');
44
+ const line = params.get('weftlet-line'), column = params.get('weftlet-column');
45
+ if ([...params.keys()].some(key => !keys.has(key) || params.getAll(key).length !== 1) ||
46
+ !symbol || symbol !== symbol.trim() || symbol.length > 2048 || /[\x00-\x1f\x7f-\x9f\uFFFD]/.test(symbol) ||
47
+ ((line !== null || column !== null) && (!positive(line) || !positive(column)))) {
48
+ return { problem: 'Invalid symbol selector; use one symbol and optionally both positive line and column.' };
49
+ }
50
+ return { symbol, ...(line !== null ? { line: Number(line), column: Number(column) } : {}) };
51
+ }
@@ -0,0 +1,7 @@
1
+ import { type Store, type NamedNode } from 'n3';
2
+ import type { GraphFact } from '@weftlet/core';
3
+ export declare function addBindingEvidence(dataset: Store, record: NamedNode, fact: GraphFact, factIri: (id: string) => string): void;
4
+ type FactsById = ReadonlyMap<string, GraphFact>;
5
+ /** Supplements shape validation by comparing proofs with the actual same-snapshot assertions. */
6
+ export declare function bindingEvidenceIssues(fact: GraphFact, byId: FactsById): readonly string[];
7
+ export {};
@@ -0,0 +1,109 @@
1
+ import { DataFactory } from 'n3';
2
+ import { stableJson } from '../infrastructure/util.js';
3
+ import { CG, RDF, XSD } from './shacl-shapes.js';
4
+ import { invalidCodeRange } from './code-rdf.js';
5
+ const { namedNode: iri, literal } = DataFactory;
6
+ export function addBindingEvidence(dataset, record, fact, factIri) {
7
+ const binding = fact.codeBinding;
8
+ if (!binding)
9
+ return;
10
+ const prop = (node, key, value) => {
11
+ if (value !== undefined)
12
+ dataset.addQuad(node, iri(CG + key), literal(String(value), iri(XSD + (typeof value === 'number' ? 'integer' : 'string'))));
13
+ };
14
+ dataset.addQuad(record, iri(RDF + 'type'), iri(CG + 'DocumentCodeBinding'));
15
+ prop(record, 'bindingVersion', binding.schemaVersion);
16
+ prop(record, 'bindingMethod', binding.method);
17
+ for (const [key, value] of Object.entries(binding.selector ?? {}))
18
+ prop(record, `bindingSelector/${key}`, value);
19
+ const citations = (key, spans, proofs) => spans.forEach((span, index) => {
20
+ const term = iri(`${record.value}:${key}:${index}`);
21
+ dataset.addQuad(record, iri(CG + key), term);
22
+ dataset.addQuad(term, iri(RDF + 'type'), iri(CG + 'BindingCitation'));
23
+ for (const [name, value] of Object.entries(span))
24
+ prop(term, name, value);
25
+ if (proofs)
26
+ dataset.addQuad(term, iri(CG + 'bindingFact'), iri(factIri(proofs[index].factId)));
27
+ });
28
+ citations('bindingDocument', binding.documentCitations ?? []);
29
+ citations('bindingSelection', (binding.selectedOccurrences ?? []).map(item => item.provenance), binding.selectedOccurrences);
30
+ citations('bindingDefinition', (binding.definitions ?? []).map(item => item.provenance), binding.definitions);
31
+ citations('bindingSubject', (binding.subjectEvidence ?? []).map(item => item.provenance), binding.subjectEvidence);
32
+ }
33
+ function proofIssues(proof, byId) {
34
+ const issues = [], original = byId.get(proof.factId);
35
+ if (!original || stableJson(original.provenance) !== stableJson(proof.provenance))
36
+ issues.push('Binding proof is absent or has different provenance');
37
+ if (invalidCodeRange(proof.provenance) || proof.provenance.endLine < proof.provenance.startLine)
38
+ issues.push('Invalid binding proof range');
39
+ return issues;
40
+ }
41
+ function selectionIssues(proof, fact, byId) {
42
+ const issues = [...proofIssues(proof, byId)], original = byId.get(proof.factId);
43
+ const selector = fact.codeBinding.selector;
44
+ if (!original?.codeOccurrence)
45
+ return [...issues, 'Binding selection must cite an indexed occurrence'];
46
+ const target = original.codeOccurrence.role === 'definition' ? original.subject : original.object;
47
+ if (target.id !== fact.object.id || proof.provenance.path !== selector?.path)
48
+ issues.push('Binding selection does not identify the target symbol in its selected file');
49
+ if (selector?.line !== undefined && (selector.line !== proof.provenance.startLine || selector.column !== proof.provenance.startColumn))
50
+ issues.push('Binding selected occurrence differs from its exact selector');
51
+ if (selector?.line === undefined && original.codeOccurrence.role !== 'definition')
52
+ issues.push('A name selector requires a definition');
53
+ if (proof.provenance.commitSha !== fact.provenance.commitSha)
54
+ issues.push('Binding selection uses another revision');
55
+ return issues;
56
+ }
57
+ function definitionIssues(proof, fact, byId) {
58
+ const issues = [...proofIssues(proof, byId)], original = byId.get(proof.factId);
59
+ if (original?.codeOccurrence?.role !== 'definition' || original.subject.id !== fact.object.id)
60
+ issues.push('Binding definition does not define its target symbol');
61
+ if (proof.provenance.commitSha !== fact.provenance.commitSha)
62
+ issues.push('Binding definition uses another revision');
63
+ return issues;
64
+ }
65
+ function subjectIssues(proof, fact, byId) {
66
+ const issues = [...proofIssues(proof, byId)], original = byId.get(proof.factId);
67
+ if (!original || ![original.subject, original.object].some(node => node.id === fact.subject.id) ||
68
+ original.authority !== proof.authority || original.sourceAuthority !== proof.sourceAuthority || original.extractionReview !== proof.extractionReview) {
69
+ issues.push('Binding subject evidence or its authority was changed');
70
+ }
71
+ return issues;
72
+ }
73
+ function documentIssues(fact) {
74
+ const documents = fact.codeBinding.documentCitations ?? [];
75
+ if (!documents.some(span => stableJson(span) === stableJson(fact.provenance)) || documents.some(span => span.path !== fact.provenance.path || span.digest !== fact.provenance.digest || span.commitSha !== fact.provenance.commitSha ||
76
+ span.endLine < span.startLine || invalidCodeRange(span)))
77
+ return ['Binding document citations do not match their source'];
78
+ return [];
79
+ }
80
+ function mappingIssues(fact) {
81
+ const binding = fact.codeBinding, issues = [];
82
+ if (fact.predicate !== 'references' && !(binding.subjectEvidence?.length))
83
+ issues.push('Business mapping requires cited subject evidence');
84
+ if (fact.predicate !== 'references' && !['Capability', 'Workflow', 'Contract', 'Invariant'].includes(fact.subject.kind))
85
+ issues.push('Business mapping requires a business subject');
86
+ if (fact.authority === 'draft' && fact.sourceAuthority !== 'draft')
87
+ issues.push('Draft bindings require draft source authority');
88
+ if (fact.sourceRole === 'validation' && fact.predicate === 'implementedBy')
89
+ issues.push('Validation evidence cannot establish an implementation mapping');
90
+ if (binding.subjectEvidence?.some(proof => proof.extractionReview === 'unreviewed') && fact.extractionReview !== 'unreviewed')
91
+ issues.push('Binding cannot approve unreviewed subject evidence');
92
+ return issues;
93
+ }
94
+ /** Supplements shape validation by comparing proofs with the actual same-snapshot assertions. */
95
+ export function bindingEvidenceIssues(fact, byId) {
96
+ const binding = fact.codeBinding;
97
+ if (!binding)
98
+ return [];
99
+ const issues = [...documentIssues(fact), ...mappingIssues(fact)], selector = binding.selector;
100
+ const positive = (n) => typeof n === 'number' && Number.isSafeInteger(n) && n > 0;
101
+ if ((selector?.line !== undefined || selector?.column !== undefined) && (!positive(selector.line) || !positive(selector.column))) {
102
+ issues.push('Exact occurrence selectors require positive line and column');
103
+ }
104
+ return [...issues,
105
+ ...(binding.selectedOccurrences ?? []).flatMap(proof => selectionIssues(proof, fact, byId)),
106
+ ...(binding.definitions ?? []).flatMap(proof => definitionIssues(proof, fact, byId)),
107
+ ...(binding.subjectEvidence ?? []).flatMap(proof => subjectIssues(proof, fact, byId)),
108
+ ];
109
+ }
@@ -0,0 +1,4 @@
1
+ import { type Store, type NamedNode } from 'n3';
2
+ import { type GraphFact } from '@weftlet/core';
3
+ export declare function addCallEvidence(dataset: Store, record: NamedNode, fact: GraphFact, factIri: (id: string) => string): void;
4
+ export declare function callEvidenceIssues(fact: GraphFact, byId: ReadonlyMap<string, GraphFact>): readonly string[];
@@ -0,0 +1,94 @@
1
+ import { DataFactory } from 'n3';
2
+ import { repositoryFileNode } from '@weftlet/core';
3
+ import { CG, RDF, XSD } from './shacl-shapes.js';
4
+ import { stableJson } from '../infrastructure/util.js';
5
+ import { invalidCodeRange } from './code-rdf.js';
6
+ const { namedNode: iri, literal } = DataFactory;
7
+ function containsRange(outer, inner) {
8
+ const compare = (a, b, c, d) => a - c || (b ?? 0) - (d ?? 0);
9
+ return compare(outer.startLine, outer.startColumn, inner.startLine, inner.startColumn) <= 0 &&
10
+ compare(outer.endLine, outer.endColumn, inner.endLine, inner.endColumn) >= 0;
11
+ }
12
+ export function addCallEvidence(dataset, record, fact, factIri) {
13
+ const call = fact.codeCall;
14
+ if (!call && fact.predicate !== 'calls')
15
+ return;
16
+ // Type calls even when metadata is missing so SHACL rejects them without a global OR scan.
17
+ dataset.addQuad(record, iri(RDF + 'type'), iri(CG + 'CodeCall'));
18
+ if (!call)
19
+ return;
20
+ const prop = (node, key, value) => {
21
+ if (value !== undefined)
22
+ dataset.addQuad(node, iri(CG + key), literal(String(value), iri(XSD +
23
+ (typeof value === 'number' ? 'integer' : typeof value === 'boolean' ? 'boolean' : 'string'))));
24
+ };
25
+ for (const [key, value] of Object.entries({ callVersion: call.schemaVersion, callMethod: call.method,
26
+ callSyntax: call.syntax, callDispatch: call.dispatch, callOptional: call.optional, callCaller: call.caller,
27
+ callTarget: call.target, callRuntime: call.runtime }))
28
+ prop(record, key, value);
29
+ const proofs = (key, values) => values.forEach((proof, index) => {
30
+ const term = iri(`${record.value}:${key}:${index}`);
31
+ dataset.addQuad(record, iri(CG + key), term);
32
+ dataset.addQuad(term, iri(RDF + 'type'), iri(CG + 'CallProof'));
33
+ dataset.addQuad(term, iri(CG + 'callFact'), iri(factIri(proof.factId)));
34
+ for (const [key, value] of Object.entries(proof.provenance))
35
+ prop(term, key, value);
36
+ });
37
+ proofs('callCallerDefinition', call.callerDefinitions ?? []);
38
+ proofs('callOccurrence', call.targetOccurrences ?? []);
39
+ proofs('callTargetDefinition', call.targetDefinitions ?? []);
40
+ }
41
+ function proofIssues(fact, proof, byId, role, nodeId) {
42
+ const issues = [];
43
+ const original = byId.get(proof.factId);
44
+ if (!original || stableJson(original.provenance) !== stableJson(proof.provenance) || original.codeOccurrence?.role !== role ||
45
+ (role === 'definition' ? original.subject.id : original.object.id) !== nodeId ||
46
+ proof.provenance.commitSha !== fact.provenance.commitSha || invalidCodeRange(proof.provenance)) {
47
+ issues.push('Call proof is missing, changed, from another revision or does not identify its endpoint');
48
+ }
49
+ return issues;
50
+ }
51
+ function occurrenceIssues(fact, proof, byId) {
52
+ const issues = proofIssues(fact, proof, byId, 'reference', fact.object.id);
53
+ if (proof.provenance.path !== fact.provenance.path || proof.provenance.digest !== fact.provenance.digest ||
54
+ !containsRange(fact.provenance, proof.provenance))
55
+ issues.push('Target occurrence must lie within the cited call site');
56
+ return issues;
57
+ }
58
+ function callerIssues(fact, byId) {
59
+ const call = fact.codeCall, callers = call.callerDefinitions ?? [], issues = [];
60
+ if (call.caller !== 'indexed-callable') {
61
+ if (callers.length || fact.subject.id !== repositoryFileNode(fact.provenance.path).id)
62
+ issues.push('File-scope calls require their canonical physical file');
63
+ return issues;
64
+ }
65
+ if (!callers.length || fact.subject.kind !== 'CodeSymbol')
66
+ issues.push('Indexed caller requires callable definition evidence');
67
+ for (const proof of callers) {
68
+ issues.push(...proofIssues(fact, proof, byId, 'definition', fact.subject.id));
69
+ if (proof.provenance.path !== fact.provenance.path || proof.provenance.digest !== fact.provenance.digest)
70
+ issues.push('Caller and call site must share source bytes');
71
+ }
72
+ return issues;
73
+ }
74
+ export function callEvidenceIssues(fact, byId) {
75
+ const call = fact.codeCall;
76
+ if (!call)
77
+ return fact.predicate === 'calls' ? ['A call requires static syntax and indexed proof metadata'] : [];
78
+ const issues = [];
79
+ if (fact.codeBinding || fact.codeOccurrence)
80
+ issues.push('A call assertion must remain separate from its supporting reference');
81
+ const targets = call.targetDefinitions ?? [], occurrences = call.targetOccurrences ?? [];
82
+ issues.push(...callerIssues(fact, byId));
83
+ if ((call.target === 'external') !== (targets.length === 0))
84
+ issues.push('Call target resolution disagrees with its indexed definitions');
85
+ if (call.target === 'indexed-class' && call.syntax !== 'construct')
86
+ issues.push('A class target requires construction syntax');
87
+ if ((call.syntax === 'construct') !== (call.dispatch === 'construct'))
88
+ issues.push('Construction and dispatch disagree');
89
+ for (const proof of targets)
90
+ issues.push(...proofIssues(fact, proof, byId, 'definition', fact.object.id));
91
+ for (const proof of occurrences)
92
+ issues.push(...occurrenceIssues(fact, proof, byId));
93
+ return issues;
94
+ }