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,288 @@
1
+ /**
2
+ * .agents/runtime/event-store.js
3
+ * ContextOS — Event-Sourced Durable Event Store
4
+ *
5
+ * Implements Section 17.3 of CONTEXTOS_IMPLEMENTATION_PLAN.md:
6
+ * - Monotonically incrementing revision log with SHA-256 event checksums
7
+ * - Periodic snapshots with replay recovery
8
+ * - Torn tail detection and automatic quarantine (zero lost valid snapshots)
9
+ * - Atomic event append with collision prevention
10
+ * - Zero install-time dependencies (pure Node.js crypto & fs)
11
+ */
12
+
13
+ 'use strict';
14
+
15
+ const fs = require('fs');
16
+ const path = require('path');
17
+ const crypto = require('crypto');
18
+
19
+ function sha256(str) {
20
+ return crypto.createHash('sha256').update(str || '').digest('hex');
21
+ }
22
+
23
+ class DurableEventStore {
24
+ /**
25
+ * @param {Object} options
26
+ * @param {string} options.baseDir - Base state directory (.agents/.contextos/state)
27
+ * @param {number} [options.snapshotInterval=50] - Events between snapshots
28
+ */
29
+ constructor(options = {}) {
30
+ if (!options.baseDir) {
31
+ throw new Error('DurableEventStore requires baseDir');
32
+ }
33
+
34
+ this.baseDir = path.resolve(options.baseDir);
35
+ this.eventsDir = path.join(this.baseDir, 'events');
36
+ this.snapshotsDir = path.join(this.baseDir, 'snapshots');
37
+ this.quarantineDir = path.join(this.baseDir, 'quarantine');
38
+ this.snapshotInterval = Math.max(5, options.snapshotInterval || 50);
39
+
40
+ fs.mkdirSync(this.eventsDir, { recursive: true });
41
+ fs.mkdirSync(this.snapshotsDir, { recursive: true });
42
+ fs.mkdirSync(this.quarantineDir, { recursive: true });
43
+ }
44
+
45
+ /**
46
+ * Appends an immutable event to the log.
47
+ *
48
+ * @param {string} streamId - Identifier of the entity/thread
49
+ * @param {Object} eventData - Payload data
50
+ * @param {number} expectedRevision - Optimistic concurrency revision check
51
+ * @returns {Object} Stored event record
52
+ */
53
+ append(streamId, eventData = {}, expectedRevision = null) {
54
+ const streamDir = path.join(this.eventsDir, streamId);
55
+ fs.mkdirSync(streamDir, { recursive: true });
56
+
57
+ // Determine next monotonic revision
58
+ const existingEvents = this._listEventFiles(streamId);
59
+ const lastRevision = existingEvents.length > 0 ? existingEvents[existingEvents.length - 1].revision : 0;
60
+
61
+ if (typeof expectedRevision === 'number' && expectedRevision !== lastRevision) {
62
+ const err = new Error(
63
+ `Optimistic concurrency conflict in stream "${streamId}": expected revision ${expectedRevision}, last revision is ${lastRevision}`
64
+ );
65
+ err.code = 'CTX_EVENT_CONCURRENCY_CONFLICT';
66
+ throw err;
67
+ }
68
+
69
+ const nextRevision = lastRevision + 1;
70
+ const eventId = crypto.randomUUID();
71
+ const timestamp = Date.now();
72
+
73
+ const record = {
74
+ streamId,
75
+ eventId,
76
+ revision: nextRevision,
77
+ timestamp,
78
+ data: eventData,
79
+ };
80
+
81
+ const checksum = sha256(JSON.stringify(record));
82
+ const fullEnvelope = {
83
+ ...record,
84
+ checksum,
85
+ };
86
+
87
+ const revPadded = String(nextRevision).padStart(6, '0');
88
+ const filename = `${revPadded}-${eventId}.json`;
89
+ const targetFile = path.join(streamDir, filename);
90
+ const tempFile = `${targetFile}.${crypto.randomBytes(4).toString('hex')}.tmp`;
91
+
92
+ try {
93
+ fs.writeFileSync(tempFile, JSON.stringify(fullEnvelope, null, 2), 'utf8');
94
+ fs.renameSync(tempFile, targetFile);
95
+ } catch (err) {
96
+ try { if (fs.existsSync(tempFile)) fs.unlinkSync(tempFile); } catch {}
97
+ throw err;
98
+ }
99
+
100
+ return Object.freeze(fullEnvelope);
101
+ }
102
+
103
+ /**
104
+ * Creates a snapshot of the reduced state up to a given revision.
105
+ *
106
+ * @param {string} streamId
107
+ * @param {Object} state
108
+ * @param {number} revision
109
+ * @returns {Object} Snapshot record
110
+ */
111
+ createSnapshot(streamId, state, revision) {
112
+ const streamSnapDir = path.join(this.snapshotsDir, streamId);
113
+ fs.mkdirSync(streamSnapDir, { recursive: true });
114
+
115
+ const revPadded = String(revision).padStart(6, '0');
116
+ const filename = `snapshot-${revPadded}.json`;
117
+ const targetFile = path.join(streamSnapDir, filename);
118
+ const tempFile = `${targetFile}.${crypto.randomBytes(4).toString('hex')}.tmp`;
119
+
120
+ const snapshot = {
121
+ streamId,
122
+ revision,
123
+ timestamp: Date.now(),
124
+ state,
125
+ checksum: sha256(JSON.stringify(state)),
126
+ };
127
+
128
+ try {
129
+ fs.writeFileSync(tempFile, JSON.stringify(snapshot, null, 2), 'utf8');
130
+ fs.renameSync(tempFile, targetFile);
131
+ } catch (err) {
132
+ try { if (fs.existsSync(tempFile)) fs.unlinkSync(tempFile); } catch {}
133
+ throw err;
134
+ }
135
+
136
+ return Object.freeze(snapshot);
137
+ }
138
+
139
+ /**
140
+ * Reads latest valid snapshot for stream if available.
141
+ *
142
+ * @param {string} streamId
143
+ * @returns {Object|null}
144
+ */
145
+ getLatestSnapshot(streamId) {
146
+ const streamSnapDir = path.join(this.snapshotsDir, streamId);
147
+ if (!fs.existsSync(streamSnapDir)) return null;
148
+
149
+ const files = fs.readdirSync(streamSnapDir)
150
+ .filter(f => f.startsWith('snapshot-') && f.endsWith('.json'))
151
+ .sort();
152
+
153
+ if (files.length === 0) return null;
154
+
155
+ const latestFile = files[files.length - 1];
156
+ try {
157
+ const content = fs.readFileSync(path.join(streamSnapDir, latestFile), 'utf8');
158
+ const snap = JSON.parse(content);
159
+ // Check integrity
160
+ if (snap.checksum && snap.checksum !== sha256(JSON.stringify(snap.state))) {
161
+ // Corrupted snapshot -> quarantine
162
+ this._quarantineFile(path.join(streamSnapDir, latestFile), 'corrupted_snapshot_checksum');
163
+ return null;
164
+ }
165
+ return snap;
166
+ } catch {
167
+ this._quarantineFile(path.join(streamSnapDir, latestFile), 'unparseable_snapshot');
168
+ return null;
169
+ }
170
+ }
171
+
172
+ /**
173
+ * Reads all valid events in stream.
174
+ *
175
+ * @param {string} streamId
176
+ * @returns {Array<Object>}
177
+ */
178
+ readStream(streamId) {
179
+ const eventFiles = this._listEventFiles(streamId);
180
+ const events = [];
181
+ for (const item of eventFiles) {
182
+ const evt = this._readAndVerifyEvent(item.filePath);
183
+ if (evt) {
184
+ events.push(evt);
185
+ }
186
+ }
187
+ return events;
188
+ }
189
+
190
+ /**
191
+ * Replays events from snapshot or beginning to restore current state.
192
+ * Tolerates and quarantines torn tail events from unexpected crashes.
193
+ *
194
+ * @param {string} streamId
195
+ * @param {Function} reducer - (state, event) => newState
196
+ * @param {Object} [initialState={}]
197
+ * @returns {{ state: Object, revision: number, replayedEvents: number, quarantinedCount: number }}
198
+ */
199
+ replay(streamId, reducer, initialState = {}) {
200
+ if (typeof reducer !== 'function') {
201
+ throw new Error('replay requires a reducer function');
202
+ }
203
+
204
+ const snapshot = this.getLatestSnapshot(streamId);
205
+ let state = snapshot ? JSON.parse(JSON.stringify(snapshot.state)) : JSON.parse(JSON.stringify(initialState));
206
+ let lastRevision = snapshot ? snapshot.revision : 0;
207
+
208
+ const eventFiles = this._listEventFiles(streamId);
209
+ let replayedEvents = 0;
210
+ let quarantinedCount = 0;
211
+
212
+ for (const item of eventFiles) {
213
+ if (item.revision <= lastRevision) continue;
214
+
215
+ const eventData = this._readAndVerifyEvent(item.filePath);
216
+ if (!eventData) {
217
+ // Torn tail detected at end of stream! Quarantine it to recover.
218
+ this._quarantineFile(item.filePath, 'torn_tail_corrupted_event');
219
+ quarantinedCount++;
220
+ break; // Stop replaying past torn tail
221
+ }
222
+
223
+ state = reducer(state, eventData);
224
+ lastRevision = eventData.revision;
225
+ replayedEvents++;
226
+ }
227
+
228
+ return {
229
+ state: Object.freeze(state),
230
+ revision: lastRevision,
231
+ replayedEvents,
232
+ quarantinedCount,
233
+ };
234
+ }
235
+
236
+ _listEventFiles(streamId) {
237
+ const streamDir = path.join(this.eventsDir, streamId);
238
+ if (!fs.existsSync(streamDir)) return [];
239
+
240
+ const files = fs.readdirSync(streamDir)
241
+ .filter(f => /^\d{6}-[a-zA-Z0-9-]+\.json$/.test(f))
242
+ .sort();
243
+
244
+ return files.map(f => {
245
+ const rev = parseInt(f.slice(0, 6), 10);
246
+ return {
247
+ filename: f,
248
+ revision: rev,
249
+ filePath: path.join(streamDir, f),
250
+ };
251
+ });
252
+ }
253
+
254
+ _readAndVerifyEvent(filePath) {
255
+ try {
256
+ const content = fs.readFileSync(filePath, 'utf8');
257
+ const envelope = JSON.parse(content);
258
+
259
+ if (!envelope.revision || !envelope.checksum) {
260
+ return null;
261
+ }
262
+
263
+ const { checksum, ...rest } = envelope;
264
+ const expectedChecksum = sha256(JSON.stringify(rest));
265
+ if (checksum !== expectedChecksum) {
266
+ return null; // Checksum mismatch
267
+ }
268
+
269
+ return envelope;
270
+ } catch {
271
+ return null;
272
+ }
273
+ }
274
+
275
+ _quarantineFile(filePath, reason) {
276
+ try {
277
+ const filename = path.basename(filePath);
278
+ const target = path.join(this.quarantineDir, `${Date.now()}-${reason}-${filename}`);
279
+ fs.renameSync(filePath, target);
280
+ } catch {
281
+ // ignore
282
+ }
283
+ }
284
+ }
285
+
286
+ module.exports = {
287
+ DurableEventStore,
288
+ };
@@ -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
+ };