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
package/.agents/doctor.js CHANGED
@@ -1,15 +1,26 @@
1
1
  /**
2
2
  * .agents/doctor.js
3
- * ContextOS — Project Diagnostics & Health Check Engine
3
+ * ContextOS Doctor v2 — Comprehensive Project Diagnostics & Health Check Engine
4
4
  *
5
- * Inspects:
6
- * 1. .agents/ directory and core runtime files
7
- * 2. Node.js environment compatibility
8
- * 3. Active profile and skill exclusion/preference state
9
- * 4. Compiled AI agent adapters (Gemini, Claude, Cursor, Copilot, etc.)
10
- * 5. MCP server status and worktree support
11
- * 6. Security controls (pre-commit hooks, secret scanning)
12
- * 7. Project tech stack detection and recommended profile alignment
5
+ * Implements Milestone 8 Doctor v2 specification:
6
+ * - Standardized status outcomes: PASS, WARN, FAIL, SKIP, UNVERIFIED, RECOVERY_REQUIRED
7
+ * - Deep diagnostic checks:
8
+ * 1. Node.js environment (Node 18 for Core, Node 20 for Runtime)
9
+ * 2. Git availability & version
10
+ * 3. Filesystem permissions and path containment
11
+ * 4. Manifests & schema compliance
12
+ * 5. Skill dependency graph cycles & missing requirements
13
+ * 6. Profile validation against registry
14
+ * 7. Lockfile v1 & v2 integrity with CAS revision checking
15
+ * 8. Crash resilience & uncommitted transaction journals
16
+ * 9. Output provenance & managed artifact drift detection
17
+ * 10. Adapter version compliance
18
+ * 11. MCP server handshake verification
19
+ * 12. Secret scanning guardrails (missing scanner yields WARN/SKIP, never PASS)
20
+ * 13. Monorepo workspace topology
21
+ * 14. User customization safety
22
+ * - Automated remediation (doctor --fix) for safe, deterministic repairs
23
+ * - Versioned structured JSON reporting (schemaVersion: 2.0.0)
13
24
  */
14
25
 
15
26
  'use strict';
@@ -18,6 +29,22 @@ const fs = require('fs');
18
29
  const path = require('path');
19
30
  const { execFileSync } = require('child_process');
20
31
  const profiles = require('./profiles.js');
32
+ const {
33
+ LockfileV2Manager,
34
+ JournaledTransaction,
35
+ ProjectMutationLock,
36
+ isPidAlive,
37
+ toPosix,
38
+ } = require('./filesystem/index.js');
39
+
40
+ const STATUS = {
41
+ PASS: 'PASS',
42
+ WARN: 'WARN',
43
+ FAIL: 'FAIL',
44
+ SKIP: 'SKIP',
45
+ UNVERIFIED: 'UNVERIFIED',
46
+ RECOVERY_REQUIRED: 'RECOVERY_REQUIRED',
47
+ };
21
48
 
