contextos-agents 1.6.1 → 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 (323) hide show
  1. package/.agents/AGENTS.md +6 -1
  2. package/.agents/adapters/aider/export.js +117 -97
  3. package/.agents/adapters/claude/export.js +68 -26
  4. package/.agents/adapters/copilot/export.js +90 -51
  5. package/.agents/adapters/cursor/export.js +83 -68
  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 +109 -62
  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/references/context-rules.md +59 -0
  18. package/.agents/core/skills/context-manager/skill.yaml +10 -5
  19. package/.agents/core/skills/context-os/SKILL.md +3 -6
  20. package/.agents/core/skills/context-os/skill.yaml +14 -8
  21. package/.agents/core/skills/engineering-workflow/SKILL.md +1 -1
  22. package/.agents/core/skills/engineering-workflow/skill.yaml +7 -7
  23. package/.agents/core/skills/gemini-precision/SKILL.md +4 -0
  24. package/.agents/core/skills/gemini-precision/skill.yaml +5 -6
  25. package/.agents/core/skills/gstack-roles/SKILL.md +3 -1
  26. package/.agents/core/skills/gstack-roles/skill.yaml +9 -6
  27. package/.agents/core/skills/ponytail-mindset/skill.yaml +7 -7
  28. package/.agents/core/skills/security/skill.yaml +21 -2
  29. package/.agents/ctx.js +587 -111
  30. package/.agents/customization-dx.js +282 -0
  31. package/.agents/doctor.js +877 -33
  32. package/.agents/filesystem/index.js +71 -0
  33. package/.agents/filesystem/journaled-transaction.js +451 -0
  34. package/.agents/filesystem/lockfile-v2.js +275 -0
  35. package/.agents/filesystem/platform-hardening.js +222 -0
  36. package/.agents/filesystem/project-lock.js +218 -0
  37. package/.agents/filesystem/safe-path.js +256 -0
  38. package/.agents/generated/claude/skills/context-os/SKILL.md +1 -1
  39. package/.agents/generated/claude/skills/engineering-workflow/SKILL.md +1 -1
  40. package/.agents/generated/claude/skills/gemini-precision/SKILL.md +4 -0
  41. package/.agents/generated/claude/skills/gstack-roles/SKILL.md +3 -1
  42. package/.agents/generated/gemini/skills/context-os/SKILL.md +2 -2
  43. package/.agents/generated/gemini/skills/engineering-workflow/SKILL.md +1 -1
  44. package/.agents/generated/gemini/skills/gemini-precision/SKILL.md +4 -0
  45. package/.agents/generated/gemini/skills/gstack-roles/SKILL.md +3 -1
  46. package/.agents/plugins/contextos/hooks.json +25 -0
  47. package/.agents/plugins/contextos/plugin.json +19 -0
  48. package/.agents/plugins.js +432 -73
  49. package/.agents/profiles.js +507 -46
  50. package/.agents/resolver.js +50 -414
  51. package/.agents/schemas/attestation.review.v1.json +111 -0
  52. package/.agents/schemas/attestation.verification.v1.json +85 -0
  53. package/.agents/schemas/lockfile.v2.schema.json +134 -0
  54. package/.agents/schemas/profile.v2.schema.json +114 -0
  55. package/.agents/schemas/runtime.thread.v1.json +192 -0
  56. package/.agents/schemas/skill.manifest.v2.json +177 -0
  57. package/.agents/schemas/verification.spec.v1.json +39 -0
  58. package/.agents/schemas/workspace.graph.schema.json +106 -0
  59. package/.agents/stats.js +22 -9
  60. package/.agents/transaction-core/event-store.js +288 -0
  61. package/.agents/transaction-core/idempotency.js +129 -0
  62. package/.agents/transaction-core/ipc-lock.js +311 -0
  63. package/.agents/transaction-core/plugin-supply-chain-bundle.js +436 -0
  64. package/.agents/validate.js +143 -14
  65. package/.agents/watch.js +354 -102
  66. package/.agents/workspace/workspace-graph.js +778 -0
  67. package/README.md +59 -387
  68. package/benchmarks/v2/analysis/statistics.js +140 -0
  69. package/benchmarks/v2/analysis/stats.js +69 -0
  70. package/benchmarks/v2/arms/arm-definitions.js +79 -0
  71. package/benchmarks/v2/dataset.schema.json +34 -0
  72. package/benchmarks/v2/evaluators/index.js +25 -0
  73. package/benchmarks/v2/evaluators/verified-success.js +116 -0
  74. package/benchmarks/v2/harness/runner.js +88 -0
  75. package/benchmarks/v2/pilot-tasks.json +392 -0
  76. package/bin/commands/recover.js +88 -0
  77. package/bin/commands/uninstall.js +207 -0
  78. package/bin/commands/update.js +325 -0
  79. package/bin/commands.js +342 -0
  80. package/bin/index.js +326 -149
  81. package/bin/lib/detector.js +106 -0
  82. package/bin/lib/lockfile.js +253 -0
  83. package/bin/lib/safe-writer.js +290 -0
  84. package/package.json +85 -73
  85. package/registry.json +15 -7
  86. package/registry.schema.json +3 -1
  87. package/registry.v2.schema.json +86 -0
  88. package/.agents/core/profiles/backend.yaml +0 -47
  89. package/.agents/core/profiles/enterprise.yaml +0 -46
  90. package/.agents/core/profiles/frontend.yaml +0 -46
  91. package/.agents/core/profiles/hackathon.yaml +0 -45
  92. package/.agents/core/profiles/mvp.yaml +0 -44
  93. package/.agents/core/profiles/startup.yaml +0 -48
  94. package/.agents/core/skills/adapters/EXAMPLES.md +0 -19
  95. package/.agents/core/skills/adapters/SKILL.md +0 -105
  96. package/.agents/core/skills/adapters/TROUBLESHOOTING.md +0 -7
  97. package/.agents/core/skills/adapters/VALIDATION.json +0 -12
  98. package/.agents/core/skills/adapters/skill.yaml +0 -10
  99. package/.agents/core/skills/architecture-diagrams/SKILL.md +0 -108
  100. package/.agents/core/skills/architecture-diagrams/VALIDATION.json +0 -12
  101. package/.agents/core/skills/architecture-diagrams/skill.yaml +0 -8
  102. package/.agents/core/skills/brutalist-design/SKILL.md +0 -150
  103. package/.agents/core/skills/brutalist-design/VALIDATION.json +0 -12
  104. package/.agents/core/skills/brutalist-design/skill.yaml +0 -8
  105. package/.agents/core/skills/database/EXAMPLES.md +0 -74
  106. package/.agents/core/skills/database/SKILL.md +0 -101
  107. package/.agents/core/skills/database/TROUBLESHOOTING.md +0 -18
  108. package/.agents/core/skills/database/VALIDATION.json +0 -11
  109. package/.agents/core/skills/database/skill.yaml +0 -25
  110. package/.agents/core/skills/ddd/EXAMPLES.md +0 -42
  111. package/.agents/core/skills/ddd/SKILL.md +0 -247
  112. package/.agents/core/skills/ddd/TROUBLESHOOTING.md +0 -19
  113. package/.agents/core/skills/ddd/VALIDATION.json +0 -12
  114. package/.agents/core/skills/ddd/ddd.md +0 -178
  115. package/.agents/core/skills/ddd/skill.yaml +0 -10
  116. package/.agents/core/skills/decisions/EXAMPLES.md +0 -35
  117. package/.agents/core/skills/decisions/SKILL.md +0 -90
  118. package/.agents/core/skills/decisions/TROUBLESHOOTING.md +0 -13
  119. package/.agents/core/skills/decisions/VALIDATION.json +0 -12
  120. package/.agents/core/skills/decisions/skill.yaml +0 -10
  121. package/.agents/core/skills/docker/EXAMPLES.md +0 -56
  122. package/.agents/core/skills/docker/SKILL.md +0 -63
  123. package/.agents/core/skills/docker/TROUBLESHOOTING.md +0 -18
  124. package/.agents/core/skills/docker/VALIDATION.json +0 -11
  125. package/.agents/core/skills/docker/skill.yaml +0 -23
  126. package/.agents/core/skills/fastapi/EXAMPLES.md +0 -36
  127. package/.agents/core/skills/fastapi/SKILL.md +0 -148
  128. package/.agents/core/skills/fastapi/TROUBLESHOOTING.md +0 -19
  129. package/.agents/core/skills/fastapi/VALIDATION.json +0 -12
  130. package/.agents/core/skills/fastapi/fastapi.md +0 -112
  131. package/.agents/core/skills/fastapi/skill.yaml +0 -10
  132. package/.agents/core/skills/generators/EXAMPLES.md +0 -19
  133. package/.agents/core/skills/generators/SKILL.md +0 -112
  134. package/.agents/core/skills/generators/TROUBLESHOOTING.md +0 -7
  135. package/.agents/core/skills/generators/VALIDATION.json +0 -12
  136. package/.agents/core/skills/generators/skill.yaml +0 -10
  137. package/.agents/core/skills/generators/templates/API.md +0 -77
  138. package/.agents/core/skills/generators/templates/ARCHITECTURE.md +0 -70
  139. package/.agents/core/skills/generators/templates/DATABASE.md +0 -42
  140. package/.agents/core/skills/generators/templates/DECISION.md +0 -46
  141. package/.agents/core/skills/generators/templates/PRD.md +0 -67
  142. package/.agents/core/skills/generators/templates/PROJECT_GRAPH.md +0 -56
  143. package/.agents/core/skills/generators/templates/ROADMAP.md +0 -51
  144. package/.agents/core/skills/generators/templates/TASKS.md +0 -43
  145. package/.agents/core/skills/generators/templates/UI.md +0 -73
  146. package/.agents/core/skills/graphify/EXAMPLES.md +0 -73
  147. package/.agents/core/skills/graphify/SKILL.md +0 -130
  148. package/.agents/core/skills/graphify/VALIDATION.json +0 -12
  149. package/.agents/core/skills/graphify/skill.yaml +0 -13
  150. package/.agents/core/skills/impeccable-design/EXAMPLES.md +0 -26
  151. package/.agents/core/skills/impeccable-design/SKILL.md +0 -201
  152. package/.agents/core/skills/impeccable-design/TROUBLESHOOTING.md +0 -19
  153. package/.agents/core/skills/impeccable-design/VALIDATION.json +0 -12
  154. package/.agents/core/skills/impeccable-design/skill.yaml +0 -14
  155. package/.agents/core/skills/interview-me/SKILL.md +0 -97
  156. package/.agents/core/skills/interview-me/VALIDATION.json +0 -12
  157. package/.agents/core/skills/interview-me/skill.yaml +0 -8
  158. package/.agents/core/skills/microservices/EXAMPLES.md +0 -38
  159. package/.agents/core/skills/microservices/SKILL.md +0 -164
  160. package/.agents/core/skills/microservices/TROUBLESHOOTING.md +0 -19
  161. package/.agents/core/skills/microservices/VALIDATION.json +0 -12
  162. package/.agents/core/skills/microservices/microservices.md +0 -119
  163. package/.agents/core/skills/microservices/skill.yaml +0 -10
  164. package/.agents/core/skills/minimalist-design/SKILL.md +0 -113
  165. package/.agents/core/skills/minimalist-design/VALIDATION.json +0 -12
  166. package/.agents/core/skills/minimalist-design/skill.yaml +0 -8
  167. package/.agents/core/skills/nestjs/EXAMPLES.md +0 -40
  168. package/.agents/core/skills/nestjs/SKILL.md +0 -139
  169. package/.agents/core/skills/nestjs/TROUBLESHOOTING.md +0 -19
  170. package/.agents/core/skills/nestjs/VALIDATION.json +0 -12
  171. package/.agents/core/skills/nestjs/nestjs.md +0 -103
  172. package/.agents/core/skills/nestjs/skill.yaml +0 -10
  173. package/.agents/core/skills/nextjs/EXAMPLES.md +0 -40
  174. package/.agents/core/skills/nextjs/SKILL.md +0 -163
  175. package/.agents/core/skills/nextjs/TROUBLESHOOTING.md +0 -19
  176. package/.agents/core/skills/nextjs/VALIDATION.json +0 -12
  177. package/.agents/core/skills/nextjs/nextjs.md +0 -67
  178. package/.agents/core/skills/nextjs/skill.yaml +0 -10
  179. package/.agents/core/skills/node/EXAMPLES.md +0 -80
  180. package/.agents/core/skills/node/SKILL.md +0 -128
  181. package/.agents/core/skills/node/TROUBLESHOOTING.md +0 -19
  182. package/.agents/core/skills/node/VALIDATION.json +0 -12
  183. package/.agents/core/skills/node/node.md +0 -87
  184. package/.agents/core/skills/node/skill.yaml +0 -10
  185. package/.agents/core/skills/performance/EXAMPLES.md +0 -30
  186. package/.agents/core/skills/performance/SKILL.md +0 -75
  187. package/.agents/core/skills/performance/TROUBLESHOOTING.md +0 -19
  188. package/.agents/core/skills/performance/VALIDATION.json +0 -12
  189. package/.agents/core/skills/performance/performance.md +0 -52
  190. package/.agents/core/skills/performance/skill.yaml +0 -10
  191. package/.agents/core/skills/react/EXAMPLES.md +0 -79
  192. package/.agents/core/skills/react/SKILL.md +0 -132
  193. package/.agents/core/skills/react/TROUBLESHOOTING.md +0 -19
  194. package/.agents/core/skills/react/VALIDATION.json +0 -12
  195. package/.agents/core/skills/react/react.md +0 -93
  196. package/.agents/core/skills/react/skill.yaml +0 -10
  197. package/.agents/core/skills/react-best-practices/SKILL.md +0 -155
  198. package/.agents/core/skills/react-best-practices/VALIDATION.json +0 -12
  199. package/.agents/core/skills/react-best-practices/skill.yaml +0 -10
  200. package/.agents/core/skills/redesign-audit/SKILL.md +0 -117
  201. package/.agents/core/skills/redesign-audit/VALIDATION.json +0 -12
  202. package/.agents/core/skills/redesign-audit/skill.yaml +0 -8
  203. package/.agents/core/skills/soft-design/SKILL.md +0 -108
  204. package/.agents/core/skills/soft-design/VALIDATION.json +0 -12
  205. package/.agents/core/skills/soft-design/skill.yaml +0 -8
  206. package/.agents/core/skills/state-management/EXAMPLES.md +0 -56
  207. package/.agents/core/skills/state-management/SKILL.md +0 -48
  208. package/.agents/core/skills/state-management/TROUBLESHOOTING.md +0 -18
  209. package/.agents/core/skills/state-management/VALIDATION.json +0 -11
  210. package/.agents/core/skills/state-management/skill.yaml +0 -22
  211. package/.agents/core/skills/subagent-orchestrator/SKILL.md +0 -100
  212. package/.agents/core/skills/subagent-orchestrator/VALIDATION.json +0 -12
  213. package/.agents/core/skills/subagent-orchestrator/skill.yaml +0 -8
  214. package/.agents/core/skills/system-design/EXAMPLES.md +0 -75
  215. package/.agents/core/skills/system-design/SKILL.md +0 -419
  216. package/.agents/core/skills/system-design/TROUBLESHOOTING.md +0 -19
  217. package/.agents/core/skills/system-design/VALIDATION.json +0 -12
  218. package/.agents/core/skills/system-design/skill.yaml +0 -13
  219. package/.agents/core/skills/system-design/system-design.md +0 -112
  220. package/.agents/core/skills/testing/EXAMPLES.md +0 -71
  221. package/.agents/core/skills/testing/SKILL.md +0 -70
  222. package/.agents/core/skills/testing/TROUBLESHOOTING.md +0 -18
  223. package/.agents/core/skills/testing/VALIDATION.json +0 -11
  224. package/.agents/core/skills/testing/skill.yaml +0 -26
  225. package/.agents/core/skills/typescript/EXAMPLES.md +0 -64
  226. package/.agents/core/skills/typescript/SKILL.md +0 -112
  227. package/.agents/core/skills/typescript/TROUBLESHOOTING.md +0 -19
  228. package/.agents/core/skills/typescript/VALIDATION.json +0 -12
  229. package/.agents/core/skills/typescript/skill.yaml +0 -10
  230. package/.agents/core/skills/typescript/typescript.md +0 -71
  231. package/.agents/core/skills/ui-design/EXAMPLES.md +0 -21
  232. package/.agents/core/skills/ui-design/SKILL.md +0 -124
  233. package/.agents/core/skills/ui-design/TROUBLESHOOTING.md +0 -19
  234. package/.agents/core/skills/ui-design/VALIDATION.json +0 -12
  235. package/.agents/core/skills/ui-design/skill.yaml +0 -10
  236. package/.agents/core/skills/ui-design/ui.md +0 -88
  237. package/.agents/core/skills/ui-ux-pro/EXAMPLES.md +0 -62
  238. package/.agents/core/skills/ui-ux-pro/SKILL.md +0 -375
  239. package/.agents/core/skills/ui-ux-pro/TROUBLESHOOTING.md +0 -19
  240. package/.agents/core/skills/ui-ux-pro/VALIDATION.json +0 -12
  241. package/.agents/core/skills/ui-ux-pro/skill.yaml +0 -13
  242. package/.agents/core/skills/ux-design/EXAMPLES.md +0 -36
  243. package/.agents/core/skills/ux-design/SKILL.md +0 -116
  244. package/.agents/core/skills/ux-design/TROUBLESHOOTING.md +0 -19
  245. package/.agents/core/skills/ux-design/VALIDATION.json +0 -12
  246. package/.agents/core/skills/ux-design/skill.yaml +0 -10
  247. package/.agents/core/skills/ux-design/ux.md +0 -80
  248. package/.agents/core/skills/vercel-optimize/SKILL.md +0 -83
  249. package/.agents/core/skills/vercel-optimize/VALIDATION.json +0 -12
  250. package/.agents/core/skills/vercel-optimize/skill.yaml +0 -10
  251. package/.agents/core/skills/web-accessibility/EXAMPLES.md +0 -39
  252. package/.agents/core/skills/web-accessibility/SKILL.md +0 -170
  253. package/.agents/core/skills/web-accessibility/TROUBLESHOOTING.md +0 -19
  254. package/.agents/core/skills/web-accessibility/VALIDATION.json +0 -12
  255. package/.agents/core/skills/web-accessibility/accessibility.md +0 -63
  256. package/.agents/core/skills/web-accessibility/skill.yaml +0 -10
  257. package/.agents/generated/claude/skills/adapters/SKILL.md +0 -126
  258. package/.agents/generated/claude/skills/architecture-diagrams/SKILL.md +0 -101
  259. package/.agents/generated/claude/skills/brutalist-design/SKILL.md +0 -145
  260. package/.agents/generated/claude/skills/database/SKILL.md +0 -191
  261. package/.agents/generated/claude/skills/ddd/SKILL.md +0 -305
  262. package/.agents/generated/claude/skills/decisions/SKILL.md +0 -134
  263. package/.agents/generated/claude/skills/docker/SKILL.md +0 -135
  264. package/.agents/generated/claude/skills/fastapi/SKILL.md +0 -200
  265. package/.agents/generated/claude/skills/generators/SKILL.md +0 -133
  266. package/.agents/generated/claude/skills/graphify/SKILL.md +0 -198
  267. package/.agents/generated/claude/skills/impeccable-design/SKILL.md +0 -241
  268. package/.agents/generated/claude/skills/interview-me/SKILL.md +0 -90
  269. package/.agents/generated/claude/skills/microservices/SKILL.md +0 -218
  270. package/.agents/generated/claude/skills/minimalist-design/SKILL.md +0 -108
  271. package/.agents/generated/claude/skills/nestjs/SKILL.md +0 -195
  272. package/.agents/generated/claude/skills/nextjs/SKILL.md +0 -219
  273. package/.agents/generated/claude/skills/node/SKILL.md +0 -224
  274. package/.agents/generated/claude/skills/performance/SKILL.md +0 -121
  275. package/.agents/generated/claude/skills/react/SKILL.md +0 -227
  276. package/.agents/generated/claude/skills/react-best-practices/SKILL.md +0 -146
  277. package/.agents/generated/claude/skills/redesign-audit/SKILL.md +0 -112
  278. package/.agents/generated/claude/skills/soft-design/SKILL.md +0 -103
  279. package/.agents/generated/claude/skills/state-management/SKILL.md +0 -120
  280. package/.agents/generated/claude/skills/subagent-orchestrator/SKILL.md +0 -93
  281. package/.agents/generated/claude/skills/system-design/SKILL.md +0 -507
  282. package/.agents/generated/claude/skills/testing/SKILL.md +0 -157
  283. package/.agents/generated/claude/skills/typescript/SKILL.md +0 -192
  284. package/.agents/generated/claude/skills/ui-design/SKILL.md +0 -161
  285. package/.agents/generated/claude/skills/ui-ux-pro/SKILL.md +0 -451
  286. package/.agents/generated/claude/skills/ux-design/SKILL.md +0 -168
  287. package/.agents/generated/claude/skills/vercel-optimize/SKILL.md +0 -76
  288. package/.agents/generated/claude/skills/web-accessibility/SKILL.md +0 -225
  289. package/.agents/generated/gemini/skills/adapters/SKILL.md +0 -135
  290. package/.agents/generated/gemini/skills/architecture-diagrams/SKILL.md +0 -107
  291. package/.agents/generated/gemini/skills/brutalist-design/SKILL.md +0 -151
  292. package/.agents/generated/gemini/skills/database/SKILL.md +0 -200
  293. package/.agents/generated/gemini/skills/ddd/SKILL.md +0 -314
  294. package/.agents/generated/gemini/skills/decisions/SKILL.md +0 -143
  295. package/.agents/generated/gemini/skills/docker/SKILL.md +0 -144
  296. package/.agents/generated/gemini/skills/fastapi/SKILL.md +0 -209
  297. package/.agents/generated/gemini/skills/generators/SKILL.md +0 -142
  298. package/.agents/generated/gemini/skills/graphify/SKILL.md +0 -205
  299. package/.agents/generated/gemini/skills/impeccable-design/SKILL.md +0 -250
  300. package/.agents/generated/gemini/skills/interview-me/SKILL.md +0 -96
  301. package/.agents/generated/gemini/skills/microservices/SKILL.md +0 -227
  302. package/.agents/generated/gemini/skills/minimalist-design/SKILL.md +0 -114
  303. package/.agents/generated/gemini/skills/nestjs/SKILL.md +0 -204
  304. package/.agents/generated/gemini/skills/nextjs/SKILL.md +0 -298
  305. package/.agents/generated/gemini/skills/node/SKILL.md +0 -323
  306. package/.agents/generated/gemini/skills/performance/SKILL.md +0 -185
  307. package/.agents/generated/gemini/skills/react/SKILL.md +0 -332
  308. package/.agents/generated/gemini/skills/react-best-practices/SKILL.md +0 -152
  309. package/.agents/generated/gemini/skills/redesign-audit/SKILL.md +0 -118
  310. package/.agents/generated/gemini/skills/soft-design/SKILL.md +0 -109
  311. package/.agents/generated/gemini/skills/state-management/SKILL.md +0 -129
  312. package/.agents/generated/gemini/skills/subagent-orchestrator/SKILL.md +0 -99
  313. package/.agents/generated/gemini/skills/system-design/SKILL.md +0 -631
  314. package/.agents/generated/gemini/skills/testing/SKILL.md +0 -166
  315. package/.agents/generated/gemini/skills/typescript/SKILL.md +0 -275
  316. package/.agents/generated/gemini/skills/ui-design/SKILL.md +0 -170
  317. package/.agents/generated/gemini/skills/ui-ux-pro/SKILL.md +0 -460
  318. package/.agents/generated/gemini/skills/ux-design/SKILL.md +0 -177
  319. package/.agents/generated/gemini/skills/vercel-optimize/SKILL.md +0 -82
  320. package/.agents/generated/gemini/skills/web-accessibility/SKILL.md +0 -300
  321. package/.agents/mcp/runtime.py +0 -470
  322. package/.agents/mcp/server.mjs +0 -189271
  323. package/benchmarks/gemini-issues.js +0 -533
