| 1 | // Package capdiag collects read-only capability diagnostics for Skills, |
| 2 | // Commands, Hooks, plugin packages, MCP servers, and instruction docs. |
| 3 | // CLI and desktop share Collect; only CLI --live starts MCP processes. |
| 4 | package capdiag |
| 5 | |
| 6 | import ( |
| 7 | "time" |
| 8 | |
| 9 | "reasonix/internal/plugin" |
| 10 | ) |
| 11 | |
| 12 | // SchemaVersion is the JSON report version. Bump only on breaking shape changes. |
| 13 | const SchemaVersion = 1 |
| 14 | |
| 15 | // Options configure Collect. |
| 16 | type Options struct { |
| 17 | // Root is the workspace root (default: current working directory). |
| 18 | Root string |
| 19 | // Live starts automatic MCP servers in an isolated Host (CLI only). |
| 20 | Live bool |
| 21 | // LiveTimeout is the per-server probe timeout when Live is true. |
| 22 | LiveTimeout time.Duration |
| 23 | // RuntimeHost, when set, merges connected/failed/deferred status from an |
| 24 | // existing Host (desktop active session). Collect never starts MCP when |
| 25 | // RuntimeHost is set unless Live is also true (desktop passes Live=false). |
| 26 | RuntimeHost *plugin.Host |
| 27 | // HomeDir and ReasonixHomeDir override discovery roots (tests). |
| 28 | HomeDir string |
| 29 | ReasonixHomeDir string |
| 30 | } |
| 31 | |
| 32 | // Report is the stable capability diagnostics payload. |
| 33 | type Report struct { |
| 34 | SchemaVersion int `json:"schema_version"` |
| 35 | Root string `json:"root"` |
| 36 | Live bool `json:"live"` |
| 37 | Summary Summary `json:"summary"` |
| 38 | Instructions InstructionsReport `json:"instructions"` |
| 39 | Skills AssetReport `json:"skills"` |
| 40 | Commands AssetReport `json:"commands"` |
| 41 | Hooks HookReport `json:"hooks"` |
| 42 | Plugins PluginPackageReport `json:"plugins"` |
| 43 | MCP MCPReport `json:"mcp"` |
| 44 | Issues []Issue `json:"issues"` |
| 45 | } |
| 46 | |
| 47 | // Summary counts issues and resources. |
| 48 | type Summary struct { |
| 49 | Errors int `json:"errors"` |
| 50 | Warnings int `json:"warnings"` |
| 51 | Infos int `json:"infos"` |
| 52 | |
| 53 | Instructions int `json:"instructions"` |
| 54 | Skills int `json:"skills"` |
| 55 | Commands int `json:"commands"` |
| 56 | Hooks int `json:"hooks"` |
| 57 | Plugins int `json:"plugins"` |
| 58 | MCPServers int `json:"mcp_servers"` |
| 59 | } |
| 60 | |
| 61 | // Issue is one diagnostic finding with a stable code. |
| 62 | type Issue struct { |
| 63 | Severity string `json:"severity"` // error | warning | info |
| 64 | Code string `json:"code"` |
| 65 | Subsystem string `json:"subsystem"` |
| 66 | Name string `json:"name,omitempty"` |
| 67 | Source string `json:"source,omitempty"` |
| 68 | Message string `json:"message"` |
| 69 | Remediation string `json:"remediation,omitempty"` |
| 70 | SettingsTab string `json:"settings_tab,omitempty"` |
| 71 | } |
| 72 | |
| 73 | // InstructionsReport lists loaded instruction/memory docs in load order. |
| 74 | type InstructionsReport struct { |
| 75 | Docs []InstructionDoc `json:"docs"` |
| 76 | } |
| 77 | |
| 78 | // InstructionDoc is one REASONIX.md / AGENTS.md / CLAUDE.md source. |
| 79 | type InstructionDoc struct { |
| 80 | Path string `json:"path"` |
| 81 | Scope string `json:"scope"` |
| 82 | Directory string `json:"directory,omitempty"` |
| 83 | Depth int `json:"depth"` |
| 84 | Order int `json:"order"` |
| 85 | } |
| 86 | |
| 87 | // AssetReport covers skills or commands. |
| 88 | type AssetReport struct { |
| 89 | Roots []RootInfo `json:"roots"` |
| 90 | Entries []AssetEntry `json:"entries"` |
| 91 | Winners int `json:"winners"` |
| 92 | Shadowed int `json:"shadowed"` |
| 93 | Disabled int `json:"disabled,omitempty"` |
| 94 | ParseErrors int `json:"parse_errors,omitempty"` |
| 95 | } |
| 96 | |
| 97 | // RootInfo is one discovery directory. |
| 98 | type RootInfo struct { |
| 99 | Path string `json:"path"` |
| 100 | Scope string `json:"scope,omitempty"` |
| 101 | Status string `json:"status"` |
| 102 | } |
| 103 | |
| 104 | // AssetEntry is one skill or command candidate. |
| 105 | type AssetEntry struct { |
| 106 | Name string `json:"name"` |
| 107 | Description string `json:"description,omitempty"` |
| 108 | Scope string `json:"scope,omitempty"` |
| 109 | Path string `json:"path"` |
| 110 | Status string `json:"status"` // winner | shadowed | disabled | error |
| 111 | WinnerPath string `json:"winner_path,omitempty"` |
| 112 | Error string `json:"error,omitempty"` |
| 113 | RunAs string `json:"run_as,omitempty"` |
| 114 | } |
| 115 | |
| 116 | // HookReport covers hook configuration. |
| 117 | type HookReport struct { |
| 118 | // TrustedProject is retained in schema v1 for compatibility. Project hooks |
| 119 | // are enabled by default, so this is true whenever a project root is present. |
| 120 | TrustedProject bool `json:"trusted_project"` |
| 121 | ProjectDefines bool `json:"project_defines_hooks"` |
| 122 | Sources []HookSource `json:"sources"` |
| 123 | Entries []HookEntry `json:"entries"` |
| 124 | } |
| 125 | |
| 126 | // HookSource is one settings/manifest source. |
| 127 | type HookSource struct { |
| 128 | Scope string `json:"scope"` |
| 129 | Path string `json:"path"` |
| 130 | Status string `json:"status"` |
| 131 | HookCount int `json:"hook_count"` |
| 132 | ParseError string `json:"parse_error,omitempty"` |
| 133 | } |
| 134 | |
| 135 | // HookEntry is one configured hook. |
| 136 | type HookEntry struct { |
| 137 | Event string `json:"event"` |
| 138 | Match string `json:"match,omitempty"` |
| 139 | Command string `json:"command,omitempty"` |
| 140 | ContextFile string `json:"context_file,omitempty"` |
| 141 | Description string `json:"description,omitempty"` |
| 142 | TimeoutMS int `json:"timeout_ms,omitempty"` |
| 143 | Scope string `json:"scope"` |
| 144 | Source string `json:"source"` |
| 145 | Blocking bool `json:"blocking"` |
| 146 | } |
| 147 | |
| 148 | // PluginPackageReport covers installed plugin packages. |
| 149 | type PluginPackageReport struct { |
| 150 | StatePath string `json:"state_path,omitempty"` |
| 151 | Packages []PluginPackageInfo `json:"packages"` |
| 152 | } |
| 153 | |
| 154 | // PluginPackageInfo is one installed package. |
| 155 | type PluginPackageInfo struct { |
| 156 | Name string `json:"name"` |
| 157 | Enabled bool `json:"enabled"` |
| 158 | Version string `json:"version,omitempty"` |
| 159 | Root string `json:"root"` |
| 160 | ManifestKind string `json:"manifest_kind,omitempty"` |
| 161 | Skills int `json:"skills"` |
| 162 | Commands int `json:"commands"` |
| 163 | Hooks int `json:"hooks"` |
| 164 | MCPServers int `json:"mcp_servers"` |
| 165 | // Prompts, Themes, and Runtime are the Manifest v1 additions. They stay |
| 166 | // omitempty so schema v1 consumers see no shape change for legacy |
| 167 | // packages. |
| 168 | Prompts int `json:"prompts,omitempty"` |
| 169 | Themes int `json:"themes,omitempty"` |
| 170 | Runtime bool `json:"runtime,omitempty"` |
| 171 | Warnings []string `json:"warnings,omitempty"` |
| 172 | Status string `json:"status"` // ok | missing_root | invalid_manifest | disabled |
| 173 | } |
| 174 | |
| 175 | // MCPReport covers merged MCP server configuration and optional live/runtime state. |
| 176 | type MCPReport struct { |
| 177 | Servers []MCPServerInfo `json:"servers"` |
| 178 | } |
| 179 | |
| 180 | // MCPServerInfo is one merged MCP server. |
| 181 | type MCPServerInfo struct { |
| 182 | Name string `json:"name"` |
| 183 | Source string `json:"source,omitempty"` // user_config | project_config | project_mcp_json | plugin_package | host_session |
| 184 | SourcePath string `json:"source_path,omitempty"` |
| 185 | Effective bool `json:"effective"` |
| 186 | PackageOwner string `json:"package_owner,omitempty"` |
| 187 | Transport string `json:"transport"` |
| 188 | StartIntent string `json:"start_intent"` // automatic | off |
| 189 | Command string `json:"command,omitempty"` // redacted path form |
| 190 | URLHost string `json:"url_host,omitempty"` |
| 191 | EnvKeys []string `json:"env_keys,omitempty"` |
| 192 | HeaderKeys []string `json:"header_keys,omitempty"` |
| 193 | RuntimeStatus string `json:"runtime_status,omitempty"` // connected | failed | deferred | disabled | skipped | probed |
| 194 | ToolCount int `json:"tool_count,omitempty"` |
| 195 | Tools []MCPToolInfo `json:"tools,omitempty"` |
| 196 | Error string `json:"error,omitempty"` |
| 197 | StartupStage string `json:"startup_stage,omitempty"` |
| 198 | StartupElapsedMS int64 `json:"startup_elapsed_ms,omitempty"` |
| 199 | Stderr string `json:"stderr,omitempty"` |
| 200 | } |
| 201 | |
| 202 | // MCPToolInfo is one tool discovered during live/runtime probe. |
| 203 | type MCPToolInfo struct { |
| 204 | Name string `json:"name"` |
| 205 | ReadOnlyHint bool `json:"read_only_hint,omitempty"` |
| 206 | DestructiveHint bool `json:"destructive_hint,omitempty"` |
| 207 | } |
| 208 |