@fkqfkq123/opencode-autopilot 0.1.3 → 0.1.5

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,465 @@
1
+ # Autopilot
2
+
3
+ [English](./README.md) | 中文说明
4
+
5
+ Autopilot 是一个面向 OpenCode 风格运行时的 **attached-session workflow harness**。它提供从需求精炼、计划、开发、评审到测试的 workflow runtime 骨架,以及可本地加载的插件、CLI 入口和诊断能力。
6
+
7
+ ## 1. 这个项目提供什么
8
+
9
+ - 完整 workflow phase 主链:`spec_refinement -> plan -> develop -> review -> test -> done`
10
+ - 面向 OpenCode 的 workflow 命令:`workflow_open`、`workflow_attach`、`workflow_status`、`workflow_answer`、`workflow_approve`、`workflow_resume`、`workflow_back`
11
+ - OpenCode 风格宿主的插件加载能力与原生 primary workflow agent 注册能力
12
+ - `install` / `doctor` 初始化与自检流程
13
+ - review/test loop-back、人工断点、事件存储、attach / re-attach 支持
14
+
15
+ ## 2. 适合谁使用
16
+
17
+ 如果你希望:
18
+
19
+ - 在 OpenCode 风格宿主中接入 workflow 主代理
20
+ - 把工程流程拆成显式 phase
21
+ - 通过插件形式验证 workflow runtime 与 command surface
22
+
23
+ 那么这个项目适合你。
24
+
25
+ ## 3. 环境准备
26
+
27
+ 建议环境:
28
+
29
+ - macOS / Linux / Windows
30
+ - [Bun](https://bun.sh/) `1.3.5` 或兼容版本
31
+ - 已安装 OpenCode(如果你需要实际加载并验证插件)
32
+
33
+ 检查 Bun:
34
+
35
+ ```bash
36
+ bun --version
37
+ ```
38
+
39
+ 如果还没有安装 Bun:
40
+
41
+ ```bash
42
+ curl -fsSL https://bun.sh/install | bash
43
+ ```
44
+
45
+ 安装完成后重启终端,再执行:
46
+
47
+ ```bash
48
+ bun --version
49
+ ```
50
+
51
+ ## 4. 安装
52
+
53
+ ### 4.1 推荐方式:作为 npm 插件包安装
54
+
55
+ OpenCode 原生支持 npm 插件。等这个包发布后,最简单的配置方式是:
56
+
57
+ ```json
58
+ {
59
+ "plugin": ["@fkqfkq123/opencode-autopilot"]
60
+ }
61
+ ```
62
+
63
+ 也可以固定版本:
64
+
65
+ ```json
66
+ {
67
+ "plugin": ["@fkqfkq123/opencode-autopilot@0.1.2"]
68
+ }
69
+ ```
70
+
71
+ 在这种模式下,OpenCode 会自动安装并缓存 npm 包,不需要手动 `git clone`、不需要本地 `plugin.js`、也不需要额外安装脚本。
72
+
73
+ ### 4.2 备用方式:通过 GitHub Releases 安装
74
+
75
+ 如果你更希望走本地文件安装路径,或者需要 fallback 分发方式,可以使用 GitHub Releases 一键安装脚本:
76
+
77
+ ```bash
78
+ curl -fsSL https://raw.githubusercontent.com/juhuaxia/Autopilot/main/install.sh | bash
79
+ ```
80
+
81
+ 安装指定版本:
82
+
83
+ ```bash
84
+ curl -fsSL https://raw.githubusercontent.com/juhuaxia/Autopilot/main/install.sh | bash -s -- --version v0.1.2
85
+ ```
86
+
87
+ 这个备用安装脚本会:
88
+
89
+ - 从 GitHub Releases 下载预构建发布包
90
+ - 安装到 `~/.config/opencode/plugins/autopilot/`
91
+ - 更新 `~/.config/opencode/opencode.json`
92
+
93
+ 发布要求:
94
+
95
+ - 每个 GitHub Release 需要包含 `autopilot-release.tar.gz`
96
+ - 仓库内置 `.github/workflows/release.yml`,在推送 `v*` tag 时会自动构建并上传该文件
97
+
98
+ 如果你想修改源码或从源码开发,继续看下面的源码安装流程。
99
+
100
+ ### 4.3 克隆仓库
101
+
102
+ ```bash
103
+ git clone https://github.com/juhuaxia/Autopilot.git
104
+ cd Autopilot
105
+ ```
106
+
107
+ ### 4.4 安装依赖
108
+
109
+ ```bash
110
+ bun install
111
+ ```
112
+
113
+ 项目使用 `bun.lock`,依赖版本应保持可复现。
114
+
115
+ ## 5. 推荐首次执行的命令
116
+
117
+ 在项目根目录执行:
118
+
119
+ ```bash
120
+ bun run src/cli.ts install
121
+ bun run src/cli.ts doctor
122
+ bun run build
123
+ ```
124
+
125
+ 它们分别会:
126
+
127
+ 1. `install`
128
+ - 创建项目级 `.workflow-harness/autopilot.json`
129
+ - 在缺失时创建全局 `~/.config/opencode/autopilot.json`
130
+ - 尝试安全写入 `~/.config/opencode/opencode.json`
131
+ - 如有 `opencode.jsonc`,在安全情况下归一化写回 `opencode.json`
132
+ 2. `doctor`
133
+ - 检查 `autopilot.json`
134
+ - 检查 `skillRoots`
135
+ - 检查各 phase 的 `requiredSkills`
136
+ - 输出告警与缺失项
137
+ 3. `build`
138
+ - 编译 TypeScript
139
+ - 生成 `dist/plugin.js`
140
+
141
+ 之后建议确认:
142
+
143
+ - `.workflow-harness/autopilot.json` 已生成
144
+ - `doctor` 没有阻断性配置问题
145
+ - `dist/plugin.js` 已生成
146
+
147
+ ## 6. 常用开发命令
148
+
149
+ ### 6.1 构建
150
+
151
+ ```bash
152
+ bun run build
153
+ ```
154
+
155
+ ### 6.2 类型检查
156
+
157
+ ```bash
158
+ bun run typecheck
159
+ ```
160
+
161
+ ### 6.3 运行测试
162
+
163
+ ```bash
164
+ bun test
165
+ ```
166
+
167
+ ### 6.4 运行插件 smoke test
168
+
169
+ ```bash
170
+ bun run smoke:plugin
171
+ ```
172
+
173
+ ### 6.5 直接运行 CLI
174
+
175
+ ```bash
176
+ bun run src/cli.ts doctor
177
+ bun run src/cli.ts install
178
+ ```
179
+
180
+ 也可以通过脚本别名运行:
181
+
182
+ ```bash
183
+ bun run cli --help
184
+ ```
185
+
186
+ > CLI 主要用于 workflow 初始化、attach/status 流程以及 install/doctor 动作。
187
+
188
+ ## 7. CLI 快速使用
189
+
190
+ ### 7.1 初始化配置
191
+
192
+ ```bash
193
+ bun run src/cli.ts install
194
+ ```
195
+
196
+ ### 7.2 做一次自检
197
+
198
+ ```bash
199
+ bun run src/cli.ts doctor
200
+ ```
201
+
202
+ ### 7.3 创建一个 workflow
203
+
204
+ ```bash
205
+ bun run src/cli.ts workflow-open wf-1
206
+ ```
207
+
208
+ ### 7.4 查看 workflow 状态
209
+
210
+ ```bash
211
+ bun run src/cli.ts workflow-status wf-1
212
+ ```
213
+
214
+ ### 7.5 重新 attach 到 workflow channel
215
+
216
+ ```bash
217
+ bun run src/cli.ts workflow-attach wf-1
218
+ ```
219
+
220
+ ## 8. 如何把插件加载到 OpenCode
221
+
222
+ ### 8.0 推荐的 npm 插件方式
223
+
224
+ 推荐的 OpenCode 配置:
225
+
226
+ ```json
227
+ {
228
+ "plugin": ["@fkqfkq123/opencode-autopilot"]
229
+ }
230
+ ```
231
+
232
+ ### 8.1 备用的 release 安装方式
233
+
234
+ 如果你希望使用本地安装后的 fallback 插件,执行:
235
+
236
+ ```bash
237
+ curl -fsSL https://raw.githubusercontent.com/juhuaxia/Autopilot/main/install.sh | bash
238
+ ```
239
+
240
+ 默认安装位置:
241
+
242
+ ```txt
243
+ ~/.config/opencode/plugins/autopilot/
244
+ ```
245
+
246
+ 安装脚本会写入类似这样的插件入口:
247
+
248
+ ```txt
249
+ file:///Users/<your-user>/.config/opencode/plugins/autopilot/plugin.js
250
+ ```
251
+
252
+ ### 8.2 源码开发方式
253
+
254
+ 执行:
255
+
256
+ ```bash
257
+ bun run src/cli.ts install
258
+ bun run src/cli.ts doctor
259
+ ```
260
+
261
+ 如果 installer 能安全改写 OpenCode 配置,通常不需要手工编辑。
262
+
263
+ > `install.sh` 面向 GitHub Releases 安装;项目内 installer 面向源码开发场景。
264
+
265
+ ### 8.3 手工注册插件
266
+
267
+ OpenCode 配置通常在:
268
+
269
+ - `~/.config/opencode/opencode.json`
270
+ - 或 `~/.config/opencode/opencode.jsonc`
271
+
272
+ #### 方案 A:加载构建产物
273
+
274
+ 先构建:
275
+
276
+ ```bash
277
+ bun run build
278
+ ```
279
+
280
+ 然后在 OpenCode 配置中加入:
281
+
282
+ ```json
283
+ {
284
+ "plugin": [
285
+ "file:///ABSOLUTE_PATH_TO_PROJECT/dist/plugin.js"
286
+ ]
287
+ }
288
+ ```
289
+
290
+ #### 方案 B:开发阶段直接加载源码
291
+
292
+ ```json
293
+ {
294
+ "plugin": [
295
+ "file:///ABSOLUTE_PATH_TO_PROJECT/plugin.ts"
296
+ ]
297
+ }
298
+ ```
299
+
300
+ ### 8.4 启动 OpenCode
301
+
302
+ 交互模式:
303
+
304
+ ```bash
305
+ opencode
306
+ ```
307
+
308
+ Server 模式:
309
+
310
+ ```bash
311
+ opencode serve
312
+ ```
313
+
314
+ ## 9. 加载成功后你应该看到什么
315
+
316
+ 插件会暴露这些工具/命令:
317
+
318
+ - `workflow_channel`
319
+ - `workflow_open`
320
+ - `workflow_attach`
321
+ - `workflow_status`
322
+ - `workflow_answer`
323
+ - `workflow_approve`
324
+ - `workflow_resume`
325
+ - `workflow_back`
326
+ - `workflow_doctor`
327
+
328
+ 推荐优先使用 split tools:
329
+
330
+ - `workflow_open`
331
+ - `workflow_attach`
332
+ - `workflow_status`
333
+ - `workflow_answer`
334
+ - `workflow_approve`
335
+ - `workflow_resume`
336
+ - `workflow_back`
337
+
338
+ 典型加载日志:
339
+
340
+ ```txt
341
+ [autopilot] Autopilot plugin loaded (... commands)
342
+ ```
343
+
344
+ ## 10. 目录结构
345
+
346
+ ### 10.1 配置层级
347
+
348
+ - 用户默认:`~/.config/opencode/autopilot.json`
349
+ - 项目级覆盖:`<repo>/.workflow-harness/autopilot.json`
350
+ - 运行时状态:`<repo>/.workflow-harness/workflows/<workflowId>/`
351
+
352
+ ### 10.2 各目录作用
353
+
354
+ - `src/` — CLI 入口与顶层源码
355
+ - `packages/runtime/` — workflow runtime 实现
356
+ - `tests/` — 测试
357
+ - `scripts/` — 辅助脚本
358
+ - `.workflow-harness/` — 运行时配置、状态与产物
359
+ - `dist/` — 构建输出
360
+
361
+ ## 11. 最小 `autopilot.json` 示例
362
+
363
+ 先从完全中性的配置开始:
364
+
365
+ ```json
366
+ {
367
+ "skillRoots": ["~/.claude/skills", "~/.config/opencode/skills"],
368
+ "phases": {
369
+ "develop": { "requiredSkills": [] },
370
+ "test": { "requiredSkills": [] }
371
+ }
372
+ }
373
+ ```
374
+
375
+ 然后按项目需要添加 skill。以前端项目为例:
376
+
377
+ ```json
378
+ {
379
+ "skillRoots": ["~/.claude/skills", "~/.config/opencode/skills"],
380
+ "phases": {
381
+ "develop": { "requiredSkills": ["frontend-design"] },
382
+ "test": { "requiredSkills": ["playwright"] }
383
+ }
384
+ }
385
+ ```
386
+
387
+ 这个前端示例只是示例,workflow runtime 默认并不绑定前端。
388
+
389
+ 建议:
390
+
391
+ - skill / profile 配置放在全局或项目级 `autopilot.json`
392
+ - 不要把 skill 配置写到 `workflows/<workflowId>/`
393
+ - 新配置上线前先跑 `workflow_doctor` 或 CLI `doctor`
394
+
395
+ ## 12. 常见问题
396
+
397
+ ### Q1:`install` 无法改写 OpenCode 配置怎么办?
398
+
399
+ 手工编辑 OpenCode 配置,把发布版插件路径或本地构建路径加入 `plugin` 数组。例如:
400
+
401
+ ```json
402
+ {
403
+ "plugin": [
404
+ "file:///Users/<your-user>/.config/opencode/plugins/autopilot/plugin.js"
405
+ ]
406
+ }
407
+ ```
408
+
409
+ 如果你是源码开发场景,也可以指向本地的 `dist/plugin.js`。
410
+
411
+ 如果你使用 npm 插件模式,则只需要这样配置:
412
+
413
+ ```json
414
+ {
415
+ "plugin": ["@fkqfkq123/opencode-autopilot"]
416
+ }
417
+ ```
418
+
419
+ ### Q2:为什么 build 以后还是看不到 workflow 命令?
420
+
421
+ 通常是宿主集成层还没有把导出的 `workflowCommands` 真正注册到宿主命令面板 / tool surface。
422
+
423
+ ### Q3:我应该先验证什么?
424
+
425
+ 建议先按这个顺序验证:
426
+
427
+ 1. 宿主能导入插件文件
428
+ 2. 宿主能调用默认导出
429
+ 3. 宿主能收到一个 plugin-like 对象
430
+
431
+ 不要一开始就先验证 UI。
432
+
433
+ ### Q4:插件根入口有什么约束?
434
+
435
+ 根入口 `plugin.ts` 应只暴露一个宿主可调用的默认导出,不要在根入口额外暴露内部 class 或 helper function。
436
+
437
+ ## 13. 推荐阅读
438
+
439
+ | 文档 | 用途 |
440
+ |---|---|
441
+ | `README.md` | 安装、发布、使用总入口 |
442
+ | `WORKFLOW_SKILL_PROFILE_ARCHITECTURE_CN.md` | skill / profile 配置设计 |
443
+ | `OPENCODE_WORKFLOW_AGENT_GUIDE.md` | agent / tool 调用顺序 |
444
+ | `REQUIREMENT_TEMPLATE.md` | 需求输入模板 |
445
+
446
+ 内部规划稿、验收草案、状态记录等可以保存在本地 `docs_internal/` 中,该目录默认已忽略。
447
+
448
+ ## 14. 最快可用路径
449
+
450
+ 如果你只想最快跑起源码开发环境:
451
+
452
+ ```bash
453
+ bun install
454
+ bun run src/cli.ts install
455
+ bun run src/cli.ts doctor
456
+ bun run build
457
+ opencode
458
+ ```
459
+
460
+ 然后:
461
+
462
+ 1. 确认 `.workflow-harness/autopilot.json` 已生成
463
+ 2. 确认 `dist/plugin.js` 已生成
464
+ 3. 如果 OpenCode 没有自动加载插件,手工把 `file:///ABSOLUTE_PATH_TO_PROJECT/dist/plugin.js` 加到配置里
465
+ 4. 在宿主中验证 `workflow_open`、`workflow_attach`、`workflow_status` 是否可见
package/README.md CHANGED
@@ -123,11 +123,12 @@ bun run build
123
123
  What they do:
124
124
 
125
125
  1. `install`
126
- - creates project-level `.workflow-harness/workflow.json`
126
+ - creates project-level `.workflow-harness/autopilot.json`
127
+ - creates global `~/.config/opencode/autopilot.json` when missing
127
128
  - tries to safely update `~/.config/opencode/opencode.json`
128
129
  - normalizes `opencode.jsonc` into `opencode.json` when safe
129
130
  2. `doctor`
130
- - checks `workflow.json`
131
+ - checks `autopilot.json`
131
132
  - checks `skillRoots`
132
133
  - checks phase-level `requiredSkills`
133
134
  - reports warnings and missing pieces
@@ -137,7 +138,7 @@ What they do:
137
138
 
138
139
  After that, confirm:
139
140
 
140
- - `.workflow-harness/workflow.json` exists
141
+ - `.workflow-harness/autopilot.json` exists
141
142
  - `doctor` shows no blocking configuration issue
142
143
  - `dist/plugin.js` exists
143
144
 
@@ -342,8 +343,8 @@ Typical load log:
342
343
 
343
344
  ### 10.1 Configuration layers
344
345
 
345
- - user default: `~/.config/opencode/workflow.json`
346
- - project override: `<repo>/.workflow-harness/workflow.json`
346
+ - user default: `~/.config/opencode/autopilot.json`
347
+ - project override: `<repo>/.workflow-harness/autopilot.json`
347
348
  - runtime state: `<repo>/.workflow-harness/workflows/<workflowId>/`
348
349
 
349
350
  ### 10.2 What each directory does
@@ -355,7 +356,7 @@ Typical load log:
355
356
  - `.workflow-harness/` — runtime config, state, and artifacts
356
357
  - `dist/` — build output
357
358
 
358
- ## 11. Minimal `workflow.json` example
359
+ ## 11. Minimal `autopilot.json` example
359
360
 
360
361
  Start with a fully neutral config:
361
362
 
@@ -385,7 +386,7 @@ That frontend example is only an example. The workflow runtime is not frontend-b
385
386
 
386
387
  Recommendations:
387
388
 
388
- - keep skill/profile config in global or project-level `workflow.json`
389
+ - keep skill/profile config in global or project-level `autopilot.json`
389
390
  - do not put skill config under `workflows/<workflowId>/`
390
391
  - run `workflow_doctor` or CLI `doctor` before using a new config
391
392
 
@@ -456,7 +457,7 @@ opencode
456
457
 
457
458
  Then:
458
459
 
459
- 1. confirm `.workflow-harness/workflow.json` exists
460
+ 1. confirm `.workflow-harness/autopilot.json` exists
460
461
  2. confirm `dist/plugin.js` exists
461
462
  3. if OpenCode does not auto-load the plugin, add `file:///ABSOLUTE_PATH_TO_PROJECT/dist/plugin.js` manually to config
462
463
  4. verify `workflow_open`, `workflow_attach`, and `workflow_status` are visible in the host
@@ -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,6 +21,8 @@ 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(homedir(), ".config", "opencode", AUTOPILOT_CONFIG_FILENAME));
23
26
  const resolvedConfig = await resolveWorkflowConfig({
24
27
  projectConfigFile: workspace.workflowConfigFile(),
25
28
  });
