sela-core 0.1.0-alpha.41 → 0.1.0-alpha.43

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.
Files changed (133) hide show
  1. package/dist/cli/commands/dnaSync.d.ts +3 -0
  2. package/dist/cli/commands/dnaSync.d.ts.map +1 -0
  3. package/dist/cli/commands/dnaSync.js +200 -0
  4. package/dist/cli/commands/init.d.ts.map +1 -1
  5. package/dist/cli/commands/init.js +51 -23
  6. package/dist/cli/commands/sync.d.ts.map +1 -1
  7. package/dist/cli/commands/sync.js +18 -0
  8. package/dist/cli/conventionsInit.d.ts +6 -0
  9. package/dist/cli/conventionsInit.d.ts.map +1 -1
  10. package/dist/cli/conventionsInit.js +2 -1
  11. package/dist/cli/index.js +3 -0
  12. package/dist/config/ConfigLoader.d.ts.map +1 -1
  13. package/dist/config/ConfigLoader.js +70 -29
  14. package/dist/config/Conventions.d.ts +8 -2
  15. package/dist/config/Conventions.d.ts.map +1 -1
  16. package/dist/config/Conventions.js +6 -3
  17. package/dist/config/HealingGovernance.d.ts +35 -0
  18. package/dist/config/HealingGovernance.d.ts.map +1 -0
  19. package/dist/config/HealingGovernance.js +187 -0
  20. package/dist/config/PathScopeGuard.d.ts +8 -0
  21. package/dist/config/PathScopeGuard.d.ts.map +1 -1
  22. package/dist/config/PathScopeGuard.js +4 -1
  23. package/dist/config/SelaConfig.d.ts +97 -1
  24. package/dist/config/SelaConfig.d.ts.map +1 -1
  25. package/dist/config/SelaConfig.js +84 -1
  26. package/dist/engine/HealingRegistry.d.ts +13 -0
  27. package/dist/engine/HealingRegistry.d.ts.map +1 -1
  28. package/dist/engine/HealingRegistry.js +21 -0
  29. package/dist/engine/SelaEngine.d.ts +59 -3
  30. package/dist/engine/SelaEngine.d.ts.map +1 -1
  31. package/dist/engine/SelaEngine.js +699 -36
  32. package/dist/engine/healing/AncestryValidator.d.ts +68 -0
  33. package/dist/engine/healing/AncestryValidator.d.ts.map +1 -0
  34. package/dist/engine/healing/AncestryValidator.js +57 -0
  35. package/dist/engine/healing/MethodParityGuard.d.ts +48 -0
  36. package/dist/engine/healing/MethodParityGuard.d.ts.map +1 -0
  37. package/dist/engine/healing/MethodParityGuard.js +204 -0
  38. package/dist/engine/lifecycle/SelaLifecycleStateManager.d.ts +124 -0
  39. package/dist/engine/lifecycle/SelaLifecycleStateManager.d.ts.map +1 -0
  40. package/dist/engine/lifecycle/SelaLifecycleStateManager.js +241 -0
  41. package/dist/engine/singleton.d.ts +2 -0
  42. package/dist/engine/singleton.d.ts.map +1 -1
  43. package/dist/engine/singleton.js +43 -1
  44. package/dist/errors/FailureClassifier.d.ts +62 -0
  45. package/dist/errors/FailureClassifier.d.ts.map +1 -0
  46. package/dist/errors/FailureClassifier.js +280 -0
  47. package/dist/errors/SelectorForensics.d.ts +38 -0
  48. package/dist/errors/SelectorForensics.d.ts.map +1 -0
  49. package/dist/errors/SelectorForensics.js +282 -0
  50. package/dist/fixtures/ancestryHeal.d.ts +42 -0
  51. package/dist/fixtures/ancestryHeal.d.ts.map +1 -0
  52. package/dist/fixtures/ancestryHeal.js +190 -0
  53. package/dist/fixtures/errorClassification.d.ts +21 -0
  54. package/dist/fixtures/errorClassification.d.ts.map +1 -0
  55. package/dist/fixtures/errorClassification.js +28 -0
  56. package/dist/fixtures/expectProxy.d.ts +31 -7
  57. package/dist/fixtures/expectProxy.d.ts.map +1 -1
  58. package/dist/fixtures/expectProxy.js +332 -127
  59. package/dist/fixtures/index.d.ts +3 -2
  60. package/dist/fixtures/index.d.ts.map +1 -1
  61. package/dist/fixtures/index.js +242 -65
  62. package/dist/fixtures/lineage.d.ts +99 -0
  63. package/dist/fixtures/lineage.d.ts.map +1 -0
  64. package/dist/fixtures/lineage.js +259 -0
  65. package/dist/fixtures/moduleExpect.d.ts +2 -1
  66. package/dist/fixtures/moduleExpect.d.ts.map +1 -1
  67. package/dist/fixtures/moduleExpect.js +32 -23
  68. package/dist/index.cjs +26 -0
  69. package/dist/index.d.cts +3 -0
  70. package/dist/index.d.cts.map +1 -0
  71. package/dist/index.d.mts.map +1 -0
  72. package/dist/index.d.ts +3 -1
  73. package/dist/index.d.ts.map +1 -1
  74. package/dist/index.esm.d.mts +12 -0
  75. package/dist/index.esm.d.mts.map +1 -0
  76. package/dist/index.esm.mjs +36 -0
  77. package/dist/index.js +9 -2
  78. package/dist/services/ConventionsScanner.d.ts.map +1 -1
  79. package/dist/services/ConventionsScanner.js +2 -12
  80. package/dist/services/DnaEditorService.d.ts.map +1 -1
  81. package/dist/services/DnaEditorService.js +3 -2
  82. package/dist/services/DnaSyncService.d.ts +77 -0
  83. package/dist/services/DnaSyncService.d.ts.map +1 -0
  84. package/dist/services/DnaSyncService.js +484 -0
  85. package/dist/services/HealJournal.d.ts +22 -0
  86. package/dist/services/HealJournal.d.ts.map +1 -0
  87. package/dist/services/HealJournal.js +127 -0
  88. package/dist/services/HealReportService.d.ts +6 -0
  89. package/dist/services/HealReportService.d.ts.map +1 -1
  90. package/dist/services/HealReportService.js +106 -10
  91. package/dist/services/HealingCacheService.d.ts +7 -0
  92. package/dist/services/HealingCacheService.d.ts.map +1 -1
  93. package/dist/services/HealingCacheService.js +21 -0
  94. package/dist/services/LLMService.d.ts.map +1 -1
  95. package/dist/services/LLMService.js +133 -7
  96. package/dist/services/LedgerAnchor.d.ts +35 -0
  97. package/dist/services/LedgerAnchor.d.ts.map +1 -0
  98. package/dist/services/LedgerAnchor.js +154 -0
  99. package/dist/services/SafetyGuard.d.ts +2 -0
  100. package/dist/services/SafetyGuard.d.ts.map +1 -1
  101. package/dist/services/SmartDOMPruner.d.ts +58 -27
  102. package/dist/services/SmartDOMPruner.d.ts.map +1 -1
  103. package/dist/services/SmartDOMPruner.js +545 -122
  104. package/dist/services/SnapshotService.d.ts +28 -4
  105. package/dist/services/SnapshotService.d.ts.map +1 -1
  106. package/dist/services/SnapshotService.js +244 -11
  107. package/dist/services/SourceUpdater.d.ts.map +1 -1
  108. package/dist/services/SourceUpdater.js +25 -0
  109. package/dist/services/confidence/BusinessContextParser.d.ts +15 -1
  110. package/dist/services/confidence/BusinessContextParser.d.ts.map +1 -1
  111. package/dist/services/confidence/BusinessContextParser.js +54 -0
  112. package/dist/services/confidence/ConfidenceVectorEngine.d.ts.map +1 -1
  113. package/dist/services/confidence/ConfidenceVectorEngine.js +5 -1
  114. package/dist/services/confidence/types.d.ts +18 -0
  115. package/dist/services/confidence/types.d.ts.map +1 -1
  116. package/dist/services/safety/rules/ConfidenceVectorRule.d.ts.map +1 -1
  117. package/dist/services/safety/rules/ConfidenceVectorRule.js +1 -0
  118. package/dist/types/index.d.ts +202 -4
  119. package/dist/types/index.d.ts.map +1 -1
  120. package/dist/types/index.js +6 -1
  121. package/dist/utils/DOMUtils.d.ts +10 -0
  122. package/dist/utils/DOMUtils.d.ts.map +1 -1
  123. package/dist/utils/DOMUtils.js +47 -26
  124. package/dist/utils/StackUtils.d.ts.map +1 -1
  125. package/dist/utils/StackUtils.js +10 -1
  126. package/dist/utils/implicitAriaRole.d.ts +5 -0
  127. package/dist/utils/implicitAriaRole.d.ts.map +1 -0
  128. package/dist/utils/implicitAriaRole.js +113 -0
  129. package/dist/utils/sourcePathGuard.d.ts +10 -0
  130. package/dist/utils/sourcePathGuard.d.ts.map +1 -0
  131. package/dist/utils/sourcePathGuard.js +59 -0
  132. package/dist/vendor/chart.umd.min.js +14 -0
  133. package/package.json +19 -7
