contextos-agents 1.7.0 → 2.0.0-beta.2

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 (309) hide show
  1. package/.agents/AGENTS.md +1 -1
  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 +80 -101
  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 +574 -111
  26. package/.agents/customization-dx.js +282 -0
  27. package/.agents/doctor.js +719 -64
  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 +259 -60
  38. package/.agents/profiles.js +486 -51
  39. package/.agents/resolver.js +50 -534
  40. package/.agents/schemas/attestation.review.v1.json +111 -0
  41. package/.agents/schemas/attestation.verification.v1.json +85 -0
  42. package/.agents/schemas/lockfile.v2.schema.json +134 -0
  43. package/.agents/schemas/profile.v2.schema.json +114 -0
  44. package/.agents/schemas/runtime.thread.v1.json +192 -0
  45. package/.agents/schemas/skill.manifest.v2.json +177 -0
  46. package/.agents/schemas/verification.spec.v1.json +39 -0
  47. package/.agents/schemas/workspace.graph.schema.json +106 -0
  48. package/.agents/transaction-core/event-store.js +288 -0
  49. package/.agents/transaction-core/idempotency.js +129 -0
  50. package/.agents/transaction-core/ipc-lock.js +311 -0
  51. package/.agents/transaction-core/plugin-supply-chain-bundle.js +436 -0
  52. package/.agents/validate.js +44 -1
  53. package/.agents/watch.js +354 -102
  54. package/.agents/workspace/workspace-graph.js +778 -0
  55. package/README.md +59 -387
  56. package/benchmarks/v2/analysis/statistics.js +140 -0
  57. package/benchmarks/v2/analysis/stats.js +69 -0
  58. package/benchmarks/v2/arms/arm-definitions.js +79 -0
  59. package/benchmarks/v2/dataset.schema.json +34 -0
  60. package/benchmarks/v2/evaluators/index.js +25 -0
  61. package/benchmarks/v2/evaluators/verified-success.js +116 -0
  62. package/benchmarks/v2/harness/runner.js +88 -0
  63. package/benchmarks/v2/pilot-tasks.json +392 -0
  64. package/bin/commands/recover.js +88 -0
  65. package/bin/commands/update.js +80 -17
  66. package/bin/commands.js +62 -25
  67. package/bin/index.js +138 -81
  68. package/bin/lib/lockfile.js +5 -3
  69. package/bin/lib/safe-writer.js +34 -3
  70. package/package.json +85 -72
  71. package/registry.json +2 -2
  72. package/registry.v2.schema.json +86 -0
  73. package/.agents/core/profiles/backend.yaml +0 -47
  74. package/.agents/core/profiles/enterprise.yaml +0 -46
  75. package/.agents/core/profiles/frontend.yaml +0 -46
  76. package/.agents/core/profiles/hackathon.yaml +0 -45
  77. package/.agents/core/profiles/mvp.yaml +0 -44
  78. package/.agents/core/profiles/startup.yaml +0 -48
  79. package/.agents/core/skills/adapters/EXAMPLES.md +0 -19
  80. package/.agents/core/skills/adapters/SKILL.md +0 -105
  81. package/.agents/core/skills/adapters/TROUBLESHOOTING.md +0 -7
  82. package/.agents/core/skills/adapters/VALIDATION.json +0 -12
  83. package/.agents/core/skills/adapters/skill.yaml +0 -16
  84. package/.agents/core/skills/architecture-diagrams/SKILL.md +0 -108
  85. package/.agents/core/skills/architecture-diagrams/VALIDATION.json +0 -12
  86. package/.agents/core/skills/architecture-diagrams/skill.yaml +0 -12
  87. package/.agents/core/skills/brutalist-design/SKILL.md +0 -150
  88. package/.agents/core/skills/brutalist-design/VALIDATION.json +0 -12
  89. package/.agents/core/skills/brutalist-design/skill.yaml +0 -12
  90. package/.agents/core/skills/database/EXAMPLES.md +0 -74
  91. package/.agents/core/skills/database/SKILL.md +0 -101
  92. package/.agents/core/skills/database/TROUBLESHOOTING.md +0 -18
  93. package/.agents/core/skills/database/VALIDATION.json +0 -11
  94. package/.agents/core/skills/database/skill.yaml +0 -31
  95. package/.agents/core/skills/ddd/EXAMPLES.md +0 -42
  96. package/.agents/core/skills/ddd/SKILL.md +0 -247
  97. package/.agents/core/skills/ddd/TROUBLESHOOTING.md +0 -19
  98. package/.agents/core/skills/ddd/VALIDATION.json +0 -12
  99. package/.agents/core/skills/ddd/ddd.md +0 -178
  100. package/.agents/core/skills/ddd/skill.yaml +0 -17
  101. package/.agents/core/skills/decisions/EXAMPLES.md +0 -35
  102. package/.agents/core/skills/decisions/SKILL.md +0 -90
  103. package/.agents/core/skills/decisions/TROUBLESHOOTING.md +0 -13
  104. package/.agents/core/skills/decisions/VALIDATION.json +0 -12
  105. package/.agents/core/skills/decisions/skill.yaml +0 -16
  106. package/.agents/core/skills/docker/EXAMPLES.md +0 -56
  107. package/.agents/core/skills/docker/SKILL.md +0 -63
  108. package/.agents/core/skills/docker/TROUBLESHOOTING.md +0 -18
  109. package/.agents/core/skills/docker/VALIDATION.json +0 -11
  110. package/.agents/core/skills/docker/skill.yaml +0 -29
  111. package/.agents/core/skills/fastapi/EXAMPLES.md +0 -36
  112. package/.agents/core/skills/fastapi/SKILL.md +0 -148
  113. package/.agents/core/skills/fastapi/TROUBLESHOOTING.md +0 -19
  114. package/.agents/core/skills/fastapi/VALIDATION.json +0 -12
  115. package/.agents/core/skills/fastapi/fastapi.md +0 -112
  116. package/.agents/core/skills/fastapi/skill.yaml +0 -17
  117. package/.agents/core/skills/generators/EXAMPLES.md +0 -19
  118. package/.agents/core/skills/generators/SKILL.md +0 -112
  119. package/.agents/core/skills/generators/TROUBLESHOOTING.md +0 -7
  120. package/.agents/core/skills/generators/VALIDATION.json +0 -12
  121. package/.agents/core/skills/generators/skill.yaml +0 -25
  122. package/.agents/core/skills/generators/templates/API.md +0 -77
  123. package/.agents/core/skills/generators/templates/ARCHITECTURE.md +0 -70
  124. package/.agents/core/skills/generators/templates/DATABASE.md +0 -42
  125. package/.agents/core/skills/generators/templates/DECISION.md +0 -46
  126. package/.agents/core/skills/generators/templates/PRD.md +0 -67
  127. package/.agents/core/skills/generators/templates/PROJECT_GRAPH.md +0 -56
  128. package/.agents/core/skills/generators/templates/ROADMAP.md +0 -51
  129. package/.agents/core/skills/generators/templates/TASKS.md +0 -43
  130. package/.agents/core/skills/generators/templates/UI.md +0 -73
  131. package/.agents/core/skills/graphify/EXAMPLES.md +0 -73
  132. package/.agents/core/skills/graphify/SKILL.md +0 -130
  133. package/.agents/core/skills/graphify/VALIDATION.json +0 -12
  134. package/.agents/core/skills/graphify/skill.yaml +0 -18
  135. package/.agents/core/skills/impeccable-design/EXAMPLES.md +0 -26
  136. package/.agents/core/skills/impeccable-design/SKILL.md +0 -201
  137. package/.agents/core/skills/impeccable-design/TROUBLESHOOTING.md +0 -19
  138. package/.agents/core/skills/impeccable-design/VALIDATION.json +0 -12
  139. package/.agents/core/skills/impeccable-design/skill.yaml +0 -20
  140. package/.agents/core/skills/interview-me/SKILL.md +0 -97
  141. package/.agents/core/skills/interview-me/VALIDATION.json +0 -12
  142. package/.agents/core/skills/interview-me/skill.yaml +0 -12
  143. package/.agents/core/skills/microservices/EXAMPLES.md +0 -38
  144. package/.agents/core/skills/microservices/SKILL.md +0 -164
  145. package/.agents/core/skills/microservices/TROUBLESHOOTING.md +0 -19
  146. package/.agents/core/skills/microservices/VALIDATION.json +0 -12
  147. package/.agents/core/skills/microservices/microservices.md +0 -119
  148. package/.agents/core/skills/microservices/skill.yaml +0 -17
  149. package/.agents/core/skills/minimalist-design/SKILL.md +0 -113
  150. package/.agents/core/skills/minimalist-design/VALIDATION.json +0 -12
  151. package/.agents/core/skills/minimalist-design/skill.yaml +0 -12
  152. package/.agents/core/skills/nestjs/EXAMPLES.md +0 -40
  153. package/.agents/core/skills/nestjs/SKILL.md +0 -139
  154. package/.agents/core/skills/nestjs/TROUBLESHOOTING.md +0 -19
  155. package/.agents/core/skills/nestjs/VALIDATION.json +0 -12
  156. package/.agents/core/skills/nestjs/nestjs.md +0 -103
  157. package/.agents/core/skills/nestjs/skill.yaml +0 -17
  158. package/.agents/core/skills/nextjs/EXAMPLES.md +0 -40
  159. package/.agents/core/skills/nextjs/SKILL.md +0 -163
  160. package/.agents/core/skills/nextjs/TROUBLESHOOTING.md +0 -19
  161. package/.agents/core/skills/nextjs/VALIDATION.json +0 -12
  162. package/.agents/core/skills/nextjs/nextjs.md +0 -67
  163. package/.agents/core/skills/nextjs/skill.yaml +0 -17
  164. package/.agents/core/skills/node/EXAMPLES.md +0 -80
  165. package/.agents/core/skills/node/SKILL.md +0 -128
  166. package/.agents/core/skills/node/TROUBLESHOOTING.md +0 -19
  167. package/.agents/core/skills/node/VALIDATION.json +0 -12
  168. package/.agents/core/skills/node/node.md +0 -87
  169. package/.agents/core/skills/node/skill.yaml +0 -17
  170. package/.agents/core/skills/performance/EXAMPLES.md +0 -30
  171. package/.agents/core/skills/performance/SKILL.md +0 -75
  172. package/.agents/core/skills/performance/TROUBLESHOOTING.md +0 -19
  173. package/.agents/core/skills/performance/VALIDATION.json +0 -12
  174. package/.agents/core/skills/performance/performance.md +0 -52
  175. package/.agents/core/skills/performance/skill.yaml +0 -17
  176. package/.agents/core/skills/react/EXAMPLES.md +0 -79
  177. package/.agents/core/skills/react/SKILL.md +0 -132
  178. package/.agents/core/skills/react/TROUBLESHOOTING.md +0 -19
  179. package/.agents/core/skills/react/VALIDATION.json +0 -12
  180. package/.agents/core/skills/react/react.md +0 -93
  181. package/.agents/core/skills/react/skill.yaml +0 -17
  182. package/.agents/core/skills/react-best-practices/SKILL.md +0 -155
  183. package/.agents/core/skills/react-best-practices/VALIDATION.json +0 -12
  184. package/.agents/core/skills/react-best-practices/skill.yaml +0 -14
  185. package/.agents/core/skills/redesign-audit/SKILL.md +0 -117
  186. package/.agents/core/skills/redesign-audit/VALIDATION.json +0 -12
  187. package/.agents/core/skills/redesign-audit/skill.yaml +0 -12
  188. package/.agents/core/skills/soft-design/SKILL.md +0 -108
  189. package/.agents/core/skills/soft-design/VALIDATION.json +0 -12
  190. package/.agents/core/skills/soft-design/skill.yaml +0 -12
  191. package/.agents/core/skills/state-management/EXAMPLES.md +0 -56
  192. package/.agents/core/skills/state-management/SKILL.md +0 -48
  193. package/.agents/core/skills/state-management/TROUBLESHOOTING.md +0 -18
  194. package/.agents/core/skills/state-management/VALIDATION.json +0 -11
  195. package/.agents/core/skills/state-management/skill.yaml +0 -28
  196. package/.agents/core/skills/subagent-orchestrator/SKILL.md +0 -117
  197. package/.agents/core/skills/subagent-orchestrator/VALIDATION.json +0 -12
  198. package/.agents/core/skills/subagent-orchestrator/skill.yaml +0 -12
  199. package/.agents/core/skills/system-design/EXAMPLES.md +0 -75
  200. package/.agents/core/skills/system-design/SKILL.md +0 -419
  201. package/.agents/core/skills/system-design/TROUBLESHOOTING.md +0 -19
  202. package/.agents/core/skills/system-design/VALIDATION.json +0 -12
  203. package/.agents/core/skills/system-design/skill.yaml +0 -20
  204. package/.agents/core/skills/system-design/system-design.md +0 -112
  205. package/.agents/core/skills/testing/EXAMPLES.md +0 -71
  206. package/.agents/core/skills/testing/SKILL.md +0 -70
  207. package/.agents/core/skills/testing/TROUBLESHOOTING.md +0 -18
  208. package/.agents/core/skills/testing/VALIDATION.json +0 -11
  209. package/.agents/core/skills/testing/skill.yaml +0 -32
  210. package/.agents/core/skills/typescript/EXAMPLES.md +0 -64
  211. package/.agents/core/skills/typescript/SKILL.md +0 -112
  212. package/.agents/core/skills/typescript/TROUBLESHOOTING.md +0 -19
  213. package/.agents/core/skills/typescript/VALIDATION.json +0 -12
  214. package/.agents/core/skills/typescript/skill.yaml +0 -17
  215. package/.agents/core/skills/typescript/typescript.md +0 -71
  216. package/.agents/core/skills/ui-design/EXAMPLES.md +0 -21
  217. package/.agents/core/skills/ui-design/SKILL.md +0 -124
  218. package/.agents/core/skills/ui-design/TROUBLESHOOTING.md +0 -19
  219. package/.agents/core/skills/ui-design/VALIDATION.json +0 -12
  220. package/.agents/core/skills/ui-design/skill.yaml +0 -17
  221. package/.agents/core/skills/ui-design/ui.md +0 -88
  222. package/.agents/core/skills/ui-ux-pro/EXAMPLES.md +0 -62
  223. package/.agents/core/skills/ui-ux-pro/SKILL.md +0 -375
  224. package/.agents/core/skills/ui-ux-pro/TROUBLESHOOTING.md +0 -19
  225. package/.agents/core/skills/ui-ux-pro/VALIDATION.json +0 -12
  226. package/.agents/core/skills/ui-ux-pro/skill.yaml +0 -19
  227. package/.agents/core/skills/ux-design/EXAMPLES.md +0 -36
  228. package/.agents/core/skills/ux-design/SKILL.md +0 -116
  229. package/.agents/core/skills/ux-design/TROUBLESHOOTING.md +0 -19
  230. package/.agents/core/skills/ux-design/VALIDATION.json +0 -12
  231. package/.agents/core/skills/ux-design/skill.yaml +0 -17
  232. package/.agents/core/skills/ux-design/ux.md +0 -80
  233. package/.agents/core/skills/vercel-optimize/SKILL.md +0 -83
  234. package/.agents/core/skills/vercel-optimize/VALIDATION.json +0 -12
  235. package/.agents/core/skills/vercel-optimize/scripts/collect-signals.mjs +0 -131
  236. package/.agents/core/skills/vercel-optimize/scripts/gate-investigations.mjs +0 -142
  237. package/.agents/core/skills/vercel-optimize/scripts/merge-signals.mjs +0 -143
  238. package/.agents/core/skills/vercel-optimize/scripts/scan-codebase.mjs +0 -174
  239. package/.agents/core/skills/vercel-optimize/skill.yaml +0 -18
  240. package/.agents/core/skills/web-accessibility/EXAMPLES.md +0 -39
  241. package/.agents/core/skills/web-accessibility/SKILL.md +0 -170
  242. package/.agents/core/skills/web-accessibility/TROUBLESHOOTING.md +0 -19
  243. package/.agents/core/skills/web-accessibility/VALIDATION.json +0 -12
  244. package/.agents/core/skills/web-accessibility/accessibility.md +0 -63
  245. package/.agents/core/skills/web-accessibility/skill.yaml +0 -17
  246. package/.agents/generated/claude/skills/adapters/SKILL.md +0 -126
  247. package/.agents/generated/claude/skills/architecture-diagrams/SKILL.md +0 -101
  248. package/.agents/generated/claude/skills/brutalist-design/SKILL.md +0 -145
  249. package/.agents/generated/claude/skills/database/SKILL.md +0 -191
  250. package/.agents/generated/claude/skills/ddd/SKILL.md +0 -305
  251. package/.agents/generated/claude/skills/decisions/SKILL.md +0 -134
  252. package/.agents/generated/claude/skills/docker/SKILL.md +0 -135
  253. package/.agents/generated/claude/skills/fastapi/SKILL.md +0 -200
  254. package/.agents/generated/claude/skills/generators/SKILL.md +0 -133
  255. package/.agents/generated/claude/skills/graphify/SKILL.md +0 -198
  256. package/.agents/generated/claude/skills/impeccable-design/SKILL.md +0 -241
  257. package/.agents/generated/claude/skills/interview-me/SKILL.md +0 -90
  258. package/.agents/generated/claude/skills/microservices/SKILL.md +0 -218
  259. package/.agents/generated/claude/skills/minimalist-design/SKILL.md +0 -108
  260. package/.agents/generated/claude/skills/nestjs/SKILL.md +0 -195
  261. package/.agents/generated/claude/skills/nextjs/SKILL.md +0 -219
  262. package/.agents/generated/claude/skills/node/SKILL.md +0 -224
  263. package/.agents/generated/claude/skills/performance/SKILL.md +0 -121
  264. package/.agents/generated/claude/skills/react/SKILL.md +0 -227
  265. package/.agents/generated/claude/skills/react-best-practices/SKILL.md +0 -146
  266. package/.agents/generated/claude/skills/redesign-audit/SKILL.md +0 -112
  267. package/.agents/generated/claude/skills/soft-design/SKILL.md +0 -103
  268. package/.agents/generated/claude/skills/state-management/SKILL.md +0 -120
  269. package/.agents/generated/claude/skills/subagent-orchestrator/SKILL.md +0 -110
  270. package/.agents/generated/claude/skills/system-design/SKILL.md +0 -507
  271. package/.agents/generated/claude/skills/testing/SKILL.md +0 -157
  272. package/.agents/generated/claude/skills/typescript/SKILL.md +0 -192
  273. package/.agents/generated/claude/skills/ui-design/SKILL.md +0 -161
  274. package/.agents/generated/claude/skills/ui-ux-pro/SKILL.md +0 -451
  275. package/.agents/generated/claude/skills/ux-design/SKILL.md +0 -168
  276. package/.agents/generated/claude/skills/vercel-optimize/SKILL.md +0 -76
  277. package/.agents/generated/claude/skills/web-accessibility/SKILL.md +0 -225
  278. package/.agents/generated/gemini/skills/adapters/SKILL.md +0 -135
  279. package/.agents/generated/gemini/skills/architecture-diagrams/SKILL.md +0 -107
  280. package/.agents/generated/gemini/skills/brutalist-design/SKILL.md +0 -151
  281. package/.agents/generated/gemini/skills/database/SKILL.md +0 -200
  282. package/.agents/generated/gemini/skills/ddd/SKILL.md +0 -314
  283. package/.agents/generated/gemini/skills/decisions/SKILL.md +0 -143
  284. package/.agents/generated/gemini/skills/docker/SKILL.md +0 -144
  285. package/.agents/generated/gemini/skills/fastapi/SKILL.md +0 -209
  286. package/.agents/generated/gemini/skills/generators/SKILL.md +0 -142
  287. package/.agents/generated/gemini/skills/graphify/SKILL.md +0 -205
  288. package/.agents/generated/gemini/skills/impeccable-design/SKILL.md +0 -250
  289. package/.agents/generated/gemini/skills/interview-me/SKILL.md +0 -96
  290. package/.agents/generated/gemini/skills/microservices/SKILL.md +0 -227
  291. package/.agents/generated/gemini/skills/minimalist-design/SKILL.md +0 -114
  292. package/.agents/generated/gemini/skills/nestjs/SKILL.md +0 -204
  293. package/.agents/generated/gemini/skills/nextjs/SKILL.md +0 -298
  294. package/.agents/generated/gemini/skills/node/SKILL.md +0 -323
  295. package/.agents/generated/gemini/skills/performance/SKILL.md +0 -185
  296. package/.agents/generated/gemini/skills/react/SKILL.md +0 -332
  297. package/.agents/generated/gemini/skills/react-best-practices/SKILL.md +0 -152
  298. package/.agents/generated/gemini/skills/redesign-audit/SKILL.md +0 -118
  299. package/.agents/generated/gemini/skills/soft-design/SKILL.md +0 -109
  300. package/.agents/generated/gemini/skills/state-management/SKILL.md +0 -129
  301. package/.agents/generated/gemini/skills/subagent-orchestrator/SKILL.md +0 -116
  302. package/.agents/generated/gemini/skills/system-design/SKILL.md +0 -631
  303. package/.agents/generated/gemini/skills/testing/SKILL.md +0 -166
  304. package/.agents/generated/gemini/skills/typescript/SKILL.md +0 -275
  305. package/.agents/generated/gemini/skills/ui-design/SKILL.md +0 -170
  306. package/.agents/generated/gemini/skills/ui-ux-pro/SKILL.md +0 -460
  307. package/.agents/generated/gemini/skills/ux-design/SKILL.md +0 -177
  308. package/.agents/generated/gemini/skills/vercel-optimize/SKILL.md +0 -82
  309. package/.agents/generated/gemini/skills/web-accessibility/SKILL.md +0 -300
