@shapeshift-labs/frontier-lang-compiler 0.2.78 → 0.2.80
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 +21 -0
- package/dist/declarations/semantic-edit-script.d.ts +143 -0
- package/dist/declarations/semantic-history.d.ts +48 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.js +2 -0
- package/dist/internal/index-impl/semanticEditScriptClassification.js +135 -0
- package/dist/internal/index-impl/semanticEditScripts.js +220 -0
- package/dist/internal/index-impl/semanticHistoryLineageResolution.js +256 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -353,6 +353,27 @@ console.log(changeSet.mergeCandidate.readiness); // merge-admission classificati
|
|
|
353
353
|
|
|
354
354
|
Use `diffNativeSourceImports` when the worker or runner already produced `importNativeSource` results. Changed regions include a `metadata.changedRegionProjection` envelope with before/after source hashes, source-map links, ownership keys, readiness, and `autoMergeClaim: false` so swarm admission tools can score or port patches without treating semantic metadata as proof. Body-only edits that the lightweight scanner cannot anchor to a symbol are still reported as file-level changed regions instead of being silently treated as safe.
|
|
355
355
|
|
|
356
|
+
Build a three-way semantic edit script when a coordinator has base, worker, and current-head source text. This is the first deterministic bridge from semantic sidecars to automatic merge admission:
|
|
357
|
+
|
|
358
|
+
```js
|
|
359
|
+
import { createSemanticEditScript } from '@shapeshift-labs/frontier-lang-compiler';
|
|
360
|
+
|
|
361
|
+
const script = createSemanticEditScript({
|
|
362
|
+
language: 'typescript',
|
|
363
|
+
sourcePath: 'src/runtime.ts',
|
|
364
|
+
baseSourceText: 'export function step(v) { return v + 1; }\n',
|
|
365
|
+
workerSourceText: 'export function step(v) { return v + 2; }\n',
|
|
366
|
+
headSourceText: 'export function step(v) { return v + 1; }\n'
|
|
367
|
+
});
|
|
368
|
+
|
|
369
|
+
console.log(script.operations[0].kind); // "replaceBody"
|
|
370
|
+
console.log(script.operations[0].status); // "portable"
|
|
371
|
+
console.log(script.admission.status); // "auto-merge-candidate"
|
|
372
|
+
console.log(script.admission.autoMergeClaim); // false
|
|
373
|
+
```
|
|
374
|
+
|
|
375
|
+
If the current head already changed the same semantic anchor, the script reports `conflict`; if the anchor moved or was renamed, it reports `needs-port` with a `reanchor` target; if no current head is supplied, it reports a review-only candidate. The script is intentionally an edit/admission record, not a proof of semantic equivalence.
|
|
376
|
+
|
|
356
377
|
Bundle a native diff into a compact semantic patch record when a coordinator needs hashes, changed semantic regions, source-map links, proof/history/evidence IDs, and merge-admission status without loading whole source files:
|
|
357
378
|
|
|
358
379
|
```js
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
EvidenceRecord,
|
|
3
|
+
FrontierSourceLanguage,
|
|
4
|
+
SemanticMergeReadiness,
|
|
5
|
+
SourceSpan
|
|
6
|
+
} from '@shapeshift-labs/frontier-lang-kernel';
|
|
7
|
+
import type { ImportNativeSourceOptions, NativeSourceImportResult } from './import-adapter-core.js';
|
|
8
|
+
|
|
9
|
+
export type SemanticEditScriptOperationStatus =
|
|
10
|
+
| 'candidate'
|
|
11
|
+
| 'portable'
|
|
12
|
+
| 'already-applied'
|
|
13
|
+
| 'needs-port'
|
|
14
|
+
| 'conflict'
|
|
15
|
+
| 'stale'
|
|
16
|
+
| 'blocked';
|
|
17
|
+
|
|
18
|
+
export type SemanticEditScriptAdmissionStatus =
|
|
19
|
+
| 'auto-merge-candidate'
|
|
20
|
+
| 'needs-port'
|
|
21
|
+
| 'conflict'
|
|
22
|
+
| 'stale'
|
|
23
|
+
| 'blocked'
|
|
24
|
+
| 'evidence-only';
|
|
25
|
+
|
|
26
|
+
export interface SemanticEditScriptOperation {
|
|
27
|
+
readonly id: string;
|
|
28
|
+
readonly kind: string;
|
|
29
|
+
readonly changeKind?: string;
|
|
30
|
+
readonly anchor: {
|
|
31
|
+
readonly key?: string;
|
|
32
|
+
readonly conflictKey?: string;
|
|
33
|
+
readonly regionId?: string;
|
|
34
|
+
readonly regionKind?: string;
|
|
35
|
+
readonly granularity?: string;
|
|
36
|
+
readonly language?: FrontierSourceLanguage | string;
|
|
37
|
+
readonly sourcePath?: string;
|
|
38
|
+
readonly symbolId?: string;
|
|
39
|
+
readonly symbolName?: string;
|
|
40
|
+
readonly symbolKind?: string;
|
|
41
|
+
readonly sourceSpan?: SourceSpan;
|
|
42
|
+
};
|
|
43
|
+
readonly hashes?: {
|
|
44
|
+
readonly baseSourceHash?: string;
|
|
45
|
+
readonly workerSourceHash?: string;
|
|
46
|
+
readonly headSourceHash?: string;
|
|
47
|
+
readonly baseSpanHash?: string;
|
|
48
|
+
readonly workerSpanHash?: string;
|
|
49
|
+
readonly headSpanHash?: string;
|
|
50
|
+
readonly baseTextHash?: string;
|
|
51
|
+
readonly workerTextHash?: string;
|
|
52
|
+
readonly beforeSignatureHash?: string;
|
|
53
|
+
readonly afterSignatureHash?: string;
|
|
54
|
+
};
|
|
55
|
+
readonly status: SemanticEditScriptOperationStatus;
|
|
56
|
+
readonly reanchor?: {
|
|
57
|
+
readonly fromAnchorKey?: string;
|
|
58
|
+
readonly toAnchorKey?: string;
|
|
59
|
+
readonly lineageStatus?: string;
|
|
60
|
+
readonly traversedEventIds?: readonly string[];
|
|
61
|
+
};
|
|
62
|
+
readonly readiness: SemanticMergeReadiness | string;
|
|
63
|
+
readonly confidence: number;
|
|
64
|
+
readonly reasonCodes: readonly string[];
|
|
65
|
+
readonly evidenceIds?: readonly string[];
|
|
66
|
+
readonly metadata?: Record<string, unknown>;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export interface SemanticEditScriptSummary {
|
|
70
|
+
readonly operations: number;
|
|
71
|
+
readonly byStatus: Readonly<Record<string, number>>;
|
|
72
|
+
readonly byKind: Readonly<Record<string, number>>;
|
|
73
|
+
readonly portable: number;
|
|
74
|
+
readonly alreadyApplied: number;
|
|
75
|
+
readonly needsPort: number;
|
|
76
|
+
readonly conflicts: number;
|
|
77
|
+
readonly stale: number;
|
|
78
|
+
readonly blocked: number;
|
|
79
|
+
readonly candidates: number;
|
|
80
|
+
readonly autoMergeCandidates: number;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export interface SemanticEditScriptAdmission {
|
|
84
|
+
readonly status: SemanticEditScriptAdmissionStatus;
|
|
85
|
+
readonly action: 'run-gates-and-apply' | 'reanchor-or-human-port' | 'record-evidence' | 'block' | string;
|
|
86
|
+
readonly reviewRequired: boolean;
|
|
87
|
+
readonly autoApplyCandidate: boolean;
|
|
88
|
+
readonly autoMergeClaim: false;
|
|
89
|
+
readonly semanticEquivalenceClaim: false;
|
|
90
|
+
readonly reasonCodes: readonly string[];
|
|
91
|
+
readonly conflictKeys: readonly string[];
|
|
92
|
+
readonly evidenceIds: readonly string[];
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
export interface SemanticEditScript {
|
|
96
|
+
readonly kind: 'frontier.lang.semanticEditScript';
|
|
97
|
+
readonly version: 1;
|
|
98
|
+
readonly schema: 'frontier.lang.semanticEditScript.v1';
|
|
99
|
+
readonly id: string;
|
|
100
|
+
readonly stableId: string;
|
|
101
|
+
readonly hash: string;
|
|
102
|
+
readonly language?: FrontierSourceLanguage | string;
|
|
103
|
+
readonly sourcePath?: string;
|
|
104
|
+
readonly baseHash?: string;
|
|
105
|
+
readonly workerHash?: string;
|
|
106
|
+
readonly headHash?: string;
|
|
107
|
+
readonly workerChangeSetId: string;
|
|
108
|
+
readonly headChangeSetId?: string;
|
|
109
|
+
readonly lineageInferenceId?: string;
|
|
110
|
+
readonly operations: readonly SemanticEditScriptOperation[];
|
|
111
|
+
readonly summary: SemanticEditScriptSummary;
|
|
112
|
+
readonly admission: SemanticEditScriptAdmission;
|
|
113
|
+
readonly evidence: readonly EvidenceRecord[];
|
|
114
|
+
readonly metadata?: Record<string, unknown>;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
export interface CreateSemanticEditScriptOptions {
|
|
118
|
+
readonly id?: string;
|
|
119
|
+
readonly language?: FrontierSourceLanguage | string;
|
|
120
|
+
readonly sourcePath?: string;
|
|
121
|
+
readonly parser?: string;
|
|
122
|
+
readonly base?: NativeSourceImportResult | ImportNativeSourceOptions;
|
|
123
|
+
readonly before?: NativeSourceImportResult | ImportNativeSourceOptions;
|
|
124
|
+
readonly worker?: NativeSourceImportResult | ImportNativeSourceOptions;
|
|
125
|
+
readonly after?: NativeSourceImportResult | ImportNativeSourceOptions;
|
|
126
|
+
readonly head?: NativeSourceImportResult | ImportNativeSourceOptions;
|
|
127
|
+
readonly current?: NativeSourceImportResult | ImportNativeSourceOptions;
|
|
128
|
+
readonly baseSourceText?: string;
|
|
129
|
+
readonly beforeSourceText?: string;
|
|
130
|
+
readonly workerSourceText?: string;
|
|
131
|
+
readonly afterSourceText?: string;
|
|
132
|
+
readonly headSourceText?: string;
|
|
133
|
+
readonly currentSourceText?: string;
|
|
134
|
+
readonly baseSourceHash?: string;
|
|
135
|
+
readonly workerSourceHash?: string;
|
|
136
|
+
readonly headSourceHash?: string;
|
|
137
|
+
readonly generatedAt?: number;
|
|
138
|
+
readonly evidenceId?: string;
|
|
139
|
+
readonly metadata?: Record<string, unknown>;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
export declare const SemanticEditScriptAdmissionStatuses: readonly SemanticEditScriptAdmissionStatus[];
|
|
143
|
+
export declare function createSemanticEditScript(input?: CreateSemanticEditScriptOptions): SemanticEditScript;
|
|
@@ -6,7 +6,7 @@ import type {
|
|
|
6
6
|
SourceSpan
|
|
7
7
|
} from '@shapeshift-labs/frontier-lang-kernel';
|
|
8
8
|
import type { SemanticImportOwnershipRegion } from './semantic-sidecar.js';
|
|
9
|
-
import type { CreateSemanticLineageEventInput, SemanticLineageEvent } from './semantic-lineage.js';
|
|
9
|
+
import type { CreateSemanticLineageEventInput, SemanticAnchor, SemanticLineageEvent, SemanticLineageMap, SemanticLineageResolution } from './semantic-lineage.js';
|
|
10
10
|
|
|
11
11
|
import type { SemanticHistoryAdmissionStatus, SemanticHistoryReviewerStatus, SemanticHistoryReplayLinkKind, SemanticHistoryOverlapKind, SemanticHistoryConflictReason, SemanticHistoryClaimKind, SemanticHistoryClaimStatus, SemanticHistoryProofAttemptStatus, SemanticHistoryMergeDecisionStatus, SemanticHistoryActorRef, SemanticHistoryRecordSourceRef, SemanticHistorySourceRef, SemanticHistoryOwnershipRegionRef, SemanticHistoryCandidateRef, SemanticHistoryClaimRecord, SemanticHistoryImportedParserEvidenceRecord, SemanticHistoryProofAttemptRecord, SemanticHistoryPatchAncestryRecord, SemanticHistoryMergeDecisionRecord, SemanticHistoryClaimInput, SemanticHistoryImportedParserEvidenceInput, SemanticHistoryProofAttemptInput, SemanticHistoryPatchAncestryInput, SemanticHistoryMergeDecisionInput, SemanticHistoryReviewerState, SemanticHistoryAdmissionState, SemanticHistoryReplayLink, SemanticHistoryRecordIndex } from './semantic-history-records.js';
|
|
12
12
|
export type { SemanticHistoryAdmissionStatus, SemanticHistoryReviewerStatus, SemanticHistoryReplayLinkKind, SemanticHistoryOverlapKind, SemanticHistoryConflictReason, SemanticHistoryClaimKind, SemanticHistoryClaimStatus, SemanticHistoryProofAttemptStatus, SemanticHistoryMergeDecisionStatus, SemanticHistoryActorRef, SemanticHistoryRecordSourceRef, SemanticHistorySourceRef, SemanticHistoryOwnershipRegionRef, SemanticHistoryCandidateRef, SemanticHistoryClaimRecord, SemanticHistoryImportedParserEvidenceRecord, SemanticHistoryProofAttemptRecord, SemanticHistoryPatchAncestryRecord, SemanticHistoryMergeDecisionRecord, SemanticHistoryClaimInput, SemanticHistoryImportedParserEvidenceInput, SemanticHistoryProofAttemptInput, SemanticHistoryPatchAncestryInput, SemanticHistoryMergeDecisionInput, SemanticHistoryReviewerState, SemanticHistoryAdmissionState, SemanticHistoryReplayLink, SemanticHistoryRecordIndex } from './semantic-history-records.js';
|
|
@@ -138,6 +138,51 @@ export interface SemanticHistoryOverlapRecord {
|
|
|
138
138
|
readonly reviewer: { readonly left?: SemanticHistoryReviewerStatus; readonly right?: SemanticHistoryReviewerStatus };
|
|
139
139
|
}
|
|
140
140
|
|
|
141
|
+
export type SemanticHistoryLineageAnchorSetKind = 'active' | 'candidate' | 'inactive' | 'deleted' | 'unresolved' | 'blocked';
|
|
142
|
+
|
|
143
|
+
export interface SemanticHistoryLineageAnchorRecord extends Pick<SemanticAnchor, 'key' | 'id' | 'kind' | 'language' | 'sourcePath' | 'symbolId' | 'symbolName'> {
|
|
144
|
+
readonly status: string;
|
|
145
|
+
readonly resolutionId: string;
|
|
146
|
+
readonly reasonCodes: readonly string[];
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
export type SemanticHistoryLineageAnchorInventory = Readonly<Record<SemanticHistoryLineageAnchorSetKind, readonly SemanticHistoryLineageAnchorRecord[]>>;
|
|
150
|
+
|
|
151
|
+
export interface SemanticHistoryRecordLineageResolution {
|
|
152
|
+
readonly kind: 'frontier.lang.semanticHistoryRecordLineageResolution';
|
|
153
|
+
readonly version: 1;
|
|
154
|
+
readonly id: string;
|
|
155
|
+
readonly stableId: string;
|
|
156
|
+
readonly hash: string;
|
|
157
|
+
readonly sourceRecordId?: string;
|
|
158
|
+
readonly sourceRecordStableId?: string;
|
|
159
|
+
readonly sourceRecordHash?: string;
|
|
160
|
+
readonly generatedAt: number | string;
|
|
161
|
+
readonly resolutions: readonly SemanticLineageResolution[];
|
|
162
|
+
readonly anchorInventory: SemanticHistoryLineageAnchorInventory;
|
|
163
|
+
readonly resolvedRecord: SemanticHistoryRecord;
|
|
164
|
+
readonly summary: Record<string, unknown> & {
|
|
165
|
+
readonly anchorCount: number;
|
|
166
|
+
readonly currentAnchorKeys: readonly string[];
|
|
167
|
+
readonly ownershipKeys: readonly string[];
|
|
168
|
+
readonly conflictKeys: readonly string[];
|
|
169
|
+
readonly activeAnchorKeys: readonly string[];
|
|
170
|
+
readonly candidateAnchorKeys: readonly string[];
|
|
171
|
+
readonly inactiveAnchorKeys: readonly string[];
|
|
172
|
+
readonly deletedAnchorKeys: readonly string[];
|
|
173
|
+
readonly unresolvedAnchorKeys: readonly string[];
|
|
174
|
+
readonly blockedAnchorKeys: readonly string[];
|
|
175
|
+
readonly reasonCodes: readonly string[];
|
|
176
|
+
};
|
|
177
|
+
readonly admission: {
|
|
178
|
+
readonly readiness: SemanticMergeReadiness | string;
|
|
179
|
+
readonly reviewRequired: boolean;
|
|
180
|
+
readonly autoMergeClaim: false;
|
|
181
|
+
readonly semanticEquivalenceClaim: false;
|
|
182
|
+
};
|
|
183
|
+
readonly metadata?: Record<string, unknown>;
|
|
184
|
+
}
|
|
185
|
+
|
|
141
186
|
export declare const SemanticHistoryAdmissionStatuses: readonly SemanticHistoryAdmissionStatus[];
|
|
142
187
|
export declare const SemanticHistoryReviewerStatuses: readonly SemanticHistoryReviewerStatus[];
|
|
143
188
|
export declare const SemanticHistoryOverlapKinds: readonly SemanticHistoryOverlapKind[];
|
|
@@ -146,3 +191,5 @@ export declare function createSemanticHistoryRecord(input?: CreateSemanticHistor
|
|
|
146
191
|
export declare function querySemanticHistoryRecordOverlaps(records: SemanticHistoryRecord | readonly SemanticHistoryRecord[], options?: SemanticHistoryOverlapQueryOptions): readonly SemanticHistoryOverlapRecord[];
|
|
147
192
|
export declare function semanticHistoryRecordsOverlap(left: SemanticHistoryRecord, right: SemanticHistoryRecord, options?: SemanticHistoryOverlapQueryOptions): boolean;
|
|
148
193
|
export declare function semanticHistoryRecordsConflict(left: SemanticHistoryRecord, right: SemanticHistoryRecord, options?: SemanticHistoryOverlapQueryOptions): boolean;
|
|
194
|
+
export declare function resolveSemanticHistoryRecordLineage(record: SemanticHistoryRecord, eventsOrMap?: SemanticLineageMap | readonly CreateSemanticLineageEventInput[], options?: { readonly id?: string; readonly generatedAt?: number | string; readonly maxDepth?: number; readonly keepDeletedAnchors?: boolean; readonly keepCandidateAnchors?: boolean; readonly keepBlockedAnchors?: boolean; readonly keepUnresolvedAnchors?: boolean; readonly metadata?: Record<string, unknown> }): SemanticHistoryRecordLineageResolution;
|
|
195
|
+
export declare function resolveSemanticHistoryRecordsLineage(records: readonly SemanticHistoryRecord[] | SemanticHistoryRecord, eventsOrMap?: SemanticLineageMap | readonly CreateSemanticLineageEventInput[], options?: { readonly generatedAt?: number | string; readonly maxDepth?: number; readonly keepDeletedAnchors?: boolean; readonly keepCandidateAnchors?: boolean; readonly keepBlockedAnchors?: boolean; readonly keepUnresolvedAnchors?: boolean; readonly metadata?: Record<string, unknown> }): readonly SemanticHistoryRecordLineageResolution[];
|
package/dist/index.d.ts
CHANGED
|
@@ -16,6 +16,7 @@ export * from './declarations/source-preservation.js';
|
|
|
16
16
|
export * from './declarations/semantic-sidecar-admission.js';
|
|
17
17
|
export * from './declarations/semantic-merge-candidates.js';
|
|
18
18
|
export * from './declarations/semantic-merge-conflicts.js';
|
|
19
|
+
export * from './declarations/semantic-edit-script.js';
|
|
19
20
|
export * from './declarations/semantic-lineage.js';
|
|
20
21
|
export * from './declarations/semantic-history.js';
|
|
21
22
|
export * from './declarations/semantic-patch-bundle.js';
|
package/dist/index.js
CHANGED
|
@@ -69,10 +69,12 @@ export { queryProjectionReadinessMatrix } from './internal/index-impl/queryProje
|
|
|
69
69
|
export { createSemanticPatchBundleRecord, querySemanticPatchBundleRecords, SemanticPatchBundleAdmissionStatuses } from './internal/index-impl/semanticPatchBundleRecords.js';
|
|
70
70
|
export { createSemanticMergeCandidateAdmissionRecord, decorateSemanticMergeCandidateForAdmission, querySemanticMergeCandidateAdmissionOverlaps, SemanticMergeCandidateProjectionRisks, semanticMergeCandidateReadinessSortKey, sortSemanticMergeCandidateAdmissionRecords } from './internal/index-impl/semanticMergeCandidateRecords.js';
|
|
71
71
|
export { querySemanticMergeConflictClasses, SemanticMergeConflictClasses, semanticMergeConflictRiskScore, sortSemanticMergeCandidatesByConflictRisk, summarizeSemanticMergeConflicts } from './internal/index-impl/semanticMergeConflicts.js';
|
|
72
|
+
export { createSemanticEditScript, SemanticEditScriptAdmissionStatuses } from './internal/index-impl/semanticEditScripts.js';
|
|
72
73
|
export { queryUniversalConversionPlan } from './internal/index-impl/queryUniversalConversionPlan.js';
|
|
73
74
|
export { createSemanticAnchor, createSemanticLineageEvent, createSemanticLineageMap, querySemanticLineageEvents, SemanticLineageEventKinds } from './internal/index-impl/semanticLineageRecords.js';
|
|
74
75
|
export { resolveSemanticLineage, resolveSemanticLineageBatch, SemanticLineageResolutionStatuses } from './internal/index-impl/semanticLineageResolutionRecords.js';
|
|
75
76
|
export { createSemanticHistoryRecord, querySemanticHistoryRecordOverlaps, SemanticHistoryAdmissionStatuses, SemanticHistoryConflictReasons, SemanticHistoryOverlapKinds, SemanticHistoryReviewerStatuses, semanticHistoryRecordsConflict, semanticHistoryRecordsOverlap } from './internal/index-impl/semanticHistoryRecords.js';
|
|
77
|
+
export { resolveSemanticHistoryRecordLineage, resolveSemanticHistoryRecordsLineage } from './internal/index-impl/semanticHistoryLineageResolution.js';
|
|
76
78
|
export { readSemanticSliceJson } from './internal/index-impl/readSemanticSliceJson.js';
|
|
77
79
|
export { readUniversalAstJson } from './internal/index-impl/readUniversalAstJson.js';
|
|
78
80
|
export { renderTargetAst } from './internal/index-impl/renderTargetAst.js';
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
import { uniqueStrings } from '../../native-import-utils.js';
|
|
2
|
+
import { resolveSemanticLineage } from './semanticLineageResolutionRecords.js';
|
|
3
|
+
|
|
4
|
+
export const SemanticEditScriptAdmissionStatuses = Object.freeze([
|
|
5
|
+
'auto-merge-candidate',
|
|
6
|
+
'needs-port',
|
|
7
|
+
'conflict',
|
|
8
|
+
'stale',
|
|
9
|
+
'blocked',
|
|
10
|
+
'evidence-only'
|
|
11
|
+
]);
|
|
12
|
+
|
|
13
|
+
export function classifySemanticEdit(input) {
|
|
14
|
+
if (!input.context.head) return editStatus('candidate', 'needs-review', 0.66, ['head-source-not-provided']);
|
|
15
|
+
if (!input.anchorKey) return editStatus('blocked', 'blocked', 0, ['missing-semantic-anchor']);
|
|
16
|
+
if (input.context.workerChangeSet.beforeHash &&
|
|
17
|
+
input.context.headChangeSet?.afterHash === input.context.workerChangeSet.beforeHash &&
|
|
18
|
+
sameSourcePath(input.context.base, input.context.head)) {
|
|
19
|
+
return editStatus('portable', 'ready', 0.95, ['head-source-matches-base']);
|
|
20
|
+
}
|
|
21
|
+
if (input.region.changeKind === 'added') return classifyAddedRegion(input);
|
|
22
|
+
if (!input.baseSymbol) return classifyMissingBaseAnchor(input);
|
|
23
|
+
if (!input.headSymbol) return classifyMissingHeadAnchor(input);
|
|
24
|
+
if (input.headSymbol.spanHash && input.baseSymbol.spanHash && input.headSymbol.spanHash === input.baseSymbol.spanHash) {
|
|
25
|
+
return editStatus('portable', 'ready', 0.9, ['head-anchor-matches-base']);
|
|
26
|
+
}
|
|
27
|
+
if (input.headSymbol.spanHash && input.workerSymbol?.spanHash && input.headSymbol.spanHash === input.workerSymbol.spanHash) {
|
|
28
|
+
return editStatus('already-applied', 'ready', 0.92, ['head-anchor-matches-worker']);
|
|
29
|
+
}
|
|
30
|
+
return editStatus('conflict', 'blocked', 0.2, ['head-anchor-changed-since-base']);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export function summarizeSemanticEditOperations(operations) {
|
|
34
|
+
const byStatus = countBy(operations.map((operation) => operation.status));
|
|
35
|
+
const byKind = countBy(operations.map((operation) => operation.kind));
|
|
36
|
+
return {
|
|
37
|
+
operations: operations.length,
|
|
38
|
+
byStatus,
|
|
39
|
+
byKind,
|
|
40
|
+
portable: byStatus.portable ?? 0,
|
|
41
|
+
alreadyApplied: byStatus['already-applied'] ?? 0,
|
|
42
|
+
needsPort: byStatus['needs-port'] ?? 0,
|
|
43
|
+
conflicts: byStatus.conflict ?? 0,
|
|
44
|
+
stale: byStatus.stale ?? 0,
|
|
45
|
+
blocked: byStatus.blocked ?? 0,
|
|
46
|
+
candidates: byStatus.candidate ?? 0,
|
|
47
|
+
autoMergeCandidates: (byStatus.portable ?? 0) + (byStatus['already-applied'] ?? 0)
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export function semanticEditAdmission(input) {
|
|
52
|
+
const status = admissionStatus(input.summary);
|
|
53
|
+
return {
|
|
54
|
+
status,
|
|
55
|
+
action: admissionAction(status),
|
|
56
|
+
reviewRequired: status !== 'auto-merge-candidate',
|
|
57
|
+
autoApplyCandidate: status === 'auto-merge-candidate',
|
|
58
|
+
autoMergeClaim: false,
|
|
59
|
+
semanticEquivalenceClaim: false,
|
|
60
|
+
reasonCodes: uniqueStrings([
|
|
61
|
+
...input.workerChangeSet.reasons,
|
|
62
|
+
...(input.headChangeSet?.reasons ?? []),
|
|
63
|
+
...input.operations.flatMap((operation) => operation.reasonCodes ?? [])
|
|
64
|
+
]),
|
|
65
|
+
conflictKeys: uniqueStrings(input.operations.map((operation) => operation.anchor?.conflictKey).filter(Boolean)),
|
|
66
|
+
evidenceIds: uniqueStrings(input.operations.flatMap((operation) => operation.evidenceIds ?? []))
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function classifyAddedRegion(input) {
|
|
71
|
+
if (!input.headSymbol) return editStatus('portable', 'ready', 0.86, ['added-anchor-absent-from-head']);
|
|
72
|
+
if (input.headSymbol.spanHash && input.workerSymbol?.spanHash && input.headSymbol.spanHash === input.workerSymbol.spanHash) {
|
|
73
|
+
return editStatus('already-applied', 'ready', 0.88, ['added-anchor-already-present-in-head']);
|
|
74
|
+
}
|
|
75
|
+
return editStatus('conflict', 'blocked', 0.25, ['added-anchor-already-exists-in-head']);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function classifyMissingBaseAnchor(input) {
|
|
79
|
+
const conflictKey = input.region.conflictKey ?? `region:${input.anchorKey}`;
|
|
80
|
+
if (input.context.headChangedConflictKeys.has(conflictKey)) {
|
|
81
|
+
return editStatus('conflict', 'blocked', 0.18, ['base-anchor-missing-and-head-changed-same-region']);
|
|
82
|
+
}
|
|
83
|
+
return editStatus('stale', 'needs-review', 0.25, ['base-anchor-missing']);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function classifyMissingHeadAnchor(input) {
|
|
87
|
+
const resolved = input.context.headLineage
|
|
88
|
+
? resolveSemanticLineage(input.context.headLineage.lineageMap, { anchorKey: input.anchorKey })
|
|
89
|
+
: undefined;
|
|
90
|
+
if (resolved?.status === 'resolved' && resolved.currentAnchors.length === 1) {
|
|
91
|
+
return editStatus('needs-port', 'needs-review', 0.72, ['anchor-moved-or-renamed'], {
|
|
92
|
+
fromAnchorKey: input.anchorKey,
|
|
93
|
+
toAnchorKey: resolved.currentAnchors[0].key,
|
|
94
|
+
lineageStatus: resolved.status,
|
|
95
|
+
traversedEventIds: resolved.traversedEventIds
|
|
96
|
+
}, resolved.evidenceIds);
|
|
97
|
+
}
|
|
98
|
+
if (resolved?.status === 'ambiguous') {
|
|
99
|
+
return editStatus('blocked', 'blocked', 0.1, ['anchor-lineage-ambiguous'], { fromAnchorKey: input.anchorKey, lineageStatus: resolved.status }, resolved.evidenceIds);
|
|
100
|
+
}
|
|
101
|
+
if (resolved?.status === 'deleted') {
|
|
102
|
+
return editStatus('blocked', 'blocked', 0.1, ['anchor-deleted-in-head'], { fromAnchorKey: input.anchorKey, lineageStatus: resolved.status }, resolved.evidenceIds);
|
|
103
|
+
}
|
|
104
|
+
return editStatus('stale', 'needs-review', 0.25, ['head-anchor-missing']);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function editStatus(status, readiness, confidence, reasonCodes, reanchor, evidenceIds = []) {
|
|
108
|
+
return { status, readiness, confidence, reasonCodes, reanchor, evidenceIds };
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
function admissionStatus(summary) {
|
|
112
|
+
if (summary.operations === 0) return 'evidence-only';
|
|
113
|
+
if (summary.blocked > 0) return 'blocked';
|
|
114
|
+
if (summary.conflicts > 0) return 'conflict';
|
|
115
|
+
if (summary.stale > 0) return 'stale';
|
|
116
|
+
if (summary.needsPort > 0 || summary.candidates > 0) return 'needs-port';
|
|
117
|
+
return 'auto-merge-candidate';
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
function admissionAction(status) {
|
|
121
|
+
if (status === 'auto-merge-candidate') return 'run-gates-and-apply';
|
|
122
|
+
if (status === 'needs-port') return 'reanchor-or-human-port';
|
|
123
|
+
if (status === 'evidence-only') return 'record-evidence';
|
|
124
|
+
return 'block';
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
function countBy(values) {
|
|
128
|
+
const result = {};
|
|
129
|
+
for (const value of values.filter(Boolean)) result[value] = (result[value] ?? 0) + 1;
|
|
130
|
+
return result;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
function sameSourcePath(left, right) {
|
|
134
|
+
return (left?.sourcePath ?? left?.nativeSource?.sourcePath) === (right?.sourcePath ?? right?.nativeSource?.sourcePath);
|
|
135
|
+
}
|
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
import { hashSemanticValue } from '@shapeshift-labs/frontier-lang-kernel';
|
|
2
|
+
import { idFragment, uniqueStrings } from '../../native-import-utils.js';
|
|
3
|
+
import { createSemanticImportSidecar } from './createSemanticImportSidecar.js';
|
|
4
|
+
import { diffNativeSourceImports } from './diffNativeSourceImports.js';
|
|
5
|
+
import { inferSemanticLineageEvents } from './inferSemanticLineageEvents.js';
|
|
6
|
+
import { mapDiffSymbols } from './mapDiffSymbols.js';
|
|
7
|
+
import { nativeImportSourceText } from './nativeImportSourceText.js';
|
|
8
|
+
import { normalizeNativeDiffImport } from './normalizeNativeDiffImport.js';
|
|
9
|
+
import {
|
|
10
|
+
classifySemanticEdit,
|
|
11
|
+
SemanticEditScriptAdmissionStatuses,
|
|
12
|
+
semanticEditAdmission,
|
|
13
|
+
summarizeSemanticEditOperations
|
|
14
|
+
} from './semanticEditScriptClassification.js';
|
|
15
|
+
import { sourceTextForSpan } from './sourceTextForSpan.js';
|
|
16
|
+
|
|
17
|
+
export { SemanticEditScriptAdmissionStatuses };
|
|
18
|
+
|
|
19
|
+
export function createSemanticEditScript(input = {}, options = {}) {
|
|
20
|
+
const language = input.language ?? input.worker?.language ?? input.after?.language ?? input.base?.language ?? input.before?.language;
|
|
21
|
+
if (!language) throw new Error('createSemanticEditScript requires a language');
|
|
22
|
+
const sourcePath = input.sourcePath ?? input.worker?.sourcePath ?? input.after?.sourcePath ?? input.base?.sourcePath ?? input.before?.sourcePath;
|
|
23
|
+
const base = normalizeNativeDiffImport(sourceInput(input, 'base'), { ...input, language, sourcePath }, 'base');
|
|
24
|
+
const worker = normalizeNativeDiffImport(sourceInput(input, 'worker'), { ...input, language, sourcePath }, 'worker');
|
|
25
|
+
if (!base || !worker) throw new Error('createSemanticEditScript requires base and worker source inputs');
|
|
26
|
+
const head = normalizeNativeDiffImport(sourceInput(input, 'head'), { ...input, language, sourcePath }, 'head');
|
|
27
|
+
const workerChangeSet = diffNativeSourceImports({
|
|
28
|
+
...input,
|
|
29
|
+
language,
|
|
30
|
+
sourcePath,
|
|
31
|
+
before: base,
|
|
32
|
+
after: worker,
|
|
33
|
+
id: input.workerChangeSetId ?? `${input.id ?? 'semantic_edit'}_worker`
|
|
34
|
+
});
|
|
35
|
+
const headChangeSet = head ? diffNativeSourceImports({
|
|
36
|
+
...input,
|
|
37
|
+
language,
|
|
38
|
+
sourcePath,
|
|
39
|
+
before: base,
|
|
40
|
+
after: head,
|
|
41
|
+
id: input.headChangeSetId ?? `${input.id ?? 'semantic_edit'}_head`
|
|
42
|
+
}) : undefined;
|
|
43
|
+
const headLineage = head ? inferSemanticLineageEvents({
|
|
44
|
+
before: base,
|
|
45
|
+
after: head,
|
|
46
|
+
id: input.lineageInferenceId ?? `${input.id ?? 'semantic_edit'}_head_lineage`,
|
|
47
|
+
language,
|
|
48
|
+
sourcePath,
|
|
49
|
+
generatedAt: input.generatedAt,
|
|
50
|
+
metadata: { source: 'createSemanticEditScript' }
|
|
51
|
+
}) : undefined;
|
|
52
|
+
const context = createEditContext({ base, worker, head, workerChangeSet, headChangeSet, headLineage });
|
|
53
|
+
const operations = workerChangeSet.changedRegions.map((region, index) => semanticEditOperation(region, index, context, input));
|
|
54
|
+
const summary = summarizeSemanticEditOperations(operations);
|
|
55
|
+
const admission = semanticEditAdmission({ operations, summary, head, workerChangeSet, headChangeSet, input });
|
|
56
|
+
const evidence = semanticEditEvidence({ input, language, sourcePath, workerChangeSet, headChangeSet, headLineage, summary, admission });
|
|
57
|
+
const core = {
|
|
58
|
+
kind: 'frontier.lang.semanticEditScript',
|
|
59
|
+
version: 1,
|
|
60
|
+
schema: 'frontier.lang.semanticEditScript.v1',
|
|
61
|
+
language,
|
|
62
|
+
sourcePath,
|
|
63
|
+
baseHash: workerChangeSet.beforeHash,
|
|
64
|
+
workerHash: workerChangeSet.afterHash,
|
|
65
|
+
headHash: headChangeSet?.afterHash,
|
|
66
|
+
workerChangeSetId: workerChangeSet.id,
|
|
67
|
+
headChangeSetId: headChangeSet?.id,
|
|
68
|
+
lineageInferenceId: headLineage?.id,
|
|
69
|
+
operations,
|
|
70
|
+
summary,
|
|
71
|
+
admission,
|
|
72
|
+
evidence,
|
|
73
|
+
metadata: compactRecord({
|
|
74
|
+
autoMergeClaim: false,
|
|
75
|
+
semanticEquivalenceClaim: false,
|
|
76
|
+
workerReadiness: workerChangeSet.readiness,
|
|
77
|
+
headReadiness: headChangeSet?.readiness,
|
|
78
|
+
workerReasons: workerChangeSet.reasons,
|
|
79
|
+
headReasons: headChangeSet?.reasons,
|
|
80
|
+
...input.metadata,
|
|
81
|
+
...options.metadata
|
|
82
|
+
})
|
|
83
|
+
};
|
|
84
|
+
const hash = hashSemanticValue(core);
|
|
85
|
+
const id = input.id ?? `semantic_edit_script_${idFragment(firstString(sourcePath, language, hash))}`;
|
|
86
|
+
return { ...core, id, stableId: `semantic_edit_script_${idFragment(hash)}`, hash };
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function sourceInput(input, side) {
|
|
90
|
+
if (side === 'base') return input.base ?? input.before ?? sourceTextInput(input, 'base') ?? sourceTextInput(input, 'before');
|
|
91
|
+
if (side === 'worker') return input.worker ?? input.after ?? sourceTextInput(input, 'worker') ?? sourceTextInput(input, 'after');
|
|
92
|
+
return input.head ?? input.current ?? sourceTextInput(input, 'head') ?? sourceTextInput(input, 'current');
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function sourceTextInput(input, side) {
|
|
96
|
+
const sourceText = input[`${side}SourceText`];
|
|
97
|
+
if (sourceText === undefined) return undefined;
|
|
98
|
+
return {
|
|
99
|
+
language: input.language,
|
|
100
|
+
sourcePath: input[`${side}SourcePath`] ?? input.sourcePath,
|
|
101
|
+
sourceText,
|
|
102
|
+
sourceHash: input[`${side}SourceHash`],
|
|
103
|
+
parser: input.parser,
|
|
104
|
+
metadata: input[`${side}Metadata`]
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
function createEditContext(input) {
|
|
109
|
+
return {
|
|
110
|
+
...input,
|
|
111
|
+
baseSymbols: symbolsByAnchor(input.base),
|
|
112
|
+
workerSymbols: symbolsByAnchor(input.worker),
|
|
113
|
+
headSymbols: symbolsByAnchor(input.head),
|
|
114
|
+
headChangedConflictKeys: new Set(input.headChangeSet?.conflictKeys ?? [])
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
function symbolsByAnchor(imported) {
|
|
119
|
+
if (!imported) return new Map();
|
|
120
|
+
const sidecar = createSemanticImportSidecar(imported, { id: `semantic_edit_sidecar_${idFragment(imported.id ?? imported.sourcePath ?? 'source')}` });
|
|
121
|
+
const result = new Map();
|
|
122
|
+
for (const symbol of mapDiffSymbols(imported, sidecar).values()) {
|
|
123
|
+
for (const key of uniqueStrings([symbol.ownershipKey, symbol.key, symbol.id])) {
|
|
124
|
+
result.set(key, symbol);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
return result;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
function semanticEditOperation(region, index, context, input) {
|
|
131
|
+
const anchorKey = region.key ?? region.conflictKey ?? region.id;
|
|
132
|
+
const baseSymbol = context.baseSymbols.get(anchorKey);
|
|
133
|
+
const workerSymbol = context.workerSymbols.get(anchorKey);
|
|
134
|
+
const headSymbol = context.headSymbols.get(anchorKey);
|
|
135
|
+
const classification = classifySemanticEdit({ region, anchorKey, baseSymbol, workerSymbol, headSymbol, context });
|
|
136
|
+
const kind = semanticEditOperationKind(region);
|
|
137
|
+
const baseText = spanText(context.base, baseSymbol?.sourceSpan ?? region.metadata?.changedRegionProjection?.before?.sourceSpan ?? region.sourceSpan);
|
|
138
|
+
const workerText = spanText(context.worker, workerSymbol?.sourceSpan ?? region.metadata?.changedRegionProjection?.after?.sourceSpan ?? region.sourceSpan);
|
|
139
|
+
return compactRecord({
|
|
140
|
+
id: `semantic_edit_op_${idFragment(firstString(input.id, anchorKey, index))}`,
|
|
141
|
+
kind,
|
|
142
|
+
changeKind: region.changeKind,
|
|
143
|
+
anchor: compactRecord({
|
|
144
|
+
key: anchorKey,
|
|
145
|
+
conflictKey: region.conflictKey ?? `region:${anchorKey}`,
|
|
146
|
+
regionId: region.id,
|
|
147
|
+
regionKind: region.regionKind,
|
|
148
|
+
granularity: region.granularity,
|
|
149
|
+
language: region.language ?? context.workerChangeSet.language,
|
|
150
|
+
sourcePath: region.sourcePath ?? context.workerChangeSet.sourcePath,
|
|
151
|
+
symbolId: region.symbolId ?? workerSymbol?.id ?? baseSymbol?.id,
|
|
152
|
+
symbolName: region.symbolName ?? workerSymbol?.name ?? baseSymbol?.name,
|
|
153
|
+
symbolKind: region.symbolKind ?? workerSymbol?.kind ?? baseSymbol?.kind,
|
|
154
|
+
sourceSpan: workerSymbol?.sourceSpan ?? region.sourceSpan
|
|
155
|
+
}),
|
|
156
|
+
hashes: compactRecord({
|
|
157
|
+
baseSourceHash: context.workerChangeSet.beforeHash,
|
|
158
|
+
workerSourceHash: context.workerChangeSet.afterHash,
|
|
159
|
+
headSourceHash: context.headChangeSet?.afterHash,
|
|
160
|
+
baseSpanHash: baseSymbol?.spanHash,
|
|
161
|
+
workerSpanHash: workerSymbol?.spanHash,
|
|
162
|
+
headSpanHash: headSymbol?.spanHash,
|
|
163
|
+
baseTextHash: baseText === undefined ? undefined : hashSemanticValue(baseText),
|
|
164
|
+
workerTextHash: workerText === undefined ? undefined : hashSemanticValue(workerText),
|
|
165
|
+
beforeSignatureHash: workerSymbol?.beforeSignatureHash ?? baseSymbol?.signatureHash,
|
|
166
|
+
afterSignatureHash: workerSymbol?.afterSignatureHash ?? workerSymbol?.signatureHash
|
|
167
|
+
}),
|
|
168
|
+
status: classification.status,
|
|
169
|
+
reanchor: classification.reanchor,
|
|
170
|
+
readiness: classification.readiness,
|
|
171
|
+
confidence: classification.confidence,
|
|
172
|
+
reasonCodes: classification.reasonCodes,
|
|
173
|
+
evidenceIds: classification.evidenceIds,
|
|
174
|
+
metadata: {
|
|
175
|
+
autoMergeClaim: false,
|
|
176
|
+
semanticEquivalenceClaim: false
|
|
177
|
+
}
|
|
178
|
+
});
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
function semanticEditOperationKind(region) {
|
|
182
|
+
const prefix = region.changeKind === 'added' ? 'add' : region.changeKind === 'removed' ? 'remove' : 'replace';
|
|
183
|
+
const kind = String(region.regionKind ?? region.granularity ?? 'region');
|
|
184
|
+
if (kind === 'body') return `${prefix}Body`;
|
|
185
|
+
if (kind === 'import') return `${prefix}Import`;
|
|
186
|
+
if (kind === 'type') return `${prefix}TypeDeclaration`;
|
|
187
|
+
if (kind === 'property') return `${prefix}Property`;
|
|
188
|
+
return `${prefix}Region`;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
function spanText(imported, span) {
|
|
192
|
+
return sourceTextForSpan(nativeImportSourceText(imported), span);
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
function semanticEditEvidence(input) {
|
|
196
|
+
return [{
|
|
197
|
+
id: input.input.evidenceId ?? `evidence_${idFragment(input.input.id ?? input.sourcePath ?? 'semantic_edit')}_semantic_edit_script`,
|
|
198
|
+
kind: 'semantic-edit-script',
|
|
199
|
+
status: input.admission.status === 'blocked' || input.admission.status === 'conflict' ? 'needs-review' : 'passed',
|
|
200
|
+
path: input.sourcePath,
|
|
201
|
+
summary: `Created semantic edit script with ${input.summary.operations} operation(s): ${input.admission.status}.`,
|
|
202
|
+
metadata: {
|
|
203
|
+
workerChangeSetId: input.workerChangeSet.id,
|
|
204
|
+
headChangeSetId: input.headChangeSet?.id,
|
|
205
|
+
lineageInferenceId: input.headLineage?.id,
|
|
206
|
+
summary: input.summary,
|
|
207
|
+
admissionStatus: input.admission.status,
|
|
208
|
+
autoMergeClaim: false,
|
|
209
|
+
semanticEquivalenceClaim: false
|
|
210
|
+
}
|
|
211
|
+
}];
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
function firstString(...values) {
|
|
215
|
+
return values.map((value) => value === undefined || value === null ? '' : String(value)).find(Boolean);
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
function compactRecord(value) {
|
|
219
|
+
return Object.fromEntries(Object.entries(value ?? {}).filter(([, entry]) => entry !== undefined && (!Array.isArray(entry) || entry.length > 0)));
|
|
220
|
+
}
|
|
@@ -0,0 +1,256 @@
|
|
|
1
|
+
import { hashSemanticValue } from '@shapeshift-labs/frontier-lang-kernel';
|
|
2
|
+
import { idFragment, uniqueStrings as uniqueRawStrings } from '../../native-import-utils.js';
|
|
3
|
+
import { resolveSemanticLineage } from './semanticLineageResolutionRecords.js';
|
|
4
|
+
|
|
5
|
+
export function resolveSemanticHistoryRecordLineage(record = {}, eventsOrMap = [], options = {}) {
|
|
6
|
+
const generatedAt = options.generatedAt ?? Date.now();
|
|
7
|
+
const sourceIndex = record.index ?? {};
|
|
8
|
+
const anchorKeys = uniqueStrings([
|
|
9
|
+
...array(sourceIndex.semanticAnchorKeys),
|
|
10
|
+
...array(sourceIndex.ownershipKeys),
|
|
11
|
+
...array(sourceIndex.conflictKeys)
|
|
12
|
+
]);
|
|
13
|
+
const resolutions = anchorKeys.map((anchorKey) => resolveSemanticLineage(eventsOrMap, {
|
|
14
|
+
anchorKey,
|
|
15
|
+
generatedAt
|
|
16
|
+
}, { maxDepth: options.maxDepth }));
|
|
17
|
+
const byAnchorKey = new Map(resolutions.map((resolution) => [resolution.query.anchorKey, resolution]));
|
|
18
|
+
const resolvedIndex = {
|
|
19
|
+
...sourceIndex,
|
|
20
|
+
ownershipKeys: resolveIndexKeys(sourceIndex.ownershipKeys, byAnchorKey, options),
|
|
21
|
+
conflictKeys: resolveIndexKeys(sourceIndex.conflictKeys, byAnchorKey, options),
|
|
22
|
+
semanticAnchorKeys: resolveIndexKeys(sourceIndex.semanticAnchorKeys, byAnchorKey, options),
|
|
23
|
+
sourcePaths: uniqueStrings([
|
|
24
|
+
...array(sourceIndex.sourcePaths),
|
|
25
|
+
...resolutions.flatMap((resolution) => resolution.currentAnchors.map((anchor) => anchor.sourcePath))
|
|
26
|
+
]),
|
|
27
|
+
lineageEventIds: uniqueStrings([
|
|
28
|
+
...array(sourceIndex.lineageEventIds),
|
|
29
|
+
...resolutions.flatMap((resolution) => resolution.traversedEventIds)
|
|
30
|
+
]),
|
|
31
|
+
semanticLineageKinds: uniqueStrings([
|
|
32
|
+
...array(sourceIndex.semanticLineageKinds),
|
|
33
|
+
...resolutions.flatMap((resolution) => resolution.lineageEventKinds)
|
|
34
|
+
]),
|
|
35
|
+
crdtOperationIds: uniqueStrings([
|
|
36
|
+
...array(sourceIndex.crdtOperationIds),
|
|
37
|
+
...resolutions.flatMap((resolution) => resolution.crdtOperationIds)
|
|
38
|
+
]),
|
|
39
|
+
crdtHeads: uniqueStrings([
|
|
40
|
+
...array(sourceIndex.crdtHeads),
|
|
41
|
+
...resolutions.flatMap((resolution) => resolution.crdtHeads)
|
|
42
|
+
]),
|
|
43
|
+
evidenceIds: uniqueStrings([
|
|
44
|
+
...array(sourceIndex.evidenceIds),
|
|
45
|
+
...resolutions.flatMap((resolution) => resolution.evidenceIds)
|
|
46
|
+
]),
|
|
47
|
+
proofIds: uniqueStrings([
|
|
48
|
+
...array(sourceIndex.proofIds),
|
|
49
|
+
...resolutions.flatMap((resolution) => resolution.proofIds)
|
|
50
|
+
])
|
|
51
|
+
};
|
|
52
|
+
const anchorInventory = createAnchorInventory(resolutions);
|
|
53
|
+
const summary = summarizeSemanticHistoryLineageResolutions(resolutions, resolvedIndex, anchorInventory);
|
|
54
|
+
const readiness = summary.cycle > 0 || summary.maxDepth > 0 ? 'blocked' : summary.resolved > 0 || summary.ambiguous > 0 || summary.deleted > 0 || summary.recreated > 0 ? 'needs-review' : 'ready';
|
|
55
|
+
const resolutionCore = {
|
|
56
|
+
kind: 'frontier.lang.semanticHistoryRecordLineageResolution',
|
|
57
|
+
version: 1,
|
|
58
|
+
sourceRecordId: record.id,
|
|
59
|
+
sourceRecordStableId: record.stableId,
|
|
60
|
+
sourceRecordHash: record.hash,
|
|
61
|
+
generatedAt,
|
|
62
|
+
resolutions,
|
|
63
|
+
anchorInventory,
|
|
64
|
+
summary,
|
|
65
|
+
admission: {
|
|
66
|
+
readiness,
|
|
67
|
+
reviewRequired: readiness !== 'ready',
|
|
68
|
+
autoMergeClaim: false,
|
|
69
|
+
semanticEquivalenceClaim: false
|
|
70
|
+
},
|
|
71
|
+
metadata: compactRecord(options.metadata)
|
|
72
|
+
};
|
|
73
|
+
const hash = hashSemanticValue(resolutionCore);
|
|
74
|
+
const id = options.id ?? `semantic_history_lineage_resolution_${idFragment(firstString(record.id, record.stableId, hash))}`;
|
|
75
|
+
const resolvedRecord = createResolvedHistoryRecord(record, resolvedIndex, { id, hash, generatedAt, summary, readiness });
|
|
76
|
+
return {
|
|
77
|
+
...resolutionCore,
|
|
78
|
+
id,
|
|
79
|
+
stableId: `semantic_history_lineage_resolution_${idFragment(hash)}`,
|
|
80
|
+
hash,
|
|
81
|
+
resolvedRecord
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export function resolveSemanticHistoryRecordsLineage(records = [], eventsOrMap = [], options = {}) {
|
|
86
|
+
return array(records).map((record) => resolveSemanticHistoryRecordLineage(record, eventsOrMap, options));
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function createResolvedHistoryRecord(record, index, resolution) {
|
|
90
|
+
const core = {
|
|
91
|
+
...record,
|
|
92
|
+
id: `${record.id ?? 'semantic_history'}#lineage-resolved`,
|
|
93
|
+
index,
|
|
94
|
+
metadata: {
|
|
95
|
+
...(record.metadata ?? {}),
|
|
96
|
+
semanticHistoryLineageResolution: {
|
|
97
|
+
id: resolution.id,
|
|
98
|
+
sourceRecordId: record.id,
|
|
99
|
+
sourceRecordHash: record.hash,
|
|
100
|
+
readiness: resolution.readiness,
|
|
101
|
+
summary: resolution.summary,
|
|
102
|
+
anchorSummary: {
|
|
103
|
+
activeAnchorKeys: resolution.summary.activeAnchorKeys,
|
|
104
|
+
candidateAnchorKeys: resolution.summary.candidateAnchorKeys,
|
|
105
|
+
inactiveAnchorKeys: resolution.summary.inactiveAnchorKeys,
|
|
106
|
+
blockedAnchorKeys: resolution.summary.blockedAnchorKeys
|
|
107
|
+
},
|
|
108
|
+
autoMergeClaim: false,
|
|
109
|
+
semanticEquivalenceClaim: false
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
};
|
|
113
|
+
const stableCore = { ...core };
|
|
114
|
+
delete stableCore.hash;
|
|
115
|
+
delete stableCore.stableId;
|
|
116
|
+
const hash = hashSemanticValue(stableCore);
|
|
117
|
+
return {
|
|
118
|
+
...core,
|
|
119
|
+
stableId: `semantic_history_${idFragment(hash)}`,
|
|
120
|
+
hash
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
function resolveIndexKeys(values, resolutions, options) {
|
|
125
|
+
return uniqueStrings(array(values).flatMap((value) => {
|
|
126
|
+
const key = String(value);
|
|
127
|
+
const resolution = resolutions.get(key);
|
|
128
|
+
if (!resolution) return [key];
|
|
129
|
+
if (resolution.status === 'deleted' && options.keepDeletedAnchors !== true) return [];
|
|
130
|
+
if (resolution.status === 'cycle' && options.keepBlockedAnchors !== true) return [];
|
|
131
|
+
if (resolution.status === 'max-depth' && options.keepBlockedAnchors !== true) return [];
|
|
132
|
+
if (resolution.status === 'not-found' && options.keepUnresolvedAnchors !== true) return [];
|
|
133
|
+
if (resolution.status === 'ambiguous' && options.keepCandidateAnchors === false) return [];
|
|
134
|
+
const current = resolution.currentAnchors.map((anchor) => anchor.key).filter(Boolean);
|
|
135
|
+
return current.length ? current : [key];
|
|
136
|
+
}));
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
function summarizeSemanticHistoryLineageResolutions(resolutions, index, anchorInventory) {
|
|
140
|
+
const byStatus = {};
|
|
141
|
+
for (const resolution of resolutions) byStatus[resolution.status] = (byStatus[resolution.status] ?? 0) + 1;
|
|
142
|
+
return {
|
|
143
|
+
anchorCount: resolutions.length,
|
|
144
|
+
unchanged: byStatus.unchanged ?? 0,
|
|
145
|
+
resolved: byStatus.resolved ?? 0,
|
|
146
|
+
ambiguous: byStatus.ambiguous ?? 0,
|
|
147
|
+
deleted: byStatus.deleted ?? 0,
|
|
148
|
+
recreated: byStatus.recreated ?? 0,
|
|
149
|
+
cycle: byStatus.cycle ?? 0,
|
|
150
|
+
maxDepth: byStatus['max-depth'] ?? 0,
|
|
151
|
+
notFound: byStatus['not-found'] ?? 0,
|
|
152
|
+
currentAnchorKeys: uniqueStrings(index.semanticAnchorKeys),
|
|
153
|
+
ownershipKeys: uniqueStrings(index.ownershipKeys),
|
|
154
|
+
conflictKeys: uniqueStrings(index.conflictKeys),
|
|
155
|
+
sourcePaths: uniqueStrings(index.sourcePaths),
|
|
156
|
+
activeAnchorKeys: uniqueStrings(anchorInventory.active.map((anchor) => anchor.key)),
|
|
157
|
+
candidateAnchorKeys: uniqueStrings(anchorInventory.candidate.map((anchor) => anchor.key)),
|
|
158
|
+
inactiveAnchorKeys: uniqueStrings(anchorInventory.inactive.map((anchor) => anchor.key)),
|
|
159
|
+
deletedAnchorKeys: uniqueStrings(anchorInventory.deleted.map((anchor) => anchor.key)),
|
|
160
|
+
unresolvedAnchorKeys: uniqueStrings(anchorInventory.unresolved.map((anchor) => anchor.key)),
|
|
161
|
+
blockedAnchorKeys: uniqueStrings(anchorInventory.blocked.map((anchor) => anchor.key)),
|
|
162
|
+
traversedEventIds: uniqueStrings(resolutions.flatMap((resolution) => resolution.traversedEventIds)),
|
|
163
|
+
terminalEventIds: uniqueStrings(resolutions.flatMap((resolution) => resolution.terminalEventIds)),
|
|
164
|
+
reasonCodes: uniqueStrings(resolutions.flatMap((resolution) => resolution.reasonCodes))
|
|
165
|
+
};
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
function createAnchorInventory(resolutions) {
|
|
169
|
+
const inventory = {
|
|
170
|
+
active: [],
|
|
171
|
+
candidate: [],
|
|
172
|
+
inactive: [],
|
|
173
|
+
deleted: [],
|
|
174
|
+
unresolved: [],
|
|
175
|
+
blocked: []
|
|
176
|
+
};
|
|
177
|
+
for (const resolution of resolutions) {
|
|
178
|
+
const start = anchorEntry(resolution.startAnchor ?? queryAnchor(resolution), resolution);
|
|
179
|
+
const current = resolution.currentAnchors.map((anchor) => anchorEntry(anchor, resolution));
|
|
180
|
+
if (resolution.status === 'ambiguous') {
|
|
181
|
+
inventory.candidate.push(...current);
|
|
182
|
+
if (start) inventory.inactive.push(start);
|
|
183
|
+
continue;
|
|
184
|
+
}
|
|
185
|
+
if (resolution.status === 'deleted') {
|
|
186
|
+
if (start) {
|
|
187
|
+
inventory.inactive.push(start);
|
|
188
|
+
inventory.deleted.push(start);
|
|
189
|
+
}
|
|
190
|
+
continue;
|
|
191
|
+
}
|
|
192
|
+
if (resolution.status === 'cycle' || resolution.status === 'max-depth') {
|
|
193
|
+
if (start) {
|
|
194
|
+
inventory.inactive.push(start);
|
|
195
|
+
inventory.blocked.push(start);
|
|
196
|
+
}
|
|
197
|
+
continue;
|
|
198
|
+
}
|
|
199
|
+
if (resolution.status === 'not-found') {
|
|
200
|
+
if (start) {
|
|
201
|
+
inventory.inactive.push(start);
|
|
202
|
+
inventory.unresolved.push(start);
|
|
203
|
+
}
|
|
204
|
+
continue;
|
|
205
|
+
}
|
|
206
|
+
inventory.active.push(...current);
|
|
207
|
+
}
|
|
208
|
+
return {
|
|
209
|
+
active: uniqueAnchorEntries(inventory.active),
|
|
210
|
+
candidate: uniqueAnchorEntries(inventory.candidate),
|
|
211
|
+
inactive: uniqueAnchorEntries(inventory.inactive),
|
|
212
|
+
deleted: uniqueAnchorEntries(inventory.deleted),
|
|
213
|
+
unresolved: uniqueAnchorEntries(inventory.unresolved),
|
|
214
|
+
blocked: uniqueAnchorEntries(inventory.blocked)
|
|
215
|
+
};
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
function anchorEntry(anchor, resolution) {
|
|
219
|
+
if (!anchor) return undefined;
|
|
220
|
+
return compactRecord({
|
|
221
|
+
key: anchor.key,
|
|
222
|
+
id: anchor.id,
|
|
223
|
+
kind: anchor.kind,
|
|
224
|
+
language: anchor.language,
|
|
225
|
+
sourcePath: anchor.sourcePath,
|
|
226
|
+
symbolId: anchor.symbolId,
|
|
227
|
+
symbolName: anchor.symbolName,
|
|
228
|
+
status: resolution.status,
|
|
229
|
+
resolutionId: resolution.id,
|
|
230
|
+
reasonCodes: uniqueStrings(resolution.reasonCodes)
|
|
231
|
+
});
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
function queryAnchor(resolution) {
|
|
235
|
+
return compactRecord({
|
|
236
|
+
key: resolution.query?.anchorKey,
|
|
237
|
+
id: resolution.query?.anchorId,
|
|
238
|
+
sourcePath: resolution.query?.sourcePath,
|
|
239
|
+
symbolName: resolution.query?.symbolName
|
|
240
|
+
});
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
function uniqueAnchorEntries(entries) {
|
|
244
|
+
const seen = new Set();
|
|
245
|
+
return entries.filter(Boolean).filter((entry) => {
|
|
246
|
+
const key = entry.key ?? entry.id ?? `${entry.sourcePath ?? ''}:${entry.symbolName ?? ''}`;
|
|
247
|
+
if (!key || seen.has(key)) return false;
|
|
248
|
+
seen.add(key);
|
|
249
|
+
return true;
|
|
250
|
+
});
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
function array(value) { return value === undefined || value === null ? [] : Array.isArray(value) ? value : [value]; }
|
|
254
|
+
function uniqueStrings(values) { return uniqueRawStrings(array(values).map((value) => String(value ?? '')).filter(Boolean)); }
|
|
255
|
+
function firstString(...values) { return values.map((value) => value === undefined || value === null ? '' : String(value)).find(Boolean); }
|
|
256
|
+
function compactRecord(value) { return Object.fromEntries(Object.entries(value ?? {}).filter(([, entry]) => entry !== undefined)); }
|
package/package.json
CHANGED