@@ -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
  ];
@@ -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);
@@ -29,7 +29,7 @@ function gitignoreHasWorkflowHarness(content) {
29
29
  }
30
30
  export async function runWorkflowDoctor(workspace) {
31
31
  const projectConfigFile = workspace.workflowConfigFile();
32
- const globalConfigFile = join(homedir(), ".config", "opencode", "workflow.json");
32
+ const globalConfigFile = join(homedir(), ".config", "opencode", AUTOPILOT_CONFIG_FILENAME);
33
33
  const resolvedConfig = await resolveWorkflowConfig({
34
34
  projectConfigFile,
35
35
  });
@@ -52,7 +52,7 @@ export async function runWorkflowDoctor(workspace) {
52
52
  }
53
53
  const hasGlobalConfig = await fileExists(globalConfigFile);
54
54
  if (!hasGlobalConfig) {
55
- warnings.push(`Global workflow.json not found: ${globalConfigFile}`);
55
+ warnings.push(`Global ${AUTOPILOT_CONFIG_FILENAME} not found: ${globalConfigFile}`);
56
56
  }
57
57
  const workspaceRoot = workspace.baseDir().endsWith(".workflow-harness")
58
58
  ? dirname(workspace.baseDir())
@@ -91,10 +91,10 @@ export async function runWorkflowDoctor(workspace) {
91
91
  });
92
92
  const nextSteps = [];
93
93
  if (!(await fileExists(projectConfigFile))) {
94
- nextSteps.push(`Run installer to generate project workflow.json: ${projectConfigFile}`);
94
+ nextSteps.push(`Run installer to generate project ${AUTOPILOT_CONFIG_FILENAME}: ${projectConfigFile}`);
95
95
  }
96
96
  if (resolvedConfig.skillRoots.length === 0) {
97
- nextSteps.push("Add skillRoots to workflow.json if you want phase skill injection");
97
+ nextSteps.push(`Add skillRoots to ${AUTOPILOT_CONFIG_FILENAME} if you want phase skill injection`);
98
98
  }
99
99
  if (missingSkills.length > 0) {
100
100
  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
 
@@ -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.5",
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"