opencode-resolve 0.1.7 → 0.1.9

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.
@@ -17,10 +17,50 @@
17
17
  // permission values, and wrong types throw an error at load time.
18
18
 
19
19
  {
20
+ // -----------------------------------------------------------------------
21
+ // profile ("mix" | "glm" | "gpt")
22
+ // Activates provider-specific agent configurations.
23
+ // Set automatically by postinstall based on detected providers.
24
+ //
25
+ // "mix" — explicit mixed/default profile. Uses standard resolver prompts,
26
+ // DEFAULT_ENABLED, and whatever model aliases you configure.
27
+ // "glm" — GLM-only, optimized for ZAI coding-plan: reduced maxSteps,
28
+ // token-efficient prompts, no hard concurrency cap by default.
29
+ // No deep-reviewer by default.
30
+ // "gpt" — GPT-only, high-performance: parallel coder dispatch, full agent
31
+ // roster, higher maxSteps.
32
+ //
33
+ // When omitted, the plugin defaults to "mix".
34
+ // -----------------------------------------------------------------------
35
+ "profile": "mix",
36
+
37
+ // -----------------------------------------------------------------------
38
+ // tier ("bronze" | "silver" | "gold")
39
+ // Agent roster preset. Overrides the default enabled list when set.
40
+ //
41
+ // "bronze" — Minimum agents: coder + resolver only. Maximum token savings.
42
+ // "silver" — Standard: coder, resolver, explorer, reviewer, planner.
43
+ // No deep-reviewer. Best balance for most projects.
44
+ // "gold" — Full power: all 8 agents including debugger and researcher.
45
+ // Maximum capability for complex tasks.
46
+ //
47
+ // Postinstall sets this automatically:
48
+ // GLM → silver, GPT → gold, Mix/unknown → no tier (DEFAULT_ENABLED applies).
49
+ //
50
+ // Explicit `enabled` array always wins over tier.
51
+ // -----------------------------------------------------------------------
52
+ // "tier": "silver",
53
+
20
54
  // -----------------------------------------------------------------------
21
55
  // enabled (string[])
22
- // Which resolve agents to inject.
23
- // Default: ["coder", "resolver", "explorer", "reviewer", "deep-reviewer"].
56
+ // Which resolve agents to inject. When omitted, the tier or profile default
57
+ // is used. Explicit `enabled` wins over both tier and profile defaults.
58
+ //
59
+ // Mix/default (no tier): ["coder", "resolver", "explorer", "reviewer",
60
+ // "deep-reviewer", "planner"].
61
+ //
62
+ // GLM profile default: ["coder", "resolver", "explorer", "reviewer",
63
+ // "planner"] — deep-reviewer omitted to save tokens.
24
64
  //
25
65
  // Core path: resolver + coder form the fixed-role verified resolve loop.
26
66
  // Internal specialists: explorer, reviewer, deep-reviewer are injected as
@@ -30,7 +70,7 @@
30
70
  // Native OpenCode agents (plan, build) are always preserved and never
31
71
  // touched by this list. Per-agent `agents.<name>.enabled` overrides this.
32
72
  // -----------------------------------------------------------------------
33
- "enabled": ["coder", "resolver", "explorer", "reviewer", "deep-reviewer"],
73
+ // "enabled": ["coder", "resolver", "explorer", "reviewer", "deep-reviewer", "planner"],
34
74
 
35
75
  // -----------------------------------------------------------------------
36
76
  // preserveNative (boolean)
@@ -55,20 +95,23 @@
55
95
 
56
96
  // -----------------------------------------------------------------------
57
97
  // autoApprove (boolean)
58
- // When true, every default `"ask"` permission on enabled agents is flipped
59
- // to `"allow"`. Never touches `"deny"`. Never overrides a permission key
60
- // you set explicitly under `agents.<name>.permission`. Default: true.
61
- //
62
- // Set to false for the conservative ask-every-time behavior.
98
+ // Documentary flag. The plugin's permission.ask hook automatically
99
+ // classifies bash commands as safe (allow) / dangerous (deny) / unknown (ask).
100
+ // Base agent permissions are already set to the correct defaults (allow for
101
+ // edit/webfetch, ask for bash on write agents, deny for read-only agents).
102
+ // Changing this flag has no effect on behavior — it exists for config
103
+ // readability and future compatibility. Default: true.
63
104
  // -----------------------------------------------------------------------
64
105
  "autoApprove": true,
65
106
 
66
107
  // -----------------------------------------------------------------------
67
108
  // maxParallelSubagents (positive integer)
68
- // Maximum number of subagents of the SAME ROLE the resolver may dispatch
69
- // concurrently. Default: 2 at most two coders in parallel.
109
+ // Optional prompt-level cap for how many subagents of the SAME ROLE the
110
+ // resolver may dispatch concurrently. When omitted, the resolver uses soft
111
+ // fan-out guidance and backs off on rate-limit errors. GLM profile does not
112
+ // impose a hard cap unless you set one.
70
113
  // 1 = strict per-role serial
71
- // 2 = default
114
+ // 2 = up to two coders in parallel
72
115
  // N = up to N of each role
73
116
  // -----------------------------------------------------------------------
74
117
  "maxParallelSubagents": 2,
@@ -76,9 +119,12 @@
76
119
  // -----------------------------------------------------------------------
77
120
  // models (object)
78
121
  // Alias map. Keys are either an agent name (coder, reviewer, resolver,
79
- // architect, gpt-coder, debugger, researcher, explorer, deep-reviewer)
122
+ // codex, glm, architect, gpt-coder, debugger, researcher, explorer,
123
+ // deep-reviewer, planner)
80
124
  // OR one of the well-known aliases (`glm`, `gpt`, `quick`, `deep`,
81
- // `fast`, `strong`, `mini`, `codex`).
125
+ // `fast`, `strong`, `mini`, `codex`, `bronze`, `silver`, `gold`,
126
+ // `gpt-bronze`, `gpt-silver`, `gpt-gold`, `glm-bronze`, `glm-silver`,
127
+ // `glm-gold`).
82
128
  // Values are model identifiers OR another alias.
83
129
  //
84
130
  // Resolution order for a given agent:
@@ -94,11 +140,17 @@
94
140
  // When resolve.json is created for the first time by postinstall, the
95
141
  // plugin inspects your OpenCode model config and writes an adaptive
96
142
  // preset:
97
- // - GLM/ZAI detectedmixed GLM + GPT aliases
98
- // - OpenAI/GPT detected single-provider GPT aliases using your model
99
- // - Other or no model empty (model-neutral inheritance)
100
- // Existing resolve.json files are never overwritten. Delete resolve.json
101
- // and reinstall to regenerate the preset.
143
+ // - interactive terminalalways prompts for mix/gpt/glm, three-tier models,
144
+ // and optional dedicated codex/glm primary agents
145
+ // - non-interactive install no guessed model pinning; profile mix,
146
+ // models {}, and resolver/codex/glm primary routes enabled
147
+ // - OPENCODE_RESOLVE_AUTO_PRESET=1 legacy non-interactive
148
+ // provider-adapted presets
149
+ // Existing resolve.json files are never overwritten without consent.
150
+ // Interactive reinstall asks whether to update or back up and run fresh
151
+ // setup. Non-interactive automation can set:
152
+ // OPENCODE_RESOLVE_REINSTALL=fresh
153
+ // OPENCODE_RESOLVE_REINSTALL=update
102
154
  //
103
155
  // Example — one efficient model for everything (recommended default):
104
156
  // "models": {}
@@ -111,17 +163,22 @@
111
163
  // "resolver": "strong"
112
164
  // }
113
165
  //
114
- // Example — GLM + GPT mixed preset (auto-generated for GLM/ZAI users):
166
+ // Example — GLM-only three-tier preset (auto-generated for GLM/ZAI users):
167
+ // GLM fills all tiers — no GPT dependency, so token exhaustion on
168
+ // third-party providers never blocks agent execution.
115
169
  // "models": {
116
170
  // "glm": "zai-coding-plan/glm-5.1",
117
- // "gpt": "openai/gpt-5.5",
118
- // "fast": "glm",
119
- // "strong": "gpt",
120
- // "coder": "glm",
121
- // "resolver": "gpt",
122
- // "reviewer": "gpt",
123
- // "deep-reviewer": "gpt",
124
- // "explorer": "fast"
171
+ // "bronze": "zai-coding-plan/glm-5.1",
172
+ // "silver": "zai-coding-plan/glm-5.1",
173
+ // "gold": "zai-coding-plan/glm-5.1",
174
+ // "fast": "bronze",
175
+ // "strong": "gold",
176
+ // "coder": "silver",
177
+ // "resolver": "gold",
178
+ // "reviewer": "gold",
179
+ // "deep-reviewer": "gold",
180
+ // "explorer": "bronze",
181
+ // "planner": "gold"
125
182
  // }
126
183
  //
127
184
  // Legacy aliases (glm, gpt, quick, deep) remain supported for backward
@@ -164,6 +221,15 @@
164
221
  }
