contextos-agents 1.6.1 → 2.0.0-beta.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.agents/AGENTS.md +6 -1
- package/.agents/adapters/aider/export.js +117 -97
- package/.agents/adapters/claude/export.js +68 -26
- package/.agents/adapters/copilot/export.js +90 -51
- package/.agents/adapters/cursor/export.js +83 -68
- package/.agents/adapters/drift-detector.js +196 -0
- package/.agents/adapters/gemini/export.js +76 -45
- package/.agents/adapters/pure-compiler.js +443 -0
- package/.agents/adapters/zed/export.js +109 -62
- package/.agents/compiled/registry.v2.json +504 -0
- package/.agents/compiled/registry.v2.sha256 +1 -0
- package/.agents/compiler/manifest-compiler.js +963 -0
- package/.agents/compiler/vendor/yaml.LICENSE.txt +13 -0
- package/.agents/compiler/vendor/yaml.SBOM.json +6 -0
- package/.agents/compiler/vendor/yaml.js +139 -0
- package/.agents/core/profiles/init.yaml +25 -0
- package/.agents/core/skills/context-manager/references/context-rules.md +59 -0
- package/.agents/core/skills/context-manager/skill.yaml +10 -5
- package/.agents/core/skills/context-os/SKILL.md +3 -6
- package/.agents/core/skills/context-os/skill.yaml +14 -8
- package/.agents/core/skills/engineering-workflow/SKILL.md +1 -1
- package/.agents/core/skills/engineering-workflow/skill.yaml +7 -7
- package/.agents/core/skills/gemini-precision/SKILL.md +4 -0
- package/.agents/core/skills/gemini-precision/skill.yaml +5 -6
- package/.agents/core/skills/gstack-roles/SKILL.md +3 -1
- package/.agents/core/skills/gstack-roles/skill.yaml +9 -6
- package/.agents/core/skills/ponytail-mindset/skill.yaml +7 -7
- package/.agents/core/skills/security/skill.yaml +21 -2
- package/.agents/ctx.js +587 -111
- package/.agents/customization-dx.js +282 -0
- package/.agents/doctor.js +877 -33
- package/.agents/filesystem/index.js +71 -0
- package/.agents/filesystem/journaled-transaction.js +451 -0
- package/.agents/filesystem/lockfile-v2.js +275 -0
- package/.agents/filesystem/platform-hardening.js +222 -0
- package/.agents/filesystem/project-lock.js +218 -0
- package/.agents/filesystem/safe-path.js +256 -0
- package/.agents/generated/claude/skills/context-os/SKILL.md +1 -1
- package/.agents/generated/claude/skills/engineering-workflow/SKILL.md +1 -1
- package/.agents/generated/claude/skills/gemini-precision/SKILL.md +4 -0
- package/.agents/generated/claude/skills/gstack-roles/SKILL.md +3 -1
- package/.agents/generated/gemini/skills/context-os/SKILL.md +2 -2
- package/.agents/generated/gemini/skills/engineering-workflow/SKILL.md +1 -1
- package/.agents/generated/gemini/skills/gemini-precision/SKILL.md +4 -0
- package/.agents/generated/gemini/skills/gstack-roles/SKILL.md +3 -1
- package/.agents/plugins/contextos/hooks.json +25 -0
- package/.agents/plugins/contextos/plugin.json +19 -0
- package/.agents/plugins.js +432 -73
- package/.agents/profiles.js +507 -46
- package/.agents/resolver.js +50 -414
- package/.agents/schemas/attestation.review.v1.json +111 -0
- package/.agents/schemas/attestation.verification.v1.json +85 -0
- package/.agents/schemas/lockfile.v2.schema.json +134 -0
- package/.agents/schemas/profile.v2.schema.json +114 -0
- package/.agents/schemas/runtime.thread.v1.json +192 -0
- package/.agents/schemas/skill.manifest.v2.json +177 -0
- package/.agents/schemas/verification.spec.v1.json +39 -0
- package/.agents/schemas/workspace.graph.schema.json +106 -0
- package/.agents/stats.js +22 -9
- package/.agents/transaction-core/event-store.js +288 -0
- package/.agents/transaction-core/idempotency.js +129 -0
- package/.agents/transaction-core/ipc-lock.js +311 -0
- package/.agents/transaction-core/plugin-supply-chain-bundle.js +436 -0
- package/.agents/validate.js +143 -14
- package/.agents/watch.js +354 -102
- package/.agents/workspace/workspace-graph.js +778 -0
- package/README.md +59 -387
- package/benchmarks/v2/analysis/statistics.js +140 -0
- package/benchmarks/v2/analysis/stats.js +69 -0
- package/benchmarks/v2/arms/arm-definitions.js +79 -0
- package/benchmarks/v2/dataset.schema.json +34 -0
- package/benchmarks/v2/evaluators/index.js +25 -0
- package/benchmarks/v2/evaluators/verified-success.js +116 -0
- package/benchmarks/v2/harness/runner.js +88 -0
- package/benchmarks/v2/pilot-tasks.json +392 -0
- package/bin/commands/recover.js +88 -0
- package/bin/commands/uninstall.js +207 -0
- package/bin/commands/update.js +325 -0
- package/bin/commands.js +342 -0
- package/bin/index.js +326 -149
- package/bin/lib/detector.js +106 -0
- package/bin/lib/lockfile.js +253 -0
- package/bin/lib/safe-writer.js +290 -0
- package/package.json +85 -73
- package/registry.json +15 -7
- package/registry.schema.json +3 -1
- package/registry.v2.schema.json +86 -0
- package/.agents/core/profiles/backend.yaml +0 -47
- package/.agents/core/profiles/enterprise.yaml +0 -46
- package/.agents/core/profiles/frontend.yaml +0 -46
- package/.agents/core/profiles/hackathon.yaml +0 -45
- package/.agents/core/profiles/mvp.yaml +0 -44
- package/.agents/core/profiles/startup.yaml +0 -48
- package/.agents/core/skills/adapters/EXAMPLES.md +0 -19
- package/.agents/core/skills/adapters/SKILL.md +0 -105
- package/.agents/core/skills/adapters/TROUBLESHOOTING.md +0 -7
- package/.agents/core/skills/adapters/VALIDATION.json +0 -12
- package/.agents/core/skills/adapters/skill.yaml +0 -10
- package/.agents/core/skills/architecture-diagrams/SKILL.md +0 -108
- package/.agents/core/skills/architecture-diagrams/VALIDATION.json +0 -12
- package/.agents/core/skills/architecture-diagrams/skill.yaml +0 -8
- package/.agents/core/skills/brutalist-design/SKILL.md +0 -150
- package/.agents/core/skills/brutalist-design/VALIDATION.json +0 -12
- package/.agents/core/skills/brutalist-design/skill.yaml +0 -8
- package/.agents/core/skills/database/EXAMPLES.md +0 -74
- package/.agents/core/skills/database/SKILL.md +0 -101
- package/.agents/core/skills/database/TROUBLESHOOTING.md +0 -18
- package/.agents/core/skills/database/VALIDATION.json +0 -11
- package/.agents/core/skills/database/skill.yaml +0 -25
- package/.agents/core/skills/ddd/EXAMPLES.md +0 -42
- package/.agents/core/skills/ddd/SKILL.md +0 -247
- package/.agents/core/skills/ddd/TROUBLESHOOTING.md +0 -19
- package/.agents/core/skills/ddd/VALIDATION.json +0 -12
- package/.agents/core/skills/ddd/ddd.md +0 -178
- package/.agents/core/skills/ddd/skill.yaml +0 -10
- package/.agents/core/skills/decisions/EXAMPLES.md +0 -35
- package/.agents/core/skills/decisions/SKILL.md +0 -90
- package/.agents/core/skills/decisions/TROUBLESHOOTING.md +0 -13
- package/.agents/core/skills/decisions/VALIDATION.json +0 -12
- package/.agents/core/skills/decisions/skill.yaml +0 -10
- package/.agents/core/skills/docker/EXAMPLES.md +0 -56
- package/.agents/core/skills/docker/SKILL.md +0 -63
- package/.agents/core/skills/docker/TROUBLESHOOTING.md +0 -18
- package/.agents/core/skills/docker/VALIDATION.json +0 -11
- package/.agents/core/skills/docker/skill.yaml +0 -23
- package/.agents/core/skills/fastapi/EXAMPLES.md +0 -36
- package/.agents/core/skills/fastapi/SKILL.md +0 -148
- package/.agents/core/skills/fastapi/TROUBLESHOOTING.md +0 -19
- package/.agents/core/skills/fastapi/VALIDATION.json +0 -12
- package/.agents/core/skills/fastapi/fastapi.md +0 -112
- package/.agents/core/skills/fastapi/skill.yaml +0 -10
- package/.agents/core/skills/generators/EXAMPLES.md +0 -19
- package/.agents/core/skills/generators/SKILL.md +0 -112
- package/.agents/core/skills/generators/TROUBLESHOOTING.md +0 -7
- package/.agents/core/skills/generators/VALIDATION.json +0 -12
- package/.agents/core/skills/generators/skill.yaml +0 -10
- package/.agents/core/skills/generators/templates/API.md +0 -77
- package/.agents/core/skills/generators/templates/ARCHITECTURE.md +0 -70
- package/.agents/core/skills/generators/templates/DATABASE.md +0 -42
- package/.agents/core/skills/generators/templates/DECISION.md +0 -46
- package/.agents/core/skills/generators/templates/PRD.md +0 -67
- package/.agents/core/skills/generators/templates/PROJECT_GRAPH.md +0 -56
- package/.agents/core/skills/generators/templates/ROADMAP.md +0 -51
- package/.agents/core/skills/generators/templates/TASKS.md +0 -43
- package/.agents/core/skills/generators/templates/UI.md +0 -73
- package/.agents/core/skills/graphify/EXAMPLES.md +0 -73
- package/.agents/core/skills/graphify/SKILL.md +0 -130
- package/.agents/core/skills/graphify/VALIDATION.json +0 -12
- package/.agents/core/skills/graphify/skill.yaml +0 -13
- package/.agents/core/skills/impeccable-design/EXAMPLES.md +0 -26
- package/.agents/core/skills/impeccable-design/SKILL.md +0 -201
- package/.agents/core/skills/impeccable-design/TROUBLESHOOTING.md +0 -19
- package/.agents/core/skills/impeccable-design/VALIDATION.json +0 -12
- package/.agents/core/skills/impeccable-design/skill.yaml +0 -14
- package/.agents/core/skills/interview-me/SKILL.md +0 -97
- package/.agents/core/skills/interview-me/VALIDATION.json +0 -12
- package/.agents/core/skills/interview-me/skill.yaml +0 -8
- package/.agents/core/skills/microservices/EXAMPLES.md +0 -38
- package/.agents/core/skills/microservices/SKILL.md +0 -164
- package/.agents/core/skills/microservices/TROUBLESHOOTING.md +0 -19
- package/.agents/core/skills/microservices/VALIDATION.json +0 -12
- package/.agents/core/skills/microservices/microservices.md +0 -119
- package/.agents/core/skills/microservices/skill.yaml +0 -10
- package/.agents/core/skills/minimalist-design/SKILL.md +0 -113
- package/.agents/core/skills/minimalist-design/VALIDATION.json +0 -12
- package/.agents/core/skills/minimalist-design/skill.yaml +0 -8
- package/.agents/core/skills/nestjs/EXAMPLES.md +0 -40
- package/.agents/core/skills/nestjs/SKILL.md +0 -139
- package/.agents/core/skills/nestjs/TROUBLESHOOTING.md +0 -19
- package/.agents/core/skills/nestjs/VALIDATION.json +0 -12
- package/.agents/core/skills/nestjs/nestjs.md +0 -103
- package/.agents/core/skills/nestjs/skill.yaml +0 -10
- package/.agents/core/skills/nextjs/EXAMPLES.md +0 -40
- package/.agents/core/skills/nextjs/SKILL.md +0 -163
- package/.agents/core/skills/nextjs/TROUBLESHOOTING.md +0 -19
- package/.agents/core/skills/nextjs/VALIDATION.json +0 -12
- package/.agents/core/skills/nextjs/nextjs.md +0 -67
- package/.agents/core/skills/nextjs/skill.yaml +0 -10
- package/.agents/core/skills/node/EXAMPLES.md +0 -80
- package/.agents/core/skills/node/SKILL.md +0 -128
- package/.agents/core/skills/node/TROUBLESHOOTING.md +0 -19
- package/.agents/core/skills/node/VALIDATION.json +0 -12
- package/.agents/core/skills/node/node.md +0 -87
- package/.agents/core/skills/node/skill.yaml +0 -10
- package/.agents/core/skills/performance/EXAMPLES.md +0 -30
- package/.agents/core/skills/performance/SKILL.md +0 -75
- package/.agents/core/skills/performance/TROUBLESHOOTING.md +0 -19
- package/.agents/core/skills/performance/VALIDATION.json +0 -12
- package/.agents/core/skills/performance/performance.md +0 -52
- package/.agents/core/skills/performance/skill.yaml +0 -10
- package/.agents/core/skills/react/EXAMPLES.md +0 -79
- package/.agents/core/skills/react/SKILL.md +0 -132
- package/.agents/core/skills/react/TROUBLESHOOTING.md +0 -19
- package/.agents/core/skills/react/VALIDATION.json +0 -12
- package/.agents/core/skills/react/react.md +0 -93
- package/.agents/core/skills/react/skill.yaml +0 -10
- package/.agents/core/skills/react-best-practices/SKILL.md +0 -155
- package/.agents/core/skills/react-best-practices/VALIDATION.json +0 -12
- package/.agents/core/skills/react-best-practices/skill.yaml +0 -10
- package/.agents/core/skills/redesign-audit/SKILL.md +0 -117
- package/.agents/core/skills/redesign-audit/VALIDATION.json +0 -12
- package/.agents/core/skills/redesign-audit/skill.yaml +0 -8
- package/.agents/core/skills/soft-design/SKILL.md +0 -108
- package/.agents/core/skills/soft-design/VALIDATION.json +0 -12
- package/.agents/core/skills/soft-design/skill.yaml +0 -8
- package/.agents/core/skills/state-management/EXAMPLES.md +0 -56
- package/.agents/core/skills/state-management/SKILL.md +0 -48
- package/.agents/core/skills/state-management/TROUBLESHOOTING.md +0 -18
- package/.agents/core/skills/state-management/VALIDATION.json +0 -11
- package/.agents/core/skills/state-management/skill.yaml +0 -22
- package/.agents/core/skills/subagent-orchestrator/SKILL.md +0 -100
- package/.agents/core/skills/subagent-orchestrator/VALIDATION.json +0 -12
- package/.agents/core/skills/subagent-orchestrator/skill.yaml +0 -8
- package/.agents/core/skills/system-design/EXAMPLES.md +0 -75
- package/.agents/core/skills/system-design/SKILL.md +0 -419
- package/.agents/core/skills/system-design/TROUBLESHOOTING.md +0 -19
- package/.agents/core/skills/system-design/VALIDATION.json +0 -12
- package/.agents/core/skills/system-design/skill.yaml +0 -13
- package/.agents/core/skills/system-design/system-design.md +0 -112
- package/.agents/core/skills/testing/EXAMPLES.md +0 -71
- package/.agents/core/skills/testing/SKILL.md +0 -70
- package/.agents/core/skills/testing/TROUBLESHOOTING.md +0 -18
- package/.agents/core/skills/testing/VALIDATION.json +0 -11
- package/.agents/core/skills/testing/skill.yaml +0 -26
- package/.agents/core/skills/typescript/EXAMPLES.md +0 -64
- package/.agents/core/skills/typescript/SKILL.md +0 -112
- package/.agents/core/skills/typescript/TROUBLESHOOTING.md +0 -19
- package/.agents/core/skills/typescript/VALIDATION.json +0 -12
- package/.agents/core/skills/typescript/skill.yaml +0 -10
- package/.agents/core/skills/typescript/typescript.md +0 -71
- package/.agents/core/skills/ui-design/EXAMPLES.md +0 -21
- package/.agents/core/skills/ui-design/SKILL.md +0 -124
- package/.agents/core/skills/ui-design/TROUBLESHOOTING.md +0 -19
- package/.agents/core/skills/ui-design/VALIDATION.json +0 -12
- package/.agents/core/skills/ui-design/skill.yaml +0 -10
- package/.agents/core/skills/ui-design/ui.md +0 -88
- package/.agents/core/skills/ui-ux-pro/EXAMPLES.md +0 -62
- package/.agents/core/skills/ui-ux-pro/SKILL.md +0 -375
- package/.agents/core/skills/ui-ux-pro/TROUBLESHOOTING.md +0 -19
- package/.agents/core/skills/ui-ux-pro/VALIDATION.json +0 -12
- package/.agents/core/skills/ui-ux-pro/skill.yaml +0 -13
- package/.agents/core/skills/ux-design/EXAMPLES.md +0 -36
- package/.agents/core/skills/ux-design/SKILL.md +0 -116
- package/.agents/core/skills/ux-design/TROUBLESHOOTING.md +0 -19
- package/.agents/core/skills/ux-design/VALIDATION.json +0 -12
- package/.agents/core/skills/ux-design/skill.yaml +0 -10
- package/.agents/core/skills/ux-design/ux.md +0 -80
- package/.agents/core/skills/vercel-optimize/SKILL.md +0 -83
- package/.agents/core/skills/vercel-optimize/VALIDATION.json +0 -12
- package/.agents/core/skills/vercel-optimize/skill.yaml +0 -10
- package/.agents/core/skills/web-accessibility/EXAMPLES.md +0 -39
- package/.agents/core/skills/web-accessibility/SKILL.md +0 -170
- package/.agents/core/skills/web-accessibility/TROUBLESHOOTING.md +0 -19
- package/.agents/core/skills/web-accessibility/VALIDATION.json +0 -12
- package/.agents/core/skills/web-accessibility/accessibility.md +0 -63
- package/.agents/core/skills/web-accessibility/skill.yaml +0 -10
- package/.agents/generated/claude/skills/adapters/SKILL.md +0 -126
- package/.agents/generated/claude/skills/architecture-diagrams/SKILL.md +0 -101
- package/.agents/generated/claude/skills/brutalist-design/SKILL.md +0 -145
- package/.agents/generated/claude/skills/database/SKILL.md +0 -191
- package/.agents/generated/claude/skills/ddd/SKILL.md +0 -305
- package/.agents/generated/claude/skills/decisions/SKILL.md +0 -134
- package/.agents/generated/claude/skills/docker/SKILL.md +0 -135
- package/.agents/generated/claude/skills/fastapi/SKILL.md +0 -200
- package/.agents/generated/claude/skills/generators/SKILL.md +0 -133
- package/.agents/generated/claude/skills/graphify/SKILL.md +0 -198
- package/.agents/generated/claude/skills/impeccable-design/SKILL.md +0 -241
- package/.agents/generated/claude/skills/interview-me/SKILL.md +0 -90
- package/.agents/generated/claude/skills/microservices/SKILL.md +0 -218
- package/.agents/generated/claude/skills/minimalist-design/SKILL.md +0 -108
- package/.agents/generated/claude/skills/nestjs/SKILL.md +0 -195
- package/.agents/generated/claude/skills/nextjs/SKILL.md +0 -219
- package/.agents/generated/claude/skills/node/SKILL.md +0 -224
- package/.agents/generated/claude/skills/performance/SKILL.md +0 -121
- package/.agents/generated/claude/skills/react/SKILL.md +0 -227
- package/.agents/generated/claude/skills/react-best-practices/SKILL.md +0 -146
- package/.agents/generated/claude/skills/redesign-audit/SKILL.md +0 -112
- package/.agents/generated/claude/skills/soft-design/SKILL.md +0 -103
- package/.agents/generated/claude/skills/state-management/SKILL.md +0 -120
- package/.agents/generated/claude/skills/subagent-orchestrator/SKILL.md +0 -93
- package/.agents/generated/claude/skills/system-design/SKILL.md +0 -507
- package/.agents/generated/claude/skills/testing/SKILL.md +0 -157
- package/.agents/generated/claude/skills/typescript/SKILL.md +0 -192
- package/.agents/generated/claude/skills/ui-design/SKILL.md +0 -161
- package/.agents/generated/claude/skills/ui-ux-pro/SKILL.md +0 -451
- package/.agents/generated/claude/skills/ux-design/SKILL.md +0 -168
- package/.agents/generated/claude/skills/vercel-optimize/SKILL.md +0 -76
- package/.agents/generated/claude/skills/web-accessibility/SKILL.md +0 -225
- package/.agents/generated/gemini/skills/adapters/SKILL.md +0 -135
- package/.agents/generated/gemini/skills/architecture-diagrams/SKILL.md +0 -107
- package/.agents/generated/gemini/skills/brutalist-design/SKILL.md +0 -151
- package/.agents/generated/gemini/skills/database/SKILL.md +0 -200
- package/.agents/generated/gemini/skills/ddd/SKILL.md +0 -314
- package/.agents/generated/gemini/skills/decisions/SKILL.md +0 -143
- package/.agents/generated/gemini/skills/docker/SKILL.md +0 -144
- package/.agents/generated/gemini/skills/fastapi/SKILL.md +0 -209
- package/.agents/generated/gemini/skills/generators/SKILL.md +0 -142
- package/.agents/generated/gemini/skills/graphify/SKILL.md +0 -205
- package/.agents/generated/gemini/skills/impeccable-design/SKILL.md +0 -250
- package/.agents/generated/gemini/skills/interview-me/SKILL.md +0 -96
- package/.agents/generated/gemini/skills/microservices/SKILL.md +0 -227
- package/.agents/generated/gemini/skills/minimalist-design/SKILL.md +0 -114
- package/.agents/generated/gemini/skills/nestjs/SKILL.md +0 -204
- package/.agents/generated/gemini/skills/nextjs/SKILL.md +0 -298
- package/.agents/generated/gemini/skills/node/SKILL.md +0 -323
- package/.agents/generated/gemini/skills/performance/SKILL.md +0 -185
- package/.agents/generated/gemini/skills/react/SKILL.md +0 -332
- package/.agents/generated/gemini/skills/react-best-practices/SKILL.md +0 -152
- package/.agents/generated/gemini/skills/redesign-audit/SKILL.md +0 -118
- package/.agents/generated/gemini/skills/soft-design/SKILL.md +0 -109
- package/.agents/generated/gemini/skills/state-management/SKILL.md +0 -129
- package/.agents/generated/gemini/skills/subagent-orchestrator/SKILL.md +0 -99
- package/.agents/generated/gemini/skills/system-design/SKILL.md +0 -631
- package/.agents/generated/gemini/skills/testing/SKILL.md +0 -166
- package/.agents/generated/gemini/skills/typescript/SKILL.md +0 -275
- package/.agents/generated/gemini/skills/ui-design/SKILL.md +0 -170
- package/.agents/generated/gemini/skills/ui-ux-pro/SKILL.md +0 -460
- package/.agents/generated/gemini/skills/ux-design/SKILL.md +0 -177
- package/.agents/generated/gemini/skills/vercel-optimize/SKILL.md +0 -82
- package/.agents/generated/gemini/skills/web-accessibility/SKILL.md +0 -300
- package/.agents/mcp/runtime.py +0 -470
- package/.agents/mcp/server.mjs +0 -189271
- package/benchmarks/gemini-issues.js +0 -533
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
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
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)
|
|
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
|
-
|
|
50
|
-
|
|
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
|
-
|
|
110
|
+
id: 'node_version',
|
|
111
|
+
status: STATUS.PASS,
|
|
112
|
+
ok: true,
|
|
53
113
|
version,
|
|
54
|
-
message:
|
|
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 {
|
|
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 {
|
|
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 {
|
|
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 {
|
|
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
|
-
|
|
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 {
|
|
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 {
|
|
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
|
|
|
@@ -112,6 +264,422 @@ function checkCompiledAdapters(projectDir) {
|
|
|
112
264
|
return compiled;
|
|
113
265
|
}
|
|
114
266
|
|
|
267
|
+
function checkLockfileIntegrity(projectDir) {
|
|
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
|
+
};
|
|
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
|
|
316
|
+
try {
|
|
317
|
+
const raw = fs.readFileSync(v1Path, 'utf8');
|
|
318
|
+
const data = JSON.parse(raw);
|
|
319
|
+
if (!data || typeof data !== '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
|
+
};
|
|
329
|
+
}
|
|
330
|
+
if (!data.schemaVersion || typeof data.schemaVersion !== 'number') {
|
|
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
|
+
};
|
|
340
|
+
}
|
|
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
|
+
};
|
|
351
|
+
}
|
|
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
|
+
};
|
|
362
|
+
} catch (err) {
|
|
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
|
|
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
|
+
};
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
function checkAdapterIntegrity(projectDir) {
|
|
401
|
+
const errors = [];
|
|
402
|
+
const filesToCheck = [
|
|
403
|
+
path.join(projectDir, '.cursorrules'),
|
|
404
|
+
path.join(projectDir, '.github', 'copilot-instructions.md'),
|
|
405
|
+
path.join(projectDir, '.aider.conf.yml'),
|
|
406
|
+
path.join(projectDir, '.zed', 'rules.md'),
|
|
407
|
+
];
|
|
408
|
+
for (const f of filesToCheck) {
|
|
409
|
+
if (fs.existsSync(f)) {
|
|
410
|
+
try {
|
|
411
|
+
const stat = fs.statSync(f);
|
|
412
|
+
if (stat.size === 0) {
|
|
413
|
+
errors.push(`Adapter file ${toPosix(path.relative(projectDir, f))} is empty (0 bytes)`);
|
|
414
|
+
}
|
|
415
|
+
} catch {
|
|
416
|
+
errors.push(`Adapter file ${toPosix(path.relative(projectDir, f))} is unreadable`);
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
const cursorRulesDir = path.join(projectDir, '.cursor', 'rules');
|
|
422
|
+
if (fs.existsSync(cursorRulesDir)) {
|
|
423
|
+
try {
|
|
424
|
+
const files = fs.readdirSync(cursorRulesDir);
|
|
425
|
+
for (const file of files) {
|
|
426
|
+
const full = path.join(cursorRulesDir, file);
|
|
427
|
+
if (fs.statSync(full).size === 0) {
|
|
428
|
+
errors.push(`Cursor rule ${file} is empty (0 bytes)`);
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
} catch {}
|
|
432
|
+
}
|
|
433
|
+
|
|
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
|
+
};
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
function checkSymlinks(projectDir) {
|
|
445
|
+
const errors = [];
|
|
446
|
+
const targetDir = path.join(projectDir, '.agents');
|
|
447
|
+
if (!fs.existsSync(targetDir)) return { ok: true, errors: [], status: STATUS.PASS };
|
|
448
|
+
|
|
449
|
+
function scan(current) {
|
|
450
|
+
let entries;
|
|
451
|
+
try {
|
|
452
|
+
entries = fs.readdirSync(current);
|
|
453
|
+
} catch {
|
|
454
|
+
return;
|
|
455
|
+
}
|
|
456
|
+
for (const name of entries) {
|
|
457
|
+
const full = path.join(current, name);
|
|
458
|
+
try {
|
|
459
|
+
const lstat = fs.lstatSync(full);
|
|
460
|
+
if (lstat.isSymbolicLink()) {
|
|
461
|
+
if (!fs.existsSync(full)) {
|
|
462
|
+
errors.push(`Broken symlink: ${toPosix(path.relative(projectDir, full))}`);
|
|
463
|
+
}
|
|
464
|
+
} else if (lstat.isDirectory()) {
|
|
465
|
+
scan(full);
|
|
466
|
+
}
|
|
467
|
+
} catch {
|
|
468
|
+
errors.push(`Cannot stat file: ${toPosix(path.relative(projectDir, full))}`);
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
scan(targetDir);
|
|
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
|
+
}
|
|
681
|
+
}
|
|
682
|
+
|
|
115
683
|
function inspectSkills(projectDir, activeProfile) {
|
|
116
684
|
const skillsDir = path.join(projectDir, '.agents', 'core', 'skills');
|
|
117
685
|
if (!fs.existsSync(skillsDir)) return { total: 0, active: 0, excluded: 0, byDomain: { frontend: [], backend: [], cross: [] } };
|
|
@@ -139,35 +707,240 @@ function inspectSkills(projectDir, activeProfile) {
|
|
|
139
707
|
};
|
|
140
708
|
}
|
|
141
709
|
|
|
142
|
-
|
|
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
|
+
*/
|
|
782
|
+
function runDoctor(projectDir = process.cwd(), options = {}) {
|
|
143
783
|
const version = (() => {
|
|
144
784
|
try {
|
|
145
785
|
return require('../package.json').version;
|
|
146
786
|
} catch {
|
|
147
|
-
return '1.
|
|
787
|
+
return '1.7.1';
|
|
148
788
|
}
|
|
149
789
|
})();
|
|
150
790
|
|
|
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 = [];
|
|
805
|
+
const errors = [];
|
|
806
|
+
const warnings = [];
|
|
807
|
+
|
|
151
808
|
const agentsDir = path.join(projectDir, '.agents');
|
|
152
809
|
const hasAgents = fs.existsSync(agentsDir);
|
|
810
|
+
if (!hasAgents) {
|
|
811
|
+
errors.push('.agents/ directory missing (run: npx contextos-agents init)');
|
|
812
|
+
}
|
|
813
|
+
|
|
153
814
|
const nodeCheck = checkNodeVersion();
|
|
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);
|
|
826
|
+
|
|
827
|
+
const lockfileCheck = checkLockfileIntegrity(projectDir);
|
|
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);
|
|
835
|
+
|
|
836
|
+
const adapterCheck = checkAdapterIntegrity(projectDir);
|
|
837
|
+
checks.push(adapterCheck);
|
|
838
|
+
if (!adapterCheck.ok) errors.push(...adapterCheck.errors);
|
|
839
|
+
|
|
840
|
+
const symlinkCheck = checkSymlinks(projectDir);
|
|
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);
|
|
847
|
+
|
|
154
848
|
const activeProfile = hasAgents ? profiles.getActiveProfile(projectDir) : null;
|
|
155
849
|
const skillsInfo = inspectSkills(projectDir, activeProfile);
|
|
156
850
|
const compiledAdapters = checkCompiledAdapters(projectDir);
|
|
157
851
|
|
|
158
|
-
const
|
|
159
|
-
|
|
852
|
+
const mcpCheck = checkMcpHandshake(projectDir);
|
|
853
|
+
checks.push(mcpCheck);
|
|
854
|
+
if (!mcpCheck.ok) errors.push(mcpCheck.message);
|
|
160
855
|
|
|
161
856
|
const hookCheck = checkPreCommitHook(projectDir);
|
|
857
|
+
checks.push(hookCheck);
|
|
858
|
+
if (hookCheck.status === STATUS.WARN) warnings.push(hookCheck.message);
|
|
859
|
+
|
|
162
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
|
+
|
|
163
880
|
const stack = profiles.detectStack(projectDir);
|
|
164
881
|
|
|
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;
|
|
897
|
+
|
|
898
|
+
if (isJson) {
|
|
899
|
+
const report = {
|
|
900
|
+
schemaVersion: '2.0.0',
|
|
901
|
+
status: overallStatus,
|
|
902
|
+
ok,
|
|
903
|
+
version,
|
|
904
|
+
timestamp: new Date().toISOString(),
|
|
905
|
+
errors,
|
|
906
|
+
warnings,
|
|
907
|
+
checks,
|
|
908
|
+
hasAgents,
|
|
909
|
+
nodeOk: nodeCheck.ok,
|
|
910
|
+
activeProfile,
|
|
911
|
+
skillsInfo,
|
|
912
|
+
compiledAdapters,
|
|
913
|
+
hasMcp: mcpCheck.ok && mcpCheck.status === STATUS.PASS,
|
|
914
|
+
hookCheck,
|
|
915
|
+
secretCheck,
|
|
916
|
+
lockfileCheck,
|
|
917
|
+
adapterCheck,
|
|
918
|
+
symlinkCheck,
|
|
919
|
+
driftCheck,
|
|
920
|
+
sandboxCheck,
|
|
921
|
+
claimCheck,
|
|
922
|
+
projectLockCheck,
|
|
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
|
+
},
|
|
931
|
+
};
|
|
932
|
+
console.log(JSON.stringify(report, null, 2));
|
|
933
|
+
if (options.exitOnError !== false && !ok && require.main === module) {
|
|
934
|
+
process.exit(1);
|
|
935
|
+
}
|
|
936
|
+
return report;
|
|
937
|
+
}
|
|
938
|
+
|
|
165
939
|
console.log('\n┌─────────────────────────────────────────────────────────────┐');
|
|
166
|
-
console.log(`│ ContextOS Doctor — Project Health Check v${version.padEnd(
|
|
940
|
+
console.log(`│ ContextOS Doctor v2 — Project Health Check v${version.padEnd(14)}│`);
|
|
167
941
|
console.log('├─────────────────────────────────────────────────────────────┤');
|
|
168
942
|
console.log('│ │');
|
|
169
943
|
|
|
170
|
-
// .agents directory
|
|
171
944
|
if (hasAgents) {
|
|
172
945
|
console.log('│ ✓ .agents/ directory found │');
|
|
173
946
|
} else {
|
|
@@ -178,6 +951,15 @@ function runDoctor(projectDir = process.cwd()) {
|
|
|
178
951
|
const nodeIcon = nodeCheck.ok ? '✓' : '✗';
|
|
179
952
|
console.log(`│ ${nodeIcon} ${nodeCheck.message.padEnd(58)}│`);
|
|
180
953
|
|
|
954
|
+
// Lockfile
|
|
955
|
+
if (lockfileCheck.exists) {
|
|
956
|
+
const lockIcon = lockfileCheck.ok ? '✓' : '✗';
|
|
957
|
+
const lockStr = `Lockfile: ${lockfileCheck.ok ? lockfileCheck.message : lockfileCheck.error}`;
|
|
958
|
+
console.log(`│ ${lockIcon} ${lockStr.slice(0, 58).padEnd(58)}│`);
|
|
959
|
+
} else {
|
|
960
|
+
console.log('│ • Lockfile: not present (optional for dev repository) │');
|
|
961
|
+
}
|
|
962
|
+
|
|
181
963
|
// Active profile
|
|
182
964
|
if (activeProfile) {
|
|
183
965
|
const profStr = `Active profile: ${activeProfile.name || activeProfile.profile}`;
|
|
@@ -199,22 +981,43 @@ function runDoctor(projectDir = process.cwd()) {
|
|
|
199
981
|
console.log('│ • Adapters compiled: none (run: contextos export gemini) │');
|
|
200
982
|
}
|
|
201
983
|
|
|
984
|
+
// Symlinks
|
|
985
|
+
const symIcon = symlinkCheck.ok ? '✓' : '✗';
|
|
986
|
+
const symStr = symlinkCheck.ok ? 'Symlinks: verified (0 broken)' : `Broken symlinks: ${symlinkCheck.errors.length}`;
|
|
987
|
+
console.log(`│ ${symIcon} ${symStr.padEnd(58)}│`);
|
|
988
|
+
|
|
202
989
|
// MCP
|
|
203
|
-
if (
|
|
990
|
+
if (mcpCheck.status === STATUS.PASS) {
|
|
204
991
|
console.log('│ ✓ MCP server: installed (.agents/mcp/server.mjs) │');
|
|
205
992
|
} else {
|
|
206
993
|
console.log('│ • MCP server: not installed (run: contextos setup-mcp) │');
|
|
207
994
|
}
|
|
208
995
|
|
|
209
|
-
//
|
|
996
|
+
// Pre-commit hook
|
|
210
997
|
const hookIcon = hookCheck.ok ? '✓' : '•';
|
|
211
998
|
const hookStr = `Pre-commit hook: ${hookCheck.message}`;
|
|
212
999
|
console.log(`│ ${hookIcon} ${hookStr.padEnd(58)}│`);
|
|
213
1000
|
|
|
214
|
-
|
|
1001
|
+
// Secret scanner
|
|
1002
|
+
const secretIcon = secretCheck.ok ? '✓' : (secretCheck.status === STATUS.WARN ? '•' : '✗');
|
|
215
1003
|
const secretStr = `Secret scanner: ${secretCheck.message}`;
|
|
216
1004
|
console.log(`│ ${secretIcon} ${secretStr.padEnd(58)}│`);
|
|
217
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
|
+
|
|
218
1021
|
console.log('│ │');
|
|
219
1022
|
console.log('├─────────────────────────────────────────────────────────────┤');
|
|
220
1023
|
console.log('│ Project Stack & Profile Recommendations │');
|
|
@@ -242,28 +1045,69 @@ function runDoctor(projectDir = process.cwd()) {
|
|
|
242
1045
|
console.log('│ │');
|
|
243
1046
|
console.log('└─────────────────────────────────────────────────────────────┘\n');
|
|
244
1047
|
|
|
245
|
-
|
|
1048
|
+
if (errors.length > 0) {
|
|
1049
|
+
console.error(`[ERROR] Diagnostic check failed with ${errors.length} error(s):`);
|
|
1050
|
+
for (const err of errors) {
|
|
1051
|
+
console.error(` - ${err}`);
|
|
1052
|
+
}
|
|
1053
|
+
console.error('');
|
|
1054
|
+
}
|
|
1055
|
+
|
|
1056
|
+
const result = {
|
|
1057
|
+
schemaVersion: '2.0.0',
|
|
1058
|
+
status: overallStatus,
|
|
1059
|
+
ok,
|
|
1060
|
+
errors,
|
|
1061
|
+
warnings,
|
|
1062
|
+
checks,
|
|
246
1063
|
hasAgents,
|
|
247
1064
|
nodeOk: nodeCheck.ok,
|
|
248
1065
|
activeProfile,
|
|
249
1066
|
skillsInfo,
|
|
250
1067
|
compiledAdapters,
|
|
251
|
-
hasMcp,
|
|
1068
|
+
hasMcp: mcpCheck.ok && mcpCheck.status === STATUS.PASS,
|
|
252
1069
|
hookCheck,
|
|
253
1070
|
secretCheck,
|
|
1071
|
+
lockfileCheck,
|
|
1072
|
+
adapterCheck,
|
|
1073
|
+
symlinkCheck,
|
|
1074
|
+
driftCheck,
|
|
1075
|
+
sandboxCheck,
|
|
1076
|
+
claimCheck,
|
|
1077
|
+
projectLockCheck,
|
|
254
1078
|
stack,
|
|
255
1079
|
};
|
|
1080
|
+
|
|
1081
|
+
if (options.exitOnError !== false && !ok && require.main === module) {
|
|
1082
|
+
process.exit(1);
|
|
1083
|
+
}
|
|
1084
|
+
|
|
1085
|
+
return result;
|
|
256
1086
|
}
|
|
257
1087
|
|
|
258
1088
|
if (require.main === module) {
|
|
259
|
-
runDoctor();
|
|
1089
|
+
const res = runDoctor();
|
|
1090
|
+
if (!res.ok) process.exit(1);
|
|
260
1091
|
}
|
|
261
1092
|
|
|
262
1093
|
module.exports = {
|
|
1094
|
+
STATUS,
|
|
263
1095
|
runDoctor,
|
|
1096
|
+
applyDoctorFix,
|
|
264
1097
|
checkNodeVersion,
|
|
1098
|
+
checkGitVersion,
|
|
1099
|
+
checkFilesystemPermissions,
|
|
265
1100
|
checkPreCommitHook,
|
|
266
1101
|
checkSecretScanner,
|
|
267
1102
|
checkCompiledAdapters,
|
|
1103
|
+
checkLockfileIntegrity,
|
|
1104
|
+
checkTransactions,
|
|
1105
|
+
checkAdapterIntegrity,
|
|
1106
|
+
checkSymlinks,
|
|
1107
|
+
checkMcpHandshake,
|
|
1108
|
+
checkDriftStatus,
|
|
1109
|
+
checkSandboxAvailability,
|
|
1110
|
+
checkClaimEvidence,
|
|
1111
|
+
checkProjectLock,
|
|
268
1112
|
inspectSkills,
|
|
269
1113
|
};
|