contextos-agents 1.7.0 → 2.0.0-beta.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (309) hide show
  1. package/.agents/AGENTS.md +1 -1
  2. package/.agents/adapters/aider/export.js +117 -99
  3. package/.agents/adapters/claude/export.js +68 -26
  4. package/.agents/adapters/copilot/export.js +90 -53
  5. package/.agents/adapters/cursor/export.js +80 -101
  6. package/.agents/adapters/drift-detector.js +196 -0
  7. package/.agents/adapters/gemini/export.js +76 -45
  8. package/.agents/adapters/pure-compiler.js +443 -0
  9. package/.agents/adapters/zed/export.js +104 -96
  10. package/.agents/compiled/registry.v2.json +504 -0
  11. package/.agents/compiled/registry.v2.sha256 +1 -0
  12. package/.agents/compiler/manifest-compiler.js +963 -0
  13. package/.agents/compiler/vendor/yaml.LICENSE.txt +13 -0
  14. package/.agents/compiler/vendor/yaml.SBOM.json +6 -0
  15. package/.agents/compiler/vendor/yaml.js +139 -0
  16. package/.agents/core/profiles/init.yaml +25 -0
  17. package/.agents/core/skills/context-manager/skill.yaml +3 -5
  18. package/.agents/core/skills/context-os/SKILL.md +3 -6
  19. package/.agents/core/skills/context-os/skill.yaml +3 -8
  20. package/.agents/core/skills/engineering-workflow/skill.yaml +1 -7
  21. package/.agents/core/skills/gemini-precision/skill.yaml +1 -6
  22. package/.agents/core/skills/gstack-roles/skill.yaml +3 -6
  23. package/.agents/core/skills/ponytail-mindset/skill.yaml +1 -7
  24. package/.agents/core/skills/security/skill.yaml +15 -3
  25. package/.agents/ctx.js +574 -111
  26. package/.agents/customization-dx.js +282 -0
  27. package/.agents/doctor.js +719 -64
  28. package/.agents/filesystem/index.js +71 -0
  29. package/.agents/filesystem/journaled-transaction.js +451 -0
  30. package/.agents/filesystem/lockfile-v2.js +275 -0
  31. package/.agents/filesystem/platform-hardening.js +222 -0
  32. package/.agents/filesystem/project-lock.js +218 -0
  33. package/.agents/filesystem/safe-path.js +256 -0
  34. package/.agents/generated/claude/skills/context-os/SKILL.md +1 -1
  35. package/.agents/generated/gemini/skills/context-os/SKILL.md +2 -2
  36. package/.agents/plugins/contextos/plugin.json +1 -1
  37. package/.agents/plugins.js +259 -60
  38. package/.agents/profiles.js +486 -51
  39. package/.agents/resolver.js +50 -534
  40. package/.agents/schemas/attestation.review.v1.json +111 -0
  41. package/.agents/schemas/attestation.verification.v1.json +85 -0
  42. package/.agents/schemas/lockfile.v2.schema.json +134 -0
  43. package/.agents/schemas/profile.v2.schema.json +114 -0
  44. package/.agents/schemas/runtime.thread.v1.json +192 -0
  45. package/.agents/schemas/skill.manifest.v2.json +177 -0
  46. package/.agents/schemas/verification.spec.v1.json +39 -0
  47. package/.agents/schemas/workspace.graph.schema.json +106 -0
  48. package/.agents/transaction-core/event-store.js +288 -0
  49. package/.agents/transaction-core/idempotency.js +129 -0
  50. package/.agents/transaction-core/ipc-lock.js +311 -0
  51. package/.agents/transaction-core/plugin-supply-chain-bundle.js +436 -0
  52. package/.agents/validate.js +44 -1
  53. package/.agents/watch.js +354 -102
  54. package/.agents/workspace/workspace-graph.js +778 -0
  55. package/README.md +59 -387
  56. package/benchmarks/v2/analysis/statistics.js +140 -0
  57. package/benchmarks/v2/analysis/stats.js +69 -0
  58. package/benchmarks/v2/arms/arm-definitions.js +79 -0
  59. package/benchmarks/v2/dataset.schema.json +34 -0
  60. package/benchmarks/v2/evaluators/index.js +25 -0
  61. package/benchmarks/v2/evaluators/verified-success.js +116 -0
  62. package/benchmarks/v2/harness/runner.js +88 -0
  63. package/benchmarks/v2/pilot-tasks.json +392 -0
  64. package/bin/commands/recover.js +88 -0
  65. package/bin/commands/update.js +80 -17
  66. package/bin/commands.js +62 -25
  67. package/bin/index.js +138 -81
  68. package/bin/lib/lockfile.js +5 -3
  69. package/bin/lib/safe-writer.js +34 -3
  70. package/package.json +85 -72
  71. package/registry.json +2 -2
  72. package/registry.v2.schema.json +86 -0
  73. package/.agents/core/profiles/backend.yaml +0 -47
  74. package/.agents/core/profiles/enterprise.yaml +0 -46
  75. package/.agents/core/profiles/frontend.yaml +0 -46
  76. package/.agents/core/profiles/hackathon.yaml +0 -45
  77. package/.agents/core/profiles/mvp.yaml +0 -44
  78. package/.agents/core/profiles/startup.yaml +0 -48
  79. package/.agents/core/skills/adapters/EXAMPLES.md +0 -19
  80. package/.agents/core/skills/adapters/SKILL.md +0 -105
  81. package/.agents/core/skills/adapters/TROUBLESHOOTING.md +0 -7
  82. package/.agents/core/skills/adapters/VALIDATION.json +0 -12
  83. package/.agents/core/skills/adapters/skill.yaml +0 -16
  84. package/.agents/core/skills/architecture-diagrams/SKILL.md +0 -108
  85. package/.agents/core/skills/architecture-diagrams/VALIDATION.json +0 -12
  86. package/.agents/core/skills/architecture-diagrams/skill.yaml +0 -12
  87. package/.agents/core/skills/brutalist-design/SKILL.md +0 -150
  88. package/.agents/core/skills/brutalist-design/VALIDATION.json +0 -12
  89. package/.agents/core/skills/brutalist-design/skill.yaml +0 -12
  90. package/.agents/core/skills/database/EXAMPLES.md +0 -74
  91. package/.agents/core/skills/database/SKILL.md +0 -101
  92. package/.agents/core/skills/database/TROUBLESHOOTING.md +0 -18
  93. package/.agents/core/skills/database/VALIDATION.json +0 -11
  94. package/.agents/core/skills/database/skill.yaml +0 -31
  95. package/.agents/core/skills/ddd/EXAMPLES.md +0 -42
  96. package/.agents/core/skills/ddd/SKILL.md +0 -247
  97. package/.agents/core/skills/ddd/TROUBLESHOOTING.md +0 -19
  98. package/.agents/core/skills/ddd/VALIDATION.json +0 -12
  99. package/.agents/core/skills/ddd/ddd.md +0 -178
  100. package/.agents/core/skills/ddd/skill.yaml +0 -17
  101. package/.agents/core/skills/decisions/EXAMPLES.md +0 -35
  102. package/.agents/core/skills/decisions/SKILL.md +0 -90
  103. package/.agents/core/skills/decisions/TROUBLESHOOTING.md +0 -13
  104. package/.agents/core/skills/decisions/VALIDATION.json +0 -12
  105. package/.agents/core/skills/decisions/skill.yaml +0 -16
  106. package/.agents/core/skills/docker/EXAMPLES.md +0 -56
  107. package/.agents/core/skills/docker/SKILL.md +0 -63
  108. package/.agents/core/skills/docker/TROUBLESHOOTING.md +0 -18
  109. package/.agents/core/skills/docker/VALIDATION.json +0 -11
  110. package/.agents/core/skills/docker/skill.yaml +0 -29
  111. package/.agents/core/skills/fastapi/EXAMPLES.md +0 -36
  112. package/.agents/core/skills/fastapi/SKILL.md +0 -148
  113. package/.agents/core/skills/fastapi/TROUBLESHOOTING.md +0 -19
  114. package/.agents/core/skills/fastapi/VALIDATION.json +0 -12
  115. package/.agents/core/skills/fastapi/fastapi.md +0 -112
  116. package/.agents/core/skills/fastapi/skill.yaml +0 -17
  117. package/.agents/core/skills/generators/EXAMPLES.md +0 -19
  118. package/.agents/core/skills/generators/SKILL.md +0 -112
  119. package/.agents/core/skills/generators/TROUBLESHOOTING.md +0 -7
  120. package/.agents/core/skills/generators/VALIDATION.json +0 -12
  121. package/.agents/core/skills/generators/skill.yaml +0 -25
  122. package/.agents/core/skills/generators/templates/API.md +0 -77
  123. package/.agents/core/skills/generators/templates/ARCHITECTURE.md +0 -70
  124. package/.agents/core/skills/generators/templates/DATABASE.md +0 -42
  125. package/.agents/core/skills/generators/templates/DECISION.md +0 -46
  126. package/.agents/core/skills/generators/templates/PRD.md +0 -67
  127. package/.agents/core/skills/generators/templates/PROJECT_GRAPH.md +0 -56
  128. package/.agents/core/skills/generators/templates/ROADMAP.md +0 -51
  129. package/.agents/core/skills/generators/templates/TASKS.md +0 -43
  130. package/.agents/core/skills/generators/templates/UI.md +0 -73
  131. package/.agents/core/skills/graphify/EXAMPLES.md +0 -73
  132. package/.agents/core/skills/graphify/SKILL.md +0 -130
  133. package/.agents/core/skills/graphify/VALIDATION.json +0 -12
  134. package/.agents/core/skills/graphify/skill.yaml +0 -18
  135. package/.agents/core/skills/impeccable-design/EXAMPLES.md +0 -26
  136. package/.agents/core/skills/impeccable-design/SKILL.md +0 -201
  137. package/.agents/core/skills/impeccable-design/TROUBLESHOOTING.md +0 -19
  138. package/.agents/core/skills/impeccable-design/VALIDATION.json +0 -12
  139. package/.agents/core/skills/impeccable-design/skill.yaml +0 -20
  140. package/.agents/core/skills/interview-me/SKILL.md +0 -97
  141. package/.agents/core/skills/interview-me/VALIDATION.json +0 -12
  142. package/.agents/core/skills/interview-me/skill.yaml +0 -12
  143. package/.agents/core/skills/microservices/EXAMPLES.md +0 -38
  144. package/.agents/core/skills/microservices/SKILL.md +0 -164
  145. package/.agents/core/skills/microservices/TROUBLESHOOTING.md +0 -19
  146. package/.agents/core/skills/microservices/VALIDATION.json +0 -12
  147. package/.agents/core/skills/microservices/microservices.md +0 -119
  148. package/.agents/core/skills/microservices/skill.yaml +0 -17
  149. package/.agents/core/skills/minimalist-design/SKILL.md +0 -113
  150. package/.agents/core/skills/minimalist-design/VALIDATION.json +0 -12
  151. package/.agents/core/skills/minimalist-design/skill.yaml +0 -12
  152. package/.agents/core/skills/nestjs/EXAMPLES.md +0 -40
  153. package/.agents/core/skills/nestjs/SKILL.md +0 -139
  154. package/.agents/core/skills/nestjs/TROUBLESHOOTING.md +0 -19
  155. package/.agents/core/skills/nestjs/VALIDATION.json +0 -12
  156. package/.agents/core/skills/nestjs/nestjs.md +0 -103
  157. package/.agents/core/skills/nestjs/skill.yaml +0 -17
  158. package/.agents/core/skills/nextjs/EXAMPLES.md +0 -40
  159. package/.agents/core/skills/nextjs/SKILL.md +0 -163
  160. package/.agents/core/skills/nextjs/TROUBLESHOOTING.md +0 -19
  161. package/.agents/core/skills/nextjs/VALIDATION.json +0 -12
  162. package/.agents/core/skills/nextjs/nextjs.md +0 -67
  163. package/.agents/core/skills/nextjs/skill.yaml +0 -17
  164. package/.agents/core/skills/node/EXAMPLES.md +0 -80
  165. package/.agents/core/skills/node/SKILL.md +0 -128
  166. package/.agents/core/skills/node/TROUBLESHOOTING.md +0 -19
  167. package/.agents/core/skills/node/VALIDATION.json +0 -12
  168. package/.agents/core/skills/node/node.md +0 -87
  169. package/.agents/core/skills/node/skill.yaml +0 -17
  170. package/.agents/core/skills/performance/EXAMPLES.md +0 -30
  171. package/.agents/core/skills/performance/SKILL.md +0 -75
  172. package/.agents/core/skills/performance/TROUBLESHOOTING.md +0 -19
  173. package/.agents/core/skills/performance/VALIDATION.json +0 -12
  174. package/.agents/core/skills/performance/performance.md +0 -52
  175. package/.agents/core/skills/performance/skill.yaml +0 -17
  176. package/.agents/core/skills/react/EXAMPLES.md +0 -79
  177. package/.agents/core/skills/react/SKILL.md +0 -132
  178. package/.agents/core/skills/react/TROUBLESHOOTING.md +0 -19
  179. package/.agents/core/skills/react/VALIDATION.json +0 -12
  180. package/.agents/core/skills/react/react.md +0 -93
  181. package/.agents/core/skills/react/skill.yaml +0 -17
  182. package/.agents/core/skills/react-best-practices/SKILL.md +0 -155
  183. package/.agents/core/skills/react-best-practices/VALIDATION.json +0 -12
  184. package/.agents/core/skills/react-best-practices/skill.yaml +0 -14
  185. package/.agents/core/skills/redesign-audit/SKILL.md +0 -117
  186. package/.agents/core/skills/redesign-audit/VALIDATION.json +0 -12
  187. package/.agents/core/skills/redesign-audit/skill.yaml +0 -12
  188. package/.agents/core/skills/soft-design/SKILL.md +0 -108
  189. package/.agents/core/skills/soft-design/VALIDATION.json +0 -12
  190. package/.agents/core/skills/soft-design/skill.yaml +0 -12
  191. package/.agents/core/skills/state-management/EXAMPLES.md +0 -56
  192. package/.agents/core/skills/state-management/SKILL.md +0 -48
  193. package/.agents/core/skills/state-management/TROUBLESHOOTING.md +0 -18
  194. package/.agents/core/skills/state-management/VALIDATION.json +0 -11
  195. package/.agents/core/skills/state-management/skill.yaml +0 -28
  196. package/.agents/core/skills/subagent-orchestrator/SKILL.md +0 -117
  197. package/.agents/core/skills/subagent-orchestrator/VALIDATION.json +0 -12
  198. package/.agents/core/skills/subagent-orchestrator/skill.yaml +0 -12
  199. package/.agents/core/skills/system-design/EXAMPLES.md +0 -75
  200. package/.agents/core/skills/system-design/SKILL.md +0 -419
  201. package/.agents/core/skills/system-design/TROUBLESHOOTING.md +0 -19
  202. package/.agents/core/skills/system-design/VALIDATION.json +0 -12
  203. package/.agents/core/skills/system-design/skill.yaml +0 -20
  204. package/.agents/core/skills/system-design/system-design.md +0 -112
  205. package/.agents/core/skills/testing/EXAMPLES.md +0 -71
  206. package/.agents/core/skills/testing/SKILL.md +0 -70
  207. package/.agents/core/skills/testing/TROUBLESHOOTING.md +0 -18
  208. package/.agents/core/skills/testing/VALIDATION.json +0 -11
  209. package/.agents/core/skills/testing/skill.yaml +0 -32
  210. package/.agents/core/skills/typescript/EXAMPLES.md +0 -64
  211. package/.agents/core/skills/typescript/SKILL.md +0 -112
  212. package/.agents/core/skills/typescript/TROUBLESHOOTING.md +0 -19
  213. package/.agents/core/skills/typescript/VALIDATION.json +0 -12
  214. package/.agents/core/skills/typescript/skill.yaml +0 -17
  215. package/.agents/core/skills/typescript/typescript.md +0 -71
  216. package/.agents/core/skills/ui-design/EXAMPLES.md +0 -21
  217. package/.agents/core/skills/ui-design/SKILL.md +0 -124
  218. package/.agents/core/skills/ui-design/TROUBLESHOOTING.md +0 -19
  219. package/.agents/core/skills/ui-design/VALIDATION.json +0 -12
  220. package/.agents/core/skills/ui-design/skill.yaml +0 -17
  221. package/.agents/core/skills/ui-design/ui.md +0 -88
  222. package/.agents/core/skills/ui-ux-pro/EXAMPLES.md +0 -62
  223. package/.agents/core/skills/ui-ux-pro/SKILL.md +0 -375
  224. package/.agents/core/skills/ui-ux-pro/TROUBLESHOOTING.md +0 -19
  225. package/.agents/core/skills/ui-ux-pro/VALIDATION.json +0 -12
  226. package/.agents/core/skills/ui-ux-pro/skill.yaml +0 -19
  227. package/.agents/core/skills/ux-design/EXAMPLES.md +0 -36
  228. package/.agents/core/skills/ux-design/SKILL.md +0 -116
  229. package/.agents/core/skills/ux-design/TROUBLESHOOTING.md +0 -19
  230. package/.agents/core/skills/ux-design/VALIDATION.json +0 -12
  231. package/.agents/core/skills/ux-design/skill.yaml +0 -17
  232. package/.agents/core/skills/ux-design/ux.md +0 -80
  233. package/.agents/core/skills/vercel-optimize/SKILL.md +0 -83
  234. package/.agents/core/skills/vercel-optimize/VALIDATION.json +0 -12
  235. package/.agents/core/skills/vercel-optimize/scripts/collect-signals.mjs +0 -131
  236. package/.agents/core/skills/vercel-optimize/scripts/gate-investigations.mjs +0 -142
  237. package/.agents/core/skills/vercel-optimize/scripts/merge-signals.mjs +0 -143
  238. package/.agents/core/skills/vercel-optimize/scripts/scan-codebase.mjs +0 -174
  239. package/.agents/core/skills/vercel-optimize/skill.yaml +0 -18
  240. package/.agents/core/skills/web-accessibility/EXAMPLES.md +0 -39
  241. package/.agents/core/skills/web-accessibility/SKILL.md +0 -170
  242. package/.agents/core/skills/web-accessibility/TROUBLESHOOTING.md +0 -19
  243. package/.agents/core/skills/web-accessibility/VALIDATION.json +0 -12
  244. package/.agents/core/skills/web-accessibility/accessibility.md +0 -63
  245. package/.agents/core/skills/web-accessibility/skill.yaml +0 -17
  246. package/.agents/generated/claude/skills/adapters/SKILL.md +0 -126
  247. package/.agents/generated/claude/skills/architecture-diagrams/SKILL.md +0 -101
  248. package/.agents/generated/claude/skills/brutalist-design/SKILL.md +0 -145
  249. package/.agents/generated/claude/skills/database/SKILL.md +0 -191
  250. package/.agents/generated/claude/skills/ddd/SKILL.md +0 -305
  251. package/.agents/generated/claude/skills/decisions/SKILL.md +0 -134
  252. package/.agents/generated/claude/skills/docker/SKILL.md +0 -135
  253. package/.agents/generated/claude/skills/fastapi/SKILL.md +0 -200
  254. package/.agents/generated/claude/skills/generators/SKILL.md +0 -133
  255. package/.agents/generated/claude/skills/graphify/SKILL.md +0 -198
  256. package/.agents/generated/claude/skills/impeccable-design/SKILL.md +0 -241
  257. package/.agents/generated/claude/skills/interview-me/SKILL.md +0 -90
  258. package/.agents/generated/claude/skills/microservices/SKILL.md +0 -218
  259. package/.agents/generated/claude/skills/minimalist-design/SKILL.md +0 -108
  260. package/.agents/generated/claude/skills/nestjs/SKILL.md +0 -195
  261. package/.agents/generated/claude/skills/nextjs/SKILL.md +0 -219
  262. package/.agents/generated/claude/skills/node/SKILL.md +0 -224
  263. package/.agents/generated/claude/skills/performance/SKILL.md +0 -121
  264. package/.agents/generated/claude/skills/react/SKILL.md +0 -227
  265. package/.agents/generated/claude/skills/react-best-practices/SKILL.md +0 -146
  266. package/.agents/generated/claude/skills/redesign-audit/SKILL.md +0 -112
  267. package/.agents/generated/claude/skills/soft-design/SKILL.md +0 -103
  268. package/.agents/generated/claude/skills/state-management/SKILL.md +0 -120
  269. package/.agents/generated/claude/skills/subagent-orchestrator/SKILL.md +0 -110
  270. package/.agents/generated/claude/skills/system-design/SKILL.md +0 -507
  271. package/.agents/generated/claude/skills/testing/SKILL.md +0 -157
  272. package/.agents/generated/claude/skills/typescript/SKILL.md +0 -192
  273. package/.agents/generated/claude/skills/ui-design/SKILL.md +0 -161
  274. package/.agents/generated/claude/skills/ui-ux-pro/SKILL.md +0 -451
  275. package/.agents/generated/claude/skills/ux-design/SKILL.md +0 -168
  276. package/.agents/generated/claude/skills/vercel-optimize/SKILL.md +0 -76
  277. package/.agents/generated/claude/skills/web-accessibility/SKILL.md +0 -225
  278. package/.agents/generated/gemini/skills/adapters/SKILL.md +0 -135
  279. package/.agents/generated/gemini/skills/architecture-diagrams/SKILL.md +0 -107
  280. package/.agents/generated/gemini/skills/brutalist-design/SKILL.md +0 -151
  281. package/.agents/generated/gemini/skills/database/SKILL.md +0 -200
  282. package/.agents/generated/gemini/skills/ddd/SKILL.md +0 -314
  283. package/.agents/generated/gemini/skills/decisions/SKILL.md +0 -143
  284. package/.agents/generated/gemini/skills/docker/SKILL.md +0 -144
  285. package/.agents/generated/gemini/skills/fastapi/SKILL.md +0 -209
  286. package/.agents/generated/gemini/skills/generators/SKILL.md +0 -142
  287. package/.agents/generated/gemini/skills/graphify/SKILL.md +0 -205
  288. package/.agents/generated/gemini/skills/impeccable-design/SKILL.md +0 -250
  289. package/.agents/generated/gemini/skills/interview-me/SKILL.md +0 -96
  290. package/.agents/generated/gemini/skills/microservices/SKILL.md +0 -227
  291. package/.agents/generated/gemini/skills/minimalist-design/SKILL.md +0 -114
  292. package/.agents/generated/gemini/skills/nestjs/SKILL.md +0 -204
  293. package/.agents/generated/gemini/skills/nextjs/SKILL.md +0 -298
  294. package/.agents/generated/gemini/skills/node/SKILL.md +0 -323
  295. package/.agents/generated/gemini/skills/performance/SKILL.md +0 -185
  296. package/.agents/generated/gemini/skills/react/SKILL.md +0 -332
  297. package/.agents/generated/gemini/skills/react-best-practices/SKILL.md +0 -152
  298. package/.agents/generated/gemini/skills/redesign-audit/SKILL.md +0 -118
  299. package/.agents/generated/gemini/skills/soft-design/SKILL.md +0 -109
  300. package/.agents/generated/gemini/skills/state-management/SKILL.md +0 -129
  301. package/.agents/generated/gemini/skills/subagent-orchestrator/SKILL.md +0 -116
  302. package/.agents/generated/gemini/skills/system-design/SKILL.md +0 -631
  303. package/.agents/generated/gemini/skills/testing/SKILL.md +0 -166
  304. package/.agents/generated/gemini/skills/typescript/SKILL.md +0 -275
  305. package/.agents/generated/gemini/skills/ui-design/SKILL.md +0 -170
  306. package/.agents/generated/gemini/skills/ui-ux-pro/SKILL.md +0 -460
  307. package/.agents/generated/gemini/skills/ux-design/SKILL.md +0 -177
  308. package/.agents/generated/gemini/skills/vercel-optimize/SKILL.md +0 -82
  309. package/.agents/generated/gemini/skills/web-accessibility/SKILL.md +0 -300
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,173 @@ 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
+ return {
171
+ id: 'pre_commit_hook',
172
+ status: STATUS.WARN,
173
+ ok: false,
174
+ message: 'not installed (run: npm run setup:hooks)',
175
+ remediation: 'Run `npm run setup:hooks` to install Git pre-commit validation hook',
176
+ };
62
177
  }