@@ -0,0 +1,275 @@
1
+ /**
2
+ * .agents/filesystem/lockfile-v2.js
3
+ * ContextOS Lockfile v2 Schema Manager & CAS Revision Tracker
4
+ *
5
+ * Implements:
6
+ * - Dual-hashing: Exact byte SHA-256 + CRLF-normalized semantic text SHA-256
7
+ * - Monotonic revision tracking with Compare-And-Swap (CAS) concurrency guard
8
+ * - Schema v2 validation adhering to .agents/schemas/lockfile.v2.schema.json
9
+ * - Generator provenance and artifact metadata management
10
+ */
11
+
12
+ const fs = require('fs');
13
+ const path = require('path');
14
+ const crypto = require('crypto');
15
+ const { safeRenameSync } = require('./safe-path.js');
16
+
17
+ const DEFAULT_LOCKFILE_SUBPATH = path.join('.agents', 'lockfile.v2.json');
18
+ const SCHEMA_PATH = path.join(__dirname, '..', 'schemas', 'lockfile.v2.schema.json');
19
+
20
+ const ERROR_CODES = {
21
+ INVALID_SCHEMA: 'CTX_LOCKFILE_INVALID_SCHEMA',
22
+ REVISION_CONFLICT: 'CTX_LOCKFILE_REVISION_CONFLICT',
23
+ CORRUPT: 'CTX_LOCKFILE_CORRUPT',
24
+ };
25
+
26
+ class LockfileV2Error extends Error {
27
+ constructor(code, message, details = {}) {
28
+ super(message);
29
+ this.name = 'LockfileV2Error';
30
+ this.code = code;
31
+ this.details = details;
32
+ }
33
+ }
34
+
35
+ /**
36
+ * Computes exact SHA-256 hash of raw byte buffer or string.
37
+ * Format: sha256:<hex>
38
+ */
39
+ function computeExactHash(content) {
40
+ const buf = Buffer.isBuffer(content) ? content : Buffer.from(String(content), 'utf8');
41
+ const hash = crypto.createHash('sha256').update(buf).digest('hex');
42
+ return `sha256:${hash}`;
43
+ }
44
+
45
+ /**
46
+ * Computes semantic CRLF-normalized text SHA-256 hash to prevent false dirty states across Git checkouts.
47
+ * Format: sha256:<hex>
48
+ */
49
+ function computeSemanticHash(content) {
50
+ const str = Buffer.isBuffer(content) ? content.toString('utf8') : String(content);
51
+ // Normalize Windows CRLF to LF and trim trailing carriage returns
52
+ const normalized = str.replace(/\r\n/g, '\n').replace(/\r/g, '\n');
53
+ const hash = crypto.createHash('sha256').update(normalized, 'utf8').digest('hex');
54
+ return `sha256:${hash}`;
55
+ }
56
+
57
+ class LockfileV2Manager {
58
+ constructor(projectRoot, options = {}) {
59
+ this.projectRoot = path.resolve(projectRoot);
60
+ this.lockfilePath = options.lockfilePath
61
+ ? path.resolve(this.projectRoot, options.lockfilePath)
62
+ : path.resolve(this.projectRoot, DEFAULT_LOCKFILE_SUBPATH);
63
+ this.schema = this.loadSchema();
64
+ }
65
+
66
+ loadSchema() {
67
+ try {
68
+ if (fs.existsSync(SCHEMA_PATH)) {
69
+ return JSON.parse(fs.readFileSync(SCHEMA_PATH, 'utf8'));
70
+ }
71
+ } catch {
72
+ // Best-effort schema load
73
+ }
74
+ return null;
75
+ }
76
+
77
+ /**
78
+ * Initializes a fresh Lockfile v2 data structure.
79
+ */
80
+ createEmpty(meta = {}) {
81
+ return {
82
+ schemaVersion: 2,
83
+ revision: 1,
84
+ package: {
85
+ name: meta.packageName || 'contextos-project',
86
+ version: meta.packageVersion || '1.0.0',
87
+ compilerVersion: meta.compilerVersion || '2.0.0',
88
+ },
89
+ profile: {
90
+ id: meta.profileId || 'default',
91
+ hash: meta.profileHash || 'sha256:0000000000000000000000000000000000000000000000000000000000000000',
92
+ },
93
+ sourceGraphHash: meta.sourceGraphHash || 'sha256:0000000000000000000000000000000000000000000000000000000000000000',
94
+ enabledAdapters: meta.enabledAdapters || [],
95
+ managedFiles: {},
96
+ };
97
+ }
98
+
99
+ /**
100
+ * Reads and parses Lockfile v2 from disk. Returns null if file does not exist.
101
+ */
102
+ read() {
103
+ if (!fs.existsSync(this.lockfilePath)) {
104
+ return null;
105
+ }
106
+
107
+ try {
108
+ const raw = fs.readFileSync(this.lockfilePath, 'utf8');
109
+ const parsed = JSON.parse(raw);
110
+ this.validate(parsed);
111
+ return parsed;
112
+ } catch (err) {
113
+ if (err instanceof LockfileV2Error) throw err;
114
+ throw new LockfileV2Error(
115
+ ERROR_CODES.CORRUPT,
116
+ `Lockfile at '${this.lockfilePath}' is corrupt or unparseable: ${err.message}`,
117
+ { error: err.message, lockfilePath: this.lockfilePath }
118
+ );
119
+ }
120
+ }
121
+
122
+ /**
123
+ * Validates lockfile structure against LockfileV2 specification.
124
+ */
125
+ validate(data) {
126
+ if (!data || typeof data !== 'object') {
127
+ throw new LockfileV2Error(ERROR_CODES.INVALID_SCHEMA, 'Lockfile data must be a non-null object');
128
+ }
129
+
130
+ if (data.schemaVersion !== 2) {
131
+ throw new LockfileV2Error(
132
+ ERROR_CODES.INVALID_SCHEMA,
133
+ `Expected schemaVersion 2, got '${data.schemaVersion}'`
134
+ );
135
+ }
136
+
137
+ if (typeof data.revision !== 'number' || data.revision < 1) {
138
+ throw new LockfileV2Error(
139
+ ERROR_CODES.INVALID_SCHEMA,
140
+ `Lockfile revision must be a positive integer, got '${data.revision}'`
141
+ );
142
+ }
143
+
144
+ if (!data.package || !data.package.name || !data.package.version) {
145
+ throw new LockfileV2Error(
146
+ ERROR_CODES.INVALID_SCHEMA,
147
+ 'Lockfile missing required package identity (name, version)'
148
+ );
149
+ }
150
+
151
+ if (!data.profile || !data.profile.id || !data.profile.hash) {
152
+ throw new LockfileV2Error(
153
+ ERROR_CODES.INVALID_SCHEMA,
154
+ 'Lockfile missing required profile metadata (id, hash)'
155
+ );
156
+ }
157
+
158
+ if (!Array.isArray(data.enabledAdapters)) {
159
+ throw new LockfileV2Error(
160
+ ERROR_CODES.INVALID_SCHEMA,
161
+ 'Lockfile enabledAdapters must be an array'
162
+ );
163
+ }
164
+
165
+ if (!data.managedFiles || typeof data.managedFiles !== 'object') {
166
+ throw new LockfileV2Error(
167
+ ERROR_CODES.INVALID_SCHEMA,
168
+ 'Lockfile managedFiles must be an object map'
169
+ );
170
+ }
171
+
172
+ // Validate individual file records
173
+ for (const [filePath, record] of Object.entries(data.managedFiles)) {
174
+ if (!record.exactSha256 || !record.semanticTextSha256 || !record.kind || !record.generator) {
175
+ throw new LockfileV2Error(
176
+ ERROR_CODES.INVALID_SCHEMA,
177
+ `Managed file '${filePath}' record is missing required fields (exactSha256, semanticTextSha256, kind, generator)`,
178
+ { filePath, record }
179
+ );
180
+ }
181
+ }
182
+
183
+ return true;
184
+ }
185
+
186
+ /**
187
+ * Saves lockfile to disk with optimistic concurrency Compare-And-Swap (CAS).
188
+ *
189
+ * @param {object} data - Lockfile data to write
190
+ * @param {number} [expectedRevision] - Optional expected revision on disk before increment
191
+ * @returns {object} Updated lockfile data with incremented revision
192
+ */
193
+ write(data, expectedRevision = undefined) {
194
+ const currentOnDisk = this.read();
195
+
196
+ if (expectedRevision !== undefined && currentOnDisk !== null) {
197
+ if (currentOnDisk.revision !== expectedRevision) {
198
+ throw new LockfileV2Error(
199
+ ERROR_CODES.REVISION_CONFLICT,
200
+ `Compare-And-Swap failed: disk revision is ${currentOnDisk.revision}, expected ${expectedRevision}`,
201
+ { actualRevision: currentOnDisk.revision, expectedRevision }
202
+ );
203
+ }
204
+ }
205
+
206
+ // Increment revision
207
+ const nextRevision = currentOnDisk ? currentOnDisk.revision + 1 : (data.revision || 1);
208
+ const dataToWrite = {
209
+ ...data,
210
+ schemaVersion: 2,
211
+ revision: nextRevision,
212
+ };
213
+
214
+ this.validate(dataToWrite);
215
+
216
+ const dir = path.dirname(this.lockfilePath);
217
+ if (!fs.existsSync(dir)) {
218
+ fs.mkdirSync(dir, { recursive: true });
219
+ }
220
+
221
+ const tmpPath = `${this.lockfilePath}.tmp-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
222
+ fs.writeFileSync(tmpPath, JSON.stringify(dataToWrite, null, 2) + '\n', 'utf8');
223
+
224
+ try {
225
+ safeRenameSync(tmpPath, this.lockfilePath);
226
+ } catch (err) {
227
+ try {
228
+ if (fs.existsSync(tmpPath)) fs.unlinkSync(tmpPath);
229
+ } catch {
230
+ // Best-effort cleanup
231
+ }
232
+ throw err;
233
+ }
234
+
235
+ return dataToWrite;
236
+ }
237
+
238
+ /**
239
+ * Adds or updates a managed file entry in memory.
240
+ */
241
+ recordManagedFile(data, relativePosixPath, fileMeta) {
242
+ if (!data.managedFiles) data.managedFiles = {};
243
+
244
+ data.managedFiles[relativePosixPath] = {
245
+ exactSha256: fileMeta.exactSha256,
246
+ semanticTextSha256: fileMeta.semanticTextSha256,
247
+ kind: fileMeta.kind || 'generated-adapter',
248
+ generator: fileMeta.generator,
249
+ inputsHash: fileMeta.inputsHash || 'sha256:0000000000000000000000000000000000000000000000000000000000000000',
250
+ mode: fileMeta.mode || 420,
251
+ lastTransaction: fileMeta.lastTransaction || undefined,
252
+ };
253
+
254
+ return data;
255
+ }
256
+
257
+ /**
258
+ * Removes a managed file entry from memory.
259
+ */
260
+ removeManagedFile(data, relativePosixPath) {
261
+ if (data.managedFiles && data.managedFiles[relativePosixPath]) {
262
+ delete data.managedFiles[relativePosixPath];
263
+ }
264
+ return data;
265
+ }
266
+ }
267
+
268
+ module.exports = {
269
+ LockfileV2Manager,
270
+ computeExactHash,
271
+ computeSemanticHash,
272
+ LockfileV2Error,
273
+ ERROR_CODES,
274
+ DEFAULT_LOCKFILE_SUBPATH,
275
+ };
@@ -0,0 +1,222 @@
1
+ /**
2
+ * .agents/filesystem/platform-hardening.js
3
+ * ContextOS — Cross-Platform Hardening & Resilience Engine
4
+ *
5
+ * Implements Section 21 of CONTEXTOS_IMPLEMENTATION_PLAN.md:
6
+ * - 21.1: Platform support contract (Node 18 Core, Node 20 Runtime, filesystem checks)
7
+ * - 21.2: Windows resilience (EPERM/EBUSY rename/unlink retry wrappers, ADS, reserved names)
8
+ * - 21.4: macOS APFS Unicode normalization (NFC standard)
9
+ * - 21.5: UNC / Network filesystem rejection (distributed lock safety)
10
+ * - 21.6: Platform-aware repository fingerprint (no blanket lowercase, git common dir, persisted UUID)
11
+ */
12
+
13
+ 'use strict';
14
+
15
+ const fs = require('fs');
16
+ const path = require('path');
17
+ const os = require('os');
18
+ const crypto = require('crypto');
19
+
20
+ function sha256(str) {
21
+ return crypto.createHash('sha256').update(str || '').digest('hex');
22
+ }
23
+
24
+ /**
25
+ * Checks if a given path is an unsupported UNC or Network Share (Section 21.5).
26
+ *
27
+ * @param {string} targetPath
28
+ * @returns {boolean} True if path is UNC or network share
29
+ */
30
+ function isNetworkOrUNCPath(targetPath) {
31
+ if (typeof targetPath !== 'string') return false;
32
+ const normalized = targetPath.replace(/\\/g, '/');
33
+
34
+ // UNC paths on Windows: //server/share or \\server\share
35
+ if (normalized.startsWith('//')) {
36
+ return true;
37
+ }
38
+
39
+ // Windows extended-length UNC: \\?\UNC\server\share
40
+ if (/^\\\\[?.]\\UNC\\/i.test(targetPath)) {
41
+ return true;
42
+ }
43
+
44
+ return false;
45
+ }
46
+
47
+ /**
48
+ * Validates platform runtime compatibility (Section 21.1).
49
+ *
50
+ * @param {Object} [options]
51
+ * @param {'core'|'runtime'} [options.component='core']
52
+ * @returns {{ supported: boolean, nodeVersion: number, required: number, issues: string[] }}
53
+ */
54
+ function validatePlatformSupport(options = {}) {
55
+ const component = options.component || 'core';
56
+ const nodeMajor = parseInt(process.versions.node.split('.')[0], 10);
57
+ const requiredMajor = component === 'runtime' ? 20 : 18;
58
+ const issues = [];
59
+
60
+ if (nodeMajor < requiredMajor) {
61
+ issues.push(
62
+ `Node.js version ${process.versions.node} is below required minimum v${requiredMajor}.x for ContextOS ${component}.`
63
+ );
64
+ }
65
+
66
+ return {
67
+ supported: issues.length === 0,
68
+ nodeVersion: nodeMajor,
69
+ required: requiredMajor,
70
+ issues,
71
+ };
72
+ }
73
+
74
+ /**
75
+ * Computes platform-aware repository fingerprint (Section 21.6).
76
+ * Preserves Linux case while normalizing Windows/macOS drive letters and APFS.
77
+ * Incorporates persisted repository UUID and Git common directory identity.
78
+ *
79
+ * @param {string} repoRoot
80
+ * @returns {string} SHA-256 fingerprint
81
+ */
82
+ function calculateRepositoryFingerprint(repoRoot) {
83
+ const resolved = path.resolve(repoRoot);
84
+ let canonicalPath = resolved;
85
+
86
+ if (process.platform === 'win32') {
87
+ // Normalize drive letter uppercase (e.g. C:) and forward slashes
88
+ canonicalPath = canonicalPath.replace(/^([a-zA-Z]):/, (_, drive) => drive.toUpperCase() + ':');
89
+ canonicalPath = canonicalPath.toLowerCase(); // Case-insensitive on Windows
90
+ } else if (process.platform === 'darwin') {
91
+ // APFS is typically case-insensitive, normalize Unicode NFC
92
+ canonicalPath = canonicalPath.normalize('NFC').toLowerCase();
93
+ }
94
+
95
+ // Persisted repository UUID
96
+ const stateDir = path.join(resolved, '.agents', '.contextos');
97
+ const uuidFile = path.join(stateDir, 'repo-id');
98
+ let repoUuid = '';
99
+
100
+ if (fs.existsSync(uuidFile)) {
101
+ try {
102
+ repoUuid = fs.readFileSync(uuidFile, 'utf8').trim();
103
+ } catch {}
104
+ }
105
+
106
+ if (!repoUuid) {
107
+ repoUuid = crypto.randomUUID();
108
+ try {
109
+ fs.mkdirSync(stateDir, { recursive: true });
110
+ fs.writeFileSync(uuidFile, repoUuid, 'utf8');
111
+ } catch {}
112
+ }
113
+
114
+ // Check Git directory identity if present
115
+ let gitIdentity = 'nogit';
116
+ const gitDir = path.join(resolved, '.git');
117
+ if (fs.existsSync(gitDir)) {
118
+ try {
119
+ const gitStat = fs.statSync(gitDir);
120
+ gitIdentity = `${gitStat.ino || 0}:${gitStat.dev || 0}`;
121
+ } catch {}
122
+ }
123
+
124
+ const payload = `${canonicalPath}|${repoUuid}|${gitIdentity}`;
125
+ return sha256(payload);
126
+ }
127
+
128
+ /**
129
+ * Normalizes Unicode text to NFC standard (Section 21.4).
130
+ *
131
+ * @param {string} text
132
+ * @returns {string}
133
+ */
134
+ function normalizeUnicodeNFC(text) {
135
+ if (typeof text !== 'string') return text;
136
+ return text.normalize('NFC');
137
+ }
138
+
139
+ /**
140
+ * Synchronously renames a file with exponential backoff retry for Windows antivirus/indexer contention (Section 21.2).
141
+ *
142
+ * @param {string} source
143
+ * @param {string} destination
144
+ * @param {Object} [options]
145
+ * @param {number} [options.maxRetries=5]
146
+ * @param {number} [options.retryDelayMs=20]
147
+ */
148
+ function safeRenameSync(source, destination, options = {}) {
149
+ const maxRetries = options.maxRetries || 5;
150
+ const retryDelayMs = options.retryDelayMs || 20;
151
+
152
+ let lastErr = null;
153
+ for (let attempt = 0; attempt <= maxRetries; attempt++) {
154
+ try {
155
+ if (process.platform === 'win32' && fs.existsSync(destination)) {
156
+ try { fs.unlinkSync(destination); } catch {}
157
+ }
158
+ fs.renameSync(source, destination);
159
+ return true;
160
+ } catch (err) {
161
+ lastErr = err;
162
+ if (err.code === 'EPERM' || err.code === 'EBUSY' || err.code === 'EACCES') {
163
+ if (attempt < maxRetries) {
164
+ const sleepMs = Math.floor(retryDelayMs * Math.pow(1.5, attempt));
165
+ // Busy-wait briefly for sync operation
166
+ const start = Date.now();
167
+ while (Date.now() - start < sleepMs) {}
168
+ continue;
169
+ }
170
+ }
171
+ throw err;
172
+ }
173
+ }
174
+
175
+ throw lastErr;
176
+ }
177
+
178
+ /**
179
+ * Synchronously unlinks a file with retry for transient Windows locks (Section 21.2).
180
+ *
181
+ * @param {string} targetPath
182
+ * @param {Object} [options]
183
+ * @param {number} [options.maxRetries=5]
184
+ * @param {number} [options.retryDelayMs=20]
185
+ */
186
+ function safeUnlinkSync(targetPath, options = {}) {
187
+ if (!fs.existsSync(targetPath)) return true;
188
+
189
+ const maxRetries = options.maxRetries || 5;
190
+ const retryDelayMs = options.retryDelayMs || 20;
191
+
192
+ let lastErr = null;
193
+ for (let attempt = 0; attempt <= maxRetries; attempt++) {
194
+ try {
195
+ fs.unlinkSync(targetPath);
196
+ return true;
197
+ } catch (err) {
198
+ lastErr = err;
199
+ if (err.code === 'EPERM' || err.code === 'EBUSY' || err.code === 'EACCES') {
200
+ if (attempt < maxRetries) {
201
+ const sleepMs = Math.floor(retryDelayMs * Math.pow(1.5, attempt));
202
+ const start = Date.now();
203
+ while (Date.now() - start < sleepMs) {}
204
+ continue;
205
+ }
206
+ }
207
+ if (!fs.existsSync(targetPath)) return true;
208
+ throw err;
209
+ }
210
+ }
211
+
212
+ throw lastErr;
213
+ }
214
+
215
+ module.exports = {
216
+ isNetworkOrUNCPath,
217
+ validatePlatformSupport,
218
+ calculateRepositoryFingerprint,
219
+ normalizeUnicodeNFC,
220
+ safeRenameSync,
221
+ safeUnlinkSync,
222
+ };
@@ -0,0 +1,218 @@
1
+ /**
2
+ * .agents/filesystem/project-lock.js
3
+ * ContextOS Project-Wide Inter-Process Mutation Lock
4
+ *
5
+ * Enforces single-writer mutual exclusion during compilation, export, and migration.
6
+ * - Atomic acquisition via exclusive filesystem creation (O_CREAT | O_EXCL / 'wx')
7
+ * - Cryptographic UUID token prevents zombie/stale processes from releasing new locks
8
+ * - Stale process detection via PID liveness probe (kill(pid, 0))
9
+ * - Structured machine-readable diagnostic output (CTX_PROJECT_BUSY)
10
+ */
11
+
12
+ const fs = require('fs');
13
+ const path = require('path');
14
+ const os = require('os');
15
+ const crypto = require('crypto');
16
+ const { isNetworkOrUNCPath } = require('./platform-hardening.js');
17
+
18
+ const LOCK_SUBPATH = path.join('.agents', '.contextos', 'locks', 'mutation.lock');
19
+ const ERROR_CODES = {
20
+ BUSY: 'CTX_PROJECT_BUSY',
21
+ INVALID_TOKEN: 'CTX_LOCK_INVALID_TOKEN',
22
+ NOT_LOCKED: 'CTX_LOCK_NOT_LOCKED',
23
+ UNSUPPORTED: 'UNSUPPORTED',
24
+ };
25
+
26
+ class ProjectLockError extends Error {
27
+ constructor(code, message, details = {}) {
28
+ super(message);
29
+ this.name = 'ProjectLockError';
30
+ this.code = code;
31
+ this.details = details;
32
+ }
33
+ }
34
+
35
+ /**
36
+ * Checks if a PID is alive on the local system.
37
+ */
38
+ function isPidAlive(pid) {
39
+ if (typeof pid !== 'number' || pid <= 0) return false;
40
+ try {
41
+ process.kill(pid, 0);
42
+ return true;
43
+ } catch (err) {
44
+ return err.code === 'EPERM'; // Process exists but lacks permission to signal
45
+ }
46
+ }
47
+
48
+ class ProjectMutationLock {
49
+ constructor(projectRoot, options = {}) {
50
+ if (isNetworkOrUNCPath(projectRoot)) {
51
+ throw new ProjectLockError(
52
+ ERROR_CODES.UNSUPPORTED,
53
+ `UNC/network filesystem mutation is unsupported for projectRoot: ${projectRoot}`,
54
+ { projectRoot, status: 'UNSUPPORTED' }
55
+ );
56
+ }
57
+ this.projectRoot = path.resolve(projectRoot);
58
+ this.lockPath = options.lockPath
59
+ ? path.resolve(this.projectRoot, options.lockPath)
60
+ : path.resolve(this.projectRoot, LOCK_SUBPATH);
61
+ this.lockDir = path.dirname(this.lockPath);
62
+ this.currentToken = null;
63
+ }
64
+
65
+ /**
66
+ * Reads and parses current lock payload, or null if lock doesn't exist.
67
+ */
68
+ inspect() {
69
+ try {
70
+ if (!fs.existsSync(this.lockPath)) return null;
71
+ const raw = fs.readFileSync(this.lockPath, 'utf8');
72
+ return JSON.parse(raw);
73
+ } catch {
74
+ return null;
75
+ }
76
+ }
77
+
78
+ /**
79
+ * Checks if lock is currently held.
80
+ */
81
+ isLocked() {
82
+ return this.inspect() !== null;
83
+ }
84
+
85
+ /**
86
+ * Attempts to acquire the mutation lock exclusively.
87
+ *
88
+ * @param {object} [meta] - Context metadata
89
+ * @param {string} [meta.command='mutation'] - Triggering command
90
+ * @param {string} [meta.projectFingerprint=''] - Project identity fingerprint
91
+ * @param {boolean} [meta.autoReapStale=true] - Automatically reclaim lock if holder PID is dead
92
+ * @returns {string} The secret lock token required for release
93
+ */
94
+ acquire(meta = {}) {
95
+ if (!fs.existsSync(this.lockDir)) {
96
+ fs.mkdirSync(this.lockDir, { recursive: true });
97
+ }
98
+
99
+ const {
100
+ command = 'mutation',
101
+ projectFingerprint = '',
102
+ autoReapStale = true,
103
+ } = meta;
104
+
105
+ // Check for existing lock and inspect holder
106
+ const existing = this.inspect();
107
+ if (existing) {
108
+ if (autoReapStale && existing.pid && !isPidAlive(existing.pid)) {
109
+ // Holder PID is dead, safe to reap stale lock
110
+ this.forceUnlock(`Auto-reaped stale lock from dead PID ${existing.pid}`);
111
+ } else {
112
+ throw new ProjectLockError(
113
+ ERROR_CODES.BUSY,
114
+ `Project is busy: lock is held by PID ${existing.pid} (${existing.command || 'unknown'}) since ${existing.acquiredAt}`,
115
+ { lockDetails: existing }
116
+ );
117
+ }
118
+ }
119
+
120
+ const token = crypto.randomUUID();
121
+ const payload = {
122
+ token,
123
+ pid: process.pid,
124
+ processStartedAt: new Date(Date.now() - Math.floor(process.uptime() * 1000)).toISOString(),
125
+ hostname: os.hostname(),
126
+ command,
127
+ projectFingerprint,
128
+ acquiredAt: new Date().toISOString(),
129
+ };
130
+
131
+ let fd = null;
132
+ try {
133
+ // 'wx' flag opens for writing, failing if path already exists (O_CREAT | O_EXCL)
134
+ fd = fs.openSync(this.lockPath, 'wx');
135
+ fs.writeFileSync(fd, JSON.stringify(payload, null, 2), 'utf8');
136
+ fs.fsyncSync(fd);
137
+ } catch (err) {
138
+ if (err.code === 'EEXIST') {
139
+ const currentHolder = this.inspect();
140
+ throw new ProjectLockError(
141
+ ERROR_CODES.BUSY,
142
+ `Project is busy: lock acquired concurrently by another process`,
143
+ { lockDetails: currentHolder }
144
+ );
145
+ }
146
+ throw err;
147
+ } finally {
148
+ if (fd !== null) {
149
+ try {
150
+ fs.closeSync(fd);
151
+ } catch {
152
+ // Best effort close
153
+ }
154
+ }
155
+ }
156
+
157
+ this.currentToken = token;
158
+ return token;
159
+ }
160
+
161
+ /**
162
+ * Releases lock verifying ownership token.
163
+ *
164
+ * @param {string} [token] - Release token (defaults to currently held token)
165
+ */
166
+ release(token = this.currentToken) {
167
+ if (!token) {
168
+ throw new ProjectLockError(
169
+ ERROR_CODES.INVALID_TOKEN,
170
+ 'Cannot release lock: no token provided and no token actively held in session'
171
+ );
172
+ }
173
+
174
+ const current = this.inspect();
175
+ if (!current) {
176
+ this.currentToken = null;
177
+ return; // Already unlocked
178
+ }
179
+
180
+ if (current.token !== token) {
181
+ throw new ProjectLockError(
182
+ ERROR_CODES.INVALID_TOKEN,
183
+ `Token mismatch: current lock belongs to PID ${current.pid} acquired at ${current.acquiredAt}`,
184
+ { expectedToken: current.token, attemptedToken: token }
185
+ );
186
+ }
187
+
188
+ try {
189
+ fs.unlinkSync(this.lockPath);
190
+ } catch (err) {
191
+ if (err.code !== 'ENOENT') throw err;
192
+ }
193
+
194
+ this.currentToken = null;
195
+ }
196
+
197
+ /**
198
+ * Administratively forces removal of the lock.
199
+ */
200
+ forceUnlock(reason = 'Administrative force unlock') {
201
+ try {
202
+ if (fs.existsSync(this.lockPath)) {
203
+ fs.unlinkSync(this.lockPath);
204
+ }
205
+ } catch (err) {
206
+ if (err.code !== 'ENOENT') throw err;
207
+ }
208
+ this.currentToken = null;
209
+ return { success: true, reason };
210
+ }
211
+ }
212
+
213
+ module.exports = {
214
+ ProjectMutationLock,
215
+ ProjectLockError,
216
+ ERROR_CODES,
217
+ isPidAlive,
218
+ };