@gong-ym/ai-spec-auto 0.2.15 → 0.2.17

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (46) hide show
  1. package/.agents/commands/common/spec-continue.md +1 -1
  2. package/.agents/commands/common/spec-start-review.md +1 -1
  3. package/.agents/commands/common/spec-start.md +1 -1
  4. package/.agents/commands/common/spec-update.md +2 -2
  5. package/.agents/commands/cursor/opsx-apply.md +0 -1
  6. package/.agents/commands/cursor/opsx-propose.md +0 -1
  7. package/.agents/flows/RUN_OUTPUT.md +0 -2
  8. package/.agents/flows/common/prd-to-delivery.md +0 -3
  9. package/.agents/flows/common/requirement-to-observability.md +0 -2
  10. package/.agents/orchestration/task-orchestrator-run-plan-template.md +2 -2
  11. package/.agents/registry/flows.json +5 -5
  12. package/.agents/registry/roles.json +13 -27
  13. package/.agents/roles/common/backend-implementer.md +3 -4
  14. package/.agents/roles/common/code-guardian.md +2 -3
  15. package/.agents/roles/common/frontend-implementer.md +3 -4
  16. package/.agents/roles/common/requirement-analyst.md +7 -18
  17. package/.agents/roles/common/tooling-implementer.md +3 -4
  18. package/.agents/rules/common/12-Superpowers/346/211/247/350/241/214/350/247/204/350/214/203.md +1 -1
  19. package/.agents/rules/common/14-/345/256/241/350/256/241/346/261/207/346/212/245/350/247/204/350/214/203.md +1 -2
  20. package/.agents/rules/profiles/react/01-/351/241/271/347/233/256/346/246/202/350/277/260.md +1 -1
  21. package/.agents/rules/profiles/react/05-API/350/247/204/350/214/203.md +2 -2
  22. package/.agents/rules/profiles/vue/05-API/350/247/204/350/214/203.md +2 -2
  23. package/.agents/skills/common/archive-change/SKILL.md +0 -1
  24. package/.agents/skills/common/create-proposal/SKILL.md +7 -8
  25. package/.agents/skills/common/execute-task/SKILL.md +3 -3
  26. package/.agents/skills/profiles/react/create-api/SKILL.md +2 -2
  27. package/.agents/skills/profiles/vue/create-api/SKILL.md +2 -2
  28. package/bin/archive-change.js +37 -259
  29. package/bin/cli.js +5 -0
  30. package/bin/demo-runtime-smoke.js +11 -74
  31. package/bin/execution-semantics.js +4 -17
  32. package/bin/expert-executor.js +4 -7
  33. package/bin/protocol-workflow.js +1 -1
  34. package/bin/refresh-command.js +212 -0
  35. package/bin/runtime-state.js +18 -14
  36. package/bin/task-orchestrator-runner.js +2 -2
  37. package/internal/ai-protocol-workflow.js +80 -119
  38. package/openspec/config.yaml.template +6 -15
  39. package/openspec/schemas/expert-delivery/schema.yaml +0 -11
  40. package/openspec/schemas/expert-delivery/templates/checklist.md +12 -30
  41. package/openspec/schemas/expert-delivery/templates/iterations.md +6 -22
  42. package/openspec/schemas/expert-delivery/templates/proposal.md +7 -35
  43. package/openspec/schemas/expert-delivery/templates/tasks.md +7 -20
  44. package/package.json +1 -1
  45. package/scripts/post-publish-auto-fix-check.js +5 -61
  46. package/openspec/schemas/expert-delivery/templates/design.md +0 -61
