swipium 1.5.0 → 2.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.
Files changed (498) hide show
  1. package/CHANGELOG.md +171 -0
  2. package/README.md +173 -280
  3. package/THREAT_MODEL.md +223 -47
  4. package/dist/appMap/automationLink.js +30 -27
  5. package/dist/appMap/automationLink.js.map +1 -1
  6. package/dist/appMap/build.js +47 -21
  7. package/dist/appMap/build.js.map +1 -1
  8. package/dist/appMap/codeIndex.js +2 -2
  9. package/dist/appMap/codeIndex.js.map +1 -1
  10. package/dist/appMap/featureIndex.js +3 -3
  11. package/dist/appMap/featureIndex.js.map +1 -1
  12. package/dist/appMap/featureModel.js +2 -2
  13. package/dist/appMap/featureModel.js.map +1 -1
  14. package/dist/appMap/firstRunApply.js +2 -2
  15. package/dist/appMap/firstRunApply.js.map +1 -1
  16. package/dist/appMap/issues.js +10 -10
  17. package/dist/appMap/issues.js.map +1 -1
  18. package/dist/appMap/migrations.js +5 -5
  19. package/dist/appMap/migrations.js.map +1 -1
  20. package/dist/appMap/prelaunch.js +2 -2
  21. package/dist/appMap/prelaunch.js.map +1 -1
  22. package/dist/appMap/projectRegistry.js +77 -11
  23. package/dist/appMap/projectRegistry.js.map +1 -1
  24. package/dist/appMap/provenance.js +2 -2
  25. package/dist/appMap/provenance.js.map +1 -1
  26. package/dist/appMap/query.js +2 -2
  27. package/dist/appMap/query.js.map +1 -1
  28. package/dist/appMap/runtimeMerge.js +3 -3
  29. package/dist/appMap/runtimeMerge.js.map +1 -1
  30. package/dist/appMap/schema.js +1 -1
  31. package/dist/appMap/schema.js.map +1 -1
  32. package/dist/appMap/screenMatch.js +1 -1
  33. package/dist/appMap/screenMatch.js.map +1 -1
  34. package/dist/appMap/staticScan.js +17 -17
  35. package/dist/appMap/staticScan.js.map +1 -1
  36. package/dist/appMap/store.js +55 -7
  37. package/dist/appMap/store.js.map +1 -1
  38. package/dist/appMap/tsAstScan.js +3 -3
  39. package/dist/appMap/tsAstScan.js.map +1 -1
  40. package/dist/artifacts/bundletool.js +5 -5
  41. package/dist/artifacts/bundletool.js.map +1 -1
  42. package/dist/artifacts/resolve.js +10 -10
  43. package/dist/artifacts/resolve.js.map +1 -1
  44. package/dist/automation/capabilities.js +3 -3
  45. package/dist/automation/capabilities.js.map +1 -1
  46. package/dist/automation/gestures.js +1 -1
  47. package/dist/automation/gestures.js.map +1 -1
  48. package/dist/automation/plan.js +7 -7
  49. package/dist/automation/plan.js.map +1 -1
  50. package/dist/automation/report.js +3 -3
  51. package/dist/automation/report.js.map +1 -1
  52. package/dist/automation/selectors.js +14 -25
  53. package/dist/automation/selectors.js.map +1 -1
  54. package/dist/automation/types.js +1 -1
  55. package/dist/automation/types.js.map +1 -1
  56. package/dist/automation/waits.js +5 -5
  57. package/dist/automation/waits.js.map +1 -1
  58. package/dist/automation/webview.js +2 -2
  59. package/dist/automation/webview.js.map +1 -1
  60. package/dist/automationGen/appiumModel.js +13 -7
  61. package/dist/automationGen/appiumModel.js.map +1 -1
  62. package/dist/automationGen/ciEmitter.js +5 -5
  63. package/dist/automationGen/ciEmitter.js.map +1 -1
  64. package/dist/automationGen/identifiers.js +224 -0
  65. package/dist/automationGen/identifiers.js.map +1 -0
  66. package/dist/automationGen/jsEmitter.js +265 -106
  67. package/dist/automationGen/jsEmitter.js.map +1 -1
  68. package/dist/automationGen/packagePatch.js +2 -2
  69. package/dist/automationGen/packagePatch.js.map +1 -1
  70. package/dist/automationGen/platformResolve.js +36 -0
  71. package/dist/automationGen/platformResolve.js.map +1 -0
  72. package/dist/automationGen/projectProfile.js +71 -26
  73. package/dist/automationGen/projectProfile.js.map +1 -1
  74. package/dist/automationGen/pythonEmitter.js +276 -105
  75. package/dist/automationGen/pythonEmitter.js.map +1 -1
  76. package/dist/automationGen/readmeEmitter.js +7 -6
  77. package/dist/automationGen/readmeEmitter.js.map +1 -1
  78. package/dist/{tools/automationGenerate.js → automationGen/run.js} +72 -113
  79. package/dist/automationGen/run.js.map +1 -0
  80. package/dist/automationGen/suitePlan.js +6 -4
  81. package/dist/automationGen/suitePlan.js.map +1 -1
  82. package/dist/automationGen/validation.js +20 -8
  83. package/dist/automationGen/validation.js.map +1 -1
  84. package/dist/build/parseBuildLog.js +2 -2
  85. package/dist/build/parseBuildLog.js.map +1 -1
  86. package/dist/build/plan.js +7 -7
  87. package/dist/build/plan.js.map +1 -1
  88. package/dist/ci/preflight.js +13 -4
  89. package/dist/ci/preflight.js.map +1 -1
  90. package/dist/cli/gc.js +74 -0
  91. package/dist/cli/gc.js.map +1 -0
  92. package/dist/cli/init.js +217 -26
  93. package/dist/cli/init.js.map +1 -1
  94. package/dist/cli/main.js +50 -0
  95. package/dist/cli/main.js.map +1 -0
  96. package/dist/cli/report.js +218 -0
  97. package/dist/cli/report.js.map +1 -0
  98. package/dist/cli/scan.js +35 -14
  99. package/dist/cli/scan.js.map +1 -1
  100. package/dist/cli/suite.js +6 -6
  101. package/dist/cli/suite.js.map +1 -1
  102. package/dist/cli/verify.js +7 -7
  103. package/dist/cli/verify.js.map +1 -1
  104. package/dist/consent/consent.js +162 -5
  105. package/dist/consent/consent.js.map +1 -1
  106. package/dist/context/detect.js +45 -14
  107. package/dist/context/detect.js.map +1 -1
  108. package/dist/context/findApps.js +6 -6
  109. package/dist/context/findApps.js.map +1 -1
  110. package/dist/context/projectRoot.js +174 -19
  111. package/dist/context/projectRoot.js.map +1 -1
  112. package/dist/context/scan.js +1 -1
  113. package/dist/context/scan.js.map +1 -1
  114. package/dist/core/capabilityGroups.js +90 -0
  115. package/dist/core/capabilityGroups.js.map +1 -0
  116. package/dist/core/target.js +3 -3
  117. package/dist/core/target.js.map +1 -1
  118. package/dist/core/targetPlan.js +65 -62
  119. package/dist/core/targetPlan.js.map +1 -1
  120. package/dist/drivers/DirectDriver.js +300 -36
  121. package/dist/drivers/DirectDriver.js.map +1 -1
  122. package/dist/drivers/SimctlDriver.js +120 -7
  123. package/dist/drivers/SimctlDriver.js.map +1 -1
  124. package/dist/drivers/WdaDriver.js +350 -50
  125. package/dist/drivers/WdaDriver.js.map +1 -1
  126. package/dist/explore/candidates.js +2 -2
  127. package/dist/explore/candidates.js.map +1 -1
  128. package/dist/explore/graph.js +5 -5
  129. package/dist/explore/graph.js.map +1 -1
  130. package/dist/explore/policy.js +5 -5
  131. package/dist/explore/policy.js.map +1 -1
  132. package/dist/explore/runner.js +301 -284
  133. package/dist/explore/runner.js.map +1 -1
  134. package/dist/explore/signatures.js +1 -1
  135. package/dist/featureTesting/executionBootstrap.js +29 -30
  136. package/dist/featureTesting/executionBootstrap.js.map +1 -1
  137. package/dist/featureTesting/featureMap.js +2 -2
  138. package/dist/featureTesting/featureMap.js.map +1 -1
  139. package/dist/featureTesting/featureScope.js +12 -12
  140. package/dist/featureTesting/featureScope.js.map +1 -1
  141. package/dist/featureTesting/mapFeatureScope.js +7 -7
  142. package/dist/featureTesting/mapFeatureScope.js.map +1 -1
  143. package/dist/featureTesting/objectiveModel.js +10 -10
  144. package/dist/featureTesting/objectiveModel.js.map +1 -1
  145. package/dist/featureTesting/resultMerge.js +6 -6
  146. package/dist/featureTesting/resultMerge.js.map +1 -1
  147. package/dist/featureTesting/sources.js +4 -4
  148. package/dist/featureTesting/sources.js.map +1 -1
  149. package/dist/featureTesting/suiteBridge.js +1 -1
  150. package/dist/featureTesting/suiteBridge.js.map +1 -1
  151. package/dist/featureTesting/synonyms.js +1 -1
  152. package/dist/featureTesting/synonyms.js.map +1 -1
  153. package/dist/featureTesting/testCaseFactory.js +11 -11
  154. package/dist/featureTesting/testCaseFactory.js.map +1 -1
  155. package/dist/featureTesting/testPlan.js +3 -3
  156. package/dist/featureTesting/testPlan.js.map +1 -1
  157. package/dist/firstRun/authStateMachine.js +1 -1
  158. package/dist/firstRun/authStateMachine.js.map +1 -1
  159. package/dist/firstRun/classifyScreen.js +5 -5
  160. package/dist/firstRun/classifyScreen.js.map +1 -1
  161. package/dist/firstRun/firstRunPlanner.js +11 -11
  162. package/dist/firstRun/firstRunPlanner.js.map +1 -1
  163. package/dist/firstRun/firstRunRunner.js +8 -8
  164. package/dist/firstRun/firstRunRunner.js.map +1 -1
  165. package/dist/firstRun/generatedDataPolicy.js +9 -9
  166. package/dist/firstRun/generatedDataPolicy.js.map +1 -1
  167. package/dist/firstRun/inputPlanner.js +5 -5
  168. package/dist/firstRun/inputPlanner.js.map +1 -1
  169. package/dist/firstRun/onboardingStateMachine.js +1 -1
  170. package/dist/firstRun/onboardingStateMachine.js.map +1 -1
  171. package/dist/firstRun/paywallPolicy.js +3 -3
  172. package/dist/firstRun/paywallPolicy.js.map +1 -1
  173. package/dist/firstRun/types.js +1 -1
  174. package/dist/firstRun/types.js.map +1 -1
  175. package/dist/fixtures/catalog.js +33 -3
  176. package/dist/fixtures/catalog.js.map +1 -1
  177. package/dist/fixtures/load.js +50 -0
  178. package/dist/fixtures/load.js.map +1 -0
  179. package/dist/flows/discover.js +2 -2
  180. package/dist/flows/discover.js.map +1 -1
  181. package/dist/flows/generate.js +20 -10
  182. package/dist/flows/generate.js.map +1 -1
  183. package/dist/flows/pack.js +3 -3
  184. package/dist/flows/pack.js.map +1 -1
  185. package/dist/flows/paths.js +57 -0
  186. package/dist/flows/paths.js.map +1 -0
  187. package/dist/flows/repair.js +111 -23
  188. package/dist/flows/repair.js.map +1 -1
  189. package/dist/flows/run.js +153 -57
  190. package/dist/flows/run.js.map +1 -1
  191. package/dist/flows/schema.js +50 -5
  192. package/dist/flows/schema.js.map +1 -1
  193. package/dist/flows/seedExec.js +4 -4
  194. package/dist/flows/seedExec.js.map +1 -1
  195. package/dist/flows/templates.js +5 -5
  196. package/dist/index.js +50 -8
  197. package/dist/index.js.map +1 -1
  198. package/dist/issues/classify.js +16 -16
  199. package/dist/issues/classify.js.map +1 -1
  200. package/dist/issues/fingerprint.js +24 -4
  201. package/dist/issues/fingerprint.js.map +1 -1
  202. package/dist/issues/index.js +94 -31
  203. package/dist/issues/index.js.map +1 -1
  204. package/dist/issues/metrics.js +4 -4
  205. package/dist/issues/metrics.js.map +1 -1
  206. package/dist/issues/recurrence.js +35 -8
  207. package/dist/issues/recurrence.js.map +1 -1
  208. package/dist/issues/report.js +4 -4
  209. package/dist/issues/report.js.map +1 -1
  210. package/dist/issues/reportBridge.js +4 -4
  211. package/dist/issues/reportBridge.js.map +1 -1
  212. package/dist/issues/schema.js +2 -2
  213. package/dist/issues/schema.js.map +1 -1
  214. package/dist/issues/sourceRevision.js +2 -2
  215. package/dist/issues/sourceRevision.js.map +1 -1
  216. package/dist/issues/store.js +110 -32
  217. package/dist/issues/store.js.map +1 -1
  218. package/dist/lib/abortScope.js +48 -0
  219. package/dist/lib/abortScope.js.map +1 -0
  220. package/dist/lib/android.js +75 -7
  221. package/dist/lib/android.js.map +1 -1
  222. package/dist/lib/coordSpace.js +1 -1
  223. package/dist/lib/coordSpace.js.map +1 -1
  224. package/dist/lib/device.js +32 -7
  225. package/dist/lib/device.js.map +1 -1
  226. package/dist/lib/gestures.js +141 -0
  227. package/dist/lib/gestures.js.map +1 -0
  228. package/dist/lib/gitignore.js +4 -3
  229. package/dist/lib/gitignore.js.map +1 -1
  230. package/dist/lib/image.js +1 -1
  231. package/dist/lib/image.js.map +1 -1
  232. package/dist/lib/lockfile.js +370 -31
  233. package/dist/lib/lockfile.js.map +1 -1
  234. package/dist/lib/needsInput.js +3 -3
  235. package/dist/lib/needsInput.js.map +1 -1
  236. package/dist/lib/png.js +2 -2
  237. package/dist/lib/png.js.map +1 -1
  238. package/dist/lib/redact.js +170 -9
  239. package/dist/lib/redact.js.map +1 -1
  240. package/dist/lib/result.js +79 -10
  241. package/dist/lib/result.js.map +1 -1
  242. package/dist/lib/schemaHash.js +3 -3
  243. package/dist/lib/schemaHash.js.map +1 -1
  244. package/dist/lib/sensitive.js +3 -2
  245. package/dist/lib/sensitive.js.map +1 -1
  246. package/dist/lib/simctl.js +3 -3
  247. package/dist/lib/simctl.js.map +1 -1
  248. package/dist/lib/spawn.js +52 -10
  249. package/dist/lib/spawn.js.map +1 -1
  250. package/dist/lib/toolAnnotations.js +114 -0
  251. package/dist/lib/toolAnnotations.js.map +1 -0
  252. package/dist/lib/wda.js +294 -15
  253. package/dist/lib/wda.js.map +1 -1
  254. package/dist/mobileAudit/checks.js +12 -12
  255. package/dist/mobileAudit/checks.js.map +1 -1
  256. package/dist/mobileAudit/evidence.js +4 -4
  257. package/dist/mobileAudit/evidence.js.map +1 -1
  258. package/dist/mobileAudit/profiles.js +1 -1
  259. package/dist/mobileAudit/profiles.js.map +1 -1
  260. package/dist/mobileAudit/results.js +3 -3
  261. package/dist/mobileAudit/results.js.map +1 -1
  262. package/dist/mobileAudit/runner.js +72 -18
  263. package/dist/mobileAudit/runner.js.map +1 -1
  264. package/dist/oracle/auth.js +1 -1
  265. package/dist/oracle/auth.js.map +1 -1
  266. package/dist/oracle/failures.js +219 -36
  267. package/dist/oracle/failures.js.map +1 -1
  268. package/dist/oracle/health.js +63 -17
  269. package/dist/oracle/health.js.map +1 -1
  270. package/dist/oracle/locator.js +13 -13
  271. package/dist/oracle/locator.js.map +1 -1
  272. package/dist/oracle/record.js +3 -3
  273. package/dist/oracle/record.js.map +1 -1
  274. package/dist/orchestration/envelope.js +2 -2
  275. package/dist/orchestration/envelope.js.map +1 -1
  276. package/dist/orchestration/goal.js +4 -4
  277. package/dist/orchestration/goal.js.map +1 -1
  278. package/dist/orchestration/testThis/execute.js +62 -21
  279. package/dist/orchestration/testThis/execute.js.map +1 -1
  280. package/dist/orchestration/testThis/pipeline.js +119 -30
  281. package/dist/orchestration/testThis/pipeline.js.map +1 -1
  282. package/dist/orchestration/testThis/plan.js +162 -73
  283. package/dist/orchestration/testThis/plan.js.map +1 -1
  284. package/dist/orchestration/testThis/sessionIntent.js +72 -0
  285. package/dist/orchestration/testThis/sessionIntent.js.map +1 -0
  286. package/dist/orchestration/testThis/terminal.js +34 -16
  287. package/dist/orchestration/testThis/terminal.js.map +1 -1
  288. package/dist/orchestration/testThis/types.js +1 -1
  289. package/dist/orchestration/testThis/types.js.map +1 -1
  290. package/dist/plan/plan.js +9 -9
  291. package/dist/plan/plan.js.map +1 -1
  292. package/dist/prompts/index.js +22 -22
  293. package/dist/prompts/index.js.map +1 -1
  294. package/dist/report/export.js +392 -37
  295. package/dist/report/export.js.map +1 -1
  296. package/dist/report/findingsDedupe.js +41 -0
  297. package/dist/report/findingsDedupe.js.map +1 -0
  298. package/dist/report/policy.js +7 -4
  299. package/dist/report/policy.js.map +1 -1
  300. package/dist/report/qaLevel.js +8 -8
  301. package/dist/report/qaLevel.js.map +1 -1
  302. package/dist/report/sarifSources.js +160 -0
  303. package/dist/report/sarifSources.js.map +1 -0
  304. package/dist/report/summary.js +2 -2
  305. package/dist/report/summary.js.map +1 -1
  306. package/dist/report/testCatalog.js +4 -4
  307. package/dist/report/testCatalog.js.map +1 -1
  308. package/dist/report/toolHealth.js +124 -0
  309. package/dist/report/toolHealth.js.map +1 -0
  310. package/dist/server.js +472 -45
  311. package/dist/server.js.map +1 -1
  312. package/dist/services/automationGenerate.js +8 -2
  313. package/dist/services/automationGenerate.js.map +1 -1
  314. package/dist/services/build.js +2 -2
  315. package/dist/services/build.js.map +1 -1
  316. package/dist/{tools → services}/flowGenerate.js +29 -13
  317. package/dist/services/flowGenerate.js.map +1 -0
  318. package/dist/services/preflight.js +4 -4
  319. package/dist/services/preflight.js.map +1 -1
  320. package/dist/services/prepareAndroid.js +46 -8
  321. package/dist/services/prepareAndroid.js.map +1 -1
  322. package/dist/services/prepareIos.js +24 -9
  323. package/dist/services/prepareIos.js.map +1 -1
  324. package/dist/services/report.js +100 -45
  325. package/dist/services/report.js.map +1 -1
  326. package/dist/services/smoke.js +32 -16
  327. package/dist/services/smoke.js.map +1 -1
  328. package/dist/services/suiteGenerate.js +28 -3
  329. package/dist/services/suiteGenerate.js.map +1 -1
  330. package/dist/services/testSuiteKnowledge.js +40 -27
  331. package/dist/services/testSuiteKnowledge.js.map +1 -1
  332. package/dist/session/attach.js +270 -6
  333. package/dist/session/attach.js.map +1 -1
  334. package/dist/session/processRegistry.js +317 -48
  335. package/dist/session/processRegistry.js.map +1 -1
  336. package/dist/session/progress.js +1 -1
  337. package/dist/session/retention.js +201 -0
  338. package/dist/session/retention.js.map +1 -0
  339. package/dist/session/store.js +275 -48
  340. package/dist/session/store.js.map +1 -1
  341. package/dist/snapshot/overlays.js +53 -8
  342. package/dist/snapshot/overlays.js.map +1 -1
  343. package/dist/snapshot/parse.js +28 -4
  344. package/dist/snapshot/parse.js.map +1 -1
  345. package/dist/snapshot/present.js +11 -5
  346. package/dist/snapshot/present.js.map +1 -1
  347. package/dist/snapshot/settle.js +39 -9
  348. package/dist/snapshot/settle.js.map +1 -1
  349. package/dist/state/consent.js +99 -0
  350. package/dist/state/consent.js.map +1 -0
  351. package/dist/state/profile.js +46 -2
  352. package/dist/state/profile.js.map +1 -1
  353. package/dist/suite/compile.js +9 -5
  354. package/dist/suite/compile.js.map +1 -1
  355. package/dist/suite/lint.js +4 -4
  356. package/dist/suite/lint.js.map +1 -1
  357. package/dist/suite/pom.js +50 -41
  358. package/dist/suite/pom.js.map +1 -1
  359. package/dist/suite/secretGuard.js +266 -0
  360. package/dist/suite/secretGuard.js.map +1 -0
  361. package/dist/suite/testcase.js +9 -7
  362. package/dist/suite/testcase.js.map +1 -1
  363. package/dist/testSuite/exporter.js +4 -4
  364. package/dist/testSuite/exporter.js.map +1 -1
  365. package/dist/testSuite/generator.js +13 -9
  366. package/dist/testSuite/generator.js.map +1 -1
  367. package/dist/testSuite/history.js +1 -1
  368. package/dist/testSuite/history.js.map +1 -1
  369. package/dist/testSuite/issueLinks.js +3 -3
  370. package/dist/testSuite/issueLinks.js.map +1 -1
  371. package/dist/testSuite/lint.js +4 -4
  372. package/dist/testSuite/lint.js.map +1 -1
  373. package/dist/testSuite/merge.js +5 -5
  374. package/dist/testSuite/merge.js.map +1 -1
  375. package/dist/testSuite/schema.js +5 -5
  376. package/dist/testSuite/schema.js.map +1 -1
  377. package/dist/testSuite/store.js +3 -3
  378. package/dist/testSuite/store.js.map +1 -1
  379. package/dist/testSuite/traceability.js +2 -2
  380. package/dist/testSuite/traceability.js.map +1 -1
  381. package/dist/tools/act.js +794 -164
  382. package/dist/tools/act.js.map +1 -1
  383. package/dist/tools/agent.js +451 -128
  384. package/dist/tools/agent.js.map +1 -1
  385. package/dist/tools/appControl.js +69 -19
  386. package/dist/tools/appControl.js.map +1 -1
  387. package/dist/tools/appMap.js +188 -130
  388. package/dist/tools/appMap.js.map +1 -1
  389. package/dist/tools/build.js +32 -42
  390. package/dist/tools/build.js.map +1 -1
  391. package/dist/tools/bundletool.js +21 -31
  392. package/dist/tools/bundletool.js.map +1 -1
  393. package/dist/tools/clearOverlay.js +55 -12
  394. package/dist/tools/clearOverlay.js.map +1 -1
  395. package/dist/tools/device.js +76 -24
  396. package/dist/tools/device.js.map +1 -1
  397. package/dist/tools/doctor.js +68 -28
  398. package/dist/tools/doctor.js.map +1 -1
  399. package/dist/tools/explore.js +41 -47
  400. package/dist/tools/explore.js.map +1 -1
  401. package/dist/tools/featureTesting.js +31 -48
  402. package/dist/tools/featureTesting.js.map +1 -1
  403. package/dist/tools/firstRun.js +34 -41
  404. package/dist/tools/firstRun.js.map +1 -1
  405. package/dist/tools/flow.js +183 -103
  406. package/dist/tools/flow.js.map +1 -1
  407. package/dist/tools/flowRepair.js +25 -13
  408. package/dist/tools/flowRepair.js.map +1 -1
  409. package/dist/tools/generate.js +52 -89
  410. package/dist/tools/generate.js.map +1 -1
  411. package/dist/tools/getArtifact.js +16 -5
  412. package/dist/tools/getArtifact.js.map +1 -1
  413. package/dist/tools/health.js +15 -12
  414. package/dist/tools/health.js.map +1 -1
  415. package/dist/tools/ios.js +45 -223
  416. package/dist/tools/ios.js.map +1 -1
  417. package/dist/tools/issues.js +251 -35
  418. package/dist/tools/issues.js.map +1 -1
  419. package/dist/tools/jobs.js +41 -16
  420. package/dist/tools/jobs.js.map +1 -1
  421. package/dist/tools/metro.js +36 -35
  422. package/dist/tools/metro.js.map +1 -1
  423. package/dist/tools/mobileAudit.js +50 -45
  424. package/dist/tools/mobileAudit.js.map +1 -1
  425. package/dist/tools/network.js +31 -14
  426. package/dist/tools/network.js.map +1 -1
  427. package/dist/tools/note.js +26 -26
  428. package/dist/tools/note.js.map +1 -1
  429. package/dist/tools/prepareIosTarget.js +10 -16
  430. package/dist/tools/prepareIosTarget.js.map +1 -1
  431. package/dist/tools/prepareTarget.js +117 -50
  432. package/dist/tools/prepareTarget.js.map +1 -1
  433. package/dist/tools/report.js +11 -22
  434. package/dist/tools/report.js.map +1 -1
  435. package/dist/tools/resolveArtifact.js +11 -15
  436. package/dist/tools/resolveArtifact.js.map +1 -1
  437. package/dist/tools/resolveTarget.js +78 -24
  438. package/dist/tools/resolveTarget.js.map +1 -1
  439. package/dist/tools/screenRecord.js +23 -21
  440. package/dist/tools/screenRecord.js.map +1 -1
  441. package/dist/tools/screenshot.js +23 -16
  442. package/dist/tools/screenshot.js.map +1 -1
  443. package/dist/tools/smoke.js +20 -16
  444. package/dist/tools/smoke.js.map +1 -1
  445. package/dist/tools/snapshot.js +155 -118
  446. package/dist/tools/snapshot.js.map +1 -1
  447. package/dist/tools/startSession.js +104 -116
  448. package/dist/tools/startSession.js.map +1 -1
  449. package/dist/tools/suite.js +72 -43
  450. package/dist/tools/suite.js.map +1 -1
  451. package/dist/tools/testSuite.js +30 -20
  452. package/dist/tools/testSuite.js.map +1 -1
  453. package/dist/tools/testThis.js +24 -28
  454. package/dist/tools/testThis.js.map +1 -1
  455. package/dist/tools/visual.js +686 -210
  456. package/dist/tools/visual.js.map +1 -1
  457. package/dist/tools/wait.js +9 -38
  458. package/dist/tools/wait.js.map +1 -1
  459. package/dist/tools/wda.js +187 -57
  460. package/dist/tools/wda.js.map +1 -1
  461. package/dist/version.js +31 -22
  462. package/dist/version.js.map +1 -1
  463. package/dist/visual/ocr.js +53 -6
  464. package/dist/visual/ocr.js.map +1 -1
  465. package/dist/visual/provider.js +80 -12
  466. package/dist/visual/provider.js.map +1 -1
  467. package/docs/README.md +24 -10
  468. package/docs/ci-reports.md +328 -0
  469. package/docs/concepts.md +227 -0
  470. package/docs/flows.md +153 -0
  471. package/docs/mcp-server.md +194 -128
  472. package/docs/physical-devices.md +84 -0
  473. package/docs/tools.md +812 -152
  474. package/package.json +3 -2
  475. package/dist/tools/assertVisual.js +0 -84
  476. package/dist/tools/assertVisual.js.map +0 -1
  477. package/dist/tools/automationGenerate.js.map +0 -1
  478. package/dist/tools/capabilities.js +0 -179
  479. package/dist/tools/capabilities.js.map +0 -1
  480. package/dist/tools/detectContext.js +0 -41
  481. package/dist/tools/detectContext.js.map +0 -1
  482. package/dist/tools/flowGenerate.js.map +0 -1
  483. package/dist/tools/history.js +0 -86
  484. package/dist/tools/history.js.map +0 -1
  485. package/dist/tools/locator.js +0 -85
  486. package/dist/tools/locator.js.map +0 -1
  487. package/dist/tools/permissions.js +0 -174
  488. package/dist/tools/permissions.js.map +0 -1
  489. package/dist/tools/plan.js +0 -52
  490. package/dist/tools/plan.js.map +0 -1
  491. package/dist/tools/screenInfo.js +0 -76
  492. package/dist/tools/screenInfo.js.map +0 -1
  493. package/dist/tools/seed.js +0 -133
  494. package/dist/tools/seed.js.map +0 -1
  495. package/dist/tools/state.js +0 -217
  496. package/dist/tools/state.js.map +0 -1
  497. package/dist/tools/visualText.js +0 -106
  498. package/dist/tools/visualText.js.map +0 -1
