@fkqfkq123/opencode-autopilot 0.1.3 → 0.1.6

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/GUIDE.zh-CN.md ADDED
@@ -0,0 +1,120 @@
1
+ # Autopilot
2
+
3
+ [English](./README.md) | 中文说明
4
+
5
+ Autopilot 是一个 OpenCode 插件,可以把自然语言需求推进成一套完整流程:需求精炼、计划、实现、评审和测试。
6
+
7
+ ## 安装
8
+
9
+ 在 OpenCode 配置里加入插件:
10
+
11
+ ```json
12
+ {
13
+ "plugin": ["@fkqfkq123/opencode-autopilot"]
14
+ }
15
+ ```
16
+
17
+ 然后重启 `opencode`。
18
+
19
+ 如果加载成功,你应该看到类似日志:
20
+
21
+ ```txt
22
+ [autopilot] Autopilot plugin loaded (... commands)
23
+ ```
24
+
25
+ ## 使用
26
+
27
+ 直接输入自然语言需求,例如:
28
+
29
+ ```txt
30
+ 给商品列表页增加排序能力,并注意回归风险。
31
+ ```
32
+
33
+ Autopilot 会通过这些工具推进流程:
34
+
35
+ - `workflow_open`
36
+ - `workflow_attach`
37
+ - `workflow_status`
38
+ - `workflow_answer`
39
+ - `workflow_approve`
40
+ - `workflow_resume`
41
+
42
+ 通常你只需要按照 workflow 输出里提示的下一步工具继续即可。
43
+
44
+ ## 配置
45
+
46
+ Autopilot 会在需要时自动创建:
47
+
48
+ ```txt
49
+ .workflow-harness/autopilot.json
50
+ ~/.config/opencode/autopilot.json
51
+ ```
52
+
53
+ 如果配置保持空值,就使用默认行为。
54
+
55
+ ### `autopilot.json`
56
+
57
+ 最小示例:
58
+
59
+ ```json
60
+ {
61
+ "skillRoots": ["~/.claude/skills", "~/.config/opencode/skills"],
62
+ "phases": {
63
+ "spec_refinement": { "requiredSkills": [] },
64
+ "plan": { "requiredSkills": [] },
65
+ "develop": { "requiredSkills": [] },
66
+ "review": { "requiredSkills": [] },
67
+ "test": { "requiredSkills": [] }
68
+ }
69
+ }
70
+ ```
71
+
72
+ 字段说明:
73
+
74
+ - `skillRoots`:要扫描的 skill 目录
75
+ - `phases.<phase>.requiredSkills`:该阶段需要注入的 skill
76
+
77
+ 支持的 phase:
78
+
79
+ - `spec_refinement`
80
+ - `plan`
81
+ - `develop`
82
+ - `review`
83
+ - `test`
84
+
85
+ 如果旧的 `workflow.json` 存在而新的 `autopilot.json` 不存在,Autopilot 会复用旧文件,并提示你后续迁移。
86
+
87
+ ## 备用安装方式
88
+
89
+ 如果你更想走本地文件安装:
90
+
91
+ ```bash
92
+ curl -fsSL https://raw.githubusercontent.com/juhuaxia/Autopilot/main/install.sh | bash
93
+ ```
94
+
95
+ ## 常见问题
96
+
97
+ ### OpenCode 启动失败
98
+
99
+ 先临时从 `opencode.json` 中移除这个插件,确认 OpenCode 能正常启动,再检查配置后重新启用。
100
+
101
+ ### 插件加载了,但看不到 workflow 命令
102
+
103
+ 通常是宿主没有正确注册导出的 workflow tools。
104
+
105
+ ### 某个 workflow 状态坏掉了
106
+
107
+ 删除当前项目的运行时目录后重新开始:
108
+
109
+ ```bash
110
+ rm -rf .workflow-harness
111
+ ```
112
+
113
+ ## 如果你要开发这个插件
114
+
115
+ ```bash
116
+ bun install
117
+ bun run typecheck
118
+ bun test
119
+ bun run build
120
+ ```
package/README.md CHANGED
@@ -2,223 +2,11 @@
2
2
 
3
3
  English | [中文说明](./GUIDE.zh-CN.md)
4
4
 
5
- Autopilot is an OpenCode-oriented **attached-session workflow harness**. It provides a workflow runtime skeleton covering refinement, planning, development, review, and testing, plus a locally loadable plugin, CLI entrypoints, and diagnostics.
5
+ Autopilot is an OpenCode plugin that turns a natural-language request into a structured workflow: refinement, plan, implementation, review, and testing.
6
6
 
7
- ## 1. What this project provides
7
+ ## Install
8
8
 