63
178
  try {
64
179
  const content = fs.readFileSync(hookPath, 'utf8');
65
180
  if (content.includes('check-secrets')) {
66
- return { ok: true, message: 'installed (with secret check)' };
181
+ return {
182
+ id: 'pre_commit_hook',
183
+ status: STATUS.PASS,
184
+ ok: true,
185
+ message: 'installed (with secret check)',
186
+ remediation: null,
187
+ };
67
188
  }
68
- return { ok: false, message: 'installed but missing check-secrets' };
189
+ return {
190
+ id: 'pre_commit_hook',
191
+ status: STATUS.WARN,
192
+ ok: false,
193
+ message: 'installed but missing check-secrets guard',
194
+ remediation: 'Reinstall hooks via `npm run setup:hooks` to include secret scanning',
195
+ };
69
196
  } catch {
70
- return { ok: false, message: 'unreadable' };
197
+ return {
198
+ id: 'pre_commit_hook',
199
+ status: STATUS.FAIL,
200
+ ok: false,
201
+ message: 'unreadable',
202
+ remediation: 'Check filesystem permissions for .git/hooks/pre-commit',
203
+ };
71
204
  }
72
205
  }
73
206
 
74
207
  function checkSecretScanner(projectDir) {
75
208
  const scriptPath = path.join(projectDir, 'scripts', 'check-secrets.js');
76
209
  if (!fs.existsSync(scriptPath)) {
77
- return { ok: true, message: 'scanner script not present (skipped)' };
210
+ // Per Milestone 8 spec: "doctor не считает отсутствующий scanner PASS"
211
+ return {
212
+ id: 'secret_scanner',
213
+ status: STATUS.WARN,
214
+ ok: false,
215
+ message: 'scanner script not present (scripts/check-secrets.js missing)',
216
+ remediation: 'Install check-secrets.js or configure repository secret scanning',
217
+ };
78
218
  }
79
219
  try {
80
220
  execFileSync(process.execPath, [scriptPath, '--all'], {
81
221
  cwd: projectDir,
82
222
  stdio: ['pipe', 'pipe', 'pipe'],
83
223
  });
84
- return { ok: true, message: '0 findings' };
224
+ return {
225
+ id: 'secret_scanner',
226
+ status: STATUS.PASS,
227
+ ok: true,
228
+ message: '0 findings',
229
+ remediation: null,
230
+ };
85
231
  } catch (err) {
86
- return { ok: false, message: 'potential secrets detected (run: npm run check:secrets)' };
232
+ return {
233
+ id: 'secret_scanner',
234
+ status: STATUS.FAIL,
235
+ ok: false,
236
+ message: 'potential secrets detected in workspace',
237
+ remediation: 'Run `npm run check:secrets` and resolve flagged secrets or add to allowlist',
238
+ };
87
239
  }
88
240
  }
