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