@@ -0,0 +1,141 @@
1
+ // Shared screen-relative gesture geometry. qa_act and the flow runner derive
2
+ // swipe/scroll vectors the SAME way: anchored as fractions of the real screen size and
3
+ // clamped on-screen, with fixed legacy coordinates ONLY as the fallback when the driver
4
+ // cannot report a size. WDA swipes are in POINTS (a modern iPhone is ≤~440pt wide), so any
5
+ // fixed pixel constant is off-screen there.
6
+ /** ~8% edge inset used by qa_act gestures. Stays clear of the iOS system-gesture zones
7
+ * (home indicator, notification/control-center edges). Flow replay keeps inset 0 so
8
+ * existing recorded flows keep their historical [1, size-1] clamp. */
9
+ export const GESTURE_EDGE_INSET = 0.08;
10
+ /** Legacy fixed-coordinate vectors, used ONLY when screenSize() is unknown. */
11
+ export const FALLBACK_SWIPE = {
12
+ up: [540, 1500, 540, 600],
13
+ down: [540, 600, 540, 1500],
14
+ left: [800, 1100, 200, 1100],
15
+ right: [200, 1100, 800, 1100],
16
+ };
17
+ export const AREA_ANCHOR = {
18
+ center: [0.5, 0.5],
19
+ top: [0.5, 0.3],
20
+ bottom: [0.5, 0.7],
21
+ left: [0.3, 0.5],
22
+ right: [0.7, 0.5],
23
+ };
24
+ /** Legacy swipe travel in px when the screen size is unknown (the old qa_act constant). */
25
+ const FALLBACK_DISTANCE = 800;
26
+ function clamper(size, insetFrac) {
27
+ const insetX = Math.max(1, Math.round(size.width * insetFrac));
28
+ const insetY = Math.max(1, Math.round(size.height * insetFrac));
29
+ return {
30
+ x: (x) => Math.max(insetX, Math.min(size.width - insetX, Math.round(x))),
31
+ y: (y) => Math.max(insetY, Math.min(size.height - insetY, Math.round(y))),
32
+ };
33
+ }
34
+ /** Device-relative swipe vector (fractions of the screen), falling back to fixed coords if no size.
35
+ * insetFrac keeps every endpoint at least that fraction of the screen away from each edge;
36
+ * the default 0 preserves the flow runner's historical [1, size-1] clamp. */
37
+ export function swipeVector(size, dir, area = 'center', distance = 0.6, insetFrac = 0) {
38
+ if (!size)
39
+ return FALLBACK_SWIPE[dir];
40
+ const [ax, ay] = AREA_ANCHOR[area];
41
+ const cx = size.width * ax;
42
+ const cy = size.height * ay;
43
+ const vd = (size.height * distance) / 2;
44
+ const hd = (size.width * distance) / 2;
45
+ const clamp = clamper(size, insetFrac);
46
+ const v = {
47
+ up: [cx, cy + vd, cx, cy - vd],
48
+ down: [cx, cy - vd, cx, cy + vd],
49
+ left: [cx + hd, cy, cx - hd, cy],
50
+ right: [cx - hd, cy, cx + hd, cy],
51
+ }[dir];
52
+ return [clamp.x(v[0]), clamp.y(v[1]), clamp.x(v[2]), clamp.y(v[3])];
53
+ }
54
+ /** Swipe starting at an explicit point (a resolved qa_act target). The start is used
55
+ * VERBATIM (it is a real element the caller asked to swipe from, and 0 is a legitimate
56
+ * coordinate), while the derived end point is clamped on-screen. */
57
+ export function swipeFromPoint(size, from, dir, distance = 0.5, insetFrac = 0) {
58
+ const dist = size ? (dir === 'up' || dir === 'down' ? size.height : size.width) * distance : FALLBACK_DISTANCE;
59
+ const to = {
60
+ up: { x: from.x, y: from.y - dist },
61
+ down: { x: from.x, y: from.y + dist },
62
+ left: { x: from.x - dist, y: from.y },
63
+ right: { x: from.x + dist, y: from.y },
64
+ }[dir];
65
+ if (!size)
66
+ return [from.x, from.y, Math.round(to.x), Math.round(to.y)];
67
+ const clamp = clamper(size, insetFrac);
68
+ return [from.x, from.y, clamp.x(to.x), clamp.y(to.y)];
69
+ }
70
+ /** Fraction of a scrollable container kept clear on each side when a scroll is anchored in it.
71
+ * keeps the finger off sticky headers/footers that sit flush with the container's edges. */
72
+ export const SCROLL_CONTAINER_INSET = 0.1;
73
+ /** Minimum on-screen extent (px/pt) a scrollable container needs on each axis to anchor a swipe. */
74
+ const MIN_SCROLL_CONTAINER = 48;
75
+ /** PURE: the visible rect of the LARGEST scrollable node, or null when there is none worth using.
76
+ * Android marks containers `scrollable="true"`; the WDA source normalizer maps
77
+ * ScrollView/Table/CollectionView to the same attribute. A clipped Android node can report
78
+ * inverted bounds (e.g. `[210,315][750,124]`, its visible area is empty): such nodes are
79
+ * ignored, and every rect is clipped to the screen when the size is known. */
80
+ export function largestScrollableRect(nodes, size) {
81
+ let best = null;
82
+ let bestArea = 0;
83
+ for (const n of nodes ?? []) {
84
+ if (!n.scrollable)
85
+ continue;
86
+ let [x1, y1, x2, y2] = n.bounds;
87
+ if (size) {
88
+ x1 = Math.max(0, x1);
89
+ y1 = Math.max(0, y1);
90
+ x2 = Math.min(size.width, x2);
91
+ y2 = Math.min(size.height, y2);
92
+ }
93
+ // inverted / collapsed after clipping > not visible, can't be a gesture anchor
94
+ if (x2 - x1 < MIN_SCROLL_CONTAINER || y2 - y1 < MIN_SCROLL_CONTAINER)
95
+ continue;
96
+ const area = (x2 - x1) * (y2 - y1);
97
+ if (area > bestArea) {
98
+ bestArea = area;
99
+ best = [x1, y1, x2, y2];
100
+ }
101
+ }
102
+ return best;
103
+ }
104
+ /** Swipe vector anchored INSIDE a scrollable container: centered on the container, travel =
105
+ * `distance` × the container's extent on the swipe axis, both endpoints kept `containerInset`
106
+ * inside the container AND (when possible) `screenInset` inside the screen. A swipe that starts
107
+ * on a sticky app bar above the list moves nothing. That's why the container matters. */
108
+ export function swipeInRect(rect, dir, size, distance = 0.6, containerInset = SCROLL_CONTAINER_INSET, screenInset = 0) {
109
+ const [x1, y1, x2, y2] = rect;
110
+ const w = x2 - x1;
111
+ const h = y2 - y1;
112
+ const cx = x1 + w / 2;
113
+ const cy = y1 + h / 2;
114
+ const axisRange = (lo, hi, extent, screen) => {
115
+ const inset = Math.max(1, Math.round(extent * containerInset));
116
+ let a = lo + inset;
117
+ let b = hi - inset;
118
+ if (screen != null && screenInset > 0) {
119
+ const si = Math.max(1, Math.round(screen * screenInset));
120
+ const sa = Math.max(a, si);
121
+ const sb = Math.min(b, screen - si);
122
+ if (sb > sa)
123
+ [a, b] = [sa, sb]; // only when the two insets still leave room
124
+ }
125
+ return [a, b];
126
+ };
127
+ const [minX, maxX] = axisRange(x1, x2, w, size?.width);
128
+ const [minY, maxY] = axisRange(y1, y2, h, size?.height);
129
+ const cX = (x) => Math.round(Math.max(minX, Math.min(maxX, x)));
130
+ const cY = (y) => Math.round(Math.max(minY, Math.min(maxY, y)));
131
+ const vd = (h * distance) / 2;
132
+ const hd = (w * distance) / 2;
133
+ const v = {
134
+ up: [cx, cy + vd, cx, cy - vd],
135
+ down: [cx, cy - vd, cx, cy + vd],
136
+ left: [cx + hd, cy, cx - hd, cy],
137
+ right: [cx - hd, cy, cx + hd, cy],
138
+ }[dir];
139
+ return [cX(v[0]), cY(v[1]), cX(v[2]), cY(v[3])];
140
+ }
141
+ //# sourceMappingURL=gestures.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"gestures.js","sourceRoot":"","sources":["../../src/lib/gestures.ts"],"names":[],"mappings":"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"}
@@ -1,6 +1,7 @@
1
- // Keep Swipium's generated artifacts out of the user's VCS. Both `swipium scan` and every
2
- // session (which writes .swipium/<id>/…) call this, so merely using Swipium never dirties a
3
- // developer's git tree. Idempotent; only touches a real git repo's .gitignore.
1
+ // Keep Swipium's generated artifacts out of the user's VCS. Called when Swipium writes the app
2
+ // map (src/appMap/store.ts) or the issue ledger (src/issues/store.ts), so the first such write
3
+ // appends `.swipium/` to the project's .gitignore. `swipium scan` itself does not call this.
4
+ // Idempotent; only touches a real git repo's .gitignore (it never runs Git).
4
5
  import { existsSync, readFileSync, appendFileSync } from 'node:fs';