89
241
 
@@ -113,27 +265,136 @@ function checkCompiledAdapters(projectDir) {
113
265
  }
114
266
 
115
267
  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)' };
268
+ const v2Path = path.join(projectDir, '.agents', 'lockfile.v2.json');
269
+ const v1Path = path.join(projectDir, '.agents', 'contextos.lock.json');
270
+ const hasV2 = fs.existsSync(v2Path);
271
+ const hasV1 = fs.existsSync(v1Path);
272
+
273
+ if (!hasV2 && !hasV1) {
274
+ return {
275
+ id: 'lockfile',
276
+ status: STATUS.WARN,
277
+ ok: true,
278
+ exists: false,
279
+ isError: false,
280
+ message: 'none (not generated yet)',
281
+ remediation: 'Run `contextos export all` or `contextos doctor --fix` to generate lockfile',
282
+ };
119
283
  }
284
+
285
+ // 1. Check Lockfile v2 if present
286
+ if (hasV2) {
287
+ try {
288
+ const raw = fs.readFileSync(v2Path, 'utf8');
289
+ const data = JSON.parse(raw);
290
+ const lockfileManager = new LockfileV2Manager(projectDir);
291
+ lockfileManager.validate(data);
292
+ const count = Object.keys(data.managedFiles || {}).length;
293
+ return {
294
+ id: 'lockfile',
295
+ status: STATUS.PASS,
296
+ ok: true,
297
+ exists: true,
298
+ isError: false,
299
+ message: `valid (v${data.schemaVersion}, rev ${data.revision}, ${count} managed files)`,
300
+ remediation: null,
301
+ };
302
+ } catch (err) {
303
+ return {
304
+ id: 'lockfile',
305
+ status: STATUS.FAIL,
306
+ ok: false,
307
+ exists: true,
308
+ isError: true,
309
+ error: `Lockfile v2 is corrupted or invalid: ${err.message}`,
310
+ remediation: 'Run `contextos doctor --fix` to regenerate valid Lockfile v2',
311
+ };
312
+ }
313
+ }
314
+
315
+ // 2. Check Lockfile v1
120
316
  try {
121
- const raw = fs.readFileSync(lockPath, 'utf8');
317
+ const raw = fs.readFileSync(v1Path, 'utf8');
122
318
  const data = JSON.parse(raw);
123
319
  if (!data || typeof data !== 'object') {
124
- return { ok: false, exists: true, isError: true, error: 'Lockfile JSON root is not an object' };
320
+ return {
321
+ id: 'lockfile',
322
+ status: STATUS.FAIL,
323
+ ok: false,
324
+ exists: true,
325
+ isError: true,
326
+ error: 'Lockfile JSON root is not an object',
327
+ remediation: 'Run `contextos doctor --fix` to rebuild lockfile',
328
+ };
125
329
  }
126
330
  if (!data.schemaVersion || typeof data.schemaVersion !== 'number') {
127
- return { ok: false, exists: true, isError: true, error: 'Lockfile missing valid schemaVersion' };
331
+ return {
332
+ id: 'lockfile',
333
+ status: STATUS.FAIL,
334
+ ok: false,
335
+ exists: true,
336
+ isError: true,
337
+ error: 'Lockfile missing valid schemaVersion',
338
+ remediation: 'Run `contextos doctor --fix` to rebuild lockfile',
339
+ };
128
340
  }
129
- if (!data.managedFiles || typeof data.managedFiles !== 'object') {
130
- return { ok: false, exists: true, isError: true, error: 'Lockfile missing managedFiles map' };
341
+ if (!data.managedFiles && !data.files) {
342
+ return {
343
+ id: 'lockfile',
344
+ status: STATUS.FAIL,
345
+ ok: false,
346
+ exists: true,
347
+ isError: true,
348
+ error: 'Lockfile missing managedFiles map',
349
+ remediation: 'Run `contextos doctor --fix` to rebuild lockfile',
350
+ };
131
351
  }
132
- const count = Object.keys(data.managedFiles).length;
133
- return { ok: true, exists: true, isError: false, message: `valid (v${data.schemaVersion}, ${count} managed files)` };
352
+ const count = Object.keys(data.managedFiles || data.files || {}).length;
353
+ return {
354
+ id: 'lockfile',
355
+ status: STATUS.PASS,
356
+ ok: true,
357
+ exists: true,
358
+ isError: false,
359
+ message: `valid (v${data.schemaVersion}, ${count} managed files)`,
360
+ remediation: null,
361
+ };
134
362
  } catch (err) {
135
- return { ok: false, exists: true, isError: true, error: `Lockfile is corrupted JSON: ${err.message}` };
363
+ return {
364
+ id: 'lockfile',
365
+ status: STATUS.FAIL,
366
+ ok: false,
367
+ exists: true,
368
+ isError: true,
369
+ error: `Lockfile is corrupted JSON: ${err.message}`,
370
+ remediation: 'Run `contextos doctor --fix` to rebuild lockfile',
371
+ };
372
+ }
373
+ }
374
+
375
+ function checkTransactions(projectDir) {
376
+ try {
377
+ const pending = JournaledTransaction.listPending(projectDir);
378
+ if (pending.length > 0) {
379
+ return {
380
+ id: 'incomplete_transactions',
381
+ status: STATUS.RECOVERY_REQUIRED,
382
+ ok: false,
383
+ error: `RECOVERY_REQUIRED: Found ${pending.length} incomplete transaction(s).`,
384
+ pending,
385
+ remediation: 'Run: contextos recover --list',
386
+ };
387
+ }
388
+ } catch {
389
+ // Best-effort
136
390
  }
391
+ return {
392
+ id: 'incomplete_transactions',
393
+ status: STATUS.PASS,
394
+ ok: true,
395
+ message: 'Clean (0 uncommitted transaction journals)',
396
+ remediation: null,
397
+ };
137
398
  }