9
- - A full workflow phase chain: `spec_refinement -> plan -> develop -> review -> test -> done`
10
- - OpenCode-facing workflow commands such as `workflow_open`, `workflow_attach`, `workflow_status`, `workflow_answer`, `workflow_approve`, `workflow_resume`, and `workflow_back`
11
- - Plugin loading and native primary workflow agent registration for OpenCode-style hosts
12
- - `install` and `doctor` flows for bootstrapping configuration and validating setup
13
- - Review/test loop-back semantics, human breakpoints, event storage, attach/re-attach support
14
-
15
- ## 2. Who this is for
16
-
17
- Autopilot is a good fit if you want to:
18
-
19
- - add a workflow primary agent to an OpenCode-style host,
20
- - structure engineering work into explicit workflow phases,
21
- - validate a workflow runtime and command surface through a plugin.
22
-
23
- ## 3. Prerequisites
24
-
25
- Recommended environment:
26
-
27
- - macOS / Linux / Windows
28
- - [Bun](https://bun.sh/) `1.3.5` or a compatible version
29
- - OpenCode installed if you want to actually load and verify the plugin
30
-
31
- Check Bun:
32
-
33
- ```bash
34
- bun --version
35
- ```
36
-
37
- If Bun is not installed yet:
38
-
39
- ```bash
40
- curl -fsSL https://bun.sh/install | bash
41
- ```
42
-
43
- Then restart your terminal and verify again:
44
-
45
- ```bash
46
- bun --version
47
- ```
48
-
49
- ## 4. Installation
50
-
51
- ### 4.1 Recommended: install as an npm plugin package
52
-
53
- OpenCode supports npm-based plugins directly. Once this package is published, the simplest configuration is:
54
-
55
- ```json
56
- {
57
- "plugin": ["@fkqfkq123/opencode-autopilot"]
58
- }
59
- ```
60
-
61
- You can also pin a version:
62
-
63
- ```json
64
- {
65
- "plugin": ["@fkqfkq123/opencode-autopilot@0.1.2"]
66
- }
67
- ```
68
-
69
- In this mode, OpenCode installs and caches the npm package automatically. No manual `git clone`, local `plugin.js`, or extra install script is required.
70
-
71
- ### 4.2 Fallback: install from GitHub Releases
72
-
73
- If you prefer a local-file installation path or need a fallback distribution mode, use the one-line installer from GitHub Releases:
74
-
75
- ```bash
76
- curl -fsSL https://raw.githubusercontent.com/juhuaxia/Autopilot/main/install.sh | bash
77
- ```
78
-
79
- Install a specific version:
80
-
81
- ```bash
82
- curl -fsSL https://raw.githubusercontent.com/juhuaxia/Autopilot/main/install.sh | bash -s -- --version v0.1.2
83
- ```
84
-
85
- The fallback installer will:
86
-
87
- - download the prebuilt release package from GitHub Releases,
88
- - install it into `~/.config/opencode/plugins/autopilot/`,
89
- - update `~/.config/opencode/opencode.json`.
90
-
91
- Release requirement:
92
-
93
- - Each GitHub Release must include `autopilot-release.tar.gz`
94
- - The repository includes `.github/workflows/release.yml`, which builds and uploads that file automatically on `v*` tags
95
-
96
- If you want to modify the codebase or work from source, continue with the source setup below.
97
-
98
- ### 4.3 Clone the repository
99
-
100
- ```bash
101
- git clone https://github.com/juhuaxia/Autopilot.git
102
- cd Autopilot
103
- ```
104
-
105
- ### 4.4 Install dependencies
106
-
107
- ```bash
108
- bun install
109
- ```
110
-
111
- The project uses `bun.lock`, so dependencies are expected to stay reproducible.
112
-
113
- ## 5. Recommended first-run commands
114
-
115
- Run these from the project root:
116
-
117
- ```bash
118
- bun run src/cli.ts install
119
- bun run src/cli.ts doctor
120
- bun run build
121
- ```
122
-
123
- What they do:
124
-
125
- 1. `install`
126
- - creates project-level `.workflow-harness/workflow.json`
127
- - tries to safely update `~/.config/opencode/opencode.json`
128
- - normalizes `opencode.jsonc` into `opencode.json` when safe
129
- 2. `doctor`
130
- - checks `workflow.json`
131
- - checks `skillRoots`
132
- - checks phase-level `requiredSkills`
133
- - reports warnings and missing pieces
134
- 3. `build`
135
- - compiles TypeScript
136
- - produces `dist/plugin.js`
137
-
138
- After that, confirm:
139
-
140
- - `.workflow-harness/workflow.json` exists
141
- - `doctor` shows no blocking configuration issue
142
- - `dist/plugin.js` exists
143
-
144
- ## 6. Common development commands
145
-
146
- ### 6.1 Build
147
-
148
- ```bash
149
- bun run build
150
- ```
151
-
152
- ### 6.2 Typecheck
153
-
154
- ```bash
155
- bun run typecheck
156
- ```
157
-
158
- ### 6.3 Run tests
159
-
160
- ```bash
161
- bun test
162
- ```
163
-
164
- ### 6.4 Run plugin smoke tests
165
-
166
- ```bash
167
- bun run smoke:plugin
168
- ```
169
-
170
- ### 6.5 Run CLI commands directly
171
-
172
- ```bash
173
- bun run src/cli.ts doctor
174
- bun run src/cli.ts install
175
- ```
176
-
177
- Or through the script alias:
178
-
179
- ```bash
180
- bun run cli --help
181
- ```
182
-
183
- > The CLI mainly provides workflow initialization, attach/status flows, and install/doctor actions.
184
-
185
- ## 7. CLI quick usage
186
-
187
- ### 7.1 Initialize config
188
-
189
- ```bash
190
- bun run src/cli.ts install
191
- ```
192
-
193
- ### 7.2 Run a self-check
194
-
195
- ```bash
196
- bun run src/cli.ts doctor
197
- ```
198
-
199
- ### 7.3 Create a workflow
200
-
201
- ```bash
202
- bun run src/cli.ts workflow-open wf-1
203
- ```
204
-
205
- ### 7.4 Check workflow status
206
-
207
- ```bash
208
- bun run src/cli.ts workflow-status wf-1
209
- ```
210
-
211
- ### 7.5 Re-attach to the workflow channel
212
-
213
- ```bash
214
- bun run src/cli.ts workflow-attach wf-1
215
- ```
216
-
217
- ## 8. Loading the plugin into OpenCode
218
-
219
- ### 8.0 Recommended npm plugin path
220
-
221
- Recommended OpenCode config:
9
+ Add the plugin to your OpenCode config:
222
10
 
223
11
  ```json
224
12
  {
@@ -226,237 +14,109 @@ Recommended OpenCode config:
226
14
  }
227
15
  ```
228
16
 
229
- ### 8.1 Fallback release-based path
230
-
231
- If you prefer a local installed fallback plugin, use:
232
-
233
- ```bash
234
- curl -fsSL https://raw.githubusercontent.com/juhuaxia/Autopilot/main/install.sh | bash
235
- ```
236
-
237
- Default install location:
238
-
239
- ```txt
240
- ~/.config/opencode/plugins/autopilot/
241
- ```
17
+ Restart `opencode`.
242
18
 
243
- The installer will register a plugin entry similar to:
19
+ If the plugin loads correctly, you should see a log like:
244
20
 
245
21
  ```txt
246
- file:///Users/<your-user>/.config/opencode/plugins/autopilot/plugin.js
247
- ```
248
-
249
- ### 8.2 Source-development path
250
-
251
- Run:
252
-
253
- ```bash
254
- bun run src/cli.ts install
255
- bun run src/cli.ts doctor
256
- ```
257
-
258
- If the installer can safely update the OpenCode config, you usually do not need to edit anything manually.
259
-
260
- > `install.sh` targets GitHub Releases installs. The project-local installer targets source-development setups.
261
-
262
- ### 8.3 Manual plugin registration
263
-
264
- OpenCode config is usually located at:
265
-
266
- - `~/.config/opencode/opencode.json`
267
- - or `~/.config/opencode/opencode.jsonc`
268
-
269
- #### Option A: load the built plugin
270
-
271
- Build first:
272
-
273
- ```bash
274
- bun run build
275
- ```
276
-
277
- Then add this to OpenCode config:
278
-
279
- ```json
280
- {
281
- "plugin": [
282
- "file:///ABSOLUTE_PATH_TO_PROJECT/dist/plugin.js"
283
- ]
284
- }
285
- ```
286
-
287
- #### Option B: load source directly during development
288
-
289
- ```json
290
- {
291
- "plugin": [
292
- "file:///ABSOLUTE_PATH_TO_PROJECT/plugin.ts"
293
- ]
294
- }
22
+ [autopilot] Autopilot plugin loaded (... commands)
295
23
  ```
296
24
 
297
- ### 8.4 Start OpenCode
25
+ ## Use
298
26
 
299
- Interactive mode:
300
-
301
- ```bash
302
- opencode
303
- ```
27
+ Start with a natural-language request, for example:
304
28
 
305
- Server mode:
306
-
307
- ```bash
308
- opencode serve
29
+ ```txt
30
+ Add sorting to the product list page and make sure regression risks are reviewed.
309
31
  ```
310
32
 
311
- ## 9. What you should see after loading
33
+ Autopilot will guide the workflow through the available tools:
312
34
 
313
- The plugin exposes these tools/commands:
314
-
315
- - `workflow_channel`
316
35
  - `workflow_open`
317
36
  - `workflow_attach`
318
37
  - `workflow_status`
319
38
  - `workflow_answer`
320
39
  - `workflow_approve`
321
40
  - `workflow_resume`
322
- - `workflow_back`
323
- - `workflow_doctor`
324
41
 
325
- Recommended split-tool entrypoints:
42
+ You usually only need to follow the next recommended tool shown by the workflow output.
326
43
 
327
- - `workflow_open`
328
- - `workflow_attach`
329
- - `workflow_status`
330
- - `workflow_answer`
331
- - `workflow_approve`
332
- - `workflow_resume`
333
- - `workflow_back`
44
+ ## Configuration
334
45
 
335
- Typical load log:
46
+ Autopilot automatically creates these files when needed:
336
47
 
337
48
  ```txt
338
- [autopilot] Autopilot plugin loaded (... commands)
49
+ .workflow-harness/autopilot.json
50
+ ~/.config/opencode/autopilot.json
339
51
  ```
340
52
 
341
- ## 10. Directory layout
342
-
343
- ### 10.1 Configuration layers
344
-
345
- - user default: `~/.config/opencode/workflow.json`
346
- - project override: `<repo>/.workflow-harness/workflow.json`
347
- - runtime state: `<repo>/.workflow-harness/workflows/<workflowId>/`
53
+ If they are left empty, Autopilot uses default behavior.
348
54
 
349
- ### 10.2 What each directory does
55
+ ### `autopilot.json`
350
56
 
351
- - `src/` — CLI entrypoints and top-level source files
352
- - `packages/runtime/` — workflow runtime implementation
353
- - `tests/` — tests
354
- - `scripts/` — auxiliary scripts
355
- - `.workflow-harness/` — runtime config, state, and artifacts
356
- - `dist/` — build output
357
-
358
- ## 11. Minimal `workflow.json` example
359
-
360
- Start with a fully neutral config:
57
+ Minimal example:
361
58
 
362
59
  ```json
363
60
  {
364
61
  "skillRoots": ["~/.claude/skills", "~/.config/opencode/skills"],
365
62
  "phases": {
63
+ "spec_refinement": { "requiredSkills": [] },
64
+ "plan": { "requiredSkills": [] },
366
65
  "develop": { "requiredSkills": [] },
66
+ "review": { "requiredSkills": [] },
367
67
  "test": { "requiredSkills": [] }
368
68
  }
369
69
  }
370
70
  ```
371
71
 
372
- Then add skills per project. For example, for a frontend-oriented project:
373
-
374
- ```json
375
- {
376
- "skillRoots": ["~/.claude/skills", "~/.config/opencode/skills"],
377
- "phases": {
378
- "develop": { "requiredSkills": ["frontend-design"] },
379
- "test": { "requiredSkills": ["playwright"] }
380
- }
381
- }
382
- ```
383
-
384
- That frontend example is only an example. The workflow runtime is not frontend-bound by default.
72
+ Field meanings:
385
73
 
386
- Recommendations:
74
+ - `skillRoots`: directories to scan for skill files
75
+ - `phases.<phase>.requiredSkills`: skills to inject into that phase
387
76
 
388
- - keep skill/profile config in global or project-level `workflow.json`
389
- - do not put skill config under `workflows/<workflowId>/`
390
- - run `workflow_doctor` or CLI `doctor` before using a new config
77
+ Supported phases:
391
78
 
392
- ## 12. FAQ
79
+ - `spec_refinement`
80
+ - `plan`
81
+ - `develop`
82
+ - `review`
83
+ - `test`
393
84
 
394
- ### Q1: What if `install` cannot update my OpenCode config?
85
+ If an old `workflow.json` exists and `autopilot.json` does not, Autopilot can reuse the legacy file and warn you to migrate.
395
86
 
396
- Edit OpenCode config manually and add either the release plugin path or the local build path to the `plugin` array. Example:
87
+ ## Fallback Install
397
88
 
398
- ```json
399
- {
400
- "plugin": [
401
- "file:///Users/<your-user>/.config/opencode/plugins/autopilot/plugin.js"
402
- ]
403
- }
404
- ```
405
-
406
- For source development, you can also point to your local `dist/plugin.js`.
89
+ If you prefer a local file install instead of the npm package:
407
90
 
408
- If you are using the npm plugin mode, you can simply configure:
409
-
410
- ```json
411
- {
412
- "plugin": ["@fkqfkq123/opencode-autopilot"]
413
- }
91
+ ```bash
92
+ curl -fsSL https://raw.githubusercontent.com/juhuaxia/Autopilot/main/install.sh | bash
414
93
  ```
415
94
 
416
- ### Q2: Why can’t I see workflow commands after building?
417
-
418
- That usually means the host integration layer has not actually registered the exported `workflowCommands` onto the host command/tool surface.
419
-
420
- ### Q3: What should I validate first?
421
-
422
- Validate this order first:
95
+ ## Troubleshooting
423
96
 
424
- 1. the host can import the plugin file
425
- 2. the host can call the default export
426
- 3. the host receives a plugin-like object
97
+ ### OpenCode fails to start
427
98
 
428
- Do not start by validating UI behavior first.
99
+ Temporarily remove the plugin from `opencode.json`, restart OpenCode, then re-enable it after checking the config.
429
100
 
430
- ### Q4: Any important rule for the plugin root entry?
101
+ ### The plugin loads but workflow commands do not appear
431
102
 
432
- The root `plugin.ts` entry should only expose a single host-callable default export. Do not expose extra internal classes or helper functions from the root entry.
103
+ This usually means the host did not register the exported workflow tools correctly.
433
104
 
434
- ## 13. Recommended reading
105
+ ### A workflow seems broken
435
106
 
436
- | Document | Purpose |
437
- |---|---|
438
- | `README.md` | Main entry for installation, release, and usage |
439
- | `WORKFLOW_SKILL_PROFILE_ARCHITECTURE_CN.md` | Skill/profile configuration design |
440
- | `OPENCODE_WORKFLOW_AGENT_GUIDE.md` | Agent/tool calling loop |
441
- | `REQUIREMENT_TEMPLATE.md` | Requirement input template |
107
+ Delete the project runtime folder and start again:
442
108
 
443
- Internal planning notes, acceptance drafts, and status scratch docs can be kept locally under `docs_internal/`, which is ignored by default.
109
+ ```bash
110
+ rm -rf .workflow-harness
111
+ ```
444
112
 
445
- ## 14. Fastest path to a working setup
113
+ ## For source development
446
114
 
447
- If you just want the fastest local source setup:
115
+ If you want to work on the plugin itself:
448
116
 
449
117
  ```bash
450
118
  bun install
451
- bun run src/cli.ts install
452
- bun run src/cli.ts doctor
119
+ bun run typecheck
120
+ bun test
453
121
  bun run build
454
- opencode
455
122
  ```
456
-
457
- Then:
458
-
459
- 1. confirm `.workflow-harness/workflow.json` exists
460
- 2. confirm `dist/plugin.js` exists
461
- 3. if OpenCode does not auto-load the plugin, add `file:///ABSOLUTE_PATH_TO_PROJECT/dist/plugin.js` manually to config
462
- 4. verify `workflow_open`, `workflow_attach`, and `workflow_status` are visible in the host
@@ -14,6 +14,7 @@ export interface CreateHarnessOptions {
14
14
  sessionClient?: OpencodeSessionClient;
15
15
  opencodeBaseUrl?: string;
16
16
  opencodePassword?: string;
17
+ homeDir?: string;
17
18
  }
18
19
  export declare function createHarness(baseDir: string, options?: CreateHarnessOptions): Promise<{
19
20
  stateStore: FileSystemWorkflowStateStore;
@@ -1,11 +1,12 @@
1
1
  import { mkdir } from "node:fs/promises";
2
+ import { homedir } from "node:os";
2
3
  import { join } from "node:path";
3
4
  import { HttpOpencodeSessionClient, InMemoryOpencodeSessionClient, } from "../../../adapters/opencode/src/opencode-session-client";
4
5
  import { DefaultPhaseTransition } from "../../../core/src/transitions/default-phase-transition";
5
6
  import { FileSystemArtifactEvaluator } from "../artifacts/file-system-artifact-evaluator";
6
7
  import { DefaultAttachService } from "../attach/attach-service";
7
8
  import { buildSkillRegistryWithWarnings } from "../config/skill-registry";
8
- import { resolveWorkflowConfig } from "../config/workflow-config";
9
+ import { ensureAutopilotConfigFile, resolveWorkflowConfig, AUTOPILOT_CONFIG_FILENAME } from "../config/workflow-config";
9
10
  import { DefaultWorkflowEngine } from "../engine/default-workflow-engine";
10
11
  import { FileSystemWorkflowEventStore } from "../events/file-system-workflow-event-store";
11
12
  import { BasicRecoveryClassifier } from "../recovery/basic-recovery-classifier";
@@ -20,8 +21,11 @@ import { DefaultWorkflowWorkspace } from "../workspace/workflow-workspace";
20
21
  export async function createHarness(baseDir, options = {}) {
21
22
  await mkdir(join(baseDir, "workflows"), { recursive: true });
22
23
  const workspace = new DefaultWorkflowWorkspace(baseDir);
24
+ await ensureAutopilotConfigFile(workspace.workflowConfigFile());
25
+ await ensureAutopilotConfigFile(join(options.homeDir ?? homedir(), ".config", "opencode", AUTOPILOT_CONFIG_FILENAME));
23
26
  const resolvedConfig = await resolveWorkflowConfig({
24
27
  projectConfigFile: workspace.workflowConfigFile(),
28
+ ...(options.homeDir ? { homeDir: options.homeDir } : {}),
25
29
  });
26
30
  const skillRegistryResult = await buildSkillRegistryWithWarnings(resolvedConfig.skillRoots);
27
31
  const skillRegistry = skillRegistryResult.registry;
@@ -1,3 +1,6 @@
1
+ export declare const AUTOPILOT_CONFIG_FILENAME = "autopilot.json";
2
+ export declare const LEGACY_WORKFLOW_CONFIG_FILENAME = "workflow.json";
3
+ export declare const DEFAULT_SKILL_ROOTS: string[];
1
4
  export type WorkflowConfigPhase = "spec_refinement" | "plan" | "develop" | "review" | "test";
2
5
  export type WorkflowPhaseConfig = {
3
6
  requiredSkills?: string[];
@@ -11,7 +14,9 @@ export type ResolvedWorkflowConfig = {
11
14
  phases: Partial<Record<WorkflowConfigPhase, WorkflowPhaseConfig>>;
12
15
  warnings: string[];
13
16
  };
17
+ export declare const DEFAULT_AUTOPILOT_CONFIG: WorkflowConfigFile;
14
18
  export declare function resolveWorkflowConfig(args: {
15
19
  projectConfigFile: string;
16
20
  homeDir?: string;
17
21
  }): Promise<ResolvedWorkflowConfig>;
22
+ export declare function ensureAutopilotConfigFile(filePath: string): Promise<void>;
@@ -1,6 +1,20 @@
1
+ import { readFile } from "node:fs/promises";
1
2
  import { homedir } from "node:os";
2
- import { join } from "node:path";
3
- import { readJsonFile } from "../shared/json-file";
3
+ import { dirname, join } from "node:path";
4
+ import { fileExists, readJsonFile, writeJsonFile } from "../shared/json-file";
5
+ export const AUTOPILOT_CONFIG_FILENAME = "autopilot.json";
6
+ export const LEGACY_WORKFLOW_CONFIG_FILENAME = "workflow.json";
7
+ export const DEFAULT_SKILL_ROOTS = ["~/.claude/skills", "~/.config/opencode/skills"];
8
+ export const DEFAULT_AUTOPILOT_CONFIG = {
9
+ skillRoots: DEFAULT_SKILL_ROOTS,
10
+ phases: {
11
+ spec_refinement: { requiredSkills: [] },
12
+ plan: { requiredSkills: [] },
13
+ develop: { requiredSkills: [] },
14
+ review: { requiredSkills: [] },
15
+ test: { requiredSkills: [] },
16
+ },
17
+ };
4
18
  const EMPTY_CONFIG = {
5
19
  skillRoots: [],
6
20
  phases: {},
@@ -43,9 +57,84 @@ function mergeConfigs(base, incoming) {
43
57
  }
44
58
  return next;
45
59
  }
60
+ async function readConfigFileWithWarnings(filePath, label) {
61
+ if (!(await fileExists(filePath))) {
62
+ return { config: null, warnings: [] };
63
+ }
64
+ try {
65
+ const raw = await readFile(filePath, "utf8");
66
+ if (!raw.trim()) {
67
+ return {
68
+ config: null,
69
+ warnings: [`${label} is empty and will be treated as defaults: ${filePath}`],
70
+ };
71
+ }
72
+ const parsed = JSON.parse(raw);
73
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
74
+ return {
75
+ config: null,
76
+ warnings: [`${label} is not a JSON object and will be ignored: ${filePath}`],
77
+ };
78
+ }
79
+ return {
80
+ config: parsed,
81
+ warnings: [],
82
+ };
83
+ }
84
+ catch (error) {
85
+ if (error instanceof SyntaxError) {
86
+ return {
87
+ config: null,
88
+ warnings: [`${label} contains invalid JSON and will be ignored: ${filePath}`],
89
+ };
90
+ }
91
+ throw error;
92
+ }
93
+ }
94
+ async function loadConfigWithLegacy(args) {
95
+ const preferred = await readConfigFileWithWarnings(args.preferredFile, args.label);
96
+ if (preferred.config || preferred.warnings.length > 0 || await fileExists(args.preferredFile)) {
97
+ return preferred;
98
+ }
99
+ const legacy = await readConfigFileWithWarnings(args.legacyFile, `Legacy ${args.label}`);
100
+ if (!legacy.config && legacy.warnings.length === 0) {
101
+ return legacy;
102
+ }
103
+ return {
104
+ config: legacy.config,
105
+ warnings: [
106
+ `Using legacy ${LEGACY_WORKFLOW_CONFIG_FILENAME}; migrate to ${AUTOPILOT_CONFIG_FILENAME}: ${args.legacyFile}`,
107
+ ...legacy.warnings,
108
+ ],
109
+ };
110
+ }
46
111
  export async function resolveWorkflowConfig(args) {
47
- const globalConfigFile = join(args.homeDir ?? homedir(), ".config", "opencode", "workflow.json");
48
- const globalConfig = await readJsonFile(globalConfigFile);
49
- const projectConfig = await readJsonFile(args.projectConfigFile);
50
- return mergeConfigs(mergeConfigs(EMPTY_CONFIG, globalConfig), projectConfig);
112
+ const globalConfigFile = join(args.homeDir ?? homedir(), ".config", "opencode", AUTOPILOT_CONFIG_FILENAME);
113
+ const globalLegacyFile = join(args.homeDir ?? homedir(), ".config", "opencode", LEGACY_WORKFLOW_CONFIG_FILENAME);
114
+ const projectLegacyFile = join(dirname(args.projectConfigFile), LEGACY_WORKFLOW_CONFIG_FILENAME);
115
+ const globalConfig = await loadConfigWithLegacy({
116
+ preferredFile: globalConfigFile,
117
+ legacyFile: globalLegacyFile,
118
+ label: `Global ${AUTOPILOT_CONFIG_FILENAME}`,
119
+ });
120
+ const projectConfig = await loadConfigWithLegacy({
121
+ preferredFile: args.projectConfigFile,
122
+ legacyFile: projectLegacyFile,
123
+ label: `Project ${AUTOPILOT_CONFIG_FILENAME}`,
124
+ });
125
+ const merged = mergeConfigs(mergeConfigs(EMPTY_CONFIG, globalConfig.config), projectConfig.config);
126
+ merged.warnings.push(...globalConfig.warnings, ...projectConfig.warnings);
127
+ return merged;
128
+ }
129
+ export async function ensureAutopilotConfigFile(filePath) {
130
+ if (await fileExists(filePath)) {
131
+ return;
132
+ }
133
+ const legacyFilePath = join(dirname(filePath), LEGACY_WORKFLOW_CONFIG_FILENAME);
134
+ const legacyConfig = await readJsonFile(legacyFilePath);
135
+ if (legacyConfig) {
136
+ await writeJsonFile(filePath, legacyConfig);
137
+ return;
138
+ }
139
+ await writeJsonFile(filePath, DEFAULT_AUTOPILOT_CONFIG);
51
140
  }
@@ -1,3 +1,4 @@
1
+ import { AUTOPILOT_CONFIG_FILENAME } from "../config/workflow-config";
1
2
  const divider = "=".repeat(64);
2
3
  export function formatWorkflowDoctorResult(result) {
3
4
  const lines = [
@@ -50,7 +51,7 @@ export function formatWorkflowInstallResult(result) {
50
51
  const lines = [
51
52
  divider,
52
53
  `Workflow Install: ${result.ok ? "OK" : "ATTENTION"}`,
53
- `Project workflow.json: ${result.projectWorkflowConfigFile}`,
54
+ `Project ${AUTOPILOT_CONFIG_FILENAME}: ${result.projectWorkflowConfigFile}`,
54
55
  `OpenCode config: ${result.opencodeConfigFile}`,
55
56
  `Plugin entry: ${result.pluginEntry}`,
56
57
  ];
@@ -20,4 +20,6 @@ export type WorkflowDoctorResult = {
20
20
  nextSteps: string[];
21
21
  warnings: string[];
22
22
  };
23
- export declare function runWorkflowDoctor(workspace: WorkflowWorkspace): Promise<WorkflowDoctorResult>;
23
+ export declare function runWorkflowDoctor(workspace: WorkflowWorkspace, options?: {
24
+ homeDir?: string;
25
+ }): Promise<WorkflowDoctorResult>;
@@ -3,7 +3,7 @@ import { readFile } from "node:fs/promises";
3
3
  import { homedir } from "node:os";
4
4
  import { dirname, join } from "node:path";
5
5
  import { buildSkillRegistryWithWarnings } from "../config/skill-registry";
6
- import { resolveWorkflowConfig } from "../config/workflow-config";
6
+ import { AUTOPILOT_CONFIG_FILENAME, resolveWorkflowConfig } from "../config/workflow-config";
7
7
  async function fileExists(filePath) {
8
8
  try {
9
9
  await access(filePath);
@@ -27,11 +27,12 @@ function gitignoreHasWorkflowHarness(content) {
27
27
  return normalized.includes(".workflow-harness/");
28
28
  });
29
29
  }
30
- export async function runWorkflowDoctor(workspace) {
30
+ export async function runWorkflowDoctor(workspace, options = {}) {
31
31
  const projectConfigFile = workspace.workflowConfigFile();
32
- const globalConfigFile = join(homedir(), ".config", "opencode", "workflow.json");
32
+ const globalConfigFile = join(options.homeDir ?? homedir(), ".config", "opencode", AUTOPILOT_CONFIG_FILENAME);
33
33
  const resolvedConfig = await resolveWorkflowConfig({
34
34
  projectConfigFile,
35
+ ...(options.homeDir ? { homeDir: options.homeDir } : {}),
35
36
  });
36
37
  const registryResult = await buildSkillRegistryWithWarnings(resolvedConfig.skillRoots);
37
38
  const checks = [];
@@ -52,7 +53,7 @@ export async function runWorkflowDoctor(workspace) {
52
53
  }
53
54
  const hasGlobalConfig = await fileExists(globalConfigFile);
54
55
  if (!hasGlobalConfig) {
55
- warnings.push(`Global workflow.json not found: ${globalConfigFile}`);
56
+ warnings.push(`Global ${AUTOPILOT_CONFIG_FILENAME} not found: ${globalConfigFile}`);
56
57
  }
57
58
  const workspaceRoot = workspace.baseDir().endsWith(".workflow-harness")
58
59
  ? dirname(workspace.baseDir())
@@ -91,10 +92,10 @@ export async function runWorkflowDoctor(workspace) {
91
92
  });
92
93
  const nextSteps = [];
93
94
  if (!(await fileExists(projectConfigFile))) {
94
- nextSteps.push(`Run installer to generate project workflow.json: ${projectConfigFile}`);
95
+ nextSteps.push(`Run installer to generate project ${AUTOPILOT_CONFIG_FILENAME}: ${projectConfigFile}`);
95
96
  }
96
97
  if (resolvedConfig.skillRoots.length === 0) {
97
- nextSteps.push("Add skillRoots to workflow.json if you want phase skill injection");
98
+ nextSteps.push(`Add skillRoots to ${AUTOPILOT_CONFIG_FILENAME} if you want phase skill injection`);
98
99
  }
99
100
  if (missingSkills.length > 0) {
100
101
  nextSteps.push("Fix missing requiredSkills or add corresponding skill files under configured skillRoots");
@@ -1,23 +1,7 @@
1
1
  import { mkdir, readFile, writeFile } from "node:fs/promises";
2
2
  import { join, resolve } from "node:path";
3
- import { access } from "node:fs/promises";
4
- import { writeJsonFile } from "../shared/json-file";
5
- const DEFAULT_WORKFLOW_CONFIG = {
6
- skillRoots: ["~/.claude/skills", "~/.config/opencode/skills"],
7
- phases: {
8
- develop: { requiredSkills: [] },
9
- test: { requiredSkills: [] },
10
- },
11
- };
12
- async function fileExists(filePath) {
13
- try {
14
- await access(filePath);
15
- return true;
16
- }
17
- catch {
18
- return false;
19
- }
20
- }
3
+ import { ensureAutopilotConfigFile, AUTOPILOT_CONFIG_FILENAME } from "../config/workflow-config";
4
+ import { fileExists, writeJsonFile } from "../shared/json-file";
21
5
  function stripJsonComments(input) {
22
6
  return input
23
7
  .replace(/^\s*\/\/.*$/gm, "")
@@ -44,18 +28,18 @@ async function resolveOpencodeConfigFile(opencodeConfigDir) {
44
28
  export async function runWorkflowInstall(args) {
45
29
  const repoRoot = resolve(args.cwd);
46
30
  const harnessDir = join(repoRoot, ".workflow-harness");
47
- const projectWorkflowConfigFile = join(harnessDir, "workflow.json");
31
+ const projectWorkflowConfigFile = join(harnessDir, AUTOPILOT_CONFIG_FILENAME);
48
32
  const opencodeConfigDir = join(args.homeDir, ".config", "opencode");
33
+ const globalAutopilotConfigFile = join(opencodeConfigDir, AUTOPILOT_CONFIG_FILENAME);
49
34
  const pluginEntryFile = args.options?.pluginEntryFile ?? "dist/plugin.js";
50
35
  const pluginEntry = `file://${join(repoRoot, pluginEntryFile)}`;
51
36
  const configResolution = await resolveOpencodeConfigFile(opencodeConfigDir);
52
37
  const opencodeConfigFile = join(opencodeConfigDir, "opencode.json");
53
38
  const warnings = [...configResolution.warnings];
54
39
  await mkdir(harnessDir, { recursive: true });
55
- if (!(await fileExists(projectWorkflowConfigFile))) {
56
- await writeJsonFile(projectWorkflowConfigFile, DEFAULT_WORKFLOW_CONFIG);
57
- }
40
+ await ensureAutopilotConfigFile(projectWorkflowConfigFile);
58
41
  await mkdir(opencodeConfigDir, { recursive: true });
42
+ await ensureAutopilotConfigFile(globalAutopilotConfigFile);
59
43
  if (!(await fileExists(configResolution.filePath))) {
60
44
  await writeJsonFile(opencodeConfigFile, {
61
45
  plugin: [pluginEntry],
@@ -5,10 +5,11 @@ import { createHarness } from "../bootstrap/create-harness";
5
5
  import { runWorkflowDoctor } from "../diagnostics/workflow-doctor";
6
6
  import { runWorkflowInstall } from "../install/workflow-installer";
7
7
  import { SdkOpencodeSessionClient, } from "../../../adapters/opencode/src/opencode-session-client";
8
- import { mkdir, readFile, writeFile } from "node:fs/promises";
8
+ import { mkdir, writeFile } from "node:fs/promises";
9
9
  import { homedir } from "node:os";
10
- import { join } from "node:path";
11
10
  import { z } from "zod";
11
+ import { readJsonFile } from "../shared/json-file";
12
+ import { DefaultWorkflowWorkspace } from "../workspace/workflow-workspace";
12
13
  const workflowIdSchema = z.string().min(1).describe("Workflow identifier");
13
14
  const WORKFLOW_PRIMARY_AGENT_PROMPT = `You are the workflow execution agent.
14
15
 
@@ -29,7 +30,7 @@ Hard rules:
29
30
  - If the user provides natural language only, keep it as-is and let the workflow runtime + downstream AI refinement handle understanding and document selection.
30
31
  `;
31
32
  const WORKFLOW_PRIMARY_AGENT_DESCRIPTION = "Primary workflow agent that drives refine->plan->develop->review->test via workflow tools";
32
- const WORKFLOW_PRIMARY_AGENT_MODEL = "ppchat-codex/gpt-5.4";
33
+ const WORKFLOW_PRIMARY_AGENT_MODEL = "openai/gpt-5.5";
33
34
  function buildPrimaryAgentMetadata(baseDir) {
34
35
  return {
35
36
  name: "workflow",
@@ -77,14 +78,8 @@ async function syncHostTodos(args) {
77
78
  if (!todoClient?.list || !todoClient.create || !todoClient.update) {
78
79
  return;
79
80
  }
80
- const stateFile = join(args.baseDir, "workflows", args.workflowId, "workflow-state.json");
81
- let workflow = null;
82
- try {
83
- workflow = JSON.parse(await readFile(stateFile, "utf8"));
84
- }
85
- catch {
86
- return;
87
- }
81
+ const workspace = new DefaultWorkflowWorkspace(args.baseDir);
82
+ const workflow = await readJsonFile(workspace.workflowStateFile(args.workflowId));
88
83
  if (!workflow?.activeSessionId || !workflow.phase || !workflow.status) {
89
84
  return;
90
85
  }
@@ -320,7 +315,7 @@ export async function workflowPlugin(input) {
320
315
  },
321
316
  },
322
317
  workflow_install: {
323
- description: "Generate workflow.json and safely register the plugin in OpenCode config.",
318
+ description: "Generate autopilot.json and safely register the plugin in OpenCode config.",
324
319
  args: {},
325
320
  execute: async () => {
326
321
  return JSON.stringify(await runWorkflowInstall({
@@ -1,2 +1,3 @@
1
+ export declare function fileExists(filePath: string): Promise<boolean>;
1
2
  export declare function readJsonFile<T>(filePath: string): Promise<T | null>;
2
3
  export declare function writeJsonFile(filePath: string, value: unknown): Promise<void>;
@@ -1,5 +1,14 @@
1
- import { mkdir, readFile, writeFile } from "node:fs/promises";
1
+ import { access, mkdir, readFile, writeFile } from "node:fs/promises";
2
2
  import { dirname } from "node:path";
3
+ export async function fileExists(filePath) {
4
+ try {
5
+ await access(filePath);
6
+ return true;
7
+ }
8
+ catch {
9
+ return false;
10
+ }
11
+ }
3
12
  export async function readJsonFile(filePath) {
4
13
  try {
5
14
  const raw = await readFile(filePath, "utf8");
@@ -1,4 +1,5 @@
1
1
  import { join } from "node:path";
2
+ import { AUTOPILOT_CONFIG_FILENAME } from "../config/workflow-config";
2
3
  export class DefaultWorkflowWorkspace {
3
4
  root;
4
5
  constructor(root) {
@@ -9,6 +10,9 @@ export class DefaultWorkflowWorkspace {
9
10
  if (!normalized) {
10
11
  throw new Error("workflowId must be a non-empty string");
11
12
  }
13
+ if (normalized.includes("..") || normalized.includes("/") || normalized.includes("\\")) {
14
+ throw new Error("workflowId must not contain path separators or traversal sequences");
15
+ }
12
16
  return normalized;
13
17
  }
14
18
  baseDir() {
@@ -18,7 +22,7 @@ export class DefaultWorkflowWorkspace {
18
22
  return join(this.root, "workflows");
19
23
  }
20
24
  workflowConfigFile() {
21
- return join(this.root, "workflow.json");
25
+ return join(this.root, AUTOPILOT_CONFIG_FILENAME);
22
26
  }
23
27
  workflowDir(workflowId) {
24
28
  return join(this.workflowsRoot(), this.normalizeWorkflowId(workflowId));
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@fkqfkq123/opencode-autopilot",
3
3
  "private": false,
4
- "version": "0.1.3",
4
+ "version": "0.1.6",
5
5
  "description": "An OpenCode plugin for attached-session workflow execution with refinement, planning, development, review, and test phases.",
6
6
  "type": "module",
7
7
  "packageManager": "bun@1.3.5",
@@ -24,7 +24,8 @@
24
24
  },
25
25
  "files": [
26
26
  "dist",
27
- "README.md"
27
+ "README.md",
28
+ "GUIDE.zh-CN.md"
28
29
  ],
29
30
  "publishConfig": {
30
31
  "access": "public"