sela-core 0.1.0-alpha.1

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 (267) hide show
  1. package/README.md +103 -0
  2. package/bin/sela.js +3 -0
  3. package/dist/cli/ErrorHandler.d.ts +10 -0
  4. package/dist/cli/ErrorHandler.d.ts.map +1 -0
  5. package/dist/cli/ErrorHandler.js +70 -0
  6. package/dist/cli/commands/bulk.d.ts +3 -0
  7. package/dist/cli/commands/bulk.d.ts.map +1 -0
  8. package/dist/cli/commands/bulk.js +158 -0
  9. package/dist/cli/commands/find.d.ts +3 -0
  10. package/dist/cli/commands/find.d.ts.map +1 -0
  11. package/dist/cli/commands/find.js +51 -0
  12. package/dist/cli/commands/init.d.ts +3 -0
  13. package/dist/cli/commands/init.d.ts.map +1 -0
  14. package/dist/cli/commands/init.js +166 -0
  15. package/dist/cli/commands/list.d.ts +3 -0
  16. package/dist/cli/commands/list.d.ts.map +1 -0
  17. package/dist/cli/commands/list.js +56 -0
  18. package/dist/cli/commands/merge.d.ts +13 -0
  19. package/dist/cli/commands/merge.d.ts.map +1 -0
  20. package/dist/cli/commands/merge.js +119 -0
  21. package/dist/cli/commands/refactor.d.ts +3 -0
  22. package/dist/cli/commands/refactor.d.ts.map +1 -0
  23. package/dist/cli/commands/refactor.js +30 -0
  24. package/dist/cli/commands/showReport.d.ts +3 -0
  25. package/dist/cli/commands/showReport.d.ts.map +1 -0
  26. package/dist/cli/commands/showReport.js +82 -0
  27. package/dist/cli/commands/status.d.ts +3 -0
  28. package/dist/cli/commands/status.d.ts.map +1 -0
  29. package/dist/cli/commands/status.js +51 -0
  30. package/dist/cli/commands/sync.d.ts +3 -0
  31. package/dist/cli/commands/sync.d.ts.map +1 -0
  32. package/dist/cli/commands/sync.js +131 -0
  33. package/dist/cli/index.d.ts +2 -0
  34. package/dist/cli/index.d.ts.map +1 -0
  35. package/dist/cli/index.js +94 -0
  36. package/dist/cli/ui/DnaTable.d.ts +3 -0
  37. package/dist/cli/ui/DnaTable.d.ts.map +1 -0
  38. package/dist/cli/ui/DnaTable.js +77 -0
  39. package/dist/cli/ui/LocatorPicker.d.ts +8 -0
  40. package/dist/cli/ui/LocatorPicker.d.ts.map +1 -0
  41. package/dist/cli/ui/LocatorPicker.js +33 -0
  42. package/dist/cli/ui/ProgressReporter.d.ts +11 -0
  43. package/dist/cli/ui/ProgressReporter.d.ts.map +1 -0
  44. package/dist/cli/ui/ProgressReporter.js +33 -0
  45. package/dist/cli/ui/RefactorWizard.d.ts +26 -0
  46. package/dist/cli/ui/RefactorWizard.d.ts.map +1 -0
  47. package/dist/cli/ui/RefactorWizard.js +295 -0
  48. package/dist/config/ConfigLoader.d.ts +16 -0
  49. package/dist/config/ConfigLoader.d.ts.map +1 -0
  50. package/dist/config/ConfigLoader.js +185 -0
  51. package/dist/config/DryRunGuard.d.ts +14 -0
  52. package/dist/config/DryRunGuard.d.ts.map +1 -0
  53. package/dist/config/DryRunGuard.js +80 -0
  54. package/dist/config/PathScopeGuard.d.ts +42 -0
  55. package/dist/config/PathScopeGuard.d.ts.map +1 -0
  56. package/dist/config/PathScopeGuard.js +192 -0
  57. package/dist/config/SelaConfig.d.ts +178 -0
  58. package/dist/config/SelaConfig.d.ts.map +1 -0
  59. package/dist/config/SelaConfig.js +141 -0
  60. package/dist/config/defineConfig.d.ts +3 -0
  61. package/dist/config/defineConfig.d.ts.map +1 -0
  62. package/dist/config/defineConfig.js +9 -0
  63. package/dist/engine/FixwrightEngine.d.ts +24 -0
  64. package/dist/engine/FixwrightEngine.d.ts.map +1 -0
  65. package/dist/engine/FixwrightEngine.js +403 -0
  66. package/dist/engine/HealingRegistry.d.ts +40 -0
  67. package/dist/engine/HealingRegistry.d.ts.map +1 -0
  68. package/dist/engine/HealingRegistry.js +99 -0
  69. package/dist/engine/SelaEngine.d.ts +57 -0
  70. package/dist/engine/SelaEngine.d.ts.map +1 -0
  71. package/dist/engine/SelaEngine.js +1146 -0
  72. package/dist/engine/singleton.d.ts +3 -0
  73. package/dist/engine/singleton.d.ts.map +1 -0
  74. package/dist/engine/singleton.js +5 -0
  75. package/dist/errors/SelaError.d.ts +146 -0
  76. package/dist/errors/SelaError.d.ts.map +1 -0
  77. package/dist/errors/SelaError.js +168 -0
  78. package/dist/fixtures/expectProxy.d.ts +12 -0
  79. package/dist/fixtures/expectProxy.d.ts.map +1 -0
  80. package/dist/fixtures/expectProxy.js +246 -0
  81. package/dist/fixtures/index.d.ts +30 -0
  82. package/dist/fixtures/index.d.ts.map +1 -0
  83. package/dist/fixtures/index.js +765 -0
  84. package/dist/fixtures/moduleExpect.d.ts +2 -0
  85. package/dist/fixtures/moduleExpect.d.ts.map +1 -0
  86. package/dist/fixtures/moduleExpect.js +54 -0
  87. package/dist/fixtures/proxyTag.d.ts +12 -0
  88. package/dist/fixtures/proxyTag.d.ts.map +1 -0
  89. package/dist/fixtures/proxyTag.js +45 -0
  90. package/dist/index.d.ts +9 -0
  91. package/dist/index.d.ts.map +1 -0
  92. package/dist/index.js +31 -0
  93. package/dist/reporter/SelaReporter.d.ts +165 -0
  94. package/dist/reporter/SelaReporter.d.ts.map +1 -0
  95. package/dist/reporter/SelaReporter.js +517 -0
  96. package/dist/services/ASTSourceUpdater.d.ts +143 -0
  97. package/dist/services/ASTSourceUpdater.d.ts.map +1 -0
  98. package/dist/services/ASTSourceUpdater.js +3514 -0
  99. package/dist/services/AnchorResolver.d.ts +157 -0
  100. package/dist/services/AnchorResolver.d.ts.map +1 -0
  101. package/dist/services/AnchorResolver.js +289 -0
  102. package/dist/services/ArgumentTypeAnalyzer.d.ts +26 -0
  103. package/dist/services/ArgumentTypeAnalyzer.d.ts.map +1 -0
  104. package/dist/services/ArgumentTypeAnalyzer.js +92 -0
  105. package/dist/services/BlastRadiusAnalyzer.d.ts +15 -0
  106. package/dist/services/BlastRadiusAnalyzer.d.ts.map +1 -0
  107. package/dist/services/BlastRadiusAnalyzer.js +107 -0
  108. package/dist/services/ChainValidator.d.ts +76 -0
  109. package/dist/services/ChainValidator.d.ts.map +1 -0
  110. package/dist/services/ChainValidator.js +570 -0
  111. package/dist/services/CrossFileHealer.d.ts +6 -0
  112. package/dist/services/CrossFileHealer.d.ts.map +1 -0
  113. package/dist/services/CrossFileHealer.js +135 -0
  114. package/dist/services/DecisionEngine.d.ts +51 -0
  115. package/dist/services/DecisionEngine.d.ts.map +1 -0
  116. package/dist/services/DecisionEngine.js +260 -0
  117. package/dist/services/DefinitionTracer.d.ts +41 -0
  118. package/dist/services/DefinitionTracer.d.ts.map +1 -0
  119. package/dist/services/DefinitionTracer.js +366 -0
  120. package/dist/services/DnaEditorService.d.ts +31 -0
  121. package/dist/services/DnaEditorService.d.ts.map +1 -0
  122. package/dist/services/DnaEditorService.js +217 -0
  123. package/dist/services/DnaIndexService.d.ts +24 -0
  124. package/dist/services/DnaIndexService.d.ts.map +1 -0
  125. package/dist/services/DnaIndexService.js +131 -0
  126. package/dist/services/HealBudget.d.ts +32 -0
  127. package/dist/services/HealBudget.d.ts.map +1 -0
  128. package/dist/services/HealBudget.js +85 -0
  129. package/dist/services/HealReportService.d.ts +161 -0
  130. package/dist/services/HealReportService.d.ts.map +1 -0
  131. package/dist/services/HealReportService.js +1039 -0
  132. package/dist/services/HealingAdvisory.d.ts +30 -0
  133. package/dist/services/HealingAdvisory.d.ts.map +1 -0
  134. package/dist/services/HealingAdvisory.js +51 -0
  135. package/dist/services/HealingCacheService.d.ts +135 -0
  136. package/dist/services/HealingCacheService.d.ts.map +1 -0
  137. package/dist/services/HealingCacheService.js +404 -0
  138. package/dist/services/HealthReportService.d.ts +10 -0
  139. package/dist/services/HealthReportService.d.ts.map +1 -0
  140. package/dist/services/HealthReportService.js +84 -0
  141. package/dist/services/InitializerUpdater.d.ts +16 -0
  142. package/dist/services/InitializerUpdater.d.ts.map +1 -0
  143. package/dist/services/InitializerUpdater.js +73 -0
  144. package/dist/services/IntentAuditor.d.ts +56 -0
  145. package/dist/services/IntentAuditor.d.ts.map +1 -0
  146. package/dist/services/IntentAuditor.js +326 -0
  147. package/dist/services/InteractiveReview.d.ts +15 -0
  148. package/dist/services/InteractiveReview.d.ts.map +1 -0
  149. package/dist/services/InteractiveReview.js +128 -0
  150. package/dist/services/LLMService.d.ts +121 -0
  151. package/dist/services/LLMService.d.ts.map +1 -0
  152. package/dist/services/LLMService.js +475 -0
  153. package/dist/services/MutationApplier.d.ts +69 -0
  154. package/dist/services/MutationApplier.d.ts.map +1 -0
  155. package/dist/services/MutationApplier.js +360 -0
  156. package/dist/services/PRAutomationService.d.ts +50 -0
  157. package/dist/services/PRAutomationService.d.ts.map +1 -0
  158. package/dist/services/PRAutomationService.js +760 -0
  159. package/dist/services/PendingPromptLedger.d.ts +51 -0
  160. package/dist/services/PendingPromptLedger.d.ts.map +1 -0
  161. package/dist/services/PendingPromptLedger.js +205 -0
  162. package/dist/services/ReportGenerator.d.ts +156 -0
  163. package/dist/services/ReportGenerator.d.ts.map +1 -0
  164. package/dist/services/ReportGenerator.js +278 -0
  165. package/dist/services/ReportMergeService.d.ts +95 -0
  166. package/dist/services/ReportMergeService.d.ts.map +1 -0
  167. package/dist/services/ReportMergeService.js +0 -0
  168. package/dist/services/SafetyGuard.d.ts +112 -0
  169. package/dist/services/SafetyGuard.d.ts.map +1 -0
  170. package/dist/services/SafetyGuard.js +87 -0
  171. package/dist/services/Sanitizer.d.ts +28 -0
  172. package/dist/services/Sanitizer.d.ts.map +1 -0
  173. package/dist/services/Sanitizer.js +142 -0
  174. package/dist/services/SanitizingAnthropic.d.ts +19 -0
  175. package/dist/services/SanitizingAnthropic.d.ts.map +1 -0
  176. package/dist/services/SanitizingAnthropic.js +84 -0
  177. package/dist/services/SelectorSanitizer.d.ts +52 -0
  178. package/dist/services/SelectorSanitizer.d.ts.map +1 -0
  179. package/dist/services/SelectorSanitizer.js +318 -0
  180. package/dist/services/SnapshotService.d.ts +11 -0
  181. package/dist/services/SnapshotService.d.ts.map +1 -0
  182. package/dist/services/SnapshotService.js +352 -0
  183. package/dist/services/SourceLinkService.d.ts +26 -0
  184. package/dist/services/SourceLinkService.d.ts.map +1 -0
  185. package/dist/services/SourceLinkService.js +165 -0
  186. package/dist/services/SourceUpdater.d.ts +45 -0
  187. package/dist/services/SourceUpdater.d.ts.map +1 -0
  188. package/dist/services/SourceUpdater.js +1091 -0
  189. package/dist/services/TemplateDiffService.d.ts +25 -0
  190. package/dist/services/TemplateDiffService.d.ts.map +1 -0
  191. package/dist/services/TemplateDiffService.js +334 -0
  192. package/dist/services/TraceBackEngine.d.ts +67 -0
  193. package/dist/services/TraceBackEngine.d.ts.map +1 -0
  194. package/dist/services/TraceBackEngine.js +672 -0
  195. package/dist/services/WorkspaceSnapshotService.d.ts +71 -0
  196. package/dist/services/WorkspaceSnapshotService.d.ts.map +1 -0
  197. package/dist/services/WorkspaceSnapshotService.js +200 -0
  198. package/dist/services/safety/PipelineContext.d.ts +63 -0
  199. package/dist/services/safety/PipelineContext.d.ts.map +1 -0
  200. package/dist/services/safety/PipelineContext.js +34 -0
  201. package/dist/services/safety/roleClassifier.d.ts +4 -0
  202. package/dist/services/safety/roleClassifier.d.ts.map +1 -0
  203. package/dist/services/safety/roleClassifier.js +233 -0
  204. package/dist/services/safety/rules/ConfidenceTierRule.d.ts +3 -0
  205. package/dist/services/safety/rules/ConfidenceTierRule.d.ts.map +1 -0
  206. package/dist/services/safety/rules/ConfidenceTierRule.js +32 -0
  207. package/dist/services/safety/rules/DangerousSinkRule.d.ts +10 -0
  208. package/dist/services/safety/rules/DangerousSinkRule.d.ts.map +1 -0
  209. package/dist/services/safety/rules/DangerousSinkRule.js +53 -0
  210. package/dist/services/safety/rules/FunctionalRoleRule.d.ts +3 -0
  211. package/dist/services/safety/rules/FunctionalRoleRule.d.ts.map +1 -0
  212. package/dist/services/safety/rules/FunctionalRoleRule.js +64 -0
  213. package/dist/services/safety/rules/HardConfidenceRule.d.ts +3 -0
  214. package/dist/services/safety/rules/HardConfidenceRule.d.ts.map +1 -0
  215. package/dist/services/safety/rules/HardConfidenceRule.js +22 -0
  216. package/dist/services/safety/rules/IntentAuditorRule.d.ts +3 -0
  217. package/dist/services/safety/rules/IntentAuditorRule.d.ts.map +1 -0
  218. package/dist/services/safety/rules/IntentAuditorRule.js +97 -0
  219. package/dist/services/safety/rules/SemanticOppositeRule.d.ts +3 -0
  220. package/dist/services/safety/rules/SemanticOppositeRule.d.ts.map +1 -0
  221. package/dist/services/safety/rules/SemanticOppositeRule.js +30 -0
  222. package/dist/services/safety/rules/StructuralOnlyRule.d.ts +3 -0
  223. package/dist/services/safety/rules/StructuralOnlyRule.d.ts.map +1 -0
  224. package/dist/services/safety/rules/StructuralOnlyRule.js +38 -0
  225. package/dist/services/safety/rules/VisibilityRule.d.ts +3 -0
  226. package/dist/services/safety/rules/VisibilityRule.d.ts.map +1 -0
  227. package/dist/services/safety/rules/VisibilityRule.js +53 -0
  228. package/dist/services/safety/rules/ZeroTrustRule.d.ts +3 -0
  229. package/dist/services/safety/rules/ZeroTrustRule.d.ts.map +1 -0
  230. package/dist/services/safety/rules/ZeroTrustRule.js +37 -0
  231. package/dist/services/safety/rules/index.d.ts +3 -0
  232. package/dist/services/safety/rules/index.d.ts.map +1 -0
  233. package/dist/services/safety/rules/index.js +34 -0
  234. package/dist/services/types.d.ts +59 -0
  235. package/dist/services/types.d.ts.map +1 -0
  236. package/dist/services/types.js +2 -0
  237. package/dist/storage/SnapshotManager.d.ts +5 -0
  238. package/dist/storage/SnapshotManager.d.ts.map +1 -0
  239. package/dist/storage/SnapshotManager.js +32 -0
  240. package/dist/types/index.d.ts +95 -0
  241. package/dist/types/index.d.ts.map +1 -0
  242. package/dist/types/index.js +7 -0
  243. package/dist/utils/DOMUtils.d.ts +49 -0
  244. package/dist/utils/DOMUtils.d.ts.map +1 -0
  245. package/dist/utils/DOMUtils.js +466 -0
  246. package/dist/utils/FileLock.d.ts +44 -0
  247. package/dist/utils/FileLock.d.ts.map +1 -0
  248. package/dist/utils/FileLock.js +189 -0
  249. package/dist/utils/IsolatedDiff.d.ts +45 -0
  250. package/dist/utils/IsolatedDiff.d.ts.map +1 -0
  251. package/dist/utils/IsolatedDiff.js +382 -0
  252. package/dist/utils/StackUtils.d.ts +11 -0
  253. package/dist/utils/StackUtils.d.ts.map +1 -0
  254. package/dist/utils/StackUtils.js +120 -0
  255. package/dist/utils/SummaryTable.d.ts +17 -0
  256. package/dist/utils/SummaryTable.d.ts.map +1 -0
  257. package/dist/utils/SummaryTable.js +37 -0
  258. package/dist/utils/atomicWrite.d.ts +9 -0
  259. package/dist/utils/atomicWrite.d.ts.map +1 -0
  260. package/dist/utils/atomicWrite.js +83 -0
  261. package/dist/utils/logger.d.ts +59 -0
  262. package/dist/utils/logger.d.ts.map +1 -0
  263. package/dist/utils/logger.js +123 -0
  264. package/dist/vendor/enquirer.d.ts +33 -0
  265. package/dist/vendor/enquirer.d.ts.map +1 -0
  266. package/dist/vendor/enquirer.js +11 -0
  267. package/package.json +71 -0
