@shapeshift-labs/frontier-lang-compiler 0.2.121 → 0.2.122

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/README.md CHANGED
@@ -286,6 +286,12 @@ re-export, export-star, local export, `export default`, and TypeScript
286
286
  `importedName`, `exportedName`, `isTypeOnly`, and public-contract metadata into
287
287
  the semantic index and project symbol graph.
288
288
 
289
+ `safeMergeJsTsProject` stays synchronous. When a caller already has parser-backed
290
+ native import results for merged output files, pass them as `outputProjectImports`
291
+ with `includeOutputProjectSymbolGraph`. The graph builder matches supplied
292
+ imports by `sourcePath` and `sourceHash`, uses them for output graph artifacts,
293
+ and falls back to the lightweight scanner for missing or stale files.
294
+
289
295
  High-risk native features also have explicit evidence policies. These policies are advisory in this package: they tell a swarm or admission queue what evidence is missing without silently changing the existing readiness classification.
290
296
 
291
297
  ```js
@@ -7,6 +7,7 @@ import type {
7
7
  JsTsSafeMergeSummary
8
8
  } from './js-ts-safe-merge.js';
9
9
  import type { JsTsSafeMemberMergePolicy, JsTsSafeMemberMergePolicyRegion } from './js-ts-safe-member-merge.js';
10
+ import type { NativeSourceImportResult } from './import-adapter-core.js';
10
11
  import type { NativeProjectImportResult, NativeProjectSymbolGraphSummary } from './native-project.js';
11
12
  import type { NativeProjectModuleResolutionOptions } from './native-project-module-resolution.js';
12
13
 
@@ -45,6 +46,11 @@ export type JsTsProjectSafeMergeFileMap =
45
46
  | ReadonlyMap<string, string>
46
47
  | readonly { readonly sourcePath?: string; readonly path?: string; readonly sourceText?: string; readonly text?: string }[];
47
48
 
49
+ export type JsTsProjectSafeMergeOutputProjectImports =
50
+ | readonly NativeSourceImportResult[]
51
+ | ReadonlyMap<string, NativeSourceImportResult>
52
+ | Readonly<Record<string, NativeSourceImportResult>>;
53
+
48
54
  export interface JsTsProjectSafeMergeInput {
49
55
  readonly id?: string;
50
56
  readonly language?: FrontierSourceLanguage | string;
@@ -56,6 +62,7 @@ export interface JsTsProjectSafeMergeInput {
56
62
  readonly allowFileAdditions?: boolean;
57
63
  readonly allowFileDeletes?: boolean;
58
64
  readonly includeOutputProjectSymbolGraph?: boolean;
65
+ readonly outputProjectImports?: JsTsProjectSafeMergeOutputProjectImports;
59
66
  readonly moduleResolution?: NativeProjectModuleResolutionOptions;
60
67
  readonly tsconfig?: NativeProjectModuleResolutionOptions;
61
68
  readonly workerChangeSetId?: string;
@@ -126,6 +133,7 @@ export interface JsTsProjectSafeMergeResult {
126
133
  readonly mergedFiles: number;
127
134
  readonly blockedFiles: number;
128
135
  readonly outputFiles: number;
136
+ readonly projectGraphConflicts: number;
129
137
  readonly semanticArtifactFiles: number;
130
138
  readonly operations: Readonly<Record<string, number>>;
131
139
  };
@@ -182,13 +182,16 @@ export interface NativeProjectSymbolGraphSummary {
182
182
  readonly remainingFields: readonly NativeProjectSymbolGraphRemainingField[];
183
183
  }
184
184
 
185
+ export interface NativeProjectSourcePreservationSummary { readonly total: number; readonly exactSourceAvailable: number; readonly sourceBytes: number; readonly tokens: number; readonly trivia: number; readonly directives: number; readonly truncated: boolean; readonly ids: readonly string[]; }
186
+
185
187
  export interface NativeProjectImportResultMetadata extends Record<string, unknown> {
186
- readonly importResultContract?: NativeImportResultContract;
187
- readonly projectAdmission?: NativeProjectImportAdmission;
188
- readonly semanticMergeAdmission?: NativeProjectImportAdmission;
189
- readonly nativeImportLossSummary?: NativeImportLossSummary;
190
- readonly semanticMergeReadiness?: SemanticMergeReadiness;
188
+ readonly sourceCount?: number; readonly sourcePaths?: readonly string[];
189
+ readonly importResultContract?: NativeImportResultContract; readonly projectAdmission?: NativeProjectImportAdmission;
190
+ readonly semanticMergeAdmission?: NativeProjectImportAdmission; readonly nativeImportLossSummary?: NativeImportLossSummary;
191
+ readonly sourcePreservationSummary?: NativeProjectSourcePreservationSummary; readonly semanticMergeReadiness?: SemanticMergeReadiness;
191
192
  readonly readinessReasons?: readonly string[];
193
+ readonly regionSummary?: NativeImportRegionSummary; readonly sourceMapSummary?: NativeImportSourceMapSummary;
194
+ readonly adapterCoverageSummary?: NativeImportAdapterCoverageContract;
192
195
  readonly projectSymbolGraph?: NativeProjectSymbolGraphSummary;
193
196
  }
194
197
 
@@ -244,24 +244,17 @@ function publicContractRegionForDeclaration(declaration, ownershipRegion, module
244
244
 
245
245
  function reExportIdentityForDeclaration(declaration, input, documentId, relationId, ownershipRegion, moduleEdge) {
246
246
  if (!moduleEdge?.isReExport && !declaration.reExport) return undefined;
247
+ const bindingCount = Number(declaration.nativeNode?.metadata?.bindingCount ?? declaration.metadata?.bindingCount ?? 0);
248
+ if (declaration.symbolKind === 'module' && !declaration.exportStar && !declaration.metadata?.exportStar && bindingCount > 1) return undefined;
249
+ const identityId = hashSemanticValue([relationId, declaration.symbolId, declaration.exportedName ?? declaration.metadata?.exportedName, declaration.importedName ?? declaration.metadata?.importedName, declaration.localName ?? declaration.metadata?.localName]);
250
+ const stableId = bindingCount > 1 ? idFragment(identityId) : idFragment(relationId);
247
251
  return compactRecord({
248
- kind: 'frontier.lang.reExportIdentity',
249
- version: 1,
250
- id: `reexport_${idFragment(relationId)}`,
251
- sourceDocumentId: documentId,
252
- sourcePath: input.sourcePath,
253
- sourceHash: input.sourceHash,
254
- moduleSpecifier: moduleEdge?.moduleSpecifier,
255
- exportedName: declaration.exportedName ?? declaration.metadata?.exportedName,
256
- importedName: declaration.importedName ?? declaration.metadata?.importedName,
257
- localName: declaration.localName ?? declaration.metadata?.localName,
258
- namespace: declaration.namespace ?? declaration.metadata?.namespace,
259
- isTypeOnly: declaration.isTypeOnly ?? declaration.metadata?.isTypeOnly ?? declaration.metadata?.typeOnly,
260
- exportStar: declaration.exportStar ?? declaration.metadata?.exportStar,
261
- symbolId: declaration.symbolId,
262
- relationId,
263
- ownershipRegionId: ownershipRegion.id,
264
- ownershipRegionKey: ownershipRegion.key,
252
+ kind: 'frontier.lang.reExportIdentity', version: 1, id: `reexport_${stableId}`, sourceDocumentId: documentId,
253
+ sourcePath: input.sourcePath, sourceHash: input.sourceHash, moduleSpecifier: moduleEdge?.moduleSpecifier,
254
+ exportedName: declaration.exportedName ?? declaration.metadata?.exportedName, importedName: declaration.importedName ?? declaration.metadata?.importedName,
255
+ localName: declaration.localName ?? declaration.metadata?.localName, namespace: declaration.namespace ?? declaration.metadata?.namespace,
256
+ isTypeOnly: declaration.isTypeOnly ?? declaration.metadata?.isTypeOnly ?? declaration.metadata?.typeOnly, exportStar: declaration.exportStar ?? declaration.metadata?.exportStar,
257
+ symbolId: declaration.symbolId, relationId, ownershipRegionId: ownershipRegion.id, ownershipRegionKey: ownershipRegion.key,
265
258
  publicContract: true
266
259
  });
267
260
  }
@@ -59,6 +59,7 @@ function createJsTsSafeMergeSemanticArtifacts(input = {}, merge = {}) {
59
59
  id: `js_ts_safe_merge_replay_${idFragment(id)}`,
60
60
  projection,
61
61
  currentSourceText: headSourceText,
62
+ currentSourceHash: headReplaySourceHash(input),
62
63
  currentSourcePath: sourcePath,
63
64
  language
64
65
  });
@@ -66,6 +67,7 @@ function createJsTsSafeMergeSemanticArtifacts(input = {}, merge = {}) {
66
67
  id: `js_ts_safe_merge_replay_already_applied_${idFragment(id)}`,
67
68
  projection,
68
69
  currentSourceText: mergedSourceText,
70
+ currentSourceHash: projection.projectedHash,
69
71
  currentSourcePath: sourcePath,
70
72
  language
71
73
  });
@@ -74,6 +76,8 @@ function createJsTsSafeMergeSemanticArtifacts(input = {}, merge = {}) {
74
76
  const status = !blocked && replayReady && alreadyAppliedReady ? 'verified' : 'blocked';
75
77
  const finalReasonCodes = uniqueStrings([
76
78
  ...reasonCodes,
79
+ ...(replayReady ? [] : replay.admission?.reasonCodes ?? replay.summary?.reasonCodes ?? []),
80
+ ...(alreadyAppliedReady ? [] : alreadyAppliedReplay.admission?.reasonCodes ?? alreadyAppliedReplay.summary?.reasonCodes ?? []),
77
81
  replayReady ? undefined : `semantic-replay-${replay.status}`,
78
82
  alreadyAppliedReady ? undefined : `semantic-replay-already-applied-${alreadyAppliedReplay.status}`
79
83
  ]);
@@ -263,4 +267,12 @@ function countBy(values, keyFor) {
263
267
  return result;
264
268
  }
265
269
 
270
+ function headReplaySourceHash(input) {
271
+ return nativeSemanticHash(input.currentSourceHash) ?? nativeSemanticHash(input.headHash);
272
+ }
273
+
274
+ function nativeSemanticHash(value) {
275
+ return typeof value === 'string' && /^fnv1a32:[0-9a-f]+$/i.test(value) ? value : undefined;
276
+ }
277
+
266
278
  export { createJsTsSafeMergeSemanticArtifacts };
@@ -0,0 +1,45 @@
1
+ import { compactRecord } from './js-ts-safe-merge-context.js';
2
+
3
+ function outputProjectGraphConflicts(projectSymbolGraph) {
4
+ const importEdges = Array.isArray(projectSymbolGraph?.importEdges) ? projectSymbolGraph.importEdges : [];
5
+ const groups = new Map();
6
+ for (const edge of importEdges) {
7
+ if (!isMissingProjectImportEdge(edge)) continue;
8
+ const key = [edge.sourcePath, edge.moduleSpecifier, edge.resolutionKind, edge.resolvedModulePath].join('\u0000');
9
+ const group = groups.get(key) ?? [];
10
+ group.push(edge);
11
+ groups.set(key, group);
12
+ }
13
+ return [...groups.values()].map(projectGraphMissingImportConflict);
14
+ }
15
+
16
+ function projectGraphMissingImportConflict(group) {
17
+ const edge = group[0] ?? {};
18
+ return {
19
+ code: 'project-output-module-unresolved',
20
+ gateId: 'project-symbol-graph',
21
+ message: `Output project graph contains unresolved ${edge.resolutionKind ?? 'missing'} import ${JSON.stringify(edge.moduleSpecifier ?? edge.resolvedModulePath ?? 'unknown')}.`,
22
+ sourcePath: edge.sourcePath,
23
+ details: compactRecord({
24
+ reasonCode: 'project-output-module-unresolved',
25
+ conflictKey: `project-module#${edge.sourcePath ?? 'unknown'}#${edge.moduleSpecifier ?? edge.resolvedModulePath ?? 'unknown'}`,
26
+ sourcePath: edge.sourcePath,
27
+ moduleSpecifier: edge.moduleSpecifier,
28
+ resolutionKind: edge.resolutionKind,
29
+ resolvedModulePath: edge.resolvedModulePath,
30
+ edgeIds: uniqueStrings(group.map((record) => record.id)),
31
+ importKinds: uniqueStrings(group.map((record) => record.importKind)),
32
+ importedNames: uniqueStrings(group.map((record) => record.importedName))
33
+ })
34
+ };
35
+ }
36
+
37
+ function isMissingProjectImportEdge(edge) {
38
+ return typeof edge?.resolutionKind === 'string' && edge.resolutionKind.endsWith('-missing');
39
+ }
40
+
41
+ function uniqueStrings(values) {
42
+ return [...new Set(values.filter((value) => typeof value === 'string' && value.length > 0))];
43
+ }
44
+
45
+ export { outputProjectGraphConflicts };
@@ -8,9 +8,18 @@ function createJsTsProjectSafeMergeGraphArtifacts(input, outputFiles, mergeId) {
8
8
  language: file.language ?? input.language ?? languageForPath(file.sourcePath),
9
9
  sourcePath: file.sourcePath,
10
10
  sourceText: file.sourceText,
11
+ sourceHash: file.sourceHash,
11
12
  metadata: { semanticImportExpected: true, projectSafeMergeOutput: true }
12
13
  }));
