@shapeshift-labs/frontier-lang-compiler 0.2.69 → 0.2.71

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.
@@ -3,12 +3,21 @@ import type { NativeImportRegionTaxonomyKind } from './native-import-losses.js';
3
3
 
4
4
  export type SemanticImportImpactRisk = 'low' | 'medium' | 'high' | string;
5
5
  export type SemanticImportImpactConfidence = 'source-exact' | 'source-addressed' | 'estimated-source-region' | 'review-required' | string;
6
+ export type SemanticImportMergeSignalStatus = 'strong' | 'partial' | 'weak' | 'blocked' | string;
6
7
 
7
8
  export interface SemanticImportImpactVerificationStep {
8
9
  readonly kind: 'dependency-review' | 'source-map-review' | 'reject-or-reprove' | 'proof-review' | 'patch-admission' | 'evidence-review' | string;
9
10
  readonly reason: string;
10
11
  readonly required: boolean;
11
12
  }
13
+ export interface SemanticImportMergeSignal {
14
+ readonly status: SemanticImportMergeSignalStatus;
15
+ readonly score: number;
16
+ readonly missing: readonly string[];
17
+ readonly reviewRequired: boolean;
18
+ readonly queryKeys: readonly string[];
19
+ }
20
+ export interface SemanticImportMergeSignalScoreSummary { readonly min: number; readonly max: number; readonly average: number; }
12
21
 