138
399
 
139
400
  function checkAdapterIntegrity(projectDir) {
@@ -149,10 +410,10 @@ function checkAdapterIntegrity(projectDir) {
149
410
  try {
150
411
  const stat = fs.statSync(f);
151
412
  if (stat.size === 0) {
152
- errors.push(`Adapter file ${path.relative(projectDir, f).replace(/\\/g, '/')} is empty (0 bytes)`);
413
+ errors.push(`Adapter file ${toPosix(path.relative(projectDir, f))} is empty (0 bytes)`);
153
414
  }
154
415
  } catch {
155
- errors.push(`Adapter file ${path.relative(projectDir, f).replace(/\\/g, '/')} is unreadable`);
416
+ errors.push(`Adapter file ${toPosix(path.relative(projectDir, f))} is unreadable`);
156
417
  }
157
418
  }
158
419
  }
@@ -170,13 +431,20 @@ function checkAdapterIntegrity(projectDir) {
170
431
  } catch {}
171
432
  }
172
433
 
173
- return { ok: errors.length === 0, errors };
434
+ return {
435
+ id: 'adapter_integrity',
436
+ status: errors.length === 0 ? STATUS.PASS : STATUS.FAIL,
437
+ ok: errors.length === 0,
438
+ errors,
439
+ message: errors.length === 0 ? 'All compiled adapter files are structurally intact' : `${errors.length} corrupt adapter file(s)`,
440
+ remediation: errors.length === 0 ? null : 'Run `contextos export all` to re-render clean adapters',
441
+ };
174
442
  }