@@ -0,0 +1,3 @@
1
+ import type { Command } from "commander";
2
+ export declare function registerDnaSync(dna: Command): void;
3
+ //# sourceMappingURL=dnaSync.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dnaSync.d.ts","sourceRoot":"","sources":["../../../src/cli/commands/dnaSync.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAmCzC,wBAAgB,eAAe,CAAC,GAAG,EAAE,OAAO,GAAG,IAAI,CA+JlD"}
@@ -0,0 +1,200 @@
1
+ "use strict";
2
+ // src/cli/commands/dnaSync.ts
3
+ //
4
+ // The PR & Sync Engine CLI surface (Blueprint B):
5
+ // sela dna sync — reconcile the ledger against merged source
6
+ // sela dna status — read-only drift report (CI gate exit codes)
7
+ // sela dna clear-tombstone — human override for a rejected-selector memory
8
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
9
+ if (k2 === undefined) k2 = k;
10
+ var desc = Object.getOwnPropertyDescriptor(m, k);
11
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
12
+ desc = { enumerable: true, get: function() { return m[k]; } };
13
+ }
14
+ Object.defineProperty(o, k2, desc);
15
+ }) : (function(o, m, k, k2) {
16
+ if (k2 === undefined) k2 = k;
17
+ o[k2] = m[k];
18
+ }));
19
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
20
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
21
+ }) : function(o, v) {
22
+ o["default"] = v;
23
+ });
24
+ var __importStar = (this && this.__importStar) || (function () {
25
+ var ownKeys = function(o) {
26
+ ownKeys = Object.getOwnPropertyNames || function (o) {
27
+ var ar = [];
28
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
29
+ return ar;
30
+ };
31
+ return ownKeys(o);
32
+ };
33
+ return function (mod) {
34
+ if (mod && mod.__esModule) return mod;
35
+ var result = {};
36
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
37
+ __setModuleDefault(result, mod);
38
+ return result;
39
+ };
40
+ })();
41
+ var __importDefault = (this && this.__importDefault) || function (mod) {
42
+ return (mod && mod.__esModule) ? mod : { "default": mod };
43
+ };
44
+ Object.defineProperty(exports, "__esModule", { value: true });
45
+ exports.registerDnaSync = registerDnaSync;
46
+ const chalk_1 = __importDefault(require("chalk"));
47
+ const fs = __importStar(require("fs"));
48
+ const path = __importStar(require("path"));
49
+ const ConfigLoader_js_1 = require("../../config/ConfigLoader.js");
50
+ const DnaSyncService_js_1 = require("../../services/DnaSyncService.js");
51
+ const SnapshotService_js_1 = require("../../services/SnapshotService.js");
52
+ const HealingCacheService_js_1 = require("../../services/HealingCacheService.js");
53
+ const LedgerAnchor_js_1 = require("../../services/LedgerAnchor.js");
54
+ const ProgressReporter_js_1 = require("../ui/ProgressReporter.js");
55
+ const STATE_COLORS = {
56
+ CONFIRMED: chalk_1.default.green,
57
+ REJECTED: chalk_1.default.red,
58
+ MANUAL_REFACTOR: chalk_1.default.yellow,
59
+ DRIFTED: chalk_1.default.yellow,
60
+ ORPHANED: chalk_1.default.gray,
61
+ };
62
+ function samePath(a, b) {
63
+ return (path.resolve(a).split(path.sep).join("/").toLowerCase() ===
64
+ path.resolve(b).split(path.sep).join("/").toLowerCase());
65
+ }
66
+ function registerDnaSync(dna) {
67
+ dna
68
+ .command("sync")
69
+ .description("Reconcile the DNA ledger against merged source (partial-PR safe): " +
70
+ "CONFIRM merged heals, REJECT + tombstone reverted ones, GC orphans")
71
+ .option("--dry-run", "Report outcomes without writing anything")
72
+ .option("--json", "Emit the raw JSON report")
73
+ .option("--prune", "Delete ORPHANED records and their visual baselines")
74
+ .option("--strict", "Exit 1 when any REJECTED or DRIFTED record is found (CI gate)")
75
+ .option("--dir <path>", "Override snapshot directory")
76
+ .action(async (opts) => {
77
+ const config = ConfigLoader_js_1.ConfigLoader.getInstance();
78
+ const snapshotDir = path.resolve(opts.dir ?? config.dnaStoragePath);
79
+ const progress = new ProgressReporter_js_1.ProgressReporter(opts.json);
80
+ const cache = new HealingCacheService_js_1.HealingCacheService();
81
+ const service = new DnaSyncService_js_1.DnaSyncService(snapshotDir);
82
+ const report = await progress.run("Reconciling ledger against source…", () => service.sync({
83
+ dryRun: opts.dryRun,
84
+ prune: opts.prune,
85
+ decidedBy: "cli",
86
+ purgeCache: (r) => cache.purgeWhere((e) => e.healedSelector === r.healedSelector &&
87
+ samePath(e.filePath, r.filePath)),
88
+ }));
89
+ if (opts.json) {
90
+ process.stdout.write(JSON.stringify(report, null, 2) + "\n");
91
+ }
92
+ else {
93
+ for (const o of report.outcomes) {
94
+ const paint = STATE_COLORS[o.state] ?? chalk_1.default.white;
95
+ const loc = o.line ? `${o.file}:${o.line}` : o.file;
96
+ process.stdout.write(` ${paint(o.state.padEnd(15))} ${o.key.slice(0, 12)} ${loc}\n` +
97
+ ` ${chalk_1.default.dim(o.reason)}` +
98
+ (o.actions.length ? chalk_1.default.dim(` [${o.actions.join(", ")}]`) : "") +
99
+ "\n");
100
+ }
101
+ const c = report.counts;
102
+ process.stdout.write(`\n ${chalk_1.default.bold("Sync summary")}${report.dryRun ? chalk_1.default.dim(" (dry run)") : ""}: ` +
103
+ `${chalk_1.default.green(`${c.CONFIRMED} confirmed`)}, ` +
104
+ `${chalk_1.default.red(`${c.REJECTED} rejected`)}, ` +
105
+ `${chalk_1.default.yellow(`${c.MANUAL_REFACTOR} refactored`)}, ` +
106
+ `${chalk_1.default.yellow(`${c.DRIFTED} drifted`)}, ` +
107
+ `${chalk_1.default.gray(`${c.ORPHANED} orphaned`)} ` +
108
+ chalk_1.default.dim(`(${report.scanned} scanned, ${report.skipped} skipped)`) +
109
+ "\n");
110
+ }
111
+ if (opts.strict && (report.counts.REJECTED > 0 || report.counts.DRIFTED > 0)) {
112
+ process.exit(1);
113
+ }
114
+ });
115
+ dna
116
+ .command("status")
117
+ .description("Read-only ledger drift report (exit 0 clean / 2 drift)")
118
+ .option("--json", "Emit raw JSON")
119
+ .option("--dir <path>", "Override snapshot directory")
120
+ .action(async (opts) => {
121
+ const config = ConfigLoader_js_1.ConfigLoader.getInstance();
122
+ const snapshotDir = path.resolve(opts.dir ?? config.dnaStoragePath);
123
+ const service = new DnaSyncService_js_1.DnaSyncService(snapshotDir);
124
+ const snapshots = new SnapshotService_js_1.SnapshotService(snapshotDir);
125
+ const counts = {
126
+ PENDING_REVIEW: 0,
127
+ CONFIRMED: 0,
128
+ REJECTED: 0,
129
+ MANUAL_REFACTOR: 0,
130
+ DRIFTED: 0,
131
+ ORPHANED: 0,
132
+ LEGACY: 0,
133
+ };
134
+ let aliases = 0;
135
+ try {
136
+ for (const f of fs
137
+ .readdirSync(snapshotDir)
138
+ .filter((x) => x.endsWith(".json") && x !== "_meta.json")) {
139
+ const rec = await snapshots.load(f.slice(0, -".json".length));
140
+ if (!rec?.healedSelector)
141
+ continue;
142
+ aliases++;
143
+ counts[rec.review?.state ?? "LEGACY"]++;
144
+ }
145
+ }
146
+ catch {
147
+ /* empty ledger */
148
+ }
149
+ const meta = service.readMeta();
150
+ const head = (0, LedgerAnchor_js_1.captureGitInfo)().commitSha;
151
+ const stale = !service.isFresh();
152
+ const drift = stale || counts.REJECTED > 0 || counts.DRIFTED > 0;
153
+ if (opts.json) {
154
+ process.stdout.write(JSON.stringify({ aliases, counts, lastSyncedSha: meta?.lastSyncedSha ?? null, head, stale, drift }, null, 2) + "\n");
155
+ }
156
+ else {
157
+ process.stdout.write(` Ledger aliases: ${aliases}\n` +
158
+ ` Review states: ${Object.entries(counts)
159
+ .filter(([, n]) => n > 0)
160
+ .map(([k, n]) => `${k}=${n}`)
161
+ .join(", ") || "none"}\n` +
162
+ ` Last synced: ${meta?.lastSyncedSha?.slice(0, 12) ?? chalk_1.default.yellow("never")}` +
163
+ ` (HEAD ${head?.slice(0, 12) ?? "n/a"})\n` +
164
+ (stale
165
+ ? chalk_1.default.yellow(" ⚠ Ledger is stale — run `sela dna sync`.\n")
166
+ : chalk_1.default.green(" ✓ Ledger is in sync with HEAD.\n")));
167
+ }
168
+ if (drift)
169
+ process.exit(2);
170
+ });
171
+ dna
172
+ .command("clear-tombstone <key>")
173
+ .description("Remove a rejected-selector memory so it may be proposed again")
174
+ .option("--selector <s>", "Remove only this tombstoned selector")
175
+ .option("--dir <path>", "Override snapshot directory")
176
+ .action(async (key, opts) => {
177
+ const config = ConfigLoader_js_1.ConfigLoader.getInstance();
178
+ const snapshotDir = path.resolve(opts.dir ?? config.dnaStoragePath);
179
+ const snapshots = new SnapshotService_js_1.SnapshotService(snapshotDir);
180
+ const rec = await snapshots.load(key);
181
+ if (!rec) {
182
+ const err = new Error(`DNA not found: ${key}`);
183
+ err.code = "DNA_NOT_FOUND";
184
+ throw err;
185
+ }
186
+ const before = rec.tombstones?.length ?? 0;
187
+ if (before === 0) {
188
+ process.stdout.write(chalk_1.default.yellow(` No tombstones on "${key}".\n`));
189
+ return;
190
+ }
191
+ const remaining = opts.selector
192
+ ? (rec.tombstones ?? []).filter((t) => t.rejectedSelector !== opts.selector)
193
+ : [];
194
+ await snapshots.save(key, {
195
+ ...rec,
196
+ tombstones: remaining.length > 0 ? remaining : undefined,
197
+ });
198
+ process.stdout.write(chalk_1.default.green(` Cleared ${before - remaining.length} tombstone(s) on "${key}".\n`));
199
+ });
200
+ }
@@ -1 +1 @@
1
- {"version":3,"file":"init.d.ts","sourceRoot":"","sources":["../../../src/cli/commands/init.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AA8BpC,MAAM,WAAW,WAAW;IAC1B,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,kDAAkD;IAClD,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,4EAA4E;IAC5E,GAAG,CAAC,EAAE,OAAO,CAAC;IACd,GAAG,CAAC,EAAE,OAAO,CAAC;IACd,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED,wBAAgB,YAAY,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,CAoBnD"}
1
+ {"version":3,"file":"init.d.ts","sourceRoot":"","sources":["../../../src/cli/commands/init.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AA2BpC,MAAM,WAAW,WAAW;IAC1B,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,kDAAkD;IAClD,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,4EAA4E;IAC5E,GAAG,CAAC,EAAE,OAAO,CAAC;IACd,GAAG,CAAC,EAAE,OAAO,CAAC;IACd,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED,wBAAgB,YAAY,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,CAoBnD"}
@@ -32,10 +32,14 @@ var __importStar = (this && this.__importStar) || (function () {
32
32
  return result;
33
33
  };
34
34
  })();
35
+ var __importDefault = (this && this.__importDefault) || function (mod) {
36
+ return (mod && mod.__esModule) ? mod : { "default": mod };
37
+ };
35
38
  Object.defineProperty(exports, "__esModule", { value: true });
36
39
  exports.registerInit = registerInit;
37
40
  const fs = __importStar(require("fs"));
38
41
  const path = __importStar(require("path"));
42
+ const chalk_1 = __importDefault(require("chalk"));
39
43
  const logger_js_1 = require("../../utils/logger.js");
40
44
  const conventionsInit_js_1 = require("../conventionsInit.js");
41
45
  const PACKAGE_NAME = "sela-core";
@@ -45,7 +49,6 @@ export default defineConfig({
45
49
  healingPolicy: 'balanced',
46
50
  updateStrategy: 'inPlace',
47
51
  autoCommit: false,
48
- dnaStoragePath: 'sela-snapshots',
49
52
  });
50
53
  `;
51
54
  const CONFIG_TEMPLATE_JS = `const { defineConfig } = require('${PACKAGE_NAME}');
@@ -54,10 +57,8 @@ module.exports = defineConfig({
54
57
  healingPolicy: 'balanced',
55
58
  updateStrategy: 'inPlace',
56
59
  autoCommit: false,
57
- dnaStoragePath: 'sela-snapshots',
58
60
  });
59
61
  `;
60
- const REQUIRED_ENV_VARS = ["ANTHROPIC_API_KEY"];
61
62
  function registerInit(program) {
62
63
  program
63
64
  .command("init")
@@ -90,17 +91,11 @@ async function runInit(opts) {
90
91
  fs.writeFileSync(configPath, isTs ? CONFIG_TEMPLATE_TS : CONFIG_TEMPLATE_JS, "utf8");
91
92
  logger_js_1.logger.success(`Created ${configFile}`);
92
93
  }
93
- // ── Environment variables ────────────────────────────────────────
94
+ // ── Environment resolve API key presence (non-fatal) ──────────
94
95
  loadDotEnv(cwd);
95
- const missing = REQUIRED_ENV_VARS.filter((k) => !process.env[k]);
96
- if (missing.length > 0) {
97
- logger_js_1.logger.warn(`Missing environment variables:`);
98
- for (const k of missing)
99
- logger_js_1.logger.info(` - ${k}`);
100
- logger_js_1.logger.info(` Add them to .env or your shell profile before running tests.\n`);
101
- }
102
- else {
103
- logger_js_1.logger.success(`Environment variables OK`);
96
+ const hasApiKey = Boolean(process.env.ANTHROPIC_API_KEY);
97
+ if (hasApiKey) {
98
+ logger_js_1.logger.success(`ANTHROPIC_API_KEY detected`);
104
99
  }
105
100
  // ── Import swap (default ON; opt out with --no-update-imports) ──
106
101
  if (opts.updateImports !== false) {
@@ -117,21 +112,54 @@ async function runInit(opts) {
117
112
  // ── Conventions auto-discovery (default ON; opt out --no-conventions) ──
118
113
  if (opts.conventions !== false) {
119
114
  logger_js_1.logger.info(`Discovering testing conventions…`);
120
- await (0, conventionsInit_js_1.runConventionsWizard)(cwd, {
121
- llm: opts.llm,
122
- yes: opts.yes,
123
- dryRun: opts.dryRun,
124
- glob: opts.glob,
125
- maxFiles: opts.maxFiles ? Number(opts.maxFiles) : undefined,
126
- fileTimeoutMs: opts.fileTimeout ? Number(opts.fileTimeout) : undefined,
127
- });
115
+ try {
116
+ await (0, conventionsInit_js_1.runConventionsWizard)(cwd, {
117
+ llm: opts.llm,
118
+ hasApiKey,
119
+ yes: opts.yes,
120
+ dryRun: opts.dryRun,
121
+ glob: opts.glob,
122
+ maxFiles: opts.maxFiles ? Number(opts.maxFiles) : undefined,
123
+ fileTimeoutMs: opts.fileTimeout ? Number(opts.fileTimeout) : undefined,
124
+ });
125
+ }
126
+ catch (err) {
127
+ logger_js_1.logger.warn(`Conventions discovery failed: ${err.message ?? err}`);
128
+ logger_js_1.logger.info(` Continuing — conventions can be re-run with: sela init\n`);
129
+ }
128
130
  }
129
131
  // ── .npmrc node-options upsert ───────────────────────────────────
130
132
  if (!opts.dryRun) {
131
133
  upsertNpmrcHook(cwd);
132
134
  }
135
+ // ── Keyless-mode advisory (rendered LAST so it's the final read) ─
136
+ if (!hasApiKey) {
137
+ renderKeylessModeBlock();
138
+ }
139
+ }
140
+ function renderKeylessModeBlock() {
141
+ const sep = chalk_1.default.yellow(" " + "─".repeat(62));
142
+ logger_js_1.logger.info("");
143
+ logger_js_1.logger.info(sep);
144
+ logger_js_1.logger.info(` ${chalk_1.default.bold(chalk_1.default.cyan("Sela initialized — Deterministic Local Mode"))}`);
145
+ logger_js_1.logger.info(sep);
146
+ logger_js_1.logger.info("");
147
+ logger_js_1.logger.info(` ${chalk_1.default.yellow("⚠")} ${chalk_1.default.yellow("No ANTHROPIC_API_KEY detected. LLM healing is disabled.")}`);
148
+ logger_js_1.logger.info("");
149
+ logger_js_1.logger.info(" All bootstrapping steps completed successfully. Static scanning");
150
+ logger_js_1.logger.info(" and deterministic convention discovery are fully operational.");
151
+ logger_js_1.logger.info("");
152
+ logger_js_1.logger.info(chalk_1.default.bold(" To enable AI-powered healing, add your key:"));
153
+ logger_js_1.logger.info(` ${chalk_1.default.dim("echo 'ANTHROPIC_API_KEY=sk-ant-...' >> .env")}`);
154
+ logger_js_1.logger.info("");
155
+ logger_js_1.logger.info(chalk_1.default.bold(" For local / private models, configure sela.config.ts:"));
156
+ logger_js_1.logger.info(` ${chalk_1.default.dim("llm: { healingModel: { provider: 'openai-compatible',")}`);
157
+ logger_js_1.logger.info(` ${chalk_1.default.dim(" modelId: 'llama3', baseUrl: 'http://localhost:11434/v1' } }")}`);
158
+ logger_js_1.logger.info("");
159
+ logger_js_1.logger.info(sep);
160
+ logger_js_1.logger.info("");
133
161
  }
134
- const NPMRC_HOOK = '--import sela/register';
162
+ const NPMRC_HOOK = '--import sela-core/register';
135
163
  const NPMRC_NODE_OPTIONS_RE = /^node-options\s*=\s*(.*)$/m;
136
164
  function upsertNpmrcHook(cwd) {
137
165
  const npmrcPath = path.join(cwd, '.npmrc');
@@ -178,7 +206,7 @@ function walkDir(dir, exts) {
178
206
  "node_modules",
179
207
  "dist",
180
208
  ".git",
181
- "sela-snapshots",
209
+ ".sela",
182
210
  "sela-snapshots",
183
211
  "test-results",
184
212
  "playwright-report",
@@ -1 +1 @@
1
- {"version":3,"file":"sync.d.ts","sourceRoot":"","sources":["../../../src/cli/commands/sync.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAgBzC,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,OAAO,GAAG,IAAI,CA+LrD"}
1
+ {"version":3,"file":"sync.d.ts","sourceRoot":"","sources":["../../../src/cli/commands/sync.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAkBzC,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,OAAO,GAAG,IAAI,CAuNrD"}
@@ -9,6 +9,7 @@ const path_1 = __importDefault(require("path"));
9
9
  const ConfigLoader_js_1 = require("../../config/ConfigLoader.js");
10
10
  const DnaIndexService_js_1 = require("../../services/DnaIndexService.js");
11
11
  const SourceLinkService_js_1 = require("../../services/SourceLinkService.js");
12
+ const SnapshotService_js_1 = require("../../services/SnapshotService.js");
12
13
  const ProgressReporter_js_1 = require("../ui/ProgressReporter.js");
13
14
  function registerSyncSource(dna) {
14
15
  dna
@@ -18,6 +19,7 @@ function registerSyncSource(dna) {
18
19
  .option("--dry-run", "Preview without writing")
19
20
  .option("--json", "Emit JSON result")
20
21
  .option("--dir <path>", "Override snapshot directory")
22
+ .option("--force", "Write even when the record's review state is not CONFIRMED (dangerous)")
21
23
  .action(async (key, opts) => {
22
24
  const config = ConfigLoader_js_1.ConfigLoader.getInstance();
23
25
  const snapshotDir = opts.dir ?? config.dnaStoragePath;
@@ -35,6 +37,22 @@ function registerSyncSource(dna) {
35
37
  err.code = "DNA_NOT_FOUND";
36
38
  throw err;
37
39
  }
40
+ // Guard (PR & Sync Engine): pushing DNA→source over a record the team
41
+ // has not CONFIRMED would re-impose a heal reviewers may have rejected
42
+ // — the exact resurrection this engine exists to prevent.
43
+ const rawSnap = await new SnapshotService_js_1.SnapshotService(snapshotDir).load(key);
44
+ const reviewState = rawSnap?.review?.state;
45
+ if (reviewState !== undefined && reviewState !== "CONFIRMED" && !opts.force) {
46
+ const reason = `DNA "${key}" review state is ${reviewState} — refusing to write it into source. ` +
47
+ "Run `sela dna sync` first, or pass --force to override.";
48
+ if (opts.json) {
49
+ process.stdout.write(JSON.stringify({ success: false, reason }, null, 2) + "\n");
50
+ process.exit(4);
51
+ }
52
+ const err = new Error(reason);
53
+ err.code = "REVIEW_STATE_GUARD";
54
+ throw err;
55
+ }
38
56
  if (!record.sourceFile || record.sourceLine <= 0) {
39
57
  if (opts.json) {
40
58
  process.stdout.write(JSON.stringify({
@@ -13,6 +13,12 @@ export interface ConventionsInitOptions {
13
13
  maxFiles?: number;
14
14
  /** Run the optional LLM polish step. Default: false. */
15
15
  llm?: boolean;
16
+ /**
17
+ * Explicit API-key presence flag. When false, LLM polish is skipped without
18
+ * consulting process.env. Falls back to Boolean(process.env.ANTHROPIC_API_KEY)
19
+ * when omitted, preserving backward-compatible behaviour for standalone calls.
20
+ */
21
+ hasApiKey?: boolean;
16
22
  /** Injected Anthropic client (tests). Falls back to a sanitizing client. */
17
23
  client?: Anthropic;
18
24
  model?: string;
@@ -1 +1 @@
1
- {"version":3,"file":"conventionsInit.d.ts","sourceRoot":"","sources":["../../src/cli/conventionsInit.ts"],"names":[],"mappings":"AAUA,OAAO,KAAK,SAAS,MAAM,mBAAmB,CAAC;AAO/C,OAAO,EAGL,KAAK,WAAW,EACjB,MAAM,mCAAmC,CAAC;AAC3C,OAAO,EACL,KAAK,WAAW,EAChB,KAAK,SAAS,EAEf,MAAM,0BAA0B,CAAC;AAelC,MAAM,MAAM,aAAa,GAAG,OAAO,GAAG,SAAS,GAAG,MAAM,CAAC;AAEzD,MAAM,WAAW,sBAAsB;IACrC,8BAA8B;IAC9B,IAAI,CAAC,EAAE,aAAa,CAAC;IACrB,sDAAsD;IACtD,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;IACnB,yCAAyC;IACzC,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,wDAAwD;IACxD,GAAG,CAAC,EAAE,OAAO,CAAC;IACd,4EAA4E;IAC5E,MAAM,CAAC,EAAE,SAAS,CAAC;IACnB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,2CAA2C;IAC3C,MAAM,CAAC,EAAE,OAAO,CAAC;CAClB;AAkCD,iFAAiF;AACjF,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CA0BhD;AAED,wEAAwE;AACxE,wBAAgB,gBAAgB,CAC9B,GAAG,EAAE,MAAM,EACX,IAAI,EAAE,IAAI,CAAC,sBAAsB,EAAE,MAAM,GAAG,SAAS,GAAG,MAAM,CAAC,GAC9D,MAAM,EAAE,CAcV;AAMD,MAAM,WAAW,gBAAgB;IAC/B,WAAW,EAAE,WAAW,CAAC;IACzB,KAAK,EAAE,SAAS,CAAC;CAClB;AAED,wBAAsB,mBAAmB,CACvC,KAAK,EAAE,MAAM,EAAE,EACf,IAAI,GAAE,sBAAsB,GAAG;IAAE,UAAU,CAAC,EAAE,WAAW,CAAC,YAAY,CAAC,CAAA;CAAO,GAC7E,OAAO,CAAC,gBAAgB,CAAC,CAsB3B;AAQD;;;;;GAKG;AACH,wBAAsB,iBAAiB,CACrC,KAAK,EAAE,WAAW,EAClB,KAAK,EAAE,SAAS,EAChB,MAAM,EAAE,SAAS,EACjB,KAAK,EAAE,MAAM,GACZ,OAAO,CAAC,WAAW,GAAG,IAAI,CAAC,CA4C7B;AAMD,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,OAAO,CAAC;CAClB;AAED,wBAAgB,wBAAwB,CACtC,GAAG,EAAE,MAAM,EACX,WAAW,EAAE,WAAW,EACxB,IAAI,GAAE;IAAE,MAAM,CAAC,EAAE,OAAO,CAAA;CAAO,GAC9B,WAAW,CAQb;AAMD,wBAAsB,oBAAoB,CACxC,GAAG,EAAE,MAAM,EACX,IAAI,GAAE,sBAAsB,GAAG;IAAE,GAAG,CAAC,EAAE,OAAO,CAAA;CAAO,GACpD,OAAO,CAAC,IAAI,CAAC,CAgHf"}
1
+ {"version":3,"file":"conventionsInit.d.ts","sourceRoot":"","sources":["../../src/cli/conventionsInit.ts"],"names":[],"mappings":"AAUA,OAAO,KAAK,SAAS,MAAM,mBAAmB,CAAC;AAO/C,OAAO,EAGL,KAAK,WAAW,EACjB,MAAM,mCAAmC,CAAC;AAC3C,OAAO,EACL,KAAK,WAAW,EAChB,KAAK,SAAS,EAEf,MAAM,0BAA0B,CAAC;AAelC,MAAM,MAAM,aAAa,GAAG,OAAO,GAAG,SAAS,GAAG,MAAM,CAAC;AAEzD,MAAM,WAAW,sBAAsB;IACrC,8BAA8B;IAC9B,IAAI,CAAC,EAAE,aAAa,CAAC;IACrB,sDAAsD;IACtD,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;IACnB,yCAAyC;IACzC,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,wDAAwD;IACxD,GAAG,CAAC,EAAE,OAAO,CAAC;IACd;;;;OAIG;IACH,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,4EAA4E;IAC5E,MAAM,CAAC,EAAE,SAAS,CAAC;IACnB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,2CAA2C;IAC3C,MAAM,CAAC,EAAE,OAAO,CAAC;CAClB;AAkCD,iFAAiF;AACjF,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CA0BhD;AAED,wEAAwE;AACxE,wBAAgB,gBAAgB,CAC9B,GAAG,EAAE,MAAM,EACX,IAAI,EAAE,IAAI,CAAC,sBAAsB,EAAE,MAAM,GAAG,SAAS,GAAG,MAAM,CAAC,GAC9D,MAAM,EAAE,CAcV;AAMD,MAAM,WAAW,gBAAgB;IAC/B,WAAW,EAAE,WAAW,CAAC;IACzB,KAAK,EAAE,SAAS,CAAC;CAClB;AAED,wBAAsB,mBAAmB,CACvC,KAAK,EAAE,MAAM,EAAE,EACf,IAAI,GAAE,sBAAsB,GAAG;IAAE,UAAU,CAAC,EAAE,WAAW,CAAC,YAAY,CAAC,CAAA;CAAO,GAC7E,OAAO,CAAC,gBAAgB,CAAC,CAsB3B;AAQD;;;;;GAKG;AACH,wBAAsB,iBAAiB,CACrC,KAAK,EAAE,WAAW,EAClB,KAAK,EAAE,SAAS,EAChB,MAAM,EAAE,SAAS,EACjB,KAAK,EAAE,MAAM,GACZ,OAAO,CAAC,WAAW,GAAG,IAAI,CAAC,CA4C7B;AAMD,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,OAAO,CAAC;CAClB;AAED,wBAAgB,wBAAwB,CACtC,GAAG,EAAE,MAAM,EACX,WAAW,EAAE,WAAW,EACxB,IAAI,GAAE;IAAE,MAAM,CAAC,EAAE,OAAO,CAAA;CAAO,GAC9B,WAAW,CAQb;AAMD,wBAAsB,oBAAoB,CACxC,GAAG,EAAE,MAAM,EACX,IAAI,GAAE,sBAAsB,GAAG;IAAE,GAAG,CAAC,EAAE,OAAO,CAAA;CAAO,GACpD,OAAO,CAAC,IAAI,CAAC,CAkHf"}
@@ -285,7 +285,8 @@ async function runConventionsWizard(cwd, opts = {}) {
285
285
  }
286
286
  // ── Optional LLM polish ───────────────────────────────────────
287
287
  let final = conventions;
288
- const canPolish = opts.llm !== false && Boolean(process.env.ANTHROPIC_API_KEY);
288
+ const apiKeyPresent = opts.hasApiKey ?? Boolean(process.env.ANTHROPIC_API_KEY);
289
+ const canPolish = opts.llm !== false && apiKeyPresent;
289
290
  const wantPolish = interactive
290
291
  ? canPolish &&
291
292
  (await new enquirer_js_1.Confirm({
package/dist/cli/index.js CHANGED
@@ -52,6 +52,7 @@ const find_js_1 = require("./commands/find.js");
52
52
  const refactor_js_1 = require("./commands/refactor.js");
53
53
  const bulk_js_1 = require("./commands/bulk.js");
54
54
  const sync_js_1 = require("./commands/sync.js");
55
+ const dnaSync_js_1 = require("./commands/dnaSync.js");
55
56
  const showReport_js_1 = require("./commands/showReport.js");
56
57
  const merge_js_1 = require("./commands/merge.js");
57
58
  const shadowRegression_js_1 = require("./commands/shadowRegression.js");
@@ -96,5 +97,7 @@ const dna = new commander_1.Command("dna")
96
97
  (0, bulk_js_1.registerBulkUpdate)(dna);
97
98
  // ── sela dna sync-source (T-15) ───────────────────────────────────────────────
98
99
  (0, sync_js_1.registerSyncSource)(dna);
100
+ // ── sela dna sync / status / clear-tombstone — PR & Sync Engine ───────────────
101
+ (0, dnaSync_js_1.registerDnaSync)(dna);
99
102
  program.addCommand(dna);
100
103
  program.parseAsync(process.argv).catch(ErrorHandler_js_1.handleError);
@@ -1 +1 @@
1
- {"version":3,"file":"ConfigLoader.d.ts","sourceRoot":"","sources":["../../src/config/ConfigLoader.ts"],"names":[],"mappings":"AAQA,OAAO,EAAE,UAAU,EAAE,cAAc,EAAiB,MAAM,cAAc,CAAC;AAqOzE,qBAAa,YAAY;IACvB,OAAO,CAAC,MAAM,CAAC,QAAQ,CAA+B;IACtD,OAAO,CAAC,MAAM,CAAC,MAAM,CAAuB;IAK5C,OAAO,CAAC,MAAM,CAAC,aAAa,CAA2B;IAEvD;;;;;;;;OAQG;IACH,MAAM,CAAC,WAAW,IAAI,cAAc;IAqCpC;;;;OAIG;IACH,MAAM,CAAC,YAAY,IAAI,UAAU;IAKjC,OAAO,CAAC,MAAM,CAAC,aAAa;IAsB5B,yEAAyE;IACzE,MAAM,CAAC,SAAS,IAAI,MAAM,GAAG,IAAI;IAKjC,uDAAuD;IACvD,MAAM,CAAC,KAAK,IAAI,IAAI;CAKrB"}
1
+ {"version":3,"file":"ConfigLoader.d.ts","sourceRoot":"","sources":["../../src/config/ConfigLoader.ts"],"names":[],"mappings":"AAQA,OAAO,EAAE,UAAU,EAAE,cAAc,EAAiB,MAAM,cAAc,CAAC;AAgSzE,qBAAa,YAAY;IACvB,OAAO,CAAC,MAAM,CAAC,QAAQ,CAA+B;IACtD,OAAO,CAAC,MAAM,CAAC,MAAM,CAAuB;IAK5C,OAAO,CAAC,MAAM,CAAC,aAAa,CAA2B;IAEvD;;;;;;;;OAQG;IACH,MAAM,CAAC,WAAW,IAAI,cAAc;IAqCpC;;;;OAIG;IACH,MAAM,CAAC,YAAY,IAAI,UAAU;IAKjC,OAAO,CAAC,MAAM,CAAC,aAAa;IAsB5B,yEAAyE;IACzE,MAAM,CAAC,SAAS,IAAI,MAAM,GAAG,IAAI;IAKjC,uDAAuD;IACvD,MAAM,CAAC,KAAK,IAAI,IAAI;CAKrB"}
@@ -55,11 +55,14 @@ const KNOWN_CONFIG_KEYS = new Set([
55
55
  "cacheStoragePath",
56
56
  "allowedPaths",
57
57
  "maxHealsPerRun",
58
+ "smartPrune",
59
+ "largeDomNodeThreshold",
58
60
  "sanitization",
59
61
  "playwrightRules",
60
62
  "blastRadiusProtection",
61
63
  "branchOverrides",
62
64
  "llm",
65
+ "healing",
63
66
  ]);
64
67
  // ─────────────────────────────────────────────────────────────────
65
68
  // FILE DISCOVERY
@@ -126,6 +129,51 @@ function validateConfigKeys(config, filePath) {
126
129
  `Valid top-level keys: ${[...KNOWN_CONFIG_KEYS].join(", ")}.`);
127
130
  }
128
131
  }
132
+ let jitiInstance = null;
133
+ function getJiti() {
134
+ if (jitiInstance)
135
+ return jitiInstance;
136
+ // eslint-disable-next-line @typescript-eslint/no-var-requires
137
+ const mod = require("jiti");
138
+ // jiti v2 exposes `createJiti`; the module root is also callable as a
139
+ // factory for backward compatibility. Support both without version-pinning.
140
+ const createJiti = (mod && mod.createJiti) || mod;
141
+ jitiInstance = createJiti(__filename, {
142
+ // Return the module namespace (with `.default`) untouched — we unwrap the
143
+ // default export ourselves, exactly as the native `require` path does.
144
+ interopDefault: false,
145
+ // Persist transpile output to disk so warm loads stay well under budget.
146
+ fsCache: true,
147
+ moduleCache: true,
148
+ });
149
+ return jitiInstance;
150
+ }
151
+ /**
152
+ * Loads a single config module, transpiling on the fly when needed.
153
+ *
154
+ * • `.ts` / `.mts` / `.cts` → always via jiti. Native `require` of a `.ts`
155
+ * file is non-deterministic across Node versions (fails pre-22, type-strips
156
+ * with caveats on ≥22), so we never rely on it for TypeScript.
157
+ * • `.js` / `.cjs` / `.mjs` → native `require` first (fast, zero overhead for
158
+ * CommonJS configs). Only if that fails — e.g. the file is authored in ESM
159
+ * (`import`/`export`) inside a CJS project — do we fall back to jiti, which
160
+ * transpiles ESM↔CJS transparently.
161
+ */
162
+ function loadConfigModule(candidate) {
163
+ const isTs = /\.[mc]?ts$/.test(candidate);
164
+ if (isTs) {
165
+ return getJiti()(candidate);
166
+ }
167
+ try {
168
+ // eslint-disable-next-line @typescript-eslint/no-var-requires
169
+ return require(candidate);
170
+ }
171
+ catch {
172
+ // CJS require choked (most often an ESM config in a CJS project). Retry
173
+ // through jiti, which handles the ESM/CJS interop boundary deterministically.
174
+ return getJiti()(candidate);
175
+ }
176
+ }
129
177
  // ─────────────────────────────────────────────────────────────────
130
178
  // CONFIG FILE LOADING
131
179
  // ─────────────────────────────────────────────────────────────────
@@ -133,9 +181,9 @@ function validateConfigKeys(config, filePath) {
133
181
  * Loads and validates the raw config from `configPath`.
134
182
  *
135
183
  * Load strategy (in order):
136
- * 1. `configPath` as-is — works when a TS loader is registered
137
- * (Playwright, ts-node, tsx).
138
- * 2. `.js` sibling — for compiled CI builds where only the transpiled file
184
+ * 1. `configPath` as-is — transpiled via the embedded jiti loader for TS,
185
+ * native `require` (jiti fallback) for JS. No host loader assumed.
186
+ * 2. `.js` sibling — for compiled builds where only the transpiled file
139
187
  * is present alongside the source `.ts`.
140
188
  *
141
189
  * Throws (never silently returns {}) when the file is found but every load
@@ -151,8 +199,7 @@ function loadRawConfig(configPath) {
151
199
  if (!fs.existsSync(candidate))
152
200
  continue;
153
201
  try {
154
- // eslint-disable-next-line @typescript-eslint/no-var-requires
155
- const mod = require(candidate);
202
+ const mod = loadConfigModule(candidate);
156
203
  const config = mod?.default ?? mod ?? {};
157
204
  if (typeof config !== "object" || config === null || Array.isArray(config)) {
158
205
  throw new Error(`${path.basename(candidate)} must export a plain object — ` +
@@ -170,23 +217,18 @@ function loadRawConfig(configPath) {
170
217
  }
171
218
  // Every attempt failed. Two cases:
172
219
  //
173
- // 1. The config cannot be loaded due to a missing or incompatible TypeScript
174
- // loader. This covers several scenarios that all stem from the same root
175
- // cause — the Node.js process has no way to execute .ts (or compiled ESM
176
- // .js) in the current environment:
177
- // ERR_UNKNOWN_FILE_EXTENSION / "Unknown file extension .ts" — plain
178
- // Node.js sees the .ts extension with no registered loader.
179
- // "Cannot use import statement outside a module" the config (or its
180
- // compiled .js sibling) uses ESM `import` syntax but Node.js is
181
- // running in CJS mode and cannot load it with require(). Happens when
182
- // a compiled sela.config.js uses top-level `import`, or when Node 20
183
- // --experimental-strip-types strips .ts annotations but the project
184
- // is CJS.
185
- // • ERR_REQUIRE_ESM / "require() of ES Module" — Node.js detected the
186
- // file is an ES module and refused the synchronous require() call.
187
- // All of these are environment/loader mismatches, not config content bugs.
188
- // Gracefully fall back to defaults so the CLI command (e.g.
189
- // `sela shadow-regression`) still runs rather than crashing.
220
+ // 1. Loader-environment error. With the embedded jiti loader this is now
221
+ // RARE jiti transpiles TS and bridges the ESM/CJS boundary itself, so
222
+ // the classic "no TypeScript loader" failures no longer occur under
223
+ // normal operation. This branch survives only as a defensive last resort
224
+ // for pathological hosts where even jiti cannot execute the file (e.g. a
225
+ // sandbox that blocks its transpile cache). The signatures below all stem
226
+ // from the same root cause the file could not be executed as a module:
227
+ // ERR_UNKNOWN_FILE_EXTENSION / "Unknown file extension .ts"
228
+ // "Cannot use import statement outside a module"
229
+ // ERR_REQUIRE_ESM / "require() of ES Module"
230
+ // These are environment issues, not config-content bugs, so fall back to
231
+ // defaults and warn rather than crashing the CLI command.
190
232
  //
191
233
  // 2. Genuine config error (syntax, key validation, etc.) → hard throw so CI
192
234
  // surfaces the problem instead of silently running on stale defaults.
@@ -202,16 +244,15 @@ function loadRawConfig(configPath) {
202
244
  msg.includes("require() of ES Module") ||
203
245
  msg.includes("ERR_REQUIRE_ESM"));
204
246
  if (isLoaderEnvError) {
205
- logger_1.logger.warn(`[Sela] sela.config.ts found but cannot be loaded in this Node.js context ` +
206
- `(no TypeScript loader active, or the compiled .js output uses ESM syntax ` +
207
- `incompatible with require()). Running on defaults. ` +
208
- `To use config values in the CLI, compile sela.config.ts to CJS ` +
209
- `(e.g. tsc --module commonjs) or set SELA_CONFIG_PATH to the compiled file.`);
247
+ logger_1.logger.warn(`[Sela] sela.config.ts found but could not be executed even with the ` +
248
+ `embedded loader in this Node.js context. Running on defaults. ` +
249
+ `This is unexpected please report it, or set SELA_CONFIG_PATH to a ` +
250
+ `precompiled sela.config.js as a workaround.`);
210
251
  return {};
211
252
  }
212
253
  throw new Error(`[Sela] Failed to load ${path.basename(configPath)}: ${msg || "unknown error"}. ` +
213
- `Ensure a TypeScript loader (Playwright, ts-node, tsx) is active, ` +
214
- `or provide a compiled sela.config.js alongside sela.config.ts.`);
254
+ `Sela transpiles TS/ESM configs internally, so this is a genuine config ` +
255
+ `error (syntax, bad export, or a throw in the file) — fix the file and retry.`);
215
256
  }
216
257
  // ─────────────────────────────────────────────────────────────────
217
258
  // DEEP MERGE (plain objects only - arrays are replaced, not merged)
@@ -53,7 +53,13 @@ export interface Conventions {
53
53
  strategies: {
54
54
  /** Ordered most→least observed (ties by STRATEGY_PRIORITY). */
55
55
  preferred: SelectorStrategy[];
56
- testIdAttribute: string;
56
+ /**
57
+ * Custom test-id attribute. Omitted from auto-discovery output; set here
58
+ * only when the user explicitly declares it via sela.config.ts
59
+ * `playwrightRules.testIdAttribute` and it is applied via
60
+ * `applyConventionsOverride` at heal time.
61
+ */
62
+ testIdAttribute?: string;
57
63
  /** Rarely-seen strategies the healer should steer away from. */
58
64
  avoid: SelectorStrategy[];
59
65
  };
@@ -80,7 +86,7 @@ export declare function argmax<K extends string>(counts: Record<string, number>,
80
86
  export declare function round2(n: number): number;
81
87
  export interface CompactConventions {
82
88
  /** preferred strategies */ s: SelectorStrategy[];
83
- /** testIdAttribute */ t: string;
89
+ /** testIdAttribute — omitted when not set */ t?: string;
84
90
  /** architecture mode */ a: ArchitectureMode;
85
91
  /** selectorSource — omitted when inline/null */ src?: string;
86
92
  /** naming case */ n: NamingCase;
@@ -1 +1 @@
1
- {"version":3,"file":"Conventions.d.ts","sourceRoot":"","sources":["../../src/config/Conventions.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"Conventions.d.ts","sourceRoot":"","sources":["../../src/config/Conventions.ts"],"names":[],"mappings":"AAuBA,sDAAsD;AACtD,MAAM,MAAM,gBAAgB,GAAG,QAAQ,GAAG,MAAM,GAAG,MAAM,GAAG,KAAK,GAAG,OAAO,CAAC;AAE5E,yDAAyD;AACzD,MAAM,MAAM,gBAAgB,GAAG,QAAQ,GAAG,UAAU,GAAG,YAAY,GAAG,OAAO,CAAC;AAE9E,0EAA0E;AAC1E,MAAM,MAAM,UAAU,GAClB,kBAAkB,GAClB,WAAW,GACX,YAAY,GACZ,OAAO,CAAC;AAKZ,eAAO,MAAM,iBAAiB,EAAE,SAAS,gBAAgB,EAMxD,CAAC;AACF,eAAO,MAAM,aAAa,EAAE,SAAS,OAAO,CAAC,gBAAgB,EAAE,OAAO,CAAC,EAItE,CAAC;AACF,eAAO,MAAM,aAAa,EAAE,SAAS,OAAO,CAAC,UAAU,EAAE,OAAO,CAAC,EAIhE,CAAC;AAEF,8EAA8E;AAC9E,eAAO,MAAM,sBAAsB,MAAM,CAAC;AAC1C,4EAA4E;AAC5E,eAAO,MAAM,sBAAsB,OAAO,CAAC;AAM3C;;;;GAIG;AACH,MAAM,WAAW,SAAS;IACxB,cAAc,EAAE,MAAM,CAAC,gBAAgB,EAAE,MAAM,CAAC,CAAC;IACjD,oEAAoE;IACpE,UAAU,EAAE;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAC;QAAC,UAAU,EAAE,MAAM,CAAA;KAAE,CAAC;IACrE,+CAA+C;IAC/C,UAAU,EAAE,MAAM,CAAC,OAAO,CAAC,UAAU,EAAE,OAAO,CAAC,EAAE,MAAM,CAAC,GAAG;QACzD,KAAK,EAAE,MAAM,CAAC;KACf,CAAC;IACF,oEAAoE;IACpE,kBAAkB,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC3C,sEAAsE;IACtE,gBAAgB,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACzC,YAAY,EAAE,MAAM,CAAC;IACrB,gBAAgB,EAAE,MAAM,CAAC;IACzB,sEAAsE;IACtE,YAAY,EAAE,MAAM,EAAE,CAAC;CACxB;AAED,wBAAgB,cAAc,IAAI,SAAS,CAW1C;AAMD,MAAM,WAAW,eAAe;IAC9B,WAAW,EAAE,MAAM,CAAC;IACpB,YAAY,EAAE,MAAM,CAAC;IACrB,gBAAgB,EAAE,MAAM,CAAC;IACzB,iDAAiD;IACjD,UAAU,EAAE,MAAM,CAAC;IACnB,MAAM,EAAE,eAAe,GAAG,cAAc,CAAC;IACzC,mEAAmE;IACnE,YAAY,CAAC,EAAE,MAAM,EAAE,CAAC;CACzB;AAED,MAAM,WAAW,WAAW;IAC1B,OAAO,EAAE,CAAC,CAAC;IACX,UAAU,EAAE;QACV,+DAA+D;QAC/D,SAAS,EAAE,gBAAgB,EAAE,CAAC;QAC9B;;;;;WAKG;QACH,eAAe,CAAC,EAAE,MAAM,CAAC;QACzB,gEAAgE;QAChE,KAAK,EAAE,gBAAgB,EAAE,CAAC;KAC3B,CAAC;IACF,YAAY,EAAE;QACZ,IAAI,EAAE,gBAAgB,CAAC;QACvB,oEAAoE;QACpE,cAAc,EAAE,MAAM,GAAG,IAAI,CAAC;KAC/B,CAAC;IACF,MAAM,EAAE;QAAE,IAAI,EAAE,UAAU,CAAA;KAAE,CAAC;IAC7B,KAAK,EAAE,eAAe,CAAC;CACxB;AAMD;;;;;GAKG;AACH,wBAAgB,MAAM,CAAC,CAAC,SAAS,MAAM,EACrC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,EAC9B,QAAQ,EAAE,SAAS,CAAC,EAAE,GACrB;IAAE,GAAG,EAAE,CAAC,GAAG,IAAI,CAAC;IAAC,UAAU,EAAE,MAAM,CAAA;CAAE,CAoBvC;AAED,wBAAgB,MAAM,CAAC,CAAC,EAAE,MAAM,GAAG,MAAM,CAExC;AAMD,MAAM,WAAW,kBAAkB;IACjC,2BAA2B,CAAC,CAAC,EAAE,gBAAgB,EAAE,CAAC;IAClD,6CAA6C,CAAC,CAAC,CAAC,EAAE,MAAM,CAAC;IACzD,wBAAwB,CAAC,CAAC,EAAE,gBAAgB,CAAC;IAC7C,gDAAgD,CAAC,GAAG,CAAC,EAAE,MAAM,CAAC;IAC9D,kBAAkB,CAAC,CAAC,EAAE,UAAU,CAAC;IACjC,sCAAsC,CAAC,CAAC,CAAC,EAAE,gBAAgB,EAAE,CAAC;CAC/D;AAED;;;;GAIG;AACH,wBAAgB,gBAAgB,CAAC,CAAC,EAAE,WAAW,GAAG,kBAAkB,CAYnE;AAMD,MAAM,WAAW,qBAAqB;IACpC,KAAK,EAAE,OAAO,CAAC;IACf,MAAM,EAAE,MAAM,EAAE,CAAC;CAClB;AAMD;;;GAGG;AACH,wBAAgB,mBAAmB,CAAC,GAAG,EAAE,OAAO,GAAG,qBAAqB,CA0CvE;AAMD,8EAA8E;AAC9E,wBAAgB,mBAAmB,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAUnE;AAID;;;;GAIG;AACH,wBAAgB,eAAe,CAAC,GAAG,GAAE,MAAsB,GAAG,WAAW,GAAG,IAAI,CAgB/E;AAED,6DAA6D;AAC7D,wBAAgB,qBAAqB,IAAI,IAAI,CAE5C;AAED,wFAAwF;AACxF,MAAM,WAAW,mBAAmB;IAClC,mBAAmB,CAAC,EAAE,CAAC,MAAM,GAAG,QAAQ,GAAG,MAAM,GAAG,KAAK,CAAC,EAAE,CAAC;IAC7D,eAAe,CAAC,EAAE,MAAM,CAAC;CAC1B;AAED;;;;GAIG;AACH,wBAAgB,wBAAwB,CACtC,CAAC,EAAE,WAAW,EACd,CAAC,CAAC,EAAE,mBAAmB,GACtB,WAAW,CAQb;AAED;;;;GAIG;AACH,wBAAgB,wBAAwB,CAAC,CAAC,EAAE,WAAW,GAAG,IAAI,GAAG,MAAM,CAKtE"}