@@ -0,0 +1,189 @@
1
+ "use strict";
2
+ // src/utils/FileLock.ts
3
+ //
4
+ // Cross-process mutex built on atomic `mkdir`, extracted from the proven
5
+ // three-layer model in HealingCacheService so every shared-write site reuses
6
+ // ONE audited primitive instead of re-rolling its own lock.
7
+ //
8
+ // Why mkdir (not a lock library): `mkdir` of an existing directory fails with
9
+ // EEXIST atomically on every OS and filesystem Node supports — no extra
10
+ // dependency, no supply-chain surface, works identically across POSIX and
11
+ // Windows and across separate Playwright worker processes.
12
+ //
13
+ // Lock identity IS the directory path. Callers derive it from the resource
14
+ // being guarded (e.g. `<file>.sela.lock`), so locks on different files never
15
+ // contend — per-file granularity preserves worker scalability.
16
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
17
+ if (k2 === undefined) k2 = k;
18
+ var desc = Object.getOwnPropertyDescriptor(m, k);
19
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
20
+ desc = { enumerable: true, get: function() { return m[k]; } };
21
+ }
22
+ Object.defineProperty(o, k2, desc);
23
+ }) : (function(o, m, k, k2) {
24
+ if (k2 === undefined) k2 = k;
25
+ o[k2] = m[k];
26
+ }));
27
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
28
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
29
+ }) : function(o, v) {
30
+ o["default"] = v;
31
+ });
32
+ var __importStar = (this && this.__importStar) || (function () {
33
+ var ownKeys = function(o) {
34
+ ownKeys = Object.getOwnPropertyNames || function (o) {
35
+ var ar = [];
36
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
37
+ return ar;
38
+ };
39
+ return ownKeys(o);
40
+ };
41
+ return function (mod) {
42
+ if (mod && mod.__esModule) return mod;
43
+ var result = {};
44
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
45
+ __setModuleDefault(result, mod);
46
+ return result;
47
+ };
48
+ })();
49
+ Object.defineProperty(exports, "__esModule", { value: true });
50
+ exports.FileLock = void 0;
51
+ exports.lockDirFor = lockDirFor;
52
+ const fs = __importStar(require("fs"));
53
+ const path = __importStar(require("path"));
54
+ const logger_1 = require("./logger");
55
+ const DEFAULT_DELAYS_MS = [50, 100, 200, 400, 800, 800];
56
+ const DEFAULT_SYNC_DELAYS_MS = [10, 25, 50, 100];
57
+ class FileLock {
58
+ lockDir;
59
+ opts;
60
+ constructor(lockDir, opts = {}) {
61
+ this.lockDir = lockDir;
62
+ this.opts = opts;
63
+ }
64
+ warn(msg) {
65
+ (this.opts.logger?.warn ?? ((m) => logger_1.logger.warn(m)))(msg);
66
+ }
67
+ /** Ensure the lock dir's PARENT exists (never the lock dir itself — that
68
+ * would defeat the EEXIST mutex). */
69
+ ensureParent() {
70
+ const parent = path.dirname(this.lockDir);
71
+ if (!fs.existsSync(parent)) {
72
+ fs.mkdirSync(parent, { recursive: true });
73
+ }
74
+ }
75
+ reclaimIfStale() {
76
+ const staleMs = this.opts.staleMs ?? 0;
77
+ if (staleMs <= 0)
78
+ return false;
79
+ try {
80
+ const age = Date.now() - fs.statSync(this.lockDir).mtimeMs;
81
+ if (age > staleMs) {
82
+ fs.rmdirSync(this.lockDir);
83
+ this.warn(`[FileLock] reclaimed stale lock ${this.lockDir} (age ${Math.round(age)}ms)`);
84
+ return true;
85
+ }
86
+ }
87
+ catch {
88
+ // stat/rmdir lost a race with the real holder — fall back to a retry.
89
+ }
90
+ return false;
91
+ }
92
+ /** One attempt, no wait. True iff this call now holds the lock. */
93
+ tryAcquire() {
94
+ this.ensureParent();
95
+ try {
96
+ fs.mkdirSync(this.lockDir);
97
+ return true;
98
+ }
99
+ catch (err) {
100
+ const code = err.code;
101
+ if (code === "EEXIST") {
102
+ if (this.reclaimIfStale()) {
103
+ try {
104
+ fs.mkdirSync(this.lockDir);
105
+ return true;
106
+ }
107
+ catch {
108
+ return false;
109
+ }
110
+ }
111
+ return false;
112
+ }
113
+ this.warn(`[FileLock] acquire failed at ${this.lockDir}: ${err.message}`);
114
+ return false;
115
+ }
116
+ }
117
+ /** Async acquire with exponential backoff. */
118
+ async acquire() {
119
+ const delays = this.opts.delaysMs ?? DEFAULT_DELAYS_MS;
120
+ for (let attempt = 0; attempt <= delays.length; attempt++) {
121
+ if (this.tryAcquire())
122
+ return true;
123
+ if (attempt === delays.length)
124
+ break;
125
+ await sleep(delays[attempt]);
126
+ }
127
+ return false;
128
+ }
129
+ release() {
130
+ try {
131
+ fs.rmdirSync(this.lockDir);
132
+ }
133
+ catch {
134
+ // best-effort — a missing lock dir is already the desired end state.
135
+ }
136
+ }
137
+ /**
138
+ * Run `fn` while holding the lock. Returns `fn`'s result, or `undefined`
139
+ * (and warns) when the lock could not be acquired within the backoff
140
+ * window. Mirrors the cache contract: a contended write is skipped, never
141
+ * thrown — the lock guards an optimisation/idempotent write, not a
142
+ * correctness invariant the caller cannot recover from.
143
+ */
144
+ async withLock(fn) {
145
+ if (!(await this.acquire())) {
146
+ this.warn(`[FileLock] could not acquire ${this.lockDir} — skipped`);
147
+ return undefined;
148
+ }
149
+ try {
150
+ return await fn();
151
+ }
152
+ finally {
153
+ this.release();
154
+ }
155
+ }
156
+ /** Synchronous variant — busy-waits between attempts. */
157
+ withLockSync(fn) {
158
+ const delays = this.opts.syncDelaysMs ?? DEFAULT_SYNC_DELAYS_MS;
159
+ for (let attempt = 0; attempt <= delays.length; attempt++) {
160
+ if (this.tryAcquire()) {
161
+ try {
162
+ return fn();
163
+ }
164
+ finally {
165
+ this.release();
166
+ }
167
+ }
168
+ if (attempt === delays.length)
169
+ break;
170
+ sleepBusy(delays[attempt]);
171
+ }
172
+ this.warn(`[FileLock] could not acquire ${this.lockDir} (sync) — skipped`);
173
+ return undefined;
174
+ }
175
+ }
176
+ exports.FileLock = FileLock;
177
+ /** Derive the conventional lock-dir path for a guarded file. */
178
+ function lockDirFor(filePath) {
179
+ return `${filePath}.sela.lock`;
180
+ }
181
+ function sleep(ms) {
182
+ return new Promise((res) => setTimeout(res, ms));
183
+ }
184
+ function sleepBusy(ms) {
185
+ const end = Date.now() + ms;
186
+ while (Date.now() < end) {
187
+ // intentional spin — sync callers cannot await
188
+ }
189
+ }
@@ -0,0 +1,45 @@
1
+ export interface IsolatedDiffOptions {
2
+ /** Lines of unchanged context around each hunk. Default 3 (git default). */
3
+ context?: number;
4
+ /**
5
+ * Path to render in the `--- a/<path>` / `+++ b/<path>` preamble. When
6
+ * omitted the preamble is dropped entirely (hunks only).
7
+ */
8
+ filePath?: string;
9
+ }
10
+ /**
11
+ * Generate a `git`-style unified diff between two in-memory strings.
12
+ * Returns `""` when the two buffers are identical.
13
+ */
14
+ export declare function generateUnifiedDiff(before: string, after: string, opts?: IsolatedDiffOptions): string;
15
+ export interface ChangeBlock {
16
+ /** Old text (newlines preserved). Empty string for pure additions. */
17
+ oldBlock: string;
18
+ /** New text (newlines preserved). Empty string for pure deletions. */
19
+ newBlock: string;
20
+ /** 1-based line in `before` where the block starts (0 if pure add). */
21
+ oldLineStart: number;
22
+ /** 1-based line in `after` where the block starts (0 if pure delete). */
23
+ newLineStart: number;
24
+ /**
25
+ * For pure additions: a non-empty line of unchanged anchor text immediately
26
+ * PRECEDING the new block in the `before` buffer. The replayer uses this
27
+ * to locate the insertion point. `null` when adding at the very top.
28
+ */
29
+ anchorBefore: string | null;
30
+ }
31
+ export declare function extractChangeBlocks(before: string, after: string): ChangeBlock[];
32
+ export interface ApplyBlocksResult {
33
+ ok: boolean;
34
+ result?: string;
35
+ /** Human-readable failure reason - surfaced in the PR-automation log. */
36
+ reason?: string;
37
+ }
38
+ /**
39
+ * Replay a list of change blocks against a fresh base buffer (typically
40
+ * the file's `HEAD` blob). Each block is applied via UNIQUE substring
41
+ * replacement - when the old block is missing or ambiguous in the base,
42
+ * we fail loudly rather than silently mutating the wrong spot.
43
+ */
44
+ export declare function applyChangeBlocks(base: string, blocks: ChangeBlock[]): ApplyBlocksResult;
45
+ //# sourceMappingURL=IsolatedDiff.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"IsolatedDiff.d.ts","sourceRoot":"","sources":["../../src/utils/IsolatedDiff.ts"],"names":[],"mappings":"AAcA,MAAM,WAAW,mBAAmB;IAClC,4EAA4E;IAC5E,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB;;;OAGG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED;;;GAGG;AACH,wBAAgB,mBAAmB,CACjC,MAAM,EAAE,MAAM,EACd,KAAK,EAAE,MAAM,EACb,IAAI,GAAE,mBAAwB,GAC7B,MAAM,CAsBR;AA6PD,MAAM,WAAW,WAAW;IAC1B,sEAAsE;IACtE,QAAQ,EAAE,MAAM,CAAC;IACjB,sEAAsE;IACtE,QAAQ,EAAE,MAAM,CAAC;IACjB,uEAAuE;IACvE,YAAY,EAAE,MAAM,CAAC;IACrB,yEAAyE;IACzE,YAAY,EAAE,MAAM,CAAC;IACrB;;;;OAIG;IACH,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;CAC7B;AAED,wBAAgB,mBAAmB,CACjC,MAAM,EAAE,MAAM,EACd,KAAK,EAAE,MAAM,GACZ,WAAW,EAAE,CAoDf;AAED,MAAM,WAAW,iBAAiB;IAChC,EAAE,EAAE,OAAO,CAAC;IACZ,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,yEAAyE;IACzE,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAC/B,IAAI,EAAE,MAAM,EACZ,MAAM,EAAE,WAAW,EAAE,GACpB,iBAAiB,CAuDnB"}
@@ -0,0 +1,382 @@
1
+ "use strict";
2
+ // src/utils/IsolatedDiff.ts
3
+ //
4
+ // Pure-JS unified-diff generator. No external `diff` package, no shell-out to
5
+ // `git diff` - the result is guaranteed to be a function ONLY of the two
6
+ // in-memory buffers passed in, never of any global working-tree state.
7
+ //
8
+ // This is the foundation of the Clean-Room Architecture: the HTML report
9
+ // renders diffs computed here, so a developer's pre-existing uncommitted
10
+ // edits CANNOT leak into Sela's report.
11
+ //
12
+ // Algorithm: classic LCS over lines, then a unified-diff emitter with
13
+ // configurable context. Memory is O(N*M) which is fine for the spec files
14
+ // Sela mutates (typically a few thousand lines max).
15
+ Object.defineProperty(exports, "__esModule", { value: true });
16
+ exports.generateUnifiedDiff = generateUnifiedDiff;
17
+ exports.extractChangeBlocks = extractChangeBlocks;
18
+ exports.applyChangeBlocks = applyChangeBlocks;
19
+ /**
20
+ * Generate a `git`-style unified diff between two in-memory strings.
21
+ * Returns `""` when the two buffers are identical.
22
+ */
23
+ function generateUnifiedDiff(before, after, opts = {}) {
24
+ if (before === after)
25
+ return "";
26
+ const context = Math.max(0, opts.context ?? 3);
27
+ const a = splitKeepEol(before);
28
+ const b = splitKeepEol(after);
29
+ const ops = diffLines(a, b);
30
+ if (ops.length === 0)
31
+ return "";
32
+ const hunks = groupHunks(ops, { a, b, context });
33
+ if (hunks.length === 0)
34
+ return "";
35
+ const out = [];
36
+ if (opts.filePath) {
37
+ const p = opts.filePath.split(/[\\/]/).join("/");
38
+ out.push(`diff --git a/${p} b/${p}`);
39
+ out.push(`--- a/${p}`);
40
+ out.push(`+++ b/${p}`);
41
+ }
42
+ for (const hunk of hunks) {
43
+ out.push(formatHunkHeader(hunk));
44
+ for (const row of hunk.rows)
45
+ out.push(row);
46
+ }
47
+ return out.join("\n") + "\n";
48
+ }
49
+ // ────────────────────────────────────────────────────────────────────
50
+ // LINE SPLITTER - preserves trailing newline / CRLF so we can emit the
51
+ // canonical `` marker when needed.
52
+ // ────────────────────────────────────────────────────────────────────
53
+ function splitKeepEol(text) {
54
+ if (text.length === 0)
55
+ return [];
56
+ const out = [];
57
+ let start = 0;
58
+ for (let i = 0; i < text.length; i++) {
59
+ if (text.charCodeAt(i) === 10 /* \n */) {
60
+ out.push(text.slice(start, i + 1));
61
+ start = i + 1;
62
+ }
63
+ }
64
+ if (start < text.length)
65
+ out.push(text.slice(start));
66
+ return out;
67
+ }
68
+ function stripEol(line) {
69
+ if (line.endsWith("\r\n"))
70
+ return line.slice(0, -2);
71
+ if (line.endsWith("\n"))
72
+ return line.slice(0, -1);
73
+ return line;
74
+ }
75
+ function hasEol(line) {
76
+ return line.endsWith("\n");
77
+ }
78
+ function diffLines(a, b) {
79
+ const m = a.length;
80
+ const n = b.length;
81
+ const w = n + 1;
82
+ const dp = new Uint32Array((m + 1) * w);
83
+ for (let i = m - 1; i >= 0; i--) {
84
+ const ai = a[i];
85
+ for (let j = n - 1; j >= 0; j--) {
86
+ if (ai === b[j]) {
87
+ dp[i * w + j] = dp[(i + 1) * w + (j + 1)] + 1;
88
+ }
89
+ else {
90
+ const down = dp[(i + 1) * w + j];
91
+ const right = dp[i * w + (j + 1)];
92
+ dp[i * w + j] = down >= right ? down : right;
93
+ }
94
+ }
95
+ }
96
+ const ops = [];
97
+ let i = 0;
98
+ let j = 0;
99
+ while (i < m && j < n) {
100
+ if (a[i] === b[j]) {
101
+ ops.push({ kind: "eq", a: i, b: j });
102
+ i++;
103
+ j++;
104
+ }
105
+ else if (dp[(i + 1) * w + j] >= dp[i * w + (j + 1)]) {
106
+ ops.push({ kind: "del", a: i });
107
+ i++;
108
+ }
109
+ else {
110
+ ops.push({ kind: "add", b: j });
111
+ j++;
112
+ }
113
+ }
114
+ while (i < m) {
115
+ ops.push({ kind: "del", a: i });
116
+ i++;
117
+ }
118
+ while (j < n) {
119
+ ops.push({ kind: "add", b: j });
120
+ j++;
121
+ }
122
+ return ops;
123
+ }
124
+ function groupHunks(ops, ctx) {
125
+ const hunks = [];
126
+ const ctxN = ctx.context;
127
+ let i = 0;
128
+ while (i < ops.length) {
129
+ // Locate the next non-equal op.
130
+ let firstChange = -1;
131
+ for (let k = i; k < ops.length; k++) {
132
+ if (ops[k].kind !== "eq") {
133
+ firstChange = k;
134
+ break;
135
+ }
136
+ }
137
+ if (firstChange === -1)
138
+ break;
139
+ // Leading-context start, bounded by previous hunk end.
140
+ let hunkStart = firstChange - ctxN;
141
+ if (hunkStart < i)
142
+ hunkStart = i;
143
+ if (hunkStart < 0)
144
+ hunkStart = 0;
145
+ let hunkEnd = firstChange;
146
+ // Extend by merging change groups separated by <= 2*ctxN equality lines.
147
+ // Avoid merging across the very end of `ops`.
148
+ while (true) {
149
+ let cursor = hunkEnd + 1;
150
+ let runEqs = 0;
151
+ let nextChange = -1;
152
+ while (cursor < ops.length) {
153
+ if (ops[cursor].kind === "eq") {
154
+ runEqs++;
155
+ cursor++;
156
+ continue;
157
+ }
158
+ nextChange = cursor;
159
+ break;
160
+ }
161
+ if (nextChange !== -1 && runEqs <= 2 * ctxN) {
162
+ hunkEnd = nextChange;
163
+ }
164
+ else {
165
+ break;
166
+ }
167
+ }
168
+ // Trailing context.
169
+ let trailing = 0;
170
+ while (hunkEnd + 1 < ops.length &&
171
+ ops[hunkEnd + 1].kind === "eq" &&
172
+ trailing < ctxN) {
173
+ hunkEnd++;
174
+ trailing++;
175
+ }
176
+ // Materialise rows + compute header coordinates.
177
+ const rows = [];
178
+ let oldStartIdx = -1;
179
+ let newStartIdx = -1;
180
+ let oldLen = 0;
181
+ let newLen = 0;
182
+ for (let k = hunkStart; k <= hunkEnd; k++) {
183
+ const op = ops[k];
184
+ if (op.kind === "eq") {
185
+ if (oldStartIdx === -1)
186
+ oldStartIdx = op.a;
187
+ if (newStartIdx === -1)
188
+ newStartIdx = op.b;
189
+ const line = ctx.a[op.a];
190
+ rows.push(" " + stripEol(line));
191
+ oldLen++;
192
+ newLen++;
193
+ if (!hasEol(line))
194
+ rows.push("\");
195
+ }
196
+ else if (op.kind === "del") {
197
+ if (oldStartIdx === -1)
198
+ oldStartIdx = op.a;
199
+ if (newStartIdx === -1) {
200
+ // Anchor on the corresponding `b` position. For pure-deletions at
201
+ // the top of file `op.a === 0` ⇒ newStart should be 0 (special
202
+ // header form `+0,0`). For mid-file deletions use the running b
203
+ // cursor - derive it from prior ops.
204
+ newStartIdx = computeBAnchor(ops, k);
205
+ }
206
+ const line = ctx.a[op.a];
207
+ rows.push("-" + stripEol(line));
208
+ oldLen++;
209
+ if (!hasEol(line))
210
+ rows.push("\");
211
+ }
212
+ else {
213
+ if (newStartIdx === -1)
214
+ newStartIdx = op.b;
215
+ if (oldStartIdx === -1)
216
+ oldStartIdx = computeAAnchor(ops, k);
217
+ const line = ctx.b[op.b];
218
+ rows.push("+" + stripEol(line));
219
+ newLen++;
220
+ if (!hasEol(line))
221
+ rows.push("\");
222
+ }
223
+ }
224
+ hunks.push({
225
+ oldStart: oldLen === 0 ? oldStartIdx : oldStartIdx + 1,
226
+ oldLen,
227
+ newStart: newLen === 0 ? newStartIdx : newStartIdx + 1,
228
+ newLen,
229
+ rows,
230
+ });
231
+ i = hunkEnd + 1;
232
+ }
233
+ return hunks;
234
+ }
235
+ /**
236
+ * For a pure-add op at position `k`, walk backwards to find the prior
237
+ * `a` index (so we know which line of the old file the hunk starts at).
238
+ */
239
+ function computeAAnchor(ops, k) {
240
+ for (let p = k - 1; p >= 0; p--) {
241
+ const op = ops[p];
242
+ if (op.kind === "eq")
243
+ return op.a + 1;
244
+ if (op.kind === "del")
245
+ return op.a + 1;
246
+ }
247
+ return 0;
248
+ }
249
+ function computeBAnchor(ops, k) {
250
+ for (let p = k - 1; p >= 0; p--) {
251
+ const op = ops[p];
252
+ if (op.kind === "eq")
253
+ return op.b + 1;
254
+ if (op.kind === "add")
255
+ return op.b + 1;
256
+ }
257
+ return 0;
258
+ }
259
+ function formatHunkHeader(hunk) {
260
+ const oldPart = hunk.oldLen === 1 ? `${hunk.oldStart}` : `${hunk.oldStart},${hunk.oldLen}`;
261
+ const newPart = hunk.newLen === 1 ? `${hunk.newStart}` : `${hunk.newStart},${hunk.newLen}`;
262
+ return `@@ -${oldPart} +${newPart} @@`;
263
+ }
264
+ function extractChangeBlocks(before, after) {
265
+ if (before === after)
266
+ return [];
267
+ const a = splitKeepEol(before);
268
+ const b = splitKeepEol(after);
269
+ const ops = diffLines(a, b);
270
+ const blocks = [];
271
+ let i = 0;
272
+ while (i < ops.length) {
273
+ if (ops[i].kind === "eq") {
274
+ i++;
275
+ continue;
276
+ }
277
+ const start = i;
278
+ while (i < ops.length && ops[i].kind !== "eq")
279
+ i++;
280
+ const oldLines = [];
281
+ const newLines = [];
282
+ let oldStart = -1;
283
+ let newStart = -1;
284
+ for (let k = start; k < i; k++) {
285
+ const op = ops[k];
286
+ if (op.kind === "del") {
287
+ if (oldStart === -1)
288
+ oldStart = op.a;
289
+ oldLines.push(a[op.a]);
290
+ }
291
+ else if (op.kind === "add") {
292
+ if (newStart === -1)
293
+ newStart = op.b;
294
+ newLines.push(b[op.b]);
295
+ }
296
+ }
297
+ let anchorBefore = null;
298
+ if (oldLines.length === 0) {
299
+ // Pure addition - find preceding unchanged anchor line.
300
+ for (let k = start - 1; k >= 0; k--) {
301
+ const op = ops[k];
302
+ if (op.kind === "eq") {
303
+ anchorBefore = a[op.a];
304
+ break;
305
+ }
306
+ }
307
+ }
308
+ blocks.push({
309
+ oldBlock: oldLines.join(""),
310
+ newBlock: newLines.join(""),
311
+ oldLineStart: oldStart >= 0 ? oldStart + 1 : 0,
312
+ newLineStart: newStart >= 0 ? newStart + 1 : 0,
313
+ anchorBefore,
314
+ });
315
+ }
316
+ return blocks;
317
+ }
318
+ /**
319
+ * Replay a list of change blocks against a fresh base buffer (typically
320
+ * the file's `HEAD` blob). Each block is applied via UNIQUE substring
321
+ * replacement - when the old block is missing or ambiguous in the base,
322
+ * we fail loudly rather than silently mutating the wrong spot.
323
+ */
324
+ function applyChangeBlocks(base, blocks) {
325
+ let working = base;
326
+ for (const block of blocks) {
327
+ if (block.oldBlock.length === 0) {
328
+ // Pure addition.
329
+ if (block.anchorBefore === null) {
330
+ if (working.length === 0) {
331
+ // Empty file - prepend.
332
+ working = block.newBlock + working;
333
+ continue;
334
+ }
335
+ // Prepend at top of file.
336
+ working = block.newBlock + working;
337
+ continue;
338
+ }
339
+ const idx = working.indexOf(block.anchorBefore);
340
+ if (idx === -1) {
341
+ return {
342
+ ok: false,
343
+ reason: `pure-add anchor not found in clean base: "${snippet(block.anchorBefore)}"`,
344
+ };
345
+ }
346
+ const lastIdx = working.lastIndexOf(block.anchorBefore);
347
+ if (idx !== lastIdx) {
348
+ return {
349
+ ok: false,
350
+ reason: `pure-add anchor is not unique in clean base: "${snippet(block.anchorBefore)}"`,
351
+ };
352
+ }
353
+ const insertAt = idx + block.anchorBefore.length;
354
+ working =
355
+ working.slice(0, insertAt) + block.newBlock + working.slice(insertAt);
356
+ continue;
357
+ }
358
+ const idx = working.indexOf(block.oldBlock);
359
+ if (idx === -1) {
360
+ return {
361
+ ok: false,
362
+ reason: `block not found in clean base - developer's dirty state likely overlaps Sela's mutation site: "${snippet(block.oldBlock)}"`,
363
+ };
364
+ }
365
+ const lastIdx = working.lastIndexOf(block.oldBlock);
366
+ if (idx !== lastIdx) {
367
+ return {
368
+ ok: false,
369
+ reason: `block matches multiple locations in clean base - refusing to guess: "${snippet(block.oldBlock)}"`,
370
+ };
371
+ }
372
+ working =
373
+ working.slice(0, idx) +
374
+ block.newBlock +
375
+ working.slice(idx + block.oldBlock.length);
376
+ }
377
+ return { ok: true, result: working };
378
+ }
379
+ function snippet(s) {
380
+ const flat = s.replace(/\r?\n/g, "\\n");
381
+ return flat.length > 80 ? flat.slice(0, 77) + "..." : flat;
382
+ }
@@ -0,0 +1,11 @@
1
+ export declare class StackUtils {
2
+ static getCallerInfo(stackOverride?: string): {
3
+ filePath: string;
4
+ fileName: string;
5
+ line: number;
6
+ column: number;
7
+ locationKey: string;
8
+ };
9
+ static getActionKey(testTitle: string, action: string, index: number): string;
10
+ }
11
+ //# sourceMappingURL=StackUtils.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"StackUtils.d.ts","sourceRoot":"","sources":["../../src/utils/StackUtils.ts"],"names":[],"mappings":"AAGA,qBAAa,UAAU;IACrB,MAAM,CAAC,aAAa,CAAC,aAAa,CAAC,EAAE,MAAM,GAAG;QAC5C,QAAQ,EAAE,MAAM,CAAC;QACjB,QAAQ,EAAE,MAAM,CAAC;QACjB,IAAI,EAAE,MAAM,CAAC;QACb,MAAM,EAAE,MAAM,CAAC;QACf,WAAW,EAAE,MAAM,CAAC;KACrB;IAmED,MAAM,CAAC,YAAY,CACjB,SAAS,EAAE,MAAM,EACjB,MAAM,EAAE,MAAM,EACd,KAAK,EAAE,MAAM,GACZ,MAAM;CA6BV"}