165
222
  },
166
223
 
224
+ // codex — Codex-optimized primary resolver (selectable in agent picker)
225
+ // Same verified resolve-loop style as resolver, with a Codex-specific
226
+ // system prompt. Enabled by GPT/mix first-install presets when selected.
227
+ "codex": {
228
+ "enabled": false,
229
+ "mode": "all",
230
+ "maxSteps": 35
231
+ },
232
+
167
233
  // Internal specialist subagents — enabled by default as subagents only.
168
234
  // Core path is resolver→coder. These are dispatched only when justified.
169
235
 
@@ -229,6 +295,20 @@
229
295
  "enabled": false,
230
296
  "mode": "subagent",
231
297
  "maxSteps": 8
298
+ },
299
+
300
+ // -----------------------------------------------------------------
301
+ // glm — GLM-optimized orchestrator (selectable in agent picker)
302
+ // Disabled by default. Enable to get a standalone GLM agent with
303
+ // coding-plan optimized prompts: session limit handling,
304
+ // token-efficient execution, no hard concurrency cap unless configured.
305
+ // Users pick this from OpenCode's agent picker as an alternative
306
+ // to the default resolver.
307
+ // -----------------------------------------------------------------
308
+ "glm": {
309
+ "enabled": false,
310
+ "mode": "all",
311
+ "maxSteps": 30
232
312
  }
