one-code-extension 0.1.3 → 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.
Files changed (300) hide show
  1. package/LICENSE +20 -0
  2. package/README.md +200 -172
  3. package/agents/explore.md +9 -6
  4. package/agents/general-purpose.md +1 -1
  5. package/agents/plan.md +6 -4
  6. package/docs/guide/README.md +52 -0
  7. package/docs/guide/bring-your-claude-code-setup.md +126 -0
  8. package/docs/guide/configuration.md +171 -0
  9. package/docs/guide/differences-from-claude-code.md +150 -0
  10. package/docs/guide/doctor.md +86 -0
  11. package/docs/guide/hooks.md +109 -0
  12. package/docs/guide/installation.md +124 -0
  13. package/docs/guide/permissions-modes-and-auto-mode.md +450 -0
  14. package/docs/guide/providers-and-models.md +194 -0
  15. package/docs/guide/reference.md +224 -0
  16. package/docs/guide/sessions-and-context.md +190 -0
  17. package/docs/guide/skills-plugins-and-mcp.md +176 -0
  18. package/docs/guide/subagents-and-workflows.md +193 -0
  19. package/docs/guide/tasks-and-background-work.md +111 -0
  20. package/docs/guide/terminal-interface.md +155 -0
  21. package/docs/guide/tools.md +175 -0
  22. package/docs/guide/troubleshooting.md +175 -0
  23. package/extensions/ask-user/index.ts +65 -71
  24. package/extensions/ask-user/questions.ts +15 -28
  25. package/extensions/ask-user/widget.ts +533 -0
  26. package/extensions/auto-mode/actions.ts +9 -26
  27. package/extensions/auto-mode/classifier-prompt.ts +127 -0
  28. package/extensions/auto-mode/classifier.ts +305 -227
  29. package/extensions/auto-mode/config.ts +279 -77
  30. package/extensions/auto-mode/defaults.ts +17 -60
  31. package/extensions/auto-mode/instructions.ts +2 -1
  32. package/extensions/auto-mode/model-picker.ts +53 -31
  33. package/extensions/auto-mode/model-select.ts +153 -82
  34. package/extensions/auto-mode/paths.ts +13 -1
  35. package/extensions/auto-mode/pause.ts +8 -0
  36. package/extensions/auto-mode/prompt.ts +205 -238
  37. package/extensions/auto-mode/recoverability.ts +229 -0
  38. package/extensions/auto-mode/rules.ts +92 -75
  39. package/extensions/auto-mode/safety-floor.ts +43 -12
  40. package/extensions/auto-mode/sensitive.ts +11 -0
  41. package/extensions/auto-mode/setup-run.ts +245 -0
  42. package/extensions/auto-mode/setup.ts +235 -0
  43. package/extensions/auto-mode/shell-analysis.ts +332 -26
  44. package/extensions/auto-mode/transcript.ts +102 -0
  45. package/extensions/background/index.ts +256 -43
  46. package/extensions/background/monitor-batch.ts +58 -0
  47. package/extensions/background/registry.ts +9 -0
  48. package/extensions/background/wakeup.ts +45 -0
  49. package/extensions/bash/background.ts +47 -22
  50. package/extensions/bash/guards.ts +229 -0
  51. package/extensions/bash/index.ts +81 -19
  52. package/extensions/branding/index.ts +44 -18
  53. package/extensions/branding/startup.ts +23 -8
  54. package/extensions/claude-compat/index.ts +17 -2
  55. package/extensions/claude-context/index.ts +41 -12
  56. package/extensions/clear/index.ts +27 -0
  57. package/extensions/compaction/fit.ts +121 -0
  58. package/extensions/compaction/index.ts +176 -71
  59. package/extensions/compaction/prompt.ts +21 -1
  60. package/extensions/context-budget/budget.ts +61 -0
  61. package/extensions/context-budget/index.ts +66 -0
  62. package/extensions/context-management/index.ts +40 -16
  63. package/extensions/doctor/build.ts +226 -0
  64. package/extensions/doctor/cli.ts +99 -0
  65. package/extensions/doctor/compat.ts +532 -0
  66. package/extensions/doctor/dependencies.ts +186 -0
  67. package/extensions/doctor/fix-prompt.ts +125 -0
  68. package/extensions/doctor/index.ts +308 -0
  69. package/extensions/doctor/models.ts +231 -0
  70. package/extensions/doctor/presets.ts +208 -0
  71. package/extensions/doctor/report.ts +189 -0
  72. package/extensions/doctor/update-lookup.ts +46 -0
  73. package/extensions/doctor/viewer.ts +124 -0
  74. package/extensions/effort/index.ts +41 -4
  75. package/extensions/effort/slider.ts +33 -2
  76. package/extensions/file-tracker/index.ts +143 -20
  77. package/extensions/file-tracker/replay.ts +46 -0
  78. package/extensions/file-tracker/tracker.ts +51 -4
  79. package/extensions/footer/footer-line.ts +207 -0
  80. package/extensions/footer/index.ts +152 -0
  81. package/extensions/footer/pr.ts +53 -0
  82. package/extensions/hooks/executor.ts +57 -20
  83. package/extensions/hooks/formatter-notice.ts +76 -0
  84. package/extensions/hooks/index.ts +314 -73
  85. package/extensions/hooks/matcher.ts +35 -1
  86. package/extensions/hooks/plugin-hooks.ts +3 -3
  87. package/extensions/hooks/protocol.ts +58 -0
  88. package/extensions/hooks/subagent-bridge.ts +77 -0
  89. package/extensions/hooks/trust.ts +15 -4
  90. package/extensions/init/index.ts +26 -0
  91. package/extensions/init/prompt.ts +40 -0
  92. package/extensions/interrupted/index.ts +35 -0
  93. package/extensions/interrupted/line.ts +9 -0
  94. package/extensions/lib/abort.ts +15 -0
  95. package/extensions/lib/agent-loader.ts +183 -0
  96. package/extensions/lib/anthropic-payload.ts +15 -0
  97. package/extensions/lib/atomic-write.ts +56 -0
  98. package/extensions/lib/bridge-watch.ts +20 -0
  99. package/extensions/lib/cache-retention.ts +60 -0
  100. package/extensions/lib/capability-index.ts +466 -0
  101. package/extensions/lib/claude-context.ts +318 -27
  102. package/extensions/lib/claude-settings.ts +64 -0
  103. package/extensions/lib/consent-preview.ts +30 -0
  104. package/extensions/lib/deferred.ts +144 -13
  105. package/extensions/lib/favorites.ts +43 -0
  106. package/extensions/lib/fetch-timeout.ts +15 -0
  107. package/extensions/lib/frontmatter.ts +31 -0
  108. package/extensions/lib/git.ts +44 -2
  109. package/extensions/lib/hook-gate.ts +90 -0
  110. package/extensions/lib/interrupt.ts +94 -0
  111. package/extensions/lib/mcp-overrides.ts +61 -0
  112. package/extensions/lib/mcp-share.ts +25 -0
  113. package/extensions/lib/mcp-status.ts +34 -0
  114. package/extensions/lib/memory.ts +41 -6
  115. package/extensions/lib/model-facts.generated.json +2480 -0
  116. package/extensions/lib/model-facts.ts +162 -0
  117. package/extensions/lib/model-policy.ts +145 -189
  118. package/extensions/lib/model-tier.ts +480 -64
  119. package/extensions/lib/model-unusable.ts +37 -0
  120. package/extensions/lib/notifications.ts +320 -1
  121. package/extensions/lib/one-code-settings.ts +63 -0
  122. package/extensions/lib/original-command.ts +71 -0
  123. package/extensions/lib/package-version.ts +31 -0
  124. package/extensions/lib/paths.ts +39 -4
  125. package/extensions/lib/permission-gate.ts +192 -0
  126. package/extensions/lib/persisted-output.ts +18 -0
  127. package/extensions/lib/pi-ai-estimate.ts +160 -0
  128. package/extensions/lib/pi-version.ts +1 -1
  129. package/extensions/lib/plan-mode-channels.ts +11 -0
  130. package/extensions/lib/plugin-overrides.ts +24 -0
  131. package/extensions/lib/plugin-root.ts +44 -0
  132. package/extensions/lib/plugins.ts +233 -54
  133. package/extensions/lib/prefix-warm-gate.ts +157 -0
  134. package/extensions/lib/process-tree.ts +56 -0
  135. package/extensions/lib/reminders.ts +321 -68
  136. package/extensions/lib/scratchpad.ts +11 -8
  137. package/extensions/lib/session-lifecycle.ts +28 -0
  138. package/extensions/lib/settings-channels.ts +14 -0
  139. package/extensions/lib/shell-tasks.ts +54 -0
  140. package/extensions/lib/skill-overrides.ts +86 -0
  141. package/extensions/lib/skill-scan.ts +110 -0
  142. package/extensions/lib/text-width.ts +267 -0
  143. package/extensions/lib/tui-render.ts +281 -54
  144. package/extensions/lib/usage-bus.ts +75 -0
  145. package/extensions/lib/usage-tracker.ts +67 -0
  146. package/extensions/lib/worktree-isolation.ts +170 -0
  147. package/extensions/lsp/client.ts +69 -12
  148. package/extensions/lsp/format.ts +12 -2
  149. package/extensions/lsp/index.ts +273 -84
  150. package/extensions/lsp/install-hints.ts +43 -0
  151. package/extensions/lsp/keep-alive.ts +19 -0
  152. package/extensions/lsp/plugin-servers.ts +331 -0
  153. package/extensions/lsp/watcher.ts +157 -0
  154. package/extensions/mcp/announce.ts +99 -0
  155. package/extensions/mcp/client.ts +181 -22
  156. package/extensions/mcp/config.ts +55 -24
  157. package/extensions/mcp/index.ts +586 -86
  158. package/extensions/mcp/oauth/browser.ts +26 -0
  159. package/extensions/mcp/oauth/callback.ts +104 -0
  160. package/extensions/mcp/oauth/flow.ts +68 -0
  161. package/extensions/mcp/oauth/provider.ts +147 -0
  162. package/extensions/mcp/oauth/store.ts +86 -0
  163. package/extensions/mcp/panel/keys.ts +34 -0
  164. package/extensions/mcp/panel/model.ts +62 -0
  165. package/extensions/mcp/panel/render.ts +141 -0
  166. package/extensions/mcp/panel/state.ts +108 -0
  167. package/extensions/mcp/schema.ts +83 -6
  168. package/extensions/mcp/trust.ts +340 -0
  169. package/extensions/memory/entries.ts +163 -0
  170. package/extensions/memory/index.ts +119 -2
  171. package/extensions/memory/open-external.ts +97 -0
  172. package/extensions/memory/panel.ts +124 -0
  173. package/extensions/model-default/index.ts +74 -0
  174. package/extensions/notebook/index.ts +36 -18
  175. package/extensions/notebook/notebook.ts +110 -17
  176. package/extensions/permissions/index.ts +1012 -247
  177. package/extensions/permissions/matcher.ts +488 -57
  178. package/extensions/permissions/modes.ts +55 -7
  179. package/extensions/permissions/project-trust.ts +81 -0
  180. package/extensions/permissions/protected-paths.ts +16 -3
  181. package/extensions/permissions/rule-prose.ts +79 -0
  182. package/extensions/permissions/session-grant.ts +91 -0
  183. package/extensions/permissions/settings.ts +112 -33
  184. package/extensions/permissions/subagent-gate.ts +57 -0
  185. package/extensions/plan-mode/index.ts +143 -33
  186. package/extensions/plan-mode/reminder.ts +16 -19
  187. package/extensions/plan-mode/viewer.ts +40 -31
  188. package/extensions/plugins/counts.ts +68 -0
  189. package/extensions/plugins/index.ts +496 -32
  190. package/extensions/plugins/install/install.ts +167 -0
  191. package/extensions/plugins/install/paths.ts +23 -0
  192. package/extensions/plugins/install/registry.ts +72 -0
  193. package/extensions/plugins/marketplace/git.ts +103 -0
  194. package/extensions/plugins/marketplace/official.ts +36 -0
  195. package/extensions/plugins/marketplace/parse.ts +94 -0
  196. package/extensions/plugins/marketplace/registry.ts +60 -0
  197. package/extensions/plugins/marketplace/sync.ts +146 -0
  198. package/extensions/plugins/marketplace/types.ts +128 -0
  199. package/extensions/plugins/panel/keys.ts +52 -0
  200. package/extensions/plugins/panel/render.ts +315 -0
  201. package/extensions/plugins/panel/rows.ts +230 -0
  202. package/extensions/plugins/panel/state.ts +333 -0
  203. package/extensions/recap/index.ts +215 -0
  204. package/extensions/recap/prompt.ts +44 -0
  205. package/extensions/recap/scheduler.ts +87 -0
  206. package/extensions/search-tools/index.ts +0 -0
  207. package/extensions/search-tools/tools.ts +7 -5
  208. package/extensions/skill/index.ts +360 -34
  209. package/extensions/skill/invoke.ts +185 -0
  210. package/extensions/skill/listing.ts +21 -0
  211. package/extensions/skill/panel/keys.ts +41 -0
  212. package/extensions/skill/panel/render.ts +91 -0
  213. package/extensions/skill/panel/state.ts +115 -0
  214. package/extensions/spinner/index.ts +110 -0
  215. package/extensions/spinner/line.ts +51 -0
  216. package/extensions/spinner/verbs.ts +201 -0
  217. package/extensions/subagents/agents.ts +88 -44
  218. package/extensions/subagents/default-model.ts +62 -32
  219. package/extensions/subagents/delegation-steer.ts +23 -0
  220. package/extensions/subagents/hand-back-review.ts +67 -0
  221. package/extensions/subagents/index.ts +1545 -346
  222. package/extensions/subagents/live-runs.ts +293 -0
  223. package/extensions/subagents/model-select.ts +252 -82
  224. package/extensions/subagents/outcome.ts +90 -0
  225. package/extensions/subagents/panel-keys.ts +41 -0
  226. package/extensions/subagents/panel-render.ts +357 -0
  227. package/extensions/subagents/panel-widget.ts +330 -0
  228. package/extensions/subagents/pending-claim.ts +45 -0
  229. package/extensions/subagents/prose.ts +105 -0
  230. package/extensions/subagents/runner.ts +674 -0
  231. package/extensions/subagents/runs.ts +68 -1
  232. package/extensions/subagents/send-to-main-tool.ts +55 -0
  233. package/extensions/subagents/session-turns.ts +138 -0
  234. package/extensions/subagents/shell-panel.ts +198 -0
  235. package/extensions/subagents/usage.ts +16 -4
  236. package/extensions/subagents/worktree.ts +13 -0
  237. package/extensions/system-prompt/environment.ts +5 -3
  238. package/extensions/system-prompt/git-status.ts +93 -0
  239. package/extensions/system-prompt/index.ts +23 -1
  240. package/extensions/system-prompt/template.ts +34 -4
  241. package/extensions/system-prompt/tiers/common.ts +31 -6
  242. package/extensions/system-prompt/tiers/low.ts +13 -3
  243. package/extensions/system-prompt/tiers/mid.ts +21 -8
  244. package/extensions/system-reminder/index.ts +53 -9
  245. package/extensions/tasks/index.ts +65 -16
  246. package/extensions/tasks/store.ts +76 -3
  247. package/extensions/tool-search/announce.ts +42 -0
  248. package/extensions/tool-search/index.ts +136 -13
  249. package/extensions/turn-duration/index.ts +60 -0
  250. package/extensions/turn-duration/line.ts +22 -0
  251. package/extensions/turn-duration/span.ts +41 -0
  252. package/extensions/turn-duration/verbs.ts +24 -0
  253. package/extensions/web/backends.ts +421 -0
  254. package/extensions/web/index.ts +178 -21
  255. package/extensions/web-fetch/index.ts +74 -18
  256. package/extensions/web-fetch/summarize.ts +1 -1
  257. package/extensions/workflow/agent-session.ts +182 -112
  258. package/extensions/workflow/globals.ts +129 -106
  259. package/extensions/workflow/index.ts +343 -28
  260. package/extensions/workflow/records.ts +118 -0
  261. package/extensions/workflow/run-manager.ts +80 -31
  262. package/extensions/workflow/saved-workflows.ts +2 -1
  263. package/extensions/workflow/script-worker.mjs +110 -0
  264. package/extensions/workflow/types.ts +74 -8
  265. package/extensions/workflow/viewer.ts +562 -0
  266. package/extensions/workflow/vm-runtime.ts +118 -22
  267. package/extensions/workflow/widget.ts +126 -18
  268. package/extensions/worktree/guards.ts +240 -0
  269. package/extensions/worktree/index.ts +30 -5
  270. package/extensions/worktree/rewrite.ts +32 -16
  271. package/package.json +20 -10
  272. package/skills/code-review/SKILL.md +165 -0
  273. package/skills/fewer-permission-prompts/SKILL.md +61 -0
  274. package/skills/security-review/SKILL.md +178 -0
  275. package/skills/simplify/SKILL.md +70 -0
  276. package/themes/{one-code-light.json → onecode-light.json} +6 -1
  277. package/themes/{one-code.json → onecode.json} +6 -1
  278. package/types/app.d.ts +8 -1
  279. package/types/community-packages.d.ts +45 -2
  280. package/docs/decisions/auto-mode.md +0 -526
  281. package/docs/decisions/branding.md +0 -127
  282. package/docs/decisions/code-review-remediation.md +0 -106
  283. package/docs/decisions/compaction.md +0 -140
  284. package/docs/decisions/distribution.md +0 -168
  285. package/docs/decisions/hooks.md +0 -64
  286. package/docs/decisions/lsp.md +0 -58
  287. package/docs/decisions/mcp.md +0 -104
  288. package/docs/decisions/memory-state.md +0 -170
  289. package/docs/decisions/model-policy.md +0 -126
  290. package/docs/decisions/modes.md +0 -178
  291. package/docs/decisions/skills-plugins.md +0 -56
  292. package/docs/decisions/subagents-workflows.md +0 -225
  293. package/docs/decisions/system-prompt.md +0 -98
  294. package/docs/decisions/tools.md +0 -407
  295. package/docs/decisions/tui.md +0 -202
  296. package/docs/decisions.md +0 -148
  297. package/extensions/subagents/child.ts +0 -414
  298. package/extensions/subagents/rpc-turns.ts +0 -123
  299. package/extensions/todo/index.ts +0 -133
  300. package/extensions/workflow/permission-gate.ts +0 -71