5
6
  import { join } from 'node:path';
6
7
  export function ensureGitignored(root) {
@@ -1 +1 @@
1
- {"version":3,"file":"gitignore.js","sourceRoot":"","sources":["../../src/lib/gitignore.ts"],"names":[],"mappings":"AAAA,0FAA0F;AAC1F,4FAA4F;AAC5F,+EAA+E;AAE/E,OAAO,EAAE,UAAU,EAAE,YAAY,EAAE,cAAc,EAAE,MAAM,SAAS,CAAC;AACnE,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAEjC,MAAM,UAAU,gBAAgB,CAAC,IAAY;IAC3C,IAAI,CAAC;QACH,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;YAAE,OAAO,YAAY,CAAC;QACzD,MAAM,EAAE,GAAG,IAAI,CAAC,IAAI,EAAE,YAAY,CAAC,CAAC;QACpC,MAAM,GAAG,GAAG,UAAU,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,YAAY,CAAC,EAAE,EAAE,MAAM,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;QAC3D,IAAI,uBAAuB,CAAC,IAAI,CAAC,GAAG,CAAC;YAAE,OAAO,SAAS,CAAC;QACxD,MAAM,MAAM,GAAG,GAAG,CAAC,MAAM,IAAI,CAAC,GAAG,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC;QAC7D,cAAc,CAAC,EAAE,EAAE,GAAG,MAAM,mDAAmD,CAAC,CAAC;QACjF,OAAO,OAAO,CAAC;IACjB,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,YAAY,CAAC,CAAC,+DAA+D;IACtF,CAAC;AACH,CAAC"}
1
+ {"version":3,"file":"gitignore.js","sourceRoot":"","sources":["../../src/lib/gitignore.ts"],"names":[],"mappings":"AAAA,+FAA+F;AAC/F,+FAA+F;AAC/F,6FAA6F;AAC7F,6EAA6E;AAE7E,OAAO,EAAE,UAAU,EAAE,YAAY,EAAE,cAAc,EAAE,MAAM,SAAS,CAAC;AACnE,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAEjC,MAAM,UAAU,gBAAgB,CAAC,IAAY;IAC3C,IAAI,CAAC;QACH,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;YAAE,OAAO,YAAY,CAAC;QACzD,MAAM,EAAE,GAAG,IAAI,CAAC,IAAI,EAAE,YAAY,CAAC,CAAC;QACpC,MAAM,GAAG,GAAG,UAAU,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,YAAY,CAAC,EAAE,EAAE,MAAM,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;QAC3D,IAAI,uBAAuB,CAAC,IAAI,CAAC,GAAG,CAAC;YAAE,OAAO,SAAS,CAAC;QACxD,MAAM,MAAM,GAAG,GAAG,CAAC,MAAM,IAAI,CAAC,GAAG,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC;QAC7D,cAAc,CAAC,EAAE,EAAE,GAAG,MAAM,mDAAmD,CAAC,CAAC;QACjF,OAAO,OAAO,CAAC;IACjB,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,YAAY,CAAC,CAAC,+DAA+D;IACtF,CAAC;AACH,CAAC"}
package/dist/lib/image.js CHANGED
@@ -1,4 +1,4 @@
1
- // Local, deterministic visual ops (PHASE3-PLAN §8.2) — baseline diff + template matching on
1
+ // Local, deterministic visual ops: baseline diff + template matching on
2
2
  // decoded pixels. No AI, no network, no third-party code: just arithmetic over PNG bytes.
3
3
  import { decodePng, toGray } from './png.js';
4
4
  /** Per-pixel diff of two same-size screenshots. A pixel "changed" if any channel differs by > tol. */
@@ -1 +1 @@
1
- {"version":3,"file":"image.js","sourceRoot":"","sources":["../../src/lib/image.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"image.js","sourceRoot":"","sources":["../../src/lib/image.ts"],"names":[],"mappings":"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"}
@@ -1,60 +1,399 @@
1
1
  // Minimal advisory file lock for cross-process coordination on the small shared JSON files
2
- // under ~/.swipium (registry.json, processes.json). Two concurrent server instances are
3
- // common (multiple MCP clients), and an unlocked read-modify-write lets them clobber each
4
- // other's entries. The lock is a DIRECTORY (mkdir is atomic on POSIX and Windows): whoever
5
- // creates it holds it. A stale lock (holder crashed mid-write) is taken over after STALE_MS.
6
- // Waiting is a short synchronous spin — callers do tiny sync JSON rewrites — and on timeout
7
- // we fail the write rather than proceeding unlocked and reintroducing read-modify-write clobbers.
8
- import { mkdirSync, rmdirSync, statSync } from 'node:fs';
2
+ // under ~/.swipium (registry.json, processes.json) and .swipium/app-map.json. Two concurrent
3
+ // server instances are common (multiple MCP clients), and an unlocked read-modify-write lets them
4
+ // clobber each other's entries. The lock is a DIRECTORY (mkdir is atomic on POSIX and Windows):
5
+ // whoever creates it holds it. Each acquisition writes an OWNER file (`<lockPath>/owner` =
6
+ // `pid:token`, created exclusively) so release can verify the lock is still ours. A holder that
7
+ // stalled past the stale threshold and was taken over must NOT delete the new owner's lock.
8
+ //
9
+ // Stale takeover (holder crashed mid-write) is CLAIM-then-MOVE (H8, review round 2):
10
+ // 1. claim: the taker creates `<lockPath>/.takeover` EXCLUSIVELY ('wx', with its own token).
11
+ // Only one taker can claim a given lock-directory instance, and the lock path never
12
+ // disappears during a failed claim, so a newcomer can't slip in;
13
+ // 2. verify: the claimed directory must still be exactly the lock it judged stale (same owner,
14
+ // still old before the claim, same inode after it). If a racing waiter already replaced it with a FRESH lock, the claim is dropped
15
+ // and the fresh lock is left untouched (it never moved);
16
+ // 3. move: rename the lock to a unique tombstone (`<lockPath>.stale-<pid>-<uuid>`), confirm the
17
+ // tombstone carries OUR claim (i.e. we moved the instance we verified), delete it, retry mkdir.
18
+ // Residual race: between verify and move the path can only change if the presumed-dead holder
19
+ // wakes up and releases, and a new holder re-creates the lock, inside that window. The post-move
20
+ // claim check catches it: the moved lock is renamed back. If that restore fails because yet
21
+ // another contender already created the path, the taker does NOT proceed (takeover failed > it
22
+ // goes back to waiting) and does NOT delete the moved lock. It stays as a tombstone (never
23
+ // treated as safe to rm while it may belong to a live holder) and is swept once older than the
24
+ // stale threshold; the displaced holder is warned at release and removes its own tombstone.
25
+ // Orphaned tombstones / claims (a taker crashed mid-takeover) are swept / expired as stale.
26
+ //
27
+ // Two variants:
28
+ // - withFileLock (sync): for the tiny synchronous JSON rewrites (registry/processes/app-map
29
+ // save). Waits are short synchronous sleeps (Atomics.wait, 25 ms slices, ≤ MAX_WAIT_MS total),
30
+ // so the critical section MUST be short: a sync fn cannot be heartbeated (no timer can fire
31
+ // while it runs). Slow work (e.g. the app-map static scan) belongs OUTSIDE the lock.
32
+ // - withFileLockAsync: for async critical sections. Waits yield to the event loop and a heartbeat
33
+ // refreshes the lock's mtime every staleMs/3 so a long holder is never judged stale.
34
+ // On timeout both variants fail rather than proceed unlocked (that would reintroduce clobbers).
35
+ import { mkdirSync, readFileSync, readdirSync, renameSync, rmSync, statSync, utimesSync, writeFileSync } from 'node:fs';
36
+ import { randomUUID } from 'node:crypto';
37
+ import { basename, dirname, join } from 'node:path';
9
38
  import { log } from './logger.js';
10
- const STALE_LOCK_MS = 10_000;
39
+ export const STALE_LOCK_MS = 10_000;
11
40
  const MAX_WAIT_MS = 2_000;
12
41
  const RETRY_SLEEP_MS = 25;
13
- /** Synchronous sleep without spinning the CPU (Node allows Atomics.wait on the main thread). */
42
+ /** 'retry' (lock vanished / stale takeover attempted) loops without sleeping at most this many
43
+ * times in a row; after that it sleeps like 'busy'. A takeover that can never succeed (EACCES on
44
+ * the claim, rename failure, a fresh claim from a crashed taker) must not busy-spin, since it would
45
+ * block the event loop forever (the deadline is checked on EVERY iteration too). */
46
+ const MAX_IMMEDIATE_RETRIES = 3;
47
+ const OWNER_FILE = 'owner';
48
+ const TOMBSTONE_MARK = '.stale-';
49
+ const CLAIM_FILE = '.takeover';
50
+ /** Test seams for simulating contender interleavings inside a takeover. Never set in production. */
51
+ export const lockHooks = {};
52
+ /** Synchronous sleep without spinning the CPU (Node allows Atomics.wait on the main thread).
53
+ * Only ever called with short slices (RETRY_SLEEP_MS / rename backoff). */
14
54
  function sleepSync(ms) {
15
55
  Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, ms);
16
56
  }
17
- /** Run `fn` while holding the advisory lock at `lockPath` (a directory that must not pre-exist).
18
- * Retries for up to MAX_WAIT_MS, taking over locks older than STALE_LOCK_MS (crashed holder). */
19
- export function withFileLock(lockPath, fn) {
20
- const deadline = Date.now() + MAX_WAIT_MS;
21
- let locked = false;
22
- while (!locked) {
57
+ /** The `pid:token` string of the lock's current owner, or null when absent/unreadable. */
58
+ function currentOwner(lockPath) {
59
+ try {
60
+ return readFileSync(join(lockPath, OWNER_FILE), 'utf8');
61
+ }
62
+ catch {
63
+ return null;
64
+ }
65
+ }
66
+ function readFileOrNull(p) {
67
+ try {
68
+ return readFileSync(p, 'utf8');
69
+ }
70
+ catch {
71
+ return null;
72
+ }
73
+ }
74
+ /** Remove our takeover claim from `dir` (only if it is still ours). */
75
+ function dropClaim(dir, claimToken) {
76
+ const claim = join(dir, CLAIM_FILE);
77
+ if (readFileOrNull(claim) === claimToken)
78
+ rmSync(claim, { force: true });
79
+ }
80
+ /** A claim left by a taker that crashed mid-takeover would block every future takeover of that
81
+ * lock: expire it once older than the stale threshold (moved aside atomically, then removed). */
82
+ function expireStaleClaim(lockPath, staleMs) {
83
+ const claim = join(lockPath, CLAIM_FILE);
84
+ try {
85
+ if (Date.now() - statSync(claim).mtimeMs <= staleMs)
86
+ return;
87
+ const lockStat = statSync(lockPath);
88
+ const dead = `${claim}.dead-${randomUUID()}`;
89
+ renameSync(claim, dead);
90
+ rmSync(dead, { force: true });
91
+ // Removing the claim bumped the lock dir's mtime; keep it looking as stale as it is.
92
+ utimesSync(lockPath, lockStat.atime, lockStat.mtime);
93
+ }
94
+ catch {
95
+ /* gone or raced another expirer */
96
+ }
97
+ }
98
+ /** Take over a lock judged stale (claim > verify > move; see the header). Returns true when the
99
+ * stale lock was removed (the caller should retry mkdir), false when the takeover failed and the
100
+ * caller must go back to waiting: the lock was already gone, another taker holds the claim, a
101
+ * racing waiter replaced it with a fresh lock (left untouched), or the moved lock turned out not
102
+ * to be the verified one (restored, or kept as a tombstone if the path was re-created meanwhile).
103
+ * Exported for tests. */
104
+ export function takeOverStaleLock(lockPath, observed, staleMs = STALE_LOCK_MS) {
105
+ const claimToken = `${process.pid}:${randomUUID()}`;
106
+ // Identify the instance BEFORE claiming (writing the claim bumps the directory's mtime).
107
+ let before;
108
+ try {
109
+ const st = statSync(lockPath);
110
+ if (currentOwner(lockPath) !== observed.owner || Date.now() - st.mtimeMs <= staleMs)
111
+ return false; // replaced/refreshed
112
+ before = { ino: st.ino, dev: st.dev };
113
+ }
114
+ catch {
115
+ return false; // already gone
116
+ }
117
+ try {
118
+ writeFileSync(join(lockPath, CLAIM_FILE), claimToken, { flag: 'wx' });
119
+ }
120
+ catch (e) {
121
+ if (e.code === 'EEXIST')
122
+ expireStaleClaim(lockPath, staleMs);
123
+ return false; // lock gone (ENOENT) or another taker is mid-takeover
124
+ }
125
+ let isSame = false;
126
+ try {
127
+ const st = statSync(lockPath);
128
+ isSame = st.ino === before.ino && st.dev === before.dev && currentOwner(lockPath) === observed.owner;
129
+ }
130
+ catch {
131
+ isSame = false;
132
+ }
133
+ if (!isSame) {
134
+ // We claimed a FRESH lock (a racing waiter won the takeover and re-acquired after our
135
+ // observation). It never moved: just withdraw the claim.
136
+ dropClaim(lockPath, claimToken);
137
+ return false;
138
+ }
139
+ lockHooks.beforeTakeoverMove?.();
140
+ const tomb = `${lockPath}${TOMBSTONE_MARK}${process.pid}-${randomUUID()}`;
141
+ try {
142
+ renameSync(lockPath, tomb); // atomic
143
+ }
144
+ catch {
145
+ return false; // released meanwhile
146
+ }
147
+ if (readFileOrNull(join(tomb, CLAIM_FILE)) !== claimToken) {
148
+ // The path was replaced between verify and move: we moved someone else's (possibly live) lock.
149
+ lockHooks.beforeTakeoverRestore?.();
23
150
  try {
24
- mkdirSync(lockPath); // atomic: throws EEXIST if another process holds the lock
25
- locked = true;
151
+ renameSync(tomb, lockPath); // put it back untouched
26
152
  }
27
153
  catch {
154
+ // Another contender already re-created the path. Do NOT delete the moved lock (it may belong
155
+ // to a live holder) and do NOT proceed as owner: the takeover failed. The tombstone is swept
156
+ // once stale; its holder is warned at release.
157
+ log('error', 'file lock takeover moved a replaced lock and could not restore it; left as tombstone, not acquiring', {
158
+ lockPath,
159
+ tomb,
160
+ });
161
+ }
162
+ return false;
163
+ }
164
+ rmSync(tomb, { recursive: true, force: true });
165
+ return true;
166
+ }
167
+ /** Remove tombstones left by a taker that crashed between rename and rm. Best-effort. */
168
+ export function sweepLockTombstones(lockPath, staleMs = STALE_LOCK_MS) {
169
+ let removed = 0;
170
+ try {
171
+ const dir = dirname(lockPath);
172
+ const prefix = `${basename(lockPath)}${TOMBSTONE_MARK}`;
173
+ for (const name of readdirSync(dir)) {
174
+ if (!name.startsWith(prefix))
175
+ continue;
176
+ const p = join(dir, name);
28
177
  try {
29
- const st = statSync(lockPath);
30
- if (Date.now() - st.mtimeMs > STALE_LOCK_MS) {
31
- rmdirSync(lockPath); // stale — the holder crashed; take over
32
- continue;
178
+ if (Date.now() - statSync(p).mtimeMs > staleMs) {
179
+ rmSync(p, { recursive: true, force: true });
180
+ removed++;
33
181
  }
34
182
  }
35
183
  catch {
36
- /* lock released between attempts — retry immediately */
37
- continue;
38
- }
39
- if (Date.now() > deadline) {
40
- log('error', 'file lock wait timed out — refusing unlocked registry mutation', { lockPath });
41
- throw new Error(`Timed out waiting for file lock ${lockPath}`);
184
+ /* raced another sweeper */
42
185
  }
43
- sleepSync(RETRY_SLEEP_MS);
44
186
  }
45
187
  }
188
+ catch {
189
+ /* parent missing/unreadable, nothing to sweep */
190
+ }
191
+ return removed;
192
+ }
193
+ /** One acquisition attempt: mkdir, else stale-check/takeover. Never sleeps. */
194
+ function tryAcquire(lockPath, token, staleMs) {
195
+ try {
196
+ mkdirSync(lockPath); // atomic: throws EEXIST if another process holds the lock
197
+ }
198
+ catch (e) {
199
+ if (e.code !== 'EEXIST')
200
+ throw e;
201
+ let observed;
202
+ try {
203
+ observed = { owner: currentOwner(lockPath), mtimeMs: statSync(lockPath).mtimeMs };
204
+ }
205
+ catch {
206
+ return 'retry'; // lock released between attempts, retry immediately
207
+ }
208
+ if (Date.now() - observed.mtimeMs > staleMs) {
209
+ // stale: the holder crashed or stalled; take over atomically, then retry mkdir
210
+ takeOverStaleLock(lockPath, observed, staleMs);
211
+ return 'retry';
212
+ }
213
+ return 'busy';
214
+ }
215
+ // We created the dir. Record ownership EXCLUSIVELY ('wx'): if an owner file already exists the
216
+ // directory is not really ours (a takeover restore swapped a live lock into this path).
217
+ try {
218
+ writeFileSync(join(lockPath, OWNER_FILE), token, { flag: 'wx' });
219
+ }
220
+ catch (e) {
221
+ if (e.code === 'EEXIST')
222
+ return 'busy';
223
+ rmSync(lockPath, { recursive: true, force: true });
224
+ throw e;
225
+ }
226
+ // Re-check ownership after acquiring: the lock we hold must carry OUR token.
227
+ if (currentOwner(lockPath) !== token)
228
+ return 'busy';
229
+ sweepLockTombstones(lockPath, staleMs);
230
+ return 'acquired';
231
+ }
232
+ function release(lockPath, token) {
233
+ // Verify-then-release: only remove the lock while the owner file still holds OUR token. If fn()
234
+ // stalled past the stale threshold another process legitimately took the lock over, and deleting it
235
+ // here would let a THIRD process acquire while the new owner still runs.
236
+ if (currentOwner(lockPath) === token) {
237
+ try {
238
+ rmSync(lockPath, { recursive: true, force: true });
239
+ }
240
+ catch {
241
+ /* already removed (stale takeover raced our release) */
242
+ }
243
+ }
244
+ else {
245
+ log('warn', 'file lock was taken over while held: this holder stalled past the stale threshold (or was displaced by a takeover race) and its work may have raced the new holder', { lockPath });
246
+ removeOwnTombstones(lockPath, token);
247
+ }
248
+ }
249
+ /** A displaced holder's lock may survive as a tombstone (see takeOverStaleLock): remove the ones
250
+ * carrying OUR token. They are ours, so this can never touch another holder's lock. */
251
+ function removeOwnTombstones(lockPath, token) {
252
+ try {
253
+ const dir = dirname(lockPath);
254
+ const prefix = `${basename(lockPath)}${TOMBSTONE_MARK}`;
255
+ for (const name of readdirSync(dir)) {
256
+ if (!name.startsWith(prefix))
257
+ continue;
258
+ const p = join(dir, name);
259
+ if (currentOwner(p) === token)
260
+ rmSync(p, { recursive: true, force: true });
261
+ }
262
+ }
263
+ catch {
264
+ /* best-effort */
265
+ }
266
+ }
267
+ function newToken() {
268
+ // Ownership token: verify identity before acting (the same spirit as processRegistry's
269
+ // serverStillAlive pid+command check).
270
+ return `${process.pid}:${randomUUID()}`;
271
+ }
272
+ function timeoutError(lockPath) {
273
+ log('error', 'file lock wait timed out, refusing unlocked registry mutation', { lockPath });
274
+ return new Error(`Timed out waiting for file lock ${lockPath}`);
275
+ }
276
+ /** Run a SHORT synchronous `fn` while holding the advisory lock at `lockPath` (a directory that
277
+ * must not pre-exist). Retries for up to maxWaitMs (blocking in 25 ms slices), taking over locks
278
+ * older than staleMs. fn cannot be heartbeated, so keep it well under staleMs. */
279
+ export function withFileLock(lockPath, fn, opts = {}) {
280
+ const staleMs = opts.staleMs ?? STALE_LOCK_MS;
281
+ const deadline = Date.now() + (opts.maxWaitMs ?? MAX_WAIT_MS);
282
+ const token = newToken();
283
+ let immediate = 0;
284
+ for (;;) {
285
+ const r = tryAcquire(lockPath, token, staleMs);
286
+ if (r === 'acquired')
287
+ break;
288
+ if (Date.now() > deadline)
289
+ throw timeoutError(lockPath);
290
+ if (r === 'retry' && immediate++ < MAX_IMMEDIATE_RETRIES)
291
+ continue;
292
+ immediate = 0;
293
+ sleepSync(RETRY_SLEEP_MS);
294
+ }
46
295
  try {
47
296
  return fn();
48
297
  }
49
298
  finally {
50
- if (locked) {
299
+ release(lockPath, token);
300
+ }
301
+ }
302
+ /** Async variant: waits yield to the event loop, and a heartbeat refreshes the lock's mtime every
303
+ * staleMs/3 while `fn` runs so a long (async) critical section is never judged stale. */
304
+ export async function withFileLockAsync(lockPath, fn, opts = {}) {
305
+ const staleMs = opts.staleMs ?? STALE_LOCK_MS;
306
+ const deadline = Date.now() + (opts.maxWaitMs ?? MAX_WAIT_MS);
307
+ const token = newToken();
308
+ let immediate = 0;
309
+ for (;;) {
310
+ const r = tryAcquire(lockPath, token, staleMs);
311
+ if (r === 'acquired')
312
+ break;
313
+ if (Date.now() > deadline)
314
+ throw timeoutError(lockPath);
315
+ if (r === 'retry' && immediate++ < MAX_IMMEDIATE_RETRIES)
316
+ continue;
317
+ immediate = 0;
318
+ await new Promise((res) => setTimeout(res, RETRY_SLEEP_MS));
319
+ }
320
+ const heartbeat = setInterval(() => {
321
+ // Only refresh a lock that is still ours. Never keep a thief's lock alive on its behalf.
322
+ if (currentOwner(lockPath) !== token)
323
+ return;
324
+ try {
325
+ const now = new Date();
326
+ utimesSync(lockPath, now, now);
327
+ }
328
+ catch {
329
+ /* lock vanished; release will log */
330
+ }
331
+ }, Math.max(10, Math.floor(staleMs / 3)));
332
+ heartbeat.unref?.();
333
+ try {
334
+ return await fn();
335
+ }
336
+ finally {
337
+ clearInterval(heartbeat);
338
+ release(lockPath, token);
339
+ }
340
+ }
341
+ const RENAME_RETRY_CODES = new Set(['EPERM', 'EBUSY', 'EACCES']);
342
+ /** renameSync that tolerates transient Windows sharing violations (antivirus/indexer briefly
343
+ * holding the target): retries EPERM/EBUSY/EACCES with short backoff (≤ ~310 ms total). On
344
+ * POSIX those errors are permanent, so it throws immediately. */
345
+ export function renameWithRetry(from, to, opts = {}) {
346
+ const rename = opts.renameFn ?? renameSync;
347
+ const isWindows = opts.isWindows ?? process.platform === 'win32';
348
+ const delays = opts.delaysMs ?? [10, 20, 40, 80, 160];
349
+ for (let attempt = 0;; attempt++) {
350
+ try {
351
+ rename(from, to);
352
+ return;
353
+ }
354
+ catch (e) {
355
+ const code = e.code ?? '';
356
+ if (!isWindows || !RENAME_RETRY_CODES.has(code) || attempt >= delays.length)
357
+ throw e;
358
+ sleepSync(delays[attempt]);
359
+ }
360
+ }
361
+ }
362
+ /** Atomic file write: unique tmp sibling + rename (with Windows retry). Removes the tmp on failure. */
363
+ export function writeFileAtomicSync(path, data) {
364
+ const tmp = `${path}.${process.pid}.${randomUUID().slice(0, 8)}.tmp`;
365
+ writeFileSync(tmp, data);
366
+ try {
367
+ renameWithRetry(tmp, path);
368
+ }
369
+ catch (e) {
370
+ rmSync(tmp, { force: true });
371
+ throw e;
372
+ }
373
+ }
374
+ /** Delete `<baseName>.*.tmp` siblings older than maxAgeMs (residue of a writer that crashed
375
+ * between write and rename). Young tmp files may belong to a live writer and are kept. */
376
+ export function cleanupOrphanTmpFiles(dir, baseName, maxAgeMs = 10 * 60_000) {
377
+ let removed = 0;
378
+ try {
379
+ for (const name of readdirSync(dir)) {
380
+ if (!name.startsWith(`${baseName}.`) || !name.endsWith('.tmp'))
381
+ continue;
382
+ const p = join(dir, name);
51
383
  try {
52
- rmdirSync(lockPath);
384
+ if (Date.now() - statSync(p).mtimeMs > maxAgeMs) {
385
+ rmSync(p, { force: true });
386
+ removed++;
387
+ }
53
388
  }
54
389
  catch {
55
- /* already removed (e.g. stale takeover by another process) */
390
+ /* raced another cleaner */
56
391
  }
57
392
  }
58
393
  }
394
+ catch {
395
+ /* dir missing, nothing to clean */
396
+ }
397
+ return removed;
59
398
  }
60
399
  //# sourceMappingURL=lockfile.js.map