233
313
  }
234
314
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "opencode-resolve",
3
- "version": "0.1.7",
4
- "description": "OpenCode plugin that adds a small coder/reviewer agent set while preserving native plan/build behavior.",
3
+ "version": "0.1.9",
4
+ "description": "OpenCode plugin that adds a lightweight resolver/coder harness for continuous agentic coding.",
5
5
  "type": "module",
6
6
  "homepage": "https://github.com/jshsakura/opencode-resolve#readme",
7
7
  "bugs": {
@@ -25,13 +25,18 @@
25
25
  "opencode-resolve.example.json",
26
26
  "opencode-resolve.reference.jsonc",
27
27
  "README.md",
28
+ "README.ko.md",
28
29
  "LICENSE"
29
30
  ],
30
31
  "scripts": {
31
32
  "build": "tsc -p tsconfig.json",
33
+ "coverage": "npm run build && node --experimental-test-coverage --test-coverage-lines=79 --test-coverage-branches=69 --test-coverage-functions=85 --test test/*.mjs",
34
+ "hooks:install": "node scripts/install-git-hooks.mjs",
32
35
  "install:local": "npm run build && node scripts/install-local.mjs",
33
36
  "postinstall": "node scripts/postinstall.mjs",
34
37
  "prepack": "npm test",
38
+ "precommit": "npm run typecheck && npm test && npm run coverage",
39
+ "prepush": "npm run precommit && npm pack --dry-run",
35
40
  "test": "npm run build && node --test test/*.mjs",
36
41
  "typecheck": "tsc -p tsconfig.json --noEmit"
37
42
  },
@@ -39,6 +44,9 @@
39
44
  "opencode",
40
45
  "opencode-plugin",
41
46
  "agents",
47
+ "agentic-coding",
48
+ "autonomous-coding",
49
+ "harness",
42
50
  "taskflow",
43
51
  "resolve",
44
52
  "task-resolution",
@@ -0,0 +1,28 @@
1
+ #!/usr/bin/env node
2
+ import { spawnSync } from "node:child_process"
3
+ import { chmodSync, existsSync } from "node:fs"
4
+ import { dirname, join } from "node:path"
5
+ import { fileURLToPath } from "node:url"
6
+
7
+ const root = dirname(dirname(fileURLToPath(import.meta.url)))
8
+ const hooksDir = join(root, ".githooks")
9
+
10
+ if (!existsSync(join(root, ".git"))) {
11
+ console.error("[opencode-resolve] not a git checkout; cannot install hooks")
12
+ process.exit(1)
13
+ }
14
+
15
+ for (const hook of ["pre-commit", "pre-push"]) {
16
+ chmodSync(join(hooksDir, hook), 0o755)
17
+ }
18
+
19
+ const result = spawnSync("git", ["config", "core.hooksPath", ".githooks"], {
20
+ cwd: root,
21
+ stdio: "inherit",
22
+ })
23
+
24
+ if (result.status !== 0) {
25
+ process.exit(result.status ?? 1)
26
+ }
27
+
28
+ console.log("[opencode-resolve] git hooks installed: core.hooksPath=.githooks")
@@ -2,6 +2,7 @@ import { constants } from "node:fs"
2
2
  import { access, mkdir, readFile, symlink, unlink, writeFile } from "node:fs/promises"
3
3
  import { homedir } from "node:os"
4
4
  import { dirname, join, resolve } from "node:path"
5
+ import { createInterface } from "node:readline/promises"
5
6
  import { fileURLToPath } from "node:url"
6
7
 
7
8
  const root = resolve(dirname(fileURLToPath(import.meta.url)), "..")
@@ -25,11 +26,58 @@ await symlink(pluginTarget, pluginLink)
25
26
 