175
443
 
176
444
  function checkSymlinks(projectDir) {
177
445
  const errors = [];
178
446
  const targetDir = path.join(projectDir, '.agents');
179
- if (!fs.existsSync(targetDir)) return { ok: true, errors: [] };
447
+ if (!fs.existsSync(targetDir)) return { ok: true, errors: [], status: STATUS.PASS };
180
448
 
181
449
  function scan(current) {
182
450
  let entries;
@@ -191,19 +459,225 @@ function checkSymlinks(projectDir) {
191
459
  const lstat = fs.lstatSync(full);
192
460
  if (lstat.isSymbolicLink()) {
193
461
  if (!fs.existsSync(full)) {
194
- errors.push(`Broken symlink: ${path.relative(projectDir, full).replace(/\\/g, '/')}`);
462
+ errors.push(`Broken symlink: ${toPosix(path.relative(projectDir, full))}`);
195
463
  }
196
464
  } else if (lstat.isDirectory()) {
197
465
  scan(full);
198
466
  }
199
467
  } catch {
200
- errors.push(`Cannot stat file: ${path.relative(projectDir, full).replace(/\\/g, '/')}`);
468
+ errors.push(`Cannot stat file: ${toPosix(path.relative(projectDir, full))}`);
201
469
  }
202
470
  }
203
471
  }
204
472
 
205
473
  scan(targetDir);
