sela-core 1.0.0
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 +93 -0
- package/bin/sela.js +3 -0
- package/dist/cli/ErrorHandler.d.ts +10 -0
- package/dist/cli/ErrorHandler.d.ts.map +1 -0
- package/dist/cli/ErrorHandler.js +70 -0
- package/dist/cli/commands/bulk.d.ts +3 -0
- package/dist/cli/commands/bulk.d.ts.map +1 -0
- package/dist/cli/commands/bulk.js +140 -0
- package/dist/cli/commands/find.d.ts +3 -0
- package/dist/cli/commands/find.d.ts.map +1 -0
- package/dist/cli/commands/find.js +51 -0
- package/dist/cli/commands/init.d.ts +3 -0
- package/dist/cli/commands/init.d.ts.map +1 -0
- package/dist/cli/commands/init.js +133 -0
- package/dist/cli/commands/list.d.ts +3 -0
- package/dist/cli/commands/list.d.ts.map +1 -0
- package/dist/cli/commands/list.js +56 -0
- package/dist/cli/commands/refactor.d.ts +3 -0
- package/dist/cli/commands/refactor.d.ts.map +1 -0
- package/dist/cli/commands/refactor.js +30 -0
- package/dist/cli/commands/status.d.ts +3 -0
- package/dist/cli/commands/status.d.ts.map +1 -0
- package/dist/cli/commands/status.js +51 -0
- package/dist/cli/commands/sync.d.ts +3 -0
- package/dist/cli/commands/sync.d.ts.map +1 -0
- package/dist/cli/commands/sync.js +123 -0
- package/dist/cli/index.d.ts +2 -0
- package/dist/cli/index.d.ts.map +1 -0
- package/dist/cli/index.js +42 -0
- package/dist/cli/ui/DnaTable.d.ts +3 -0
- package/dist/cli/ui/DnaTable.d.ts.map +1 -0
- package/dist/cli/ui/DnaTable.js +70 -0
- package/dist/cli/ui/LocatorPicker.d.ts +8 -0
- package/dist/cli/ui/LocatorPicker.d.ts.map +1 -0
- package/dist/cli/ui/LocatorPicker.js +33 -0
- package/dist/cli/ui/ProgressReporter.d.ts +11 -0
- package/dist/cli/ui/ProgressReporter.d.ts.map +1 -0
- package/dist/cli/ui/ProgressReporter.js +33 -0
- package/dist/cli/ui/RefactorWizard.d.ts +26 -0
- package/dist/cli/ui/RefactorWizard.d.ts.map +1 -0
- package/dist/cli/ui/RefactorWizard.js +269 -0
- package/dist/config/ConfigLoader.d.ts +15 -0
- package/dist/config/ConfigLoader.d.ts.map +1 -0
- package/dist/config/ConfigLoader.js +174 -0
- package/dist/config/SelaConfig.d.ts +67 -0
- package/dist/config/SelaConfig.d.ts.map +1 -0
- package/dist/config/SelaConfig.js +57 -0
- package/dist/config/defineConfig.d.ts +3 -0
- package/dist/config/defineConfig.d.ts.map +1 -0
- package/dist/config/defineConfig.js +9 -0
- package/dist/engine/FixwrightEngine.d.ts +24 -0
- package/dist/engine/FixwrightEngine.d.ts.map +1 -0
- package/dist/engine/FixwrightEngine.js +403 -0
- package/dist/engine/HealingRegistry.d.ts +40 -0
- package/dist/engine/HealingRegistry.d.ts.map +1 -0
- package/dist/engine/HealingRegistry.js +98 -0
- package/dist/engine/singleton.d.ts +3 -0
- package/dist/engine/singleton.d.ts.map +1 -0
- package/dist/engine/singleton.js +5 -0
- package/dist/fixtures/expectProxy.d.ts +12 -0
- package/dist/fixtures/expectProxy.d.ts.map +1 -0
- package/dist/fixtures/expectProxy.js +228 -0
- package/dist/fixtures/index.d.ts +6 -0
- package/dist/fixtures/index.d.ts.map +1 -0
- package/dist/fixtures/index.js +688 -0
- package/dist/fixtures/moduleExpect.d.ts +2 -0
- package/dist/fixtures/moduleExpect.d.ts.map +1 -0
- package/dist/fixtures/moduleExpect.js +46 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +28 -0
- package/dist/services/ASTSourceUpdater.d.ts +79 -0
- package/dist/services/ASTSourceUpdater.d.ts.map +1 -0
- package/dist/services/ASTSourceUpdater.js +3177 -0
- package/dist/services/ArgumentTypeAnalyzer.d.ts +26 -0
- package/dist/services/ArgumentTypeAnalyzer.d.ts.map +1 -0
- package/dist/services/ArgumentTypeAnalyzer.js +92 -0
- package/dist/services/BlastRadiusAnalyzer.d.ts +15 -0
- package/dist/services/BlastRadiusAnalyzer.d.ts.map +1 -0
- package/dist/services/BlastRadiusAnalyzer.js +103 -0
- package/dist/services/ChainValidator.d.ts +76 -0
- package/dist/services/ChainValidator.d.ts.map +1 -0
- package/dist/services/ChainValidator.js +569 -0
- package/dist/services/CrossFileHealer.d.ts +6 -0
- package/dist/services/CrossFileHealer.d.ts.map +1 -0
- package/dist/services/CrossFileHealer.js +134 -0
- package/dist/services/DefinitionTracer.d.ts +41 -0
- package/dist/services/DefinitionTracer.d.ts.map +1 -0
- package/dist/services/DefinitionTracer.js +350 -0
- package/dist/services/DnaEditorService.d.ts +31 -0
- package/dist/services/DnaEditorService.d.ts.map +1 -0
- package/dist/services/DnaEditorService.js +198 -0
- package/dist/services/DnaIndexService.d.ts +24 -0
- package/dist/services/DnaIndexService.d.ts.map +1 -0
- package/dist/services/DnaIndexService.js +131 -0
- package/dist/services/HealingAdvisory.d.ts +22 -0
- package/dist/services/HealingAdvisory.d.ts.map +1 -0
- package/dist/services/HealingAdvisory.js +42 -0
- package/dist/services/HealthReportService.d.ts +10 -0
- package/dist/services/HealthReportService.d.ts.map +1 -0
- package/dist/services/HealthReportService.js +84 -0
- package/dist/services/InitializerUpdater.d.ts +16 -0
- package/dist/services/InitializerUpdater.d.ts.map +1 -0
- package/dist/services/InitializerUpdater.js +37 -0
- package/dist/services/IntentAuditor.d.ts +39 -0
- package/dist/services/IntentAuditor.d.ts.map +1 -0
- package/dist/services/IntentAuditor.js +302 -0
- package/dist/services/LLMService.d.ts +100 -0
- package/dist/services/LLMService.d.ts.map +1 -0
- package/dist/services/LLMService.js +439 -0
- package/dist/services/SafetyGuard.d.ts +65 -0
- package/dist/services/SafetyGuard.d.ts.map +1 -0
- package/dist/services/SafetyGuard.js +524 -0
- package/dist/services/SnapshotService.d.ts +11 -0
- package/dist/services/SnapshotService.d.ts.map +1 -0
- package/dist/services/SnapshotService.js +349 -0
- package/dist/services/SourceLinkService.d.ts +26 -0
- package/dist/services/SourceLinkService.d.ts.map +1 -0
- package/dist/services/SourceLinkService.js +156 -0
- package/dist/services/SourceUpdater.d.ts +45 -0
- package/dist/services/SourceUpdater.d.ts.map +1 -0
- package/dist/services/SourceUpdater.js +1021 -0
- package/dist/services/TemplateDiffService.d.ts +25 -0
- package/dist/services/TemplateDiffService.d.ts.map +1 -0
- package/dist/services/TemplateDiffService.js +331 -0
- package/dist/services/types.d.ts +59 -0
- package/dist/services/types.d.ts.map +1 -0
- package/dist/services/types.js +2 -0
- package/dist/storage/SnapshotManager.d.ts +5 -0
- package/dist/storage/SnapshotManager.d.ts.map +1 -0
- package/dist/storage/SnapshotManager.js +31 -0
- package/dist/types/index.d.ts +95 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +7 -0
- package/dist/utils/DOMUtils.d.ts +33 -0
- package/dist/utils/DOMUtils.d.ts.map +1 -0
- package/dist/utils/DOMUtils.js +179 -0
- package/dist/utils/StackUtils.d.ts +11 -0
- package/dist/utils/StackUtils.d.ts.map +1 -0
- package/dist/utils/StackUtils.js +120 -0
- package/dist/vendor/enquirer.d.ts +33 -0
- package/dist/vendor/enquirer.d.ts.map +1 -0
- package/dist/vendor/enquirer.js +11 -0
- package/package.json +67 -0
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.CrossFileHealer = void 0;
|
|
37
|
+
const path = __importStar(require("path"));
|
|
38
|
+
const fs = __importStar(require("fs"));
|
|
39
|
+
const DefinitionTracer_js_1 = require("./DefinitionTracer.js");
|
|
40
|
+
const InitializerUpdater_js_1 = require("./InitializerUpdater.js");
|
|
41
|
+
// ═══════════════════════════════════════════════════════════════════
|
|
42
|
+
// CrossFileHealer (T-9)
|
|
43
|
+
//
|
|
44
|
+
// Resolves an imported symbol to its definition in an external file,
|
|
45
|
+
// then delegates mutation to InitializerUpdater.
|
|
46
|
+
//
|
|
47
|
+
// Entry point: CrossFileHealer.heal()
|
|
48
|
+
// modulePath — the import specifier string (e.g. "./constants")
|
|
49
|
+
// symbolName — the exported identifier name (e.g. "SHARED_SEL")
|
|
50
|
+
// oldValue — the current string value (used for verification)
|
|
51
|
+
// newValue — the replacement value
|
|
52
|
+
// callerFilePath — absolute path of the file containing the import
|
|
53
|
+
// project — the shared ASTSourceUpdater Project instance
|
|
54
|
+
// ═══════════════════════════════════════════════════════════════════
|
|
55
|
+
const DEPTH_GUARD = 3;
|
|
56
|
+
class CrossFileHealer {
|
|
57
|
+
static heal(modulePath, symbolName, newValue, callerFilePath, project, depth = 0) {
|
|
58
|
+
if (depth >= DEPTH_GUARD) {
|
|
59
|
+
return {
|
|
60
|
+
success: false,
|
|
61
|
+
reason: `CrossFileHealer: re-export chain depth exceeded for "${symbolName}"`,
|
|
62
|
+
strategy: "semantic-cross-file",
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
const externalPath = resolveModulePath(modulePath, callerFilePath);
|
|
66
|
+
if (!externalPath) {
|
|
67
|
+
return {
|
|
68
|
+
success: false,
|
|
69
|
+
reason: `CrossFileHealer: module "${modulePath}" not found relative to "${path.basename(callerFilePath)}"`,
|
|
70
|
+
strategy: "semantic-cross-file",
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
// Load external file into the shared project.
|
|
74
|
+
let externalSf = project.getSourceFile(externalPath);
|
|
75
|
+
if (externalSf) {
|
|
76
|
+
externalSf.refreshFromFileSystemSync();
|
|
77
|
+
}
|
|
78
|
+
else {
|
|
79
|
+
try {
|
|
80
|
+
externalSf = project.addSourceFileAtPath(externalPath);
|
|
81
|
+
}
|
|
82
|
+
catch (e) {
|
|
83
|
+
return {
|
|
84
|
+
success: false,
|
|
85
|
+
reason: `CrossFileHealer: failed to load "${path.basename(externalPath)}": ${e.message}`,
|
|
86
|
+
strategy: "semantic-cross-file",
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
const traceResult = DefinitionTracer_js_1.DefinitionTracer.traceByName(symbolName, externalSf);
|
|
91
|
+
if (!traceResult.found) {
|
|
92
|
+
// Follow re-export chains (barrel files).
|
|
93
|
+
if (traceResult.crossFile) {
|
|
94
|
+
console.log(`[CrossFileHealer] 🔄 "${symbolName}" re-exported from "${traceResult.modulePath}" — following chain`);
|
|
95
|
+
return CrossFileHealer.heal(traceResult.modulePath, traceResult.symbolName, newValue, externalPath, project, depth + 1);
|
|
96
|
+
}
|
|
97
|
+
return {
|
|
98
|
+
success: false,
|
|
99
|
+
reason: `CrossFileHealer: ${traceResult.reason}`,
|
|
100
|
+
strategy: "semantic-cross-file",
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
const result = InitializerUpdater_js_1.InitializerUpdater.apply(traceResult.targetNode, traceResult.targetFile, newValue);
|
|
104
|
+
if (!result) {
|
|
105
|
+
return {
|
|
106
|
+
success: false,
|
|
107
|
+
reason: `CrossFileHealer: InitializerUpdater rejected node type ${traceResult.targetNode.getKindName()}`,
|
|
108
|
+
strategy: "semantic-cross-file",
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
console.log(`[CrossFileHealer] ✅ healed "${symbolName}" in ${path.basename(externalPath)} ` +
|
|
112
|
+
`@ line ${result.lineUpdated + 1}`);
|
|
113
|
+
return {
|
|
114
|
+
success: true,
|
|
115
|
+
reason: `definition-site heal in ${path.basename(externalPath)}`,
|
|
116
|
+
strategy: "semantic-cross-file",
|
|
117
|
+
lineUpdated: result.lineUpdated,
|
|
118
|
+
healedLocatorString: newValue,
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
exports.CrossFileHealer = CrossFileHealer;
|
|
123
|
+
// ─────────────────────────────────────────────────────────────────
|
|
124
|
+
// Private helpers
|
|
125
|
+
// ─────────────────────────────────────────────────────────────────
|
|
126
|
+
function resolveModulePath(modulePath, fromFile) {
|
|
127
|
+
const base = path.dirname(path.resolve(fromFile));
|
|
128
|
+
const resolved = path.resolve(base, modulePath);
|
|
129
|
+
for (const candidate of [resolved, resolved + ".ts", resolved + ".js"]) {
|
|
130
|
+
if (fs.existsSync(candidate))
|
|
131
|
+
return candidate;
|
|
132
|
+
}
|
|
133
|
+
return null;
|
|
134
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { Node, SourceFile, StringLiteral, NoSubstitutionTemplateLiteral, TemplateExpression } from "ts-morph";
|
|
2
|
+
export type TraceResult = {
|
|
3
|
+
found: true;
|
|
4
|
+
targetNode: Node;
|
|
5
|
+
targetFile: SourceFile;
|
|
6
|
+
/** The enclosing VariableDeclaration or PropertyAssignment — used by BlastRadiusAnalyzer. */
|
|
7
|
+
declarationNode?: Node;
|
|
8
|
+
} | {
|
|
9
|
+
found: false;
|
|
10
|
+
crossFile: true;
|
|
11
|
+
modulePath: string;
|
|
12
|
+
symbolName: string;
|
|
13
|
+
} | {
|
|
14
|
+
found: false;
|
|
15
|
+
crossFile: false;
|
|
16
|
+
reason: string;
|
|
17
|
+
};
|
|
18
|
+
export type FunctionalAnalysis = {
|
|
19
|
+
kind: "template";
|
|
20
|
+
templateNode: TemplateExpression;
|
|
21
|
+
bindings: Map<string, string>;
|
|
22
|
+
functionFile: SourceFile;
|
|
23
|
+
functionLine: number;
|
|
24
|
+
} | {
|
|
25
|
+
kind: "literal";
|
|
26
|
+
literalNode: StringLiteral | NoSubstitutionTemplateLiteral;
|
|
27
|
+
functionFile: SourceFile;
|
|
28
|
+
functionLine: number;
|
|
29
|
+
} | {
|
|
30
|
+
kind: "complex";
|
|
31
|
+
reason: string;
|
|
32
|
+
};
|
|
33
|
+
export declare class DefinitionTracer {
|
|
34
|
+
static traceIdentifier(identifierNode: Node, sourceFile: SourceFile, callerLine: number, depth?: number): TraceResult;
|
|
35
|
+
static tracePropertyAccess(propAccessNode: Node, sourceFile: SourceFile, callerLine: number): TraceResult;
|
|
36
|
+
static traceByName(symbolName: string, sourceFile: SourceFile): TraceResult;
|
|
37
|
+
}
|
|
38
|
+
export declare class FunctionalReturnAnalyzer {
|
|
39
|
+
static analyze(callNode: Node, sourceFile: SourceFile): FunctionalAnalysis;
|
|
40
|
+
}
|
|
41
|
+
//# sourceMappingURL=DefinitionTracer.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"DefinitionTracer.d.ts","sourceRoot":"","sources":["../../src/services/DefinitionTracer.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,IAAI,EACJ,UAAU,EAKV,aAAa,EACb,6BAA6B,EAC7B,kBAAkB,EAGnB,MAAM,UAAU,CAAC;AAMlB,MAAM,MAAM,WAAW,GACnB;IACE,KAAK,EAAE,IAAI,CAAC;IACZ,UAAU,EAAE,IAAI,CAAC;IACjB,UAAU,EAAE,UAAU,CAAC;IACvB,6FAA6F;IAC7F,eAAe,CAAC,EAAE,IAAI,CAAC;CACxB,GACD;IAAE,KAAK,EAAE,KAAK,CAAC;IAAC,SAAS,EAAE,IAAI,CAAC;IAAC,UAAU,EAAE,MAAM,CAAC;IAAC,UAAU,EAAE,MAAM,CAAA;CAAE,GACzE;IAAE,KAAK,EAAE,KAAK,CAAC;IAAC,SAAS,EAAE,KAAK,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC;AAEvD,MAAM,MAAM,kBAAkB,GAC1B;IACE,IAAI,EAAE,UAAU,CAAC;IACjB,YAAY,EAAE,kBAAkB,CAAC;IACjC,QAAQ,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC9B,YAAY,EAAE,UAAU,CAAC;IACzB,YAAY,EAAE,MAAM,CAAC;CACtB,GACD;IACE,IAAI,EAAE,SAAS,CAAC;IAChB,WAAW,EAAE,aAAa,GAAG,6BAA6B,CAAC;IAC3D,YAAY,EAAE,UAAU,CAAC;IACzB,YAAY,EAAE,MAAM,CAAC;CACtB,GACD;IAAE,IAAI,EAAE,SAAS,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC;AAcxC,qBAAa,gBAAgB;IAK3B,MAAM,CAAC,eAAe,CACpB,cAAc,EAAE,IAAI,EACpB,UAAU,EAAE,UAAU,EACtB,UAAU,EAAE,MAAM,EAClB,KAAK,SAAI,GACR,WAAW;IAqFd,MAAM,CAAC,mBAAmB,CACxB,cAAc,EAAE,IAAI,EACpB,UAAU,EAAE,UAAU,EACtB,UAAU,EAAE,MAAM,GACjB,WAAW;IA0Ed,MAAM,CAAC,WAAW,CAAC,UAAU,EAAE,MAAM,EAAE,UAAU,EAAE,UAAU,GAAG,WAAW;CA6B5E;AASD,qBAAa,wBAAwB;IACnC,MAAM,CAAC,OAAO,CAAC,QAAQ,EAAE,IAAI,EAAE,UAAU,EAAE,UAAU,GAAG,kBAAkB;CAiF3E"}
|
|
@@ -0,0 +1,350 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.FunctionalReturnAnalyzer = exports.DefinitionTracer = void 0;
|
|
4
|
+
const ts_morph_1 = require("ts-morph");
|
|
5
|
+
// ═══════════════════════════════════════════════════════════════════
|
|
6
|
+
// DefinitionTracer
|
|
7
|
+
//
|
|
8
|
+
// T-5: traceIdentifier — follow bare variable name to its initializer.
|
|
9
|
+
// T-6: tracePropertyAccess — follow OBJECT.prop to the property value.
|
|
10
|
+
//
|
|
11
|
+
// In-file first; signals crossFile when symbol comes from an import.
|
|
12
|
+
// Never does I/O beyond what ts-morph provides on the given SourceFile.
|
|
13
|
+
// ═══════════════════════════════════════════════════════════════════
|
|
14
|
+
const MAX_TRACE_DEPTH = 5;
|
|
15
|
+
class DefinitionTracer {
|
|
16
|
+
// ─────────────────────────────────────────────────────────────────
|
|
17
|
+
// T-5: Identifier trace
|
|
18
|
+
// ─────────────────────────────────────────────────────────────────
|
|
19
|
+
static traceIdentifier(identifierNode, sourceFile, callerLine, depth = 0) {
|
|
20
|
+
if (depth >= MAX_TRACE_DEPTH) {
|
|
21
|
+
return {
|
|
22
|
+
found: false,
|
|
23
|
+
crossFile: false,
|
|
24
|
+
reason: "max trace depth reached",
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
const symbolName = identifierNode.getText().trim();
|
|
28
|
+
// 1. In-file: find closest VariableDeclaration before callerLine.
|
|
29
|
+
const decl = findVarDeclInFile(sourceFile, symbolName, callerLine);
|
|
30
|
+
if (decl) {
|
|
31
|
+
const init = decl.getInitializer();
|
|
32
|
+
if (!init) {
|
|
33
|
+
return {
|
|
34
|
+
found: false,
|
|
35
|
+
crossFile: false,
|
|
36
|
+
reason: `"${symbolName}" has no initializer`,
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
// Recursive follow: if init is another identifier, trace again.
|
|
40
|
+
if (ts_morph_1.Node.isIdentifier(init)) {
|
|
41
|
+
return DefinitionTracer.traceIdentifier(init, sourceFile, decl.getStartLineNumber(), depth + 1);
|
|
42
|
+
}
|
|
43
|
+
console.log(`[DefinitionTracer] ✅ traceIdentifier: "${symbolName}" → ` +
|
|
44
|
+
`${init.getKindName()} @ ${sourceFile.getBaseName()}:${init.getStartLineNumber()}`);
|
|
45
|
+
return { found: true, targetNode: init, targetFile: sourceFile, declarationNode: decl };
|
|
46
|
+
}
|
|
47
|
+
// 2. Symbol not in file — check import declarations.
|
|
48
|
+
const imported = findImportedSymbol(sourceFile, symbolName);
|
|
49
|
+
if (imported) {
|
|
50
|
+
console.log(`[DefinitionTracer] 🌐 traceIdentifier: "${symbolName}" comes from "${imported.modulePath}" (cross-file)`);
|
|
51
|
+
return { found: false, crossFile: true, ...imported };
|
|
52
|
+
}
|
|
53
|
+
// 3. Last resort: ts-morph language-service definition lookup.
|
|
54
|
+
try {
|
|
55
|
+
const defNodes = identifierNode.getDefinitionNodes?.();
|
|
56
|
+
if (defNodes && defNodes.length > 0) {
|
|
57
|
+
for (const def of defNodes) {
|
|
58
|
+
if (ts_morph_1.Node.isVariableDeclaration(def)) {
|
|
59
|
+
const init = def.getInitializer();
|
|
60
|
+
if (init) {
|
|
61
|
+
console.log(`[DefinitionTracer] ✅ traceIdentifier (via LangSvc): "${symbolName}" → ` +
|
|
62
|
+
`${init.getKindName()} @ ${def.getSourceFile().getBaseName()}:${init.getStartLineNumber()}`);
|
|
63
|
+
return {
|
|
64
|
+
found: true,
|
|
65
|
+
targetNode: init,
|
|
66
|
+
targetFile: def.getSourceFile(),
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
catch {
|
|
74
|
+
// Language service unavailable — not an error.
|
|
75
|
+
}
|
|
76
|
+
return {
|
|
77
|
+
found: false,
|
|
78
|
+
crossFile: false,
|
|
79
|
+
reason: `"${symbolName}" not resolved in file or imports`,
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
// ─────────────────────────────────────────────────────────────────
|
|
83
|
+
// T-6: PropertyAccessExpression trace
|
|
84
|
+
// ─────────────────────────────────────────────────────────────────
|
|
85
|
+
static tracePropertyAccess(propAccessNode, sourceFile, callerLine) {
|
|
86
|
+
if (!ts_morph_1.Node.isPropertyAccessExpression(propAccessNode)) {
|
|
87
|
+
return {
|
|
88
|
+
found: false,
|
|
89
|
+
crossFile: false,
|
|
90
|
+
reason: "node is not a PropertyAccessExpression",
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
const expr = propAccessNode;
|
|
94
|
+
const objectName = expr.getExpression().getText().trim();
|
|
95
|
+
const propName = expr.getName();
|
|
96
|
+
// Find the object's VariableDeclaration.
|
|
97
|
+
const objDecl = findVarDeclInFile(sourceFile, objectName, callerLine);
|
|
98
|
+
if (!objDecl) {
|
|
99
|
+
const imported = findImportedSymbol(sourceFile, objectName);
|
|
100
|
+
if (imported) {
|
|
101
|
+
console.log(`[DefinitionTracer] 🌐 tracePropertyAccess: "${objectName}" comes from "${imported.modulePath}" (cross-file)`);
|
|
102
|
+
return { found: false, crossFile: true, ...imported };
|
|
103
|
+
}
|
|
104
|
+
return {
|
|
105
|
+
found: false,
|
|
106
|
+
crossFile: false,
|
|
107
|
+
reason: `object "${objectName}" not found in file or imports`,
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
const init = objDecl.getInitializer();
|
|
111
|
+
if (!init || !ts_morph_1.Node.isObjectLiteralExpression(init)) {
|
|
112
|
+
return {
|
|
113
|
+
found: false,
|
|
114
|
+
crossFile: false,
|
|
115
|
+
reason: `"${objectName}" initializer is not an ObjectLiteralExpression (got ${init?.getKindName() ?? "none"})`,
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
// ts-morph getProperty(name) — returns the first matching property.
|
|
119
|
+
const prop = init.getProperty(propName);
|
|
120
|
+
if (!prop || !ts_morph_1.Node.isPropertyAssignment(prop)) {
|
|
121
|
+
return {
|
|
122
|
+
found: false,
|
|
123
|
+
crossFile: false,
|
|
124
|
+
reason: `property "${propName}" not found on "${objectName}"`,
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
const valueNode = prop.getInitializer();
|
|
128
|
+
if (!valueNode) {
|
|
129
|
+
return {
|
|
130
|
+
found: false,
|
|
131
|
+
crossFile: false,
|
|
132
|
+
reason: `property "${propName}" on "${objectName}" has no initializer`,
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
console.log(`[DefinitionTracer] ✅ tracePropertyAccess: "${objectName}.${propName}" → ` +
|
|
136
|
+
`${valueNode.getKindName()} "${valueNode.getText()}" ` +
|
|
137
|
+
`@ ${sourceFile.getBaseName()}:${valueNode.getStartLineNumber()}`);
|
|
138
|
+
return { found: true, targetNode: valueNode, targetFile: sourceFile, declarationNode: prop };
|
|
139
|
+
}
|
|
140
|
+
// ─────────────────────────────────────────────────────────────────
|
|
141
|
+
// traceByName — used by CrossFileHealer
|
|
142
|
+
//
|
|
143
|
+
// Like traceIdentifier but takes a plain symbol name string instead
|
|
144
|
+
// of an Identifier node. Searches the entire file (no callerLine
|
|
145
|
+
// constraint) so it's suitable for resolving exported symbols.
|
|
146
|
+
// ─────────────────────────────────────────────────────────────────
|
|
147
|
+
static traceByName(symbolName, sourceFile) {
|
|
148
|
+
const decl = findVarDeclInFile(sourceFile, symbolName, Number.MAX_SAFE_INTEGER);
|
|
149
|
+
if (!decl) {
|
|
150
|
+
const imported = findImportedSymbol(sourceFile, symbolName);
|
|
151
|
+
if (imported)
|
|
152
|
+
return { found: false, crossFile: true, ...imported };
|
|
153
|
+
return {
|
|
154
|
+
found: false,
|
|
155
|
+
crossFile: false,
|
|
156
|
+
reason: `"${symbolName}" not found in ${sourceFile.getBaseName()}`,
|
|
157
|
+
};
|
|
158
|
+
}
|
|
159
|
+
const init = decl.getInitializer();
|
|
160
|
+
if (!init) {
|
|
161
|
+
return {
|
|
162
|
+
found: false,
|
|
163
|
+
crossFile: false,
|
|
164
|
+
reason: `"${symbolName}" has no initializer`,
|
|
165
|
+
};
|
|
166
|
+
}
|
|
167
|
+
console.log(`[DefinitionTracer] ✅ traceByName: "${symbolName}" → ` +
|
|
168
|
+
`${init.getKindName()} @ ${sourceFile.getBaseName()}:${init.getStartLineNumber()}`);
|
|
169
|
+
return { found: true, targetNode: init, targetFile: sourceFile, declarationNode: decl };
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
exports.DefinitionTracer = DefinitionTracer;
|
|
173
|
+
// ═══════════════════════════════════════════════════════════════════
|
|
174
|
+
// FunctionalReturnAnalyzer
|
|
175
|
+
//
|
|
176
|
+
// T-7: Given a CallExpression whose callee is a local function,
|
|
177
|
+
// enter the function body and return its return expression + arg bindings.
|
|
178
|
+
// ═══════════════════════════════════════════════════════════════════
|
|
179
|
+
class FunctionalReturnAnalyzer {
|
|
180
|
+
static analyze(callNode, sourceFile) {
|
|
181
|
+
if (!ts_morph_1.Node.isCallExpression(callNode)) {
|
|
182
|
+
return { kind: "complex", reason: "node is not a CallExpression" };
|
|
183
|
+
}
|
|
184
|
+
const call = callNode;
|
|
185
|
+
const calleeExpr = call.getExpression();
|
|
186
|
+
// Only handle simple identifier callees: getScopedInput(role).
|
|
187
|
+
// PropertyAccess callees (obj.method()) are out of scope for Phase 2.
|
|
188
|
+
if (!ts_morph_1.Node.isIdentifier(calleeExpr)) {
|
|
189
|
+
return {
|
|
190
|
+
kind: "complex",
|
|
191
|
+
reason: `callee is ${calleeExpr.getKindName()}, not a simple identifier`,
|
|
192
|
+
};
|
|
193
|
+
}
|
|
194
|
+
const calleeName = calleeExpr.getText();
|
|
195
|
+
const fnResult = findFunctionBody(sourceFile, calleeName);
|
|
196
|
+
if (!fnResult) {
|
|
197
|
+
return {
|
|
198
|
+
kind: "complex",
|
|
199
|
+
reason: `function "${calleeName}" not found in file`,
|
|
200
|
+
};
|
|
201
|
+
}
|
|
202
|
+
const { returnExpr, params, line } = fnResult;
|
|
203
|
+
// Build argument bindings: paramName → runtime value text.
|
|
204
|
+
const callArgs = call.getArguments();
|
|
205
|
+
const bindings = new Map();
|
|
206
|
+
for (let i = 0; i < params.length; i++) {
|
|
207
|
+
const argNode = callArgs[i];
|
|
208
|
+
if (!argNode)
|
|
209
|
+
continue;
|
|
210
|
+
const raw = argNode.getText();
|
|
211
|
+
// Strip surrounding quotes for string literals so bindings hold the value, not the syntax.
|
|
212
|
+
const value = ts_morph_1.Node.isStringLiteral(argNode)
|
|
213
|
+
? argNode.getLiteralValue()
|
|
214
|
+
: raw.replace(/^["'`]|["'`]$/g, "");
|
|
215
|
+
bindings.set(params[i], value);
|
|
216
|
+
}
|
|
217
|
+
if (ts_morph_1.Node.isStringLiteral(returnExpr) ||
|
|
218
|
+
ts_morph_1.Node.isNoSubstitutionTemplateLiteral(returnExpr)) {
|
|
219
|
+
console.log(`[FunctionalReturnAnalyzer] ✅ "${calleeName}" returns StringLiteral ` +
|
|
220
|
+
`"${returnExpr.getLiteralValue()}" @ line ${line}`);
|
|
221
|
+
return {
|
|
222
|
+
kind: "literal",
|
|
223
|
+
literalNode: returnExpr,
|
|
224
|
+
functionFile: sourceFile,
|
|
225
|
+
functionLine: line,
|
|
226
|
+
};
|
|
227
|
+
}
|
|
228
|
+
if (ts_morph_1.Node.isTemplateExpression(returnExpr)) {
|
|
229
|
+
const head = returnExpr
|
|
230
|
+
.getHead()
|
|
231
|
+
.getLiteralText();
|
|
232
|
+
console.log(`[FunctionalReturnAnalyzer] ✅ "${calleeName}" returns TemplateExpression ` +
|
|
233
|
+
`(head: "${head}") @ line ${line}. Bindings: ${JSON.stringify(Object.fromEntries(bindings))}`);
|
|
234
|
+
return {
|
|
235
|
+
kind: "template",
|
|
236
|
+
templateNode: returnExpr,
|
|
237
|
+
bindings,
|
|
238
|
+
functionFile: sourceFile,
|
|
239
|
+
functionLine: line,
|
|
240
|
+
};
|
|
241
|
+
}
|
|
242
|
+
return {
|
|
243
|
+
kind: "complex",
|
|
244
|
+
reason: `"${calleeName}" return expression is ${returnExpr.getKindName()} — not directly healable`,
|
|
245
|
+
};
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
exports.FunctionalReturnAnalyzer = FunctionalReturnAnalyzer;
|
|
249
|
+
// ═══════════════════════════════════════════════════════════════════
|
|
250
|
+
// Private helpers
|
|
251
|
+
// ═══════════════════════════════════════════════════════════════════
|
|
252
|
+
function findVarDeclInFile(sourceFile, varName, beforeLine) {
|
|
253
|
+
const text = sourceFile.getFullText();
|
|
254
|
+
const lines = text.split("\n");
|
|
255
|
+
let beforeOffset = 0;
|
|
256
|
+
for (let i = 0; i < Math.min(beforeLine - 1, lines.length); i++) {
|
|
257
|
+
beforeOffset += lines[i].length + 1;
|
|
258
|
+
}
|
|
259
|
+
let best = null;
|
|
260
|
+
let bestOffset = -1;
|
|
261
|
+
for (const decl of sourceFile.getDescendantsOfKind(ts_morph_1.SyntaxKind.VariableDeclaration)) {
|
|
262
|
+
if (decl.getName() !== varName)
|
|
263
|
+
continue;
|
|
264
|
+
const offset = decl.getStart();
|
|
265
|
+
if (offset < beforeOffset && offset > bestOffset) {
|
|
266
|
+
best = decl;
|
|
267
|
+
bestOffset = offset;
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
return best;
|
|
271
|
+
}
|
|
272
|
+
function findImportedSymbol(sourceFile, symbolName) {
|
|
273
|
+
for (const importDecl of sourceFile.getImportDeclarations()) {
|
|
274
|
+
const modulePath = importDecl.getModuleSpecifierValue();
|
|
275
|
+
// Default import: import Foo from '...'
|
|
276
|
+
const defaultImport = importDecl.getDefaultImport();
|
|
277
|
+
if (defaultImport?.getText() === symbolName) {
|
|
278
|
+
return { modulePath, symbolName };
|
|
279
|
+
}
|
|
280
|
+
// Named imports: import { Foo, Bar as B } from '...'
|
|
281
|
+
for (const named of importDecl.getNamedImports()) {
|
|
282
|
+
const localName = named.getAliasNode()?.getText() ?? named.getName();
|
|
283
|
+
if (localName === symbolName) {
|
|
284
|
+
return { modulePath, symbolName: named.getName() };
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
// Namespace import: import * as NS from '...'
|
|
288
|
+
const nsImport = importDecl.getNamespaceImport();
|
|
289
|
+
if (nsImport?.getText() === symbolName) {
|
|
290
|
+
return { modulePath, symbolName };
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
return null;
|
|
294
|
+
}
|
|
295
|
+
function findFunctionBody(sourceFile, fnName) {
|
|
296
|
+
// 1. FunctionDeclaration: function getScopedInput(...) { ... }
|
|
297
|
+
const fnDecl = sourceFile.getFunction(fnName);
|
|
298
|
+
if (fnDecl) {
|
|
299
|
+
const params = fnDecl.getParameters().map((p) => p.getName());
|
|
300
|
+
const body = fnDecl.getBody();
|
|
301
|
+
if (!body)
|
|
302
|
+
return null;
|
|
303
|
+
const returnExpr = extractReturnExpression(body);
|
|
304
|
+
if (!returnExpr)
|
|
305
|
+
return null;
|
|
306
|
+
return { returnExpr, params, line: fnDecl.getStartLineNumber() };
|
|
307
|
+
}
|
|
308
|
+
// 2. const fnName = (...) => expr or const fnName = function(...) { ... }
|
|
309
|
+
for (const decl of sourceFile.getDescendantsOfKind(ts_morph_1.SyntaxKind.VariableDeclaration)) {
|
|
310
|
+
if (decl.getName() !== fnName)
|
|
311
|
+
continue;
|
|
312
|
+
const init = decl.getInitializer();
|
|
313
|
+
if (!init)
|
|
314
|
+
continue;
|
|
315
|
+
if (ts_morph_1.Node.isArrowFunction(init)) {
|
|
316
|
+
const arrow = init;
|
|
317
|
+
const params = arrow.getParameters().map((p) => p.getName());
|
|
318
|
+
const body = arrow.getBody();
|
|
319
|
+
// Concise body: (role) => `...` — body IS the return expression.
|
|
320
|
+
if (!ts_morph_1.Node.isBlock(body)) {
|
|
321
|
+
return { returnExpr: body, params, line: decl.getStartLineNumber() };
|
|
322
|
+
}
|
|
323
|
+
const returnExpr = extractReturnExpression(body);
|
|
324
|
+
if (!returnExpr)
|
|
325
|
+
return null;
|
|
326
|
+
return { returnExpr, params, line: decl.getStartLineNumber() };
|
|
327
|
+
}
|
|
328
|
+
if (ts_morph_1.Node.isFunctionExpression(init)) {
|
|
329
|
+
const fnExpr = init;
|
|
330
|
+
const params = fnExpr.getParameters().map((p) => p.getName());
|
|
331
|
+
const body = fnExpr.getBody();
|
|
332
|
+
const returnExpr = extractReturnExpression(body);
|
|
333
|
+
if (!returnExpr)
|
|
334
|
+
return null;
|
|
335
|
+
return { returnExpr, params, line: decl.getStartLineNumber() };
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
return null;
|
|
339
|
+
}
|
|
340
|
+
function extractReturnExpression(body) {
|
|
341
|
+
// Concise arrow body — the node itself is the return value.
|
|
342
|
+
if (!ts_morph_1.Node.isBlock(body))
|
|
343
|
+
return body;
|
|
344
|
+
// Block body: search for ReturnStatement.
|
|
345
|
+
const returnStmts = body.getDescendantsOfKind(ts_morph_1.SyntaxKind.ReturnStatement);
|
|
346
|
+
if (returnStmts.length === 0)
|
|
347
|
+
return null;
|
|
348
|
+
// Use the first one — for conditional returns we signal COMPLEX upstream.
|
|
349
|
+
return returnStmts[0].getExpression() ?? null;
|
|
350
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import type { ElementSnapshotV2 } from '../types/index.js';
|
|
2
|
+
export interface ValidationResult {
|
|
3
|
+
valid: boolean;
|
|
4
|
+
errors: string[];
|
|
5
|
+
}
|
|
6
|
+
export interface DnaSnapshotExtended extends ElementSnapshotV2 {
|
|
7
|
+
selector: string;
|
|
8
|
+
testTitle?: string;
|
|
9
|
+
sourceFile?: string;
|
|
10
|
+
sourceLine?: number;
|
|
11
|
+
lastHealed?: string | null;
|
|
12
|
+
status?: 'healed' | 'broken';
|
|
13
|
+
originalSelector?: string;
|
|
14
|
+
healedSelector?: string;
|
|
15
|
+
}
|
|
16
|
+
export type DnaPatch = Partial<DnaSnapshotExtended>;
|
|
17
|
+
export interface IDnaEditorService {
|
|
18
|
+
read(key: string): Promise<DnaSnapshotExtended>;
|
|
19
|
+
write(key: string, patch: DnaPatch): Promise<string>;
|
|
20
|
+
rollback(key: string): Promise<void>;
|
|
21
|
+
validate(candidate: unknown): ValidationResult;
|
|
22
|
+
}
|
|
23
|
+
export declare class DnaEditorService implements IDnaEditorService {
|
|
24
|
+
private readonly snapshotDir;
|
|
25
|
+
constructor(snapshotDir: string);
|
|
26
|
+
read(key: string): Promise<DnaSnapshotExtended>;
|
|
27
|
+
write(key: string, patch: DnaPatch): Promise<string>;
|
|
28
|
+
rollback(key: string): Promise<void>;
|
|
29
|
+
validate(candidate: unknown): ValidationResult;
|
|
30
|
+
}
|
|
31
|
+
//# sourceMappingURL=DnaEditorService.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"DnaEditorService.d.ts","sourceRoot":"","sources":["../../src/services/DnaEditorService.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAC;AAE3D,MAAM,WAAW,gBAAgB;IAC/B,KAAK,EAAE,OAAO,CAAC;IACf,MAAM,EAAE,MAAM,EAAE,CAAC;CAClB;AAID,MAAM,WAAW,mBAAoB,SAAQ,iBAAiB;IAC5D,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,UAAU,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,MAAM,CAAC,EAAE,QAAQ,GAAG,QAAQ,CAAC;IAC7B,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB;AAED,MAAM,MAAM,QAAQ,GAAG,OAAO,CAAC,mBAAmB,CAAC,CAAC;AAEpD,MAAM,WAAW,iBAAiB;IAChC,IAAI,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,mBAAmB,CAAC,CAAC;IAChD,KAAK,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,QAAQ,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;IACrD,QAAQ,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACrC,QAAQ,CAAC,SAAS,EAAE,OAAO,GAAG,gBAAgB,CAAC;CAChD;AA4BD,qBAAa,gBAAiB,YAAW,iBAAiB;IAC5C,OAAO,CAAC,QAAQ,CAAC,WAAW;gBAAX,WAAW,EAAE,MAAM;IAE1C,IAAI,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,mBAAmB,CAAC;IAgB/C,KAAK,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,QAAQ,GAAG,OAAO,CAAC,MAAM,CAAC;IAwCpD,QAAQ,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAa1C,QAAQ,CAAC,SAAS,EAAE,OAAO,GAAG,gBAAgB;CAoG/C"}
|