@@ -189,7 +189,6 @@ function createBootstrapPayload(runId, userInput, changeId) {
189
189
  artifacts: [
190
190
  `openspec/changes/${changeId}/proposal.md`,
191
191
  `openspec/changes/${changeId}/specs/`,
192
- `openspec/changes/${changeId}/design.md`,
193
192
  `openspec/changes/${changeId}/tasks.md`,
194
193
  'code',
195
194
  `openspec/changes/${changeId}/checklist.md`,
@@ -235,13 +234,11 @@ function createBootstrapPayload(runId, userInput, changeId) {
235
234
  artifacts: {
236
235
  proposal: `openspec/changes/${changeId}/proposal.md`,
237
236
  specs: `openspec/changes/${changeId}/specs/`,
238
- design: `openspec/changes/${changeId}/design.md`,
239
237
  tasks: `openspec/changes/${changeId}/tasks.md`,
240
238
  },
241
239
  expected_output: [
242
240
  '补齐 proposal',
243
241
  '输出 specs',
244
- '输出 design',
245
242
  '输出 tasks',
246
243
  '列出缺失输入',
247
244
  ],
@@ -279,7 +276,7 @@ function writeRequirementArtifacts(targetDir, changeId) {
279
276
  '- 新增一个商品 mock 页面,用于验证 expert-delivery 主链可运行。',
280
277
  '',
281
278
  '### 工程目标',
282
- '- 验证 proposal、design、tasks、checklist、iterations 这组模板化产物可以支撑主链闭环。',
279
+ '- 验证 proposal、tasks、checklist、iterations 这组模板化产物可以支撑主链闭环。',
283
280
  '',
284
281
  '### 变更对象与入口',
285
282
  '- 页面入口:`src/views/products/mock/index.vue`',
@@ -298,7 +295,7 @@ function writeRequirementArtifacts(targetDir, changeId) {
298
295
  '- 新增商品 mock 页面。',
299
296
  '- 新增最小路由模块。',
300
297
  '- 新增最小 mock 数据。',
301
- '- 输出结构化的 design、tasks、checklist 和 iterations。',
298
+ '- 输出结构化的 tasks、checklist 和 iterations。',
302
299
  '',
303
300
  '### Out of Scope(排除范围)',
304
301
  '- 不接真实 API(接口)。',
@@ -346,70 +343,11 @@ function writeRequirementArtifacts(targetDir, changeId) {
346
343
  '- **则** 只读取本地 mock 模块,不调用远程 API',
347
344
  ].join('\n'));
348
345
 
349
- writeFile(targetDir, `openspec/changes/${changeId}/design.md`, [
350
- '# 技术设计',
351
- '',
352
- '## 方案概览',
353
- '- 在现有 Vue 目录约定下落一个最小商品页面、路由模块和 mock 数据文件。',
354
- '- 通过结构化的 proposal、design、tasks、checklist、iterations 验证模板增强后的主链产物。',
355
- '',
356
- '## 仓库对齐',
357
- '',
358
- '### 页面与路由落点',
359
- '- 页面落在 `src/views/products/mock/index.vue`',
360
- '- 路由落在 `src/router/modules/products.ts`',
361
- '',
362
- '### 接口与数据落点',
363
- '- mock 数据落在 `src/mock/products.ts`',
364
- '- 当前示例不新增真实 API(接口) 封装',
365
- '',
366
- '### 状态与样式落点',
367
- '- 页面使用局部静态数据,不引入额外状态管理',
368
- '- 样式直接放在页面 SFC(单文件组件) 内,保持最小实现',
369
- '',
370
- '### 测试与组件库落点',
371
- '- 由 runtime smoke 和模板回归用例验证产物结构',
372
- '- 当前示例不额外接入组件库,只复用仓库现有目录约定',
373
- '',
374
- '## 关键决策',
375
- '',
376
- '### 信息结构',
377
- '- 页面只保留标题和商品列表,用最小信息结构证明页面可交付。',
378
- '',
379
- '### 状态管理方案',
380
- '- 商品数据直接从本地 mock 文件导入,避免在示例里引入无关状态层。',
381
- '',
382
- '### 组件复用策略',
383
- '- 优先复用现有 Vue 页面、路由和 mock 组织方式,不额外封装新组件。',
384
- '',
385
- '### 禁止重复实现的能力',
386
- '- 不重复实现路由基础设施、构建配置和全局状态容器。',
387
- '',
388
- '### 组件缺口',
389
- '- 当前示例未识别必须补齐的组件缺口,真实业务接入时再补充。',
390
- '',
391
- '## 数据与接口变更',
392
- '- 页面只读取本地 `productMock` 数据,不新增请求或响应契约。',
393
- '- 不引入新的 props(属性)、事件或跨模块状态同步。',
394
- '',
395
- '## 验证说明',
396
- '',
397
- '### 本地验证',
398
- '- 通过 runtime smoke 命令推进主链,确认产物、代码和归档目录都能落盘。',
399
- '',
400
- '### 浏览器验证',
401
- '- 当前示例不接真实浏览器脚本,以页面文件、路由文件和 mock 文件的落盘作为最小替代证据。',
402
- '',
403
- '### 关键验收路径',
404
- '- 生成 proposal、specs、design、tasks 后进入实现专家。',
405
- '- 页面、路由、mock 文件落盘后补齐 checklist 和 iterations,再完成归档。',
406
- ].join('\n'));
407
-
408
346
  writeFile(targetDir, `openspec/changes/${changeId}/tasks.md`, [
409
347
  '# 实施任务',
410
348
  '',
411
349
  '## 执行总原则',
412
- '- [ ] 任务必须限定在 proposal.md、design.md 和 specs 已批准范围内',
350
+ '- [ ] 任务必须限定在 proposal.md 和 specs 已批准范围内',
413
351
  '- [ ] 每个子任务都要写明目标、输入、输出、验证点和依赖或前置条件',
414
352
  '- [ ] 未完成验证前不得宣称任务完成或继续交接',
415
353
  '',
@@ -420,18 +358,18 @@ function writeRequirementArtifacts(targetDir, changeId) {
420
358
  '- [ ] 输入:proposal.md、specs/ui/spec.md、现有 `src/views` 目录约定',
421
359
  '- [ ] 输出:`src/views/products/mock/index.vue`',
422
360
  '- [ ] 验证点:页面文件存在,能引用本地 mock 数据并展示列表',
423
- '- [ ] 依赖或前置条件:requirement-analyst 已产出 proposal、specs、design',
361
+ '- [ ] 依赖或前置条件:requirement-analyst 已产出 proposal、specs',
424
362
  '',
425
363
  '### 子任务 2',
426
364
  '- [ ] 目标:补齐路由模块和 mock 数据文件',
427
- '- [ ] 输入:design.md、现有 `src/router/modules` 与 `src/mock` 目录约定',
365
+ '- [ ] 输入:proposal.md、现有 `src/router/modules` 与 `src/mock` 目录约定',
428
366
  '- [ ] 输出:`src/router/modules/products.ts`、`src/mock/products.ts`',
429
367
  '- [ ] 验证点:路由指向商品 mock 页面,mock 数据文件可被页面导入',
430
368
  '- [ ] 依赖或前置条件:子任务 1 的页面路径已确定',
431
369
  '',
432
370
  '### 子任务 3',
433
371
  '- [ ] 目标:补齐 checklist 和 iterations,完成交付闭环',
434
- '- [ ] 输入:实现结果、proposal.md、design.md、tasks.md',
372
+ '- [ ] 输入:实现结果、proposal.md、tasks.md',
435
373
  '- [ ] 输出:`checklist.md`、`iterations.md`',
436
374
  '- [ ] 验证点:检查结论、验证摘要、残留风险和交接提醒都已记录',
437
375
  '- [ ] 依赖或前置条件:页面、路由和 mock 数据文件已落盘',
@@ -493,7 +431,7 @@ function writeGuardianArtifacts(targetDir, changeId) {
493
431
  '# 检查清单',
494
432
  '',
495
433
  '## 通过项',
496
- '- [x] proposal、specs、design 与实现结果保持一致',
434
+ '- [x] proposal、specs 与实现结果保持一致',
497
435
  '- [x] 已完成任务已真实反映在代码和配套文件中',
498
436
  '- [x] 项目的目录、路由、接口、样式、测试等规则已满足当前示例边界',
499
437
  '',
@@ -509,7 +447,7 @@ function writeGuardianArtifacts(targetDir, changeId) {
509
447
  '- [x] console(控制台) 检查:通过,当前示例未引入浏览器脚本报错',
510
448
  '',
511
449
  '### 范围一致性',
512
- '- [x] 当前实现未超出 proposal、design、tasks 已批准范围',
450
+ '- [x] 当前实现未超出 proposal、tasks 已批准范围',
513
451
  '',
514
452
  '### 组件复用检查',
515
453
  '- [x] 已记录当前示例优先复用仓库既有目录约定,暂无额外组件库缺口',
@@ -534,7 +472,7 @@ function writeGuardianArtifacts(targetDir, changeId) {
534
472
  '- 关联证据:checklist.md、`.ai-spec/current-run.json` 和归档后的 `openspec/changes/archive/` 目录。',
535
473
  '',
536
474
  '## 修正动作',
537
- '- [x] 已补齐 proposal、design、tasks、checklist、iterations 的结构化示例内容。',
475
+ '- [x] 已补齐 proposal、tasks、checklist、iterations 的结构化示例内容。',
538
476
  '- 已完成动作:新增页面、路由、mock 数据,并补录验证摘要和交付结论。',
539
477
  '- 待跟进动作:后续可替换为真实业务页面或真实 AI IDE 执行回合。',
540
478
  '',
@@ -542,7 +480,7 @@ function writeGuardianArtifacts(targetDir, changeId) {
542
480
  '- [ ] 真实浏览器验证、真实 API 和更严格的组件复用约束尚未纳入本次示例。',
543
481
  '- 风险说明:如果直接把该示例当成业务实现模板,仍需补真实业务约束和验证脚本。',
544
482
  '- 触发条件:进入真实业务项目、引入真实接口或涉及复杂状态管理时。',
545
- '- 建议缓解方式:在业务项目中补齐 design、checklist 和浏览器验证证据,再推进归档。',
483
+ '- 建议缓解方式:在业务项目中补齐 checklist 和浏览器验证证据,再推进归档。',
546
484
  '',
547
485
  '## 下轮提醒',
548
486
  '- [ ] 下一轮可将最小示例替换成真实页面或真实 AI IDE 执行输入。',
@@ -704,8 +642,7 @@ function runDemoRuntimeSmoke(options = {}) {
704
642
  },
705
643
  outputs: [
706
644
  '.ai-spec/current-run.json',
707
- 'openspec/specs/ui/spec.md',
708
- 'openspec/specs/api/spec.md',
645
+ 'openspec/specs/runtime-smoke-demo.md',
709
646
  'src/views/products/mock/index.vue',
710
647
  'src/router/modules/products.ts',
711
648
  'src/mock/products.ts',
@@ -112,13 +112,13 @@ const ROLE_OPENSPEC_ACTIONS = {
112
112
  };
113
113
 
114
114
  const ROLE_REQUIRED_INPUTS = {
115
- 'frontend-implementer': ['proposal', 'specs', 'design', 'tasks'],
116
- 'code-guardian': ['proposal', 'specs', 'design', 'tasks'],
117
- 'archive-change': ['proposal', 'specs', 'design', 'tasks', 'checklist', 'iterations'],
115
+ 'frontend-implementer': ['proposal', 'specs', 'tasks'],
116
+ 'code-guardian': ['proposal', 'specs', 'tasks'],
117
+ 'archive-change': ['proposal', 'specs', 'tasks', 'checklist', 'iterations'],
118
118
  };
119
119
 
120
120
  const ROLE_REQUIRED_OUTPUTS = {
121
- 'requirement-analyst': ['proposal', 'specs', 'design', 'tasks'],
121
+ 'requirement-analyst': ['proposal', 'specs', 'tasks'],
122
122
  'code-guardian': ['checklist', 'iterations'],
123
123
  };
124
124
 
@@ -489,9 +489,6 @@ function validatePreImplementationGate(targetDir, currentRun, executionPayload)
489
489
  const specsPath = currentRun.artifacts?.specs
490
490
  ? path.join(targetDir, runtimeState.normalizeSpecsArtifactPath(currentRun.artifacts.specs))
491
491
  : null;
492
- const designPath = currentRun.artifacts?.design
493
- ? path.join(targetDir, currentRun.artifacts.design)
494
- : null;
495
492
  const tasksPath = currentRun.artifacts?.tasks
496
493
  ? path.join(targetDir, currentRun.artifacts.tasks)
497
494
  : null;
@@ -503,9 +500,6 @@ function validatePreImplementationGate(targetDir, currentRun, executionPayload)
503
500
  if (!specsPath || !fs.existsSync(specsPath)) {
504
501
  reasons.push('specs/ 缺失');
505
502
  }
506
- if (!designPath || !fs.existsSync(designPath)) {
507
- reasons.push('design.md 缺失');
508
- }
509
503
  if (!tasksPath || !fs.existsSync(tasksPath)) {
510
504
  reasons.push('tasks.md 缺失');
511
505
  }
@@ -516,7 +510,6 @@ function validatePreImplementationGate(targetDir, currentRun, executionPayload)
516
510
 
517
511
  const proposalContent = fs.readFileSync(proposalPath, 'utf8').trim();
518
512
  const specsArtifact = readMarkdownArtifactContent(specsPath);
519
- const designContent = fs.readFileSync(designPath, 'utf8').trim();
520
513
  const tasksContent = fs.readFileSync(tasksPath, 'utf8').trim();
521
514
  const taskItems = listMarkdownBullets(tasksContent);
522
515
 
@@ -549,9 +542,6 @@ function validatePreImplementationGate(targetDir, currentRun, executionPayload)
549
542
  if (specsArtifact.content.length < 40) {
550
543
  reasons.push('specs/ 过短,未达到 compact 最小信息量');
551
544
  }
552
- if (designContent.length < 40) {
553
- reasons.push('design.md 过短,未达到 compact 最小信息量');
554
- }
555
545
  if (taskItems.length < 3) {
556
546
  reasons.push('tasks.md 任务条目不足 3 条');
557
547
  }
@@ -566,9 +556,6 @@ function validatePreImplementationGate(targetDir, currentRun, executionPayload)
566
556
  if (specsArtifact.content.length < 80) {
567
557
  reasons.push('specs/ 过短,未达到 standard 最小信息量');
568
558
  }
569
- if (designContent.length < 80) {
570
- reasons.push('design.md 过短,未达到 standard 最小信息量');
571
- }
572
559
  if (headingCount < 2) {
573
560
  reasons.push('proposal.md 缺少足够的小节结构');
574
561
  }
@@ -272,7 +272,6 @@ function buildExecutionArtifactMap(changeId, artifacts = {}, options = {}) {
272
272
  return {
273
273
  proposal: null,
274
274
  specs: null,
275
- design: null,
276
275
  tasks: null,
277
276
  bugfix: artifacts.bugfix || `${historyDir}/bugfix.md`,
278
277
  implementation_notes: implementationNotes || `${historyDir}/implementation-notes.md`,
@@ -285,7 +284,6 @@ function buildExecutionArtifactMap(changeId, artifacts = {}, options = {}) {
285
284
  return {
286
285
  proposal: null,
287
286
  specs: null,
288
- design: null,
289
287
  tasks: null,
290
288
  bugfix: artifacts.bugfix || null,
291
289
  implementation_notes: implementationNotes,
@@ -298,7 +296,6 @@ function buildExecutionArtifactMap(changeId, artifacts = {}, options = {}) {
298
296
  return {
299
297
  proposal: artifacts.proposal || `${baseDir}/proposal.md`,
300
298
  specs: runtimeState.normalizeSpecsArtifactPath(artifacts.specs || `${baseDir}/specs`),
301
- design: artifacts.design || `${baseDir}/design.md`,
302
299
  tasks: artifacts.tasks || `${baseDir}/tasks.md`,
303
300
  bugfix: artifacts.bugfix || null,
304
301
  implementation_notes: implementationNotes,
@@ -818,8 +815,8 @@ function applyExecution(options) {
818
815
  if (shouldAdvanceRuntime(options)) {
819
816
  const runtimeActionPayload = buildAutoRuntimeAction(targetDir, payload);
820
817
  if (runtimeActionPayload) {
821
- if (archive_result?.archived_artifacts) {
822
- runtimeActionPayload.artifacts = archive_result.archived_artifacts;
818
+ if (archive_result?.specs_stored_at) {
819
+ runtimeActionPayload.artifacts = { specs: archive_result.specs_stored_at };
823
820
  runtimeActionPayload.skip_artifact_check = true;
824
821
  }
825
822
  const runtimePayload = normalizeRuntimeActionPayload(runtimeActionPayload);
@@ -875,8 +872,8 @@ function applyExecutionData(options) {
875
872
  if (shouldAdvanceRuntime(options)) {
876
873
  const runtimeActionPayload = buildAutoRuntimeAction(targetDir, payload);
877
874
  if (runtimeActionPayload) {
878
- if (archive_result?.archived_artifacts) {
879
- runtimeActionPayload.artifacts = archive_result.archived_artifacts;
875
+ if (archive_result?.specs_stored_at) {
876
+ runtimeActionPayload.artifacts = { specs: archive_result.specs_stored_at };
880
877
  runtimeActionPayload.skip_artifact_check = true;
881
878
  }
882
879
  const runtimePayload = normalizeRuntimeActionPayload(runtimeActionPayload);
@@ -348,7 +348,7 @@ function printUpdate(result) {
348
348
  console.log(` action: ${result.fast_path.action || '(none)'}`);
349
349
  console.log(` run_status: ${result.fast_path.run_status || '(none)'}`);
350
350
  console.log(` current_role: ${result.fast_path.current_role || '(none)'}`);
351
- console.log(` archived_to: ${result.fast_path.archived_to || '(none)'}`);
351
+ console.log(` specs_stored_at: ${result.fast_path.specs_stored_at || '(none)'}`);
352
352
  console.log(` requires_followup_turn: ${result.fast_path.requires_followup_turn ? 'yes' : 'no'}`);
353
353
  }
354
354
  console.log('turn:');
@@ -0,0 +1,212 @@
1
+ const path = require('path');
2
+ const fs = require('fs');
3
+
4
+ function parseArgs(argv) {
5
+ const options = {
6
+ target: '.',
7
+ json: false,
8
+ help: false,
9
+ };
10
+
11
+ for (let index = 0; index < argv.length; index += 1) {
12
+ const arg = argv[index];
13
+ if (arg === '--json') {
14
+ options.json = true;
15
+ } else if (arg === '--help' || arg === '-h') {
16
+ options.help = true;
17
+ } else if (!arg.startsWith('-')) {
18
+ options.target = arg;
19
+ } else {
20
+ throw new Error(`未知 refresh 参数:${arg}`);
21
+ }
22
+ }
23
+
24
+ return options;
25
+ }
26
+
27
+ function printUsage() {
28
+ console.log(`ai-spec-auto refresh [目录] [--json]
29
+
30
+ 说明:
31
+ 轻量更新命令。仅同步流程编排相关资产(commands、flows、orchestration、registry、templates)。
32
+ 不覆盖项目已定制的 rules、skills、roles。
33
+ 不重新扫描项目、不重建 manifest、不触发交互确认。
34
+
35
+ 选项:
36
+ --json 以 JSON 格式输出
37
+
38
+ 示例:
39
+ npx @gong-ym/ai-spec-auto@latest refresh
40
+ npx @gong-ym/ai-spec-auto@latest refresh .
41
+ npx @gong-ym/ai-spec-auto@latest refresh /path/to/project`);
42
+ }
43
+
44
+ function copyDirSync(src, dest) {
45
+ fs.mkdirSync(dest, { recursive: true });
46
+ const entries = fs.readdirSync(src, { withFileTypes: true });
47
+ for (const entry of entries) {
48
+ const srcPath = path.join(src, entry.name);
49
+ const destPath = path.join(dest, entry.name);
50
+ if (entry.isDirectory()) {
51
+ copyDirSync(srcPath, destPath);
52
+ } else {
53
+ fs.copyFileSync(srcPath, destPath);
54
+ }
55
+ }
56
+ }
57
+
58
+ function refresh(targetDir, pkgRoot) {
59
+ const agentsDir = path.join(targetDir, '.agents');
60
+ if (!fs.existsSync(agentsDir)) {
61
+ throw new Error(`${targetDir} 未找到 .agents/,请先运行 init`);
62
+ }
63
+
64
+ const sourceDir = pkgRoot;
65
+ const sourceAgentsDir = path.join(sourceDir, '.agents');
66
+ const updated = [];
67
+ const skipped = [];
68
+
69
+ if (!fs.existsSync(sourceAgentsDir)) {
70
+ throw new Error(`包内 .agents/ 源目录不存在:${sourceAgentsDir}`);
71
+ }
72
+
73
+ // 1. 同步流程编排相关目录(不覆盖项目已定制的 rules/skills/roles)
74
+ const flowDirs = ['commands', 'flows', 'orchestration', 'registry', 'templates'];
75
+ for (const dir of flowDirs) {
76
+ const srcDir = path.join(sourceAgentsDir, dir);
77
+ const destDir = path.join(agentsDir, dir);
78
+ if (!fs.existsSync(srcDir)) continue;
79
+
80
+ try {
81
+ if (fs.existsSync(destDir)) {
82
+ fs.rmSync(destDir, { recursive: true, force: true });
83
+ }
84
+ copyDirSync(srcDir, destDir);
85
+ updated.push(`.agents/${dir}/`);
86
+ } catch (error) {
87
+ skipped.push({ path: `.agents/${dir}/`, reason: error.message });
88
+ }
89
+ }
90
+
91
+ // 2. 同步 openspec 模板
92
+ const sourceTemplates = path.join(sourceDir, 'openspec', 'schemas', 'expert-delivery', 'templates');
93
+ const targetTemplates = path.join(targetDir, 'openspec', 'schemas', 'expert-delivery', 'templates');
94
+ if (fs.existsSync(sourceTemplates) && fs.existsSync(targetTemplates)) {
95
+ try {
96
+ const templateFiles = fs.readdirSync(sourceTemplates);
97
+ for (const file of templateFiles) {
98
+ const srcFile = path.join(sourceTemplates, file);
99
+ const destFile = path.join(targetTemplates, file);
100
+ if (fs.statSync(srcFile).isFile()) {
101
+ fs.copyFileSync(srcFile, destFile);
102
+ }
103
+ }
104
+ updated.push('openspec/schemas/expert-delivery/templates/');
105
+ } catch (error) {
106
+ skipped.push({ path: 'openspec/schemas/expert-delivery/templates/', reason: error.message });
107
+ }
108
+ }
109
+
110
+ // 3. 同步 openspec/config.yaml.template
111
+ const sourceConfig = path.join(sourceDir, 'openspec', 'config.yaml.template');
112
+ const targetConfig = path.join(targetDir, 'openspec', 'config.yaml.template');
113
+ if (fs.existsSync(sourceConfig) && fs.existsSync(targetConfig)) {
114
+ fs.copyFileSync(sourceConfig, targetConfig);
115
+ updated.push('openspec/config.yaml.template');
116
+ }
117
+
118
+ // 4. 同步 IDE 命令文件
119
+ const ides = ['cursor', 'claude', 'qoder', 'trae', 'opencode'];
120
+ for (const ide of ides) {
121
+ const sourceIdeDir = path.join(sourceDir, `.${ide}`);
122
+ const targetIdeDir = path.join(targetDir, `.${ide}`);
123
+ if (!fs.existsSync(sourceIdeDir) || !fs.existsSync(targetIdeDir)) continue;
124
+
125
+ // 同步 commands
126
+ const srcCmdDir = path.join(sourceIdeDir, 'commands');
127
+ const destCmdDir = path.join(targetIdeDir, 'commands');
128
+ if (fs.existsSync(srcCmdDir)) {
129
+ try {
130
+ if (fs.existsSync(destCmdDir)) {
131
+ fs.rmSync(destCmdDir, { recursive: true, force: true });
132
+ }
133
+ copyDirSync(srcCmdDir, destCmdDir);
134
+ updated.push(`.${ide}/commands/`);
135
+ } catch (error) {
136
+ skipped.push({ path: `.${ide}/commands/`, reason: error.message });
137
+ }
138
+ }
139
+
140
+ // 同步 rules(如果是实际文件而非 symlink)
141
+ const srcRulesDir = path.join(sourceIdeDir, 'rules');
142
+ const destRulesDir = path.join(targetIdeDir, 'rules');
143
+ if (fs.existsSync(srcRulesDir) && !isSymlink(destRulesDir)) {
144
+ try {
145
+ if (fs.existsSync(destRulesDir)) {
146
+ fs.rmSync(destRulesDir, { recursive: true, force: true });
147
+ }
148
+ copyDirSync(srcRulesDir, destRulesDir);
149
+ updated.push(`.${ide}/rules/`);
150
+ } catch (error) {
151
+ skipped.push({ path: `.${ide}/rules/`, reason: error.message });
152
+ }
153
+ }
154
+
155
+ // 同步 skills
156
+ const srcSkillsDir = path.join(sourceIdeDir, 'skills');
157
+ const destSkillsDir = path.join(targetIdeDir, 'skills');
158
+ if (fs.existsSync(srcSkillsDir)) {
159
+ try {
160
+ if (fs.existsSync(destSkillsDir)) {
161
+ fs.rmSync(destSkillsDir, { recursive: true, force: true });
162
+ }
163
+ copyDirSync(srcSkillsDir, destSkillsDir);
164
+ updated.push(`.${ide}/skills/`);
165
+ } catch (error) {
166
+ skipped.push({ path: `.${ide}/skills/`, reason: error.message });
167
+ }
168
+ }
169
+ }
170
+
171
+ return { updated, skipped };
172
+ }
173
+
174
+ function isSymlink(p) {
175
+ try {
176
+ return fs.lstatSync(p).isSymbolicLink();
177
+ } catch (_) {
178
+ return false;
179
+ }
180
+ }
181
+
182
+ function main(argv = process.argv.slice(2)) {
183
+ const options = parseArgs(argv);
184
+ if (options.help) {
185
+ printUsage();
186
+ return 0;
187
+ }
188
+
189
+ const targetDir = path.resolve(process.cwd(), options.target);
190
+ const pkgRoot = path.join(__dirname, '..');
191
+ const result = refresh(targetDir, pkgRoot);
192
+
193
+ if (options.json) {
194
+ console.log(JSON.stringify(result, null, 2));
195
+ } else {
196
+ console.log('refresh 完成');
197
+ console.log(` 更新 ${result.updated.length} 项:`);
198
+ for (const item of result.updated) {
199
+ console.log(` ✓ ${item}`);
200
+ }
201
+ if (result.skipped.length > 0) {
202
+ console.log(` 跳过 ${result.skipped.length} 项:`);
203
+ for (const item of result.skipped) {
204
+ console.log(` ✗ ${item.path} (${item.reason})`);
205
+ }
206
+ }
207
+ }
208
+
209
+ return result.skipped.length > 0 ? 1 : 0;
210
+ }
211
+
212
+ module.exports = { main, parseArgs, refresh };
@@ -683,6 +683,17 @@ function normalizeSpecsArtifactPath(relPath) {
683
683
  return null;
684
684
  }
685
685
 
686
+ // 新格式: openspec/specs/{changeId}.md 直接返回
687
+ if (/openspec[/\\]specs[/\\][^/\\]+\.md$/.test(value)) {
688
+ return value.replace(/[\\/]+$/, '');
689
+ }
690
+
691
+ // 兼容旧目录格式: openspec/specs/{changeId} 或 openspec/specs/{changeId}/...
692
+ if (/openspec[/\\]specs[/\\][^/\\]+$/.test(value)) {
693
+ return value.replace(/[\\/]+$/, '');
694
+ }
695
+
696
+ // 旧格式兼容: 提取 openspec/specs 部分
686
697
  const normalized = value.replace(/[\\/]+$/, '');
687
698
  if (/[\\/]specs$/.test(normalized)) {
688
699
  return normalized;
@@ -702,7 +713,6 @@ function buildDefaultArtifacts(changeId, options = {}) {
702
713
  return {
703
714
  proposal: null,
704
715
  specs: null,
705
- design: null,
706
716
  tasks: null,
707
717
  bugfix: `${historyDir}/bugfix.md`,
708
718
  implementation_notes: `${historyDir}/implementation-notes.md`,
@@ -716,7 +726,6 @@ function buildDefaultArtifacts(changeId, options = {}) {
716
726
  return {
717
727
  proposal: null,
718
728
  specs: null,
719
- design: null,
720
729
  tasks: null,
721
730
  bugfix: null,
722
731
  implementation_notes: null,
@@ -730,7 +739,6 @@ function buildDefaultArtifacts(changeId, options = {}) {
730
739
  return {
731
740
  proposal: `${baseDir}/proposal.md`,
732
741
  specs: `${baseDir}/specs`,
733
- design: `${baseDir}/design.md`,
734
742
  tasks: `${baseDir}/tasks.md`,
735
743
  bugfix: null,
736
744
  implementation_notes: null,
@@ -743,14 +751,14 @@ function buildDefaultArtifacts(changeId, options = {}) {
743
751
  function mergeArtifacts(baseArtifacts, inferredArtifacts) {
744
752
  const proposal = inferredArtifacts?.proposal || baseArtifacts?.proposal || null;
745
753
  const specs = normalizeSpecsArtifactPath(inferredArtifacts?.specs || baseArtifacts?.specs || null);
746
- const design = inferredArtifacts?.design || baseArtifacts?.design || null;
747
754
  const tasks = inferredArtifacts?.tasks || baseArtifacts?.tasks || null;
748
755
  const bugfix = inferredArtifacts?.bugfix || baseArtifacts?.bugfix || null;
749
756
  const implementationNotes = inferredArtifacts?.implementation_notes || baseArtifacts?.implementation_notes || null;
750
757
  const checklist = inferredArtifacts?.checklist || baseArtifacts?.checklist || null;
751
758
  const iterations = inferredArtifacts?.iterations || baseArtifacts?.iterations || null;
759
+ const summary = inferredArtifacts?.summary || baseArtifacts?.summary || null;
752
760
  const primaryArtifacts = new Set(
753
- [proposal, specs, design, tasks, bugfix, implementationNotes, checklist, iterations]
761
+ [proposal, specs, tasks, bugfix, implementationNotes, checklist, iterations, summary]
754
762
  .map((item) => (typeof item === 'string' ? item.trim().replace(/[\\/]+$/, '') : null))
755
763
  .filter(Boolean),
756
764
  );
@@ -766,12 +774,12 @@ function mergeArtifacts(baseArtifacts, inferredArtifacts) {
766
774
  const merged = {
767
775
  proposal,
768
776
  specs,
769
- design,
770
777
  tasks,
771
778
  bugfix,
772
779
  implementation_notes: implementationNotes,
773
780
  checklist,
774
781
  iterations,
782
+ summary,
775
783
  additional: Array.from(new Set(additional.filter(Boolean))),
776
784
  };
777
785
 
@@ -786,12 +794,12 @@ function inferArtifacts(artifacts) {
786
794
  const normalized = {
787
795
  proposal: null,
788
796
  specs: null,
789
- design: null,
790
797
  tasks: null,
791
798
  bugfix: null,
792
799
  implementation_notes: null,
793
800
  checklist: null,
794
801
  iterations: null,
802
+ summary: null,
795
803
  additional: [],
796
804
  };
797
805
 
@@ -800,7 +808,7 @@ function inferArtifacts(artifacts) {
800
808
  }
801
809
 
802
810
  if (artifacts && typeof artifacts === 'object' && !Array.isArray(artifacts)) {
803
- const directKeys = ['proposal', 'specs', 'design', 'tasks', 'bugfix', 'implementation_notes', 'checklist', 'iterations'];
811
+ const directKeys = ['proposal', 'specs', 'tasks', 'bugfix', 'implementation_notes', 'checklist', 'iterations', 'summary'];
804
812
  for (const key of directKeys) {
805
813
  if (typeof artifacts[key] === 'string' && artifacts[key].trim()) {
806
814
  normalized[key] = key === 'specs'
@@ -839,10 +847,6 @@ function inferArtifacts(artifacts) {
839
847
  normalized.specs = normalizeSpecsArtifactPath(item);
840
848
  continue;
841
849
  }
842
- if (item.endsWith('/design.md')) {
843
- normalized.design = item;
844
- continue;
845
- }
846
850
  if (item.endsWith('/tasks.md')) {
847
851
  normalized.tasks = item;
848
852
  continue;
@@ -1134,9 +1138,9 @@ function assertRequiredOpenSpecArtifacts(targetDir, state, action, toRole) {
1134
1138
 
1135
1139
  let requiredArtifacts = [];
1136
1140
  if (action === 'handoff' && toRole === 'frontend-implementer') {
1137
- requiredArtifacts = ['proposal', 'specs', 'design', 'tasks'];
1141
+ requiredArtifacts = ['proposal', 'specs', 'tasks'];
1138
1142
  } else if (action === 'complete') {
1139
- requiredArtifacts = ['proposal', 'specs', 'design', 'tasks', 'checklist', 'iterations'];
1143
+ requiredArtifacts = ['proposal', 'specs', 'tasks', 'checklist', 'iterations'];
1140
1144
  }
1141
1145
 
1142
1146
  if (requiredArtifacts.length === 0) {
@@ -940,8 +940,8 @@ function advanceRunner(options) {
940
940
  };
941
941
  const autoRuntimeAction = buildAutoRuntimeAction(targetDir, recorded.execution.payload);
942
942
  if (autoRuntimeAction) {
943
- if (recorded.execution.archive_result?.archived_artifacts) {
944
- autoRuntimeAction.artifacts = recorded.execution.archive_result.archived_artifacts;
943
+ if (recorded.execution.archive_result?.specs_stored_at) {
944
+ autoRuntimeAction.artifacts = { specs: recorded.execution.archive_result.specs_stored_at };
945
945
  autoRuntimeAction.skip_artifact_check = true;
946
946
  }
947
947
  recorded.runtime_action = {