cap-pro 1.0.0
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/.claude-plugin/README.md +26 -0
- package/.claude-plugin/marketplace.json +24 -0
- package/.claude-plugin/plugin.json +24 -0
- package/LICENSE +21 -0
- package/README.ja-JP.md +834 -0
- package/README.ko-KR.md +823 -0
- package/README.md +806 -0
- package/README.pt-BR.md +452 -0
- package/README.zh-CN.md +800 -0
- package/agents/cap-architect.md +269 -0
- package/agents/cap-brainstormer.md +207 -0
- package/agents/cap-curator.md +276 -0
- package/agents/cap-debugger.md +365 -0
- package/agents/cap-designer.md +246 -0
- package/agents/cap-historian.md +464 -0
- package/agents/cap-migrator.md +291 -0
- package/agents/cap-prototyper.md +197 -0
- package/agents/cap-validator.md +308 -0
- package/bin/install.js +5433 -0
- package/cap/bin/cap-tools.cjs +853 -0
- package/cap/bin/lib/arc-scanner.cjs +344 -0
- package/cap/bin/lib/cap-affinity-engine.cjs +862 -0
- package/cap/bin/lib/cap-anchor.cjs +228 -0
- package/cap/bin/lib/cap-annotation-writer.cjs +340 -0
- package/cap/bin/lib/cap-checkpoint.cjs +434 -0
- package/cap/bin/lib/cap-cluster-detect.cjs +945 -0
- package/cap/bin/lib/cap-cluster-display.cjs +52 -0
- package/cap/bin/lib/cap-cluster-format.cjs +245 -0
- package/cap/bin/lib/cap-cluster-helpers.cjs +295 -0
- package/cap/bin/lib/cap-cluster-io.cjs +212 -0
- package/cap/bin/lib/cap-completeness.cjs +540 -0
- package/cap/bin/lib/cap-deps.cjs +583 -0
- package/cap/bin/lib/cap-design-families.cjs +332 -0
- package/cap/bin/lib/cap-design.cjs +966 -0
- package/cap/bin/lib/cap-divergence-detector.cjs +400 -0
- package/cap/bin/lib/cap-doctor.cjs +752 -0
- package/cap/bin/lib/cap-feature-map-internals.cjs +19 -0
- package/cap/bin/lib/cap-feature-map-migrate.cjs +335 -0
- package/cap/bin/lib/cap-feature-map-monorepo.cjs +885 -0
- package/cap/bin/lib/cap-feature-map-shard.cjs +315 -0
- package/cap/bin/lib/cap-feature-map.cjs +1943 -0
- package/cap/bin/lib/cap-fitness-score.cjs +1075 -0
- package/cap/bin/lib/cap-impact-analysis.cjs +652 -0
- package/cap/bin/lib/cap-learn-review.cjs +1072 -0
- package/cap/bin/lib/cap-learning-signals.cjs +627 -0
- package/cap/bin/lib/cap-loader.cjs +227 -0
- package/cap/bin/lib/cap-logger.cjs +57 -0
- package/cap/bin/lib/cap-memory-bridge.cjs +764 -0
- package/cap/bin/lib/cap-memory-confidence.cjs +452 -0
- package/cap/bin/lib/cap-memory-dir.cjs +987 -0
- package/cap/bin/lib/cap-memory-engine.cjs +698 -0
- package/cap/bin/lib/cap-memory-extends.cjs +398 -0
- package/cap/bin/lib/cap-memory-graph.cjs +790 -0
- package/cap/bin/lib/cap-memory-migrate.cjs +2015 -0
- package/cap/bin/lib/cap-memory-pin.cjs +183 -0
- package/cap/bin/lib/cap-memory-platform.cjs +490 -0
- package/cap/bin/lib/cap-memory-prune.cjs +707 -0
- package/cap/bin/lib/cap-memory-schema.cjs +812 -0
- package/cap/bin/lib/cap-migrate-tags.cjs +309 -0
- package/cap/bin/lib/cap-migrate.cjs +540 -0
- package/cap/bin/lib/cap-pattern-apply.cjs +1203 -0
- package/cap/bin/lib/cap-pattern-pipeline.cjs +1034 -0
- package/cap/bin/lib/cap-plugin-manifest.cjs +80 -0
- package/cap/bin/lib/cap-realtime-affinity.cjs +399 -0
- package/cap/bin/lib/cap-reconcile.cjs +570 -0
- package/cap/bin/lib/cap-research-gate.cjs +218 -0
- package/cap/bin/lib/cap-scope-filter.cjs +402 -0
- package/cap/bin/lib/cap-semantic-pipeline.cjs +1038 -0
- package/cap/bin/lib/cap-session-extract.cjs +987 -0
- package/cap/bin/lib/cap-session.cjs +445 -0
- package/cap/bin/lib/cap-snapshot-linkage.cjs +963 -0
- package/cap/bin/lib/cap-stack-docs.cjs +646 -0
- package/cap/bin/lib/cap-tag-observer.cjs +371 -0
- package/cap/bin/lib/cap-tag-scanner.cjs +1766 -0
- package/cap/bin/lib/cap-telemetry.cjs +466 -0
- package/cap/bin/lib/cap-test-audit.cjs +1438 -0
- package/cap/bin/lib/cap-thread-migrator.cjs +307 -0
- package/cap/bin/lib/cap-thread-synthesis.cjs +545 -0
- package/cap/bin/lib/cap-thread-tracker.cjs +519 -0
- package/cap/bin/lib/cap-trace.cjs +399 -0
- package/cap/bin/lib/cap-trust-mode.cjs +336 -0
- package/cap/bin/lib/cap-ui-design-editor.cjs +642 -0
- package/cap/bin/lib/cap-ui-mind-map.cjs +712 -0
- package/cap/bin/lib/cap-ui-thread-nav.cjs +693 -0
- package/cap/bin/lib/cap-ui.cjs +1245 -0
- package/cap/bin/lib/cap-upgrade.cjs +1028 -0
- package/cap/bin/lib/cli/arg-helpers.cjs +49 -0
- package/cap/bin/lib/cli/frontmatter-router.cjs +31 -0
- package/cap/bin/lib/cli/init-router.cjs +68 -0
- package/cap/bin/lib/cli/phase-router.cjs +102 -0
- package/cap/bin/lib/cli/state-router.cjs +61 -0
- package/cap/bin/lib/cli/template-router.cjs +37 -0
- package/cap/bin/lib/cli/uat-router.cjs +29 -0
- package/cap/bin/lib/cli/validation-router.cjs +26 -0
- package/cap/bin/lib/cli/verification-router.cjs +31 -0
- package/cap/bin/lib/cli/workstream-router.cjs +39 -0
- package/cap/bin/lib/commands.cjs +961 -0
- package/cap/bin/lib/config.cjs +467 -0
- package/cap/bin/lib/convention-reader.cjs +258 -0
- package/cap/bin/lib/core.cjs +1241 -0
- package/cap/bin/lib/feature-aggregator.cjs +423 -0
- package/cap/bin/lib/frontmatter.cjs +337 -0
- package/cap/bin/lib/init.cjs +1443 -0
- package/cap/bin/lib/manifest-generator.cjs +383 -0
- package/cap/bin/lib/milestone.cjs +253 -0
- package/cap/bin/lib/model-profiles.cjs +69 -0
- package/cap/bin/lib/monorepo-context.cjs +226 -0
- package/cap/bin/lib/monorepo-migrator.cjs +509 -0
- package/cap/bin/lib/phase.cjs +889 -0
- package/cap/bin/lib/profile-output.cjs +989 -0
- package/cap/bin/lib/profile-pipeline.cjs +540 -0
- package/cap/bin/lib/roadmap.cjs +330 -0
- package/cap/bin/lib/security.cjs +394 -0
- package/cap/bin/lib/session-manager.cjs +292 -0
- package/cap/bin/lib/skeleton-generator.cjs +179 -0
- package/cap/bin/lib/state.cjs +1032 -0
- package/cap/bin/lib/template.cjs +231 -0
- package/cap/bin/lib/test-detector.cjs +62 -0
- package/cap/bin/lib/uat.cjs +283 -0
- package/cap/bin/lib/verify.cjs +889 -0
- package/cap/bin/lib/workspace-detector.cjs +371 -0
- package/cap/bin/lib/workstream.cjs +492 -0
- package/cap/commands/gsd/workstreams.md +63 -0
- package/cap/references/arc-standard.md +315 -0
- package/cap/references/cap-agent-architecture.md +101 -0
- package/cap/references/cap-gitignore-template +9 -0
- package/cap/references/cap-zero-deps.md +158 -0
- package/cap/references/checkpoints.md +778 -0
- package/cap/references/continuation-format.md +249 -0
- package/cap/references/contract-test-templates.md +312 -0
- package/cap/references/feature-map-template.md +25 -0
- package/cap/references/git-integration.md +295 -0
- package/cap/references/git-planning-commit.md +38 -0
- package/cap/references/model-profiles.md +174 -0
- package/cap/references/phase-numbering.md +126 -0
- package/cap/references/planning-config.md +202 -0
- package/cap/references/property-test-templates.md +316 -0
- package/cap/references/security-test-templates.md +347 -0
- package/cap/references/session-template.json +8 -0
- package/cap/references/tdd.md +263 -0
- package/cap/references/user-profiling.md +681 -0
- package/cap/references/verification-patterns.md +612 -0
- package/cap/templates/UAT.md +265 -0
- package/cap/templates/claude-md.md +175 -0
- package/cap/templates/codebase/architecture.md +255 -0
- package/cap/templates/codebase/concerns.md +310 -0
- package/cap/templates/codebase/conventions.md +307 -0
- package/cap/templates/codebase/integrations.md +280 -0
- package/cap/templates/codebase/stack.md +186 -0
- package/cap/templates/codebase/structure.md +285 -0
- package/cap/templates/codebase/testing.md +480 -0
- package/cap/templates/config.json +44 -0
- package/cap/templates/context.md +352 -0
- package/cap/templates/continue-here.md +78 -0
- package/cap/templates/copilot-instructions.md +7 -0
- package/cap/templates/debug-subagent-prompt.md +91 -0
- package/cap/templates/discussion-log.md +63 -0
- package/cap/templates/milestone-archive.md +123 -0
- package/cap/templates/milestone.md +115 -0
- package/cap/templates/phase-prompt.md +610 -0
- package/cap/templates/planner-subagent-prompt.md +117 -0
- package/cap/templates/project.md +186 -0
- package/cap/templates/requirements.md +231 -0
- package/cap/templates/research-project/ARCHITECTURE.md +204 -0
- package/cap/templates/research-project/FEATURES.md +147 -0
- package/cap/templates/research-project/PITFALLS.md +200 -0
- package/cap/templates/research-project/STACK.md +120 -0
- package/cap/templates/research-project/SUMMARY.md +170 -0
- package/cap/templates/research.md +552 -0
- package/cap/templates/roadmap.md +202 -0
- package/cap/templates/state.md +176 -0
- package/cap/templates/summary.md +364 -0
- package/cap/templates/user-preferences.md +498 -0
- package/cap/templates/verification-report.md +322 -0
- package/cap/workflows/add-phase.md +112 -0
- package/cap/workflows/add-tests.md +351 -0
- package/cap/workflows/add-todo.md +158 -0
- package/cap/workflows/audit-milestone.md +340 -0
- package/cap/workflows/audit-uat.md +109 -0
- package/cap/workflows/autonomous.md +891 -0
- package/cap/workflows/check-todos.md +177 -0
- package/cap/workflows/cleanup.md +152 -0
- package/cap/workflows/complete-milestone.md +767 -0
- package/cap/workflows/diagnose-issues.md +231 -0
- package/cap/workflows/discovery-phase.md +289 -0
- package/cap/workflows/discuss-phase-assumptions.md +653 -0
- package/cap/workflows/discuss-phase.md +1049 -0
- package/cap/workflows/do.md +104 -0
- package/cap/workflows/execute-phase.md +846 -0
- package/cap/workflows/execute-plan.md +514 -0
- package/cap/workflows/fast.md +105 -0
- package/cap/workflows/forensics.md +265 -0
- package/cap/workflows/health.md +181 -0
- package/cap/workflows/help.md +660 -0
- package/cap/workflows/insert-phase.md +130 -0
- package/cap/workflows/list-phase-assumptions.md +178 -0
- package/cap/workflows/list-workspaces.md +56 -0
- package/cap/workflows/manager.md +362 -0
- package/cap/workflows/map-codebase.md +377 -0
- package/cap/workflows/milestone-summary.md +223 -0
- package/cap/workflows/new-milestone.md +486 -0
- package/cap/workflows/new-project.md +1250 -0
- package/cap/workflows/new-workspace.md +237 -0
- package/cap/workflows/next.md +97 -0
- package/cap/workflows/node-repair.md +92 -0
- package/cap/workflows/note.md +156 -0
- package/cap/workflows/pause-work.md +176 -0
- package/cap/workflows/plan-milestone-gaps.md +273 -0
- package/cap/workflows/plan-phase.md +857 -0
- package/cap/workflows/plant-seed.md +169 -0
- package/cap/workflows/pr-branch.md +129 -0
- package/cap/workflows/profile-user.md +449 -0
- package/cap/workflows/progress.md +507 -0
- package/cap/workflows/quick.md +757 -0
- package/cap/workflows/remove-phase.md +155 -0
- package/cap/workflows/remove-workspace.md +90 -0
- package/cap/workflows/research-phase.md +82 -0
- package/cap/workflows/resume-project.md +326 -0
- package/cap/workflows/review.md +228 -0
- package/cap/workflows/session-report.md +146 -0
- package/cap/workflows/settings.md +283 -0
- package/cap/workflows/ship.md +228 -0
- package/cap/workflows/stats.md +60 -0
- package/cap/workflows/transition.md +671 -0
- package/cap/workflows/ui-phase.md +298 -0
- package/cap/workflows/ui-review.md +161 -0
- package/cap/workflows/update.md +323 -0
- package/cap/workflows/validate-phase.md +170 -0
- package/cap/workflows/verify-phase.md +254 -0
- package/cap/workflows/verify-work.md +637 -0
- package/commands/cap/annotate.md +165 -0
- package/commands/cap/brainstorm.md +393 -0
- package/commands/cap/checkpoint.md +106 -0
- package/commands/cap/completeness.md +94 -0
- package/commands/cap/continue.md +72 -0
- package/commands/cap/debug.md +588 -0
- package/commands/cap/deps.md +169 -0
- package/commands/cap/design.md +479 -0
- package/commands/cap/init.md +354 -0
- package/commands/cap/iterate.md +249 -0
- package/commands/cap/learn.md +459 -0
- package/commands/cap/memory.md +275 -0
- package/commands/cap/migrate-feature-map.md +91 -0
- package/commands/cap/migrate-memory.md +108 -0
- package/commands/cap/migrate-tags.md +91 -0
- package/commands/cap/migrate.md +131 -0
- package/commands/cap/prototype.md +510 -0
- package/commands/cap/reconcile.md +121 -0
- package/commands/cap/review.md +360 -0
- package/commands/cap/save.md +72 -0
- package/commands/cap/scan.md +404 -0
- package/commands/cap/start.md +356 -0
- package/commands/cap/status.md +118 -0
- package/commands/cap/test-audit.md +262 -0
- package/commands/cap/test.md +394 -0
- package/commands/cap/trace.md +133 -0
- package/commands/cap/ui.md +167 -0
- package/hooks/dist/cap-check-update.js +115 -0
- package/hooks/dist/cap-context-monitor.js +185 -0
- package/hooks/dist/cap-learn-review-hook.js +114 -0
- package/hooks/dist/cap-learning-hook.js +192 -0
- package/hooks/dist/cap-memory.js +299 -0
- package/hooks/dist/cap-prompt-guard.js +97 -0
- package/hooks/dist/cap-statusline.js +157 -0
- package/hooks/dist/cap-tag-observer.js +115 -0
- package/hooks/dist/cap-version-check.js +112 -0
- package/hooks/dist/cap-workflow-guard.js +175 -0
- package/hooks/hooks.json +55 -0
- package/package.json +58 -0
- package/scripts/base64-scan.sh +262 -0
- package/scripts/build-hooks.js +93 -0
- package/scripts/cap-removal-checklist.md +202 -0
- package/scripts/prompt-injection-scan.sh +199 -0
- package/scripts/run-tests.cjs +181 -0
- package/scripts/secret-scan.sh +227 -0
|
@@ -0,0 +1,752 @@
|
|
|
1
|
+
// @cap-context CAP v2.0 doctor utility -- checks all external dependencies CAP needs at runtime.
|
|
2
|
+
// @cap-decision Checks are split into required (Node.js, npm, git) and optional (ctx7, c8, vitest, fast-check).
|
|
3
|
+
// @cap-decision Project-specific checks only run when projectRoot is provided and package.json exists.
|
|
4
|
+
// @cap-constraint Zero external dependencies -- uses only Node.js built-ins (child_process, fs, path, os).
|
|
5
|
+
|
|
6
|
+
'use strict';
|
|
7
|
+
|
|
8
|
+
// @cap-feature(feature:F-005) Doctor Health Check — verify required and optional external dependencies
|
|
9
|
+
// @cap-feature(feature:F-019) Module Integrity Verification — verify CAP CJS modules exist and load correctly
|
|
10
|
+
// @cap-feature(feature:F-058) Claude-Code Plugin Manifest — detect npx vs plugin install modes and surface coexistence
|
|
11
|
+
|
|
12
|
+
// @cap-history(sessions:8, edits:22, since:2026-04-20, learned:2026-05-08) Frequently modified — 8 sessions, 22 edits
|
|
13
|
+
const { execSync } = require('node:child_process');
|
|
14
|
+
const fs = require('node:fs');
|
|
15
|
+
const path = require('node:path');
|
|
16
|
+
const os = require('node:os');
|
|
17
|
+
|
|
18
|
+
// Tolerant require: the install-hardening test fixture copies cap-doctor.cjs
|
|
19
|
+
// alone to verify missing-module detection. If we require cap-plugin-manifest
|
|
20
|
+
// eagerly, the load of doctor itself throws and the integrity checker reports
|
|
21
|
+
// a single "cannot load manifest" failure instead of the 30+ expected misses.
|
|
22
|
+
// A tolerant null fallback keeps CAP_MODULE_MANIFEST loadable in isolation;
|
|
23
|
+
// detectInstallMode guards its plugin-name use against the null case below.
|
|
24
|
+
let pluginManifest;
|
|
25
|
+
try { pluginManifest = require('./cap-plugin-manifest.cjs'); } catch (_e) { pluginManifest = null; }
|
|
26
|
+
|
|
27
|
+
// @cap-todo(ac:F-019/AC-5) Module manifest — authoritative list of expected CAP modules
|
|
28
|
+
// @cap-decision Manifest is a flat array of filenames maintained manually. When a new module is added to
|
|
29
|
+
// cap/bin/lib/, it must be added here. This is intentional: an explicit manifest catches accidental deletions.
|
|
30
|
+
const CAP_MODULE_MANIFEST = [
|
|
31
|
+
'arc-scanner.cjs',
|
|
32
|
+
'cap-affinity-engine.cjs',
|
|
33
|
+
'cap-anchor.cjs',
|
|
34
|
+
'cap-annotation-writer.cjs',
|
|
35
|
+
'cap-checkpoint.cjs',
|
|
36
|
+
'cap-cluster-detect.cjs',
|
|
37
|
+
'cap-cluster-display.cjs',
|
|
38
|
+
'cap-cluster-format.cjs',
|
|
39
|
+
'cap-cluster-helpers.cjs',
|
|
40
|
+
'cap-cluster-io.cjs',
|
|
41
|
+
'cap-completeness.cjs',
|
|
42
|
+
'cap-deps.cjs',
|
|
43
|
+
'cap-design.cjs',
|
|
44
|
+
'cap-design-families.cjs',
|
|
45
|
+
'cap-divergence-detector.cjs',
|
|
46
|
+
'cap-doctor.cjs',
|
|
47
|
+
'cap-feature-map.cjs',
|
|
48
|
+
// @cap-feature(feature:F-083) Shared internals module — hosts constants/primitives both
|
|
49
|
+
// cap-feature-map.cjs and cap-feature-map-monorepo.cjs need without forcing a lazy-require
|
|
50
|
+
// just to read a string literal.
|
|
51
|
+
// @cap-decision(F-083/followup) F-083-FIX-A: Bumped 85 -> 86 when cap-feature-map-internals.cjs
|
|
52
|
+
// was added (de-duplicates `FEATURE_MAP_FILE` between core and monorepo modules).
|
|
53
|
+
'cap-feature-map-internals.cjs',
|
|
54
|
+
// @cap-feature(feature:F-089) Sharded Feature Map migration — monolithic → sharded layout.
|
|
55
|
+
// @cap-decision(F-089) Bumped manifest count when cap-feature-map-migrate.cjs was added.
|
|
56
|
+
'cap-feature-map-migrate.cjs',
|
|
57
|
+
// @cap-feature(feature:F-083) Monorepo aggregation module extracted from cap-feature-map.cjs.
|
|
58
|
+
// @cap-decision(F-083) Bumped 84 -> 85 when cap-feature-map-monorepo.cjs was added.
|
|
59
|
+
'cap-feature-map-monorepo.cjs',
|
|
60
|
+
// @cap-feature(feature:F-089) Sharded Feature Map — pure shard helpers (ID validator, index parse/serialize).
|
|
61
|
+
// @cap-decision(F-089) Bumped manifest count when cap-feature-map-shard.cjs was added.
|
|
62
|
+
'cap-feature-map-shard.cjs',
|
|
63
|
+
// @cap-feature(feature:F-072) Compute Two-Layer Fitness Score — pure-compute scorer driving F-074 unlearn.
|
|
64
|
+
// @cap-decision(F-072) Bumped 77 -> 78 when cap-fitness-score.cjs was added (Two-Layer Fitness Score for Pattern Unlearn).
|
|
65
|
+
'cap-fitness-score.cjs',
|
|
66
|
+
'cap-impact-analysis.cjs',
|
|
67
|
+
// @cap-feature(feature:F-073) Review Patterns via Learn Command — board renderer + Stop-hook gate.
|
|
68
|
+
// @cap-decision(F-073) Bumped 79 -> 80 when cap-learn-review.cjs was added (closes the V5 self-learning loop).
|
|
69
|
+
'cap-learn-review.cjs',
|
|
70
|
+
// @cap-feature(feature:F-070) Collect Learning Signals — override/memory-ref/regret JSONL collectors + getSignals API.
|
|
71
|
+
'cap-learning-signals.cjs',
|
|
72
|
+
'cap-loader.cjs',
|
|
73
|
+
'cap-logger.cjs',
|
|
74
|
+
// @cap-feature(feature:F-080) Bridge to Claude-native Memory — read-only consumer of ~/.claude/projects/<slug>/memory/.
|
|
75
|
+
// @cap-decision(F-083/followup) Manifest sync: cap-memory-bridge.cjs (F-080) was on disk but missing from the manifest;
|
|
76
|
+
// added during the F-083-FIX-A internals extraction work to keep the on-disk-vs-manifest contract green.
|
|
77
|
+
'cap-memory-bridge.cjs',
|
|
78
|
+
'cap-memory-confidence.cjs',
|
|
79
|
+
'cap-memory-dir.cjs',
|
|
80
|
+
'cap-memory-engine.cjs',
|
|
81
|
+
// @cap-feature(feature:F-078) Extends-Chain Resolver — resolves `extends: platform/<topic>` chains in a single pass.
|
|
82
|
+
// @cap-decision(F-078) Bumped 82 -> 83 when cap-memory-extends.cjs was added (Platform-Bucket reader path).
|
|
83
|
+
'cap-memory-extends.cjs',
|
|
84
|
+
'cap-memory-graph.cjs',
|
|
85
|
+
// @cap-feature(feature:F-077) V6 Memory Migration Tool — one-shot migration from V5 monolith to V6 per-feature layout.
|
|
86
|
+
// @cap-decision(F-077) Bumped 81 -> 82 when cap-memory-migrate.cjs was added (V6 migration tool with hybrid classifier).
|
|
87
|
+
'cap-memory-migrate.cjs',
|
|
88
|
+
'cap-memory-pin.cjs',
|
|
89
|
+
// @cap-feature(feature:F-078) Platform-Bucket for Cross-Cutting Decisions — explicit-only platform-topic file IO + classifier.
|
|
90
|
+
// @cap-decision(F-078) Bumped 83 -> 84 when cap-memory-platform.cjs was added (Platform-Bucket file IO + writer).
|
|
91
|
+
'cap-memory-platform.cjs',
|
|
92
|
+
'cap-memory-prune.cjs',
|
|
93
|
+
// @cap-feature(feature:F-076) V6 Per-Feature Memory Format — schema + validator + round-trip-safe parser/serializer.
|
|
94
|
+
// @cap-decision(F-076) Bumped 80 -> 81 when cap-memory-schema.cjs was added (V6 memory-format pivot foundation).
|
|
95
|
+
'cap-memory-schema.cjs',
|
|
96
|
+
'cap-migrate-tags.cjs',
|
|
97
|
+
'cap-migrate.cjs',
|
|
98
|
+
// @cap-feature(feature:F-074) Enable Pattern Unlearn and Auto-Retract — apply audit + reverse patch + retract list.
|
|
99
|
+
// @cap-decision(F-074) Bumped 78 -> 79 when cap-pattern-apply.cjs was added (F-074 closes the V5 self-learning loop).
|
|
100
|
+
'cap-pattern-apply.cjs',
|
|
101
|
+
// @cap-feature(feature:F-071) Pattern Pipeline — heuristic Stage 1 + LLM-briefing Stage 2.
|
|
102
|
+
// @cap-decision(F-071) Bumped 76 -> 77 when cap-pattern-pipeline.cjs was added.
|
|
103
|
+
'cap-pattern-pipeline.cjs',
|
|
104
|
+
'cap-plugin-manifest.cjs',
|
|
105
|
+
'cap-realtime-affinity.cjs',
|
|
106
|
+
'cap-reconcile.cjs',
|
|
107
|
+
'cap-research-gate.cjs',
|
|
108
|
+
// @cap-feature(feature:F-085) Scope filter shared by cap-tag-scanner and cap-migrate-tags.
|
|
109
|
+
'cap-scope-filter.cjs',
|
|
110
|
+
'cap-semantic-pipeline.cjs',
|
|
111
|
+
'cap-session-extract.cjs',
|
|
112
|
+
'cap-session.cjs',
|
|
113
|
+
// @cap-feature(feature:F-079) Wire Snapshot Linkage to Features and Platform — resolveLinkageOptions + processSnapshots.
|
|
114
|
+
// @cap-decision(F-083/followup) Manifest sync: cap-snapshot-linkage.cjs (F-079) was on disk but missing from the manifest;
|
|
115
|
+
// added during the F-083-FIX-A internals extraction work to keep the on-disk-vs-manifest contract green.
|
|
116
|
+
'cap-snapshot-linkage.cjs',
|
|
117
|
+
'cap-stack-docs.cjs',
|
|
118
|
+
'cap-tag-observer.cjs',
|
|
119
|
+
'cap-tag-scanner.cjs',
|
|
120
|
+
// @cap-feature(feature:F-061) Token Telemetry — LLM-call metrics + per-session aggregates.
|
|
121
|
+
'cap-telemetry.cjs',
|
|
122
|
+
'cap-test-audit.cjs',
|
|
123
|
+
'cap-thread-migrator.cjs',
|
|
124
|
+
'cap-thread-synthesis.cjs',
|
|
125
|
+
'cap-thread-tracker.cjs',
|
|
126
|
+
'cap-trace.cjs',
|
|
127
|
+
// @cap-feature(feature:F-075) Trust-Mode Configuration Slot — open-closed extension point for B/C activation.
|
|
128
|
+
'cap-trust-mode.cjs',
|
|
129
|
+
// @cap-feature(feature:F-065) CAP-UI Core module entry.
|
|
130
|
+
'cap-ui.cjs',
|
|
131
|
+
// @cap-feature(feature:F-068) CAP-UI Design Editor (DESIGN.md-only edit surface).
|
|
132
|
+
'cap-ui-design-editor.cjs',
|
|
133
|
+
// @cap-feature(feature:F-066) CAP-UI Tag Mind-Map module (extracted from cap-ui.cjs during F-068 hand-off).
|
|
134
|
+
'cap-ui-mind-map.cjs',
|
|
135
|
+
// @cap-feature(feature:F-067) CAP-UI Thread + Cluster Navigator module (extracted from cap-ui.cjs during F-068 hand-off).
|
|
136
|
+
'cap-ui-thread-nav.cjs',
|
|
137
|
+
// @cap-feature(feature:F-084) Project Onboarding & Migration Orchestrator —
|
|
138
|
+
// planner + state-manager for /cap:upgrade. Companion markdown command at
|
|
139
|
+
// commands/cap/upgrade.md and SessionStart-hook at hooks/cap-version-check.js.
|
|
140
|
+
// @cap-decision(F-084) Bumped 88 -> 89 when cap-upgrade.cjs was added.
|
|
141
|
+
'cap-upgrade.cjs',
|
|
142
|
+
'commands.cjs',
|
|
143
|
+
'config.cjs',
|
|
144
|
+
'convention-reader.cjs',
|
|
145
|
+
'core.cjs',
|
|
146
|
+
'feature-aggregator.cjs',
|
|
147
|
+
'frontmatter.cjs',
|
|
148
|
+
'init.cjs',
|
|
149
|
+
'manifest-generator.cjs',
|
|
150
|
+
'milestone.cjs',
|
|
151
|
+
'model-profiles.cjs',
|
|
152
|
+
'monorepo-context.cjs',
|
|
153
|
+
'monorepo-migrator.cjs',
|
|
154
|
+
'phase.cjs',
|
|
155
|
+
'profile-output.cjs',
|
|
156
|
+
'profile-pipeline.cjs',
|
|
157
|
+
'roadmap.cjs',
|
|
158
|
+
'security.cjs',
|
|
159
|
+
'session-manager.cjs',
|
|
160
|
+
'skeleton-generator.cjs',
|
|
161
|
+
'state.cjs',
|
|
162
|
+
'template.cjs',
|
|
163
|
+
'test-detector.cjs',
|
|
164
|
+
'uat.cjs',
|
|
165
|
+
'verify.cjs',
|
|
166
|
+
'workspace-detector.cjs',
|
|
167
|
+
'workstream.cjs',
|
|
168
|
+
];
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* @typedef {Object} ToolCheck
|
|
172
|
+
* @property {string} name - Tool name
|
|
173
|
+
* @property {string} version - Detected version (or 'not found')
|
|
174
|
+
* @property {boolean} ok - Whether the tool is available
|
|
175
|
+
* @property {boolean} required - Whether CAP requires it
|
|
176
|
+
* @property {string} purpose - What CAP uses it for
|
|
177
|
+
* @property {string} installHint - How to install if missing
|
|
178
|
+
*/
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* @typedef {Object} DoctorReport
|
|
182
|
+
* @property {ToolCheck[]} tools - All checked tools
|
|
183
|
+
* @property {number} requiredOk - Count of required tools that are OK
|
|
184
|
+
* @property {number} requiredTotal - Total required tools
|
|
185
|
+
* @property {number} optionalOk - Count of optional tools that are OK
|
|
186
|
+
* @property {number} optionalTotal - Total optional tools
|
|
187
|
+
* @property {boolean} healthy - True if all required tools are OK
|
|
188
|
+
* @property {string[]} installCommands - Commands to install missing tools
|
|
189
|
+
* @property {ModuleCheck[]} [modules] - Module integrity check results
|
|
190
|
+
* @property {PlatformPathCheck} [platformPaths] - Platform path resolution results
|
|
191
|
+
* @property {number} [modulesOk] - Count of modules that passed integrity check
|
|
192
|
+
* @property {number} [modulesTotal] - Total modules checked
|
|
193
|
+
*/
|
|
194
|
+
|
|
195
|
+
/**
|
|
196
|
+
* @typedef {Object} ModuleCheck
|
|
197
|
+
* @property {string} name - Module filename (e.g., 'cap-doctor.cjs')
|
|
198
|
+
* @property {string} fullPath - Absolute path to the module
|
|
199
|
+
* @property {boolean} exists - Whether the file exists on disk
|
|
200
|
+
* @property {boolean} loads - Whether require() succeeds
|
|
201
|
+
* @property {boolean} ok - True if both exists and loads
|
|
202
|
+
* @property {string} [error] - Error message if exists or loads failed
|
|
203
|
+
*/
|
|
204
|
+
|
|
205
|
+
/**
|
|
206
|
+
* @typedef {Object} PlatformPathCheck
|
|
207
|
+
* @property {string} envHome - Value of process.env.HOME
|
|
208
|
+
* @property {string} osHomedir - Value of os.homedir()
|
|
209
|
+
* @property {boolean} homeMatch - Whether envHome and osHomedir agree
|
|
210
|
+
* @property {string} installDir - Resolved install directory
|
|
211
|
+
* @property {boolean} isSymlink - Whether the install directory is a symlink
|
|
212
|
+
* @property {string} [symlinkTarget] - Real path if installDir is a symlink
|
|
213
|
+
* @property {boolean} ok - True if no discrepancies
|
|
214
|
+
* @property {string[]} warnings - Any platform path warnings
|
|
215
|
+
*/
|
|
216
|
+
|
|
217
|
+
/**
|
|
218
|
+
* Check if a CLI tool is available and get its version.
|
|
219
|
+
* @param {string} command - Version check command (e.g., 'node --version')
|
|
220
|
+
* @param {number} [timeout=10000] - Timeout in ms
|
|
221
|
+
* @returns {{ ok: boolean, version: string }}
|
|
222
|
+
*/
|
|
223
|
+
function checkTool(command, timeout = 10000) {
|
|
224
|
+
try {
|
|
225
|
+
const output = execSync(command, {
|
|
226
|
+
encoding: 'utf8',
|
|
227
|
+
timeout,
|
|
228
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
229
|
+
}).trim();
|
|
230
|
+
// Extract version number (strip 'v' prefix if present)
|
|
231
|
+
const version = output.replace(/^v/, '').split('\n')[0].trim();
|
|
232
|
+
return { ok: true, version };
|
|
233
|
+
} catch (_e) {
|
|
234
|
+
return { ok: false, version: 'not found' };
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
/**
|
|
239
|
+
* Detect the CAP install directory.
|
|
240
|
+
* @cap-decision Auto-detection tries the global install path first ($HOME/.claude/cap/cap/bin/lib),
|
|
241
|
+
* then falls back to the directory containing this file. This handles both global npx installs
|
|
242
|
+
* and local development (running from the repo checkout).
|
|
243
|
+
* @returns {string} Absolute path to the cap/bin/lib directory
|
|
244
|
+
*/
|
|
245
|
+
function detectInstallDir() {
|
|
246
|
+
// Global install path: $HOME/.claude/cap/cap/bin/lib/
|
|
247
|
+
const homeDir = process.env.HOME || os.homedir();
|
|
248
|
+
const globalDir = path.join(homeDir, '.claude', 'cap', 'cap', 'bin', 'lib');
|
|
249
|
+
if (fs.existsSync(globalDir)) {
|
|
250
|
+
return globalDir;
|
|
251
|
+
}
|
|
252
|
+
// Fallback: this file's own directory (local dev / repo checkout)
|
|
253
|
+
return __dirname;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
// @cap-todo(ac:F-019/AC-1) Verify every required CJS module exists at the expected install path
|
|
257
|
+
// @cap-todo(ac:F-019/AC-2) Attempt require() on each module and report load failures
|
|
258
|
+
// @cap-todo(ac:F-019/AC-3) Report clear PASS/FAIL summary per module with error reason
|
|
259
|
+
/**
|
|
260
|
+
* Check integrity of all CAP CJS modules.
|
|
261
|
+
* Verifies each module in the manifest exists on disk and can be loaded via require().
|
|
262
|
+
* @param {string} [installDir] - Directory containing the CJS modules (auto-detected if omitted)
|
|
263
|
+
* @returns {{ modules: ModuleCheck[], modulesOk: number, modulesTotal: number }}
|
|
264
|
+
*/
|
|
265
|
+
function checkModuleIntegrity(installDir) {
|
|
266
|
+
const dir = installDir || detectInstallDir();
|
|
267
|
+
const modules = [];
|
|
268
|
+
|
|
269
|
+
for (const name of CAP_MODULE_MANIFEST) {
|
|
270
|
+
const fullPath = path.join(dir, name);
|
|
271
|
+
const check = { name, fullPath, exists: false, loads: false, ok: false };
|
|
272
|
+
|
|
273
|
+
// Step 1: Check file existence
|
|
274
|
+
if (!fs.existsSync(fullPath)) {
|
|
275
|
+
check.error = `File not found: ${fullPath}`;
|
|
276
|
+
modules.push(check);
|
|
277
|
+
continue;
|
|
278
|
+
}
|
|
279
|
+
check.exists = true;
|
|
280
|
+
|
|
281
|
+
// Step 2: Attempt require() to verify the module parses and loads
|
|
282
|
+
try {
|
|
283
|
+
require(path.resolve(fullPath));
|
|
284
|
+
check.loads = true;
|
|
285
|
+
check.ok = true;
|
|
286
|
+
} catch (err) {
|
|
287
|
+
check.error = `Load error: ${err.message.split('\n')[0]}`;
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
modules.push(check);
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
return {
|
|
294
|
+
modules,
|
|
295
|
+
modulesOk: modules.filter(m => m.ok).length,
|
|
296
|
+
modulesTotal: modules.length,
|
|
297
|
+
};
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
// @cap-todo(ac:F-019/AC-6) Test platform-specific path resolution (Linux vs macOS, symlinks)
|
|
301
|
+
/**
|
|
302
|
+
* Check platform-specific path resolution for the CAP install directory.
|
|
303
|
+
* Compares process.env.HOME with os.homedir() and checks for symlinks.
|
|
304
|
+
* @param {string} [installDir] - Directory to check (auto-detected if omitted)
|
|
305
|
+
* @returns {PlatformPathCheck}
|
|
306
|
+
*/
|
|
307
|
+
function checkPlatformPaths(installDir) {
|
|
308
|
+
const dir = installDir || detectInstallDir();
|
|
309
|
+
const envHome = process.env.HOME || '';
|
|
310
|
+
const osHome = os.homedir();
|
|
311
|
+
const warnings = [];
|
|
312
|
+
|
|
313
|
+
// Check HOME consistency
|
|
314
|
+
const homeMatch = envHome === osHome;
|
|
315
|
+
if (!homeMatch) {
|
|
316
|
+
warnings.push(
|
|
317
|
+
`$HOME (${envHome}) differs from os.homedir() (${osHome}). ` +
|
|
318
|
+
'This can happen under sudo, in Docker containers, or with nvm. ' +
|
|
319
|
+
'CAP uses $HOME for install paths — ensure it points to the correct user directory.'
|
|
320
|
+
);
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
// Check if install dir is a symlink
|
|
324
|
+
let isSymlink = false;
|
|
325
|
+
let symlinkTarget;
|
|
326
|
+
try {
|
|
327
|
+
const stat = fs.lstatSync(dir);
|
|
328
|
+
isSymlink = stat.isSymbolicLink();
|
|
329
|
+
if (isSymlink) {
|
|
330
|
+
symlinkTarget = fs.realpathSync(dir);
|
|
331
|
+
warnings.push(
|
|
332
|
+
`Install directory is a symlink: ${dir} -> ${symlinkTarget}`
|
|
333
|
+
);
|
|
334
|
+
}
|
|
335
|
+
} catch (_e) {
|
|
336
|
+
// Directory doesn't exist — the module integrity check will catch this
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
return {
|
|
340
|
+
envHome,
|
|
341
|
+
osHomedir: osHome,
|
|
342
|
+
homeMatch,
|
|
343
|
+
installDir: dir,
|
|
344
|
+
isSymlink,
|
|
345
|
+
symlinkTarget,
|
|
346
|
+
ok: warnings.length === 0,
|
|
347
|
+
warnings,
|
|
348
|
+
};
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
// @cap-todo(ac:F-058/AC-5) cap-doctor shall detect both install modes (npx vs plugin) and show the active mode.
|
|
352
|
+
// @cap-todo(ac:F-058/AC-6) Coexistence check — surface warning when both npx and plugin install modes are active.
|
|
353
|
+
/**
|
|
354
|
+
* @typedef {Object} InstallModeReport
|
|
355
|
+
* @property {boolean} npx - True when npx install footprint ($HOME/.claude/cap/) is present.
|
|
356
|
+
* @property {boolean} plugin - True when plugin install footprint is detected.
|
|
357
|
+
* @property {boolean} coexist - True when both npx and plugin are active simultaneously.
|
|
358
|
+
* @property {string} active - Primary active mode: 'npx', 'plugin', 'both', or 'none'.
|
|
359
|
+
* @property {string[]} pluginPaths - Absolute paths where plugin footprint was detected.
|
|
360
|
+
* @property {string} [npxPath] - Absolute path to the npx install dir when present.
|
|
361
|
+
* @property {string[]} warnings - Human-readable warnings (coexistence, missing, etc).
|
|
362
|
+
*/
|
|
363
|
+
|
|
364
|
+
/**
|
|
365
|
+
* @cap-decision Plugin footprint detection uses two filesystem-only heuristics:
|
|
366
|
+
* (1) presence of a Claude plugin cache entry under $HOME/.claude/plugins/cache/cap@*
|
|
367
|
+
* (any plugin installed via /plugin install or marketplace), and
|
|
368
|
+
* (2) presence of .claude-plugin/plugin.json in cwd with name === PLUGIN_NAME
|
|
369
|
+
* (local-dev checkout). The cwd manifest's name gate prevents foreign plugins
|
|
370
|
+
* living in the same repo from false-positive-registering as a CAP install.
|
|
371
|
+
* We do NOT read the CLAUDE_PLUGIN_ROOT env var. That variable is only set when
|
|
372
|
+
* Claude Code spawns a hook inside a plugin, never during a plain `npx cap:doctor`
|
|
373
|
+
* run from a shell, so relying on it would systematically false-negative on the
|
|
374
|
+
* CLI path where this function gets exercised most.
|
|
375
|
+
* @cap-decision npx footprint is the existing detection used by detectInstallDir() — $HOME/.claude/cap/
|
|
376
|
+
* written by the installer. No change to that detection.
|
|
377
|
+
*
|
|
378
|
+
* Detect which install mode(s) of CAP are active on this machine.
|
|
379
|
+
* @param {Object} [opts]
|
|
380
|
+
* @param {string} [opts.homeDir] - Override HOME for testing.
|
|
381
|
+
* @param {string} [opts.cwd] - Override cwd for testing.
|
|
382
|
+
* @returns {InstallModeReport}
|
|
383
|
+
*/
|
|
384
|
+
function detectInstallMode(opts) {
|
|
385
|
+
const options = opts || {};
|
|
386
|
+
const homeDir = options.homeDir || process.env.HOME || os.homedir();
|
|
387
|
+
const cwd = options.cwd || process.cwd();
|
|
388
|
+
const warnings = [];
|
|
389
|
+
|
|
390
|
+
// npx footprint: installer writes to $HOME/.claude/cap/
|
|
391
|
+
const npxPath = path.join(homeDir, '.claude', 'cap');
|
|
392
|
+
const npxPresent = fs.existsSync(npxPath);
|
|
393
|
+
|
|
394
|
+
// Plugin footprint: Claude Code caches installed plugins at $HOME/.claude/plugins/cache/<name>@<source>/
|
|
395
|
+
const pluginPaths = [];
|
|
396
|
+
// Fallback to the hard-coded plugin name when cap-plugin-manifest.cjs is not
|
|
397
|
+
// loadable (install-hardening fixture path). The name hasn't changed since
|
|
398
|
+
// F-058 and is covered by a dedicated contract test there.
|
|
399
|
+
const pluginName = (pluginManifest && pluginManifest.PLUGIN_NAME) || 'cap-pro';
|
|
400
|
+
const legacyPluginName = (pluginManifest && pluginManifest.LEGACY_PLUGIN_NAME) || 'cap';
|
|
401
|
+
// Accept both the current plugin manifest (cap-pro) and the legacy one (cap)
|
|
402
|
+
// so a user with a leftover `code-as-plan@7.x` install is still detected.
|
|
403
|
+
const isCapManifest = (pluginManifest && pluginManifest.isCapPluginManifest)
|
|
404
|
+
|| ((m) => !!(m && typeof m === 'object' && (m.name === pluginName || m.name === legacyPluginName)));
|
|
405
|
+
const pluginCacheDir = path.join(homeDir, '.claude', 'plugins', 'cache');
|
|
406
|
+
if (fs.existsSync(pluginCacheDir)) {
|
|
407
|
+
try {
|
|
408
|
+
const entries = fs.readdirSync(pluginCacheDir);
|
|
409
|
+
const capPrefix = `${pluginName}@`;
|
|
410
|
+
const legacyPrefix = `${legacyPluginName}@`;
|
|
411
|
+
for (const entry of entries) {
|
|
412
|
+
if (
|
|
413
|
+
entry === pluginName || entry.startsWith(capPrefix) ||
|
|
414
|
+
entry === legacyPluginName || entry.startsWith(legacyPrefix)
|
|
415
|
+
) {
|
|
416
|
+
pluginPaths.push(path.join(pluginCacheDir, entry));
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
} catch (err) {
|
|
420
|
+
// Surface unreadable cache directories so the user learns why detection came up empty
|
|
421
|
+
// instead of silently reporting "no plugin installed".
|
|
422
|
+
const code = err && err.code ? err.code : 'unknown';
|
|
423
|
+
warnings.push(`Plugin cache directory is unreadable (${code}): ${pluginCacheDir}`);
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
// Local-dev plugin footprint: .claude-plugin/plugin.json in cwd with name === PLUGIN_NAME.
|
|
428
|
+
// Foreign manifests (a different plugin living in the same repo) must not register as a CAP install.
|
|
429
|
+
const localManifest = path.join(cwd, '.claude-plugin', 'plugin.json');
|
|
430
|
+
if (fs.existsSync(localManifest)) {
|
|
431
|
+
try {
|
|
432
|
+
const raw = fs.readFileSync(localManifest, 'utf8');
|
|
433
|
+
const parsed = JSON.parse(raw);
|
|
434
|
+
if (isCapManifest(parsed)) {
|
|
435
|
+
pluginPaths.push(localManifest);
|
|
436
|
+
}
|
|
437
|
+
} catch (_err) {
|
|
438
|
+
// Malformed manifest JSON: do not count as a CAP footprint. The other doctor checks will
|
|
439
|
+
// separately flag the broken file via module/config validation when in scope.
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
const pluginPresent = pluginPaths.length > 0;
|
|
444
|
+
const coexist = npxPresent && pluginPresent;
|
|
445
|
+
|
|
446
|
+
let active;
|
|
447
|
+
if (coexist) {
|
|
448
|
+
active = 'both';
|
|
449
|
+
} else if (npxPresent) {
|
|
450
|
+
active = 'npx';
|
|
451
|
+
} else if (pluginPresent) {
|
|
452
|
+
active = 'plugin';
|
|
453
|
+
} else {
|
|
454
|
+
active = 'none';
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
// @cap-todo(ac:F-058/AC-6) Emit warning (not hard failure) when both modes coexist.
|
|
458
|
+
if (coexist) {
|
|
459
|
+
warnings.push(
|
|
460
|
+
'Both npx and plugin install modes are active. ' +
|
|
461
|
+
'Commands and hooks may be registered twice. ' +
|
|
462
|
+
'Recommended: pick one install path (npx is primary) and remove the other to avoid duplicate registration.'
|
|
463
|
+
);
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
const report = {
|
|
467
|
+
npx: npxPresent,
|
|
468
|
+
plugin: pluginPresent,
|
|
469
|
+
coexist,
|
|
470
|
+
active,
|
|
471
|
+
pluginPaths,
|
|
472
|
+
warnings,
|
|
473
|
+
};
|
|
474
|
+
if (npxPresent) {
|
|
475
|
+
report.npxPath = npxPath;
|
|
476
|
+
}
|
|
477
|
+
return report;
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
/**
|
|
481
|
+
* Run full doctor check.
|
|
482
|
+
* @param {string} [projectRoot] - Optional project root for project-specific checks
|
|
483
|
+
* @returns {DoctorReport}
|
|
484
|
+
*/
|
|
485
|
+
function runDoctor(projectRoot) {
|
|
486
|
+
const tools = [];
|
|
487
|
+
|
|
488
|
+
// Required tools
|
|
489
|
+
const node = checkTool('node --version');
|
|
490
|
+
tools.push({
|
|
491
|
+
name: 'Node.js',
|
|
492
|
+
...node,
|
|
493
|
+
required: true,
|
|
494
|
+
purpose: 'CAP runtime (>= 20.0.0 required)',
|
|
495
|
+
installHint: 'https://nodejs.org/ or: curl -fsSL https://fnm.vercel.app/install | bash && fnm install --lts',
|
|
496
|
+
});
|
|
497
|
+
|
|
498
|
+
const npm = checkTool('npm --version');
|
|
499
|
+
tools.push({
|
|
500
|
+
name: 'npm',
|
|
501
|
+
...npm,
|
|
502
|
+
required: true,
|
|
503
|
+
purpose: 'Package management and npx tool execution',
|
|
504
|
+
installHint: 'Comes with Node.js',
|
|
505
|
+
});
|
|
506
|
+
|
|
507
|
+
const git = checkTool('git --version');
|
|
508
|
+
// git --version returns "git version 2.45.0"
|
|
509
|
+
if (git.ok) git.version = git.version.replace('git version ', '');
|
|
510
|
+
tools.push({
|
|
511
|
+
name: 'git',
|
|
512
|
+
...git,
|
|
513
|
+
required: true,
|
|
514
|
+
purpose: 'Version control, commit history for /cap:report and /cap:review',
|
|
515
|
+
installHint: 'https://git-scm.com/downloads or: brew install git',
|
|
516
|
+
});
|
|
517
|
+
|
|
518
|
+
// Optional tools -- CAP works without them but with reduced functionality
|
|
519
|
+
const ctx7 = checkTool('npx ctx7@latest --version', 15000);
|
|
520
|
+
tools.push({
|
|
521
|
+
name: 'Context7 (ctx7)',
|
|
522
|
+
...ctx7,
|
|
523
|
+
required: false,
|
|
524
|
+
purpose: 'Library documentation fetching for /cap:init and /cap:refresh-docs',
|
|
525
|
+
installHint: 'npm install -g ctx7 (or CAP uses npx ctx7@latest on demand)',
|
|
526
|
+
});
|
|
527
|
+
|
|
528
|
+
// @cap-todo(ac:F-053/AC-5) Skip c8 check on Node >= 20 — native --experimental-test-coverage covers /cap:test-audit's needs.
|
|
529
|
+
const nodeMajor = parseInt((process.versions.node || '0').split('.')[0], 10) || 0;
|
|
530
|
+
if (nodeMajor < 20) {
|
|
531
|
+
const c8 = checkTool('npx c8 --version');
|
|
532
|
+
tools.push({
|
|
533
|
+
name: 'c8',
|
|
534
|
+
...c8,
|
|
535
|
+
required: false,
|
|
536
|
+
purpose: 'Code coverage for /cap:test-audit (Node < 20; Node 20+ uses native coverage)',
|
|
537
|
+
installHint: 'npm install -D c8',
|
|
538
|
+
});
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
// Project-specific checks (only if projectRoot provided)
|
|
542
|
+
if (projectRoot) {
|
|
543
|
+
const pkgPath = path.join(projectRoot, 'package.json');
|
|
544
|
+
if (fs.existsSync(pkgPath)) {
|
|
545
|
+
try {
|
|
546
|
+
const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8'));
|
|
547
|
+
const allDeps = { ...pkg.dependencies, ...pkg.devDependencies };
|
|
548
|
+
|
|
549
|
+
// Check vitest if project uses it
|
|
550
|
+
if (allDeps.vitest || fs.existsSync(path.join(projectRoot, 'vitest.config.ts')) || fs.existsSync(path.join(projectRoot, 'vitest.config.js'))) {
|
|
551
|
+
const vitest = checkTool('npx vitest --version');
|
|
552
|
+
tools.push({
|
|
553
|
+
name: 'vitest',
|
|
554
|
+
...vitest,
|
|
555
|
+
required: false,
|
|
556
|
+
purpose: 'TypeScript/SDK test runner (detected in this project)',
|
|
557
|
+
installHint: 'npm install -D vitest',
|
|
558
|
+
});
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
// Check fast-check if property tests are desired
|
|
562
|
+
if (allDeps['fast-check']) {
|
|
563
|
+
tools.push({
|
|
564
|
+
name: 'fast-check',
|
|
565
|
+
ok: true,
|
|
566
|
+
version: allDeps['fast-check'].replace('^', '').replace('~', ''),
|
|
567
|
+
required: false,
|
|
568
|
+
purpose: 'Property-based testing for business logic invariants',
|
|
569
|
+
installHint: 'npm install -D fast-check',
|
|
570
|
+
});
|
|
571
|
+
} else {
|
|
572
|
+
tools.push({
|
|
573
|
+
name: 'fast-check',
|
|
574
|
+
ok: false,
|
|
575
|
+
version: 'not installed',
|
|
576
|
+
required: false,
|
|
577
|
+
purpose: 'Property-based testing for business logic invariants (recommended)',
|
|
578
|
+
installHint: 'npm install -D fast-check',
|
|
579
|
+
});
|
|
580
|
+
}
|
|
581
|
+
} catch (_e) { /* malformed package.json -- skip project-specific checks */ }
|
|
582
|
+
}
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
// @cap-todo(ac:F-019/AC-4) Module integrity check runs automatically as part of /cap:doctor
|
|
586
|
+
const moduleResult = checkModuleIntegrity();
|
|
587
|
+
const platformResult = checkPlatformPaths();
|
|
588
|
+
// @cap-todo(ac:F-058/AC-5) Install-mode detection runs automatically and is surfaced in the doctor report.
|
|
589
|
+
const installMode = detectInstallMode();
|
|
590
|
+
|
|
591
|
+
// Compute summary
|
|
592
|
+
const requiredTools = tools.filter(t => t.required);
|
|
593
|
+
const optionalTools = tools.filter(t => !t.required);
|
|
594
|
+
|
|
595
|
+
const report = {
|
|
596
|
+
tools,
|
|
597
|
+
requiredOk: requiredTools.filter(t => t.ok).length,
|
|
598
|
+
requiredTotal: requiredTools.length,
|
|
599
|
+
optionalOk: optionalTools.filter(t => t.ok).length,
|
|
600
|
+
optionalTotal: optionalTools.length,
|
|
601
|
+
healthy: requiredTools.every(t => t.ok) && moduleResult.modulesOk === moduleResult.modulesTotal,
|
|
602
|
+
installCommands: [],
|
|
603
|
+
modules: moduleResult.modules,
|
|
604
|
+
modulesOk: moduleResult.modulesOk,
|
|
605
|
+
modulesTotal: moduleResult.modulesTotal,
|
|
606
|
+
platformPaths: platformResult,
|
|
607
|
+
installMode,
|
|
608
|
+
};
|
|
609
|
+
|
|
610
|
+
// Build install commands for missing tools
|
|
611
|
+
const missingOptional = optionalTools.filter(t => !t.ok);
|
|
612
|
+
const npmInstallDev = missingOptional
|
|
613
|
+
.filter(t => t.installHint.startsWith('npm install -D'))
|
|
614
|
+
.map(t => t.name.toLowerCase().replace('context7 (ctx7)', 'ctx7'));
|
|
615
|
+
|
|
616
|
+
if (npmInstallDev.length > 0) {
|
|
617
|
+
report.installCommands.push(`npm install -D ${npmInstallDev.join(' ')}`);
|
|
618
|
+
}
|
|
619
|
+
|
|
620
|
+
const globalInstall = missingOptional
|
|
621
|
+
.filter(t => t.installHint.startsWith('npm install -g'));
|
|
622
|
+
for (const t of globalInstall) {
|
|
623
|
+
report.installCommands.push(t.installHint);
|
|
624
|
+
}
|
|
625
|
+
|
|
626
|
+
return report;
|
|
627
|
+
}
|
|
628
|
+
|
|
629
|
+
/**
|
|
630
|
+
* Format the doctor report as readable terminal output.
|
|
631
|
+
* @param {DoctorReport} report
|
|
632
|
+
* @returns {string}
|
|
633
|
+
*/
|
|
634
|
+
function formatReport(report) {
|
|
635
|
+
const lines = [];
|
|
636
|
+
lines.push('cap:doctor\n');
|
|
637
|
+
|
|
638
|
+
// Required tools
|
|
639
|
+
lines.push(' Required:');
|
|
640
|
+
for (const t of report.tools.filter(t => t.required)) {
|
|
641
|
+
const icon = t.ok ? ' ✓' : ' ✗';
|
|
642
|
+
const pad = ' '.repeat(Math.max(0, 18 - t.name.length));
|
|
643
|
+
lines.push(` ${icon} ${t.name}${pad}${t.version}${t.ok ? '' : ' ← ' + t.purpose}`);
|
|
644
|
+
}
|
|
645
|
+
|
|
646
|
+
lines.push('');
|
|
647
|
+
lines.push(' Optional:');
|
|
648
|
+
for (const t of report.tools.filter(t => !t.required)) {
|
|
649
|
+
const icon = t.ok ? ' ✓' : ' -';
|
|
650
|
+
const pad = ' '.repeat(Math.max(0, 18 - t.name.length));
|
|
651
|
+
const hint = t.ok ? '' : ` ← ${t.purpose}`;
|
|
652
|
+
lines.push(` ${icon} ${t.name}${pad}${t.version}${hint}`);
|
|
653
|
+
}
|
|
654
|
+
|
|
655
|
+
// Module integrity section
|
|
656
|
+
if (report.modules && report.modules.length > 0) {
|
|
657
|
+
lines.push('');
|
|
658
|
+
lines.push(' Module Integrity:');
|
|
659
|
+
const failedModules = report.modules.filter(m => !m.ok);
|
|
660
|
+
const passedCount = report.modulesOk || 0;
|
|
661
|
+
const totalCount = report.modulesTotal || 0;
|
|
662
|
+
|
|
663
|
+
if (failedModules.length === 0) {
|
|
664
|
+
lines.push(` ✓ All ${totalCount} CAP modules verified (exist + loadable)`);
|
|
665
|
+
} else {
|
|
666
|
+
lines.push(` ${passedCount}/${totalCount} modules OK — ${failedModules.length} failed:`);
|
|
667
|
+
for (const m of failedModules) {
|
|
668
|
+
lines.push(` ✗ ${m.name} ← ${m.error}`);
|
|
669
|
+
}
|
|
670
|
+
}
|
|
671
|
+
}
|
|
672
|
+
|
|
673
|
+
// Platform path section
|
|
674
|
+
if (report.platformPaths) {
|
|
675
|
+
const pp = report.platformPaths;
|
|
676
|
+
if (!pp.ok) {
|
|
677
|
+
lines.push('');
|
|
678
|
+
lines.push(' Platform Paths:');
|
|
679
|
+
for (const w of pp.warnings) {
|
|
680
|
+
lines.push(` ⚠ ${w}`);
|
|
681
|
+
}
|
|
682
|
+
}
|
|
683
|
+
}
|
|
684
|
+
|
|
685
|
+
// @cap-todo(ac:F-058/AC-5) Render active install mode in the doctor output.
|
|
686
|
+
// @cap-todo(ac:F-058/AC-6) Render coexistence warning when both modes are active.
|
|
687
|
+
if (report.installMode) {
|
|
688
|
+
const im = report.installMode;
|
|
689
|
+
lines.push('');
|
|
690
|
+
lines.push(' Install mode:');
|
|
691
|
+
let modeLine;
|
|
692
|
+
if (im.active === 'both') {
|
|
693
|
+
modeLine = ' ⚠ npx (primary) + plugin (secondary) — coexistence detected';
|
|
694
|
+
} else if (im.active === 'npx') {
|
|
695
|
+
modeLine = ' ✓ npx (primary)';
|
|
696
|
+
} else if (im.active === 'plugin') {
|
|
697
|
+
modeLine = ' ✓ plugin';
|
|
698
|
+
} else {
|
|
699
|
+
modeLine = ' - none detected';
|
|
700
|
+
}
|
|
701
|
+
lines.push(` ${modeLine}`);
|
|
702
|
+
for (const w of im.warnings) {
|
|
703
|
+
lines.push(` ⚠ ${w}`);
|
|
704
|
+
}
|
|
705
|
+
}
|
|
706
|
+
|
|
707
|
+
lines.push('');
|
|
708
|
+
lines.push(` Required: ${report.requiredOk}/${report.requiredTotal} OK`);
|
|
709
|
+
lines.push(` Optional: ${report.optionalOk}/${report.optionalTotal} OK`);
|
|
710
|
+
if (report.modulesTotal != null) {
|
|
711
|
+
lines.push(` Modules: ${report.modulesOk}/${report.modulesTotal} OK`);
|
|
712
|
+
}
|
|
713
|
+
|
|
714
|
+
if (!report.healthy) {
|
|
715
|
+
lines.push('');
|
|
716
|
+
const toolsMissing = report.requiredOk < report.requiredTotal;
|
|
717
|
+
const modulesMissing = report.modulesOk != null && report.modulesOk < report.modulesTotal;
|
|
718
|
+
if (toolsMissing && modulesMissing) {
|
|
719
|
+
lines.push(' ✗ UNHEALTHY — required tools missing and module integrity failures detected.');
|
|
720
|
+
} else if (modulesMissing) {
|
|
721
|
+
lines.push(' ✗ UNHEALTHY — module integrity failures detected. Try: npx cap-pro@latest --force');
|
|
722
|
+
} else {
|
|
723
|
+
lines.push(' ✗ UNHEALTHY — required tools missing. CAP cannot function correctly.');
|
|
724
|
+
}
|
|
725
|
+
}
|
|
726
|
+
|
|
727
|
+
if (report.installCommands.length > 0) {
|
|
728
|
+
lines.push('');
|
|
729
|
+
lines.push(' To install missing tools:');
|
|
730
|
+
for (const cmd of report.installCommands) {
|
|
731
|
+
lines.push(` ${cmd}`);
|
|
732
|
+
}
|
|
733
|
+
}
|
|
734
|
+
|
|
735
|
+
const allModulesOk = report.modulesOk == null || report.modulesOk === report.modulesTotal;
|
|
736
|
+
if (report.healthy && report.optionalOk === report.optionalTotal && allModulesOk) {
|
|
737
|
+
lines.push('');
|
|
738
|
+
lines.push(' All tools and modules verified. CAP is fully operational.');
|
|
739
|
+
}
|
|
740
|
+
|
|
741
|
+
return lines.join('\n');
|
|
742
|
+
}
|
|
743
|
+
|
|
744
|
+
module.exports = {
|
|
745
|
+
checkTool,
|
|
746
|
+
runDoctor,
|
|
747
|
+
formatReport,
|
|
748
|
+
checkModuleIntegrity,
|
|
749
|
+
checkPlatformPaths,
|
|
750
|
+
detectInstallMode,
|
|
751
|
+
CAP_MODULE_MANIFEST,
|
|
752
|
+
};
|