206
- return { ok: errors.length === 0, errors };
474
+ return {
475
+ id: 'symlinks',
476
+ status: errors.length === 0 ? STATUS.PASS : STATUS.FAIL,
477
+ ok: errors.length === 0,
478
+ errors,
479
+ message: errors.length === 0 ? '0 broken symlinks' : `${errors.length} broken symlink(s)`,
480
+ remediation: errors.length === 0 ? null : 'Remove or re-point broken symlinks in .agents/',
481
+ };
482
+ }
483
+
484
+ function checkMcpHandshake(projectDir) {
485
+ const mcpServerPath = path.join(projectDir, '.agents', 'mcp', 'server.mjs');
486
+ if (!fs.existsSync(mcpServerPath)) {
487
+ return {
488
+ id: 'mcp_handshake',
489
+ status: STATUS.SKIP,
490
+ ok: true,
491
+ message: 'Optional MCP Bridge not installed.',
492
+ remediation: 'Install separately: npm install --save-dev @contextos/mcp',
493
+ };
494
+ }
495
+
496
+ try {
497
+ const syntaxCheck = execFileSync(process.execPath, ['--check', mcpServerPath], {
498
+ stdio: ['ignore', 'pipe', 'pipe'],
499
+ });
500
+ return {
501
+ id: 'mcp_handshake',
502
+ status: STATUS.PASS,
503
+ ok: true,
504
+ message: 'server.mjs syntax and execution environment verified',
505
+ remediation: null,
506
+ };
507
+ } catch (err) {
508
+ return {
509
+ id: 'mcp_handshake',
510
+ status: STATUS.FAIL,
511
+ ok: false,
512
+ message: `MCP server syntax failure: ${err.message}`,
513
+ remediation: 'Rebuild MCP server bundle via `node scripts/build-mcp.js`',
514
+ };
515
+ }
516
+ }
517
+
518
+ function checkSandboxAvailability() {
519
+ try {
520
+ const stdout = execFileSync('docker', ['--version'], { encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'] });
521
+ return {
522
+ id: 'sandbox_availability',
523
+ status: STATUS.PASS,
524
+ ok: true,
525
+ message: `OCI Sandbox available (${stdout.trim()})`,
526
+ remediation: null,
527
+ };
528
+ } catch (err) {
529
+ try {
530
+ const podmanOut = execFileSync('podman', ['--version'], { encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'] });
531
+ return {
532
+ id: 'sandbox_availability',
533
+ status: STATUS.PASS,
534
+ ok: true,
535
+ message: `OCI Sandbox available (${podmanOut.trim()})`,
536
+ remediation: null,
537
+ };
538
+ } catch {
539
+ return {
540
+ id: 'sandbox_availability',
541
+ status: STATUS.WARN,
542
+ ok: true,
543
+ message: 'OCI Sandbox (Docker/Podman) not found. Executions will fallback to host-unsafe mode.',
544
+ remediation: 'Install Docker or Podman to enable secure isolated execution',
545
+ };
546
+ }
547
+ }
548
+ }
549
+
550
+ function checkClaimEvidence(projectDir) {
551
+ const claimsPath = path.join(projectDir, 'benchmarks', 'claims.json');
552
+ if (!fs.existsSync(claimsPath)) {
553
+ return {
554
+ id: 'claim_evidence',
555
+ status: STATUS.SKIP,
556
+ ok: true,
557
+ message: 'No claims.json found (benchmarks/claims.json)',
558
+ remediation: null,
559
+ };
560
+ }
561
+
562
+ try {
563
+ const claimsData = JSON.parse(fs.readFileSync(claimsPath, 'utf8'));
564
+ let missingEvidence = 0;
565
+ const claims = claimsData.claims || [];
566
+ for (const claim of claims) {
567
+ if (claim.evidenceArtifact) {
568
+ const artifactPath = path.join(projectDir, claim.evidenceArtifact);
569
+ if (!fs.existsSync(artifactPath)) {
570
+ missingEvidence++;
571
+ }
572
+ }
573
+ }
574
+
575
+ if (missingEvidence > 0) {
576
+ return {
577
+ id: 'claim_evidence',
578
+ status: STATUS.FAIL,
579
+ ok: false,
580
+ message: `${missingEvidence} claim(s) missing evidence artifact(s)`,
581
+ remediation: 'Run Benchmark v2 protocol to regenerate claim evidence',
582
+ };
583
+ }
584
+
585
+ return {
586
+ id: 'claim_evidence',
587
+ status: STATUS.PASS,
588
+ ok: true,
589
+ message: `All ${claims.length} claim(s) have verified evidence artifacts`,
590
+ remediation: null,
591
+ };
592
+ } catch (e) {
593
+ return {
594
+ id: 'claim_evidence',
595
+ status: STATUS.FAIL,
596
+ ok: false,
597
+ message: `Failed to parse claims.json: ${e.message}`,
598
+ remediation: 'Fix JSON syntax in benchmarks/claims.json',
599
+ };
600
+ }
601
+ }
602
+
603
+ function checkProjectLock(projectDir) {
604
+ try {
605
+ const lock = new ProjectMutationLock(projectDir);
606
+ const payload = lock.inspect();
607
+
608
+ if (payload) {
609
+ const isAlive = isPidAlive(payload.pid);
610
+ if (!isAlive) {
611
+ return {
612
+ id: 'project_lock',
613
+ status: STATUS.WARN,
614
+ ok: true,
615
+ message: `Stale project mutation lock found (PID ${payload.pid} is dead)`,
616
+ remediation: 'Run `contextos doctor --fix` to clear stale locks',
617
+ };
618
+ }
619
+ return {
620
+ id: 'project_lock',
621
+ status: STATUS.WARN,
622
+ ok: true,
623
+ message: `Project mutation lock is currently held (PID ${payload.pid})`,
624
+ remediation: 'Wait for the current operation to finish',
625
+ };
626
+ }
627
+ return {
628
+ id: 'project_lock',
629
+ status: STATUS.PASS,
630
+ ok: true,
631
+ message: 'Project mutation lock is clean',
632
+ remediation: null,
633
+ };
634
+ } catch (e) {
635
+ return {
636
+ id: 'project_lock',
637
+ status: STATUS.UNVERIFIED,
638
+ ok: true,
639
+ message: `Project lock status unverified: ${e.message}`,
640
+ remediation: null,
641
+ };
642
+ }
643
+ }
644
+
645
+ function checkDriftStatus(projectDir) {
646
+ try {
647
+ const { detectDrift } = require('./adapters/drift-detector.js');
648
+ const drift = detectDrift(projectDir);
649
+ if (drift.isDrifted) {
650
+ const summaryParts = Object.entries(drift.counts)
651
+ .filter(([, count]) => count > 0)
652
+ .map(([state, count]) => `${state}: ${count}`);
653
+
654
+ return {
655
+ id: 'drift_status',
656
+ status: STATUS.WARN,
657
+ ok: true,
658
+ isDrifted: true,
659
+ message: `Output drift detected (${summaryParts.join(', ')})`,
660
+ remediation: 'Run `contextos export all` to synchronize generated adapters with current skills',
661
+ drift,
662
+ };
663
+ }
664
+ return {
665
+ id: 'drift_status',
666
+ status: STATUS.PASS,
667
+ ok: true,
668
+ isDrifted: false,
669
+ message: 'All generated adapter files match current source skills and profile',
670
+ remediation: null,
671
+ };
672
+ } catch {
673
+ return {
674
+ id: 'drift_status',
675
+ status: STATUS.UNVERIFIED,
676
+ ok: true,
677
+ message: 'Drift detection unverified',
678
+ remediation: null,
679
+ };
680
+ }
207
681
  }
208
682
 
209
683
  function inspectSkills(projectDir, activeProfile) {
@@ -233,16 +707,101 @@ function inspectSkills(projectDir, activeProfile) {
233
707
  };
234
708
  }
235
709
 
710
+ /**
711
+ * Executes doctor --fix safe remediation actions.
712
+ */
713
+ function applyDoctorFix(projectDir = process.cwd(), options = {}) {
714
+ const results = [];
715
+
716
+ // 1. Recompile clean generated adapters
717
+ try {
718
+ const { renderAdapters, applyArtifacts } = require('./adapters/pure-compiler.js');
719
+ const rendered = renderAdapters(projectDir, 'all');
720
+ const applyRes = applyArtifacts(projectDir, rendered.artifacts, { command: 'doctor --fix' });
721
+ results.push(`✓ Recompiled all adapters (${applyRes.appliedCount} files updated, tx: ${applyRes.txId})`);
722
+ } catch (err) {
723
+ results.push(`✗ Could not recompile adapters: ${err.message}`);
724
+ }
725
+
726
+ // 2. Clean completed transaction logs older than 1 hour
727
+ try {
728
+ const txDir = path.join(projectDir, '.agents', '.contextos', 'transactions');
729
+ if (fs.existsSync(txDir)) {
730
+ let cleanedCount = 0;
731
+ const now = Date.now();
732
+ for (const entry of fs.readdirSync(txDir)) {
733
+ const fullEntry = path.join(txDir, entry);
734
+ const journalPath = path.join(fullEntry, 'journal.json');
735
+ if (fs.existsSync(journalPath)) {
736
+ try {
737
+ const journal = JSON.parse(fs.readFileSync(journalPath, 'utf8'));
738
+ if (journal.status === 'COMMITTED' || journal.status === 'ROLLED_BACK') {
739
+ const ageMs = now - (journal.committedAt || journal.startedAt || 0);
740
+ if (ageMs > 3600000) {
741
+ fs.rmSync(fullEntry, { recursive: true, force: true });
742
+ cleanedCount++;
743
+ }
744
+ }
745
+ } catch {}
746
+ }
747
+ }
748
+ if (cleanedCount > 0) {
749
+ results.push(`✓ Pruned ${cleanedCount} completed transaction journal(s)`);
750
+ }
751
+ }
752
+ } catch {}
753
+
754
+ // 3. Clear stale project mutation locks
755
+ try {
756
+ const { ProjectMutationLock, isPidAlive } = require('./filesystem/index.js');
757
+ const lock = new ProjectMutationLock(projectDir);
758
+ const payload = lock.inspect();
759
+ if (payload && !isPidAlive(payload.pid)) {
760
+ if (fs.existsSync(lock.lockPath)) {
761
+ fs.unlinkSync(lock.lockPath);
762
+ results.push(`✓ Pruned stale project mutation lock (PID ${payload.pid})`);
763
+ }
764
+ }
765
+ } catch {}
766
+
767
+ // 4. Clear temporary probe/error files
768
+ try {
769
+ const errLog = path.join(projectDir, '.agents', '.contextos', 'watch-error.json');
770
+ if (fs.existsSync(errLog)) {
771
+ fs.unlinkSync(errLog);
772
+ results.push('✓ Cleared stale watch error log');
773
+ }
774
+ } catch {}
775
+
776
+ return results;
777
+ }
778
+
779
+ /**
780
+ * Main entrypoint for ContextOS Doctor v2.
781
+ */
236
782
  function runDoctor(projectDir = process.cwd(), options = {}) {
237
783
  const version = (() => {
238
784
  try {
239
785
  return require('../package.json').version;
240
786
  } catch {
241
- return '1.7.0';
787
+ return '1.7.1';
242
788
  }
243
789
  })();
244
790
 
245
791
  const isJson = Boolean(options.json);
792
+
793
+ // If --fix requested, run safe remediation first
794
+ let fixResults = [];
795
+ if (options.fix) {
796
+ fixResults = applyDoctorFix(projectDir, options);
797
+ if (!isJson) {
798
+ console.log('\n[ContextOS Doctor --fix] Running automated remediations:');
799
+ for (const res of fixResults) console.log(` ${res}`);
800
+ console.log('');
801
+ }
802
+ }
803
+
804
+ const checks = [];
246
805
  const errors = [];
247
806
  const warnings = [];
248
807
 
@@ -253,58 +812,122 @@ function runDoctor(projectDir = process.cwd(), options = {}) {
253
812
  }
254
813
 
255
814
  const nodeCheck = checkNodeVersion();
256
- if (!nodeCheck.ok) {
257
- errors.push(nodeCheck.message);
258
- }
815
+ checks.push(nodeCheck);
816
+ if (!nodeCheck.ok) errors.push(nodeCheck.message);
817
+ else if (nodeCheck.status === STATUS.WARN) warnings.push(nodeCheck.message);
818
+
819
+ const gitCheck = checkGitVersion();
820
+ checks.push(gitCheck);
821
+ if (!gitCheck.ok) errors.push(gitCheck.message);
822
+
823
+ const fsCheck = checkFilesystemPermissions(projectDir);
824
+ checks.push(fsCheck);
825
+ if (!fsCheck.ok) errors.push(fsCheck.message);
259
826
 
260
827
  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
- }
828
+ checks.push(lockfileCheck);
829
+ if (!lockfileCheck.ok) errors.push(lockfileCheck.error || lockfileCheck.message);
830
+ else if (lockfileCheck.status === STATUS.WARN) warnings.push(lockfileCheck.message);
831
+
832
+ const txCheck = checkTransactions(projectDir);
833
+ checks.push(txCheck);
834
+ if (!txCheck.ok) errors.push(txCheck.error);
266
835
 
267
836
  const adapterCheck = checkAdapterIntegrity(projectDir);
268
- if (!adapterCheck.ok) {
269
- errors.push(...adapterCheck.errors);
270
- }
837
+ checks.push(adapterCheck);
838
+ if (!adapterCheck.ok) errors.push(...adapterCheck.errors);
271
839
 
272
840
  const symlinkCheck = checkSymlinks(projectDir);
273
- if (!symlinkCheck.ok) {
274
- errors.push(...symlinkCheck.errors);
275
- }
841
+ checks.push(symlinkCheck);
842
+ if (!symlinkCheck.ok) errors.push(...symlinkCheck.errors);
843
+
844
+ const driftCheck = checkDriftStatus(projectDir);
845
+ checks.push(driftCheck);
846
+ if (driftCheck.status === STATUS.WARN) warnings.push(driftCheck.message);
276
847
 
277
848
  const activeProfile = hasAgents ? profiles.getActiveProfile(projectDir) : null;
278
849
  const skillsInfo = inspectSkills(projectDir, activeProfile);
279
850
  const compiledAdapters = checkCompiledAdapters(projectDir);
280
851
 
281
- const mcpServerPath = path.join(projectDir, '.agents', 'mcp', 'server.mjs');
282
- const hasMcp = fs.existsSync(mcpServerPath);
852
+ const mcpCheck = checkMcpHandshake(projectDir);
853
+ checks.push(mcpCheck);
854
+ if (!mcpCheck.ok) errors.push(mcpCheck.message);
283
855
 
284
856
  const hookCheck = checkPreCommitHook(projectDir);
857
+ checks.push(hookCheck);
858
+ if (hookCheck.status === STATUS.WARN) warnings.push(hookCheck.message);
859
+
285
860
  const secretCheck = checkSecretScanner(projectDir);
861
+ checks.push(secretCheck);
862
+ if (!secretCheck.ok) {
863
+ if (secretCheck.status === STATUS.WARN) warnings.push(secretCheck.message);
864
+ else errors.push(secretCheck.message);
865
+ }
866
+
867
+ const sandboxCheck = checkSandboxAvailability();
868
+ checks.push(sandboxCheck);
869
+ if (sandboxCheck.status === STATUS.WARN) warnings.push(sandboxCheck.message);
870
+
871
+ const claimCheck = checkClaimEvidence(projectDir);
872
+ checks.push(claimCheck);
873
+ if (!claimCheck.ok) errors.push(claimCheck.message);
874
+ else if (claimCheck.status === STATUS.SKIP) warnings.push(claimCheck.message);
875
+
876
+ const projectLockCheck = checkProjectLock(projectDir);
877
+ checks.push(projectLockCheck);
878
+ if (projectLockCheck.status === STATUS.WARN) warnings.push(projectLockCheck.message);
879
+
286
880
  const stack = profiles.detectStack(projectDir);
287
881
 
288
- const ok = errors.length === 0;
882
+ // Determine overall status
883
+ let overallStatus = STATUS.PASS;
884
+ if (txCheck.status === STATUS.RECOVERY_REQUIRED) {
885
+ overallStatus = STATUS.RECOVERY_REQUIRED;
886
+ } else if (errors.length > 0) {
887
+ overallStatus = STATUS.FAIL;
888
+ } else if (warnings.length > 0) {
889
+ overallStatus = STATUS.WARN;
890
+ }
891
+
892
+ if (options.strict && warnings.length > 0) {
893
+ overallStatus = STATUS.FAIL;
894
+ }
895
+
896
+ const ok = overallStatus === STATUS.PASS || overallStatus === STATUS.WARN;
289
897
 
290
898
  if (isJson) {
291
899
  const report = {
900
+ schemaVersion: '2.0.0',
901
+ status: overallStatus,
292
902
  ok,
293
903
  version,
904
+ timestamp: new Date().toISOString(),
294
905
  errors,
295
906
  warnings,
907
+ checks,
296
908
  hasAgents,
297
909
  nodeOk: nodeCheck.ok,
298
910
  activeProfile,
299
911
  skillsInfo,
300
912
  compiledAdapters,
301
- hasMcp,
913
+ hasMcp: mcpCheck.ok && mcpCheck.status === STATUS.PASS,
302
914
  hookCheck,
303
915
  secretCheck,
304
916
  lockfileCheck,
305
917
  adapterCheck,
306
918
  symlinkCheck,
919
+ driftCheck,
920
+ sandboxCheck,
921
+ claimCheck,
922
+ projectLockCheck,
307
923
  stack,
924
+ summary: {
925
+ total: checks.length,
926
+ pass: checks.filter(c => c.status === STATUS.PASS).length,
927
+ warn: checks.filter(c => c.status === STATUS.WARN).length,
928
+ fail: checks.filter(c => c.status === STATUS.FAIL).length,
929
+ recovery: checks.filter(c => c.status === STATUS.RECOVERY_REQUIRED).length,
930
+ },
308
931
  };
309
932
  console.log(JSON.stringify(report, null, 2));
310
933
  if (options.exitOnError !== false && !ok && require.main === module) {
@@ -314,11 +937,10 @@ function runDoctor(projectDir = process.cwd(), options = {}) {
314
937
  }
315
938
 
316
939
  console.log('\n┌─────────────────────────────────────────────────────────────┐');
317
- console.log(`│ ContextOS Doctor — Project Health Check v${version.padEnd(16)}│`);
940
+ console.log(`│ ContextOS Doctor v2 — Project Health Check v${version.padEnd(14)}│`);
318
941
  console.log('├─────────────────────────────────────────────────────────────┤');
319
942
  console.log('│ │');
320
943
 
321
- // .agents directory
322
944
  if (hasAgents) {
323
945
  console.log('│ ✓ .agents/ directory found │');
324
946
  } else {
@@ -365,21 +987,37 @@ function runDoctor(projectDir = process.cwd(), options = {}) {
365
987
  console.log(`│ ${symIcon} ${symStr.padEnd(58)}│`);
366
988
 
367
989
  // MCP
368
- if (hasMcp) {
990
+ if (mcpCheck.status === STATUS.PASS) {
369
991
  console.log('│ ✓ MCP server: installed (.agents/mcp/server.mjs) │');
370
992
  } else {
371
993
  console.log('│ • MCP server: not installed (run: contextos setup-mcp) │');
372
994
  }
373
995
 
374
- // Security
996
+ // Pre-commit hook
375
997
  const hookIcon = hookCheck.ok ? '✓' : '•';
376
998
  const hookStr = `Pre-commit hook: ${hookCheck.message}`;
377
999
  console.log(`│ ${hookIcon} ${hookStr.padEnd(58)}│`);
378
1000
 
379
- const secretIcon = secretCheck.ok ? '✓' : '✗';
1001
+ // Secret scanner
1002
+ const secretIcon = secretCheck.ok ? '✓' : (secretCheck.status === STATUS.WARN ? '•' : '✗');
380
1003
  const secretStr = `Secret scanner: ${secretCheck.message}`;
381
1004
  console.log(`│ ${secretIcon} ${secretStr.padEnd(58)}│`);
382
1005
 
1006
+ // Sandbox
1007
+ const sandboxIcon = sandboxCheck.status === STATUS.PASS ? '✓' : '•';
1008
+ const sandboxStr = `Sandbox: ${sandboxCheck.message}`;
1009
+ console.log(`│ ${sandboxIcon} ${sandboxStr.slice(0, 58).padEnd(58)}│`);
1010
+
1011
+ // Claims Evidence
1012
+ const claimIcon = claimCheck.status === STATUS.PASS ? '✓' : (claimCheck.status === STATUS.SKIP ? '•' : '✗');
1013
+ const claimStr = `Claims evidence: ${claimCheck.message}`;
1014
+ console.log(`│ ${claimIcon} ${claimStr.slice(0, 58).padEnd(58)}│`);
1015
+
1016
+ // Project Mutation Lock
1017
+ const lockStateIcon = projectLockCheck.status === STATUS.PASS ? '✓' : (projectLockCheck.status === STATUS.WARN ? '•' : '✗');
1018
+ const lockStateStr = `Mutation lock: ${projectLockCheck.message}`;
1019
+ console.log(`│ ${lockStateIcon} ${lockStateStr.slice(0, 58).padEnd(58)}│`);
1020
+
383
1021
  console.log('│ │');
384
1022
  console.log('├─────────────────────────────────────────────────────────────┤');
385
1023
  console.log('│ Project Stack & Profile Recommendations │');
@@ -416,20 +1054,27 @@ function runDoctor(projectDir = process.cwd(), options = {}) {
416
1054
  }
417
1055
 
418
1056
  const result = {
1057
+ schemaVersion: '2.0.0',
1058
+ status: overallStatus,
419
1059
  ok,
420
1060
  errors,
421
1061
  warnings,
1062
+ checks,
422
1063
  hasAgents,
423
1064
  nodeOk: nodeCheck.ok,
424
1065
  activeProfile,
425
1066
  skillsInfo,
426
1067
  compiledAdapters,
427
- hasMcp,
1068
+ hasMcp: mcpCheck.ok && mcpCheck.status === STATUS.PASS,
428
1069
  hookCheck,
429
1070
  secretCheck,
430
1071
  lockfileCheck,
431
1072
  adapterCheck,
432
1073
  symlinkCheck,
1074
+ driftCheck,
1075
+ sandboxCheck,
1076
+ claimCheck,
1077
+ projectLockCheck,
433
1078
  stack,
434
1079
  };
435
1080
 
@@ -446,13 +1091,23 @@ if (require.main === module) {
446
1091
  }
447
1092
 
448
1093
  module.exports = {
1094
+ STATUS,
449
1095
  runDoctor,
1096
+ applyDoctorFix,
450
1097
  checkNodeVersion,
1098
+ checkGitVersion,
1099
+ checkFilesystemPermissions,
451
1100
  checkPreCommitHook,
452
1101
  checkSecretScanner,
453
1102
  checkCompiledAdapters,
454
1103
  checkLockfileIntegrity,
1104
+ checkTransactions,
455
1105
  checkAdapterIntegrity,
456
1106
  checkSymlinks,
1107
+ checkMcpHandshake,
1108
+ checkDriftStatus,
1109
+ checkSandboxAvailability,
1110
+ checkClaimEvidence,
1111
+ checkProjectLock,
457
1112
  inspectSkills,
458
1113
  };