13
- const imports = sources.map((source) => importNativeSource(source));
14
+ const suppliedImports = normalizeOutputProjectImports(input.outputProjectImports);
15
+ const importSelections = sources.map((source) => {
16
+ const suppliedImport = matchingOutputProjectImport(source, suppliedImports);
17
+ return {
18
+ importResult: suppliedImport ?? importNativeSource(source),
19
+ sourceKind: suppliedImport ? 'supplied-output-project-import' : 'lightweight-output-project-scan'
20
+ };
21
+ });
22
+ const imports = importSelections.map((selection) => selection.importResult);
14
23
  const projectImport = createNativeProjectImportResult({
15
24
  id: `${mergeId}_output_project_import`,
16
25
  projectRoot: input.projectRoot,
@@ -18,7 +27,12 @@ function createJsTsProjectSafeMergeGraphArtifacts(input, outputFiles, mergeId) {
18
27
  sources,
19
28
  metadata: {
20
29
  projectSafeMergeId: mergeId,
21
- outputProjectSymbolGraph: true
30
+ outputProjectSymbolGraph: true,
31
+ outputProjectImportSource: {
32
+ suppliedImports: suppliedImports.length,
33
+ matchedSuppliedImports: importSelections.filter((selection) => selection.sourceKind === 'supplied-output-project-import').length,
34
+ scannerFallbackImports: importSelections.filter((selection) => selection.sourceKind === 'lightweight-output-project-scan').length
35
+ }
22
36
  }
23
37
  }, imports);
24
38
  return {
@@ -27,10 +41,55 @@ function createJsTsProjectSafeMergeGraphArtifacts(input, outputFiles, mergeId) {
27
41
  };
28
42
  }
29
43
 
44
+ function normalizeOutputProjectImports(value) {
45
+ if (!value) return [];
46
+ if (Array.isArray(value)) return value.filter(Boolean);
47
+ if (value instanceof Map) return [...value.values()].filter(Boolean);
48
+ if (typeof value === 'object') return Object.values(value).filter(Boolean);
49
+ return [];
50
+ }
51
+
52
+ function matchingOutputProjectImport(source, imports) {
53
+ const sourcePath = String(source.sourcePath ?? '');
54
+ const sourceHash = String(source.sourceHash ?? '');
55
+ return imports.find((importResult) => {
56
+ const importSourcePath = sourcePathForImport(importResult);
57
+ if (importSourcePath && sourcePath && importSourcePath !== sourcePath) return false;
58
+ const importSourceHash = sourceHashForImport(importResult);
59
+ if (importSourceHash && sourceHash && importSourceHash !== sourceHash) return false;
60
+ return importSourcePath === sourcePath || importSourceHash === sourceHash;
61
+ });
62
+ }
63
+
64
+ function sourcePathForImport(importResult) {
65
+ return firstString(
66
+ importResult?.sourcePath,
67
+ importResult?.nativeSource?.sourcePath,
68
+ importResult?.nativeAst?.sourcePath,
69
+ importResult?.semanticIndex?.documents?.[0]?.path
70
+ );
71
+ }
72
+
73
+ function sourceHashForImport(importResult) {
74
+ return firstString(
75
+ importResult?.sourceHash,
76
+ importResult?.nativeSource?.sourceHash,
77
+ importResult?.nativeAst?.sourceHash,
78
+ importResult?.semanticIndex?.documents?.[0]?.sourceHash
79
+ );
80
+ }
81
+
30
82
  function languageForPath(sourcePath) {
31
83
  const path = String(sourcePath ?? '').toLowerCase();
32
84
  if (path.endsWith('.js') || path.endsWith('.jsx') || path.endsWith('.mjs') || path.endsWith('.cjs')) return 'javascript';
33
85
  return 'typescript';
34
86
  }
35
87
 
88
+ function firstString(...values) {
89
+ for (const value of values) {
90
+ if (value !== undefined && value !== null && String(value)) return String(value);
91
+ }
92
+ return undefined;
93
+ }
94
+
36
95
  export { createJsTsProjectSafeMergeGraphArtifacts };
@@ -2,13 +2,13 @@ import { hashSemanticValue } from '@shapeshift-labs/frontier-lang-kernel';
2
2
  import { safeMergeJsTsSource } from './js-ts-safe-merge-composed.js';
3
3
  import { compactRecord } from './js-ts-safe-merge-context.js';
4
4
  import { createJsTsProjectSafeMergeGraphArtifacts } from './js-ts-safe-project-merge-graph.js';
5
+ import { outputProjectGraphConflicts } from './js-ts-safe-project-merge-graph-conflicts.js';
5
6
 
6
7
  function safeMergeJsTsProject(input = {}) {
7
8
  const id = String(input.id ?? 'js_ts_project_safe_merge');
8
9
  const files = normalizeProjectFiles(input);
9
10
  const fileResults = files.map((file) => mergeProjectFile(file, input, id));
10
11
  const blockedFiles = fileResults.filter((file) => file.status === 'blocked');
11
- const status = blockedFiles.length ? 'blocked' : 'merged';
12
12
  const outputFiles = fileResults
13
13
  .filter((file) => typeof file.outputSourceText === 'string')
14
14
  .map((file) => compactRecord({
@@ -18,10 +18,19 @@ function safeMergeJsTsProject(input = {}) {
18
18
  sourceHash: file.outputHash,
19
19
  operation: file.operation
20
20
  }));
21
- const reasonCodes = uniqueStrings(blockedFiles.flatMap((file) => file.admission.reasonCodes));
22
- const graphArtifacts = status === 'merged' && input.includeOutputProjectSymbolGraph
21
+ const graphArtifacts = blockedFiles.length === 0 && input.includeOutputProjectSymbolGraph
23
22
  ? createJsTsProjectSafeMergeGraphArtifacts(input, outputFiles, id)
24
23
  : undefined;
24
+ const graphConflicts = outputProjectGraphConflicts(graphArtifacts?.projectSymbolGraph);
25
+ const status = blockedFiles.length || graphConflicts.length ? 'blocked' : 'merged';
26
+ const reasonCodes = uniqueStrings([
27
+ ...blockedFiles.flatMap((file) => file.admission.reasonCodes),
28
+ ...graphConflicts.map((conflict) => conflict.code)
29
+ ]);
30
+ const conflictKeys = uniqueStrings([
31
+ ...fileResults.flatMap((file) => file.conflictKeys),
32
+ ...graphConflicts.map((conflict) => conflict.details?.conflictKey)
33
+ ]);
25
34
  const core = {
26
35
  kind: 'frontier.lang.jsTsProjectSafeMerge',
27
36
  version: 1,
@@ -32,7 +41,7 @@ function safeMergeJsTsProject(input = {}) {
32
41
  outputFiles,
33
42
  outputProjectImport: graphArtifacts?.projectImport,
34
43
  outputProjectSymbolGraph: graphArtifacts?.projectSymbolGraph,
35
- conflicts: fileResults.flatMap((file) => file.conflicts),
44
+ conflicts: [...fileResults.flatMap((file) => file.conflicts), ...graphConflicts],
36
45
  admission: {
37
46
  status: status === 'merged' ? 'auto-merge-candidate' : 'blocked',
38
47
  action: status === 'merged' ? 'apply-project' : 'human-review',
@@ -41,15 +50,16 @@ function safeMergeJsTsProject(input = {}) {
41
50
  autoMergeClaim: false,
42
51
  semanticEquivalenceClaim: false,
43
52
  reasonCodes,
44
- conflictKeys: uniqueStrings(fileResults.flatMap((file) => file.conflictKeys))
53
+ conflictKeys
45
54
  },
46
- summary: projectSummary(fileResults),
55
+ summary: projectSummary(fileResults, graphConflicts),
47
56
  metadata: compactRecord({
48
57
  workerChangeSetId: input.workerChangeSetId,
49
58
  headChangeSetId: input.headChangeSetId,
50
59
  projectRoot: input.projectRoot,
51
60
  filesInput: Array.isArray(input.files) ? 'records' : 'maps',
52
61
  outputProjectSymbolGraph: Boolean(graphArtifacts?.projectSymbolGraph),
62
+ projectGraphConflicts: graphConflicts.length || undefined,
53
63
  autoMergeClaim: false,
54
64
  semanticEquivalenceClaim: false
55
65
  })
@@ -235,7 +245,7 @@ function sourceLedgersForFile(input, sourcePath) {
235
245
  return undefined;
236
246
  }
237
247
 
238
- function projectSummary(files) {
248
+ function projectSummary(files, graphConflicts = []) {
239
249
  const byOperation = {};
240
250
  for (const file of files) byOperation[file.operation] = (byOperation[file.operation] ?? 0) + 1;
241
251
  return {
@@ -243,6 +253,7 @@ function projectSummary(files) {
243
253
  mergedFiles: files.filter((file) => file.status === 'merged').length,
244
254
  blockedFiles: files.filter((file) => file.status === 'blocked').length,
245
255
  outputFiles: files.filter((file) => typeof file.outputSourceText === 'string').length,
256
+ projectGraphConflicts: graphConflicts.length,
246
257
  semanticArtifactFiles: files.filter((file) => file.semanticArtifacts).length,
247
258
  operations: byOperation
248
259
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@shapeshift-labs/frontier-lang-compiler",
3
- "version": "0.2.121",
3
+ "version": "0.2.122",
4
4
  "description": "Compiler facade for Frontier Lang source documents and language projection adapters.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",