@@ -0,0 +1,129 @@
1
+ /**
2
+ * .agents/runtime/idempotency.js
3
+ * ContextOS — Runtime Idempotency Engine
4
+ *
5
+ * Implements Section 17.5 of CONTEXTOS_IMPLEMENTATION_PLAN.md:
6
+ * - Idempotency key registration and deduplication
7
+ * - Safe replay: returns existing job/worktree without spawning duplicate branches
8
+ * - Conflict detection: raises error if payload differs for the same idempotency key
9
+ * - Atomic persistence to disk
10
+ */
11
+
12
+ 'use strict';
13
+
14
+ const fs = require('fs');
15
+ const path = require('path');
16
+ const crypto = require('crypto');
17
+
18
+ function sha256(str) {
19
+ return crypto.createHash('sha256').update(str || '').digest('hex');
20
+ }
21
+
22
+ class IdempotencyRegistry {
23
+ /**
24
+ * @param {Object} options
25
+ * @param {string} options.baseDir - Base directory (.agents/.contextos)
26
+ */
27
+ constructor(options = {}) {
28
+ this.rootDir = path.resolve(options.baseDir || process.cwd());
29
+ this.registryDir = path.join(this.rootDir, '.agents', '.contextos', 'idempotency');
30
+ fs.mkdirSync(this.registryDir, { recursive: true });
31
+ }
32
+
33
+ _getKeyPath(idempotencyKey) {
34
+ const safeKey = crypto.createHash('sha256').update(idempotencyKey).digest('hex');
35
+ return path.join(this.registryDir, `${safeKey}.json`);
36
+ }
37
+
38
+ /**
39
+ * Hashes request payload deterministically.
40
+ */
41
+ hashPayload(payload) {
42
+ if (typeof payload === 'string') return sha256(payload);
43
+ return sha256(JSON.stringify(payload || {}));
44
+ }
45
+
46
+ /**
47
+ * Checks if an idempotency key already exists.
48
+ *
49
+ * @param {string} idempotencyKey
50
+ * @returns {Object|null}
51
+ */
52
+ get(idempotencyKey) {
53
+ if (!idempotencyKey) return null;
54
+ const keyPath = this._getKeyPath(idempotencyKey);
55
+ if (!fs.existsSync(keyPath)) return null;
56
+
57
+ try {
58
+ const content = fs.readFileSync(keyPath, 'utf8');
59
+ return JSON.parse(content);
60
+ } catch {
61
+ return null;
62
+ }
63
+ }
64
+
65
+ /**
66
+ * Executes an operation with idempotency protection.
67
+ * If key was previously executed with identical payload, returns cached result.
68
+ * If payload differs, throws CTX_IDEMPOTENCY_CONFLICT.
69
+ *
70
+ * @param {string} idempotencyKey
71
+ * @param {Object|string} payload
72
+ * @param {Function} factoryFn - Async function returning { jobId, threadId, branchName, worktreePath }
73
+ * @returns {Promise<{ isExisting: boolean, record: Object }>}
74
+ */
75
+ async execute(idempotencyKey, payload, factoryFn) {
76
+ if (!idempotencyKey) {
77
+ // No idempotency key -> execute directly without caching
78
+ const result = await factoryFn();
79
+ return { isExisting: false, record: result };
80
+ }
81
+
82
+ const payloadHash = this.hashPayload(payload);
83
+ const existing = this.get(idempotencyKey);
84
+
85
+ if (existing) {
86
+ if (existing.payloadHash !== payloadHash) {
87
+ const err = new Error(
88
+ `Idempotency conflict for key "${idempotencyKey}": payload does not match previously submitted request.`
89
+ );
90
+ err.code = 'CTX_IDEMPOTENCY_CONFLICT';
91
+ err.idempotencyKey = idempotencyKey;
92
+ err.existingJobId = existing.jobId;
93
+ throw err;
94
+ }
95
+
96
+ // Existing request matched identically -> return existing job without creating duplicate
97
+ return { isExisting: true, record: existing };
98
+ }
99
+
100
+ // Execute operation
101
+ const result = await factoryFn();
102
+
103
+ const record = {
104
+ idempotencyKey,
105
+ payloadHash,
106
+ jobId: result.jobId || result.id || `job-${Date.now()}`,
107
+ threadId: result.threadId || result.id || null,
108
+ branchName: result.branchName || null,
109
+ worktreePath: result.worktreePath || null,
110
+ createdAt: Date.now(),
111
+ metadata: result,
112
+ };
113
+
114
+ const keyPath = this._getKeyPath(idempotencyKey);
115
+ const tempPath = `${keyPath}.${crypto.randomBytes(4).toString('hex')}.tmp`;
116
+ try {
117
+ fs.writeFileSync(tempPath, JSON.stringify(record, null, 2), 'utf8');
118
+ fs.renameSync(tempPath, keyPath);
119
+ } catch {
120
+ try { if (fs.existsSync(tempPath)) fs.unlinkSync(tempPath); } catch {}
121
+ }
122
+
123
+ return { isExisting: false, record };
124
+ }
125
+ }
126
+
127
+ module.exports = {
128
+ IdempotencyRegistry,
129
+ };
@@ -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
+ };