13
22
  export interface SemanticImportImpactRecord {
14
23
  readonly id: string;
@@ -39,6 +48,7 @@ export interface SemanticImportImpactRecord {
39
48
  readonly readiness: SemanticMergeReadiness;
40
49
  readonly risk: SemanticImportImpactRisk;
41
50
  readonly confidence: SemanticImportImpactConfidence;
51
+ readonly mergeSignal: SemanticImportMergeSignal;
42
52
  }
43
53
 
44
54
  export interface SemanticImportImpactSummary {
@@ -51,5 +61,7 @@ export interface SemanticImportImpactSummary {
51
61
  readonly sourceMapMappings: number; readonly sourcePreservationRecords: number; readonly patchHints: number;
52
62
  readonly proofObligations: number; readonly openProofObligations: number; readonly failedProofObligations: number; readonly paradigmSemantics: number; readonly loweringRecords: number;
53
63
  readonly evidenceIds: number; readonly verificationPlans: readonly string[]; readonly requiredVerificationSteps: number;
64
+ readonly byMergeSignal: Readonly<Record<string, number>>; readonly weakMergeSignals: number; readonly reviewRequiredMergeSignals: number;
65
+ readonly mergeSignalQueryKeys: readonly string[]; readonly mergeSignalScores: SemanticImportMergeSignalScoreSummary;
54
66
  };
55
67
  }
@@ -1,5 +1,5 @@
1
1
  import{nativeLanguageCompileTarget}from'../../coverage-matrix-profiles.js';import{idFragment,uniqueByEvidenceId,uniqueByLossId}from'../../native-import-utils.js';
2
- import{classifyNativeImportReadiness}from'./classifyNativeImportReadiness.js';import{createNativeRoundtripEvidence}from'./createNativeRoundtripEvidence.js';import{createProjectionTargetLossMatrix}from'./createProjectionTargetLossMatrix.js';import{importNativeSource}from'./importNativeSource.js';import{isNativeSourceImportResult}from'./isNativeSourceImportResult.js';import{nativeCompileSourceLanguage}from'./nativeCompileSourceLanguage.js';import{nativeCompileTarget}from'./nativeCompileTarget.js';import{nativeSourceCompileEvidence}from'./nativeSourceCompileEvidence.js';import{nativeSourceCompileSourceMaps}from'./nativeSourceCompileSourceMaps.js';import{nativeSourceCompileTargetCoverage}from'./nativeSourceCompileTargetCoverage.js';import{nativeSourceCompileTargetLosses}from'./nativeSourceCompileTargetLosses.js';import{projectNativeImportToSource}from'./projectNativeImportToSource.js';import{resolveNativeTargetProjectionAdapter}from'./resolveNativeTargetProjectionAdapter.js';import{runNativeTargetProjectionAdapter}from'./runNativeTargetProjectionAdapter.js';import{summarizeNativeImportLosses}from'./summarizeNativeImportLosses.js';
2
+ import{classifyNativeImportReadiness}from'./classifyNativeImportReadiness.js';import{createNativeRoundtripEvidence}from'./createNativeRoundtripEvidence.js';import{createProjectionTargetLossMatrix}from'./createProjectionTargetLossMatrix.js';import{importNativeSource}from'./importNativeSource.js';import{isNativeSourceImportResult}from'./isNativeSourceImportResult.js';import{nativeCompileSourceLanguage}from'./nativeCompileSourceLanguage.js';import{nativeCompileTarget}from'./nativeCompileTarget.js';import{nativeProjectionReview}from'./nativeProjectionReview.js';import{nativeSourceCompileEvidence}from'./nativeSourceCompileEvidence.js';import{nativeSourceCompileSourceMaps}from'./nativeSourceCompileSourceMaps.js';import{nativeSourceCompileTargetCoverage}from'./nativeSourceCompileTargetCoverage.js';import{nativeSourceCompileTargetLosses}from'./nativeSourceCompileTargetLosses.js';import{projectNativeImportToSource}from'./projectNativeImportToSource.js';import{resolveNativeTargetProjectionAdapter}from'./resolveNativeTargetProjectionAdapter.js';import{runNativeTargetProjectionAdapter}from'./runNativeTargetProjectionAdapter.js';import{summarizeNativeImportLosses}from'./summarizeNativeImportLosses.js';
3
3
  export function compileNativeSource(input, options = {}) {
4
4
  const importResult = isNativeSourceImportResult(input) ? input : importNativeSource(input);
5
5
  const sourceLanguage = nativeCompileSourceLanguage(importResult, input);
@@ -124,6 +124,17 @@ export function compileNativeSource(input, options = {}) {
124
124
  compileResultId: id
125
125
  });
126
126
  const sourceMap = sourceMaps[0];
127
+ const projectionReview = nativeProjectionReview({
128
+ mode: projection.mode, outputMode, language: projection.language, sourceLanguage, target,
129
+ sourcePath: importResult.sourcePath ?? importResult.nativeSource?.sourcePath,
130
+ exactSourceAvailable: projection.metadata?.exactSourceAvailable === true,
131
+ sourceTextAvailable: projection.metadata?.sourceTextAvailable === true,
132
+ sourceHashVerified: projection.metadata?.sourceHashVerified === true,
133
+ declarationCount: projection.declarations.length, sourceMapCount: sourceMaps.length,
134
+ losses, targetLosses, readiness: readiness.readiness, targetCoverage, targetProjection
135
+ });
136
+ compileEvidence.metadata.projectionReview = projectionReview;
137
+ for (const record of sourceMaps) record.metadata = { ...(record.metadata ?? {}), projectionReview };
127
138
  const roundtripEvidence = createNativeRoundtripEvidence(importResult, {
128
139
  id: `evidence_${idPart}_${idFragment(target)}_native_roundtrip`,
129
140
  projection,
@@ -179,6 +190,7 @@ export function compileNativeSource(input, options = {}) {
179
190
  targetLossClass: targetCoverage.lossClass,
180
191
  targetReadiness: targetCoverage.readiness,
181
192
  targetSupported: targetCoverage.supported,
193
+ projectionReview,
182
194
  ...options.metadata,
183
195
  roundtripEvidence: roundtripEvidence.metadata.roundtripEvidence
184
196
  }
@@ -140,14 +140,18 @@ export function createLightweightNativeImport(input) {
140
140
  kind: 'import',
141
141
  status: 'passed',
142
142
  path: input.sourcePath,
143
- summary: `Lightweight declaration scan found ${symbols.length} symbol(s) and ${dependencies.summary.total} dependency edge(s).`,
144
- metadata: { parser, dependencyRelations: dependencies.summary.total }
143
+ summary: `Lightweight declaration scan found ${symbols.length} symbol(s), ${dependencies.summary.total} dependency edge(s), and ${dependencies.summary.controlFlow + dependencies.summary.effects + dependencies.summary.mutations} semantic fact(s).`,
144
+ metadata: { parser, dependencyRelations: dependencies.summary.total, semanticFacts: {
145
+ controlFlow: dependencies.summary.controlFlow,
146
+ effects: dependencies.summary.effects,
147
+ mutations: dependencies.summary.mutations
148
+ } }
145
149
  }],
146
150
  metadata: {
147
151
  parser,
148
152
  coverage: 'declarations-only',
149
153
  dependencyRelations: dependencies.summary,
150
- unsupported: ['full expression AST', 'type checking', 'control flow', 'comments and formatting preservation']
154
+ unsupported: ['full expression AST', 'type checking', 'full control-flow graph', 'comments and formatting preservation']
151
155
  }
152
156
  });
153
157
 
