one-code-extension 0.1.4 → 0.3.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/LICENSE +20 -0
- package/README.md +200 -172
- package/agents/explore.md +9 -6
- package/agents/general-purpose.md +1 -1
- package/agents/plan.md +6 -4
- package/docs/guide/README.md +52 -0
- package/docs/guide/bring-your-claude-code-setup.md +126 -0
- package/docs/guide/configuration.md +171 -0
- package/docs/guide/differences-from-claude-code.md +150 -0
- package/docs/guide/doctor.md +86 -0
- package/docs/guide/hooks.md +109 -0
- package/docs/guide/installation.md +124 -0
- package/docs/guide/permissions-modes-and-auto-mode.md +450 -0
- package/docs/guide/providers-and-models.md +194 -0
- package/docs/guide/reference.md +224 -0
- package/docs/guide/sessions-and-context.md +190 -0
- package/docs/guide/skills-plugins-and-mcp.md +176 -0
- package/docs/guide/subagents-and-workflows.md +193 -0
- package/docs/guide/tasks-and-background-work.md +111 -0
- package/docs/guide/terminal-interface.md +155 -0
- package/docs/guide/tools.md +175 -0
- package/docs/guide/troubleshooting.md +175 -0
- package/extensions/ask-user/index.ts +65 -71
- package/extensions/ask-user/questions.ts +15 -28
- package/extensions/ask-user/widget.ts +533 -0
- package/extensions/auto-mode/actions.ts +9 -26
- package/extensions/auto-mode/classifier-prompt.ts +127 -0
- package/extensions/auto-mode/classifier.ts +305 -227
- package/extensions/auto-mode/config.ts +279 -77
- package/extensions/auto-mode/defaults.ts +17 -60
- package/extensions/auto-mode/instructions.ts +2 -1
- package/extensions/auto-mode/model-picker.ts +53 -31
- package/extensions/auto-mode/model-select.ts +153 -82
- package/extensions/auto-mode/paths.ts +13 -1
- package/extensions/auto-mode/pause.ts +8 -0
- package/extensions/auto-mode/prompt.ts +205 -238
- package/extensions/auto-mode/recoverability.ts +229 -0
- package/extensions/auto-mode/rules.ts +92 -75
- package/extensions/auto-mode/safety-floor.ts +43 -12
- package/extensions/auto-mode/sensitive.ts +11 -0
- package/extensions/auto-mode/setup-run.ts +245 -0
- package/extensions/auto-mode/setup.ts +235 -0
- package/extensions/auto-mode/shell-analysis.ts +332 -26
- package/extensions/auto-mode/transcript.ts +102 -0
- package/extensions/background/index.ts +256 -43
- package/extensions/background/monitor-batch.ts +58 -0
- package/extensions/background/registry.ts +9 -0
- package/extensions/background/wakeup.ts +45 -0
- package/extensions/bash/background.ts +47 -22
- package/extensions/bash/guards.ts +229 -0
- package/extensions/bash/index.ts +81 -19
- package/extensions/branding/index.ts +44 -18
- package/extensions/branding/startup.ts +23 -8
- package/extensions/claude-compat/index.ts +17 -2
- package/extensions/claude-context/index.ts +41 -12
- package/extensions/clear/index.ts +27 -0
- package/extensions/compaction/fit.ts +121 -0
- package/extensions/compaction/index.ts +176 -71
- package/extensions/compaction/prompt.ts +21 -1
- package/extensions/context-budget/budget.ts +61 -0
- package/extensions/context-budget/index.ts +66 -0
- package/extensions/context-management/index.ts +40 -16
- package/extensions/doctor/build.ts +226 -0
- package/extensions/doctor/cli.ts +99 -0
- package/extensions/doctor/compat.ts +532 -0
- package/extensions/doctor/dependencies.ts +186 -0
- package/extensions/doctor/fix-prompt.ts +125 -0
- package/extensions/doctor/index.ts +308 -0
- package/extensions/doctor/models.ts +231 -0
- package/extensions/doctor/presets.ts +208 -0
- package/extensions/doctor/report.ts +189 -0
- package/extensions/doctor/update-lookup.ts +46 -0
- package/extensions/doctor/viewer.ts +124 -0
- package/extensions/effort/index.ts +41 -4
- package/extensions/effort/slider.ts +33 -2
- package/extensions/file-tracker/index.ts +143 -20
- package/extensions/file-tracker/replay.ts +46 -0
- package/extensions/file-tracker/tracker.ts +51 -4
- package/extensions/footer/footer-line.ts +207 -0
- package/extensions/footer/index.ts +152 -0
- package/extensions/footer/pr.ts +53 -0
- package/extensions/hooks/executor.ts +57 -20
- package/extensions/hooks/formatter-notice.ts +76 -0
- package/extensions/hooks/index.ts +314 -73
- package/extensions/hooks/matcher.ts +35 -1
- package/extensions/hooks/plugin-hooks.ts +3 -3
- package/extensions/hooks/protocol.ts +58 -0
- package/extensions/hooks/subagent-bridge.ts +77 -0
- package/extensions/hooks/trust.ts +15 -4
- package/extensions/init/index.ts +26 -0
- package/extensions/init/prompt.ts +40 -0
- package/extensions/interrupted/index.ts +35 -0
- package/extensions/interrupted/line.ts +9 -0
- package/extensions/lib/abort.ts +15 -0
- package/extensions/lib/agent-loader.ts +183 -0
- package/extensions/lib/anthropic-payload.ts +15 -0
- package/extensions/lib/atomic-write.ts +56 -0
- package/extensions/lib/bridge-watch.ts +20 -0
- package/extensions/lib/cache-retention.ts +60 -0
- package/extensions/lib/capability-index.ts +466 -0
- package/extensions/lib/claude-context.ts +318 -27
- package/extensions/lib/claude-settings.ts +64 -0
- package/extensions/lib/consent-preview.ts +30 -0
- package/extensions/lib/deferred.ts +144 -13
- package/extensions/lib/favorites.ts +43 -0
- package/extensions/lib/fetch-timeout.ts +15 -0
- package/extensions/lib/frontmatter.ts +31 -0
- package/extensions/lib/git.ts +44 -2
- package/extensions/lib/hook-gate.ts +90 -0
- package/extensions/lib/interrupt.ts +94 -0
- package/extensions/lib/mcp-overrides.ts +61 -0
- package/extensions/lib/mcp-share.ts +25 -0
- package/extensions/lib/mcp-status.ts +34 -0
- package/extensions/lib/memory.ts +41 -6
- package/extensions/lib/model-facts.generated.json +2480 -0
- package/extensions/lib/model-facts.ts +162 -0
- package/extensions/lib/model-policy.ts +145 -189
- package/extensions/lib/model-tier.ts +480 -64
- package/extensions/lib/model-unusable.ts +37 -0
- package/extensions/lib/notifications.ts +320 -1
- package/extensions/lib/one-code-settings.ts +63 -0
- package/extensions/lib/original-command.ts +71 -0
- package/extensions/lib/package-version.ts +31 -0
- package/extensions/lib/paths.ts +39 -4
- package/extensions/lib/permission-gate.ts +192 -0
- package/extensions/lib/persisted-output.ts +18 -0
- package/extensions/lib/pi-ai-estimate.ts +160 -0
- package/extensions/lib/pi-version.ts +1 -1
- package/extensions/lib/plan-mode-channels.ts +11 -0
- package/extensions/lib/plugin-overrides.ts +24 -0
- package/extensions/lib/plugin-root.ts +44 -0
- package/extensions/lib/plugins.ts +233 -54
- package/extensions/lib/prefix-warm-gate.ts +157 -0
- package/extensions/lib/process-tree.ts +56 -0
- package/extensions/lib/reminders.ts +321 -68
- package/extensions/lib/scratchpad.ts +11 -8
- package/extensions/lib/session-lifecycle.ts +28 -0
- package/extensions/lib/settings-channels.ts +14 -0
- package/extensions/lib/shell-tasks.ts +54 -0
- package/extensions/lib/skill-overrides.ts +86 -0
- package/extensions/lib/skill-scan.ts +110 -0
- package/extensions/lib/text-width.ts +267 -0
- package/extensions/lib/tui-render.ts +281 -54
- package/extensions/lib/usage-bus.ts +75 -0
- package/extensions/lib/usage-tracker.ts +67 -0
- package/extensions/lib/worktree-isolation.ts +170 -0
- package/extensions/lsp/client.ts +69 -12
- package/extensions/lsp/format.ts +12 -2
- package/extensions/lsp/index.ts +273 -84
- package/extensions/lsp/install-hints.ts +43 -0
- package/extensions/lsp/keep-alive.ts +19 -0
- package/extensions/lsp/plugin-servers.ts +331 -0
- package/extensions/lsp/watcher.ts +157 -0
- package/extensions/mcp/announce.ts +99 -0
- package/extensions/mcp/client.ts +181 -22
- package/extensions/mcp/config.ts +55 -24
- package/extensions/mcp/index.ts +586 -86
- package/extensions/mcp/oauth/browser.ts +26 -0
- package/extensions/mcp/oauth/callback.ts +104 -0
- package/extensions/mcp/oauth/flow.ts +68 -0
- package/extensions/mcp/oauth/provider.ts +147 -0
- package/extensions/mcp/oauth/store.ts +86 -0
- package/extensions/mcp/panel/keys.ts +34 -0
- package/extensions/mcp/panel/model.ts +62 -0
- package/extensions/mcp/panel/render.ts +141 -0
- package/extensions/mcp/panel/state.ts +108 -0
- package/extensions/mcp/schema.ts +83 -6
- package/extensions/mcp/trust.ts +340 -0
- package/extensions/memory/entries.ts +163 -0
- package/extensions/memory/index.ts +119 -2
- package/extensions/memory/open-external.ts +97 -0
- package/extensions/memory/panel.ts +124 -0
- package/extensions/model-default/index.ts +74 -0
- package/extensions/notebook/index.ts +36 -18
- package/extensions/notebook/notebook.ts +110 -17
- package/extensions/permissions/index.ts +1012 -247
- package/extensions/permissions/matcher.ts +488 -57
- package/extensions/permissions/modes.ts +55 -7
- package/extensions/permissions/project-trust.ts +81 -0
- package/extensions/permissions/protected-paths.ts +16 -3
- package/extensions/permissions/rule-prose.ts +79 -0
- package/extensions/permissions/session-grant.ts +91 -0
- package/extensions/permissions/settings.ts +112 -33
- package/extensions/permissions/subagent-gate.ts +57 -0
- package/extensions/plan-mode/index.ts +143 -33
- package/extensions/plan-mode/reminder.ts +16 -19
- package/extensions/plan-mode/viewer.ts +40 -31
- package/extensions/plugins/counts.ts +68 -0
- package/extensions/plugins/index.ts +496 -32
- package/extensions/plugins/install/install.ts +167 -0
- package/extensions/plugins/install/paths.ts +23 -0
- package/extensions/plugins/install/registry.ts +72 -0
- package/extensions/plugins/marketplace/git.ts +103 -0
- package/extensions/plugins/marketplace/official.ts +36 -0
- package/extensions/plugins/marketplace/parse.ts +94 -0
- package/extensions/plugins/marketplace/registry.ts +60 -0
- package/extensions/plugins/marketplace/sync.ts +146 -0
- package/extensions/plugins/marketplace/types.ts +128 -0
- package/extensions/plugins/panel/keys.ts +52 -0
- package/extensions/plugins/panel/render.ts +315 -0
- package/extensions/plugins/panel/rows.ts +230 -0
- package/extensions/plugins/panel/state.ts +333 -0
- package/extensions/recap/index.ts +215 -0
- package/extensions/recap/prompt.ts +44 -0
- package/extensions/recap/scheduler.ts +87 -0
- package/extensions/search-tools/index.ts +0 -0
- package/extensions/search-tools/tools.ts +7 -5
- package/extensions/skill/index.ts +360 -34
- package/extensions/skill/invoke.ts +185 -0
- package/extensions/skill/listing.ts +21 -0
- package/extensions/skill/panel/keys.ts +41 -0
- package/extensions/skill/panel/render.ts +91 -0
- package/extensions/skill/panel/state.ts +115 -0
- package/extensions/spinner/index.ts +110 -0
- package/extensions/spinner/line.ts +51 -0
- package/extensions/spinner/verbs.ts +201 -0
- package/extensions/subagents/agents.ts +88 -44
- package/extensions/subagents/default-model.ts +62 -32
- package/extensions/subagents/delegation-steer.ts +23 -0
- package/extensions/subagents/hand-back-review.ts +67 -0
- package/extensions/subagents/index.ts +1545 -346
- package/extensions/subagents/live-runs.ts +293 -0
- package/extensions/subagents/model-select.ts +252 -82
- package/extensions/subagents/outcome.ts +90 -0
- package/extensions/subagents/panel-keys.ts +41 -0
- package/extensions/subagents/panel-render.ts +357 -0
- package/extensions/subagents/panel-widget.ts +330 -0
- package/extensions/subagents/pending-claim.ts +45 -0
- package/extensions/subagents/prose.ts +105 -0
- package/extensions/subagents/runner.ts +674 -0
- package/extensions/subagents/runs.ts +68 -1
- package/extensions/subagents/send-to-main-tool.ts +55 -0
- package/extensions/subagents/session-turns.ts +138 -0
- package/extensions/subagents/shell-panel.ts +198 -0
- package/extensions/subagents/usage.ts +16 -4
- package/extensions/subagents/worktree.ts +13 -0
- package/extensions/system-prompt/environment.ts +5 -3
- package/extensions/system-prompt/git-status.ts +93 -0
- package/extensions/system-prompt/index.ts +23 -1
- package/extensions/system-prompt/template.ts +34 -4
- package/extensions/system-prompt/tiers/common.ts +31 -6
- package/extensions/system-prompt/tiers/low.ts +13 -3
- package/extensions/system-prompt/tiers/mid.ts +21 -8
- package/extensions/system-reminder/index.ts +53 -9
- package/extensions/tasks/index.ts +65 -16
- package/extensions/tasks/store.ts +76 -3
- package/extensions/tool-search/announce.ts +42 -0
- package/extensions/tool-search/index.ts +136 -13
- package/extensions/turn-duration/index.ts +60 -0
- package/extensions/turn-duration/line.ts +22 -0
- package/extensions/turn-duration/span.ts +41 -0
- package/extensions/turn-duration/verbs.ts +24 -0
- package/extensions/web/backends.ts +421 -0
- package/extensions/web/index.ts +178 -21
- package/extensions/web-fetch/index.ts +74 -18
- package/extensions/web-fetch/summarize.ts +1 -1
- package/extensions/workflow/agent-session.ts +182 -112
- package/extensions/workflow/globals.ts +129 -106
- package/extensions/workflow/index.ts +343 -28
- package/extensions/workflow/records.ts +118 -0
- package/extensions/workflow/run-manager.ts +80 -31
- package/extensions/workflow/saved-workflows.ts +2 -1
- package/extensions/workflow/script-worker.mjs +110 -0
- package/extensions/workflow/types.ts +74 -8
- package/extensions/workflow/viewer.ts +562 -0
- package/extensions/workflow/vm-runtime.ts +118 -22
- package/extensions/workflow/widget.ts +126 -18
- package/extensions/worktree/guards.ts +240 -0
- package/extensions/worktree/index.ts +30 -5
- package/extensions/worktree/rewrite.ts +32 -16
- package/package.json +20 -10
- package/skills/code-review/SKILL.md +165 -0
- package/skills/fewer-permission-prompts/SKILL.md +61 -0
- package/skills/security-review/SKILL.md +178 -0
- package/skills/simplify/SKILL.md +70 -0
- package/themes/{one-code-light.json → onecode-light.json} +6 -1
- package/themes/{one-code.json → onecode.json} +6 -1
- package/types/app.d.ts +8 -1
- package/types/community-packages.d.ts +45 -2
- package/docs/decisions/auto-mode.md +0 -526
- package/docs/decisions/branding.md +0 -127
- package/docs/decisions/code-review-remediation.md +0 -106
- package/docs/decisions/compaction.md +0 -140
- package/docs/decisions/distribution.md +0 -168
- package/docs/decisions/hooks.md +0 -64
- package/docs/decisions/lsp.md +0 -58
- package/docs/decisions/mcp.md +0 -104
- package/docs/decisions/memory-state.md +0 -170
- package/docs/decisions/model-policy.md +0 -126
- package/docs/decisions/modes.md +0 -178
- package/docs/decisions/skills-plugins.md +0 -56
- package/docs/decisions/subagents-workflows.md +0 -225
- package/docs/decisions/system-prompt.md +0 -98
- package/docs/decisions/tools.md +0 -407
- package/docs/decisions/tui.md +0 -217
- package/docs/decisions.md +0 -148
- package/extensions/subagents/child.ts +0 -414
- package/extensions/subagents/rpc-turns.ts +0 -123
- package/extensions/todo/index.ts +0 -133
- package/extensions/workflow/permission-gate.ts +0 -71
|
@@ -0,0 +1,532 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The imported-configuration scan (pure fs): what of the user's Claude Code
|
|
3
|
+
* setup One Code found, what it reads from each file, and what it does NOT
|
|
4
|
+
* honour — the compatibility report behind "your setup works unchanged".
|
|
5
|
+
*
|
|
6
|
+
* Every settings file gets one row: present / valid / the keys One Code consumes
|
|
7
|
+
* from it (permissions, hooks, plugins, MCP policy, the one `env` variable it
|
|
8
|
+
* borrows) and the keys it leaves alone. "Leaves alone" is stated per file
|
|
9
|
+
* because the answer differs by scope on purpose — a repository's own
|
|
10
|
+
* `.claude/settings.json` may not grant itself `auto` mode or configure the
|
|
11
|
+
* auto-mode classifier (docs/decisions/modes.md, auto-mode.md) — and a user
|
|
12
|
+
* reading "works unchanged" must see exactly where it does not.
|
|
13
|
+
*
|
|
14
|
+
* Findings are the things silent elsewhere: a settings file that fails to
|
|
15
|
+
* parse (loaders skip it and the user believes its rules are in force), a
|
|
16
|
+
* `defaultMode` in a file that never contributes one, a CLAUDE.md over Claude
|
|
17
|
+
* Code's 40k-char soft limit, a hook or MCP config the loaders rejected.
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
import { existsSync, readdirSync, readFileSync, statSync } from "node:fs";
|
|
21
|
+
import { basename, join } from "node:path";
|
|
22
|
+
import { managedSettingsPaths } from "../auto-mode/config.ts";
|
|
23
|
+
import { loadPluginHooks } from "../hooks/plugin-hooks.ts";
|
|
24
|
+
import { type HooksSource, loadHookSettings } from "../hooks/settings.ts";
|
|
25
|
+
import { claudeJsonPath, claudeUserDir } from "../lib/paths.ts";
|
|
26
|
+
import { claudeUserSettingsPath, settingsPaths } from "../lib/claude-settings.ts";
|
|
27
|
+
import { discoverContextFilePaths } from "../lib/claude-context.ts";
|
|
28
|
+
import { CLAUDE_MD_CHAR_LIMIT, claudeMdLimitWarning, indexLimitStatus, projectMemoryDir } from "../lib/memory.ts";
|
|
29
|
+
import { readDisabledMcpServers } from "../lib/mcp-overrides.ts";
|
|
30
|
+
import { oneCodeProjectSettingsPath, oneCodeSettingsPath } from "../lib/one-code-settings.ts";
|
|
31
|
+
import { parseFrontmatterLoosely } from "../lib/frontmatter.ts";
|
|
32
|
+
import { defaultDiscoverRoots, discoverPlugins, type DiscoveredPlugins } from "../lib/plugins.ts";
|
|
33
|
+
import { BUNDLED_SKILLS_DIR, promptTemplateNames, scanSkills } from "../lib/skill-scan.ts";
|
|
34
|
+
import { loadServers, type McpServer } from "../mcp/config.ts";
|
|
35
|
+
import type { PermissionMode } from "../permissions/matcher.ts";
|
|
36
|
+
import { MODES_NEVER_FROM_PROJECT } from "../permissions/settings.ts";
|
|
37
|
+
import { agentDirs, parseAgentFile } from "../subagents/agents.ts";
|
|
38
|
+
import type { Finding, ReportLine, ReportSection, SessionView } from "./report.ts";
|
|
39
|
+
import { countNoun, shortenHome } from "./report.ts";
|
|
40
|
+
|
|
41
|
+
export type SettingsScope = "claude-user" | "claude-project" | "claude-local" | "managed" | "onecode-user" | "onecode-project" | "claude-json";
|
|
42
|
+
|
|
43
|
+
export interface SettingsFileReport {
|
|
44
|
+
scope: SettingsScope;
|
|
45
|
+
label: string;
|
|
46
|
+
path: string;
|
|
47
|
+
exists: boolean;
|
|
48
|
+
/** False when the file exists but is not a JSON object. */
|
|
49
|
+
valid: boolean;
|
|
50
|
+
error?: string;
|
|
51
|
+
/** One phrase per consumed key, e.g. `permissions: 12 allow, 2 deny`. */
|
|
52
|
+
used: string[];
|
|
53
|
+
/** Top-level keys present that One Code never reads from this file. */
|
|
54
|
+
ignored: string[];
|
|
55
|
+
/** Keys present that One Code reads from OTHER scopes but deliberately not from this one. */
|
|
56
|
+
refused: string[];
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export interface ContextFileReport {
|
|
60
|
+
path: string;
|
|
61
|
+
descriptor: string;
|
|
62
|
+
chars: number;
|
|
63
|
+
overLimit: boolean;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export interface CompatReport {
|
|
67
|
+
files: SettingsFileReport[];
|
|
68
|
+
contextFiles: ContextFileReport[];
|
|
69
|
+
memory: { dir: string; indexExists: boolean; indexStatus?: "ok" | "near" | "over" };
|
|
70
|
+
agents: { user: number; project: number; plugin: number; bundled: number; failed: string[] };
|
|
71
|
+
skills: { user: number; project: number; plugin: number; bundled: number; noDescription: string[] };
|
|
72
|
+
commands: number;
|
|
73
|
+
plugins: { claude: { total: number; enabled: number }; oneCode: { total: number; enabled: number } };
|
|
74
|
+
hooks: { sources: HooksSource[]; commands: number; diagnostics: string[] };
|
|
75
|
+
mcp: { servers: McpServer[]; disabled: Set<string>; configErrors: string[] };
|
|
76
|
+
findings: Finding[];
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* The top-level keys One Code reads from each settings scope — the policy the
|
|
81
|
+
* loaders implement (permissions/settings.ts, hooks/settings.ts, auto-mode/config.ts,
|
|
82
|
+
* subagents/default-model.ts, mcp/trust.ts, lib/plugins.ts). A repository's own
|
|
83
|
+
* files get no `autoMode` (the classifier is what contains the repo) and no `env`
|
|
84
|
+
* (a checked-in env block must not route subagents).
|
|
85
|
+
*/
|
|
86
|
+
const MCP_POLICY_KEYS = ["enableAllProjectMcpServers", "enabledMcpjsonServers", "disabledMcpjsonServers"];
|
|
87
|
+
const CLAUDE_PROJECT_KEYS = new Set(["permissions", "hooks", "enabledPlugins", ...MCP_POLICY_KEYS]);
|
|
88
|
+
const SCOPE_KEYS: Record<SettingsScope, ReadonlySet<string>> = {
|
|
89
|
+
"claude-user": new Set(["permissions", "hooks", "env", "enabledPlugins", "autoMode", ...MCP_POLICY_KEYS]),
|
|
90
|
+
"claude-project": CLAUDE_PROJECT_KEYS,
|
|
91
|
+
"claude-local": CLAUDE_PROJECT_KEYS,
|
|
92
|
+
managed: new Set(["permissions", "hooks", "env", "autoMode", "enabledPlugins"]),
|
|
93
|
+
"onecode-user": new Set(["subagentModel", "subagentModelSetFor", "autoMode", "permissions", "webSearch", "disabledMcpServers"]),
|
|
94
|
+
"onecode-project": new Set(["permissions", "disabledMcpServers"]),
|
|
95
|
+
"claude-json": new Set(),
|
|
96
|
+
};
|
|
97
|
+
/** Never worth reporting as "ignored". */
|
|
98
|
+
const SILENT_KEYS = new Set(["$schema"]);
|
|
99
|
+
|
|
100
|
+
type Json = Record<string, unknown>;
|
|
101
|
+
|
|
102
|
+
function readJsonObject(path: string): { exists: boolean; value?: Json; error?: string } {
|
|
103
|
+
if (!existsSync(path)) return { exists: false };
|
|
104
|
+
try {
|
|
105
|
+
const parsed = JSON.parse(readFileSync(path, "utf-8")) as unknown;
|
|
106
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) return { exists: true, error: "root must be a JSON object" };
|
|
107
|
+
return { exists: true, value: parsed as Json };
|
|
108
|
+
} catch (error) {
|
|
109
|
+
return { exists: true, error: (error as Error).message };
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
const count = (value: unknown): number => (Array.isArray(value) ? value.length : 0);
|
|
114
|
+
|
|
115
|
+
function permissionsSummary(perms: unknown, scope: SettingsScope, findings: Finding[], path: string, home: string): { used?: string; refused?: string } {
|
|
116
|
+
if (!perms || typeof perms !== "object") return {};
|
|
117
|
+
const p = perms as Json;
|
|
118
|
+
const parts: string[] = [];
|
|
119
|
+
const allow = count(p.allow);
|
|
120
|
+
const deny = count(p.deny);
|
|
121
|
+
const ask = count(p.ask);
|
|
122
|
+
if (allow || deny || ask) parts.push(`${allow} allow, ${deny} deny, ${ask} ask`);
|
|
123
|
+
if (scope === "claude-project" || scope === "claude-local") {
|
|
124
|
+
if (allow) parts.push("allow rules apply after a once-per-config consent");
|
|
125
|
+
}
|
|
126
|
+
let refused: string | undefined;
|
|
127
|
+
const mode = typeof p.defaultMode === "string" ? p.defaultMode : undefined;
|
|
128
|
+
if (mode) {
|
|
129
|
+
const repoFile = scope === "claude-project" || scope === "claude-local";
|
|
130
|
+
if (scope === "onecode-user" || scope === "onecode-project") {
|
|
131
|
+
refused = `permissions.defaultMode "${mode}" — never read from One Code's own files`;
|
|
132
|
+
findings.push({
|
|
133
|
+
level: "warn",
|
|
134
|
+
text: `${shortenHome(path, home)} sets permissions.defaultMode "${mode}", which One Code does not read from its own settings files.`,
|
|
135
|
+
fix: "Set the default mode in ~/.claude/settings.json (or start with --permission-mode); remove the key here.",
|
|
136
|
+
});
|
|
137
|
+
} else if (repoFile && MODES_NEVER_FROM_PROJECT.has(mode as PermissionMode)) {
|
|
138
|
+
refused = `permissions.defaultMode "${mode}" — a repository's files may not select it`;
|
|
139
|
+
findings.push({
|
|
140
|
+
level: "warn",
|
|
141
|
+
text: `${shortenHome(path, home)} sets permissions.defaultMode "${mode}"; a repository's own settings never select that mode (it would let a cloned repo grant itself unattended approval). Claude Code applies the same rule.`,
|
|
142
|
+
fix: "Set it in ~/.claude/settings.json instead, or pass --permission-mode.",
|
|
143
|
+
});
|
|
144
|
+
} else {
|
|
145
|
+
parts.push(`defaultMode ${mode}`);
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
if (p.disableBypassPermissionsMode === "disable") parts.push("bypass mode disabled");
|
|
149
|
+
if (p.additionalDirectories !== undefined) parts.push("additionalDirectories (not honoured)");
|
|
150
|
+
return { used: parts.length ? `permissions: ${parts.join("; ")}` : undefined, refused };
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
function hooksSummary(hooks: unknown): string | undefined {
|
|
154
|
+
if (!hooks || typeof hooks !== "object") return undefined;
|
|
155
|
+
const events = Object.entries(hooks as Json)
|
|
156
|
+
.filter(([, groups]) => Array.isArray(groups) && groups.length > 0)
|
|
157
|
+
.map(([event, groups]) => `${event} ×${(groups as unknown[]).reduce<number>((n, g) => n + count((g as Json)?.hooks), 0)}`);
|
|
158
|
+
return events.length ? `hooks: ${events.join(", ")}` : undefined;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
function summarizeFile(scope: SettingsScope, label: string, path: string, home: string, findings: Finding[]): SettingsFileReport {
|
|
162
|
+
const read = readJsonObject(path);
|
|
163
|
+
const report: SettingsFileReport = { scope, label, path, exists: read.exists, valid: !read.error, error: read.error, used: [], ignored: [], refused: [] };
|
|
164
|
+
if (!read.exists) return report;
|
|
165
|
+
if (read.error || !read.value) {
|
|
166
|
+
findings.push({
|
|
167
|
+
level: "error",
|
|
168
|
+
text: `${shortenHome(path, home)} is not valid JSON (${read.error}); everything in it is ignored, so rules you believe are in force are not.`,
|
|
169
|
+
fix: "Fix the syntax (a trailing comma is the usual cause) and rerun /doctor.",
|
|
170
|
+
});
|
|
171
|
+
return report;
|
|
172
|
+
}
|
|
173
|
+
const file = read.value;
|
|
174
|
+
const known = SCOPE_KEYS[scope];
|
|
175
|
+
/** Keys a branch below has already classified (used, refused or ignored), so the final sweep skips them. */
|
|
176
|
+
const handled = new Set<string>();
|
|
177
|
+
|
|
178
|
+
if (scope === "claude-json") {
|
|
179
|
+
const servers = file.mcpServers && typeof file.mcpServers === "object" ? Object.keys(file.mcpServers as Json).length : 0;
|
|
180
|
+
if (servers) report.used.push(`mcpServers: ${countNoun(servers, "user-scope server")}`);
|
|
181
|
+
report.used.push("everything else in this file (onboarding state, usage counters) is Claude Code's own");
|
|
182
|
+
return report;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
const perms = permissionsSummary(file.permissions, scope, findings, path, home);
|
|
186
|
+
if (perms.used) report.used.push(perms.used);
|
|
187
|
+
if (perms.refused) report.refused.push(perms.refused);
|
|
188
|
+
const hooks = hooksSummary(file.hooks);
|
|
189
|
+
if (hooks && known.has("hooks")) report.used.push(hooks);
|
|
190
|
+
if (file.enabledPlugins && typeof file.enabledPlugins === "object" && known.has("enabledPlugins")) {
|
|
191
|
+
report.used.push(`enabledPlugins: ${Object.keys(file.enabledPlugins as Json).length}`);
|
|
192
|
+
}
|
|
193
|
+
if (file.env && typeof file.env === "object") {
|
|
194
|
+
handled.add("env");
|
|
195
|
+
const env = file.env as Json;
|
|
196
|
+
const keys = Object.keys(env);
|
|
197
|
+
if (known.has("env")) {
|
|
198
|
+
const borrowed = typeof env.CLAUDE_CODE_SUBAGENT_MODEL === "string" ? `env.CLAUDE_CODE_SUBAGENT_MODEL = "${env.CLAUDE_CODE_SUBAGENT_MODEL}"` : undefined;
|
|
199
|
+
if (borrowed) report.used.push(borrowed);
|
|
200
|
+
const rest = keys.filter((k) => k !== "CLAUDE_CODE_SUBAGENT_MODEL");
|
|
201
|
+
if (rest.length) report.ignored.push(`env (${countNoun(rest.length, "variable")} — One Code does not export settings env blocks)`);
|
|
202
|
+
} else if (keys.length) {
|
|
203
|
+
report.refused.push(`env (${keys.length}) — a repository's env block is not applied`);
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
if (file.autoMode && typeof file.autoMode === "object") {
|
|
207
|
+
handled.add("autoMode");
|
|
208
|
+
const auto = file.autoMode as Json;
|
|
209
|
+
const keys = Object.keys(auto);
|
|
210
|
+
if (known.has("autoMode")) {
|
|
211
|
+
if (scope === "claude-user" && ("classifierModel" in auto || "classifierModelSetFor" in auto)) {
|
|
212
|
+
report.refused.push("autoMode.classifierModel — One Code's own key, read from ~/.onecode/settings.json only");
|
|
213
|
+
findings.push({
|
|
214
|
+
level: "warn",
|
|
215
|
+
text: `${shortenHome(path, home)} carries autoMode.classifierModel, a One Code key that is now read only from ~/.onecode/settings.json; the value here is ignored.`,
|
|
216
|
+
fix: "Move it with /auto-mode model (which writes the new location) and delete the stale key from ~/.claude/settings.json.",
|
|
217
|
+
});
|
|
218
|
+
}
|
|
219
|
+
const consumed = keys.filter((k) => !(scope === "claude-user" && (k === "classifierModel" || k === "classifierModelSetFor")));
|
|
220
|
+
if (consumed.length) report.used.push(`autoMode: ${consumed.join(", ")}`);
|
|
221
|
+
} else {
|
|
222
|
+
report.refused.push(`autoMode (${keys.join(", ")}) — never read from a repository's files`);
|
|
223
|
+
findings.push({
|
|
224
|
+
level: "warn",
|
|
225
|
+
text: `${shortenHome(path, home)} has an autoMode block; auto-mode configuration is read from user and managed settings only, so it is ignored here (Claude Code does the same).`,
|
|
226
|
+
fix: "Move the block to ~/.onecode/settings.json (or run /auto-mode setup there).",
|
|
227
|
+
});
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
if (scope === "claude-user" && ("subagentModel" in file || "subagentModelSetFor" in file)) {
|
|
231
|
+
handled.add("subagentModel").add("subagentModelSetFor");
|
|
232
|
+
report.refused.push("subagentModel — One Code's own key, read from ~/.onecode/settings.json only");
|
|
233
|
+
findings.push({
|
|
234
|
+
level: "warn",
|
|
235
|
+
text: `${shortenHome(path, home)} carries subagentModel, a One Code key that is now read only from ~/.onecode/settings.json; the value here is ignored.`,
|
|
236
|
+
fix: "Re-set it with /subagent (which writes the new location) and delete the stale key from ~/.claude/settings.json.",
|
|
237
|
+
});
|
|
238
|
+
}
|
|
239
|
+
if (scope === "onecode-user") {
|
|
240
|
+
if (typeof file.subagentModel === "string") report.used.push(`subagentModel "${file.subagentModel}"`);
|
|
241
|
+
if (file.webSearch && typeof file.webSearch === "object") report.used.push("webSearch (keys/order for the search fallback)");
|
|
242
|
+
}
|
|
243
|
+
for (const key of ["enableAllProjectMcpServers", "enabledMcpjsonServers", "disabledMcpjsonServers"]) {
|
|
244
|
+
if (key in file && known.has(key)) report.used.push(`${key}: ${Array.isArray(file[key]) ? count(file[key]) : String(file[key])}`);
|
|
245
|
+
}
|
|
246
|
+
if ("disabledMcpServers" in file && known.has("disabledMcpServers")) report.used.push(`disabledMcpServers: ${count(file.disabledMcpServers)} (via /mcp disable)`);
|
|
247
|
+
|
|
248
|
+
for (const key of Object.keys(file)) {
|
|
249
|
+
if (known.has(key) || SILENT_KEYS.has(key) || handled.has(key)) continue;
|
|
250
|
+
report.ignored.push(key);
|
|
251
|
+
}
|
|
252
|
+
return report;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
function walkMarkdown(dir: string, out: string[] = []): string[] {
|
|
256
|
+
let entries: string[];
|
|
257
|
+
try {
|
|
258
|
+
entries = readdirSync(dir);
|
|
259
|
+
} catch {
|
|
260
|
+
return out;
|
|
261
|
+
}
|
|
262
|
+
for (const entry of entries) {
|
|
263
|
+
const full = join(dir, entry);
|
|
264
|
+
try {
|
|
265
|
+
const stat = statSync(full);
|
|
266
|
+
if (stat.isDirectory()) walkMarkdown(full, out);
|
|
267
|
+
else if (entry.endsWith(".md")) out.push(full);
|
|
268
|
+
} catch {
|
|
269
|
+
// unreadable entry: skip
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
return out;
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
export interface CompatInput {
|
|
276
|
+
cwd: string;
|
|
277
|
+
home: string;
|
|
278
|
+
agentDir: string;
|
|
279
|
+
env: NodeJS.ProcessEnv;
|
|
280
|
+
/** `<package>/agents`, so bundled agents are counted. */
|
|
281
|
+
bundledAgentsDir?: string;
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
export function collectCompat(input: CompatInput): CompatReport {
|
|
285
|
+
const { cwd, home, agentDir, env } = input;
|
|
286
|
+
const findings: Finding[] = [];
|
|
287
|
+
const claudeDir = claudeUserDir(home, env);
|
|
288
|
+
const claude = settingsPaths(cwd, home);
|
|
289
|
+
|
|
290
|
+
const files: SettingsFileReport[] = [
|
|
291
|
+
summarizeFile("claude-user", "Claude Code user settings", claudeUserSettingsPath(home), home, findings),
|
|
292
|
+
summarizeFile("claude-project", "project settings (checked in)", claude.project, home, findings),
|
|
293
|
+
summarizeFile("claude-local", "project settings (local)", claude.local, home, findings),
|
|
294
|
+
...managedSettingsPaths().map((path) => summarizeFile("managed", "managed settings (organisation policy)", path, home, findings)),
|
|
295
|
+
summarizeFile("claude-json", "Claude Code's .claude.json", claudeJsonPath(home, env), home, findings),
|
|
296
|
+
summarizeFile("onecode-user", "One Code user settings", oneCodeSettingsPath(home, env), home, findings),
|
|
297
|
+
summarizeFile("onecode-project", "One Code per-repository settings", oneCodeProjectSettingsPath(cwd, home, env), home, findings),
|
|
298
|
+
];
|
|
299
|
+
|
|
300
|
+
// Context files, sized — the block every turn pays for.
|
|
301
|
+
const contextFiles: ContextFileReport[] = discoverContextFilePaths({
|
|
302
|
+
cwd,
|
|
303
|
+
homeClaudeDir: claudeDir,
|
|
304
|
+
homeOneCodeDir: join(home, ".onecode"),
|
|
305
|
+
agentsFallback: true,
|
|
306
|
+
}).map(({ path, descriptor }) => {
|
|
307
|
+
let chars = 0;
|
|
308
|
+
try {
|
|
309
|
+
chars = readFileSync(path, "utf-8").length;
|
|
310
|
+
} catch {
|
|
311
|
+
// unreadable: reported as 0 chars
|
|
312
|
+
}
|
|
313
|
+
const warning = claudeMdLimitWarning(basename(path), chars);
|
|
314
|
+
if (warning) {
|
|
315
|
+
findings.push({ level: "warn", text: `${shortenHome(path, home)}: ${warning}`, fix: `Trim it below ${CLAUDE_MD_CHAR_LIMIT / 1000}k chars, or run /doctor fix to have the model propose what to cut.` });
|
|
316
|
+
}
|
|
317
|
+
return { path, descriptor, chars, overLimit: warning !== null };
|
|
318
|
+
});
|
|
319
|
+
|
|
320
|
+
// Auto-memory (shared with Claude Code).
|
|
321
|
+
const memDir = projectMemoryDir(cwd, home);
|
|
322
|
+
const indexPath = join(memDir, "MEMORY.md");
|
|
323
|
+
let indexStatus: "ok" | "near" | "over" | undefined;
|
|
324
|
+
if (existsSync(indexPath)) {
|
|
325
|
+
try {
|
|
326
|
+
indexStatus = indexLimitStatus(readFileSync(indexPath, "utf-8"));
|
|
327
|
+
} catch {
|
|
328
|
+
indexStatus = undefined;
|
|
329
|
+
}
|
|
330
|
+
if (indexStatus === "over") findings.push({ level: "warn", text: `${shortenHome(indexPath, home)} is over the 200-line / 25KB load limit; entries past it are dropped.`, fix: "Shorten the index to one line per entry (/memory opens it)." });
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
// Plugins (Claude Code-installed, read-through, plus One Code's own root).
|
|
334
|
+
let discovered: DiscoveredPlugins | undefined;
|
|
335
|
+
try {
|
|
336
|
+
discovered = discoverPlugins(defaultDiscoverRoots(agentDir, cwd, home));
|
|
337
|
+
} catch (error) {
|
|
338
|
+
findings.push({ level: "warn", text: `Plugin discovery failed: ${(error as Error).message}` });
|
|
339
|
+
}
|
|
340
|
+
const pluginCounts = (origin: "claude" | "one-code") => {
|
|
341
|
+
const list = (discovered?.plugins ?? []).filter((p) => p.originRoot === origin);
|
|
342
|
+
return { total: list.length, enabled: list.filter((p) => p.enabled).length };
|
|
343
|
+
};
|
|
344
|
+
|
|
345
|
+
// Agents.
|
|
346
|
+
const agentFailed: string[] = [];
|
|
347
|
+
const countAgents = (dir: string): number => {
|
|
348
|
+
let ok = 0;
|
|
349
|
+
for (const file of walkMarkdown(dir)) {
|
|
350
|
+
let content = "";
|
|
351
|
+
try {
|
|
352
|
+
content = readFileSync(file, "utf-8");
|
|
353
|
+
} catch {
|
|
354
|
+
continue;
|
|
355
|
+
}
|
|
356
|
+
if (parseAgentFile(file, content)) ok++;
|
|
357
|
+
else agentFailed.push(file);
|
|
358
|
+
}
|
|
359
|
+
return ok;
|
|
360
|
+
};
|
|
361
|
+
const [userAgentsDir, projectAgentsDir] = agentDirs(cwd, home);
|
|
362
|
+
const agents = {
|
|
363
|
+
user: countAgents(userAgentsDir),
|
|
364
|
+
project: countAgents(projectAgentsDir),
|
|
365
|
+
plugin: (discovered?.agentDirs ?? []).reduce((n, d) => n + walkMarkdown(d.dir).length, 0),
|
|
366
|
+
bundled: input.bundledAgentsDir ? walkMarkdown(input.bundledAgentsDir).length : 0,
|
|
367
|
+
failed: agentFailed,
|
|
368
|
+
};
|
|
369
|
+
for (const file of agentFailed) {
|
|
370
|
+
findings.push({ level: "warn", text: `Agent file ${shortenHome(file, home)} has no body and is skipped.`, fix: "Add the agent's instructions below the frontmatter, or remove the file." });
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
// Skills.
|
|
374
|
+
const scanned = scanSkills(cwd, home, agentDir, discovered?.skills ?? [], BUNDLED_SKILLS_DIR);
|
|
375
|
+
const noDescription: string[] = [];
|
|
376
|
+
for (const skill of scanned) {
|
|
377
|
+
try {
|
|
378
|
+
const { frontmatter } = parseFrontmatterLoosely(readFileSync(skill.path, "utf-8"));
|
|
379
|
+
if (typeof frontmatter.description !== "string" || !frontmatter.description.trim()) noDescription.push(skill.path);
|
|
380
|
+
} catch {
|
|
381
|
+
// unreadable SKILL.md: pi reports it when it loads
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
const bundledSkills = scanned.filter((s) => s.path.startsWith(BUNDLED_SKILLS_DIR)).length;
|
|
385
|
+
const skills = {
|
|
386
|
+
user: scanned.filter((s) => s.scope === "user" && !s.path.startsWith(BUNDLED_SKILLS_DIR)).length,
|
|
387
|
+
project: scanned.filter((s) => s.scope === "project" && !s.path.startsWith(BUNDLED_SKILLS_DIR)).length,
|
|
388
|
+
plugin: scanned.filter((s) => s.scope === "plugin").length,
|
|
389
|
+
bundled: bundledSkills,
|
|
390
|
+
noDescription,
|
|
391
|
+
};
|
|
392
|
+
for (const path of noDescription) {
|
|
393
|
+
findings.push({
|
|
394
|
+
level: "warn",
|
|
395
|
+
text: `${shortenHome(path, home)} has no description in its frontmatter (malformed YAML drops every field), so the model matches it against its first body line.`,
|
|
396
|
+
fix: "Give the skill `name:` and `description:` lines between the leading `---` markers.",
|
|
397
|
+
});
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
// Hooks.
|
|
401
|
+
const hookDiagnostics: string[] = [];
|
|
402
|
+
const hooksLoaded = loadHookSettings(claudeDir, cwd);
|
|
403
|
+
hookDiagnostics.push(...hooksLoaded.diagnostics);
|
|
404
|
+
const pluginHooks = discovered ? loadPluginHooks(defaultDiscoverRoots(agentDir, cwd, home), hookDiagnostics) : [];
|
|
405
|
+
const hookSources = [...hooksLoaded.sources, ...pluginHooks];
|
|
406
|
+
const hookCommands = hookSources.reduce(
|
|
407
|
+
(n, source) => n + Object.values(source.config).reduce((m, groups) => m + (groups ?? []).reduce((k, g) => k + g.hooks.length, 0), 0),
|
|
408
|
+
0,
|
|
409
|
+
);
|
|
410
|
+
for (const diagnostic of hookDiagnostics) findings.push({ level: "warn", text: `Hooks: ${diagnostic}` });
|
|
411
|
+
|
|
412
|
+
// MCP.
|
|
413
|
+
const configErrors: string[] = [];
|
|
414
|
+
const servers = loadServers(cwd, home, env, discovered?.mcpConfigs ?? [], {
|
|
415
|
+
pluginNames: discovered?.mcpConfigPlugins,
|
|
416
|
+
onError: (path, message) => configErrors.push(`${shortenHome(path, home)}: ${message}`),
|
|
417
|
+
});
|
|
418
|
+
for (const error of configErrors) findings.push({ level: "error", text: `MCP config ${error} — its servers are not loaded.`, fix: "Fix the JSON and run /mcp reconnect." });
|
|
419
|
+
for (const server of servers) {
|
|
420
|
+
if (server.missingEnv?.length) {
|
|
421
|
+
findings.push({
|
|
422
|
+
level: "warn",
|
|
423
|
+
text: `MCP server "${server.name}" references unset environment variable${server.missingEnv.length === 1 ? "" : "s"} ${server.missingEnv.join(", ")} (from ${shortenHome(server.source, home)}).`,
|
|
424
|
+
fix: "Export the variable before launching, or the server will fail to connect.",
|
|
425
|
+
});
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
return {
|
|
430
|
+
files,
|
|
431
|
+
contextFiles,
|
|
432
|
+
memory: { dir: memDir, indexExists: existsSync(indexPath), indexStatus },
|
|
433
|
+
agents,
|
|
434
|
+
skills,
|
|
435
|
+
commands: promptTemplateNames(cwd, home, agentDir).length,
|
|
436
|
+
plugins: { claude: pluginCounts("claude"), oneCode: pluginCounts("one-code") },
|
|
437
|
+
hooks: { sources: hookSources, commands: hookCommands, diagnostics: hookDiagnostics },
|
|
438
|
+
mcp: { servers, disabled: readDisabledMcpServers(cwd, home, env), configErrors },
|
|
439
|
+
findings,
|
|
440
|
+
};
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
/** `user 2 · project 0 · plugin 7` — counts by origin, the noun being the origin not the thing counted. */
|
|
444
|
+
const byOrigin = (counts: Record<string, number>): string =>
|
|
445
|
+
Object.entries(counts)
|
|
446
|
+
.map(([origin, n]) => `${origin} ${n}`)
|
|
447
|
+
.join(" · ");
|
|
448
|
+
|
|
449
|
+
export function importedConfigSection(compat: CompatReport, home: string): ReportSection {
|
|
450
|
+
const lines: ReportLine[] = [];
|
|
451
|
+
for (const file of compat.files) {
|
|
452
|
+
if (!file.exists) continue;
|
|
453
|
+
const path = shortenHome(file.path, home);
|
|
454
|
+
if (!file.valid) {
|
|
455
|
+
lines.push({ text: `${file.label} ${path}: invalid JSON — ignored`, level: "error" });
|
|
456
|
+
continue;
|
|
457
|
+
}
|
|
458
|
+
const used = file.used.length ? file.used.join(" · ") : "nothing One Code reads";
|
|
459
|
+
lines.push({ text: `${file.label} ${path}: ${used}`, level: "ok" });
|
|
460
|
+
for (const refused of file.refused) lines.push({ text: `not honoured here: ${refused}`, indent: 1, level: "warn" });
|
|
461
|
+
if (file.ignored.length) lines.push({ text: `not used by One Code: ${file.ignored.join(", ")}`, indent: 1, level: "dim" });
|
|
462
|
+
}
|
|
463
|
+
if (lines.length === 0) lines.push({ text: "No settings files found — defaults apply (auto permission mode, no hooks, no plugins).", level: "dim" });
|
|
464
|
+
|
|
465
|
+
if (compat.contextFiles.length) {
|
|
466
|
+
const total = compat.contextFiles.reduce((n, f) => n + f.chars, 0);
|
|
467
|
+
lines.push({ text: `Instruction files (${compat.contextFiles.length}, ${(total / 1000).toFixed(1)}k chars read every turn):`, level: "ok" });
|
|
468
|
+
for (const file of compat.contextFiles) {
|
|
469
|
+
lines.push({ text: `${shortenHome(file.path, home)} — ${(file.chars / 1000).toFixed(1)}k chars${file.overLimit ? " (over the 40k soft limit)" : ""}`, indent: 1, level: file.overLimit ? "warn" : "dim" });
|
|
470
|
+
}
|
|
471
|
+
} else {
|
|
472
|
+
lines.push({ text: "Instruction files: none (no CLAUDE.md, AGENTS.md or ONECODE.md from here to the repository root)", level: "dim" });
|
|
473
|
+
}
|
|
474
|
+
lines.push({
|
|
475
|
+
text: compat.memory.indexExists
|
|
476
|
+
? `Auto-memory: ${shortenHome(compat.memory.dir, home)} (MEMORY.md ${compat.memory.indexStatus ?? "present"}; shared with Claude Code)`
|
|
477
|
+
: `Auto-memory: ${shortenHome(compat.memory.dir, home)} (empty; created when the model saves its first note)`,
|
|
478
|
+
level: "dim",
|
|
479
|
+
});
|
|
480
|
+
const a = compat.agents;
|
|
481
|
+
lines.push({
|
|
482
|
+
text: `Agents: ${byOrigin({ user: a.user, project: a.project, plugin: a.plugin, bundled: a.bundled })}${a.failed.length ? ` · ${a.failed.length} skipped` : ""}`,
|
|
483
|
+
level: a.failed.length ? "warn" : "ok",
|
|
484
|
+
});
|
|
485
|
+
const s = compat.skills;
|
|
486
|
+
lines.push({
|
|
487
|
+
text: `Skills: ${byOrigin({ user: s.user, project: s.project, plugin: s.plugin, bundled: s.bundled })} · ${countNoun(compat.commands, "command template")}`,
|
|
488
|
+
level: s.noDescription.length ? "warn" : "ok",
|
|
489
|
+
});
|
|
490
|
+
const p = compat.plugins;
|
|
491
|
+
lines.push({
|
|
492
|
+
text: `Plugins: ${p.claude.enabled}/${p.claude.total} Claude Code-installed enabled (read-only; toggle in /plugins), ${p.oneCode.enabled}/${p.oneCode.total} installed by One Code`,
|
|
493
|
+
level: "ok",
|
|
494
|
+
});
|
|
495
|
+
if (compat.hooks.sources.length) {
|
|
496
|
+
const scopes = compat.hooks.sources.map((s) => (s.pluginName ? `plugin ${s.pluginName}` : s.scope)).join(", ");
|
|
497
|
+
lines.push({ text: `Hooks: ${countNoun(compat.hooks.commands, "command")} from ${scopes}`, level: compat.hooks.diagnostics.length ? "warn" : "ok" });
|
|
498
|
+
} else {
|
|
499
|
+
lines.push({ text: "Hooks: none configured", level: "dim" });
|
|
500
|
+
}
|
|
501
|
+
return { title: "Imported configuration", subtitle: "what One Code reads from your Claude Code and One Code files", lines };
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
export function mcpSection(compat: CompatReport, session: SessionView | undefined, home: string): ReportSection {
|
|
505
|
+
const lines: ReportLine[] = [];
|
|
506
|
+
const live = new Map((session?.mcp?.servers ?? []).map((s) => [s.name, s]));
|
|
507
|
+
if (compat.mcp.servers.length === 0 && compat.mcp.configErrors.length === 0) {
|
|
508
|
+
lines.push({ text: "No MCP servers configured (.mcp.json, ~/.claude.json, or a plugin's .mcp.json)", level: "dim" });
|
|
509
|
+
}
|
|
510
|
+
for (const server of compat.mcp.servers) {
|
|
511
|
+
const status = live.get(server.name);
|
|
512
|
+
const disabled = compat.mcp.disabled.has(server.name);
|
|
513
|
+
const where = shortenHome(server.source, home);
|
|
514
|
+
const shape = server.kind === "stdio" ? `stdio: ${server.command}` : `http: ${server.url}`;
|
|
515
|
+
let state: string;
|
|
516
|
+
let level: ReportLine["level"] = "ok";
|
|
517
|
+
if (disabled) {
|
|
518
|
+
state = "disabled (/mcp enable to turn on)";
|
|
519
|
+
level = "dim";
|
|
520
|
+
} else if (status) {
|
|
521
|
+
state = status.status === "connected" ? `connected${status.toolCount !== undefined ? ` · ${status.toolCount} tools` : ""}` : status.status === "authNeeded" ? "needs authentication (/mcp)" : status.status === "connecting" ? "connecting…" : `failed${status.detail ? `: ${status.detail}` : ""}`;
|
|
522
|
+
level = status.status === "connected" ? "ok" : status.status === "connecting" ? "dim" : "warn";
|
|
523
|
+
} else {
|
|
524
|
+
state = server.missingEnv?.length ? `missing ${server.missingEnv.join(", ")}` : "configured (status known once a session connects)";
|
|
525
|
+
level = server.missingEnv?.length ? "warn" : "dim";
|
|
526
|
+
}
|
|
527
|
+
lines.push({ text: `${server.name} — ${shape} · ${state}`, level });
|
|
528
|
+
lines.push({ text: `from ${where}`, indent: 1, level: "dim" });
|
|
529
|
+
}
|
|
530
|
+
for (const error of compat.mcp.configErrors) lines.push({ text: `config error: ${error}`, level: "error" });
|
|
531
|
+
return { title: "MCP servers", lines };
|
|
532
|
+
}
|