opencode-plugin-coding 0.1.2

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.

Potentially problematic release.


This version of opencode-plugin-coding might be problematic. Click here for more details.

@@ -0,0 +1,211 @@
1
+ /**
2
+ * opencode-plugin-coding — OpenCode plugin for multi-agent coding workflows.
3
+ *
4
+ * Registers skills, commands, and agents dynamically via the config hook.
5
+ * Agents are registered from workflow.json + guide files — no .opencode/agents/*.md needed.
6
+ * Install: add to "plugin" array in opencode.json (global or project).
7
+ */
8
+
9
+ import path from "path";
10
+ import fs from "fs";
11
+ import { fileURLToPath } from "url";
12
+
13
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
14
+ const pluginRoot = path.resolve(__dirname, "../..");
15
+
16
+ // Simple frontmatter extraction (no external dependencies)
17
+ const extractFrontmatter = (content) => {
18
+ const match = content.match(/^---\n([\s\S]*?)\n---\n([\s\S]*)$/);
19
+ if (!match) return { meta: {}, body: content };
20
+
21
+ const meta = {};
22
+ for (const line of match[1].split("\n")) {
23
+ const idx = line.indexOf(":");
24
+ if (idx > 0) {
25
+ const key = line.slice(0, idx).trim();
26
+ const value = line
27
+ .slice(idx + 1)
28
+ .trim()
29
+ .replace(/^["']|["']$/g, "");
30
+ meta[key] = value;
31
+ }
32
+ }
33
+ return { meta, body: match[2] };
34
+ };
35
+
36
+ // Load all command .md files from commands/ and register them via config
37
+ const loadCommands = () => {
38
+ const commandsDir = path.join(pluginRoot, "commands");
39
+ if (!fs.existsSync(commandsDir)) return {};
40
+
41
+ const commands = {};
42
+ for (const file of fs.readdirSync(commandsDir)) {
43
+ if (!file.endsWith(".md")) continue;
44
+ const name = file.replace(/\.md$/, "");
45
+ const content = fs.readFileSync(path.join(commandsDir, file), "utf8");
46
+ const { meta, body } = extractFrontmatter(content);
47
+
48
+ commands[name] = {
49
+ description: meta.description || `Run /${name}`,
50
+ template: body.trim(),
51
+ };
52
+ if (meta.agent) commands[name].agent = meta.agent;
53
+ if (meta.model) commands[name].model = meta.model;
54
+ }
55
+ return commands;
56
+ };
57
+
58
+ // Read a guide file and return its content as a prompt string
59
+ const readGuide = (filename) => {
60
+ const guidePath = path.join(pluginRoot, "guides", filename);
61
+ if (!fs.existsSync(guidePath)) return "";
62
+ return fs.readFileSync(guidePath, "utf8").trim();
63
+ };
64
+
65
+ // Read workflow.json from the project directory
66
+ const readWorkflowJson = (directory) => {
67
+ const workflowPath = path.join(directory, ".opencode", "workflow.json");
68
+ if (!fs.existsSync(workflowPath)) return null;
69
+ try {
70
+ return JSON.parse(fs.readFileSync(workflowPath, "utf8"));
71
+ } catch {
72
+ return null;
73
+ }
74
+ };
75
+
76
+ // Agent role definitions: guide file, description, and permissions
77
+ const AGENT_ROLES = {
78
+ coreCoder: {
79
+ guide: "core-coder-guide.md",
80
+ description:
81
+ "Core implementation agent — executes plans, writes code, runs verification, creates PRs.",
82
+ permission: {
83
+ edit: "allow",
84
+ bash: { "*": "allow" },
85
+ read: "allow",
86
+ webfetch: "allow",
87
+ },
88
+ },
89
+ coreReviewer: {
90
+ guide: "core-reviewer-guide.md",
91
+ description:
92
+ "Core code reviewer (blocking) — full verification in worktree, reviews all areas.",
93
+ permission: {
94
+ edit: "deny",
95
+ bash: { "*": "allow" },
96
+ read: "allow",
97
+ webfetch: "deny",
98
+ },
99
+ },
100
+ reviewer: {
101
+ guide: "reviewer-guide.md",
102
+ description:
103
+ "Code reviewer (non-blocking) — diff-based review on assigned areas.",
104
+ permission: {
105
+ edit: "deny",
106
+ bash: {
107
+ "*": "deny",
108
+ "gh api *": "allow",
109
+ "gh pr diff *": "allow",
110
+ "gh pr view *": "allow",
111
+ "gh pr checks *": "allow",
112
+ },
113
+ read: "allow",
114
+ webfetch: "deny",
115
+ },
116
+ },
117
+ securityReviewer: {
118
+ guide: "security-reviewer-guide.md",
119
+ description: "Security reviewer — pre-merge security analysis.",
120
+ permission: {
121
+ edit: "deny",
122
+ bash: {
123
+ "*": "deny",
124
+ "gh api *": "allow",
125
+ "gh pr diff *": "allow",
126
+ "gh pr view *": "allow",
127
+ "gh pr checks *": "allow",
128
+ },
129
+ read: "allow",
130
+ webfetch: "deny",
131
+ },
132
+ },
133
+ };
134
+
135
+ // Map workflow.json agent fields to their role key
136
+ const FIELD_TO_ROLE = {
137
+ coreCoder: "coreCoder",
138
+ coreReviewers: "coreReviewer",
139
+ reviewers: "reviewer",
140
+ securityReviewers: "securityReviewer",
141
+ };
142
+
143
+ // Register agents from workflow.json into cfg.agent
144
+ const registerAgents = (config, directory) => {
145
+ const workflow = readWorkflowJson(directory);
146
+ if (!workflow?.agents) return;
147
+
148
+ config.agent = config.agent || {};
149
+
150
+ for (const [field, roleKey] of Object.entries(FIELD_TO_ROLE)) {
151
+ const role = AGENT_ROLES[roleKey];
152
+ const prompt = readGuide(role.guide);
153
+ const entries = workflow.agents[field];
154
+ if (!entries) continue;
155
+
156
+ // Normalize: coreCoder is a single object, others are arrays
157
+ const agentList = Array.isArray(entries) ? entries : [entries];
158
+
159
+ for (const agent of agentList) {
160
+ // Support both { name, model } objects and bare strings (backward compat)
161
+ const name = typeof agent === "string" ? agent : agent.name;
162
+ const model = typeof agent === "string" ? undefined : agent.model;
163
+
164
+ if (!name) continue;
165
+
166
+ // Don't override user-defined agents
167
+ if (config.agent[name]) continue;
168
+
169
+ const agentConfig = {
170
+ description: role.description,
171
+ mode: "subagent",
172
+ prompt,
173
+ permission: role.permission,
174
+ };
175
+
176
+ // Only set model if explicitly provided (non-empty)
177
+ if (model) {
178
+ agentConfig.model = model;
179
+ }
180
+
181
+ config.agent[name] = agentConfig;
182
+ }
183
+ }
184
+ };
185
+
186
+ export const ZooplanktonCodingPlugin = async ({ directory }) => {
187
+ const skillsDir = path.join(pluginRoot, "skills");
188
+
189
+ return {
190
+ config: async (config) => {
191
+ // Skills — add our skills/ directory to discovery paths
192
+ config.skills = config.skills || {};
193
+ config.skills.paths = config.skills.paths || [];
194
+ if (!config.skills.paths.includes(skillsDir)) {
195
+ config.skills.paths.push(skillsDir);
196
+ }
197
+
198
+ // Commands — register from commands/*.md
199
+ config.command = config.command || {};
200
+ const pluginCommands = loadCommands();
201
+ for (const [name, cmd] of Object.entries(pluginCommands)) {
202
+ if (!config.command[name]) {
203
+ config.command[name] = cmd;
204
+ }
205
+ }
206
+
207
+ // Agents — register from workflow.json + guide files
208
+ registerAgents(config, directory);
209
+ },
210
+ };
211
+ };
package/README.md ADDED
@@ -0,0 +1,132 @@
1
+ # opencode-plugin-coding
2
+
3
+ A shared [OpenCode](https://opencode.ai) plugin for multi-agent software development workflows. Provides skills, guides, and dynamically registered agents that standardize the full cycle: brainstorm, plan, implement, review, debug, and retrospect.
4
+
5
+ ## Skills
6
+
7
+ | Skill | Description |
8
+ |-------|-------------|
9
+ | `brainstorm` | Socratic design interview that refines rough ideas into a validated design document |
10
+ | `plan` | Decomposes a design into bite-sized implementation tasks with file paths and acceptance criteria |
11
+ | `orchestrate` | Full multi-agent workflow: implement → review → merge → retrospective |
12
+ | `test-driven-development` | RED-GREEN-REFACTOR cycle enforcement |
13
+ | `systematic-debugging` | 4-phase debugging: reproduce → hypothesize → isolate → fix |
14
+ | `git-worktree` | Create and manage isolated git worktrees for parallel development |
15
+ | `playwright` | Browser automation via Playwright MCP server |
16
+
17
+ ## Commands
18
+
19
+ | Command | Description |
20
+ |---------|-------------|
21
+ | `/zooplankton-coding-init` | Auto-detect project, generate `workflow.json` |
22
+ | `/zooplankton-coding-update` | Check workflow.json schema updates, show plugin changes |
23
+
24
+ ## Guides
25
+
26
+ Guide files define the prompt and behavior for each agent role. The plugin loads them automatically — they are not installed in consumer projects.
27
+
28
+ - `guides/core-coder-guide.md` — Instructions for the core implementation agent
29
+ - `guides/core-reviewer-guide.md` — Instructions for core reviewers (worktree + full verification)
30
+ - `guides/reviewer-guide.md` — Instructions for normal reviewers (diff-based review)
31
+ - `guides/security-reviewer-guide.md` — Instructions for the security reviewer (pre-merge)
32
+
33
+ ## Setup
34
+
35
+ ### 1. Install the plugin
36
+
37
+ Add the plugin to your project's `opencode.json`:
38
+
39
+ ```json
40
+ {
41
+ "$schema": "https://opencode.ai/config.json",
42
+ "plugin": [
43
+ "opencode-plugin-coding"
44
+ ]
45
+ }
46
+ ```
47
+
48
+ OpenCode will auto-install the plugin from npm via Bun at startup. The plugin registers all skills, commands, and agents automatically — no symlinks, manual copies, or `.opencode/agents/*.md` files needed.
49
+
50
+ ### Pin a specific version (optional)
51
+
52
+ ```json
53
+ "plugin": ["opencode-plugin-coding@0.1.0"]
54
+ ```
55
+
56
+ ### Global installation (optional)
57
+
58
+ To make the plugin available across all projects without adding it to each project's `opencode.json`, add the same `plugin` entry to `~/.config/opencode/opencode.json`.
59
+
60
+ ### 2. Run /zooplankton-coding-init
61
+
62
+ From your project root in OpenCode, run:
63
+
64
+ ```
65
+ /zooplankton-coding-init
66
+ ```
67
+
68
+ This will:
69
+ - Auto-detect project settings (language, framework, package manager, commands)
70
+ - Generate `.opencode/workflow.json` with project-specific configuration and agent definitions
71
+ - Update `.gitignore` for ephemeral plugin files
72
+
73
+ ### 3. Configure agents
74
+
75
+ Review the `agents` section in `.opencode/workflow.json`. Each agent is a `{ name, model }` object. The plugin reads these at startup and dynamically registers agents with the appropriate permissions and prompts from the guide files. To change models or add/remove agents, just edit `workflow.json` and restart OpenCode.
76
+
77
+ > **How it works:** The plugin uses OpenCode's `config` hook to register agents via `config.agent`, skills via `config.skills.paths`, and commands via `config.command`.
78
+
79
+ ## Project-Level Files
80
+
81
+ After `/zooplankton-coding-init`, your project will have:
82
+
83
+ | File | Committed? | Purpose |
84
+ |------|-----------|---------|
85
+ | `.opencode/workflow.json` | Yes | Project configuration + agent definitions |
86
+ | `.opencode/reviewer-knowledge.json` | No (gitignored) | Adaptive reviewer scoring cache |
87
+ | `.opencode/plans/<branch>.md` | No (gitignored) | Ephemeral plan files |
88
+ | `.opencode/retrospectives/<branch>.md` | No (gitignored) | Ephemeral retrospective files |
89
+
90
+ ## Configuration
91
+
92
+ `workflow.json` schema:
93
+
94
+ ```json
95
+ {
96
+ "project": {
97
+ "name": "my-project",
98
+ "repo": "Org/my-project",
99
+ "defaultBranch": "master"
100
+ },
101
+ "stack": {
102
+ "language": "typescript",
103
+ "framework": "react",
104
+ "packageManager": "yarn"
105
+ },
106
+ "commands": {
107
+ "build": "yarn build",
108
+ "lint": "yarn lint",
109
+ "test": "yarn test",
110
+ "typecheck": "npx tsc --noEmit"
111
+ },
112
+ "agents": {
113
+ "coreCoder": { "name": "core-coder", "model": "github-copilot/claude-opus-4.6" },
114
+ "coreReviewers": [
115
+ { "name": "core-reviewer-1", "model": "github-copilot/claude-sonnet-4.6" },
116
+ { "name": "core-reviewer-2", "model": "github-copilot/gpt-5.4" }
117
+ ],
118
+ "reviewers": [
119
+ { "name": "reviewer-1", "model": "alibaba-coding-plan-cn/glm-5" },
120
+ { "name": "reviewer-2", "model": "alibaba-coding-plan-cn/MiniMax-M2.5" }
121
+ ],
122
+ "securityReviewers": []
123
+ },
124
+ "testDrivenDevelopment": { "enabled": false },
125
+ "docsToRead": ["AGENTS.md"],
126
+ "reviewFocus": ["type safety", "error handling"]
127
+ }
128
+ ```
129
+
130
+ ## License
131
+
132
+ MIT
@@ -0,0 +1,164 @@
1
+ ---
2
+ description: Auto-detect project settings and generate workflow.json for the coding plugin.
3
+ ---
4
+
5
+ # /zooplankton-coding-init — Project Setup
6
+
7
+ When the user runs `/zooplankton-coding-init`, perform the following steps to bootstrap the opencode-plugin-coding configuration for this project.
8
+
9
+ ## Step 1: Auto-Detect Project Settings
10
+
11
+ Read the project root and detect:
12
+
13
+ ### Project identity
14
+
15
+ ```bash
16
+ # Get repo name from git remote
17
+ REPO=$(git remote get-url origin 2>/dev/null | sed 's/.*[:/]\([^/]*\/[^/]*\)\.git$/\1/' | sed 's/.*[:/]\([^/]*\/[^/]*\)$/\1/')
18
+ PROJECT_NAME=$(basename "$(pwd)")
19
+ DEFAULT_BRANCH=$(git symbolic-ref refs/remotes/origin/HEAD 2>/dev/null | sed 's@^refs/remotes/origin/@@' || echo "master")
20
+ ```
21
+
22
+ ### Stack detection
23
+
24
+ 1. Read `package.json` if it exists:
25
+ - `language`: "typescript" if `tsconfig.json` exists, else "javascript"
26
+ - `framework`: detect from dependencies — "react-native", "react", "vue", "next", "nuxt", "express", "koa", "electron", etc.
27
+ - `packageManager`: check for `yarn.lock` → "yarn", `pnpm-lock.yaml` → "pnpm", `bun.lockb` → "bun", else "npm"
28
+
29
+ 2. If no `package.json`, check for other project types (Python, Go, Rust, etc.) and set accordingly.
30
+
31
+ ### Command detection
32
+
33
+ Look at `package.json` scripts and detect:
34
+
35
+ - `build`: prefer `scripts.build` → `"<pm> build"`, else `""`
36
+ - `lint`: prefer `scripts.lint` → `"<pm> lint"`, else check for eslint/stylelint/prettier individually
37
+ - `test`: prefer `scripts.test` → `"<pm> test"`, else `""`
38
+ - `typecheck`: if TypeScript, prefer `scripts.typecheck` → `"<pm> typecheck"`, else `"npx tsc --noEmit"` if `tsconfig.json` exists
39
+
40
+ ### Docs to read
41
+
42
+ Detect which docs exist and add them:
43
+
44
+ ```bash
45
+ # Check for common doc files
46
+ ls AGENTS.md doc/CODE_STYLE.md doc/UPGRADE_PLAN.md doc/Game.md doc/MODERNIZATION_PLAN.md 2>/dev/null
47
+ ```
48
+
49
+ Add all found files to the `docsToRead` array.
50
+
51
+ ## Step 2: Generate workflow.json
52
+
53
+ Create `.opencode/workflow.json` with the detected settings. The `agents` section uses `{ name, model }` objects — the plugin reads these to dynamically register agents with the correct model, permissions, and prompt.
54
+
55
+ ```json
56
+ {
57
+ "project": {
58
+ "name": "<detected>",
59
+ "repo": "<detected>",
60
+ "defaultBranch": "<detected>"
61
+ },
62
+ "stack": {
63
+ "language": "<detected>",
64
+ "framework": "<detected>",
65
+ "packageManager": "<detected>"
66
+ },
67
+ "commands": {
68
+ "build": "<detected>",
69
+ "lint": "<detected>",
70
+ "test": "<detected>",
71
+ "typecheck": "<detected>"
72
+ },
73
+ "agents": {
74
+ "coreCoder": { "name": "core-coder", "model": "github-copilot/claude-opus-4.6" },
75
+ "coreReviewers": [
76
+ { "name": "core-reviewer-1", "model": "github-copilot/claude-sonnet-4.6" },
77
+ { "name": "core-reviewer-2", "model": "github-copilot/gpt-5.4" }
78
+ ],
79
+ "reviewers": [
80
+ { "name": "reviewer-1", "model": "alibaba-coding-plan-cn/glm-5" },
81
+ { "name": "reviewer-2", "model": "alibaba-coding-plan-cn/MiniMax-M2.5" },
82
+ { "name": "reviewer-3", "model": "alibaba-coding-plan-cn/qwen3.5-plus" },
83
+ { "name": "reviewer-4", "model": "alibaba-coding-plan-cn/kimi-k2.5" },
84
+ { "name": "reviewer-5", "model": "volcengine-plan/ark-code-latest" },
85
+ { "name": "reviewer-6", "model": "volcengine-plan/deepseek-v3.2" }
86
+ ],
87
+ "securityReviewers": []
88
+ },
89
+ "testDrivenDevelopment": { "enabled": false },
90
+ "docsToRead": ["<detected>"],
91
+ "reviewFocus": []
92
+ }
93
+ ```
94
+
95
+ Create the `.opencode/` directory if it doesn't exist:
96
+
97
+ ```bash
98
+ mkdir -p .opencode
99
+ ```
100
+
101
+ Write the file. If `.opencode/workflow.json` already exists, warn the user and ask before overwriting.
102
+
103
+ **How agents work:** The plugin JS reads `workflow.json` at startup and dynamically registers each agent via OpenCode's `config.agent` API, using the guide files (`guides/*.md`) as prompts and the role-appropriate permissions. No `.opencode/agents/*.md` files are needed in consumer projects — the plugin handles everything.
104
+
105
+ ## Step 3: Update .gitignore
106
+
107
+ Append these entries to the project's `.gitignore` idempotently (only add lines that are not already present):
108
+
109
+ ```
110
+ # opencode-plugin-coding ephemeral files
111
+ .opencode/reviewer-knowledge.json
112
+ .opencode/plans/
113
+ .opencode/retrospectives/
114
+ .opencode/designs/
115
+ .worktrees/
116
+ ```
117
+
118
+ Check each line before appending — do not create duplicates.
119
+
120
+ ## Step 4: Create Required Directories
121
+
122
+ ```bash
123
+ mkdir -p .opencode/plans
124
+ mkdir -p .opencode/retrospectives
125
+ ```
126
+
127
+ ## Step 5: Print Summary
128
+
129
+ After setup, print a summary:
130
+
131
+ ```
132
+ ## /zooplankton-coding-init Complete
133
+
134
+ **Project:** <name>
135
+ **Repo:** <repo>
136
+ **Stack:** <language> / <framework> / <packageManager>
137
+
138
+ **Commands detected:**
139
+ - build: `<command>` (or "not detected")
140
+ - lint: `<command>` (or "not detected")
141
+ - test: `<command>` (or "not detected")
142
+ - typecheck: `<command>` (or "not detected")
143
+
144
+ **Agents configured (via workflow.json):**
145
+ - Core coder: `core-coder` → `github-copilot/claude-opus-4.6`
146
+ - Core reviewers: `core-reviewer-1`, `core-reviewer-2`
147
+ - Normal reviewers: `reviewer-1` ... `reviewer-6`
148
+ - Security reviewers: (none — add to workflow.json to enable)
149
+
150
+ **Files created/updated:**
151
+ - `.opencode/workflow.json` — project configuration
152
+ - `.gitignore` — updated with plugin ignore patterns
153
+
154
+ **Manual review checklist:**
155
+ - [ ] Verify detected commands are correct in `.opencode/workflow.json`
156
+ - [ ] Review model assignments in `workflow.json` → `agents` section
157
+ - [ ] Add or remove agents and adjust model IDs as needed
158
+ - [ ] Add entries to `agents.securityReviewers` if security review is needed
159
+ - [ ] Enable `testDrivenDevelopment` if using test-driven development
160
+ - [ ] Set `reviewFocus` entries if this project has specific review emphases
161
+ - [ ] Review `docsToRead` list and add any project-specific docs
162
+
163
+ > **Note:** Agents are registered dynamically by the plugin from workflow.json — no agent `.md` files needed. To change models or add/remove agents, just edit `workflow.json` and restart OpenCode.
164
+ ```
@@ -0,0 +1,93 @@
1
+ ---
2
+ description: Check workflow.json for schema updates and show what's new in the plugin.
3
+ ---
4
+
5
+ # /zooplankton-coding-update — Sync Configuration
6
+
7
+ When the user runs `/zooplankton-coding-update`, check the project's workflow.json against the current plugin schema and report any needed updates.
8
+
9
+ ## Step 1: Read Current Configuration
10
+
11
+ Read `.opencode/workflow.json` from the project. If it doesn't exist, tell the user to run `/zooplankton-coding-init` first.
12
+
13
+ ## Step 2: Schema Validation
14
+
15
+ Compare the project's `workflow.json` against the template at `templates/workflow.json`. Check for:
16
+
17
+ ### Missing fields
18
+
19
+ If the template has fields that the project file lacks, list them:
20
+
21
+ ```
22
+ New fields available:
23
+ - `<field>`: <description> (default: <value>)
24
+ ```
25
+
26
+ Offer to add them with sensible defaults.
27
+
28
+ ### Deprecated fields
29
+
30
+ If the project file has fields that are no longer in the template, warn:
31
+
32
+ ```
33
+ Deprecated fields found:
34
+ - `<field>`: <reason>
35
+ ```
36
+
37
+ ### Agent format migration
38
+
39
+ If the project's `agents` section uses the old bare-string format:
40
+
41
+ ```json
42
+ "coreCoder": "core-coder"
43
+ ```
44
+
45
+ Offer to migrate to the new `{ name, model }` format:
46
+
47
+ ```json
48
+ "coreCoder": { "name": "core-coder", "model": "" }
49
+ ```
50
+
51
+ Show the proposed migration and ask for confirmation before writing.
52
+
53
+ ## Step 3: Guide Changes Summary
54
+
55
+ Read the plugin's guide files (`guides/*.md`) and show a brief changelog if significant changes were made since the last update. This is informational only — guides are loaded dynamically by the plugin, so no action is needed from the user.
56
+
57
+ ```
58
+ Plugin guide updates (applied automatically):
59
+ - core-coder-guide.md: <summary of changes, or "no changes">
60
+ - core-reviewer-guide.md: <summary>
61
+ - reviewer-guide.md: <summary>
62
+ - security-reviewer-guide.md: <summary>
63
+ ```
64
+
65
+ ## Step 4: Apply Accepted Changes
66
+
67
+ For each change the user accepts:
68
+
69
+ 1. Update `.opencode/workflow.json` with the new fields/format
70
+ 2. Preserve all existing user values
71
+
72
+ For each change the user rejects:
73
+
74
+ 1. Skip — leave unchanged
75
+ 2. Note that the configuration may be outdated
76
+
77
+ ## Step 5: Print Summary
78
+
79
+ ```
80
+ ## /zooplankton-coding-update Complete
81
+
82
+ **Schema updates applied:** <count>
83
+ **Schema updates skipped:** <count>
84
+
85
+ **Reminder:** Agents are registered dynamically from workflow.json by the plugin.
86
+ To change models or add/remove agents, edit `.opencode/workflow.json` and restart OpenCode.
87
+ ```
88
+
89
+ ## Rules
90
+
91
+ - **Never overwrite user values** without confirmation — model assignments, agent names, project settings are user customizations
92
+ - **Preserve comments** if the file has any (though JSON doesn't support comments, some users use JSONC)
93
+ - If no updates are needed, say so clearly and exit