@grafema/util 0.3.0-beta
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/LICENSE +190 -0
- package/dist/api/GraphAPI.d.ts +87 -0
- package/dist/api/GraphAPI.d.ts.map +1 -0
- package/dist/api/GraphAPI.js +212 -0
- package/dist/api/GraphAPI.js.map +1 -0
- package/dist/api/GuaranteeAPI.d.ts +147 -0
- package/dist/api/GuaranteeAPI.d.ts.map +1 -0
- package/dist/api/GuaranteeAPI.js +290 -0
- package/dist/api/GuaranteeAPI.js.map +1 -0
- package/dist/config/ConfigLoader.d.ts +214 -0
- package/dist/config/ConfigLoader.d.ts.map +1 -0
- package/dist/config/ConfigLoader.js +441 -0
- package/dist/config/ConfigLoader.js.map +1 -0
- package/dist/config/index.d.ts +6 -0
- package/dist/config/index.d.ts.map +1 -0
- package/dist/config/index.js +5 -0
- package/dist/config/index.js.map +1 -0
- package/dist/core/CoverageAnalyzer.d.ts +65 -0
- package/dist/core/CoverageAnalyzer.d.ts.map +1 -0
- package/dist/core/CoverageAnalyzer.js +199 -0
- package/dist/core/CoverageAnalyzer.js.map +1 -0
- package/dist/core/FileExplainer.d.ts +101 -0
- package/dist/core/FileExplainer.d.ts.map +1 -0
- package/dist/core/FileExplainer.js +140 -0
- package/dist/core/FileExplainer.js.map +1 -0
- package/dist/core/FileOverview.d.ts +124 -0
- package/dist/core/FileOverview.d.ts.map +1 -0
- package/dist/core/FileOverview.js +279 -0
- package/dist/core/FileOverview.js.map +1 -0
- package/dist/core/GrafemaUri.d.ts +66 -0
- package/dist/core/GrafemaUri.d.ts.map +1 -0
- package/dist/core/GrafemaUri.js +191 -0
- package/dist/core/GrafemaUri.js.map +1 -0
- package/dist/core/GraphBackend.d.ts +158 -0
- package/dist/core/GraphBackend.d.ts.map +1 -0
- package/dist/core/GraphBackend.js +85 -0
- package/dist/core/GraphBackend.js.map +1 -0
- package/dist/core/GraphFreshnessChecker.d.ts +33 -0
- package/dist/core/GraphFreshnessChecker.d.ts.map +1 -0
- package/dist/core/GraphFreshnessChecker.js +104 -0
- package/dist/core/GraphFreshnessChecker.js.map +1 -0
- package/dist/core/GuaranteeManager.d.ts +254 -0
- package/dist/core/GuaranteeManager.d.ts.map +1 -0
- package/dist/core/GuaranteeManager.js +447 -0
- package/dist/core/GuaranteeManager.js.map +1 -0
- package/dist/core/HashUtils.d.ts +24 -0
- package/dist/core/HashUtils.d.ts.map +1 -0
- package/dist/core/HashUtils.js +46 -0
- package/dist/core/HashUtils.js.map +1 -0
- package/dist/core/IncrementalReanalyzer.d.ts +33 -0
- package/dist/core/IncrementalReanalyzer.d.ts.map +1 -0
- package/dist/core/IncrementalReanalyzer.js +67 -0
- package/dist/core/IncrementalReanalyzer.js.map +1 -0
- package/dist/core/ResourceRegistry.d.ts +17 -0
- package/dist/core/ResourceRegistry.d.ts.map +1 -0
- package/dist/core/ResourceRegistry.js +32 -0
- package/dist/core/ResourceRegistry.js.map +1 -0
- package/dist/core/SemanticId.d.ts +159 -0
- package/dist/core/SemanticId.d.ts.map +1 -0
- package/dist/core/SemanticId.js +291 -0
- package/dist/core/SemanticId.js.map +1 -0
- package/dist/core/VersionManager.d.ts +166 -0
- package/dist/core/VersionManager.d.ts.map +1 -0
- package/dist/core/VersionManager.js +239 -0
- package/dist/core/VersionManager.js.map +1 -0
- package/dist/core/brandNodeInternal.d.ts +14 -0
- package/dist/core/brandNodeInternal.d.ts.map +1 -0
- package/dist/core/brandNodeInternal.js +4 -0
- package/dist/core/brandNodeInternal.js.map +1 -0
- package/dist/core/nodes/GuaranteeNode.d.ts +76 -0
- package/dist/core/nodes/GuaranteeNode.d.ts.map +1 -0
- package/dist/core/nodes/GuaranteeNode.js +118 -0
- package/dist/core/nodes/GuaranteeNode.js.map +1 -0
- package/dist/core/nodes/IssueNode.d.ts +73 -0
- package/dist/core/nodes/IssueNode.d.ts.map +1 -0
- package/dist/core/nodes/IssueNode.js +130 -0
- package/dist/core/nodes/IssueNode.js.map +1 -0
- package/dist/core/nodes/NodeKind.d.ts +104 -0
- package/dist/core/nodes/NodeKind.d.ts.map +1 -0
- package/dist/core/nodes/NodeKind.js +166 -0
- package/dist/core/nodes/NodeKind.js.map +1 -0
- package/dist/diagnostics/DiagnosticCollector.d.ts +103 -0
- package/dist/diagnostics/DiagnosticCollector.d.ts.map +1 -0
- package/dist/diagnostics/DiagnosticCollector.js +133 -0
- package/dist/diagnostics/DiagnosticCollector.js.map +1 -0
- package/dist/diagnostics/DiagnosticReporter.d.ts +122 -0
- package/dist/diagnostics/DiagnosticReporter.d.ts.map +1 -0
- package/dist/diagnostics/DiagnosticReporter.js +300 -0
- package/dist/diagnostics/DiagnosticReporter.js.map +1 -0
- package/dist/diagnostics/DiagnosticWriter.d.ts +31 -0
- package/dist/diagnostics/DiagnosticWriter.d.ts.map +1 -0
- package/dist/diagnostics/DiagnosticWriter.js +44 -0
- package/dist/diagnostics/DiagnosticWriter.js.map +1 -0
- package/dist/diagnostics/categories.d.ts +57 -0
- package/dist/diagnostics/categories.d.ts.map +1 -0
- package/dist/diagnostics/categories.js +71 -0
- package/dist/diagnostics/categories.js.map +1 -0
- package/dist/diagnostics/index.d.ts +17 -0
- package/dist/diagnostics/index.d.ts.map +1 -0
- package/dist/diagnostics/index.js +15 -0
- package/dist/diagnostics/index.js.map +1 -0
- package/dist/errors/GrafemaError.d.ts +200 -0
- package/dist/errors/GrafemaError.d.ts.map +1 -0
- package/dist/errors/GrafemaError.js +209 -0
- package/dist/errors/GrafemaError.js.map +1 -0
- package/dist/index.d.ts +75 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +76 -0
- package/dist/index.js.map +1 -0
- package/dist/instructions/index.d.ts +8 -0
- package/dist/instructions/index.d.ts.map +1 -0
- package/dist/instructions/index.js +20 -0
- package/dist/instructions/index.js.map +1 -0
- package/dist/instructions/onboarding.md +133 -0
- package/dist/knowledge/KnowledgeBase.d.ts +113 -0
- package/dist/knowledge/KnowledgeBase.d.ts.map +1 -0
- package/dist/knowledge/KnowledgeBase.js +420 -0
- package/dist/knowledge/KnowledgeBase.js.map +1 -0
- package/dist/knowledge/SemanticAddressResolver.d.ts +59 -0
- package/dist/knowledge/SemanticAddressResolver.d.ts.map +1 -0
- package/dist/knowledge/SemanticAddressResolver.js +160 -0
- package/dist/knowledge/SemanticAddressResolver.js.map +1 -0
- package/dist/knowledge/git-ingest.d.ts +58 -0
- package/dist/knowledge/git-ingest.d.ts.map +1 -0
- package/dist/knowledge/git-ingest.js +301 -0
- package/dist/knowledge/git-ingest.js.map +1 -0
- package/dist/knowledge/git-queries.d.ts +86 -0
- package/dist/knowledge/git-queries.d.ts.map +1 -0
- package/dist/knowledge/git-queries.js +177 -0
- package/dist/knowledge/git-queries.js.map +1 -0
- package/dist/knowledge/index.d.ts +14 -0
- package/dist/knowledge/index.d.ts.map +1 -0
- package/dist/knowledge/index.js +10 -0
- package/dist/knowledge/index.js.map +1 -0
- package/dist/knowledge/parser.d.ts +39 -0
- package/dist/knowledge/parser.d.ts.map +1 -0
- package/dist/knowledge/parser.js +292 -0
- package/dist/knowledge/parser.js.map +1 -0
- package/dist/knowledge/types.d.ts +133 -0
- package/dist/knowledge/types.d.ts.map +1 -0
- package/dist/knowledge/types.js +8 -0
- package/dist/knowledge/types.js.map +1 -0
- package/dist/logging/Logger.d.ts +98 -0
- package/dist/logging/Logger.d.ts.map +1 -0
- package/dist/logging/Logger.js +274 -0
- package/dist/logging/Logger.js.map +1 -0
- package/dist/notation/archetypes.d.ts +36 -0
- package/dist/notation/archetypes.d.ts.map +1 -0
- package/dist/notation/archetypes.js +173 -0
- package/dist/notation/archetypes.js.map +1 -0
- package/dist/notation/fold.d.ts +25 -0
- package/dist/notation/fold.d.ts.map +1 -0
- package/dist/notation/fold.js +598 -0
- package/dist/notation/fold.js.map +1 -0
- package/dist/notation/index.d.ts +18 -0
- package/dist/notation/index.d.ts.map +1 -0
- package/dist/notation/index.js +16 -0
- package/dist/notation/index.js.map +1 -0
- package/dist/notation/lodExtractor.d.ts +32 -0
- package/dist/notation/lodExtractor.d.ts.map +1 -0
- package/dist/notation/lodExtractor.js +149 -0
- package/dist/notation/lodExtractor.js.map +1 -0
- package/dist/notation/nameShortener.d.ts +22 -0
- package/dist/notation/nameShortener.d.ts.map +1 -0
- package/dist/notation/nameShortener.js +24 -0
- package/dist/notation/nameShortener.js.map +1 -0
- package/dist/notation/perspectives.d.ts +11 -0
- package/dist/notation/perspectives.d.ts.map +1 -0
- package/dist/notation/perspectives.js +16 -0
- package/dist/notation/perspectives.js.map +1 -0
- package/dist/notation/renderer.d.ts +31 -0
- package/dist/notation/renderer.d.ts.map +1 -0
- package/dist/notation/renderer.js +315 -0
- package/dist/notation/renderer.js.map +1 -0
- package/dist/notation/traceRenderer.d.ts +39 -0
- package/dist/notation/traceRenderer.d.ts.map +1 -0
- package/dist/notation/traceRenderer.js +358 -0
- package/dist/notation/traceRenderer.js.map +1 -0
- package/dist/notation/types.d.ts +66 -0
- package/dist/notation/types.d.ts.map +1 -0
- package/dist/notation/types.js +10 -0
- package/dist/notation/types.js.map +1 -0
- package/dist/queries/NodeContext.d.ts +81 -0
- package/dist/queries/NodeContext.d.ts.map +1 -0
- package/dist/queries/NodeContext.js +196 -0
- package/dist/queries/NodeContext.js.map +1 -0
- package/dist/queries/findCallsInFunction.d.ts +62 -0
- package/dist/queries/findCallsInFunction.d.ts.map +1 -0
- package/dist/queries/findCallsInFunction.js +169 -0
- package/dist/queries/findCallsInFunction.js.map +1 -0
- package/dist/queries/findContainingFunction.d.ts +57 -0
- package/dist/queries/findContainingFunction.d.ts.map +1 -0
- package/dist/queries/findContainingFunction.js +91 -0
- package/dist/queries/findContainingFunction.js.map +1 -0
- package/dist/queries/index.d.ts +18 -0
- package/dist/queries/index.d.ts.map +1 -0
- package/dist/queries/index.js +14 -0
- package/dist/queries/index.js.map +1 -0
- package/dist/queries/traceDataflow.d.ts +65 -0
- package/dist/queries/traceDataflow.d.ts.map +1 -0
- package/dist/queries/traceDataflow.js +754 -0
- package/dist/queries/traceDataflow.js.map +1 -0
- package/dist/queries/traceValues.d.ts +70 -0
- package/dist/queries/traceValues.d.ts.map +1 -0
- package/dist/queries/traceValues.js +373 -0
- package/dist/queries/traceValues.js.map +1 -0
- package/dist/queries/types.d.ts +166 -0
- package/dist/queries/types.d.ts.map +1 -0
- package/dist/queries/types.js +10 -0
- package/dist/queries/types.js.map +1 -0
- package/dist/schema/GraphSchemaExtractor.d.ts +53 -0
- package/dist/schema/GraphSchemaExtractor.d.ts.map +1 -0
- package/dist/schema/GraphSchemaExtractor.js +125 -0
- package/dist/schema/GraphSchemaExtractor.js.map +1 -0
- package/dist/schema/InterfaceSchemaExtractor.d.ts +73 -0
- package/dist/schema/InterfaceSchemaExtractor.d.ts.map +1 -0
- package/dist/schema/InterfaceSchemaExtractor.js +113 -0
- package/dist/schema/InterfaceSchemaExtractor.js.map +1 -0
- package/dist/schema/index.d.ts +5 -0
- package/dist/schema/index.d.ts.map +1 -0
- package/dist/schema/index.js +3 -0
- package/dist/schema/index.js.map +1 -0
- package/dist/storage/backends/RFDBServerBackend.d.ts +356 -0
- package/dist/storage/backends/RFDBServerBackend.d.ts.map +1 -0
- package/dist/storage/backends/RFDBServerBackend.js +748 -0
- package/dist/storage/backends/RFDBServerBackend.js.map +1 -0
- package/dist/storage/backends/typeValidation.d.ts +47 -0
- package/dist/storage/backends/typeValidation.d.ts.map +1 -0
- package/dist/storage/backends/typeValidation.js +141 -0
- package/dist/storage/backends/typeValidation.js.map +1 -0
- package/dist/utils/findRfdbBinary.d.ts +67 -0
- package/dist/utils/findRfdbBinary.d.ts.map +1 -0
- package/dist/utils/findRfdbBinary.js +261 -0
- package/dist/utils/findRfdbBinary.js.map +1 -0
- package/dist/utils/lazyDownload.d.ts +43 -0
- package/dist/utils/lazyDownload.d.ts.map +1 -0
- package/dist/utils/lazyDownload.js +175 -0
- package/dist/utils/lazyDownload.js.map +1 -0
- package/dist/utils/moduleResolution.d.ts +134 -0
- package/dist/utils/moduleResolution.d.ts.map +1 -0
- package/dist/utils/moduleResolution.js +189 -0
- package/dist/utils/moduleResolution.js.map +1 -0
- package/dist/utils/resolveNodeFile.d.ts +13 -0
- package/dist/utils/resolveNodeFile.d.ts.map +1 -0
- package/dist/utils/resolveNodeFile.js +18 -0
- package/dist/utils/resolveNodeFile.js.map +1 -0
- package/dist/utils/startRfdbServer.d.ts +63 -0
- package/dist/utils/startRfdbServer.d.ts.map +1 -0
- package/dist/utils/startRfdbServer.js +142 -0
- package/dist/utils/startRfdbServer.js.map +1 -0
- package/dist/validation/PathValidator.d.ts +80 -0
- package/dist/validation/PathValidator.d.ts.map +1 -0
- package/dist/validation/PathValidator.js +252 -0
- package/dist/validation/PathValidator.js.map +1 -0
- package/dist/version.d.ts +11 -0
- package/dist/version.d.ts.map +1 -0
- package/dist/version.js +26 -0
- package/dist/version.js.map +1 -0
- package/package.json +50 -0
- package/src/api/GraphAPI.ts +307 -0
- package/src/api/GuaranteeAPI.ts +402 -0
- package/src/config/ConfigLoader.ts +653 -0
- package/src/config/index.ts +13 -0
- package/src/core/CoverageAnalyzer.ts +243 -0
- package/src/core/FileExplainer.ts +179 -0
- package/src/core/FileOverview.ts +397 -0
- package/src/core/GrafemaUri.ts +216 -0
- package/src/core/GraphBackend.ts +266 -0
- package/src/core/GraphFreshnessChecker.ts +145 -0
- package/src/core/GuaranteeManager.ts +684 -0
- package/src/core/HashUtils.ts +48 -0
- package/src/core/IncrementalReanalyzer.ts +106 -0
- package/src/core/ResourceRegistry.ts +39 -0
- package/src/core/SemanticId.ts +423 -0
- package/src/core/VersionManager.ts +405 -0
- package/src/core/brandNodeInternal.ts +16 -0
- package/src/core/nodes/GuaranteeNode.ts +162 -0
- package/src/core/nodes/IssueNode.ts +177 -0
- package/src/core/nodes/NodeKind.ts +192 -0
- package/src/diagnostics/DiagnosticCollector.ts +170 -0
- package/src/diagnostics/DiagnosticReporter.ts +395 -0
- package/src/diagnostics/DiagnosticWriter.ts +50 -0
- package/src/diagnostics/categories.ts +104 -0
- package/src/diagnostics/index.ts +30 -0
- package/src/errors/GrafemaError.ts +297 -0
- package/src/index.ts +261 -0
- package/src/instructions/index.ts +21 -0
- package/src/instructions/onboarding.md +133 -0
- package/src/knowledge/KnowledgeBase.ts +486 -0
- package/src/knowledge/SemanticAddressResolver.ts +191 -0
- package/src/knowledge/git-ingest.ts +402 -0
- package/src/knowledge/git-queries.ts +269 -0
- package/src/knowledge/index.ts +29 -0
- package/src/knowledge/parser.ts +294 -0
- package/src/knowledge/types.ts +146 -0
- package/src/logging/Logger.ts +303 -0
- package/src/notation/archetypes.ts +189 -0
- package/src/notation/fold.ts +696 -0
- package/src/notation/index.ts +27 -0
- package/src/notation/lodExtractor.ts +177 -0
- package/src/notation/nameShortener.ts +24 -0
- package/src/notation/perspectives.ts +18 -0
- package/src/notation/renderer.ts +394 -0
- package/src/notation/traceRenderer.ts +458 -0
- package/src/notation/types.ts +79 -0
- package/src/queries/NodeContext.ts +280 -0
- package/src/queries/findCallsInFunction.ts +249 -0
- package/src/queries/findContainingFunction.ts +124 -0
- package/src/queries/index.ts +44 -0
- package/src/queries/traceDataflow.ts +838 -0
- package/src/queries/traceValues.ts +531 -0
- package/src/queries/types.ts +191 -0
- package/src/schema/GraphSchemaExtractor.ts +177 -0
- package/src/schema/InterfaceSchemaExtractor.ts +173 -0
- package/src/schema/index.ts +5 -0
- package/src/storage/backends/RFDBServerBackend.ts +895 -0
- package/src/storage/backends/typeValidation.ts +154 -0
- package/src/utils/findRfdbBinary.ts +288 -0
- package/src/utils/lazyDownload.ts +206 -0
- package/src/utils/moduleResolution.ts +271 -0
- package/src/utils/resolveNodeFile.ts +18 -0
- package/src/utils/startRfdbServer.ts +197 -0
- package/src/validation/PathValidator.ts +334 -0
- package/src/version.ts +28 -0
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* HashUtils - unified hash computation for Grafema
|
|
3
|
+
*
|
|
4
|
+
* WHY THIS EXISTS:
|
|
5
|
+
* - 6 copies of the same hash computation existed across the codebase
|
|
6
|
+
* - Single source of truth ensures consistent hashing everywhere
|
|
7
|
+
* - Makes future algorithm changes (e.g., SHA-256 -> BLAKE3) trivial
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { createHash } from 'crypto';
|
|
11
|
+
import { readFileSync } from 'fs';
|
|
12
|
+
import { readFile } from 'fs/promises';
|
|
13
|
+
|
|
14
|
+
const HASH_ALGORITHM = 'sha256';
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Calculate hash from a file path (synchronous).
|
|
18
|
+
* Returns null if file doesn't exist or is unreadable.
|
|
19
|
+
*/
|
|
20
|
+
export function calculateFileHash(filePath: string): string | null {
|
|
21
|
+
try {
|
|
22
|
+
const content = readFileSync(filePath, 'utf-8');
|
|
23
|
+
return createHash(HASH_ALGORITHM).update(content).digest('hex');
|
|
24
|
+
} catch {
|
|
25
|
+
return null;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Calculate hash from a file path (asynchronous).
|
|
31
|
+
* Returns null if file doesn't exist or is unreadable.
|
|
32
|
+
*/
|
|
33
|
+
export async function calculateFileHashAsync(filePath: string): Promise<string | null> {
|
|
34
|
+
try {
|
|
35
|
+
const content = await readFile(filePath, 'utf-8');
|
|
36
|
+
return createHash(HASH_ALGORITHM).update(content).digest('hex');
|
|
37
|
+
} catch {
|
|
38
|
+
return null;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Calculate hash from content string.
|
|
44
|
+
* Always returns a hash (never null).
|
|
45
|
+
*/
|
|
46
|
+
export function calculateContentHash(content: string): string {
|
|
47
|
+
return createHash(HASH_ALGORITHM).update(content).digest('hex');
|
|
48
|
+
}
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* IncrementalReanalyzer - selective re-analysis of stale modules
|
|
3
|
+
*
|
|
4
|
+
* Shells out to grafema-orchestrator for reanalysis.
|
|
5
|
+
* The Rust orchestrator handles clearing, indexing, analysis, and enrichment.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { spawn } from 'child_process';
|
|
9
|
+
import { join } from 'path';
|
|
10
|
+
import type { GraphBackend } from '@grafema/types';
|
|
11
|
+
import type { StaleModule } from './GraphFreshnessChecker.js';
|
|
12
|
+
import { findOrchestratorBinary } from '../utils/findRfdbBinary.js';
|
|
13
|
+
|
|
14
|
+
export interface ReanalysisOptions {
|
|
15
|
+
skipEnrichment?: boolean;
|
|
16
|
+
onProgress?: (info: ReanalysisProgress) => void;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export interface ReanalysisProgress {
|
|
20
|
+
phase: 'clearing' | 'indexing' | 'analysis' | 'enrichment';
|
|
21
|
+
processedFiles: number;
|
|
22
|
+
totalFiles: number;
|
|
23
|
+
currentService?: string;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export interface ReanalysisResult {
|
|
27
|
+
modulesReanalyzed: number;
|
|
28
|
+
modulesDeleted: number;
|
|
29
|
+
nodesCreated: number;
|
|
30
|
+
edgesCreated: number;
|
|
31
|
+
nodesCleared: number;
|
|
32
|
+
durationMs: number;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export class IncrementalReanalyzer {
|
|
36
|
+
private _graph: GraphBackend;
|
|
37
|
+
private projectPath: string;
|
|
38
|
+
|
|
39
|
+
constructor(graph: GraphBackend, projectPath: string) {
|
|
40
|
+
this._graph = graph;
|
|
41
|
+
this.projectPath = projectPath;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
async reanalyze(
|
|
45
|
+
staleModules: StaleModule[],
|
|
46
|
+
_options: ReanalysisOptions = {}
|
|
47
|
+
): Promise<ReanalysisResult> {
|
|
48
|
+
const startTime = Date.now();
|
|
49
|
+
|
|
50
|
+
if (staleModules.length === 0) {
|
|
51
|
+
return {
|
|
52
|
+
modulesReanalyzed: 0,
|
|
53
|
+
modulesDeleted: 0,
|
|
54
|
+
nodesCreated: 0,
|
|
55
|
+
edgesCreated: 0,
|
|
56
|
+
nodesCleared: 0,
|
|
57
|
+
durationMs: Date.now() - startTime,
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const binary = findOrchestratorBinary();
|
|
62
|
+
if (!binary) {
|
|
63
|
+
throw new Error(
|
|
64
|
+
'grafema-orchestrator binary not found. ' +
|
|
65
|
+
'Build it with: cd packages/grafema-orchestrator && cargo build --release'
|
|
66
|
+
);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const socketPath = join(this.projectPath, '.grafema', 'rfdb.sock');
|
|
70
|
+
|
|
71
|
+
return new Promise<ReanalysisResult>((resolve, reject) => {
|
|
72
|
+
const child = spawn(binary, [
|
|
73
|
+
'analyze',
|
|
74
|
+
'--project', this.projectPath,
|
|
75
|
+
'--socket', socketPath,
|
|
76
|
+
], {
|
|
77
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
let stderr = '';
|
|
81
|
+
child.stderr?.on('data', (data: Buffer) => {
|
|
82
|
+
stderr += data.toString();
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
child.on('error', (err) => {
|
|
86
|
+
reject(new Error(`Failed to start grafema-orchestrator: ${err.message}`));
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
child.on('close', (code) => {
|
|
90
|
+
if (code !== 0) {
|
|
91
|
+
reject(new Error(`grafema-orchestrator exited with code ${code}: ${stderr}`));
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
resolve({
|
|
96
|
+
modulesReanalyzed: staleModules.filter(m => m.currentHash !== null).length,
|
|
97
|
+
modulesDeleted: staleModules.filter(m => m.currentHash === null).length,
|
|
98
|
+
nodesCreated: 0,
|
|
99
|
+
edgesCreated: 0,
|
|
100
|
+
nodesCleared: 0,
|
|
101
|
+
durationMs: Date.now() - startTime,
|
|
102
|
+
});
|
|
103
|
+
});
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import type { Resource, ResourceId, ResourceRegistry as IResourceRegistry } from '@grafema/types';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* In-memory Resource registry for a single pipeline run.
|
|
5
|
+
* Created by Orchestrator at run start, cleared at run end.
|
|
6
|
+
*
|
|
7
|
+
* Thread safety: Not needed — Grafema runs plugins sequentially within
|
|
8
|
+
* each phase. Plugins are toposorted and run one at a time.
|
|
9
|
+
*/
|
|
10
|
+
export class ResourceRegistryImpl implements IResourceRegistry {
|
|
11
|
+
private resources = new Map<ResourceId, Resource>();
|
|
12
|
+
|
|
13
|
+
getOrCreate<T extends Resource>(id: ResourceId, factory: () => T): T {
|
|
14
|
+
let resource = this.resources.get(id);
|
|
15
|
+
if (!resource) {
|
|
16
|
+
resource = factory();
|
|
17
|
+
if (resource.id !== id) {
|
|
18
|
+
throw new Error(
|
|
19
|
+
`Resource factory returned resource with id "${resource.id}" but expected "${id}"`
|
|
20
|
+
);
|
|
21
|
+
}
|
|
22
|
+
this.resources.set(id, resource);
|
|
23
|
+
}
|
|
24
|
+
return resource as T;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
get<T extends Resource>(id: ResourceId): T | undefined {
|
|
28
|
+
return this.resources.get(id) as T | undefined;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
has(id: ResourceId): boolean {
|
|
32
|
+
return this.resources.has(id);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/** Clear all Resources. Called by Orchestrator at the end of a run. */
|
|
36
|
+
clear(): void {
|
|
37
|
+
this.resources.clear();
|
|
38
|
+
}
|
|
39
|
+
}
|
|
@@ -0,0 +1,423 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SemanticId - Stable identifiers for code elements
|
|
3
|
+
*
|
|
4
|
+
* Semantic IDs provide stable identifiers for code elements that don't change
|
|
5
|
+
* when unrelated code is added/removed (no line numbers in IDs).
|
|
6
|
+
*
|
|
7
|
+
* Format: {file}->{scope_path}->{type}->{name}[#discriminator]
|
|
8
|
+
*
|
|
9
|
+
* Examples:
|
|
10
|
+
* src/app.js->global->FUNCTION->processData
|
|
11
|
+
* src/app.js->UserService->METHOD->login
|
|
12
|
+
* src/app.js->getUser->if#0->CALL->console.log#0
|
|
13
|
+
*
|
|
14
|
+
* Special formats:
|
|
15
|
+
* Singletons: net:stdio->__stdio__
|
|
16
|
+
* External modules: EXTERNAL_MODULE->lodash
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Location in source file
|
|
21
|
+
*/
|
|
22
|
+
export interface Location {
|
|
23
|
+
line: number;
|
|
24
|
+
column: number;
|
|
25
|
+
endLine?: number;
|
|
26
|
+
endColumn?: number;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Scope context for semantic ID generation
|
|
31
|
+
*/
|
|
32
|
+
export interface ScopeContext {
|
|
33
|
+
/** Source file path */
|
|
34
|
+
file: string;
|
|
35
|
+
/** Array of scope names, e.g. ['MyClass', 'myMethod', 'if#1'] */
|
|
36
|
+
scopePath: string[];
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Options for semantic ID generation
|
|
41
|
+
*/
|
|
42
|
+
export interface SemanticIdOptions {
|
|
43
|
+
/** Counter for disambiguation (#N) */
|
|
44
|
+
discriminator?: number;
|
|
45
|
+
/** Context string for special cases ([context]) */
|
|
46
|
+
context?: string;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Parsed semantic ID components
|
|
51
|
+
*/
|
|
52
|
+
export interface ParsedSemanticId {
|
|
53
|
+
file: string;
|
|
54
|
+
scopePath: string[];
|
|
55
|
+
type: string;
|
|
56
|
+
name: string;
|
|
57
|
+
discriminator?: number;
|
|
58
|
+
context?: string;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Item with name and location for discriminator computation
|
|
63
|
+
*/
|
|
64
|
+
export interface LocatedItem {
|
|
65
|
+
name: string;
|
|
66
|
+
location: Location;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// =============================================================================
|
|
70
|
+
// Semantic ID v2
|
|
71
|
+
// =============================================================================
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Parsed v2 semantic ID components.
|
|
75
|
+
*
|
|
76
|
+
* v2 format: file->TYPE->name[in:namedParent,h:xxxx]#N
|
|
77
|
+
*
|
|
78
|
+
* Key difference from v1: no scope path in ID. Only nearest named ancestor
|
|
79
|
+
* (namedParent) is encoded. Anonymous scopes (if, for, try) are invisible.
|
|
80
|
+
*/
|
|
81
|
+
export interface ParsedSemanticIdV2 {
|
|
82
|
+
file: string;
|
|
83
|
+
type: string;
|
|
84
|
+
name: string;
|
|
85
|
+
namedParent?: string;
|
|
86
|
+
contentHash?: string;
|
|
87
|
+
counter?: number;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Content hint data for computing content hash.
|
|
92
|
+
* Each node type supplies different hints for disambiguation.
|
|
93
|
+
*/
|
|
94
|
+
export interface ContentHashHints {
|
|
95
|
+
/** Number of arguments (CALL) or parameters (FUNCTION) */
|
|
96
|
+
arity?: number;
|
|
97
|
+
/** First literal argument value (CALL) */
|
|
98
|
+
firstLiteralArg?: string;
|
|
99
|
+
/** First parameter name (FUNCTION) */
|
|
100
|
+
firstParamName?: string;
|
|
101
|
+
/** RHS expression type (VARIABLE/CONSTANT) */
|
|
102
|
+
rhsType?: string;
|
|
103
|
+
/** First significant token of RHS (VARIABLE/CONSTANT) */
|
|
104
|
+
rhsToken?: string;
|
|
105
|
+
/** Object expression chain (PROPERTY_ACCESS) */
|
|
106
|
+
objectChain?: string;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Compute semantic ID for any node type.
|
|
111
|
+
*
|
|
112
|
+
* @param type - Node type (FUNCTION, CALL, VARIABLE, etc.)
|
|
113
|
+
* @param name - Node name
|
|
114
|
+
* @param context - Scope context from ScopeTracker
|
|
115
|
+
* @param options - Optional discriminator or context
|
|
116
|
+
* @returns Semantic ID string
|
|
117
|
+
*/
|
|
118
|
+
export function computeSemanticId(
|
|
119
|
+
type: string,
|
|
120
|
+
name: string,
|
|
121
|
+
context: ScopeContext,
|
|
122
|
+
options?: SemanticIdOptions
|
|
123
|
+
): string {
|
|
124
|
+
const { file, scopePath } = context;
|
|
125
|
+
const scope = scopePath.length > 0 ? scopePath.join('->') : 'global';
|
|
126
|
+
|
|
127
|
+
let id = `${file}->${scope}->${type}->${name}`;
|
|
128
|
+
|
|
129
|
+
if (options?.discriminator !== undefined) {
|
|
130
|
+
id += `#${options.discriminator}`;
|
|
131
|
+
} else if (options?.context) {
|
|
132
|
+
id += `[${options.context}]`;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
return id;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Parse semantic ID back to components.
|
|
140
|
+
*
|
|
141
|
+
* @param id - Semantic ID to parse
|
|
142
|
+
* @returns Parsed components or null if invalid
|
|
143
|
+
*/
|
|
144
|
+
export function parseSemanticId(id: string): ParsedSemanticId | null {
|
|
145
|
+
// Handle singletons
|
|
146
|
+
if (id.startsWith('net:stdio') || id.startsWith('net:request')) {
|
|
147
|
+
const [prefix, name] = id.split('->');
|
|
148
|
+
return {
|
|
149
|
+
file: '',
|
|
150
|
+
scopePath: [prefix],
|
|
151
|
+
type: 'SINGLETON',
|
|
152
|
+
name,
|
|
153
|
+
discriminator: undefined
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
if (id.startsWith('EXTERNAL_MODULE')) {
|
|
158
|
+
const [, name] = id.split('->');
|
|
159
|
+
return {
|
|
160
|
+
file: '',
|
|
161
|
+
scopePath: [],
|
|
162
|
+
type: 'EXTERNAL_MODULE',
|
|
163
|
+
name,
|
|
164
|
+
discriminator: undefined
|
|
165
|
+
};
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
const parts = id.split('->');
|
|
169
|
+
if (parts.length < 4) return null;
|
|
170
|
+
|
|
171
|
+
const file = parts[0];
|
|
172
|
+
const type = parts[parts.length - 2];
|
|
173
|
+
let name = parts[parts.length - 1];
|
|
174
|
+
const scopePath = parts.slice(1, -2);
|
|
175
|
+
|
|
176
|
+
// Parse discriminator or context
|
|
177
|
+
let discriminator: number | undefined;
|
|
178
|
+
let context: string | undefined;
|
|
179
|
+
|
|
180
|
+
const hashMatch = name.match(/^(.+)#(\d+)$/);
|
|
181
|
+
if (hashMatch) {
|
|
182
|
+
name = hashMatch[1];
|
|
183
|
+
discriminator = parseInt(hashMatch[2], 10);
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
const bracketMatch = name.match(/^(.+)\[(.+)\]$/);
|
|
187
|
+
if (bracketMatch) {
|
|
188
|
+
name = bracketMatch[1];
|
|
189
|
+
context = bracketMatch[2];
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
return { file, scopePath, type, name, discriminator, context };
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
/**
|
|
196
|
+
* Compute discriminator for items with same name in same scope.
|
|
197
|
+
* Uses line/column for stable ordering.
|
|
198
|
+
*
|
|
199
|
+
* @param items - All items in scope
|
|
200
|
+
* @param targetName - Name to find discriminator for
|
|
201
|
+
* @param targetLocation - Location of target item
|
|
202
|
+
* @returns Discriminator (0-based index among same-named items)
|
|
203
|
+
*/
|
|
204
|
+
export function computeDiscriminator(
|
|
205
|
+
items: LocatedItem[],
|
|
206
|
+
targetName: string,
|
|
207
|
+
targetLocation: Location
|
|
208
|
+
): number {
|
|
209
|
+
// Filter items with same name
|
|
210
|
+
const sameNameItems = items.filter(item => item.name === targetName);
|
|
211
|
+
|
|
212
|
+
if (sameNameItems.length <= 1) {
|
|
213
|
+
return 0;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
// Sort by line, then by column for stable ordering
|
|
217
|
+
sameNameItems.sort((a, b) => {
|
|
218
|
+
if (a.location.line !== b.location.line) {
|
|
219
|
+
return a.location.line - b.location.line;
|
|
220
|
+
}
|
|
221
|
+
return a.location.column - b.location.column;
|
|
222
|
+
});
|
|
223
|
+
|
|
224
|
+
// Find index of target
|
|
225
|
+
const index = sameNameItems.findIndex(
|
|
226
|
+
item =>
|
|
227
|
+
item.location.line === targetLocation.line &&
|
|
228
|
+
item.location.column === targetLocation.column
|
|
229
|
+
);
|
|
230
|
+
|
|
231
|
+
return index >= 0 ? index : 0;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
// =============================================================================
|
|
235
|
+
// Semantic ID v2 Functions
|
|
236
|
+
// =============================================================================
|
|
237
|
+
|
|
238
|
+
/**
|
|
239
|
+
* Compute v2 semantic ID.
|
|
240
|
+
*
|
|
241
|
+
* Format: file->TYPE->name (top-level, no collision)
|
|
242
|
+
* Format: file->TYPE->name[in:parent] (nested, no collision)
|
|
243
|
+
* Format: file->TYPE->name[in:parent,h:xxxx] (collision, hash disambiguates)
|
|
244
|
+
* Format: file->TYPE->name[in:parent,h:xxxx]#N (hash collision, counter)
|
|
245
|
+
*
|
|
246
|
+
* @param type - Node type (FUNCTION, CALL, VARIABLE, etc.)
|
|
247
|
+
* @param name - Node name
|
|
248
|
+
* @param file - Source file path
|
|
249
|
+
* @param namedParent - Nearest named ancestor (undefined for top-level)
|
|
250
|
+
* @param contentHash - 4-hex content hash for disambiguation
|
|
251
|
+
* @param counter - Counter for hash collisions
|
|
252
|
+
*/
|
|
253
|
+
export function computeSemanticIdV2(
|
|
254
|
+
type: string,
|
|
255
|
+
name: string,
|
|
256
|
+
file: string,
|
|
257
|
+
namedParent?: string,
|
|
258
|
+
contentHash?: string,
|
|
259
|
+
counter?: number
|
|
260
|
+
): string {
|
|
261
|
+
const brackets: string[] = [];
|
|
262
|
+
if (namedParent) brackets.push(`in:${namedParent}`);
|
|
263
|
+
if (contentHash) brackets.push(`h:${contentHash}`);
|
|
264
|
+
|
|
265
|
+
let id = `${file}->${type}->${name}`;
|
|
266
|
+
if (brackets.length > 0) {
|
|
267
|
+
id += `[${brackets.join(',')}]`;
|
|
268
|
+
}
|
|
269
|
+
if (counter !== undefined && counter > 0) {
|
|
270
|
+
id += `#${counter}`;
|
|
271
|
+
}
|
|
272
|
+
return id;
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
/**
|
|
276
|
+
* Parse v2 semantic ID back to components.
|
|
277
|
+
*
|
|
278
|
+
* Handles v2 format: file->TYPE->name[in:parent,h:xxxx]#N
|
|
279
|
+
* and special formats: net:stdio->__stdio__, EXTERNAL_MODULE->lodash
|
|
280
|
+
*
|
|
281
|
+
* @returns Parsed components or null if not a valid v2 ID
|
|
282
|
+
*/
|
|
283
|
+
export function parseSemanticIdV2(id: string): ParsedSemanticIdV2 | null {
|
|
284
|
+
// Handle grafema:// URI input — convert to compact first
|
|
285
|
+
if (id.startsWith('grafema://')) {
|
|
286
|
+
const scheme = 'grafema://';
|
|
287
|
+
const rest = id.slice(scheme.length);
|
|
288
|
+
|
|
289
|
+
const slashUnderscore = rest.indexOf('/_/');
|
|
290
|
+
const hashPos = rest.indexOf('#');
|
|
291
|
+
|
|
292
|
+
if (slashUnderscore !== -1 && (hashPos === -1 || slashUnderscore < hashPos)) {
|
|
293
|
+
// Virtual node: decode and re-parse
|
|
294
|
+
const encodedId = rest.slice(slashUnderscore + 3);
|
|
295
|
+
const decoded = encodedId
|
|
296
|
+
.replaceAll('%3E', '>').replaceAll('%3e', '>')
|
|
297
|
+
.replaceAll('%5B', '[').replaceAll('%5b', '[')
|
|
298
|
+
.replaceAll('%5D', ']').replaceAll('%5d', ']')
|
|
299
|
+
.replaceAll('%23', '#');
|
|
300
|
+
return parseSemanticIdV2(decoded);
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
if (hashPos !== -1) {
|
|
304
|
+
const pathPart = rest.slice(0, hashPos);
|
|
305
|
+
const fragment = rest.slice(hashPos + 1);
|
|
306
|
+
const decoded = fragment
|
|
307
|
+
.replaceAll('%3E', '>').replaceAll('%3e', '>')
|
|
308
|
+
.replaceAll('%5B', '[').replaceAll('%5b', '[')
|
|
309
|
+
.replaceAll('%5D', ']').replaceAll('%5d', ']')
|
|
310
|
+
.replaceAll('%23', '#');
|
|
311
|
+
|
|
312
|
+
if (decoded === 'MODULE') {
|
|
313
|
+
const segments = pathPart.split('/');
|
|
314
|
+
const host = segments[0];
|
|
315
|
+
const skip = (host === 'github.com' || host === 'gitlab.com' || host === 'bitbucket.org') ? 3 : 2;
|
|
316
|
+
const filePath = segments.slice(skip).join('/');
|
|
317
|
+
return { file: filePath, type: 'MODULE', name: filePath };
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
// Standard node: extract file and reconstruct compact ID
|
|
321
|
+
const segments = pathPart.split('/');
|
|
322
|
+
const host = segments[0];
|
|
323
|
+
const skip = (host === 'github.com' || host === 'gitlab.com' || host === 'bitbucket.org') ? 3 : 2;
|
|
324
|
+
const filePath = segments.slice(skip).join('/');
|
|
325
|
+
const compactId = `${filePath}->${decoded}`;
|
|
326
|
+
return parseSemanticIdV2(compactId);
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
return null;
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
// Handle singletons
|
|
333
|
+
if (id.startsWith('net:stdio') || id.startsWith('net:request')) {
|
|
334
|
+
const arrowIdx = id.indexOf('->');
|
|
335
|
+
if (arrowIdx === -1) return null;
|
|
336
|
+
return { file: '', type: 'SINGLETON', name: id.slice(arrowIdx + 2) };
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
if (id.startsWith('EXTERNAL_MODULE')) {
|
|
340
|
+
const arrowIdx = id.indexOf('->');
|
|
341
|
+
if (arrowIdx === -1) return null;
|
|
342
|
+
return { file: '', type: 'EXTERNAL_MODULE', name: id.slice(arrowIdx + 2) };
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
// v2 format: file->TYPE->name[in:parent,h:xxxx]#N
|
|
346
|
+
// Exactly two '->' delimiters: file->TYPE->rest
|
|
347
|
+
const firstArrow = id.indexOf('->');
|
|
348
|
+
if (firstArrow === -1) return null;
|
|
349
|
+
|
|
350
|
+
const secondArrow = id.indexOf('->', firstArrow + 2);
|
|
351
|
+
if (secondArrow === -1) return null;
|
|
352
|
+
|
|
353
|
+
const file = id.slice(0, firstArrow);
|
|
354
|
+
const type = id.slice(firstArrow + 2, secondArrow);
|
|
355
|
+
let rest = id.slice(secondArrow + 2);
|
|
356
|
+
|
|
357
|
+
// Check this isn't a v1 ID (v1 has 4+ parts, meaning rest contains '->')
|
|
358
|
+
// v2 names don't contain '->' in practice
|
|
359
|
+
if (rest.includes('->')) return null;
|
|
360
|
+
|
|
361
|
+
// Parse counter suffix: #N (only at the very end, after brackets)
|
|
362
|
+
let counter: number | undefined;
|
|
363
|
+
const counterMatch = rest.match(/#(\d+)$/);
|
|
364
|
+
if (counterMatch) {
|
|
365
|
+
counter = parseInt(counterMatch[1], 10);
|
|
366
|
+
rest = rest.slice(0, -counterMatch[0].length);
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
// Parse bracket content: [in:parent,h:xxxx]
|
|
370
|
+
let namedParent: string | undefined;
|
|
371
|
+
let contentHash: string | undefined;
|
|
372
|
+
let name = rest;
|
|
373
|
+
|
|
374
|
+
const bracketStart = rest.indexOf('[');
|
|
375
|
+
const bracketEnd = rest.lastIndexOf(']');
|
|
376
|
+
if (bracketStart !== -1 && bracketEnd === rest.length - 1) {
|
|
377
|
+
name = rest.slice(0, bracketStart);
|
|
378
|
+
const bracketContent = rest.slice(bracketStart + 1, bracketEnd);
|
|
379
|
+
|
|
380
|
+
for (const part of bracketContent.split(',')) {
|
|
381
|
+
const colonIdx = part.indexOf(':');
|
|
382
|
+
if (colonIdx === -1) continue;
|
|
383
|
+
const key = part.slice(0, colonIdx);
|
|
384
|
+
const value = part.slice(colonIdx + 1);
|
|
385
|
+
if (key === 'in') namedParent = value;
|
|
386
|
+
else if (key === 'h') contentHash = value;
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
return { file, type, name, namedParent, contentHash, counter };
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
/**
|
|
394
|
+
* FNV-1a hash function, returns 4-hex-char string.
|
|
395
|
+
*
|
|
396
|
+
* FNV-1a is simple, fast, and has good distribution for short strings.
|
|
397
|
+
* 4 hex chars = 16 bits = 65536 buckets.
|
|
398
|
+
*
|
|
399
|
+
* @param hints - Content data to hash
|
|
400
|
+
* @returns 4-char hex string (e.g., "a1b2")
|
|
401
|
+
*/
|
|
402
|
+
export function computeContentHash(hints: ContentHashHints): string {
|
|
403
|
+
const parts: string[] = [];
|
|
404
|
+
if (hints.arity !== undefined) parts.push(`a:${hints.arity}`);
|
|
405
|
+
if (hints.firstLiteralArg !== undefined) parts.push(`l:${hints.firstLiteralArg}`);
|
|
406
|
+
if (hints.firstParamName !== undefined) parts.push(`p:${hints.firstParamName}`);
|
|
407
|
+
if (hints.rhsType !== undefined) parts.push(`r:${hints.rhsType}`);
|
|
408
|
+
if (hints.rhsToken !== undefined) parts.push(`t:${hints.rhsToken}`);
|
|
409
|
+
if (hints.objectChain !== undefined) parts.push(`o:${hints.objectChain}`);
|
|
410
|
+
|
|
411
|
+
const input = parts.join('|');
|
|
412
|
+
|
|
413
|
+
// FNV-1a 32-bit
|
|
414
|
+
let hash = 0x811c9dc5; // FNV offset basis
|
|
415
|
+
for (let i = 0; i < input.length; i++) {
|
|
416
|
+
hash ^= input.charCodeAt(i);
|
|
417
|
+
hash = Math.imul(hash, 0x01000193); // FNV prime
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
// Truncate to 16 bits, format as 4-hex
|
|
421
|
+
const truncated = (hash >>> 0) & 0xffff;
|
|
422
|
+
return truncated.toString(16).padStart(4, '0');
|
|
423
|
+
}
|