contextos-agents 1.7.0 → 2.0.0-beta.3

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 (313) hide show
  1. package/.agents/AGENTS.md +16 -50
  2. package/.agents/adapters/aider/export.js +117 -99
  3. package/.agents/adapters/claude/export.js +68 -26
  4. package/.agents/adapters/copilot/export.js +90 -53
  5. package/.agents/adapters/cursor/export.js +100 -104
  6. package/.agents/adapters/drift-detector.js +196 -0
  7. package/.agents/adapters/gemini/export.js +76 -45
  8. package/.agents/adapters/pure-compiler.js +443 -0
  9. package/.agents/adapters/zed/export.js +104 -96
  10. package/.agents/compiled/registry.v2.json +504 -0
  11. package/.agents/compiled/registry.v2.sha256 +1 -0
  12. package/.agents/compiler/manifest-compiler.js +963 -0
  13. package/.agents/compiler/vendor/yaml.LICENSE.txt +13 -0
  14. package/.agents/compiler/vendor/yaml.SBOM.json +6 -0
  15. package/.agents/compiler/vendor/yaml.js +139 -0
  16. package/.agents/core/profiles/init.yaml +25 -0
  17. package/.agents/core/skills/context-manager/skill.yaml +3 -5
  18. package/.agents/core/skills/context-os/SKILL.md +3 -6
  19. package/.agents/core/skills/context-os/skill.yaml +3 -8
  20. package/.agents/core/skills/engineering-workflow/skill.yaml +1 -7
  21. package/.agents/core/skills/gemini-precision/skill.yaml +1 -6
  22. package/.agents/core/skills/gstack-roles/skill.yaml +3 -6
  23. package/.agents/core/skills/ponytail-mindset/skill.yaml +1 -7
  24. package/.agents/core/skills/security/skill.yaml +15 -3
  25. package/.agents/ctx.js +537 -112
  26. package/.agents/customization-dx.js +282 -0
  27. package/.agents/doctor.js +855 -66
  28. package/.agents/filesystem/index.js +71 -0
  29. package/.agents/filesystem/journaled-transaction.js +451 -0
  30. package/.agents/filesystem/lockfile-v2.js +275 -0
  31. package/.agents/filesystem/platform-hardening.js +222 -0
  32. package/.agents/filesystem/project-lock.js +218 -0
  33. package/.agents/filesystem/safe-path.js +256 -0
  34. package/.agents/generated/claude/skills/context-os/SKILL.md +1 -1
  35. package/.agents/generated/gemini/skills/context-os/SKILL.md +2 -2
  36. package/.agents/plugins/contextos/plugin.json +1 -1
  37. package/.agents/plugins.js +278 -64
  38. package/.agents/profiles.js +486 -51
  39. package/.agents/resolver/canonical-resolver.js +1348 -0
  40. package/.agents/resolver.js +50 -534
  41. package/.agents/rules/rule-catalog.js +525 -0
  42. package/.agents/schemas/attestation.review.v1.json +111 -0
  43. package/.agents/schemas/attestation.verification.v1.json +85 -0
  44. package/.agents/schemas/lockfile.v2.schema.json +134 -0
  45. package/.agents/schemas/profile.v2.schema.json +114 -0
  46. package/.agents/schemas/runtime.thread.v1.json +192 -0
  47. package/.agents/schemas/skill.manifest.v2.json +177 -0
  48. package/.agents/schemas/verification.spec.v1.json +39 -0
  49. package/.agents/schemas/workspace.graph.schema.json +106 -0
  50. package/.agents/skills-index.json +6 -166
  51. package/.agents/stats.js +9 -9
  52. package/.agents/transaction-core/event-store.js +288 -0
  53. package/.agents/transaction-core/idempotency.js +129 -0
  54. package/.agents/transaction-core/ipc-lock.js +311 -0
  55. package/.agents/transaction-core/plugin-supply-chain-bundle.js +436 -0
  56. package/.agents/validate.js +44 -1
  57. package/.agents/watch.js +354 -102
  58. package/.agents/workspace/workspace-graph.js +778 -0
  59. package/README.md +59 -387
  60. package/benchmarks/v2/analysis/statistics.js +140 -0
  61. package/benchmarks/v2/analysis/stats.js +69 -0
  62. package/benchmarks/v2/arms/arm-definitions.js +79 -0
  63. package/benchmarks/v2/dataset.schema.json +34 -0
  64. package/benchmarks/v2/evaluators/index.js +25 -0
  65. package/benchmarks/v2/evaluators/verified-success.js +116 -0
  66. package/benchmarks/v2/harness/runner.js +88 -0
  67. package/benchmarks/v2/pilot-tasks.json +392 -0
  68. package/bin/commands/recover.js +88 -0
  69. package/bin/commands/update.js +80 -17
  70. package/bin/commands.js +62 -25
  71. package/bin/index.js +138 -81
  72. package/bin/lib/lockfile.js +5 -3
  73. package/bin/lib/safe-writer.js +34 -3
  74. package/package.json +87 -72
  75. package/registry.json +2 -2
  76. package/registry.v2.schema.json +86 -0
  77. package/.agents/core/profiles/backend.yaml +0 -47
  78. package/.agents/core/profiles/enterprise.yaml +0 -46
  79. package/.agents/core/profiles/frontend.yaml +0 -46
  80. package/.agents/core/profiles/hackathon.yaml +0 -45
  81. package/.agents/core/profiles/mvp.yaml +0 -44
  82. package/.agents/core/profiles/startup.yaml +0 -48
  83. package/.agents/core/skills/adapters/EXAMPLES.md +0 -19
  84. package/.agents/core/skills/adapters/SKILL.md +0 -105
  85. package/.agents/core/skills/adapters/TROUBLESHOOTING.md +0 -7
  86. package/.agents/core/skills/adapters/VALIDATION.json +0 -12
  87. package/.agents/core/skills/adapters/skill.yaml +0 -16
  88. package/.agents/core/skills/architecture-diagrams/SKILL.md +0 -108
  89. package/.agents/core/skills/architecture-diagrams/VALIDATION.json +0 -12
  90. package/.agents/core/skills/architecture-diagrams/skill.yaml +0 -12
  91. package/.agents/core/skills/brutalist-design/SKILL.md +0 -150
  92. package/.agents/core/skills/brutalist-design/VALIDATION.json +0 -12
  93. package/.agents/core/skills/brutalist-design/skill.yaml +0 -12
  94. package/.agents/core/skills/database/EXAMPLES.md +0 -74
  95. package/.agents/core/skills/database/SKILL.md +0 -101
  96. package/.agents/core/skills/database/TROUBLESHOOTING.md +0 -18
  97. package/.agents/core/skills/database/VALIDATION.json +0 -11
  98. package/.agents/core/skills/database/skill.yaml +0 -31
  99. package/.agents/core/skills/ddd/EXAMPLES.md +0 -42
  100. package/.agents/core/skills/ddd/SKILL.md +0 -247
  101. package/.agents/core/skills/ddd/TROUBLESHOOTING.md +0 -19
  102. package/.agents/core/skills/ddd/VALIDATION.json +0 -12
  103. package/.agents/core/skills/ddd/ddd.md +0 -178
  104. package/.agents/core/skills/ddd/skill.yaml +0 -17
  105. package/.agents/core/skills/decisions/EXAMPLES.md +0 -35
  106. package/.agents/core/skills/decisions/SKILL.md +0 -90
  107. package/.agents/core/skills/decisions/TROUBLESHOOTING.md +0 -13
  108. package/.agents/core/skills/decisions/VALIDATION.json +0 -12
  109. package/.agents/core/skills/decisions/skill.yaml +0 -16
  110. package/.agents/core/skills/docker/EXAMPLES.md +0 -56
  111. package/.agents/core/skills/docker/SKILL.md +0 -63
  112. package/.agents/core/skills/docker/TROUBLESHOOTING.md +0 -18
  113. package/.agents/core/skills/docker/VALIDATION.json +0 -11
  114. package/.agents/core/skills/docker/skill.yaml +0 -29
  115. package/.agents/core/skills/fastapi/EXAMPLES.md +0 -36
  116. package/.agents/core/skills/fastapi/SKILL.md +0 -148
  117. package/.agents/core/skills/fastapi/TROUBLESHOOTING.md +0 -19
  118. package/.agents/core/skills/fastapi/VALIDATION.json +0 -12
  119. package/.agents/core/skills/fastapi/fastapi.md +0 -112
  120. package/.agents/core/skills/fastapi/skill.yaml +0 -17
  121. package/.agents/core/skills/generators/EXAMPLES.md +0 -19
  122. package/.agents/core/skills/generators/SKILL.md +0 -112
  123. package/.agents/core/skills/generators/TROUBLESHOOTING.md +0 -7
  124. package/.agents/core/skills/generators/VALIDATION.json +0 -12
  125. package/.agents/core/skills/generators/skill.yaml +0 -25
  126. package/.agents/core/skills/generators/templates/API.md +0 -77
  127. package/.agents/core/skills/generators/templates/ARCHITECTURE.md +0 -70
  128. package/.agents/core/skills/generators/templates/DATABASE.md +0 -42
  129. package/.agents/core/skills/generators/templates/DECISION.md +0 -46
  130. package/.agents/core/skills/generators/templates/PRD.md +0 -67
  131. package/.agents/core/skills/generators/templates/PROJECT_GRAPH.md +0 -56
  132. package/.agents/core/skills/generators/templates/ROADMAP.md +0 -51
  133. package/.agents/core/skills/generators/templates/TASKS.md +0 -43
  134. package/.agents/core/skills/generators/templates/UI.md +0 -73
  135. package/.agents/core/skills/graphify/EXAMPLES.md +0 -73
  136. package/.agents/core/skills/graphify/SKILL.md +0 -130
  137. package/.agents/core/skills/graphify/VALIDATION.json +0 -12
  138. package/.agents/core/skills/graphify/skill.yaml +0 -18
  139. package/.agents/core/skills/impeccable-design/EXAMPLES.md +0 -26
  140. package/.agents/core/skills/impeccable-design/SKILL.md +0 -201
  141. package/.agents/core/skills/impeccable-design/TROUBLESHOOTING.md +0 -19
  142. package/.agents/core/skills/impeccable-design/VALIDATION.json +0 -12
  143. package/.agents/core/skills/impeccable-design/skill.yaml +0 -20
  144. package/.agents/core/skills/interview-me/SKILL.md +0 -97
  145. package/.agents/core/skills/interview-me/VALIDATION.json +0 -12
  146. package/.agents/core/skills/interview-me/skill.yaml +0 -12
  147. package/.agents/core/skills/microservices/EXAMPLES.md +0 -38
  148. package/.agents/core/skills/microservices/SKILL.md +0 -164
  149. package/.agents/core/skills/microservices/TROUBLESHOOTING.md +0 -19
  150. package/.agents/core/skills/microservices/VALIDATION.json +0 -12
  151. package/.agents/core/skills/microservices/microservices.md +0 -119
  152. package/.agents/core/skills/microservices/skill.yaml +0 -17
  153. package/.agents/core/skills/minimalist-design/SKILL.md +0 -113
  154. package/.agents/core/skills/minimalist-design/VALIDATION.json +0 -12
  155. package/.agents/core/skills/minimalist-design/skill.yaml +0 -12
  156. package/.agents/core/skills/nestjs/EXAMPLES.md +0 -40
  157. package/.agents/core/skills/nestjs/SKILL.md +0 -139
  158. package/.agents/core/skills/nestjs/TROUBLESHOOTING.md +0 -19
  159. package/.agents/core/skills/nestjs/VALIDATION.json +0 -12
  160. package/.agents/core/skills/nestjs/nestjs.md +0 -103
  161. package/.agents/core/skills/nestjs/skill.yaml +0 -17
  162. package/.agents/core/skills/nextjs/EXAMPLES.md +0 -40
  163. package/.agents/core/skills/nextjs/SKILL.md +0 -163
  164. package/.agents/core/skills/nextjs/TROUBLESHOOTING.md +0 -19
  165. package/.agents/core/skills/nextjs/VALIDATION.json +0 -12
  166. package/.agents/core/skills/nextjs/nextjs.md +0 -67
  167. package/.agents/core/skills/nextjs/skill.yaml +0 -17
  168. package/.agents/core/skills/node/EXAMPLES.md +0 -80
  169. package/.agents/core/skills/node/SKILL.md +0 -128
  170. package/.agents/core/skills/node/TROUBLESHOOTING.md +0 -19
  171. package/.agents/core/skills/node/VALIDATION.json +0 -12
  172. package/.agents/core/skills/node/node.md +0 -87
  173. package/.agents/core/skills/node/skill.yaml +0 -17
  174. package/.agents/core/skills/performance/EXAMPLES.md +0 -30
  175. package/.agents/core/skills/performance/SKILL.md +0 -75
  176. package/.agents/core/skills/performance/TROUBLESHOOTING.md +0 -19
  177. package/.agents/core/skills/performance/VALIDATION.json +0 -12
  178. package/.agents/core/skills/performance/performance.md +0 -52
  179. package/.agents/core/skills/performance/skill.yaml +0 -17
  180. package/.agents/core/skills/react/EXAMPLES.md +0 -79
  181. package/.agents/core/skills/react/SKILL.md +0 -132
  182. package/.agents/core/skills/react/TROUBLESHOOTING.md +0 -19
  183. package/.agents/core/skills/react/VALIDATION.json +0 -12
  184. package/.agents/core/skills/react/react.md +0 -93
  185. package/.agents/core/skills/react/skill.yaml +0 -17
  186. package/.agents/core/skills/react-best-practices/SKILL.md +0 -155
  187. package/.agents/core/skills/react-best-practices/VALIDATION.json +0 -12
  188. package/.agents/core/skills/react-best-practices/skill.yaml +0 -14
  189. package/.agents/core/skills/redesign-audit/SKILL.md +0 -117
  190. package/.agents/core/skills/redesign-audit/VALIDATION.json +0 -12
  191. package/.agents/core/skills/redesign-audit/skill.yaml +0 -12
  192. package/.agents/core/skills/soft-design/SKILL.md +0 -108
  193. package/.agents/core/skills/soft-design/VALIDATION.json +0 -12
  194. package/.agents/core/skills/soft-design/skill.yaml +0 -12
  195. package/.agents/core/skills/state-management/EXAMPLES.md +0 -56
  196. package/.agents/core/skills/state-management/SKILL.md +0 -48
  197. package/.agents/core/skills/state-management/TROUBLESHOOTING.md +0 -18
  198. package/.agents/core/skills/state-management/VALIDATION.json +0 -11
  199. package/.agents/core/skills/state-management/skill.yaml +0 -28
  200. package/.agents/core/skills/subagent-orchestrator/SKILL.md +0 -117
  201. package/.agents/core/skills/subagent-orchestrator/VALIDATION.json +0 -12
  202. package/.agents/core/skills/subagent-orchestrator/skill.yaml +0 -12
  203. package/.agents/core/skills/system-design/EXAMPLES.md +0 -75
  204. package/.agents/core/skills/system-design/SKILL.md +0 -419
  205. package/.agents/core/skills/system-design/TROUBLESHOOTING.md +0 -19
  206. package/.agents/core/skills/system-design/VALIDATION.json +0 -12
  207. package/.agents/core/skills/system-design/skill.yaml +0 -20
  208. package/.agents/core/skills/system-design/system-design.md +0 -112
  209. package/.agents/core/skills/testing/EXAMPLES.md +0 -71
  210. package/.agents/core/skills/testing/SKILL.md +0 -70
  211. package/.agents/core/skills/testing/TROUBLESHOOTING.md +0 -18
  212. package/.agents/core/skills/testing/VALIDATION.json +0 -11
  213. package/.agents/core/skills/testing/skill.yaml +0 -32
  214. package/.agents/core/skills/typescript/EXAMPLES.md +0 -64
  215. package/.agents/core/skills/typescript/SKILL.md +0 -112
  216. package/.agents/core/skills/typescript/TROUBLESHOOTING.md +0 -19
  217. package/.agents/core/skills/typescript/VALIDATION.json +0 -12
  218. package/.agents/core/skills/typescript/skill.yaml +0 -17
  219. package/.agents/core/skills/typescript/typescript.md +0 -71
  220. package/.agents/core/skills/ui-design/EXAMPLES.md +0 -21
  221. package/.agents/core/skills/ui-design/SKILL.md +0 -124
  222. package/.agents/core/skills/ui-design/TROUBLESHOOTING.md +0 -19
  223. package/.agents/core/skills/ui-design/VALIDATION.json +0 -12
  224. package/.agents/core/skills/ui-design/skill.yaml +0 -17
  225. package/.agents/core/skills/ui-design/ui.md +0 -88
  226. package/.agents/core/skills/ui-ux-pro/EXAMPLES.md +0 -62
  227. package/.agents/core/skills/ui-ux-pro/SKILL.md +0 -375
  228. package/.agents/core/skills/ui-ux-pro/TROUBLESHOOTING.md +0 -19
  229. package/.agents/core/skills/ui-ux-pro/VALIDATION.json +0 -12
  230. package/.agents/core/skills/ui-ux-pro/skill.yaml +0 -19
  231. package/.agents/core/skills/ux-design/EXAMPLES.md +0 -36
  232. package/.agents/core/skills/ux-design/SKILL.md +0 -116
  233. package/.agents/core/skills/ux-design/TROUBLESHOOTING.md +0 -19
  234. package/.agents/core/skills/ux-design/VALIDATION.json +0 -12
  235. package/.agents/core/skills/ux-design/skill.yaml +0 -17
  236. package/.agents/core/skills/ux-design/ux.md +0 -80
  237. package/.agents/core/skills/vercel-optimize/SKILL.md +0 -83
  238. package/.agents/core/skills/vercel-optimize/VALIDATION.json +0 -12
  239. package/.agents/core/skills/vercel-optimize/scripts/collect-signals.mjs +0 -131
  240. package/.agents/core/skills/vercel-optimize/scripts/gate-investigations.mjs +0 -142
  241. package/.agents/core/skills/vercel-optimize/scripts/merge-signals.mjs +0 -143
  242. package/.agents/core/skills/vercel-optimize/scripts/scan-codebase.mjs +0 -174
  243. package/.agents/core/skills/vercel-optimize/skill.yaml +0 -18
  244. package/.agents/core/skills/web-accessibility/EXAMPLES.md +0 -39
  245. package/.agents/core/skills/web-accessibility/SKILL.md +0 -170
  246. package/.agents/core/skills/web-accessibility/TROUBLESHOOTING.md +0 -19
  247. package/.agents/core/skills/web-accessibility/VALIDATION.json +0 -12
  248. package/.agents/core/skills/web-accessibility/accessibility.md +0 -63
  249. package/.agents/core/skills/web-accessibility/skill.yaml +0 -17
  250. package/.agents/generated/claude/skills/adapters/SKILL.md +0 -126
  251. package/.agents/generated/claude/skills/architecture-diagrams/SKILL.md +0 -101
  252. package/.agents/generated/claude/skills/brutalist-design/SKILL.md +0 -145
  253. package/.agents/generated/claude/skills/database/SKILL.md +0 -191
  254. package/.agents/generated/claude/skills/ddd/SKILL.md +0 -305
  255. package/.agents/generated/claude/skills/decisions/SKILL.md +0 -134
  256. package/.agents/generated/claude/skills/docker/SKILL.md +0 -135
  257. package/.agents/generated/claude/skills/fastapi/SKILL.md +0 -200
  258. package/.agents/generated/claude/skills/generators/SKILL.md +0 -133
  259. package/.agents/generated/claude/skills/graphify/SKILL.md +0 -198
  260. package/.agents/generated/claude/skills/impeccable-design/SKILL.md +0 -241
  261. package/.agents/generated/claude/skills/interview-me/SKILL.md +0 -90
  262. package/.agents/generated/claude/skills/microservices/SKILL.md +0 -218
  263. package/.agents/generated/claude/skills/minimalist-design/SKILL.md +0 -108
  264. package/.agents/generated/claude/skills/nestjs/SKILL.md +0 -195
  265. package/.agents/generated/claude/skills/nextjs/SKILL.md +0 -219
  266. package/.agents/generated/claude/skills/node/SKILL.md +0 -224
  267. package/.agents/generated/claude/skills/performance/SKILL.md +0 -121
  268. package/.agents/generated/claude/skills/react/SKILL.md +0 -227
  269. package/.agents/generated/claude/skills/react-best-practices/SKILL.md +0 -146
  270. package/.agents/generated/claude/skills/redesign-audit/SKILL.md +0 -112
  271. package/.agents/generated/claude/skills/soft-design/SKILL.md +0 -103
  272. package/.agents/generated/claude/skills/state-management/SKILL.md +0 -120
  273. package/.agents/generated/claude/skills/subagent-orchestrator/SKILL.md +0 -110
  274. package/.agents/generated/claude/skills/system-design/SKILL.md +0 -507
  275. package/.agents/generated/claude/skills/testing/SKILL.md +0 -157
  276. package/.agents/generated/claude/skills/typescript/SKILL.md +0 -192
  277. package/.agents/generated/claude/skills/ui-design/SKILL.md +0 -161
  278. package/.agents/generated/claude/skills/ui-ux-pro/SKILL.md +0 -451
  279. package/.agents/generated/claude/skills/ux-design/SKILL.md +0 -168
  280. package/.agents/generated/claude/skills/vercel-optimize/SKILL.md +0 -76
  281. package/.agents/generated/claude/skills/web-accessibility/SKILL.md +0 -225
  282. package/.agents/generated/gemini/skills/adapters/SKILL.md +0 -135
  283. package/.agents/generated/gemini/skills/architecture-diagrams/SKILL.md +0 -107
  284. package/.agents/generated/gemini/skills/brutalist-design/SKILL.md +0 -151
  285. package/.agents/generated/gemini/skills/database/SKILL.md +0 -200
  286. package/.agents/generated/gemini/skills/ddd/SKILL.md +0 -314
  287. package/.agents/generated/gemini/skills/decisions/SKILL.md +0 -143
  288. package/.agents/generated/gemini/skills/docker/SKILL.md +0 -144
  289. package/.agents/generated/gemini/skills/fastapi/SKILL.md +0 -209
  290. package/.agents/generated/gemini/skills/generators/SKILL.md +0 -142
  291. package/.agents/generated/gemini/skills/graphify/SKILL.md +0 -205
  292. package/.agents/generated/gemini/skills/impeccable-design/SKILL.md +0 -250
  293. package/.agents/generated/gemini/skills/interview-me/SKILL.md +0 -96
  294. package/.agents/generated/gemini/skills/microservices/SKILL.md +0 -227
  295. package/.agents/generated/gemini/skills/minimalist-design/SKILL.md +0 -114
  296. package/.agents/generated/gemini/skills/nestjs/SKILL.md +0 -204
  297. package/.agents/generated/gemini/skills/nextjs/SKILL.md +0 -298
  298. package/.agents/generated/gemini/skills/node/SKILL.md +0 -323
  299. package/.agents/generated/gemini/skills/performance/SKILL.md +0 -185
  300. package/.agents/generated/gemini/skills/react/SKILL.md +0 -332
  301. package/.agents/generated/gemini/skills/react-best-practices/SKILL.md +0 -152
  302. package/.agents/generated/gemini/skills/redesign-audit/SKILL.md +0 -118
  303. package/.agents/generated/gemini/skills/soft-design/SKILL.md +0 -109
  304. package/.agents/generated/gemini/skills/state-management/SKILL.md +0 -129
  305. package/.agents/generated/gemini/skills/subagent-orchestrator/SKILL.md +0 -116
  306. package/.agents/generated/gemini/skills/system-design/SKILL.md +0 -631
  307. package/.agents/generated/gemini/skills/testing/SKILL.md +0 -166
  308. package/.agents/generated/gemini/skills/typescript/SKILL.md +0 -275
  309. package/.agents/generated/gemini/skills/ui-design/SKILL.md +0 -170
  310. package/.agents/generated/gemini/skills/ui-ux-pro/SKILL.md +0 -460
  311. package/.agents/generated/gemini/skills/ux-design/SKILL.md +0 -177
  312. package/.agents/generated/gemini/skills/vercel-optimize/SKILL.md +0 -82
  313. package/.agents/generated/gemini/skills/web-accessibility/SKILL.md +0 -300
