@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,191 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SemanticAddressResolver — lazy resolver from semantic addresses to code graph node IDs.
|
|
3
|
+
*
|
|
4
|
+
* Semantic addresses (e.g., "src/auth.js:hashPassword:FUNCTION") are human-readable
|
|
5
|
+
* references to code. After re-analysis, node IDs may change.
|
|
6
|
+
* This resolver bridges KB references to current graph state at query time.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import type { ParsedSemanticAddress, ResolvedAddress } from './types.js';
|
|
10
|
+
|
|
11
|
+
/** Minimal backend interface for resolving semantic addresses */
|
|
12
|
+
export interface ResolverBackend {
|
|
13
|
+
getAllNodes(filter?: Record<string, unknown>): Promise<Array<Record<string, unknown> & { id: string }>>;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
interface CacheEntry {
|
|
17
|
+
result: ResolvedAddress;
|
|
18
|
+
resolvedAt: number;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Parse a semantic address string into its components.
|
|
23
|
+
*
|
|
24
|
+
* Format: `file:name:TYPE` or `file:scope1:...:scopeN:name:TYPE`
|
|
25
|
+
* - Last segment = node type (FUNCTION, CLASS, VARIABLE, etc.)
|
|
26
|
+
* - First segment = file path (contains `/` or `.`)
|
|
27
|
+
* - Middle segments = scope path; last middle segment is the name
|
|
28
|
+
*
|
|
29
|
+
* @returns parsed address or null if invalid
|
|
30
|
+
*/
|
|
31
|
+
export function parseSemanticAddress(address: string): ParsedSemanticAddress | null {
|
|
32
|
+
if (!address || typeof address !== 'string') return null;
|
|
33
|
+
|
|
34
|
+
const parts = address.split(':');
|
|
35
|
+
// Minimum: file:name:TYPE = 3 parts
|
|
36
|
+
if (parts.length < 3) return null;
|
|
37
|
+
|
|
38
|
+
const type = parts[parts.length - 1];
|
|
39
|
+
const file = parts[0];
|
|
40
|
+
|
|
41
|
+
// File must contain / or . to be valid
|
|
42
|
+
if (!file.includes('/') && !file.includes('.')) return null;
|
|
43
|
+
|
|
44
|
+
// Type must be uppercase
|
|
45
|
+
if (type !== type.toUpperCase() || !/^[A-Z_]+$/.test(type)) return null;
|
|
46
|
+
|
|
47
|
+
// Middle segments: everything between file and type
|
|
48
|
+
const middle = parts.slice(1, -1);
|
|
49
|
+
if (middle.length === 0) return null;
|
|
50
|
+
|
|
51
|
+
const name = middle[middle.length - 1];
|
|
52
|
+
const scopePath = middle.slice(0, -1);
|
|
53
|
+
|
|
54
|
+
return { file, name, type, scopePath };
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export class SemanticAddressResolver {
|
|
58
|
+
private backend: ResolverBackend;
|
|
59
|
+
private cache = new Map<string, CacheEntry>();
|
|
60
|
+
private generation = 0;
|
|
61
|
+
|
|
62
|
+
constructor(backend: ResolverBackend) {
|
|
63
|
+
this.backend = backend;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Increment the generation counter, marking all cached entries stale.
|
|
68
|
+
* Call this after re-analysis to force re-resolution on next access.
|
|
69
|
+
*/
|
|
70
|
+
bumpGeneration(): void {
|
|
71
|
+
this.generation++;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/** Current generation number */
|
|
75
|
+
getGeneration(): number {
|
|
76
|
+
return this.generation;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Resolve a semantic address to a code graph node ID.
|
|
81
|
+
*
|
|
82
|
+
* - `kb:` addresses pass through without backend query (KB-internal refs).
|
|
83
|
+
* - Results are cached; stale entries (resolvedAt < generation) are re-resolved.
|
|
84
|
+
*/
|
|
85
|
+
async resolve(address: string): Promise<ResolvedAddress> {
|
|
86
|
+
// KB-internal addresses pass through
|
|
87
|
+
if (address.startsWith('kb:')) {
|
|
88
|
+
return { address, codeNodeId: null, status: 'resolved' };
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// Check cache
|
|
92
|
+
const cached = this.cache.get(address);
|
|
93
|
+
if (cached && cached.resolvedAt >= this.generation) {
|
|
94
|
+
return cached.result;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
const parsed = parseSemanticAddress(address);
|
|
98
|
+
if (!parsed) {
|
|
99
|
+
const result: ResolvedAddress = { address, codeNodeId: null, status: 'dangling' };
|
|
100
|
+
this.cache.set(address, { result, resolvedAt: this.generation });
|
|
101
|
+
return result;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// Query backend for matching nodes
|
|
105
|
+
const filter: Record<string, unknown> = {
|
|
106
|
+
file: parsed.file,
|
|
107
|
+
name: parsed.name,
|
|
108
|
+
nodeType: parsed.type,
|
|
109
|
+
};
|
|
110
|
+
|
|
111
|
+
const nodes = await this.backend.getAllNodes(filter);
|
|
112
|
+
|
|
113
|
+
let result: ResolvedAddress;
|
|
114
|
+
|
|
115
|
+
if (nodes.length === 1) {
|
|
116
|
+
result = { address, codeNodeId: nodes[0].id, status: 'resolved' };
|
|
117
|
+
} else if (nodes.length === 0) {
|
|
118
|
+
result = { address, codeNodeId: null, status: 'dangling' };
|
|
119
|
+
} else {
|
|
120
|
+
// Multiple matches — disambiguate by scope path
|
|
121
|
+
const match = this.disambiguateByScope(nodes, parsed.scopePath);
|
|
122
|
+
if (match) {
|
|
123
|
+
result = { address, codeNodeId: match.id, status: 'resolved' };
|
|
124
|
+
} else {
|
|
125
|
+
// Take first match as best-effort
|
|
126
|
+
result = { address, codeNodeId: nodes[0].id, status: 'resolved' };
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
this.cache.set(address, { result, resolvedAt: this.generation });
|
|
131
|
+
return result;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Resolve multiple addresses in bulk.
|
|
136
|
+
*/
|
|
137
|
+
async resolveAll(addresses: string[]): Promise<ResolvedAddress[]> {
|
|
138
|
+
return Promise.all(addresses.map(addr => this.resolve(addr)));
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* Get all addresses that resolved as dangling at the current generation.
|
|
143
|
+
*/
|
|
144
|
+
getDanglingAddresses(): ResolvedAddress[] {
|
|
145
|
+
const results: ResolvedAddress[] = [];
|
|
146
|
+
for (const entry of this.cache.values()) {
|
|
147
|
+
if (entry.resolvedAt >= this.generation && entry.result.status === 'dangling') {
|
|
148
|
+
results.push(entry.result);
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
return results;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* Disambiguate multiple matching nodes using the scope path from the address.
|
|
156
|
+
* Matches the node whose scopePath best matches the address's scope segments.
|
|
157
|
+
*/
|
|
158
|
+
private disambiguateByScope(
|
|
159
|
+
nodes: Array<Record<string, unknown> & { id: string }>,
|
|
160
|
+
addressScopePath: string[],
|
|
161
|
+
): { id: string } | null {
|
|
162
|
+
if (addressScopePath.length === 0) return null;
|
|
163
|
+
|
|
164
|
+
let bestMatch: { id: string } | null = null;
|
|
165
|
+
let bestScore = -1;
|
|
166
|
+
|
|
167
|
+
for (const node of nodes) {
|
|
168
|
+
const nodeScopePath = node['scopePath'];
|
|
169
|
+
if (!Array.isArray(nodeScopePath)) continue;
|
|
170
|
+
|
|
171
|
+
// Score: count matching scope segments from the end
|
|
172
|
+
let score = 0;
|
|
173
|
+
const aLen = addressScopePath.length;
|
|
174
|
+
const nLen = nodeScopePath.length;
|
|
175
|
+
for (let i = 1; i <= Math.min(aLen, nLen); i++) {
|
|
176
|
+
if (addressScopePath[aLen - i] === nodeScopePath[nLen - i]) {
|
|
177
|
+
score++;
|
|
178
|
+
} else {
|
|
179
|
+
break;
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
if (score > bestScore) {
|
|
184
|
+
bestScore = score;
|
|
185
|
+
bestMatch = node;
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
return bestScore > 0 ? bestMatch : null;
|
|
190
|
+
}
|
|
191
|
+
}
|
|
@@ -0,0 +1,402 @@
|
|
|
1
|
+
import { execSync } from 'child_process';
|
|
2
|
+
import { readFileSync, writeFileSync, existsSync, mkdirSync, rmSync } from 'fs';
|
|
3
|
+
import { join } from 'path';
|
|
4
|
+
import { parse as parseYaml, stringify as stringifyYaml } from 'yaml';
|
|
5
|
+
|
|
6
|
+
export interface RawCommit {
|
|
7
|
+
hash: string;
|
|
8
|
+
message: string;
|
|
9
|
+
authorName: string;
|
|
10
|
+
authorEmail: string;
|
|
11
|
+
date: string;
|
|
12
|
+
files: FileChange[];
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export interface FileChange {
|
|
16
|
+
path: string;
|
|
17
|
+
added: number;
|
|
18
|
+
removed: number;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export interface AuthorEntry {
|
|
22
|
+
id: string;
|
|
23
|
+
type: 'AUTHOR';
|
|
24
|
+
name: string;
|
|
25
|
+
emails: string[];
|
|
26
|
+
aliases: string[];
|
|
27
|
+
projections: string[];
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export interface CommitEntry {
|
|
31
|
+
type: 'COMMIT';
|
|
32
|
+
hash: string;
|
|
33
|
+
message: string;
|
|
34
|
+
author_ref: string;
|
|
35
|
+
date: string;
|
|
36
|
+
files: FileChange[];
|
|
37
|
+
projections: string[];
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export interface IngestResult {
|
|
41
|
+
commits: number;
|
|
42
|
+
authors: number;
|
|
43
|
+
filesChanged: number;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export interface Meta {
|
|
47
|
+
last_commit: string;
|
|
48
|
+
last_ingest: string;
|
|
49
|
+
branch: string;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function slugify(name: string): string {
|
|
53
|
+
return name.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-|-$/g, '');
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function resolveRenamedPath(rawPath: string): string {
|
|
57
|
+
const match = rawPath.match(/\{[^}]* => ([^}]*)\}/);
|
|
58
|
+
if (match) {
|
|
59
|
+
return rawPath.replace(/\{[^}]* => [^}]*\}/, match[1]);
|
|
60
|
+
}
|
|
61
|
+
return rawPath;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export function parseGitLog(rawOutput: string): RawCommit[] {
|
|
65
|
+
const commits: RawCommit[] = [];
|
|
66
|
+
const blocks = rawOutput.split('COMMIT_START\n').filter(b => b.trim());
|
|
67
|
+
|
|
68
|
+
for (const block of blocks) {
|
|
69
|
+
const lines = block.split('\n');
|
|
70
|
+
if (lines.length < 5) continue;
|
|
71
|
+
|
|
72
|
+
const hash = lines[0].trim();
|
|
73
|
+
const message = lines[1].trim();
|
|
74
|
+
const authorName = lines[2].trim();
|
|
75
|
+
const authorEmail = lines[3].trim();
|
|
76
|
+
const date = lines[4].trim();
|
|
77
|
+
|
|
78
|
+
const files: FileChange[] = [];
|
|
79
|
+
for (let i = 5; i < lines.length; i++) {
|
|
80
|
+
const line = lines[i].trim();
|
|
81
|
+
if (!line) continue;
|
|
82
|
+
|
|
83
|
+
const parts = line.split('\t');
|
|
84
|
+
if (parts.length < 3) continue;
|
|
85
|
+
|
|
86
|
+
const addedRaw = parts[0];
|
|
87
|
+
const removedRaw = parts[1];
|
|
88
|
+
const rawPath = parts.slice(2).join('\t');
|
|
89
|
+
const path = resolveRenamedPath(rawPath);
|
|
90
|
+
|
|
91
|
+
const added = addedRaw === '-' ? 0 : parseInt(addedRaw, 10) || 0;
|
|
92
|
+
const removed = removedRaw === '-' ? 0 : parseInt(removedRaw, 10) || 0;
|
|
93
|
+
|
|
94
|
+
files.push({ path, added, removed });
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
commits.push({ hash, message, authorName, authorEmail, date, files });
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
return commits;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
export function normalizeAuthors(commits: RawCommit[]): Map<string, AuthorEntry> {
|
|
104
|
+
const emailGroups = new Map<string, { names: Map<string, number>; emails: Set<string> }>();
|
|
105
|
+
|
|
106
|
+
for (const commit of commits) {
|
|
107
|
+
const emailKey = commit.authorEmail.toLowerCase();
|
|
108
|
+
let group = emailGroups.get(emailKey);
|
|
109
|
+
if (!group) {
|
|
110
|
+
group = { names: new Map(), emails: new Set() };
|
|
111
|
+
emailGroups.set(emailKey, group);
|
|
112
|
+
}
|
|
113
|
+
group.emails.add(commit.authorEmail);
|
|
114
|
+
const count = group.names.get(commit.authorName) || 0;
|
|
115
|
+
group.names.set(commit.authorName, count + 1);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
const authors = new Map<string, AuthorEntry>();
|
|
119
|
+
|
|
120
|
+
for (const [, group] of emailGroups) {
|
|
121
|
+
let primaryName = '';
|
|
122
|
+
let maxCount = 0;
|
|
123
|
+
for (const [name, count] of group.names) {
|
|
124
|
+
if (count > maxCount) {
|
|
125
|
+
maxCount = count;
|
|
126
|
+
primaryName = name;
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
const aliases: string[] = [];
|
|
131
|
+
for (const [name] of group.names) {
|
|
132
|
+
if (name !== primaryName) {
|
|
133
|
+
aliases.push(name);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
const id = slugify(primaryName);
|
|
138
|
+
const emails = Array.from(group.emails);
|
|
139
|
+
|
|
140
|
+
authors.set(id, {
|
|
141
|
+
id,
|
|
142
|
+
type: 'AUTHOR',
|
|
143
|
+
name: primaryName,
|
|
144
|
+
emails,
|
|
145
|
+
aliases,
|
|
146
|
+
projections: ['temporal', 'organizational'],
|
|
147
|
+
});
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
return authors;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
export class GitIngest {
|
|
154
|
+
private knowledgeDir: string;
|
|
155
|
+
|
|
156
|
+
constructor(knowledgeDir: string) {
|
|
157
|
+
this.knowledgeDir = knowledgeDir;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
async ingestFull(repoPath: string, branch?: string, since?: string): Promise<IngestResult> {
|
|
161
|
+
const derivedDir = join(this.knowledgeDir, 'derived');
|
|
162
|
+
if (existsSync(derivedDir)) {
|
|
163
|
+
rmSync(derivedDir, { recursive: true, force: true });
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
const raw = this.runGitLog(repoPath, undefined, branch, since);
|
|
167
|
+
const rawCommits = parseGitLog(raw);
|
|
168
|
+
const authors = normalizeAuthors(rawCommits);
|
|
169
|
+
|
|
170
|
+
const commitEntries = rawCommits.map(rc => this.toCommitEntry(rc, authors));
|
|
171
|
+
|
|
172
|
+
this.writeAuthors(authors);
|
|
173
|
+
this.writeCommits(commitEntries);
|
|
174
|
+
|
|
175
|
+
const meta: Meta = {
|
|
176
|
+
last_commit: rawCommits.length > 0 ? rawCommits[0].hash : '',
|
|
177
|
+
last_ingest: new Date().toISOString(),
|
|
178
|
+
branch: branch || 'HEAD',
|
|
179
|
+
};
|
|
180
|
+
this.writeMeta(meta);
|
|
181
|
+
|
|
182
|
+
const filesChanged = new Set(rawCommits.flatMap(c => c.files.map(f => f.path))).size;
|
|
183
|
+
|
|
184
|
+
return {
|
|
185
|
+
commits: rawCommits.length,
|
|
186
|
+
authors: authors.size,
|
|
187
|
+
filesChanged,
|
|
188
|
+
};
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
async ingestIncremental(repoPath: string, branch?: string): Promise<IngestResult> {
|
|
192
|
+
const meta = this.readMeta();
|
|
193
|
+
if (!meta) {
|
|
194
|
+
return this.ingestFull(repoPath, branch);
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
const lastCommitHash = meta.last_commit;
|
|
198
|
+
const raw = this.runGitLog(repoPath, lastCommitHash, branch);
|
|
199
|
+
const rawCommits = parseGitLog(raw);
|
|
200
|
+
|
|
201
|
+
if (rawCommits.length === 0) {
|
|
202
|
+
return { commits: 0, authors: 0, filesChanged: 0 };
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
const newAuthors = normalizeAuthors(rawCommits);
|
|
206
|
+
const existingAuthors = this.readExistingAuthors();
|
|
207
|
+
const mergedAuthors = this.mergeAuthors(existingAuthors, newAuthors);
|
|
208
|
+
|
|
209
|
+
const newCommitEntries = rawCommits.map(rc => this.toCommitEntry(rc, mergedAuthors));
|
|
210
|
+
this.mergeCommits(newCommitEntries);
|
|
211
|
+
this.writeAuthors(mergedAuthors);
|
|
212
|
+
|
|
213
|
+
const updatedMeta: Meta = {
|
|
214
|
+
last_commit: rawCommits[0].hash,
|
|
215
|
+
last_ingest: new Date().toISOString(),
|
|
216
|
+
branch: branch || meta.branch,
|
|
217
|
+
};
|
|
218
|
+
this.writeMeta(updatedMeta);
|
|
219
|
+
|
|
220
|
+
const filesChanged = new Set(rawCommits.flatMap(c => c.files.map(f => f.path))).size;
|
|
221
|
+
|
|
222
|
+
return {
|
|
223
|
+
commits: rawCommits.length,
|
|
224
|
+
authors: newAuthors.size,
|
|
225
|
+
filesChanged,
|
|
226
|
+
};
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
private runGitLog(repoPath: string, afterCommit?: string, branch?: string, sinceDate?: string): string {
|
|
230
|
+
const ref = branch || 'HEAD';
|
|
231
|
+
const range = afterCommit ? `${afterCommit}..${ref}` : ref;
|
|
232
|
+
const sinceFlag = sinceDate ? ` --since="${sinceDate}"` : '';
|
|
233
|
+
const cmd = `git log --numstat --format="COMMIT_START%n%H%n%s%n%an%n%ae%n%aI"${sinceFlag} ${range}`;
|
|
234
|
+
|
|
235
|
+
try {
|
|
236
|
+
return execSync(cmd, { cwd: repoPath, encoding: 'utf-8', maxBuffer: 100 * 1024 * 1024 });
|
|
237
|
+
} catch (error: unknown) {
|
|
238
|
+
// Git exits with 128 for "fatal" errors (empty repo, bad revision, etc.)
|
|
239
|
+
// These are expected cases, not failures worth surfacing.
|
|
240
|
+
const exitCode = (error as { status?: number }).status;
|
|
241
|
+
if (exitCode === 128) {
|
|
242
|
+
return '';
|
|
243
|
+
}
|
|
244
|
+
const msg = error instanceof Error ? error.message : String(error);
|
|
245
|
+
throw new Error(`git log failed: ${msg}`);
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
private writeAuthors(authors: Map<string, AuthorEntry>): void {
|
|
250
|
+
const derivedDir = join(this.knowledgeDir, 'derived');
|
|
251
|
+
mkdirSync(derivedDir, { recursive: true });
|
|
252
|
+
|
|
253
|
+
const entries = Array.from(authors.values());
|
|
254
|
+
const filePath = join(derivedDir, 'authors.yaml');
|
|
255
|
+
writeFileSync(filePath, stringifyYaml(entries), 'utf-8');
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
private writeCommits(commits: CommitEntry[]): void {
|
|
259
|
+
const commitsDir = join(this.knowledgeDir, 'derived', 'commits');
|
|
260
|
+
mkdirSync(commitsDir, { recursive: true });
|
|
261
|
+
|
|
262
|
+
const monthGroups = new Map<string, CommitEntry[]>();
|
|
263
|
+
for (const commit of commits) {
|
|
264
|
+
const month = commit.date.slice(0, 7); // YYYY-MM
|
|
265
|
+
let group = monthGroups.get(month);
|
|
266
|
+
if (!group) {
|
|
267
|
+
group = [];
|
|
268
|
+
monthGroups.set(month, group);
|
|
269
|
+
}
|
|
270
|
+
group.push(commit);
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
for (const [month, group] of monthGroups) {
|
|
274
|
+
const filePath = join(commitsDir, `${month}.yaml`);
|
|
275
|
+
writeFileSync(filePath, stringifyYaml(group), 'utf-8');
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
private writeMeta(meta: Meta): void {
|
|
280
|
+
const derivedDir = join(this.knowledgeDir, 'derived');
|
|
281
|
+
mkdirSync(derivedDir, { recursive: true });
|
|
282
|
+
|
|
283
|
+
const filePath = join(derivedDir, 'meta.yaml');
|
|
284
|
+
writeFileSync(filePath, stringifyYaml(meta), 'utf-8');
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
private readMeta(): Meta | null {
|
|
288
|
+
const filePath = join(this.knowledgeDir, 'derived', 'meta.yaml');
|
|
289
|
+
if (!existsSync(filePath)) return null;
|
|
290
|
+
|
|
291
|
+
try {
|
|
292
|
+
const content = readFileSync(filePath, 'utf-8');
|
|
293
|
+
return parseYaml(content) as Meta;
|
|
294
|
+
} catch {
|
|
295
|
+
return null;
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
private readExistingAuthors(): Map<string, AuthorEntry> {
|
|
300
|
+
const filePath = join(this.knowledgeDir, 'derived', 'authors.yaml');
|
|
301
|
+
const authors = new Map<string, AuthorEntry>();
|
|
302
|
+
|
|
303
|
+
if (!existsSync(filePath)) return authors;
|
|
304
|
+
|
|
305
|
+
try {
|
|
306
|
+
const content = readFileSync(filePath, 'utf-8');
|
|
307
|
+
const entries = parseYaml(content) as AuthorEntry[];
|
|
308
|
+
if (Array.isArray(entries)) {
|
|
309
|
+
for (const entry of entries) {
|
|
310
|
+
authors.set(entry.id, entry);
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
} catch {
|
|
314
|
+
// ignore parse errors
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
return authors;
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
private mergeAuthors(
|
|
321
|
+
existing: Map<string, AuthorEntry>,
|
|
322
|
+
incoming: Map<string, AuthorEntry>,
|
|
323
|
+
): Map<string, AuthorEntry> {
|
|
324
|
+
const merged = new Map(existing);
|
|
325
|
+
|
|
326
|
+
for (const [id, entry] of incoming) {
|
|
327
|
+
const existingEntry = merged.get(id);
|
|
328
|
+
if (existingEntry) {
|
|
329
|
+
const allEmails = new Set([...existingEntry.emails, ...entry.emails]);
|
|
330
|
+
const allAliases = new Set([...existingEntry.aliases, ...entry.aliases]);
|
|
331
|
+
merged.set(id, {
|
|
332
|
+
...existingEntry,
|
|
333
|
+
emails: Array.from(allEmails),
|
|
334
|
+
aliases: Array.from(allAliases),
|
|
335
|
+
});
|
|
336
|
+
} else {
|
|
337
|
+
merged.set(id, entry);
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
return merged;
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
private mergeCommits(newCommits: CommitEntry[]): void {
|
|
345
|
+
const commitsDir = join(this.knowledgeDir, 'derived', 'commits');
|
|
346
|
+
mkdirSync(commitsDir, { recursive: true });
|
|
347
|
+
|
|
348
|
+
const monthGroups = new Map<string, CommitEntry[]>();
|
|
349
|
+
for (const commit of newCommits) {
|
|
350
|
+
const month = commit.date.slice(0, 7);
|
|
351
|
+
let group = monthGroups.get(month);
|
|
352
|
+
if (!group) {
|
|
353
|
+
group = [];
|
|
354
|
+
monthGroups.set(month, group);
|
|
355
|
+
}
|
|
356
|
+
group.push(commit);
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
for (const [month, group] of monthGroups) {
|
|
360
|
+
const filePath = join(commitsDir, `${month}.yaml`);
|
|
361
|
+
let existing: CommitEntry[] = [];
|
|
362
|
+
|
|
363
|
+
if (existsSync(filePath)) {
|
|
364
|
+
try {
|
|
365
|
+
const content = readFileSync(filePath, 'utf-8');
|
|
366
|
+
const parsed = parseYaml(content);
|
|
367
|
+
if (Array.isArray(parsed)) {
|
|
368
|
+
existing = parsed as CommitEntry[];
|
|
369
|
+
}
|
|
370
|
+
} catch {
|
|
371
|
+
// ignore parse errors, start fresh
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
const existingHashes = new Set(existing.map(c => c.hash));
|
|
376
|
+
const dedupedNew = group.filter(c => !existingHashes.has(c.hash));
|
|
377
|
+
const merged = [...existing, ...dedupedNew];
|
|
378
|
+
|
|
379
|
+
writeFileSync(filePath, stringifyYaml(merged), 'utf-8');
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
private toCommitEntry(rc: RawCommit, authors: Map<string, AuthorEntry>): CommitEntry {
|
|
384
|
+
let authorRef = '';
|
|
385
|
+
for (const [id, author] of authors) {
|
|
386
|
+
if (author.emails.some(e => e.toLowerCase() === rc.authorEmail.toLowerCase())) {
|
|
387
|
+
authorRef = `kb:author:${id}`;
|
|
388
|
+
break;
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
return {
|
|
393
|
+
type: 'COMMIT',
|
|
394
|
+
hash: rc.hash,
|
|
395
|
+
message: rc.message,
|
|
396
|
+
author_ref: authorRef,
|
|
397
|
+
date: rc.date,
|
|
398
|
+
files: rc.files,
|
|
399
|
+
projections: ['temporal', 'organizational'],
|
|
400
|
+
};
|
|
401
|
+
}
|
|
402
|
+
}
|