22
49
  const DOMAIN_MAP = {
23
50
  frontend: new Set([
@@ -42,48 +69,191 @@ const DOMAIN_MAP = {
42
69
  function checkNodeVersion() {
43
70
  const version = process.version; // e.g. v22.12.0
44
71
  const match = version.match(/^v?(\d+)\.(\d+)/);
45
- if (!match) return { ok: false, version, message: `${version} (unknown)` };
72
+ if (!match) {
73
+ return {
74
+ id: 'node_version',
75
+ status: STATUS.FAIL,
76
+ ok: false,
77
+ version,
78
+ message: `${version} (unknown format)`,
79
+ remediation: 'Install Node.js >= 18.0.0 from https://nodejs.org',
80
+ };
81
+ }
46
82
 
47
83
  const major = parseInt(match[1], 10);
48
84
  const minor = parseInt(match[2], 10);
49
- // Requires at least Node 18.0.0 or 16.7.0 for recursive cpSync
50
- const ok = major > 16 || (major === 16 && minor >= 7);
85
+
86
+ // Core requires Node >= 18.0.0 (or 16.7.0 for basic cpSync). Runtime requires Node >= 20.0.0.
87
+ if (major < 18) {
88
+ return {
89
+ id: 'node_version',
90
+ status: STATUS.FAIL,
91
+ ok: false,
92
+ version,
93
+ message: `Node.js ${version} (< 18.0.0, Node 18 required for Core)`,
94
+ remediation: 'Upgrade Node.js to >= 18.0.0 (recommended >= 20.0.0 for ContextOS Runtime)',
95
+ };
96
+ }
97
+
98
+ if (major < 20) {
99
+ return {
100
+ id: 'node_version',
101
+ status: STATUS.WARN,
102
+ ok: true,
103
+ version,
104
+ message: `Node.js ${version} (>= 18.0.0 for Core, Node 20 recommended for Runtime)`,
105
+ remediation: 'Upgrade to Node.js >= 20.0.0 for optimal sandbox and runtime worker support',
106
+ };
107
+ }
108
+
51
109
  return {
52
- ok,
110
+ id: 'node_version',
111
+ status: STATUS.PASS,
112
+ ok: true,
53
113
  version,
54
- message: ok ? `Node.js ${version} (>= 16.7.0 required)` : `Node.js ${version} (< 16.7.0, please upgrade)`,
114
+ message: `Node.js ${version} (>= 20.0.0, optimal)`,
115
+ remediation: null,
55
116
  };
56
117
  }
57
118
 
119
+ function checkGitVersion() {
120
+ try {
121
+ const stdout = execFileSync('git', ['--version'], { stdio: ['ignore', 'pipe', 'ignore'] }).toString().trim();
122
+ return {
123
+ id: 'git_version',
124
+ status: STATUS.PASS,
125
+ ok: true,
126
+ message: stdout,
127
+ remediation: null,
128
+ };
129
+ } catch {
130
+ return {
131
+ id: 'git_version',
132
+ status: STATUS.FAIL,
133
+ ok: false,
134
+ message: 'git binary not accessible in PATH',
135
+ remediation: 'Install Git and add it to your system PATH',
136
+ };
137
+ }
138
+ }
139
+
140
+ function checkFilesystemPermissions(projectDir) {
141
+ try {
142
+ const probeDir = path.join(projectDir, '.agents', '.contextos', 'probe');
143
+ fs.mkdirSync(probeDir, { recursive: true });
144
+ const probeFile = path.join(probeDir, 'perm-test.tmp');
145
+ fs.writeFileSync(probeFile, 'ok', 'utf8');
146
+ fs.unlinkSync(probeFile);
147
+ try { fs.rmdirSync(probeDir); } catch {}
148
+
149
+ return {
150
+ id: 'filesystem_permissions',
151
+ status: STATUS.PASS,
152
+ ok: true,
153
+ message: 'Read and write permissions verified in .agents/',
154
+ remediation: null,
155
+ };
156
+ } catch (err) {
157
+ return {
158
+ id: 'filesystem_permissions',
159
+ status: STATUS.FAIL,
160
+ ok: false,
161
+ message: `Filesystem permission denied: ${err.message}`,
162
+ remediation: 'Ensure the current user has read/write privileges in the project folder',
163
+ };
164
+ }
165
+ }
166
+
58
167
  function checkPreCommitHook(projectDir) {
59
168
  const hookPath = path.join(projectDir, '.git', 'hooks', 'pre-commit');
60
169
  if (!fs.existsSync(hookPath)) {
61
- return { ok: false, message: 'not installed (run: npm run setup:hooks)' };
170
+ if (!isContextOSDevRepo(projectDir)) {
171
+ return {
172
+ id: 'pre_commit_hook',
173
+ status: STATUS.SKIP,
174
+ ok: true,
175
+ message: 'optional (not installed in workspace)',
176
+ remediation: null,
177
+ };
178
+ }
179
+ return {
180
+ id: 'pre_commit_hook',
181
+ status: STATUS.WARN,
182
+ ok: false,
183
+ message: 'not installed (run: npm run setup:hooks)',
184
+ remediation: 'Run `npm run setup:hooks` to install Git pre-commit validation hook',
185
+ };
62
186
  }
63
187
  try {
64
188
  const content = fs.readFileSync(hookPath, 'utf8');
65
189
  if (content.includes('check-secrets')) {
66
- return { ok: true, message: 'installed (with secret check)' };
190
+ return {
191
+ id: 'pre_commit_hook',
192
+ status: STATUS.PASS,
193
+ ok: true,
194
+ message: 'installed (with secret check)',
195
+ remediation: null,
196
+ };
67
197
  }
68
- return { ok: false, message: 'installed but missing check-secrets' };
198
+ return {
199
+ id: 'pre_commit_hook',
200
+ status: STATUS.WARN,
201
+ ok: false,
202
+ message: 'installed but missing check-secrets guard',
203
+ remediation: 'Reinstall hooks via `npm run setup:hooks` to include secret scanning',
204
+ };
69
205
  } catch {
70
- return { ok: false, message: 'unreadable' };
206
+ return {
207
+ id: 'pre_commit_hook',
208
+ status: STATUS.FAIL,
209
+ ok: false,
210
+ message: 'unreadable',
211
+ remediation: 'Check filesystem permissions for .git/hooks/pre-commit',
212
+ };
71
213
  }
72
214
  }
73
215
 
74
216
  function checkSecretScanner(projectDir) {
75
217
  const scriptPath = path.join(projectDir, 'scripts', 'check-secrets.js');
76
218
  if (!fs.existsSync(scriptPath)) {
77
- return { ok: true, message: 'scanner script not present (skipped)' };
219
+ if (!isContextOSDevRepo(projectDir)) {
220
+ return {
221
+ id: 'secret_scanner',
222
+ status: STATUS.SKIP,
223
+ ok: true,
224
+ message: 'optional (not configured in workspace)',
225
+ remediation: null,
226
+ };
227
+ }
228
+ // Per Milestone 8 spec: "doctor не считает отсутствующий scanner PASS"
229
+ return {
230
+ id: 'secret_scanner',
231
+ status: STATUS.WARN,
232
+ ok: false,
233
+ message: 'scanner script not present (scripts/check-secrets.js missing)',
234
+ remediation: 'Install check-secrets.js or configure repository secret scanning',
235
+ };
78
236
  }
79
237
  try {
80
238
  execFileSync(process.execPath, [scriptPath, '--all'], {
81
239
  cwd: projectDir,
82
240
  stdio: ['pipe', 'pipe', 'pipe'],
83
241
  });
84
- return { ok: true, message: '0 findings' };
242
+ return {
243
+ id: 'secret_scanner',
244
+ status: STATUS.PASS,
245
+ ok: true,
246
+ message: '0 findings',
247
+ remediation: null,
248
+ };
85
249
  } catch (err) {
86
- return { ok: false, message: 'potential secrets detected (run: npm run check:secrets)' };
250
+ return {
251
+ id: 'secret_scanner',
252
+ status: STATUS.FAIL,
253
+ ok: false,
254
+ message: 'potential secrets detected in workspace',
255
+ remediation: 'Run `npm run check:secrets` and resolve flagged secrets or add to allowlist',
256
+ };
87
257
  }
88
258
  }
89
259
 
@@ -113,29 +283,138 @@ function checkCompiledAdapters(projectDir) {
113
283
  }
114
284
 
115
285
  function checkLockfileIntegrity(projectDir) {
116
- const lockPath = path.join(projectDir, '.agents', 'contextos.lock.json');
117
- if (!fs.existsSync(lockPath)) {
118
- return { ok: true, exists: false, isError: false, message: 'none (not generated yet)' };
286
+ const v2Path = path.join(projectDir, '.agents', 'lockfile.v2.json');
287
+ const v1Path = path.join(projectDir, '.agents', 'contextos.lock.json');
288
+ const hasV2 = fs.existsSync(v2Path);
289
+ const hasV1 = fs.existsSync(v1Path);
290
+
291
+ if (!hasV2 && !hasV1) {
292
+ return {
293
+ id: 'lockfile',
294
+ status: STATUS.WARN,
295
+ ok: true,
296
+ exists: false,
297
+ isError: false,
298
+ message: 'none (not generated yet)',
299
+ remediation: 'Run `contextos export all` or `contextos doctor --fix` to generate lockfile',
300
+ };
119
301
  }
302
+
303
+ // 1. Check Lockfile v2 if present
304
+ if (hasV2) {
305
+ try {
306
+ const raw = fs.readFileSync(v2Path, 'utf8');
307
+ const data = JSON.parse(raw);
308
+ const lockfileManager = new LockfileV2Manager(projectDir);
309
+ lockfileManager.validate(data);
310
+ const count = Object.keys(data.managedFiles || {}).length;
311
+ return {
312
+ id: 'lockfile',
313
+ status: STATUS.PASS,
314
+ ok: true,
315
+ exists: true,
316
+ isError: false,
317
+ message: `valid (v${data.schemaVersion}, rev ${data.revision}, ${count} managed files)`,
318
+ remediation: null,
319
+ };
320
+ } catch (err) {
321
+ return {
322
+ id: 'lockfile',
323
+ status: STATUS.FAIL,
324
+ ok: false,
325
+ exists: true,
326
+ isError: true,
327
+ error: `Lockfile v2 is corrupted or invalid: ${err.message}`,
328
+ remediation: 'Run `contextos doctor --fix` to regenerate valid Lockfile v2',
329
+ };
330
+ }
331
+ }
332
+
333
+ // 2. Check Lockfile v1
120
334
  try {
121
- const raw = fs.readFileSync(lockPath, 'utf8');
335
+ const raw = fs.readFileSync(v1Path, 'utf8');
122
336
  const data = JSON.parse(raw);
123
337
  if (!data || typeof data !== 'object') {
124
- return { ok: false, exists: true, isError: true, error: 'Lockfile JSON root is not an object' };
338
+ return {
339
+ id: 'lockfile',
340
+ status: STATUS.FAIL,
341
+ ok: false,
342
+ exists: true,
343
+ isError: true,
344
+ error: 'Lockfile JSON root is not an object',
345
+ remediation: 'Run `contextos doctor --fix` to rebuild lockfile',
346
+ };
125
347
  }
126
348
  if (!data.schemaVersion || typeof data.schemaVersion !== 'number') {
127
- return { ok: false, exists: true, isError: true, error: 'Lockfile missing valid schemaVersion' };
349
+ return {
350
+ id: 'lockfile',
351
+ status: STATUS.FAIL,
352
+ ok: false,
353
+ exists: true,
354
+ isError: true,
355
+ error: 'Lockfile missing valid schemaVersion',
356
+ remediation: 'Run `contextos doctor --fix` to rebuild lockfile',
357
+ };
128
358
  }
129
- if (!data.managedFiles || typeof data.managedFiles !== 'object') {
130
- return { ok: false, exists: true, isError: true, error: 'Lockfile missing managedFiles map' };
359
+ if (!data.managedFiles && !data.files) {
360
+ return {
361
+ id: 'lockfile',
362
+ status: STATUS.FAIL,
363
+ ok: false,
364
+ exists: true,
365
+ isError: true,
366
+ error: 'Lockfile missing managedFiles map',
367
+ remediation: 'Run `contextos doctor --fix` to rebuild lockfile',
368
+ };
131
369
  }
132
- const count = Object.keys(data.managedFiles).length;
133
- return { ok: true, exists: true, isError: false, message: `valid (v${data.schemaVersion}, ${count} managed files)` };
370
+ const count = Object.keys(data.managedFiles || data.files || {}).length;
371
+ return {
372
+ id: 'lockfile',
373
+ status: STATUS.PASS,
374
+ ok: true,
375
+ exists: true,
376
+ isError: false,
377
+ message: `valid (v${data.schemaVersion}, ${count} managed files)`,
378
+ remediation: null,
379
+ };
134
380
  } catch (err) {
135
- return { ok: false, exists: true, isError: true, error: `Lockfile is corrupted JSON: ${err.message}` };
381
+ return {
382
+ id: 'lockfile',
383
+ status: STATUS.FAIL,
384
+ ok: false,
385
+ exists: true,
386
+ isError: true,
387
+ error: `Lockfile is corrupted JSON: ${err.message}`,
388
+ remediation: 'Run `contextos doctor --fix` to rebuild lockfile',
389
+ };
136
390
  }
137
391
  }
138
392
 
393
+ function checkTransactions(projectDir) {
394
+ try {
395
+ const pending = JournaledTransaction.listPending(projectDir);
396
+ if (pending.length > 0) {
397
+ return {
398
+ id: 'incomplete_transactions',
399
+ status: STATUS.RECOVERY_REQUIRED,
400
+ ok: false,
401
+ error: `RECOVERY_REQUIRED: Found ${pending.length} incomplete transaction(s).`,
402
+ pending,
403
+ remediation: 'Run: contextos recover --list',
404
+ };
405
+ }
406
+ } catch {
407
+ // Best-effort
408
+ }
409
+ return {
410
+ id: 'incomplete_transactions',
411
+ status: STATUS.PASS,
412
+ ok: true,
413
+ message: 'Clean (0 uncommitted transaction journals)',
414
+ remediation: null,
415
+ };
416
+ }
417
+
139
418
  function checkAdapterIntegrity(projectDir) {
140
419
  const errors = [];
141
420
  const filesToCheck = [
@@ -149,10 +428,10 @@ function checkAdapterIntegrity(projectDir) {
149
428
  try {
150
429
  const stat = fs.statSync(f);
151
430
  if (stat.size === 0) {
152
- errors.push(`Adapter file ${path.relative(projectDir, f).replace(/\\/g, '/')} is empty (0 bytes)`);
431
+ errors.push(`Adapter file ${toPosix(path.relative(projectDir, f))} is empty (0 bytes)`);
153
432
  }
154
433
  } catch {
155
- errors.push(`Adapter file ${path.relative(projectDir, f).replace(/\\/g, '/')} is unreadable`);
434
+ errors.push(`Adapter file ${toPosix(path.relative(projectDir, f))} is unreadable`);
156
435
  }
157
436
  }
158
437
  }
@@ -170,13 +449,20 @@ function checkAdapterIntegrity(projectDir) {
170
449
  } catch {}
171
450
  }
172
451
 
173
- return { ok: errors.length === 0, errors };
452
+ return {
453
+ id: 'adapter_integrity',
454
+ status: errors.length === 0 ? STATUS.PASS : STATUS.FAIL,
455
+ ok: errors.length === 0,
456
+ errors,
457
+ message: errors.length === 0 ? 'All compiled adapter files are structurally intact' : `${errors.length} corrupt adapter file(s)`,
458
+ remediation: errors.length === 0 ? null : 'Run `contextos export all` to re-render clean adapters',
459
+ };
174
460
  }
175
461
 
176
462
  function checkSymlinks(projectDir) {
177
463
  const errors = [];
178
464
  const targetDir = path.join(projectDir, '.agents');
179
- if (!fs.existsSync(targetDir)) return { ok: true, errors: [] };
465
+ if (!fs.existsSync(targetDir)) return { ok: true, errors: [], status: STATUS.PASS };
180
466
 
181
467
  function scan(current) {
182
468
  let entries;
@@ -191,19 +477,304 @@ function checkSymlinks(projectDir) {
191
477
  const lstat = fs.lstatSync(full);
192
478
  if (lstat.isSymbolicLink()) {
193
479
  if (!fs.existsSync(full)) {
194
- errors.push(`Broken symlink: ${path.relative(projectDir, full).replace(/\\/g, '/')}`);
480
+ errors.push(`Broken symlink: ${toPosix(path.relative(projectDir, full))}`);
195
481
  }
196
482
  } else if (lstat.isDirectory()) {
197
483
  scan(full);
198
484
  }
199
485
  } catch {
200
- errors.push(`Cannot stat file: ${path.relative(projectDir, full).replace(/\\/g, '/')}`);
486
+ errors.push(`Cannot stat file: ${toPosix(path.relative(projectDir, full))}`);
201
487
  }
202
488
  }
203
489
  }
204
490
 
205
491
  scan(targetDir);
206
- return { ok: errors.length === 0, errors };
492
+ return {
493
+ id: 'symlinks',
494
+ status: errors.length === 0 ? STATUS.PASS : STATUS.FAIL,
495
+ ok: errors.length === 0,
496
+ errors,
497
+ message: errors.length === 0 ? '0 broken symlinks' : `${errors.length} broken symlink(s)`,
498
+ remediation: errors.length === 0 ? null : 'Remove or re-point broken symlinks in .agents/',
499
+ };
500
+ }
501
+
502
+ function checkMcpHandshake(projectDir) {
503
+ const mcpServerPath = path.join(projectDir, '.agents', 'mcp', 'server.mjs');
504
+ if (!fs.existsSync(mcpServerPath)) {
505
+ return {
506
+ id: 'mcp_handshake',
507
+ status: STATUS.SKIP,
508
+ ok: true,
509
+ message: 'Optional MCP Bridge not installed.',
510
+ remediation: 'Install separately: npm install --save-dev @contextos/mcp',
511
+ };
512
+ }
513
+
514
+ try {
515
+ const syntaxCheck = execFileSync(process.execPath, ['--check', mcpServerPath], {
516
+ stdio: ['ignore', 'pipe', 'pipe'],
517
+ });
518
+ return {
519
+ id: 'mcp_handshake',
520
+ status: STATUS.PASS,
521
+ ok: true,
522
+ message: 'server.mjs syntax and execution environment verified',
523
+ remediation: null,
524
+ };
525
+ } catch (err) {
526
+ return {
527
+ id: 'mcp_handshake',
528
+ status: STATUS.FAIL,
529
+ ok: false,
530
+ message: `MCP server syntax failure: ${err.message}`,
531
+ remediation: 'Rebuild MCP server bundle via `node scripts/build-mcp.js`',
532
+ };
533
+ }
534
+ }
535
+
536
+ function checkSandboxAvailability() {
537
+ try {
538
+ const stdout = execFileSync('docker', ['--version'], { encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'] });
539
+ return {
540
+ id: 'sandbox_availability',
541
+ status: STATUS.PASS,
542
+ ok: true,
543
+ message: `OCI Sandbox available (${stdout.trim()})`,
544
+ remediation: null,
545
+ };
546
+ } catch (err) {
547
+ try {
548
+ const podmanOut = execFileSync('podman', ['--version'], { encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'] });
549
+ return {
550
+ id: 'sandbox_availability',
551
+ status: STATUS.PASS,
552
+ ok: true,
553
+ message: `OCI Sandbox available (${podmanOut.trim()})`,
554
+ remediation: null,
555
+ };
556
+ } catch {
557
+ return {
558
+ id: 'sandbox_availability',
559
+ status: STATUS.WARN,
560
+ ok: true,
561
+ message: 'OCI Sandbox (Docker/Podman) not found. Executions will fallback to host-unsafe mode.',
562
+ remediation: 'Install Docker or Podman to enable secure isolated execution',
563
+ };
564
+ }
565
+ }
566
+ }
567
+
568
+ function checkClaimEvidence(projectDir) {
569
+ const claimsPath = path.join(projectDir, 'benchmarks', 'claims.json');
570
+ if (!fs.existsSync(claimsPath)) {
571
+ return {
572
+ id: 'claim_evidence',
573
+ status: STATUS.SKIP,
574
+ ok: true,
575
+ message: 'No claims.json found (benchmarks/claims.json)',
576
+ remediation: null,
577
+ };
578
+ }
579
+
580
+ try {
581
+ const claimsData = JSON.parse(fs.readFileSync(claimsPath, 'utf8'));
582
+ let missingEvidence = 0;
583
+ const claims = claimsData.claims || [];
584
+ for (const claim of claims) {
585
+ if (claim.evidenceArtifact) {
586
+ const artifactPath = path.join(projectDir, claim.evidenceArtifact);
587
+ if (!fs.existsSync(artifactPath)) {
588
+ missingEvidence++;
589
+ }
590
+ }
591
+ }
592
+
593
+ if (missingEvidence > 0) {
594
+ return {
595
+ id: 'claim_evidence',
596
+ status: STATUS.FAIL,
597
+ ok: false,
598
+ message: `${missingEvidence} claim(s) missing evidence artifact(s)`,
599
+ remediation: 'Run Benchmark v2 protocol to regenerate claim evidence',
600
+ };
601
+ }
602
+
603
+ return {
604
+ id: 'claim_evidence',
605
+ status: STATUS.PASS,
606
+ ok: true,
607
+ message: `All ${claims.length} claim(s) have verified evidence artifacts`,
608
+ remediation: null,
609
+ };
610
+ } catch (e) {
611
+ return {
612
+ id: 'claim_evidence',
613
+ status: STATUS.FAIL,
614
+ ok: false,
615
+ message: `Failed to parse claims.json: ${e.message}`,
616
+ remediation: 'Fix JSON syntax in benchmarks/claims.json',
617
+ };
618
+ }
619
+ }
620
+
621
+ function checkProjectLock(projectDir) {
622
+ try {
623
+ const lock = new ProjectMutationLock(projectDir);
624
+ const payload = lock.inspect();
625
+
626
+ if (payload) {
627
+ const isAlive = isPidAlive(payload.pid);
628
+ if (!isAlive) {
629
+ return {
630
+ id: 'project_lock',
631
+ status: STATUS.WARN,
632
+ ok: true,
633
+ message: `Stale project mutation lock found (PID ${payload.pid} is dead)`,
634
+ remediation: 'Run `contextos doctor --fix` to clear stale locks',
635
+ };
636
+ }
637
+ return {
638
+ id: 'project_lock',
639
+ status: STATUS.WARN,
640
+ ok: true,
641
+ message: `Project mutation lock is currently held (PID ${payload.pid})`,
642
+ remediation: 'Wait for the current operation to finish',
643
+ };
644
+ }
645
+ return {
646
+ id: 'project_lock',
647
+ status: STATUS.PASS,
648
+ ok: true,
649
+ message: 'Project mutation lock is clean',
650
+ remediation: null,
651
+ };
652
+ } catch (e) {
653
+ return {
654
+ id: 'project_lock',
655
+ status: STATUS.UNVERIFIED,
656
+ ok: true,
657
+ message: `Project lock status unverified: ${e.message}`,
658
+ remediation: null,
659
+ };
660
+ }
661
+ }
662
+
663
+ function checkDriftStatus(projectDir) {
664
+ try {
665
+ const { detectDrift } = require('./adapters/drift-detector.js');
666
+ const drift = detectDrift(projectDir);
667
+ if (drift.isDrifted) {
668
+ const summaryParts = Object.entries(drift.counts)
669
+ .filter(([, count]) => count > 0)
670
+ .map(([state, count]) => `${state}: ${count}`);
671
+
672
+ return {
673
+ id: 'drift_status',
674
+ status: STATUS.WARN,
675
+ ok: true,
676
+ isDrifted: true,
677
+ message: `Output drift detected (${summaryParts.join(', ')})`,
678
+ remediation: 'Run `contextos export all` to synchronize generated adapters with current skills',
679
+ drift,
680
+ };
681
+ }
682
+ return {
683
+ id: 'drift_status',
684
+ status: STATUS.PASS,
685
+ ok: true,
686
+ isDrifted: false,
687
+ message: 'All generated adapter files match current source skills and profile',
688
+ remediation: null,
689
+ };
690
+ } catch {
691
+ return {
692
+ id: 'drift_status',
693
+ status: STATUS.UNVERIFIED,
694
+ ok: true,
695
+ message: 'Drift detection unverified',
696
+ remediation: null,
697
+ };
698
+ }
699
+ }
700
+
701
+ function isContextOSDevRepo(projectDir) {
702
+ try {
703
+ const pkgPath = path.join(projectDir, 'package.json');
704
+ if (!fs.existsSync(pkgPath)) return false;
705
+ const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8'));
706
+ return pkg.name === 'contextos-agents' || pkg.name === 'contextos';
707
+ } catch {
708
+ return false;
709
+ }
710
+ }
711
+
712
+ function checkModuleExecution(projectDir) {
713
+ const errors = [];
714
+
715
+ // Check CanonicalResolver
716
+ try {
717
+ const resolverPath = path.join(projectDir, '.agents', 'resolver', 'canonical-resolver.js');
718
+ if (fs.existsSync(resolverPath)) {
719
+ const resolver = require(resolverPath);
720
+ if (!resolver || typeof resolver.CanonicalResolver !== 'function') {
721
+ errors.push('CanonicalResolver is not exported correctly');
722
+ }
723
+ } else {
724
+ errors.push('.agents/resolver/canonical-resolver.js missing');
725
+ }
726
+ } catch (err) {
727
+ errors.push(`CanonicalResolver import failed: ${err.message}`);
728
+ }
729
+
730
+ // Check RuleCatalog
731
+ try {
732
+ const rulesPath = path.join(projectDir, '.agents', 'rules', 'rule-catalog.js');
733
+ if (fs.existsSync(rulesPath)) {
734
+ const rules = require(rulesPath);
735
+ if (!rules || typeof rules.RuleCatalog !== 'function') {
736
+ errors.push('RuleCatalog is not exported correctly');
737
+ }
738
+ } else {
739
+ errors.push('.agents/rules/rule-catalog.js missing');
740
+ }
741
+ } catch (err) {
742
+ errors.push(`RuleCatalog import failed: ${err.message}`);
743
+ }
744
+
745
+ // Check ManifestCompiler
746
+ try {
747
+ const compilerPath = path.join(projectDir, '.agents', 'compiler', 'manifest-compiler.js');
748
+ if (fs.existsSync(compilerPath)) {
749
+ const compiler = require(compilerPath);
750
+ if (!compiler || typeof compiler.ManifestCompiler !== 'function') {
751
+ errors.push('ManifestCompiler is not exported correctly');
752
+ }
753
+ } else {
754
+ errors.push('.agents/compiler/manifest-compiler.js missing');
755
+ }
756
+ } catch (err) {
757
+ errors.push(`ManifestCompiler import failed: ${err.message}`);
758
+ }
759
+
760
+ if (errors.length > 0) {
761
+ return {
762
+ id: 'module_execution',
763
+ status: STATUS.FAIL,
764
+ ok: false,
765
+ message: errors.join('; '),
766
+ errors,
767
+ remediation: 'Reinstall package via `npx contextos-agents init --force` to restore core modules',
768
+ };
769
+ }
770
+
771
+ return {
772
+ id: 'module_execution',
773
+ status: STATUS.PASS,
774
+ ok: true,
775
+ message: 'all core modules verified (resolver, rules, compiler)',
776
+ remediation: null,
777
+ };
207
778
  }
208
779
 
209
780
  function inspectSkills(projectDir, activeProfile) {
@@ -233,16 +804,119 @@ function inspectSkills(projectDir, activeProfile) {
233
804
  };
234
805
  }
235
806
 
807
+ /**
808
+ * Executes doctor --fix safe remediation actions.
809
+ */
810
+ function applyDoctorFix(projectDir = process.cwd(), options = {}) {
811
+ const results = [];
812
+
813
+ // 1. Recompile clean generated adapters
814
+ try {
815
+ const { renderAdapters, applyArtifacts } = require('./adapters/pure-compiler.js');
816
+ const rendered = renderAdapters(projectDir, 'all');
817
+ const applyRes = applyArtifacts(projectDir, rendered.artifacts, { command: 'doctor --fix' });
818
+ results.push(`✓ Recompiled all adapters (${applyRes.appliedCount} files updated, tx: ${applyRes.txId})`);
819
+ } catch (err) {
820
+ results.push(`✗ Could not recompile adapters: ${err.message}`);
821
+ }
822
+
823
+ // 2. Clean completed transaction logs older than 1 hour
824
+ try {
825
+ const txDir = path.join(projectDir, '.agents', '.contextos', 'transactions');
826
+ if (fs.existsSync(txDir)) {
827
+ let cleanedCount = 0;
828
+ const now = Date.now();
829
+ for (const entry of fs.readdirSync(txDir)) {
830
+ const fullEntry = path.join(txDir, entry);
831
+ const journalPath = path.join(fullEntry, 'journal.json');
832
+ if (fs.existsSync(journalPath)) {
833
+ try {
834
+ const journal = JSON.parse(fs.readFileSync(journalPath, 'utf8'));
835
+ if (journal.status === 'COMMITTED' || journal.status === 'ROLLED_BACK') {
836
+ const ageMs = now - (journal.committedAt || journal.startedAt || 0);
837
+ if (ageMs > 3600000) {
838
+ fs.rmSync(fullEntry, { recursive: true, force: true });
839
+ cleanedCount++;
840
+ }
841
+ }
842
+ } catch {}
843
+ }
844
+ }
845
+ if (cleanedCount > 0) {
846
+ results.push(`✓ Pruned ${cleanedCount} completed transaction journal(s)`);
847
+ }
848
+ }
849
+ } catch {}
850
+
851
+ // 3. Clear stale project mutation locks
852
+ try {
853
+ const { ProjectMutationLock, isPidAlive } = require('./filesystem/index.js');
854
+ const lock = new ProjectMutationLock(projectDir);
855
+ const payload = lock.inspect();
856
+ if (payload && !isPidAlive(payload.pid)) {
857
+ if (fs.existsSync(lock.lockPath)) {
858
+ fs.unlinkSync(lock.lockPath);
859
+ results.push(`✓ Pruned stale project mutation lock (PID ${payload.pid})`);
860
+ }
861
+ }
862
+ } catch {}
863
+
864
+ // 4. Clear temporary probe/error files
865
+ try {
866
+ const errLog = path.join(projectDir, '.agents', '.contextos', 'watch-error.json');
867
+ if (fs.existsSync(errLog)) {
868
+ fs.unlinkSync(errLog);
869
+ results.push('✓ Cleared stale watch error log');
870
+ }
871
+ } catch {}
872
+
873
+ return results;
874
+ }
875
+
876
+ /**
877
+ * Main entrypoint for ContextOS Doctor v2.
878
+ */
236
879
  function runDoctor(projectDir = process.cwd(), options = {}) {
237
880
  const version = (() => {
238
881
  try {
239
- return require('../package.json').version;
882
+ const parentPkgPath = path.join(__dirname, '..', 'package.json');
883
+ if (fs.existsSync(parentPkgPath)) {
884
+ const parentPkg = JSON.parse(fs.readFileSync(parentPkgPath, 'utf8'));
885
+ if (parentPkg.name === 'contextos-agents' || parentPkg.name === 'contextos') {
886
+ return parentPkg.version;
887
+ }
888
+ }
889
+ const lockfileV2Path = path.join(projectDir, '.agents', 'lockfile.v2.json');
890
+ if (fs.existsSync(lockfileV2Path)) {
891
+ const lf = JSON.parse(fs.readFileSync(lockfileV2Path, 'utf8'));
892
+ if (lf.package && lf.package.compilerVersion) return lf.package.compilerVersion;
893
+ }
894
+ const lockfilePath = path.join(projectDir, '.agents', 'lockfile.json');
895
+ if (fs.existsSync(lockfilePath)) {
896
+ const lf = JSON.parse(fs.readFileSync(lockfilePath, 'utf8'));
897
+ if (lf.generator && lf.generator.version) return lf.generator.version;
898
+ if (lf.version) return lf.version;
899
+ }
900
+ return '2.0.0';
240
901
  } catch {
241
- return '1.7.0';
902
+ return '2.0.0';
242
903
  }
243
904
  })();
244
905
 
245
906
  const isJson = Boolean(options.json);
907
+
908
+ // If --fix requested, run safe remediation first
909
+ let fixResults = [];
910
+ if (options.fix) {
911
+ fixResults = applyDoctorFix(projectDir, options);
912
+ if (!isJson) {
913
+ console.log('\n[ContextOS Doctor --fix] Running automated remediations:');
914
+ for (const res of fixResults) console.log(` ${res}`);
915
+ console.log('');
916
+ }
917
+ }
918
+
919
+ const checks = [];
246
920
  const errors = [];
247
921
  const warnings = [];
248
922
 
@@ -252,59 +926,135 @@ function runDoctor(projectDir = process.cwd(), options = {}) {
252
926
  errors.push('.agents/ directory missing (run: npx contextos-agents init)');
253
927
  }
254
928
 
255
- const nodeCheck = checkNodeVersion();
256
- if (!nodeCheck.ok) {
257
- errors.push(nodeCheck.message);
929
+ const moduleExecCheck = hasAgents ? checkModuleExecution(projectDir) : {
930
+ id: 'module_execution',
931
+ status: STATUS.FAIL,
932
+ ok: false,
933
+ message: '.agents directory missing',
934
+ remediation: 'Run: npx contextos-agents init',
935
+ };
936
+ checks.push(moduleExecCheck);
937
+ if (!moduleExecCheck.ok) {
938
+ errors.push(moduleExecCheck.message);
258
939
  }
259
940
 
941
+ const nodeCheck = checkNodeVersion();
942
+ checks.push(nodeCheck);
943
+ if (!nodeCheck.ok) errors.push(nodeCheck.message);
944
+ else if (nodeCheck.status === STATUS.WARN) warnings.push(nodeCheck.message);
945
+
946
+ const gitCheck = checkGitVersion();
947
+ checks.push(gitCheck);
948
+ if (!gitCheck.ok) errors.push(gitCheck.message);
949
+
950
+ const fsCheck = checkFilesystemPermissions(projectDir);
951
+ checks.push(fsCheck);
952
+ if (!fsCheck.ok) errors.push(fsCheck.message);
953
+
260
954
  const lockfileCheck = checkLockfileIntegrity(projectDir);
261
- if (!lockfileCheck.ok) {
262
- errors.push(lockfileCheck.error);
263
- } else if (!lockfileCheck.exists && hasAgents) {
264
- warnings.push('Lockfile missing (.agents/contextos.lock.json)');
265
- }
955
+ checks.push(lockfileCheck);
956
+ if (!lockfileCheck.ok) errors.push(lockfileCheck.error || lockfileCheck.message);
957
+ else if (lockfileCheck.status === STATUS.WARN) warnings.push(lockfileCheck.message);
958
+
959
+ const txCheck = checkTransactions(projectDir);
960
+ checks.push(txCheck);
961
+ if (!txCheck.ok) errors.push(txCheck.error);
266
962
 
267
963
  const adapterCheck = checkAdapterIntegrity(projectDir);
268
- if (!adapterCheck.ok) {
269
- errors.push(...adapterCheck.errors);
270
- }
964
+ checks.push(adapterCheck);
965
+ if (!adapterCheck.ok) errors.push(...adapterCheck.errors);
271
966
 
272
967
  const symlinkCheck = checkSymlinks(projectDir);
273
- if (!symlinkCheck.ok) {
274
- errors.push(...symlinkCheck.errors);
275
- }
968
+ checks.push(symlinkCheck);
969
+ if (!symlinkCheck.ok) errors.push(...symlinkCheck.errors);
970
+
971
+ const driftCheck = checkDriftStatus(projectDir);
972
+ checks.push(driftCheck);
973
+ if (driftCheck.status === STATUS.WARN) warnings.push(driftCheck.message);
276
974
 
277
975
  const activeProfile = hasAgents ? profiles.getActiveProfile(projectDir) : null;
278
976
  const skillsInfo = inspectSkills(projectDir, activeProfile);
279
977
  const compiledAdapters = checkCompiledAdapters(projectDir);
280
978
 
281
- const mcpServerPath = path.join(projectDir, '.agents', 'mcp', 'server.mjs');
282
- const hasMcp = fs.existsSync(mcpServerPath);
979
+ const mcpCheck = checkMcpHandshake(projectDir);
980
+ checks.push(mcpCheck);
981
+ if (!mcpCheck.ok) errors.push(mcpCheck.message);
283
982
 
284
983
  const hookCheck = checkPreCommitHook(projectDir);
984
+ checks.push(hookCheck);
985
+ if (hookCheck.status === STATUS.WARN) warnings.push(hookCheck.message);
986
+
285
987
  const secretCheck = checkSecretScanner(projectDir);
988
+ checks.push(secretCheck);
989
+ if (!secretCheck.ok) {
990
+ if (secretCheck.status === STATUS.WARN) warnings.push(secretCheck.message);
991
+ else errors.push(secretCheck.message);
992
+ }
993
+
994
+ const sandboxCheck = checkSandboxAvailability();
995
+ checks.push(sandboxCheck);
996
+ if (sandboxCheck.status === STATUS.WARN) warnings.push(sandboxCheck.message);
997
+
998
+ const claimCheck = checkClaimEvidence(projectDir);
999
+ checks.push(claimCheck);
1000
+ if (!claimCheck.ok) errors.push(claimCheck.message);
1001
+ else if (claimCheck.status === STATUS.SKIP) warnings.push(claimCheck.message);
1002
+
1003
+ const projectLockCheck = checkProjectLock(projectDir);
1004
+ checks.push(projectLockCheck);
1005
+ if (projectLockCheck.status === STATUS.WARN) warnings.push(projectLockCheck.message);
1006
+
286
1007
  const stack = profiles.detectStack(projectDir);
287
1008
 
288
- const ok = errors.length === 0;
1009
+ // Determine overall status
1010
+ let overallStatus = STATUS.PASS;
1011
+ if (txCheck.status === STATUS.RECOVERY_REQUIRED) {
1012
+ overallStatus = STATUS.RECOVERY_REQUIRED;
1013
+ } else if (errors.length > 0) {
1014
+ overallStatus = STATUS.FAIL;
1015
+ } else if (warnings.length > 0) {
1016
+ overallStatus = STATUS.WARN;
1017
+ }
1018
+
1019
+ if (options.strict && warnings.length > 0) {
1020
+ overallStatus = STATUS.FAIL;
1021
+ }
1022
+
1023
+ const ok = overallStatus === STATUS.PASS || overallStatus === STATUS.WARN;
289
1024
 
290
1025
  if (isJson) {
291
1026
  const report = {
1027
+ schemaVersion: '2.0.0',
1028
+ status: overallStatus,
292
1029
  ok,
293
1030
  version,
1031
+ timestamp: new Date().toISOString(),
294
1032
  errors,
295
1033
  warnings,
1034
+ checks,
296
1035
  hasAgents,
297
1036
  nodeOk: nodeCheck.ok,
298
1037
  activeProfile,
299
1038
  skillsInfo,
300
1039
  compiledAdapters,
301
- hasMcp,
1040
+ hasMcp: mcpCheck.ok && mcpCheck.status === STATUS.PASS,
302
1041
  hookCheck,
303
1042
  secretCheck,
304
1043
  lockfileCheck,
305
1044
  adapterCheck,
306
1045
  symlinkCheck,
1046
+ driftCheck,
1047
+ sandboxCheck,
1048
+ claimCheck,
1049
+ projectLockCheck,
307
1050
  stack,
1051
+ summary: {
1052
+ total: checks.length,
1053
+ pass: checks.filter(c => c.status === STATUS.PASS).length,
1054
+ warn: checks.filter(c => c.status === STATUS.WARN).length,
1055
+ fail: checks.filter(c => c.status === STATUS.FAIL).length,
1056
+ recovery: checks.filter(c => c.status === STATUS.RECOVERY_REQUIRED).length,
1057
+ },
308
1058
  };
309
1059
  console.log(JSON.stringify(report, null, 2));
310
1060
  if (options.exitOnError !== false && !ok && require.main === module) {
@@ -314,17 +1064,21 @@ function runDoctor(projectDir = process.cwd(), options = {}) {
314
1064
  }
315
1065
 
316
1066
  console.log('\n┌─────────────────────────────────────────────────────────────┐');
317
- console.log(`│ ContextOS Doctor — Project Health Check v${version.padEnd(16)}│`);
1067
+ console.log(`│ ContextOS Doctor v2 — Project Health Check v${version.padEnd(14)}│`);
318
1068
  console.log('├─────────────────────────────────────────────────────────────┤');
319
1069
  console.log('│ │');
320
1070
 
321
- // .agents directory
322
1071
  if (hasAgents) {
323
1072
  console.log('│ ✓ .agents/ directory found │');
324
1073
  } else {
325
1074
  console.log('│ ✗ .agents/ directory missing (run: npx contextos-agents) │');
326
1075
  }
327
1076
 
1077
+ // Execution check
1078
+ const execIcon = moduleExecCheck.ok ? '✓' : '✗';
1079
+ const execStr = `Core modules: ${moduleExecCheck.message}`;
1080
+ console.log(`│ ${execIcon} ${execStr.slice(0, 58).padEnd(58)}│`);
1081
+
328
1082
  // Node version
329
1083
  const nodeIcon = nodeCheck.ok ? '✓' : '✗';
330
1084
  console.log(`│ ${nodeIcon} ${nodeCheck.message.padEnd(58)}│`);
@@ -365,21 +1119,37 @@ function runDoctor(projectDir = process.cwd(), options = {}) {
365
1119
  console.log(`│ ${symIcon} ${symStr.padEnd(58)}│`);
366
1120
 
367
1121
  // MCP
368
- if (hasMcp) {
1122
+ if (mcpCheck.status === STATUS.PASS) {
369
1123
  console.log('│ ✓ MCP server: installed (.agents/mcp/server.mjs) │');
370
1124
  } else {
371
- console.log('│ • MCP server: not installed (run: contextos setup-mcp) │');
1125
+ console.log('│ • MCP server: not installed (run: npm i -D @contextos/mcp) │');
372
1126
  }
373
1127
 
374
- // Security
1128
+ // Pre-commit hook
375
1129
  const hookIcon = hookCheck.ok ? '✓' : '•';
376
1130
  const hookStr = `Pre-commit hook: ${hookCheck.message}`;
377
1131
  console.log(`│ ${hookIcon} ${hookStr.padEnd(58)}│`);
378
1132
 
379
- const secretIcon = secretCheck.ok ? '✓' : '✗';
1133
+ // Secret scanner
1134
+ const secretIcon = secretCheck.ok ? '✓' : (secretCheck.status === STATUS.WARN ? '•' : '✗');
380
1135
  const secretStr = `Secret scanner: ${secretCheck.message}`;
381
1136
  console.log(`│ ${secretIcon} ${secretStr.padEnd(58)}│`);
382
1137
 
1138
+ // Sandbox
1139
+ const sandboxIcon = sandboxCheck.status === STATUS.PASS ? '✓' : '•';
1140
+ const sandboxStr = `Sandbox: ${sandboxCheck.message}`;
1141
+ console.log(`│ ${sandboxIcon} ${sandboxStr.slice(0, 58).padEnd(58)}│`);
1142
+
1143
+ // Claims Evidence
1144
+ const claimIcon = claimCheck.status === STATUS.PASS ? '✓' : (claimCheck.status === STATUS.SKIP ? '•' : '✗');
1145
+ const claimStr = `Claims evidence: ${claimCheck.message}`;
1146
+ console.log(`│ ${claimIcon} ${claimStr.slice(0, 58).padEnd(58)}│`);
1147
+
1148
+ // Project Mutation Lock
1149
+ const lockStateIcon = projectLockCheck.status === STATUS.PASS ? '✓' : (projectLockCheck.status === STATUS.WARN ? '•' : '✗');
1150
+ const lockStateStr = `Mutation lock: ${projectLockCheck.message}`;
1151
+ console.log(`│ ${lockStateIcon} ${lockStateStr.slice(0, 58).padEnd(58)}│`);
1152
+
383
1153
  console.log('│ │');
384
1154
  console.log('├─────────────────────────────────────────────────────────────┤');
385
1155
  console.log('│ Project Stack & Profile Recommendations │');
@@ -416,20 +1186,27 @@ function runDoctor(projectDir = process.cwd(), options = {}) {
416
1186
  }
417
1187
 
418
1188
  const result = {
1189
+ schemaVersion: '2.0.0',
1190
+ status: overallStatus,
419
1191
  ok,
420
1192
  errors,
421
1193
  warnings,
1194
+ checks,
422
1195
  hasAgents,
423
1196
  nodeOk: nodeCheck.ok,
424
1197
  activeProfile,
425
1198
  skillsInfo,
426
1199
  compiledAdapters,
427
- hasMcp,
1200
+ hasMcp: mcpCheck.ok && mcpCheck.status === STATUS.PASS,
428
1201
  hookCheck,
429
1202
  secretCheck,
430
1203
  lockfileCheck,
431
1204
  adapterCheck,
432
1205
  symlinkCheck,
1206
+ driftCheck,
1207
+ sandboxCheck,
1208
+ claimCheck,
1209
+ projectLockCheck,
433
1210
  stack,
434
1211
  };
435
1212
 
@@ -446,13 +1223,25 @@ if (require.main === module) {
446
1223
  }
447
1224
 
448
1225
  module.exports = {
1226
+ STATUS,
449
1227
  runDoctor,
1228
+ applyDoctorFix,
450
1229
  checkNodeVersion,
1230
+ checkGitVersion,
1231
+ checkFilesystemPermissions,
451
1232
  checkPreCommitHook,
452
1233
  checkSecretScanner,
453
1234
  checkCompiledAdapters,
454
1235
  checkLockfileIntegrity,
1236
+ checkTransactions,
455
1237
  checkAdapterIntegrity,
456
1238
  checkSymlinks,
1239
+ checkMcpHandshake,
1240
+ checkDriftStatus,
1241
+ checkSandboxAvailability,
1242
+ checkClaimEvidence,
1243
+ checkProjectLock,
1244
+ checkModuleExecution,
1245
+ isContextOSDevRepo,
457
1246
  inspectSkills,
458
1247
  };