26
27
  if (!(await exists(resolveConfigPath))) {
27
28
  await createAdaptiveResolveConfig()
29
+ } else {
30
+ await handleExistingResolveConfig()
28
31
  }
29
32
 
30
33
  console.log(`Linked plugin: ${pluginLink} -> ${pluginTarget}`)
31
34
  console.log(`Resolve config: ${resolveConfigPath}`)
32
35
 
36
+ async function handleExistingResolveConfig() {
37
+ const action = await chooseExistingResolveConfigAction()
38
+ if (action !== "fresh") {
39
+ console.log(`Existing resolve config preserved: ${resolveConfigPath}`)
40
+ return
41
+ }
42
+
43
+ await backupResolveConfig()
44
+ await createAdaptiveResolveConfig()
45
+ }
46
+
47
+ async function chooseExistingResolveConfigAction() {
48
+ const requested = (process.env.OPENCODE_RESOLVE_REINSTALL ?? "").trim().toLowerCase()
49
+ if (["fresh", "reset", "recreate", "new"].includes(requested)) return "fresh"
50
+ if (["update", "keep", "migrate", "preserve"].includes(requested)) return "update"
51
+ if (requested) {
52
+ console.warn(`Ignoring unknown OPENCODE_RESOLVE_REINSTALL=${JSON.stringify(requested)}; use "fresh" or "update".`)
53
+ }
54
+
55
+ if (!process.stdin.isTTY || !process.stdout.isTTY) {
56
+ console.log(`Existing resolve config found; preserving it. Set OPENCODE_RESOLVE_REINSTALL=fresh for a fresh reinstall.`)
57
+ return "update"
58
+ }
59
+
60
+ const rl = createInterface({ input: process.stdin, output: process.stdout })
61
+ try {
62
+ console.log("")
63
+ console.log(`Existing resolve config found: ${resolveConfigPath}`)
64
+ console.log(" 1. update existing config — preserve your settings")
65
+ console.log(" 2. fresh reinstall — back up resolve.json and create a new config")
66
+ const raw = await rl.question("Existing config [1=update, 2=fresh reinstall, default 1]: ")
67
+ return raw.trim() === "2" ? "fresh" : "update"
68
+ } finally {
69
+ rl.close()
70
+ }
71
+ }
72
+
73
+ async function backupResolveConfig() {
74
+ const raw = await readFile(resolveConfigPath, "utf8")
75
+ const stamp = new Date().toISOString().replace(/[:.]/g, "-")
76
+ const backupPath = `${resolveConfigPath}.bak.${stamp}`
77
+ await writeFile(backupPath, raw)
78
+ console.log(`Backed up existing resolve config to ${backupPath}`)
79
+ }
80
+
33
81
  async function createAdaptiveResolveConfig() {
34
82
  const raw = await readFile(exampleConfig, "utf8")
35
83
  const example = JSON.parse(raw)
@@ -42,10 +90,20 @@ async function createAdaptiveResolveConfig() {
42
90
  // opencode.json not found or unreadable — use empty config
43
91
  }
44
92
 
45
- const currentModel = detectOpenCodeModel(opencodeConfig)
46
- const preset = buildModelPreset(currentModel)
47
-
48
93
  const resolveConfig = { ...example }
94
+ let preset = {}
95
+ if (process.env.OPENCODE_RESOLVE_AUTO_PRESET === "1") {
96
+ const currentModel = detectOpenCodeModel(opencodeConfig)
97
+ preset = buildModelPreset(currentModel)
98
+ } else {
99
+ resolveConfig.profile = "mix"
100
+ resolveConfig.models = {}
101
+ resolveConfig.agents = {
102
+ ...resolveConfig.agents,
103
+ codex: { ...(resolveConfig.agents?.codex ?? {}), enabled: true },
104
+ glm: { ...(resolveConfig.agents?.glm ?? {}), enabled: true },
105
+ }
106
+ }
49
107
  if (preset && Object.keys(preset).length > 0) {
50
108
  resolveConfig.models = preset
51
109
  }
@@ -53,7 +111,9 @@ async function createAdaptiveResolveConfig() {
53
111
  await mkdir(configDir, { recursive: true })
54
112
  await writeFile(resolveConfigPath, `${JSON.stringify(resolveConfig, null, 2)}\n`)
55
113
 
56
- const label = getPresetLabel(currentModel)
114
+ const label = process.env.OPENCODE_RESOLVE_AUTO_PRESET === "1"
115
+ ? getPresetLabel(detectOpenCodeModel(opencodeConfig))
116
+ : "prompt-required"
57
117
  console.log(`Created ${resolveConfigPath} (preset: ${label})`)
58
118
  }
59
119