@@ -164,6 +168,12 @@ export function createLightweightNativeImport(input) {
164
168
  declarationCount: declarations.length,
165
169
  dependencyRelationCount: dependencies.summary.total,
166
170
  dependencyOccurrenceCount: dependencies.occurrences.length,
171
+ semanticFactCount: dependencies.summary.controlFlow + dependencies.summary.effects + dependencies.summary.mutations,
172
+ semanticFactSummary: {
173
+ controlFlow: dependencies.summary.controlFlow,
174
+ effects: dependencies.summary.effects,
175
+ mutations: dependencies.summary.mutations
176
+ },
167
177
  ...(input.sourcePreservation ? {
168
178
  sourcePreservationId: input.sourcePreservation.id,
169
179
  sourcePreservationSummary: input.sourcePreservation.summary
@@ -0,0 +1,73 @@
1
+ import{uniqueStrings}from'../../native-import-utils.js';
2
+
3
+ export function nativeProjectionReview(input) {
4
+ const lossIds = (input.losses ?? []).map((loss) => loss.id).filter(Boolean);
5
+ const blockingLossIds = (input.losses ?? []).filter((loss) => loss.severity === 'error').map((loss) => loss.id).filter(Boolean);
6
+ const targetLossIds = (input.targetLosses ?? []).map((loss) => loss.id).filter(Boolean);
7
+ const fallbackReasons = nativeProjectionFallbackReasons(input, blockingLossIds, targetLossIds);
8
+ const reviewRequired = input.readiness !== 'ready' || fallbackReasons.length > 0 || blockingLossIds.length > 0;
9
+ const status = nativeProjectionReviewStatus(input, blockingLossIds);
10
+ return {
11
+ kind: 'frontier.lang.nativeProjectionReview',
12
+ version: 1,
13
+ status,
14
+ reviewRequired,
15
+ projectionMode: input.mode,
16
+ outputMode: input.outputMode ?? input.mode,
17
+ language: input.language,
18
+ sourceLanguage: input.sourceLanguage ?? input.language,
19
+ target: input.target,
20
+ sourcePath: input.sourcePath,
21
+ exactSourceAvailable: input.exactSourceAvailable === true,
22
+ sourceTextAvailable: input.sourceTextAvailable === true,
23
+ sourceHashVerified: input.sourceHashVerified === true,
24
+ declarationCount: input.declarationCount ?? 0,
25
+ sourceMapCount: input.sourceMapCount ?? 0,
26
+ readiness: input.readiness,
27
+ targetLossClass: input.targetCoverage?.lossClass,
28
+ targetReadiness: input.targetCoverage?.readiness,
29
+ targetSupported: input.targetCoverage?.supported,
30
+ targetProjectionAdapterId: input.targetProjection?.adapter?.id,
31
+ fallbackReasons,
32
+ lossIds: uniqueStrings(lossIds),
33
+ blockingLossIds: uniqueStrings(blockingLossIds),
34
+ targetLossIds: uniqueStrings(targetLossIds),
35
+ queryKeys: nativeProjectionReviewQueryKeys(input, status, fallbackReasons, reviewRequired)
36
+ };
37
+ }
38
+
39
+ function nativeProjectionFallbackReasons(input, blockingLossIds, targetLossIds) {
40
+ const reasons = [];
41
+ if (input.exactSourceAvailable !== true) reasons.push('no-exact-source');
42
+ if (input.sourceHashVerified !== true) reasons.push('source-hash-unverified');
43
+ if (input.mode === 'native-source-stubs') reasons.push('declaration-stubs');
44
+ if (input.outputMode === 'target-stubs') reasons.push('target-stubs');
45
+ if (input.targetCoverage?.supported === false) reasons.push('target-unsupported');
46
+ if (input.targetCoverage?.lossClass === 'missingAdapter') reasons.push('missing-target-adapter');
47
+ if (blockingLossIds.length > 0) reasons.push('blocking-losses');
48
+ if (targetLossIds.length > 0) reasons.push('target-losses');
49
+ return uniqueStrings(reasons);
50
+ }
51
+
52
+ function nativeProjectionReviewStatus(input, blockingLossIds) {
53
+ if (blockingLossIds.length > 0 || input.readiness === 'blocked' || input.targetCoverage?.readiness === 'blocked') return 'blocked';
54
+ if (input.outputMode === 'target-adapter') return 'target-adapter';
55
+ if (input.outputMode === 'target-stubs' || input.mode === 'native-source-stubs') return 'stub-only';
56
+ if (input.sourceHashVerified === true && input.exactSourceAvailable === true) return 'preserved-source';
57
+ return 'needs-review';
58
+ }
59
+
60
+ function nativeProjectionReviewQueryKeys(input, status, fallbackReasons, reviewRequired) {
61
+ return uniqueStrings([
62
+ `projection-review:${status}`,
63
+ reviewRequired ? 'review-required' : 'review-not-required',
64
+ input.language ? `language:${input.language}` : undefined,
65
+ input.sourceLanguage ? `source-language:${input.sourceLanguage}` : undefined,
66
+ input.target ? `target:${input.target}` : undefined,
67
+ input.sourcePath ? `source:${input.sourcePath}` : undefined,
68
+ input.mode ? `projection-mode:${input.mode}` : undefined,
69
+ input.outputMode ? `output-mode:${input.outputMode}` : undefined,
70
+ input.targetCoverage?.lossClass ? `target-loss-class:${input.targetCoverage.lossClass}` : undefined,
71
+ ...fallbackReasons.map((reason) => `fallback:${reason}`)
72
+ ].filter(Boolean));
73
+ }
@@ -11,6 +11,7 @@ export function observeNativeImporterSemanticEvidence(semanticIndex = {}) {
11
11
  ...relations.filter((relation) => semanticPredicateMatches(relation?.predicate, ['controlflow', 'cfg', 'flow'])),
12
12
  ...facts.filter((fact) => semanticPredicateMatches(fact?.predicate, ['controlflow', 'cfg', 'flow']))
13
13
  ];
14
+ const effectRecords = facts.filter((fact) => semanticPredicateMatches(fact?.predicate, ['effect', 'mutation', 'sideeffect']));
14
15
  return {
15
16
  declarations: occurrences.filter((occurrence) => occurrence?.role === 'definition' || occurrence?.role === 'declaration').length,
16
17
  references: occurrences.filter((occurrence) => {
@@ -18,6 +19,7 @@ export function observeNativeImporterSemanticEvidence(semanticIndex = {}) {
18
19
  return role && role !== 'definition' && role !== 'declaration';
19
20
  }).length + referenceRelations.length,
20
21
  types: typeFacts.length + typedSymbols.length,
21
- controlFlow: controlFlowRecords.length
22
+ controlFlow: controlFlowRecords.length,
23
+ effects: effectRecords.length
22
24
  };
23
25
  }
@@ -1,5 +1,5 @@
1
1
  import{hashSemanticValue}from'@shapeshift-labs/frontier-lang-kernel';
2
- import{classifyNativeImportReadiness}from'./classifyNativeImportReadiness.js';import{createNativeRoundtripEvidence}from'./createNativeRoundtripEvidence.js';import{nativeImportProjectionContext}from'./nativeImportProjectionContext.js';import{nativeProjectionDeclarations}from'./nativeProjectionDeclarations.js';import{nativeProjectionSourceCandidate}from'./nativeProjectionSourceCandidate.js';import{nativeProjectionStubLosses}from'./nativeProjectionStubLosses.js';import{renderNativeProjectionStubs}from'./renderNativeProjectionStubs.js';import{summarizeNativeImportLosses}from'./summarizeNativeImportLosses.js';
2
+ import{classifyNativeImportReadiness}from'./classifyNativeImportReadiness.js';import{createNativeRoundtripEvidence}from'./createNativeRoundtripEvidence.js';import{nativeImportProjectionContext}from'./nativeImportProjectionContext.js';import{nativeProjectionDeclarations}from'./nativeProjectionDeclarations.js';import{nativeProjectionReview}from'./nativeProjectionReview.js';import{nativeProjectionSourceCandidate}from'./nativeProjectionSourceCandidate.js';import{nativeProjectionStubLosses}from'./nativeProjectionStubLosses.js';import{renderNativeProjectionStubs}from'./renderNativeProjectionStubs.js';import{summarizeNativeImportLosses}from'./summarizeNativeImportLosses.js';
3
3
  export function projectNativeImportToSource(importResult, options = {}) {
4
4
  if (!importResult || typeof importResult !== 'object') {
5
5
  throw new Error('projectNativeImportToSource requires a native import result');
@@ -49,6 +49,14 @@ export function projectNativeImportToSource(importResult, options = {}) {
49
49
  parser: context.parser,
50
50
  semanticStatus: context.semanticStatus
51
51
  });
52
+ const projectionReview = nativeProjectionReview({
53
+ mode, language: context.language, sourcePath: context.sourcePath,
54
+ exactSourceAvailable: candidateSource?.exact === true,
55
+ sourceTextAvailable: typeof candidateSource?.sourceText === 'string',
56
+ sourceHashVerified: candidateSource?.hashVerified ?? false,
57
+ declarationCount: declarations.length, losses, readiness: readiness.readiness
58
+ });
59
+ evidence[0].metadata.projectionReview = projectionReview;
52
60
  const result = {
53
61
  kind: 'frontier.lang.nativeSourceProjection',
54
62
  version: 1,
@@ -75,6 +83,7 @@ export function projectNativeImportToSource(importResult, options = {}) {
75
83
  sourcePreservationId: candidateSource?.sourcePreservationId,
76
84
  sourceHashVerified: candidateSource?.hashVerified ?? false,
77
85
  nativeImportLossSummary,
86
+ projectionReview,
78
87
  ...options.metadata
79
88
  }
80
89
  };
@@ -24,7 +24,10 @@ export function lightweightDependencyRelations(input, declarations, documentId)
24
24
  summary: {
25
25
  total: records.relations.length,
26
26
  calls: records.relations.filter((relation) => relation.predicate === 'calls').length,
27
- uses: records.relations.filter((relation) => relation.predicate === 'uses').length
27
+ uses: records.relations.filter((relation) => relation.predicate === 'uses').length,
28
+ controlFlow: records.facts.filter((fact) => fact.predicate === 'controlFlow').length,
29
+ effects: records.facts.filter((fact) => fact.predicate === 'effect').length,
30
+ mutations: records.facts.filter((fact) => fact.predicate === 'mutation').length
28
31
  }
29
32
  };
30
33
  }
@@ -67,6 +70,7 @@ function scanDeclarationDependencies(input, documentId, scan, identifiers, lines
67
70
  const state = { inBlockComment: false };
68
71
  for (let lineNumber = scan.startLine; lineNumber <= scan.endLine; lineNumber += 1) {
69
72
  const scanLine = maskDependencyLine(input, lines[lineNumber - 1]?.line ?? '', state);
73
+ addLightweightSemanticFacts(input, documentId, scan.declaration, scanLine, lineNumber, records);
70
74
  for (const match of scanLine.matchAll(/[A-Za-z_$][\w$]*/g)) {
71
75
  const name = match[0];
72
76
  if (!isDependencyIdentifier(name) || !identifiers.has(name)) continue;
@@ -83,6 +87,68 @@ function scanDeclarationDependencies(input, documentId, scan, identifiers, lines
83
87
  }
84
88
  }
85
89
 
90
+ function addLightweightSemanticFacts(input, documentId, declaration, line, lineNumber, records) {
91
+ const text = String(line ?? '').trim();
92
+ if (!text) return;
93
+ for (const item of lightweightControlFlowKinds(text)) {
94
+ addFactRecord(input, documentId, declaration, 'controlFlow', item, lineNumber, records);
95
+ }
96
+ for (const item of lightweightEffectKinds(text)) {
97
+ addFactRecord(input, documentId, declaration, 'effect', item, lineNumber, records);
98
+ }
99
+ for (const item of lightweightMutationKinds(text)) {
100
+ addFactRecord(input, documentId, declaration, 'mutation', item, lineNumber, records);
101
+ }
102
+ }
103
+
104
+ function lightweightControlFlowKinds(line) {
105
+ const kinds = [];
106
+ if (/\b(if|else|switch|case|default)\b/.test(line)) kinds.push('branch');
107
+ if (/\b(for|while|do)\b/.test(line)) kinds.push('loop');
108
+ if (/\b(return|yield)\b/.test(line)) kinds.push('exit');
109
+ if (/\b(throw|catch|finally|try)\b/.test(line)) kinds.push('exception');
110
+ if (/\b(await|async)\b/.test(line)) kinds.push('async');
111
+ return kinds;
112
+ }
113
+
114
+ function lightweightEffectKinds(line) {
115
+ const kinds = [];
116
+ if (/\bawait\b|import\s*\(/.test(line)) kinds.push('async');
117
+ if (/\b(fetch|XMLHttpRequest|WebSocket|EventSource)\s*\(/.test(line)) kinds.push('network');
118
+ if (/\b(localStorage|sessionStorage|indexedDB|caches|cookie)\b/.test(line)) kinds.push('storage');
119
+ if (/\b(setTimeout|setInterval|requestAnimationFrame|queueMicrotask)\s*\(/.test(line)) kinds.push('scheduler');
120
+ if (/\b(console|process|Deno|Bun)\s*\./.test(line)) kinds.push('host');
121
+ if (/\b(document|window|navigator|location|history)\s*\./.test(line)) kinds.push('browser');
122
+ return kinds;
123
+ }
124
+
125
+ function lightweightMutationKinds(line) {
126
+ const kinds = [];
127
+ if (/\bdelete\s+[A-Za-z_$][\w$.[\]]*/.test(line)) kinds.push('delete');
128
+ if (/(?:^|[^=!<>])=(?!=|>)/.test(line)) kinds.push('assignment');
129
+ if (/\+\+|--|(?:\+=|-=|\*=|\/=|%=|\|\|=|&&=|\?\?=)/.test(line)) kinds.push('update');
130
+ if (/\.(?:push|pop|shift|unshift|splice|sort|reverse|set|add|delete|clear)\s*\(/.test(line)) kinds.push('mutating-call');
131
+ return kinds;
132
+ }
133
+
134
+ function addFactRecord(input, documentId, declaration, predicate, factKind, lineNumber, records) {
135
+ const key = `${declaration.symbolId}|${predicate}|${factKind}|${lineNumber}`;
136
+ if (records.seen.has(key)) return;
137
+ records.seen.add(key);
138
+ records.facts.push({
139
+ id: `fact_${idFragment(declaration.symbolId)}_${predicate}_${idFragment(factKind)}_${lineNumber}`,
140
+ predicate,
141
+ subjectId: declaration.symbolId,
142
+ value: {
143
+ kind: factKind,
144
+ line: lineNumber,
145
+ sourcePath: input.sourcePath,
146
+ documentId,
147
+ confidence: 'lexical-source-scan'
148
+ }
149
+ });
150
+ }
151
+
86
152
  function addDependencyRecord(input, documentId, caller, target, occurrence, records) {
87
153
  const predicate = isCallReference(occurrence.line, occurrence.startColumn + occurrence.name.length - 1) ? 'calls' : 'uses';
88
154
  const key = `${caller.symbolId}|${predicate}|${target.symbolId}|${occurrence.lineNumber}|${occurrence.startColumn}`;
@@ -3,6 +3,7 @@ import type { NativeImportRegionTaxonomyKind } from './index.js';
3
3
 
4
4
  export type SemanticImportImpactRisk = 'low' | 'medium' | 'high' | string;
5
5
  export type SemanticImportImpactConfidence = 'source-exact' | 'source-addressed' | 'estimated-source-region' | 'review-required' | string;
6
+ export type SemanticImportMergeSignalStatus = 'strong' | 'partial' | 'weak' | 'blocked' | string;
6
7
 
7
8
  export interface SemanticImportImpactVerificationStep {
8
9
  readonly kind: 'dependency-review' | 'source-map-review' | 'reject-or-reprove' | 'proof-review' | 'patch-admission' | 'evidence-review' | string;
@@ -10,6 +11,20 @@ export interface SemanticImportImpactVerificationStep {
10
11
  readonly required: boolean;
11
12
  }
12
13
 
14
+ export interface SemanticImportMergeSignal {
15
+ readonly status: SemanticImportMergeSignalStatus;
16
+ readonly score: number;
17
+ readonly missing: readonly string[];
18
+ readonly reviewRequired: boolean;
19
+ readonly queryKeys: readonly string[];
20
+ }
21
+
22
+ export interface SemanticImportMergeSignalScoreSummary {
23
+ readonly min: number;
24
+ readonly max: number;
25
+ readonly average: number;
26
+ }
27
+
13
28
  export interface SemanticImportImpactRecord {
14
29
  readonly id: string;
15
30
  readonly kind: 'ownership-region-impact' | string;
@@ -39,6 +54,7 @@ export interface SemanticImportImpactRecord {
39
54
  readonly readiness: SemanticMergeReadiness;
40
55
  readonly risk: SemanticImportImpactRisk;
41
56
  readonly confidence: SemanticImportImpactConfidence;
57
+ readonly mergeSignal: SemanticImportMergeSignal;
42
58
  }
43
59
 
44
60
  export interface SemanticImportImpactSummary {
@@ -63,5 +79,10 @@ export interface SemanticImportImpactSummary {
63
79
  readonly evidenceIds: number;
64
80
  readonly verificationPlans: readonly string[];
65
81
  readonly requiredVerificationSteps: number;
82
+ readonly byMergeSignal: Readonly<Record<string, number>>;
83
+ readonly weakMergeSignals: number;
84
+ readonly reviewRequiredMergeSignals: number;
85
+ readonly mergeSignalQueryKeys: readonly string[];
86
+ readonly mergeSignalScores: SemanticImportMergeSignalScoreSummary;
66
87
  };
67
88
  }
@@ -1,5 +1,6 @@
1
1
  import { countBy, idFragment, maxSemanticMergeReadiness, uniqueRecordsById, uniqueStrings } from './native-import-utils.js';
2
2
  import { semanticDependencyPredicateKey } from './semantic-import-dependencies.js';
3
+ import { semanticImpactMergeSignal, summarizeSemanticImpactMergeSignals } from './semantic-import-merge-signal.js';
3
4
 
4
5
  function createSemanticImportImpact(input) {
5
6
  const relations = collectDependencyRelations(input.imports, input.dependencies);
@@ -60,7 +61,7 @@ function semanticImpactRecord(input) {
60
61
  relationCount: input.relations.length,
61
62
  verificationPlan
62
63
  });
63
- return {
64
+ const record = {
64
65
  id: `impact_${idFragment(input.region.id ?? input.region.key)}`,
65
66
  kind: 'ownership-region-impact',
66
67
  ownershipRegionId: input.region.id,
@@ -93,6 +94,7 @@ function semanticImpactRecord(input) {
93
94
  risk,
94
95
  confidence: semanticImpactConfidence(input.region, input.sourceMapMappings, input.sourcePreservationRecords)
95
96
  };
97
+ return { ...record, mergeSignal: semanticImpactMergeSignal(record) };
96
98
  }
97
99
 
98
100
  function collectDependencyRelations(imports, dependencies) {
@@ -299,7 +301,8 @@ function summarizeSemanticImpactRecords(records) {
299
301
  loweringRecords: uniqueStrings(records.flatMap((record) => record.loweringRecordIds)).length,
300
302
  evidenceIds: uniqueStrings(records.flatMap((record) => record.evidenceIds)).length,
301
303
  verificationPlans: uniqueStrings(verificationPlans),
302
- requiredVerificationSteps: records.flatMap((record) => record.verificationPlan).filter((step) => step.required).length
304
+ requiredVerificationSteps: records.flatMap((record) => record.verificationPlan).filter((step) => step.required).length,
305
+ ...summarizeSemanticImpactMergeSignals(records)
303
306
  };
304
307
  }
305
308
 
@@ -0,0 +1,86 @@
1
+ import { countBy, uniqueStrings } from './native-import-utils.js';
2
+
3
+ export function semanticImpactMergeSignal(record) {
4
+ const missing = semanticImpactMissingSignals(record);
5
+ const requiredSteps = (record.verificationPlan ?? []).filter((step) => step.required).length;
6
+ const blocked = record.readiness === 'blocked' || (record.failedProofObligationIds ?? []).length > 0;
7
+ const reviewRequired = blocked || record.readiness !== 'ready' || record.risk !== 'low' || requiredSteps > 0 || missing.length > 0;
8
+ const score = semanticImpactMergeScore(record, missing, requiredSteps, blocked);
9
+ const status = semanticImpactMergeStatus(record, score, blocked);
10
+ return {
11
+ status,
12
+ score,
13
+ missing,
14
+ reviewRequired,
15
+ queryKeys: semanticImpactMergeQueryKeys(record, status, missing, reviewRequired)
16
+ };
17
+ }
18
+
19
+ export function summarizeSemanticImpactMergeSignals(records) {
20
+ const signals = (records ?? []).map((record) => record.mergeSignal).filter(Boolean);
21
+ return {
22
+ byMergeSignal: countBy(signals.map((signal) => signal.status)),
23
+ weakMergeSignals: signals.filter((signal) => signal.status === 'weak' || signal.status === 'blocked').length,
24
+ reviewRequiredMergeSignals: signals.filter((signal) => signal.reviewRequired).length,
25
+ mergeSignalQueryKeys: uniqueStrings(signals.flatMap((signal) => signal.queryKeys)),
26
+ mergeSignalScores: semanticImpactMergeScoreSummary(signals)
27
+ };
28
+ }
29
+
30
+ function semanticImpactMissingSignals(record) {
31
+ const missing = [];
32
+ if ((record.evidenceIds ?? []).length === 0) missing.push('evidence');
33
+ if ((record.sourceMapMappingIds ?? []).length === 0) missing.push('source-map');
34
+ if ((record.sourcePreservationRecordIds ?? []).length === 0) missing.push('source-preservation');
35
+ if (record.readiness !== 'ready' && (record.patchHintIds ?? []).length === 0) missing.push('patch-hint');
36
+ if ((record.openProofObligationIds ?? []).length > 0) missing.push('proof-obligations');
37
+ if (record.confidence === 'estimated-source-region' || record.confidence === 'review-required') missing.push('source-confidence');
38
+ return uniqueStrings(missing);
39
+ }
40
+
41
+ function semanticImpactMergeScore(record, missing, requiredSteps, blocked) {
42
+ let score = 100;
43
+ if (blocked) score -= 70;
44
+ else if (record.risk === 'high') score -= 35;
45
+ else if (record.risk === 'medium') score -= 18;
46
+ if (record.readiness === 'blocked') score -= 30;
47
+ else if (record.readiness === 'needs-review') score -= 18;
48
+ else if (record.readiness === 'ready-with-losses') score -= 8;
49
+ score -= Math.min(requiredSteps * 8, 32);
50
+ score -= Math.min(missing.length * 7, 35);
51
+ if ((record.failedProofObligationIds ?? []).length > 0) score -= 30;
52
+ if ((record.openProofObligationIds ?? []).length > 0) score -= 12;
53
+ if (record.confidence === 'source-exact') score += 5;
54
+ return Math.max(0, Math.min(100, Math.round(score)));
55
+ }
56
+
57
+ function semanticImpactMergeStatus(record, score, blocked) {
58
+ if (blocked || score < 25) return 'blocked';
59
+ if (score < 50 || record.risk === 'high') return 'weak';
60
+ if (score < 80 || record.risk === 'medium' || record.readiness !== 'ready') return 'partial';
61
+ return 'strong';
62
+ }
63
+
64
+ function semanticImpactMergeQueryKeys(record, status, missing, reviewRequired) {
65
+ return uniqueStrings([
66
+ `merge-signal:${status}`,
67
+ `risk:${record.risk ?? 'unknown'}`,
68
+ `readiness:${record.readiness ?? 'unknown'}`,
69
+ reviewRequired ? 'review-required' : 'review-not-required',
70
+ ...missing.map((item) => `missing:${item}`),
71
+ record.sourcePath ? `source:${record.sourcePath}` : undefined,
72
+ record.ownershipKey ? `ownership:${record.ownershipKey}` : undefined,
73
+ ...(record.symbolNames ?? []).map((name) => `symbol-name:${name}`),
74
+ ...(record.dependencyPredicates ?? []).map((predicate) => `predicate:${predicate}`)
75
+ ].filter(Boolean));
76
+ }
77
+
78
+ function semanticImpactMergeScoreSummary(signals) {
79
+ if (!signals.length) return { min: 0, max: 0, average: 0 };
80
+ const scores = signals.map((signal) => signal.score);
81
+ return {
82
+ min: Math.min(...scores),
83
+ max: Math.max(...scores),
84
+ average: Math.round(scores.reduce((sum, score) => sum + score, 0) / scores.length)
85
+ };
86
+ }
@@ -11,6 +11,7 @@ import {
11
11
  function semanticImportSidecarEntry(imported, index, options) {
12
12
  const semanticIndex = imported?.semanticIndex ?? imported?.universalAst?.semanticIndex;
13
13
  const nativeAst = imported?.nativeAst ?? imported?.nativeSource?.ast;
14
+ const semanticFacts = semanticIndex?.facts ?? [];
14
15
  const sourceMaps = imported?.sourceMaps ?? imported?.universalAst?.sourceMaps ?? [];
15
16
  const sourceMapMappings = sourceMaps.flatMap((sourceMap) => sourceMap?.mappings ?? []);
16
17
  const sourcePreservationRecords = collectKernelSourcePreservationFromImport(imported);
@@ -18,6 +19,7 @@ function semanticImportSidecarEntry(imported, index, options) {
18
19
  const proofSpec = summarizeProofSpecLayer(imported?.universalAst?.proof ?? imported?.proof);
19
20
  const paradigmSemantics = summarizeParadigmSemanticsLayer(imported?.universalAst?.paradigmSemantics ?? imported?.paradigmSemantics);
20
21
  const dependencies = summarizeSemanticImportDependencyRelations(semanticIndex?.relations ?? []);
22
+ const factSummary = summarizeSemanticFacts(semanticFacts);
21
23
  const readiness = semanticImportEntryReadiness(imported);
22
24
  const mappingsBySymbolId = new Map();
23
25
  for (const mapping of sourceMapMappings) {
@@ -72,6 +74,9 @@ function semanticImportSidecarEntry(imported, index, options) {
72
74
  paradigmSemantics,
73
75
  dependencyRelationCount: dependencies.total,
74
76
  dependencyPredicates: dependencies.predicates,
77
+ semanticFactCount: semanticFacts.length,
78
+ semanticFactPredicates: factSummary.predicates,
79
+ semanticFactSummary: factSummary.byPredicate,
75
80
  readiness,
76
81
  emptySemanticIndex: symbols.length === 0,
77
82
  regionTaxonomy,
@@ -90,4 +95,14 @@ function semanticImportEntryReadiness(imported) {
90
95
  return readiness ?? 'needs-review';
91
96
  }
92
97
 
98
+ function summarizeSemanticFacts(facts) {
99
+ const byPredicate = {};
100
+ for (const fact of facts ?? []) {
101
+ const predicate = String(fact?.predicate ?? '').trim();
102
+ if (!predicate) continue;
103
+ byPredicate[predicate] = (byPredicate[predicate] ?? 0) + 1;
104
+ }
105
+ return { byPredicate, predicates: Object.keys(byPredicate).sort() };
106
+ }
107
+
93
108
  export { semanticImportSidecarEntry };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@shapeshift-labs/frontier-lang-compiler",
3
- "version": "0.2.69",
3
+ "version": "0.2.71",
4
4
  "description": "Compiler facade for Frontier Lang source documents and language projection adapters.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",