@@ -3,32 +3,73 @@
3
3
  *
4
4
  * ## Which files are read, and why that list is short
5
5
  *
6
- * User settings (`~/.claude/settings.json`) and managed settings only — **never
7
- * the project's `.claude/settings.json` or `.claude/settings.local.json`**. Both
8
- * live in the repository, so a checked-in file or a build step that writes one
9
- * could otherwise hand itself classifier allow rules and switch off the gate
10
- * that is meant to contain it. Claude Code made the same exclusion (it dropped
11
- * `settings.local.json` in v2.1.207); this is that property, not an omission.
6
+ * User settings (`~/.claude/settings.json`), One Code's own user settings
7
+ * (`~/.onecode/settings.json`), and managed settings only — **never the
8
+ * project's `.claude/settings.json` or `.claude/settings.local.json`**. Both
9
+ * project files live in the repository, so a checked-in file or a build step
10
+ * that writes one could otherwise hand itself classifier permissions and switch
11
+ * off the gate that is meant to contain it. Claude Code made the same exclusion
12
+ * (it dropped `settings.local.json` in v2.1.207); this is that property, not an
13
+ * omission.
12
14
  *
13
- * `"$defaults"` in any list splices the built-in rules in at that position, so
14
- * users keep inheriting updates. Omitting it replaces the list wholesale, which
15
- * is a real and occasionally correct choice — `/auto-mode config` shows the
16
- * result either way.
15
+ * ## Read vs. write: `.claude` is borrowed, `~/.onecode` is ours
16
+ *
17
+ * The CC-schema keys (`environment`, `hard_deny`/`soft_deny`/`allow`) are read
18
+ * from `~/.claude` too, because Claude Code's own `/auto-mode-setup` writes them
19
+ * there and reading them is the compatibility promise. But One Code's *own*
20
+ * keys — `classifierModel` / `classifierModelSetFor`, which Claude Code does not
21
+ * define — are read from `~/.onecode` and managed settings only, never from
22
+ * `~/.claude`: One Code used to write them there, and a stale value (a model the
23
+ * session cannot reach) must not keep biting. Every write goes to
24
+ * `~/.onecode/settings.json`; One Code never mutates Claude Code's files. See
25
+ * "Own state, borrowed config" in docs/decisions/memory-state.md.
26
+ *
27
+ * ## The customization surface (CC 2.1.233's own)
28
+ *
29
+ * The ruleset is CC's fixed monolith (classifier-prompt.ts); its prose is not
30
+ * user-editable, by design — the ruleset *is* the security boundary. What CC's
31
+ * `/auto-mode-setup` exposes (and we mirror exactly) is:
32
+ *
33
+ * - `environment` — replaces the `## Environment` slot lines. `"$defaults"`
34
+ * splices CC's default slots at that position, so a user can extend rather
35
+ * than replace.
36
+ * - `hard_deny` / `soft_deny` / `allow` — extra rules **appended** to the end of
37
+ * the matching embedded list, verbatim, exactly where CC injects them. These
38
+ * are append-only: `"$defaults"` is accepted (CC's wizard always writes it)
39
+ * but carries no meaning — the built-in rules always apply and cannot be
40
+ * removed or reordered from settings, so an extra rule can tighten or carve
41
+ * out, never rewrite the boundary.
42
+ *
43
+ * (The rule lists were briefly retired between the ruleset adoption and CC
44
+ * 2.1.233 shipping `/auto-mode-setup`, which writes this exact schema — see
45
+ * docs/decisions/auto-mode.md.)
17
46
  */
18
47
 
19
- import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
20
- import { dirname, join } from "node:path";
21
- import { DEFAULT_ALLOW, DEFAULT_ENVIRONMENT, DEFAULT_HARD_DENY, DEFAULT_SOFT_DENY } from "./defaults.ts";
48
+ import { existsSync, readFileSync } from "node:fs";
49
+ import { claudeUserSettingsPath } from "../lib/claude-settings.ts";
50
+ import { oneCodeSettingsPath, readSettingsForWrite, writeSettings } from "../lib/one-code-settings.ts";
51
+ import { DEFAULT_ENVIRONMENT, slotName } from "./defaults.ts";
22
52
 
23
53
  export interface AutoModeConfig {
54
+ /** Environment slot lines spliced into CC's ruleset (prefix-less, CC's settings format). */
24
55
  environment: string[];
56
+ /** Extra HARD BLOCK rules appended to the embedded list (`$defaults` already stripped). */
57
+ hardDeny: string[];
58
+ /** Extra SOFT BLOCK rules appended to the embedded list (`$defaults` already stripped). */
59
+ softDeny: string[];
60
+ /** Extra ALLOW rules appended to the embedded list (`$defaults` already stripped). */
25
61
  allow: string[];
26
- soft_deny: string[];
27
- hard_deny: string[];
28
62
  /** Suspend narrow Bash allow rules so every shell command reaches the classifier. */
29
63
  classifyAllShell: boolean;
30
64
  /** `provider/model-id` override for the classifier model. */
31
65
  classifierModel?: string;
66
+ /**
67
+ * Containment identity (`modelIdentity().containment`) the `classifierModel`
68
+ * was stamped for when set via `/auto-mode model`. A cross-provider setting
69
+ * whose stamp no longer matches the session is treated as stale and overridden
70
+ * — see `classifierCandidates`. Undefined for a hand-edited setting.
71
+ */
72
+ classifierModelSetFor?: string;
32
73
  /** Append every gate decision to auto-mode-decisions.jsonl next to the session files. */
33
74
  logDecisions: boolean;
34
75
  }
@@ -36,12 +77,14 @@ export interface AutoModeConfig {
36
77
  interface AutoModeSettingsFile {
37
78
  autoMode?: {
38
79
  environment?: unknown;
39
- allow?: unknown;
40
- soft_deny?: unknown;
41
80
  hard_deny?: unknown;
81
+ soft_deny?: unknown;
82
+ allow?: unknown;
42
83
  classifyAllShell?: unknown;
43
84
  classifierModel?: unknown;
85
+ classifierModelSetFor?: unknown;
44
86
  logDecisions?: unknown;
87
+ [key: string]: unknown;
45
88
  };
46
89
  [key: string]: unknown;
47
90
  }
@@ -49,15 +92,21 @@ interface AutoModeSettingsFile {
49
92
  const DEFAULTS_TOKEN = "$defaults";
50
93
 
51
94
  /** Managed-settings locations, highest authority, matching Claude Code's paths. */
52
- function managedSettingsPaths(): string[] {
95
+ export function managedSettingsPaths(): string[] {
53
96
  if (process.platform === "darwin") return ["/Library/Application Support/ClaudeCode/managed-settings.json"];
54
97
  if (process.platform === "win32") return ["C:\\ProgramData\\ClaudeCode\\managed-settings.json"];
55
98
  return ["/etc/claude-code/managed-settings.json"];
56
99
  }
57
100
 
58
- /** The files `autoMode` is read from, lowest precedence first. */
101
+ /**
102
+ * The files `autoMode` is read from, lowest precedence first. One Code's own
103
+ * auto-mode keys (`classifierModel`, `classifierModelSetFor`) are deliberately
104
+ * NOT collected from the borrowed `claudeUserSettingsPath` — Claude Code does not
105
+ * define them, and One Code once wrote them there, so a stale value must not
106
+ * survive the move to `~/.onecode` (see the read guard in the loader).
107
+ */
59
108
  export function autoModeSettingsPaths(home: string): string[] {
60
- return [join(home, ".claude", "settings.json"), ...managedSettingsPaths()];
109
+ return [claudeUserSettingsPath(home), oneCodeSettingsPath(home), ...managedSettingsPaths()];
61
110
  }
62
111
 
63
112
  function readFile(path: string, diagnostics: string[]): AutoModeSettingsFile | undefined {
@@ -75,45 +124,93 @@ function readFile(path: string, diagnostics: string[]): AutoModeSettingsFile | u
75
124
 
76
125
  const KNOWN_AUTO_MODE_KEYS = new Set([
77
126
  "environment",
78
- "allow",
79
- "soft_deny",
80
127
  "hard_deny",
128
+ "soft_deny",
129
+ "allow",
81
130
  "classifyAllShell",
82
131
  "classifierModel",
132
+ "classifierModelSetFor",
83
133
  "logDecisions",
84
134
  ]);
85
135
 
86
- const RULE_LIST_KEYS = ["environment", "allow", "soft_deny", "hard_deny"] as const;
136
+ /** Rule-extra keys, CC's spelling; appended to the matching embedded list. */
137
+ const RULE_LIST_KEYS = ["hard_deny", "soft_deny", "allow"] as const;
87
138
 
88
139
  /**
89
140
  * Shape problems in one file's `autoMode` block, as human-readable lines.
90
141
  * Purely advisory: loading stays lenient (a wrong-typed field is skipped, not
91
- * fatal), but skipped-because-mistyped must be visible in `/auto-mode config`
92
- * or it reads as the gate ignoring the user's settings.
142
+ * fatal), but skipped-because-mistyped must be visible in `/auto-mode config` or
143
+ * it reads as the gate ignoring the user's settings.
93
144
  */
94
- function validateAutoModeBlock(block: Record<string, unknown>, path: string, diagnostics: string[]): void {
95
- for (const key of Object.keys(block)) {
96
- if (!KNOWN_AUTO_MODE_KEYS.has(key)) diagnostics.push(`${path}: unknown autoMode key "${key}" is ignored`);
145
+ /**
146
+ * Validate one array-of-strings autoMode key. `omitDefaultsNote`, when given,
147
+ * is the warning pushed if "$defaults" is missing. Purely advisory: loading
148
+ * stays lenient either way.
149
+ */
150
+ function validateStringListKey(
151
+ block: Record<string, unknown>,
152
+ key: string,
153
+ path: string,
154
+ diagnostics: string[],
155
+ omitDefaultsNote?: string,
156
+ ): void {
157
+ const list = block[key];
158
+ if (list === undefined) return;
159
+ if (!Array.isArray(list)) {
160
+ diagnostics.push(`${path}: autoMode.${key} must be an array of strings — ignored`);
161
+ return;
97
162
  }
98
- for (const key of RULE_LIST_KEYS) {
99
- const value = block[key];
100
- if (value === undefined) continue;
101
- if (!Array.isArray(value)) {
102
- diagnostics.push(`${path}: autoMode.${key} must be an array of strings — ignored`);
103
- continue;
163
+ for (const [position, entry] of list.entries()) {
164
+ if (typeof entry !== "string" || entry.trim().length === 0) {
165
+ diagnostics.push(`${path}: autoMode.${key}[${position}] is not a non-empty string — dropped`);
104
166
  }
105
- for (const [position, entry] of value.entries()) {
106
- if (typeof entry !== "string" || entry.trim().length === 0) {
107
- diagnostics.push(`${path}: autoMode.${key}[${position}] is not a non-empty string — dropped`);
108
- }
167
+ }
168
+ if (!omitDefaultsNote) return;
169
+ const strings = list.filter((entry): entry is string => typeof entry === "string");
170
+ if (strings.length > 0 && !strings.some((entry) => entry.trim() === DEFAULTS_TOKEN)) {
171
+ diagnostics.push(`${path}: autoMode.${key} ${omitDefaultsNote}`);
172
+ }
173
+ }
174
+
175
+ function validateAutoModeBlock(block: Record<string, unknown>, path: string, diagnostics: string[]): void {
176
+ for (const key of Object.keys(block)) {
177
+ if (!KNOWN_AUTO_MODE_KEYS.has(key)) {
178
+ diagnostics.push(`${path}: unknown autoMode key "${key}" is ignored`);
109
179
  }
110
- const strings = value.filter((entry): entry is string => typeof entry === "string");
180
+ }
181
+ validateStringListKey(block, "environment", path, diagnostics);
182
+ // A FULL environment replacement (every built-in slot re-stated, edited or
183
+ // not) is the standard shape — CC's wizard and ours both write it — so it
184
+ // earns no warning. What does: a replacement that silently DROPS built-in
185
+ // slots because "$defaults" is absent — usually a hand-written list whose
186
+ // author believed they were adding, not replacing.
187
+ const environment = block.environment;
188
+ if (Array.isArray(environment)) {
189
+ const strings = environment.filter((entry): entry is string => typeof entry === "string");
111
190
  if (strings.length > 0 && !strings.some((entry) => entry.trim() === DEFAULTS_TOKEN)) {
112
- diagnostics.push(
113
- `${path}: autoMode.${key} omits "${DEFAULTS_TOKEN}", so it REPLACES the built-in ${key} list rather than extending it`,
114
- );
191
+ const present = new Set(strings.map(slotName).filter(Boolean));
192
+ const missing = DEFAULT_ENVIRONMENT.map(slotName).filter((name): name is string => !!name && !present.has(name));
193
+ if (missing.length > 0) {
194
+ const shown = missing.slice(0, 3).join(", ") + (missing.length > 3 ? ", …" : "");
195
+ diagnostics.push(
196
+ `${path}: autoMode.environment omits "${DEFAULTS_TOKEN}", so it REPLACES the built-in environment rather than extending it — built-in slot(s) now missing: ${shown}`,
197
+ );
198
+ }
115
199
  }
116
200
  }
201
+ // Unlike environment, the rule lists are append-only: the built-in rules
202
+ // always apply whether or not "$defaults" is present (a settings file must be
203
+ // able to add rules, never to remove the boundary), and nobody should believe
204
+ // omitting the token disabled them.
205
+ for (const key of RULE_LIST_KEYS) {
206
+ validateStringListKey(
207
+ block,
208
+ key,
209
+ path,
210
+ diagnostics,
211
+ `omits "${DEFAULTS_TOKEN}" — note the built-in rules still apply; these lists only append, never replace`,
212
+ );
213
+ }
117
214
  for (const key of ["classifyAllShell", "logDecisions"] as const) {
118
215
  if (block[key] !== undefined && typeof block[key] !== "boolean") {
119
216
  diagnostics.push(`${path}: autoMode.${key} must be a boolean — ignored`);
@@ -133,8 +230,8 @@ function stringArray(value: unknown): string[] | undefined {
133
230
  }
134
231
 
135
232
  /**
136
- * Splice `$defaults` into a user list. Entries from separate scopes are
137
- * additive: a developer can extend a managed list but not remove from it.
233
+ * Splice `$defaults` into a user list. Entries from separate scopes are additive:
234
+ * a developer can extend a managed list but not remove from it.
138
235
  */
139
236
  export function spliceDefaults(entries: string[] | undefined, defaults: string[]): string[] {
140
237
  if (!entries) return [...defaults];
@@ -156,14 +253,16 @@ export function loadAutoModeConfigWithDiagnostics(home: string): AutoModeConfigL
156
253
  const diagnostics: string[] = [];
157
254
  const collected: {
158
255
  environment?: string[];
159
- allow?: string[];
160
- soft_deny?: string[];
161
256
  hard_deny?: string[];
257
+ soft_deny?: string[];
258
+ allow?: string[];
162
259
  classifyAllShell?: boolean;
163
260
  classifierModel?: string;
261
+ classifierModelSetFor?: string;
164
262
  logDecisions?: boolean;
165
263
  } = {};
166
264
 
265
+ const claudeUser = claudeUserSettingsPath(home);
167
266
  for (const path of autoModeSettingsPaths(home)) {
168
267
  const file = readFile(path, diagnostics);
169
268
  const block = file?.autoMode;
@@ -173,27 +272,52 @@ export function loadAutoModeConfigWithDiagnostics(home: string): AutoModeConfigL
173
272
  }
174
273
  if (!block) continue;
175
274
  validateAutoModeBlock(block as Record<string, unknown>, path, diagnostics);
275
+ // Additive across scopes: a later scope extends rather than replaces, so
276
+ // managed entries cannot be dropped by a user file.
277
+ const parsed = stringArray(block.environment);
278
+ if (parsed) collected.environment = [...(collected.environment ?? []), ...parsed];
176
279
  for (const key of RULE_LIST_KEYS) {
177
- const parsed = stringArray(block[key]);
178
- // Additive across scopes: a later scope extends rather than replaces,
179
- // so managed entries cannot be dropped by a user file.
180
- if (parsed) collected[key] = [...(collected[key] ?? []), ...parsed];
280
+ const list = stringArray(block[key]);
281
+ if (list) collected[key] = [...(collected[key] ?? []), ...list];
181
282
  }
182
283
  if (typeof block.classifyAllShell === "boolean") collected.classifyAllShell = block.classifyAllShell;
183
284
  if (typeof block.logDecisions === "boolean") collected.logDecisions = block.logDecisions;
285
+ // `classifierModel` is One Code's own key, not Claude Code's. It is read from
286
+ // `~/.onecode` and managed settings only — never from `~/.claude`, where an
287
+ // old One Code build may have left a stale value. A leftover in `~/.claude` is
288
+ // surfaced as a diagnostic so the user knows why it stopped applying.
184
289
  if (typeof block.classifierModel === "string" && block.classifierModel.trim()) {
185
- collected.classifierModel = block.classifierModel.trim();
290
+ if (path === claudeUser) {
291
+ diagnostics.push(
292
+ `${path}: autoMode.classifierModel is ignored here — One Code reads it from ~/.onecode/settings.json (set it with /auto-mode model)`,
293
+ );
294
+ } else {
295
+ collected.classifierModel = block.classifierModel.trim();
296
+ // The stamp travels with the model from the same file, so a lower-precedence
297
+ // stamp cannot attach to a higher-precedence model.
298
+ collected.classifierModelSetFor =
299
+ typeof block.classifierModelSetFor === "string" && block.classifierModelSetFor.trim()
300
+ ? block.classifierModelSetFor.trim()
301
+ : undefined;
302
+ }
186
303
  }
187
304
  }
188
305
 
306
+ // Rule lists are extras appended to the embedded built-ins, so "$defaults" is
307
+ // stripped rather than expanded — the built-ins are already in the ruleset and
308
+ // must apply regardless of what settings say (append-only, by construction).
309
+ const ruleExtras = (entries: string[] | undefined): string[] =>
310
+ (entries ?? []).filter((entry) => entry.trim() !== DEFAULTS_TOKEN);
311
+
189
312
  return {
190
313
  config: {
191
314
  environment: spliceDefaults(collected.environment, DEFAULT_ENVIRONMENT),
192
- allow: spliceDefaults(collected.allow, DEFAULT_ALLOW),
193
- soft_deny: spliceDefaults(collected.soft_deny, DEFAULT_SOFT_DENY),
194
- hard_deny: spliceDefaults(collected.hard_deny, DEFAULT_HARD_DENY),
315
+ hardDeny: ruleExtras(collected.hard_deny),
316
+ softDeny: ruleExtras(collected.soft_deny),
317
+ allow: ruleExtras(collected.allow),
195
318
  classifyAllShell: collected.classifyAllShell ?? false,
196
319
  classifierModel: collected.classifierModel,
320
+ classifierModelSetFor: collected.classifierModelSetFor,
197
321
  logDecisions: collected.logDecisions ?? false,
198
322
  },
199
323
  diagnostics,
@@ -205,33 +329,111 @@ export function loadAutoModeConfig(home: string): AutoModeConfig {
205
329
  }
206
330
 
207
331
  /**
208
- * Persist `autoMode.classifierModel` in the *user* settings file, preserving
209
- * every other key. `undefined` removes the setting. Always user scope: this is
210
- * the knob that may move the classifier to another provider, so it lives where
211
- * only the user writes — never in a project file (see the module comment).
332
+ * Persist `autoMode.classifierModel` in One Code's own settings file
333
+ * (`~/.onecode/settings.json`), preserving every other key. `undefined` removes
334
+ * the setting. Never touches Claude Code's files — this is One Code's key, and
335
+ * the knob that may move the classifier to another provider, so it lives in One
336
+ * Code's own state (see the module comment).
212
337
  *
213
338
  * Unlike loading, this throws on a malformed file: a lenient read merely skips
214
- * rules, but a lenient write would replace the user's whole settings file with
215
- * only ours.
339
+ * rules, but a lenient write would replace the whole settings file with only ours.
216
340
  */
217
- export function persistClassifierModel(spec: string | undefined, home: string): void {
218
- const path = join(home, ".claude", "settings.json");
219
- let file: Record<string, unknown> = {};
220
- if (existsSync(path)) {
221
- const parsed = JSON.parse(readFileSync(path, "utf-8")) as unknown;
222
- if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
223
- throw new Error(`${path}: root must be a JSON object`);
224
- }
225
- file = parsed as Record<string, unknown>;
341
+ export function persistClassifierModel(spec: string | undefined, home: string, setForContainment?: string): void {
342
+ const path = oneCodeSettingsPath(home);
343
+ const file = readSettingsForWrite(path);
344
+ const block = asRecord(file.autoMode) ?? {};
345
+ if (spec === undefined) {
346
+ delete block.classifierModel;
347
+ delete block.classifierModelSetFor;
348
+ } else {
349
+ block.classifierModel = spec;
350
+ // Stamp the containment the model was chosen on, so a later session on a
351
+ // different provider treats a cross-provider setting as stale (parity with
352
+ // subagentModelSetFor). No stamp for a hand-edited setting.
353
+ if (setForContainment) block.classifierModelSetFor = setForContainment;
354
+ else delete block.classifierModelSetFor;
355
+ }
356
+ if (Object.keys(block).length === 0) delete file.autoMode;
357
+ else file.autoMode = block;
358
+ writeSettings(path, file);
359
+ }
360
+
361
+ /** value as a plain object, or undefined — the repeated sub-block guard. */
362
+ function asRecord(value: unknown): Record<string, unknown> | undefined {
363
+ return typeof value === "object" && value !== null && !Array.isArray(value)
364
+ ? (value as Record<string, unknown>)
365
+ : undefined;
366
+ }
367
+
368
+ /**
369
+ * Persist a `/auto-mode setup` result to One Code's own settings file: replace
370
+ * the wizard-owned keys (environment + the three rule lists — an undefined list
371
+ * removes stale values, last-writer like CC's own wizard) while preserving every
372
+ * other settings key and the non-wizard autoMode keys (classifierModel, …).
373
+ * Never touches Claude Code's files; throws on a malformed file rather than
374
+ * replacing the settings with only ours.
375
+ */
376
+ export function persistAutoModeSetup(patch: Record<string, string[] | undefined>, home: string): void {
377
+ const path = oneCodeSettingsPath(home);
378
+ const file = readSettingsForWrite(path);
379
+ const block = asRecord(file.autoMode) ?? {};
380
+ for (const key of ["environment", ...RULE_LIST_KEYS] as const) {
381
+ const value = patch[key];
382
+ if (value === undefined) delete block[key];
383
+ else block[key] = value;
226
384
  }
227
- const block =
228
- typeof file.autoMode === "object" && file.autoMode !== null && !Array.isArray(file.autoMode)
229
- ? (file.autoMode as Record<string, unknown>)
230
- : {};
231
- if (spec === undefined) delete block.classifierModel;
232
- else block.classifierModel = spec;
233
385
  if (Object.keys(block).length === 0) delete file.autoMode;
234
386
  else file.autoMode = block;
235
- mkdirSync(dirname(path), { recursive: true });
236
- writeFileSync(path, `${JSON.stringify(file, null, 2)}\n`);
387
+ writeSettings(path, file);
388
+ }
389
+
390
+ /**
391
+ * The `permissions.allow` entries in One Code's own settings file — what the
392
+ * setup audit can actually remove. Broad rules that live in Claude Code's files
393
+ * are One Code's to warn about, never to delete.
394
+ */
395
+ export function oneCodePermissionAllow(home: string): string[] {
396
+ // Lenient read (a missing/malformed file is skipped) — this is a read for
397
+ // auditing, not a write, so leniency is correct; the writers still refuse to
398
+ // clobber a malformed file. Diagnostics are discarded here.
399
+ return permissionAllowIn(oneCodeSettingsPath(home));
400
+ }
401
+
402
+ /**
403
+ * The `permissions.allow` entries in Claude Code's *user* settings file — the
404
+ * borrowed half of the audit. These are One Code's to warn about, never to
405
+ * delete: an entry here still bypasses the classifier even after its One Code
406
+ * copy is removed, so the audit must flag it separately.
407
+ */
408
+ export function claudeUserPermissionAllow(home: string): string[] {
409
+ return permissionAllowIn(claudeUserSettingsPath(home));
410
+ }
411
+
412
+ /** Shared lenient read of a settings file's `permissions.allow` string list. */
413
+ function permissionAllowIn(path: string): string[] {
414
+ const list = asRecord(readFile(path, [])?.permissions)?.allow;
415
+ return Array.isArray(list) ? list.filter((entry): entry is string => typeof entry === "string") : [];
416
+ }
417
+
418
+ /**
419
+ * Remove specific `permissions.allow` entries from One Code's own settings file
420
+ * (the setup audit's "remove them" choice). Only One Code's own file is
421
+ * touched — never Claude Code's. Exact-match only, everything else preserved;
422
+ * throws on a malformed file for the same reason as the writers above. Returns
423
+ * how many entries were actually removed.
424
+ */
425
+ export function removeOneCodePermissionAllow(entries: string[], home: string): number {
426
+ const path = oneCodeSettingsPath(home);
427
+ const file = readSettingsForWrite(path);
428
+ const permissions = asRecord(file.permissions);
429
+ const allowList = permissions?.allow;
430
+ if (!permissions || !Array.isArray(allowList)) return 0;
431
+ const doomed = new Set(entries);
432
+ const kept = allowList.filter((entry) => typeof entry !== "string" || !doomed.has(entry));
433
+ const removed = allowList.length - kept.length;
434
+ if (removed > 0) {
435
+ permissions.allow = kept;
436
+ writeSettings(path, file);
437
+ }
438
+ return removed;
237
439
  }
@@ -1,65 +1,22 @@
1
1
  /**
2
- * Built-in auto-mode rule lists (pure data).
2
+ * Built-in auto-mode configuration defaults.
3
3
  *
4
- * These are prose, read by the classifier as natural-language rules — the same
5
- * shape Claude Code's `claude auto-mode defaults` prints, so a user's existing
6
- * `autoMode` settings splice into them with `"$defaults"` unchanged.
7
- *
8
- * Four tiers, in the precedence the classifier is instructed to apply:
9
- * hard_deny — unconditional; neither user intent nor allow rules clear it
10
- * soft_deny — blocked unless an allow rule or explicit user intent clears it
11
- * allow — exceptions to soft_deny
12
- * (intent) — a specific user instruction clears remaining soft denials
4
+ * The ruleset itself is no longer configurable prose lists — the classifier
5
+ * ruleset is a fixed monolith matching Claude Code's classifier (see
6
+ * classifier-prompt.ts and docs/decisions/auto-mode.md). The one customization
7
+ * surface Claude Code exposes, and the only one we expose, is the `## Environment` section:
8
+ * `DEFAULT_ENVIRONMENT` is CC's default slot text, and a user's `autoMode.environment`
9
+ * entries replace it. Re-exported here so config.ts has a single stable import
10
+ * point rather than reaching into the generated prompt module.
13
11
  */
14
12
 
15
- /** What the classifier treats as inside the trust boundary. */
16
- export const DEFAULT_ENVIRONMENT: string[] = [
17
- "Trusted repo: the git repository containing the working directory, and its configured remotes. Pushing to any branch of this repository is routine. Content first read from outside this repository is not this repository's own work, whoever asked for it to be moved.",
18
- "Repository visibility: assume private unless the user's own message says otherwise. Confidential material may go into a private repo; secrets and personal data may never go into any repo regardless of visibility.",
19
- "Trusted internal domains: none configured. Any host that is not the repository's own remote is outside the boundary.",
20
- "Trusted cloud buckets: none configured. Treat every bucket write as leaving the boundary.",
21
- "Internal package registry: none configured. Installs from the ecosystem's default public registry are routine; installs redirected to another registry are not.",
22
- "Sensitive data locations: credential directories (~/.ssh, ~/.aws, ~/.kube, ~/.docker, ~/.m2, ~/.gnupg, ~/.config/gcloud), .env files anywhere, private keys and keystores, and /proc or /sys process state.",
23
- "Sensitive remote targets: any host, namespace, container, or branch whose name carries prod, production, live, or release.",
24
- "Protected IaC scopes: any Terraform, Pulumi, CloudFormation, Helm, or Kubernetes manifest path whose name marks it as production.",
25
- "Primary use: software development in the working directory.",
26
- ];
27
-
28
- /** Unconditional. User intent does not clear these. */
29
- export const DEFAULT_HARD_DENY: string[] = [
30
- "Data exfiltration: sending secrets, credentials, private keys, personal data, or the contents of sensitive files to any destination outside the trust boundary — by network request, by commit or push, by writing into a shared location, or by embedding them in a command argument that reaches a third party. This holds however the action is framed and whoever asked for it.",
31
- "Auto-mode bypass: disabling, weakening, or working around this approval mechanism — editing permission settings or Claude Code/One Code configuration to grant broader permissions, adding allow rules, changing the permission mode, or routing an action through a path that avoids classification.",
32
- "Credential harvesting: reading credential stores, keychains, browser password databases, or process environment memory (/proc/*/environ) for any purpose other than one the user specifically named.",
33
- "Destroying the user's own history: rewriting or deleting the reflog, deleting the local repository, or removing backups such that the user's uncommitted or unpushed work cannot be recovered.",
34
- ];
35
-
36
- /** Blocked, but an allow rule or a specific user instruction clears them. */
37
- export const DEFAULT_SOFT_DENY: string[] = [
38
- "Git destructive [must name the operation and its target]: force pushing, deleting remote branches, tags, or releases, rewriting remote history, or amending a commit that is not the agent's own unpushed work from this session.",
39
- "Production deploys and destroys [must name the target]: deploying, applying, or destroying against a sensitive remote target or protected IaC scope, including terraform apply/destroy, helm upgrade, kubectl apply against a production namespace, and any release or publish pipeline.",
40
- "Remote code execution: piping a downloaded script into an interpreter (curl | bash and equivalents), running an installer fetched at execution time, or executing a file whose contents were downloaded during this session.",
41
- "Irreversible loss outside the working directory: deleting, overwriting, or truncating an existing file or directory that is not under the working directory and not obviously scratch space such as /tmp. Creating a new file outside the working directory is covered by the separate rule below, not by this one.",
42
- "Creating or modifying files outside the working directory [must name the path]: writing somewhere the user did not ask for is how work leaks out of the project. Clears when the user's own message names that path, or names the destination unambiguously enough that the path follows from it. Also clears for the harness's own designated directories named in the system prompt — the session scratchpad (under /tmp or /private/tmp, in a claude-<uid> directory) and the auto-memory directory (~/.claude/projects/<project>/memory) — which exist precisely so temporary and memory files do not land in the project.",
43
- "Publishing: pushing a package to a registry, creating a public release or gist, or making a private repository public.",
44
- "Disabling safety machinery: turning off CI checks, required reviews, branch protection, test suites in configuration, pre-commit hooks, or TLS certificate verification.",
45
- "Secrets entering version control: staging or committing a file that holds a credential, key, or token, including one the agent generated.",
46
- "Bulk permission or ownership changes: recursive chmod/chown, or making files world-writable.",
47
- "Writing to paths whose contents execute later without further approval: .git/hooks, .git/config, editor auto-run configuration, or a build wrapper such as mvnw or gradlew.",
48
- "Database mutation outside a migration tool: dropping, truncating, or bulk-updating tables directly, against any database.",
49
- "Remote shells and port-forwards into a sensitive remote target.",
50
- "Sending repository contents to a third-party service that is not part of the configured toolchain.",
51
- ];
13
+ export { DEFAULT_ENVIRONMENT } from "./classifier-prompt.ts";
52
14
 
53
- /** Exceptions to soft_deny. */
54
- export const DEFAULT_ALLOW: string[] = [
55
- "Local file operations inside the working directory: creating, editing, moving, and deleting files and directories under it, including build output and caches.",
56
- "Reading anything inside the working directory, and reading system and tool configuration that is not on the sensitive-data list.",
57
- "Installing dependencies already declared in the project's manifest (package.json, pyproject.toml, go.mod, pom.xml, Cargo.toml) from the ecosystem's default registry.",
58
- "Running the project's own build, test, lint, format, and type-check commands, including ones that write to build directories.",
59
- "Read-only information lookups that retrieve only public data and send none of the user's own: HTTP GET/HEAD to public documentation, package metadata, and credential-free API endpoints; and public registry or DNS queries such as whois, dig, nslookup, and host, including ones that name the registry or DNS server to query.",
60
- "Git operations that do not rewrite published history: status, diff, log, add, commit, branch, checkout, merge, rebase of unpushed local work, stash, and pushing to any branch of the trusted repo.",
61
- "Creating and updating pull requests on the trusted repo, and reading issues and CI results.",
62
- "Starting, stopping, and querying local development servers and containers bound to loopback.",
63
- "Test artifacts: placeholder credentials authored as placeholders in examples and fixtures. A value copied from a real secret or a sensitive path is never a test artifact.",
64
- "Reading the project's own .env file when the user asked for work that requires its configuration, provided the values do not leave the machine.",
65
- ];
15
+ /**
16
+ * The `**Name**:` head of an Environment slot line, or undefined for header
17
+ * ("### Org-wide") and prose entries. Used to tell a full slot replacement
18
+ * (the wizard's normal output) from a partial one that silently drops slots.
19
+ */
20
+ export function slotName(line: string): string | undefined {
21
+ return line.match(/^\*\*(.+?)\*\*:/)?.[1];
22
+ }
@@ -13,6 +13,7 @@
13
13
 
14
14
  import { existsSync, readFileSync } from "node:fs";
15
15
  import { dirname, join } from "node:path";
16
+ import { claudeUserDir } from "../lib/paths.ts";
16
17
 
17
18
  const FILE_NAMES = ["CLAUDE.md", "CLAUDE.local.md", "AGENTS.md"];
18
19
 
@@ -68,7 +69,7 @@ export function loadProjectInstructions(cwd: string, home: string): string | und
68
69
  if (parent === dir) break;
69
70
  dir = parent;
70
71
  }
71
- add(join(home, ".claude", "CLAUDE.md"));
72
+ add(join(claudeUserDir(home), "CLAUDE.md"));
72
73
 
73
74
  return parts.length > 0 ? parts.join("\n\n") : undefined;
74
75
  }