@@ -0,0 +1,311 @@
1
+ /**
2
+ * .agents/runtime/ipc-lock.js
3
+ * ContextOS — Durable IPC Lease Lock Engine with Fencing Tokens
4
+ *
5
+ * Implements Section 17.1 & 17.4 of CONTEXTOS_IMPLEMENTATION_PLAN.md:
6
+ * - Atomic acquisition with monotonic fencing tokens
7
+ * - Hostname, PID, processStartedAt, instanceId, and ownerToken tracking
8
+ * - Heartbeat lease extension with configurable TTL
9
+ * - Stale owner release prevention (stale owner cannot release new lock)
10
+ * - Pure async wait with bounded exponential backoff, jitter, and AbortSignal
11
+ * - Zero install-time dependencies (pure Node.js crypto & fs)
12
+ */
13
+
14
+ 'use strict';
15
+
16
+ const fs = require('fs');
17
+ const path = require('path');
18
+ const os = require('os');
19
+ const crypto = require('crypto');
20
+
21
+ class LeaseLock {
22
+ /**
23
+ * @param {Object} options
24
+ * @param {string} options.lockFilePath - Absolute path to lock file
25
+ * @param {number} [options.ttlMs=15000] - Lease duration before expiration
26
+ * @param {number} [options.heartbeatIntervalMs=5000] - Interval between lease renewal heartbeats
27
+ * @param {string} [options.instanceId] - Machine/process instance UUID
28
+ * @param {string} [options.repositoryFingerprint]
29
+ */
30
+ constructor(options = {}) {
31
+ if (!options.lockFilePath) {
32
+ throw new Error('LeaseLock requires lockFilePath');
33
+ }
34
+
35
+ this.lockFilePath = path.resolve(options.lockFilePath);
36
+ this.lockDir = path.dirname(this.lockFilePath);
37
+ this.ttlMs = Math.max(1000, options.ttlMs || 15000);
38
+ this.heartbeatIntervalMs = Math.max(500, options.heartbeatIntervalMs || 5000);
39
+ this.instanceId = options.instanceId || crypto.randomUUID();
40
+ this.repositoryFingerprint = options.repositoryFingerprint || 'none';
41
+
42
+ this.ownerToken = null;
43
+ this.fencingToken = null;
44
+ this.heartbeatTimer = null;
45
+ this.processStartedAt = Date.now();
46
+ this.isHeld = false;
47
+ }
48
+
49
+ /**
50
+ * Reads current lock info from disk if present.
51
+ *
52
+ * @returns {Object|null}
53
+ */
54
+ readLock() {
55
+ if (!fs.existsSync(this.lockFilePath)) return null;
56
+
57
+ try {
58
+ const content = fs.readFileSync(this.lockFilePath, 'utf8');
59
+ return JSON.parse(content);
60
+ } catch {
61
+ return null;
62
+ }
63
+ }
64
+
65
+ /**
66
+ * Attempts a single atomic acquisition of the lease.
67
+ *
68
+ * @returns {boolean} True if acquired
69
+ */
70
+ tryAcquire() {
71
+ fs.mkdirSync(this.lockDir, { recursive: true });
72
+ const now = Date.now();
73
+ const current = this.readLock();
74
+
75
+ let nextFencingToken = 1;
76
+
77
+ if (current) {
78
+ const isExpired = current.expiresAt && now > current.expiresAt;
79
+ if (!isExpired) {
80
+ return false; // Valid unexpired lock held by another owner
81
+ }
82
+ // Lock is expired: takeover with incremented fencing token
83
+ nextFencingToken = (current.fencingToken || 0) + 1;
84
+ }
85
+
86
+ const newOwnerToken = crypto.randomUUID();
87
+ const payload = {
88
+ ownerToken: newOwnerToken,
89
+ fencingToken: nextFencingToken,
90
+ instanceId: this.instanceId,
91
+ hostname: os.hostname(),
92
+ pid: process.pid,
93
+ processStartedAt: this.processStartedAt,
94
+ heartbeatAt: now,
95
+ expiresAt: now + this.ttlMs,
96
+ repositoryFingerprint: this.repositoryFingerprint,
97
+ };
98
+
99
+ // Atomic write via unique temp file and rename/link
100
+ const tempFile = `${this.lockFilePath}.${crypto.randomBytes(6).toString('hex')}.tmp`;
101
+ try {
102
+ fs.writeFileSync(tempFile, JSON.stringify(payload, null, 2), { flag: 'wx', encoding: 'utf8' });
103
+
104
+ // Atomically link or rename
105
+ try {
106
+ if (process.platform === 'win32') {
107
+ // On Windows, if destination exists, renameSync fails unless unlinked first.
108
+ // Check again that current lock is still expired before replacing.
109
+ const fresh = this.readLock();
110
+ if (fresh && fresh.expiresAt && now <= fresh.expiresAt) {
111
+ fs.unlinkSync(tempFile);
112
+ return false;
113
+ }
114
+ if (fs.existsSync(this.lockFilePath)) {
115
+ try { fs.unlinkSync(this.lockFilePath); } catch {}
116
+ }
117
+ fs.renameSync(tempFile, this.lockFilePath);
118
+ } else {
119
+ // On Unix, rename is atomic POSIX replace
120
+ fs.renameSync(tempFile, this.lockFilePath);
121
+ }
122
+ } catch (renameErr) {
123
+ try { if (fs.existsSync(tempFile)) fs.unlinkSync(tempFile); } catch {}
124
+ return false;
125
+ }
126
+
127
+ // Verify that our token won the race
128
+ const verified = this.readLock();
129
+ if (!verified || verified.ownerToken !== newOwnerToken) {
130
+ return false;
131
+ }
132
+
133
+ this.ownerToken = newOwnerToken;
134
+ this.fencingToken = nextFencingToken;
135
+ this.isHeld = true;
136
+ this._startHeartbeat();
137
+ return true;
138
+ } catch {
139
+ try { if (fs.existsSync(tempFile)) fs.unlinkSync(tempFile); } catch {}
140
+ return false;
141
+ }
142
+ }
143
+
144
+ /**
145
+ * Pure async wait with exponential backoff and jitter (Section 17.4).
146
+ * No Atomics.wait on event loop.
147
+ *
148
+ * @param {Object} [options]
149
+ * @param {number} [options.timeoutMs=30000]
150
+ * @param {AbortSignal} [options.signal]
151
+ * @returns {Promise<boolean>}
152
+ */
153
+ async acquire(options = {}) {
154
+ const timeoutMs = options.timeoutMs || 30000;
155
+ const signal = options.signal;
156
+ const startTime = Date.now();
157
+
158
+ let attempt = 0;
159
+ const baseDelayMs = 50;
160
+ const maxDelayMs = 1000;
161
+
162
+ while (true) {
163
+ if (signal && signal.aborted) {
164
+ const err = new Error('Lock acquisition aborted');
165
+ err.code = 'CTX_LOCK_ACQUISITION_ABORTED';
166
+ throw err;
167
+ }
168
+
169
+ if (this.tryAcquire()) {
170
+ return true;
171
+ }
172
+
173
+ if (Date.now() - startTime >= timeoutMs) {
174
+ const err = new Error(`Timed out waiting to acquire lock: "${this.lockFilePath}" (${timeoutMs}ms)`);
175
+ err.code = 'CTX_LOCK_TIMEOUT';
176
+ throw err;
177
+ }
178
+
179
+ // Exponential backoff with jitter
180
+ attempt++;
181
+ const expDelay = Math.min(maxDelayMs, baseDelayMs * Math.pow(1.5, attempt));
182
+ const jitter = Math.random() * 0.3 * expDelay;
183
+ const sleepMs = Math.floor(expDelay + jitter);
184
+
185
+ await new Promise(resolve => setTimeout(resolve, sleepMs));
186
+ }
187
+ }
188
+
189
+ /**
190
+ * Starts periodic heartbeat extending lease expiration.
191
+ */
192
+ _startHeartbeat() {
193
+ this._stopHeartbeat();
194
+ this.heartbeatTimer = setInterval(() => {
195
+ this.heartbeat();
196
+ }, this.heartbeatIntervalMs);
197
+ // Do not prevent process exit if only heartbeat timer is alive
198
+ if (this.heartbeatTimer.unref) {
199
+ this.heartbeatTimer.unref();
200
+ }
201
+ }
202
+
203
+ _stopHeartbeat() {
204
+ if (this.heartbeatTimer) {
205
+ clearInterval(this.heartbeatTimer);
206
+ this.heartbeatTimer = null;
207
+ }
208
+ }
209
+
210
+ /**
211
+ * Extends lease expiration on disk if we are still the valid owner.
212
+ *
213
+ * @returns {boolean} True if successfully renewed
214
+ */
215
+ heartbeat() {
216
+ if (!this.isHeld || !this.ownerToken) return false;
217
+
218
+ const current = this.readLock();
219
+ // Safety invariant: only renew if our ownerToken is on disk
220
+ if (!current || current.ownerToken !== this.ownerToken) {
221
+ // We lost the lock or lease expired and was taken over
222
+ this.isHeld = false;
223
+ this._stopHeartbeat();
224
+ return false;
225
+ }
226
+
227
+ const now = Date.now();
228
+ current.heartbeatAt = now;
229
+ current.expiresAt = now + this.ttlMs;
230
+
231
+ try {
232
+ fs.writeFileSync(this.lockFilePath, JSON.stringify(current, null, 2), 'utf8');
233
+ return true;
234
+ } catch {
235
+ return false;
236
+ }
237
+ }
238
+
239
+ /**
240
+ * Releases lock strictly if ownerToken matches on disk.
241
+ * Stale owner can NEVER delete a new lock acquired by another process.
242
+ *
243
+ * @returns {boolean} True if released
244
+ */
245
+ release() {
246
+ this._stopHeartbeat();
247
+
248
+ if (!this.ownerToken) {
249
+ this.isHeld = false;
250
+ return false;
251
+ }
252
+
253
+ const current = this.readLock();
254
+ // Invariant: stale owner cannot delete a new lock
255
+ if (!current || current.ownerToken !== this.ownerToken) {
256
+ this.isHeld = false;
257
+ this.ownerToken = null;
258
+ return false; // Another owner already holds the lock
259
+ }
260
+
261
+ try {
262
+ if (fs.existsSync(this.lockFilePath)) {
263
+ fs.unlinkSync(this.lockFilePath);
264
+ }
265
+ this.isHeld = false;
266
+ this.ownerToken = null;
267
+ return true;
268
+ } catch {
269
+ this.isHeld = false;
270
+ return false;
271
+ }
272
+ }
273
+
274
+ /**
275
+ * Asserts that current process holds a valid unexpired lease with expected fencing token.
276
+ *
277
+ * @param {number} [expectedFencingToken]
278
+ */
279
+ assertValid(expectedFencingToken) {
280
+ if (!this.isHeld || !this.ownerToken) {
281
+ const err = new Error('Lock is not held by current process');
282
+ err.code = 'CTX_LOCK_NOT_HELD';
283
+ throw err;
284
+ }
285
+
286
+ const current = this.readLock();
287
+ if (!current || current.ownerToken !== this.ownerToken) {
288
+ this.isHeld = false;
289
+ const err = new Error('Lock lease was lost or taken over by another process');
290
+ err.code = 'CTX_LOCK_LOST';
291
+ throw err;
292
+ }
293
+
294
+ if (Date.now() > current.expiresAt) {
295
+ this.isHeld = false;
296
+ const err = new Error('Lock lease has expired');
297
+ err.code = 'CTX_LOCK_EXPIRED';
298
+ throw err;
299
+ }
300
+
301
+ if (typeof expectedFencingToken === 'number' && current.fencingToken !== expectedFencingToken) {
302
+ const err = new Error(`Fencing token mismatch: expected ${expectedFencingToken}, got ${current.fencingToken}`);
303
+ err.code = 'CTX_FENCING_TOKEN_MISMATCH';
304
+ throw err;
305
+ }
306
+ }
307
+ }
308
+
309
+ module.exports = {
310
+ LeaseLock,
311
+ };