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

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 (47) 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/runtime-state-handoff-spec.md +93 -1
  11. package/.agents/orchestration/task-orchestrator-run-plan-template.md +85 -4
  12. package/.agents/registry/flows.json +5 -5
  13. package/.agents/registry/roles.json +13 -27
  14. package/.agents/roles/common/backend-implementer.md +3 -4
  15. package/.agents/roles/common/code-guardian.md +2 -3
  16. package/.agents/roles/common/frontend-implementer.md +3 -4
  17. package/.agents/roles/common/requirement-analyst.md +32 -18
  18. package/.agents/roles/common/tooling-implementer.md +3 -4
  19. package/.agents/rules/common/12-Superpowers/346/211/247/350/241/214/350/247/204/350/214/203.md +1 -1
  20. 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
  21. package/.agents/rules/profiles/react/01-/351/241/271/347/233/256/346/246/202/350/277/260.md +1 -1
  22. package/.agents/rules/profiles/react/05-API/350/247/204/350/214/203.md +2 -2
  23. package/.agents/rules/profiles/vue/05-API/350/247/204/350/214/203.md +2 -2
  24. package/.agents/skills/common/archive-change/SKILL.md +0 -1
  25. package/.agents/skills/common/create-proposal/SKILL.md +7 -8
  26. package/.agents/skills/common/execute-task/SKILL.md +3 -3
  27. package/.agents/skills/profiles/react/create-api/SKILL.md +2 -2
  28. package/.agents/skills/profiles/vue/create-api/SKILL.md +2 -2
  29. package/bin/archive-change.js +37 -259
  30. package/bin/cli.js +5 -0
  31. package/bin/demo-runtime-smoke.js +11 -74
  32. package/bin/execution-semantics.js +4 -17
  33. package/bin/expert-executor.js +4 -7
  34. package/bin/protocol-workflow.js +7 -2
  35. package/bin/refresh-command.js +185 -0
  36. package/bin/runtime-state.js +18 -14
  37. package/bin/task-orchestrator-runner.js +2 -2
  38. package/internal/ai-protocol-workflow.js +80 -119
  39. package/openspec/config.yaml.template +6 -15
  40. package/openspec/schemas/expert-delivery/schema.yaml +0 -11
  41. package/openspec/schemas/expert-delivery/templates/checklist.md +12 -30
  42. package/openspec/schemas/expert-delivery/templates/iterations.md +6 -22
  43. package/openspec/schemas/expert-delivery/templates/proposal.md +7 -35
  44. package/openspec/schemas/expert-delivery/templates/tasks.md +7 -20
  45. package/package.json +1 -1
  46. package/scripts/post-publish-auto-fix-check.js +5 -61
  47. package/openspec/schemas/expert-delivery/templates/design.md +0 -61
@@ -118,132 +118,6 @@ function countTaskCompletion(tasksPath) {
118
118
  return { completed, total };
119
119
  }
120
120
 
121
- function generateArchiveSummary(archivedPath, changeId) {
122
- const proposalPath = path.join(archivedPath, 'proposal.md');
123
- const designPath = path.join(archivedPath, 'design.md');
124
- const tasksPath = path.join(archivedPath, 'tasks.md');
125
- const checklistPath = path.join(archivedPath, 'checklist.md');
126
- const specMdPath = path.join(archivedPath, 'spec.md');
127
-
128
- // 提取标题
129
- let title = changeId;
130
- if (fs.existsSync(proposalPath)) {
131
- const content = fs.readFileSync(proposalPath, 'utf8');
132
- const titleMatch = content.match(/^#\s+(.+)$/m);
133
- if (titleMatch) title = titleMatch[1].trim();
134
- }
135
-
136
- // 提取业务目标(从 proposal)
137
- let businessGoal = '';
138
- if (fs.existsSync(proposalPath)) {
139
- const content = fs.readFileSync(proposalPath, 'utf8');
140
- // 找“业务目标”或“目标”部分
141
- const goalMatch = content.match(/(?:业务目标|目标|## Goal)[^#]*?([\s\S]*?)(?=#|$)/i);
142
- if (goalMatch) businessGoal = goalMatch[1].trim().slice(0, 300);
143
- }
144
-
145
- // 提取任务完成情况
146
- const taskStats = countTaskCompletion(tasksPath);
147
-
148
- // 提取关键设计决策(从 design)
149
- let designDecisions = '';
150
- if (fs.existsSync(designPath)) {
151
- const content = fs.readFileSync(designPath, 'utf8');
152
- const decisionMatch = content.match(/(?:设计决策|关键决策|技术方案|## Decision)[^#]*?([\s\S]*?)(?=##|$)/i);
153
- if (decisionMatch) designDecisions = decisionMatch[1].trim().slice(0, 200);
154
- }
155
-
156
- // 提取验收结论(从 checklist)
157
- let acceptance = '';
158
- if (fs.existsSync(checklistPath)) {
159
- const content = fs.readFileSync(checklistPath, 'utf8');
160
- // 先匹配 ## 结论 / ## Conclusion 二级标题,再回退匹配 # 验收 / # 结论 一级标题
161
- const conclusionMatch = content.match(/## (?:结论|Conclusion)[^#]*?([\s\S]*?)(?=##?\s|$)/i)
162
- || content.match(/# (?:验收|结论)[^#]*?([\s\S]*?)(?=##?\s|$)/i);
163
- if (conclusionMatch) acceptance = conclusionMatch[1].trim().slice(0, 200);
164
- }
165
-
166
- // 提取规格(从 spec.md 或 specs/ 目录)
167
- let specSummary = '';
168
- if (fs.existsSync(specMdPath)) {
169
- specSummary = fs.readFileSync(specMdPath, 'utf8').trim().slice(0, 300);
170
- }
171
-
172
- const now = new Date().toISOString().slice(0, 10);
173
- const summary = [
174
- `# ${title}`,
175
- '',
176
- `> 归档日期:${now} | 任务完成:${taskStats.completed}/${taskStats.total}`,
177
- '',
178
- businessGoal ? `## 业务目标\n\n${businessGoal}\n` : '',
179
- specSummary ? `## 需求规格\n\n${specSummary}\n` : '',
180
- designDecisions ? `## 设计决策\n\n${designDecisions}\n` : '',
181
- acceptance ? `## 验收结论\n\n${acceptance}\n` : '',
182
- ].filter(Boolean).join('\n');
183
-
184
- const summaryPath = path.join(archivedPath, 'summary.md');
185
- fs.writeFileSync(summaryPath, summary, 'utf8');
186
- return summaryPath;
187
- }
188
-
189
- function cleanupProcessFiles(archivedPath) {
190
- const processFiles = ['proposal.md', 'design.md', 'tasks.md', 'checklist.md', 'iterations.md', 'spec.md'];
191
- const removed = [];
192
- for (const file of processFiles) {
193
- const filePath = path.join(archivedPath, file);
194
- if (fs.existsSync(filePath)) {
195
- fs.unlinkSync(filePath);
196
- removed.push(file);
197
- }
198
- }
199
- return removed;
200
- }
201
-
202
- function normalizeMarkdown(content) {
203
- return String(content || '').replace(/\r\n/g, '\n').trim();
204
- }
205
-
206
- function normalizeSpecsArtifactPath(relPath) {
207
- const value = String(relPath || '').trim();
208
- if (!value) {
209
- return null;
210
- }
211
-
212
- const normalized = value.replace(/[\\/]+$/, '');
213
- if (/[\\/]specs$/.test(normalized)) {
214
- return normalized;
215
- }
216
-
217
- const match = normalized.match(/^(.*[\\/]specs)(?:[\\/].+)?$/);
218
- return match ? match[1] : normalized;
219
- }
220
-
221
- function mergeSpecFile(targetPath, incomingContent, changeId) {
222
- if (!fs.existsSync(targetPath)) {
223
- fs.writeFileSync(targetPath, `${incomingContent.trim()}\n`, 'utf8');
224
- return 'added';
225
- }
226
-
227
- const existingContent = fs.readFileSync(targetPath, 'utf8');
228
- const normalizedExisting = normalizeMarkdown(existingContent);
229
- const normalizedIncoming = normalizeMarkdown(incomingContent);
230
-
231
- if (!normalizedIncoming || normalizedExisting === normalizedIncoming || normalizedExisting.includes(normalizedIncoming)) {
232
- return 'unchanged';
233
- }
234
-
235
- const merged = [
236
- existingContent.trimEnd(),
237
- '',
238
- `<!-- merged from change: ${changeId} -->`,
239
- '',
240
- normalizedIncoming,
241
- '',
242
- ].join('\n');
243
- fs.writeFileSync(targetPath, merged, 'utf8');
244
- return 'updated';
245
- }
246
-
247
121
  function resolveChangeId(targetDir, explicitChangeId) {
248
122
  if (explicitChangeId) {
249
123
  return explicitChangeId;
@@ -253,68 +127,28 @@ function resolveChangeId(targetDir, explicitChangeId) {
253
127
  return currentRun?.task?.change_id || currentRun?.anchor?.task?.change_id || null;
254
128
  }
255
129
 
256
- function buildArchiveDestination(baseArchiveDir, changeId, now = new Date()) {
257
- const day = now.toISOString().slice(0, 10);
258
- const baseName = `${day}-${changeId}`;
259
- let candidate = path.join(baseArchiveDir, baseName);
260
- let suffix = 1;
261
-
262
- while (fs.existsSync(candidate)) {
263
- suffix += 1;
264
- candidate = path.join(baseArchiveDir, `${baseName}-${suffix}`);
265
- }
266
-
267
- return candidate;
268
- }
269
-
270
130
  function buildAlreadyArchivedResult(targetDir, changeId) {
271
- const currentRun = readJsonIfExists(path.join(targetDir, '.ai-spec', 'current-run.json'));
272
- const proposalPath = currentRun?.artifacts?.proposal
273
- ? path.join(targetDir, currentRun.artifacts.proposal)
274
- : null;
275
- const specsPath = currentRun?.artifacts?.specs
276
- ? path.join(targetDir, normalizeSpecsArtifactPath(currentRun.artifacts.specs))
277
- : null;
278
- const designPath = currentRun?.artifacts?.design
279
- ? path.join(targetDir, currentRun.artifacts.design)
280
- : null;
281
-
282
- if (!proposalPath || !specsPath || !designPath) {
283
- return null;
284
- }
285
- if (!proposalPath.includes(`${path.sep}openspec${path.sep}changes${path.sep}archive${path.sep}`)) {
286
- return null;
287
- }
288
- if (!fs.existsSync(proposalPath) || !fs.existsSync(specsPath) || !fs.existsSync(designPath)) {
131
+ // 检查 specs 是否已存储到 openspec/specs/{changeId}.md
132
+ const specFile = path.join(targetDir, 'openspec', 'specs', `${changeId}.md`);
133
+ if (!fs.existsSync(specFile)) {
289
134
  return null;
290
135
  }
136
+ const currentRun = readJsonIfExists(path.join(targetDir, '.ai-spec', 'current-run.json'));
291
137
  if ((currentRun?.task?.change_id || currentRun?.anchor?.task?.change_id || null) !== changeId) {
292
138
  return null;
293
139
  }
294
140
 
295
- const archivedDir = path.dirname(proposalPath);
296
- const archivedRel = path.relative(targetDir, archivedDir);
297
141
  return {
298
142
  kind: 'archive-change-result',
299
143
  status: 'success',
300
144
  target: targetDir,
301
145
  change_id: changeId,
302
- archived_to: archivedRel,
303
- archived_artifacts: {
304
- proposal: path.relative(targetDir, proposalPath),
305
- specs: path.relative(targetDir, specsPath),
306
- design: path.relative(targetDir, designPath),
307
- tasks: currentRun?.artifacts?.tasks || null,
308
- checklist: currentRun?.artifacts?.checklist || null,
309
- iterations: currentRun?.artifacts?.iterations || null,
310
- },
311
- merged: {
312
- added_domains: [],
146
+ specs_stored_at: path.relative(targetDir, specFile),
147
+ archived: {
313
148
  added_specs: [],
314
- updated_specs: [],
315
- unchanged_specs: [],
149
+ updated_specs: [path.relative(targetDir, specFile)],
316
150
  },
317
- task_completion: countTaskCompletion(path.join(archivedDir, 'tasks.md')),
151
+ task_completion: { completed: 0, total: 0 },
318
152
  already_archived: true,
319
153
  };
320
154
  }
@@ -360,8 +194,10 @@ function completeRunAfterArchive(targetDir, archiveResult) {
360
194
  fromRole: currentRun.current_role || 'archive-change',
361
195
  toRole: 'archive-change',
362
196
  status: 'success',
363
- message: '归档完成,增量规范已合并并已写入归档目录。',
364
- artifactsData: archiveResult.archived_artifacts,
197
+ message: '归档完成,规范已存储到 openspec/specs/。',
198
+ artifactsData: {
199
+ specs: archiveResult.specs_stored_at,
200
+ },
365
201
  skipArtifactCheck: true,
366
202
  clearPendingGate: true,
367
203
  });
@@ -373,25 +209,6 @@ function completeRunAfterArchive(targetDir, archiveResult) {
373
209
  };
374
210
  }
375
211
 
376
- function buildArchivedAdditionalArtifacts(currentArtifacts, changeId, archivedRelBase) {
377
- const currentAdditional = Array.isArray(currentArtifacts?.additional) ? currentArtifacts.additional : [];
378
- if (currentAdditional.length === 0) {
379
- return [];
380
- }
381
-
382
- const activeBase = `openspec/changes/${changeId}`;
383
- return currentAdditional
384
- .map((item) => String(item || '').trim())
385
- .filter(Boolean)
386
- .map((item) => item.replace(/[\\/]+$/, ''))
387
- .map((item) => {
388
- if (item === activeBase || item.startsWith(`${activeBase}/`)) {
389
- return item.replace(activeBase, archivedRelBase);
390
- }
391
- return item;
392
- });
393
- }
394
-
395
212
  function archiveChange(options = {}) {
396
213
  const targetDir = path.resolve(options.target || '.');
397
214
  const changeId = resolveChangeId(targetDir, options.changeId);
@@ -413,7 +230,6 @@ function archiveChange(options = {}) {
413
230
 
414
231
  const proposalPath = path.join(changeDir, 'proposal.md');
415
232
  const tasksPath = path.join(changeDir, 'tasks.md');
416
- const designPath = path.join(changeDir, 'design.md');
417
233
  const checklistPath = path.join(changeDir, 'checklist.md');
418
234
  const iterationsPath = path.join(changeDir, 'iterations.md');
419
235
  const specsDir = path.join(changeDir, 'specs');
@@ -424,63 +240,37 @@ function archiveChange(options = {}) {
424
240
  if (!fs.existsSync(tasksPath)) {
425
241
  throw new Error(`tasks.md is missing for change ${changeId}`);
426
242
  }
427
- if (!fs.existsSync(designPath)) {
428
- throw new Error(`design.md is missing for change ${changeId}`);
429
- }
430
243
  if (!fs.existsSync(specsDir)) {
431
244
  throw new Error(`specs/ is missing for change ${changeId}`);
432
245
  }
433
246
 
434
- const targetSpecsDir = path.join(targetDir, 'openspec', 'specs');
435
- ensureDir(targetSpecsDir);
247
+ // 先统计任务完成情况(在清理之前)
248
+ const taskCompletionStats = countTaskCompletion(path.join(changeDir, 'tasks.md'));
249
+
250
+ // 合并所有 spec 文件为单个文件
251
+ const targetSpecFile = path.join(targetDir, 'openspec', 'specs', `${changeId}.md`);
252
+ ensureDir(path.dirname(targetSpecFile));
253
+
254
+ const sourceSpecFiles = listFilesRecursive(specsDir);
255
+ const specContents = sourceSpecFiles.map((f) => fs.readFileSync(f, 'utf8').trim()).filter(Boolean);
256
+ const mergedContent = specContents.join('\n\n---\n\n');
436
257
 
437
- const merged = {
438
- added_domains: [],
258
+ const archived = {
439
259
  added_specs: [],
440
260
  updated_specs: [],
441
- unchanged_specs: [],
442
261
  };
443
- const seenDomains = new Set();
444
- const sourceSpecFiles = listFilesRecursive(specsDir);
445
- for (const sourceFile of sourceSpecFiles) {
446
- const relFromSpecs = path.relative(specsDir, sourceFile);
447
- const domain = relFromSpecs.split(path.sep)[0] || null;
448
- const targetFile = path.join(targetSpecsDir, relFromSpecs);
449
- const targetDomainDir = path.dirname(targetFile);
450
- const domainRel = domain ? path.join('openspec', 'specs', domain) : null;
451
-
452
- if (domainRel && !fs.existsSync(targetDomainDir) && !seenDomains.has(domainRel)) {
453
- ensureDir(targetDomainDir);
454
- merged.added_domains.push(domainRel);
455
- seenDomains.add(domainRel);
456
- } else {
457
- ensureDir(targetDomainDir);
458
- }
459
262
 
460
- const mergeStatus = mergeSpecFile(targetFile, fs.readFileSync(sourceFile, 'utf8'), changeId);
461
- const relTargetFile = path.relative(targetDir, targetFile);
462
- if (mergeStatus === 'added') {
463
- merged.added_specs.push(relTargetFile);
464
- } else if (mergeStatus === 'updated') {
465
- merged.updated_specs.push(relTargetFile);
466
- } else {
467
- merged.unchanged_specs.push(relTargetFile);
468
- }
263
+ if (fs.existsSync(targetSpecFile)) {
264
+ fs.writeFileSync(targetSpecFile, mergedContent, 'utf8');
265
+ archived.updated_specs.push(path.relative(targetDir, targetSpecFile));
266
+ } else {
267
+ fs.writeFileSync(targetSpecFile, mergedContent, 'utf8');
268
+ archived.added_specs.push(path.relative(targetDir, targetSpecFile));
469
269
  }
470
270
 
471
- const archiveRoot = path.join(targetDir, 'openspec', 'changes', 'archive');
472
- ensureDir(archiveRoot);
473
- const archivedPath = buildArchiveDestination(archiveRoot, changeId, options.now || new Date());
474
- fs.renameSync(changeDir, archivedPath);
271
+ // 清理变更目录(不保留归档)
272
+ fs.rmSync(changeDir, { recursive: true, force: true });
475
273
 
476
- // 先统计任务完成情况(在清理之前)
477
- const taskCompletionStats = countTaskCompletion(path.join(archivedPath, 'tasks.md'));
478
-
479
- // 生成精简 summary 并清理过程文件
480
- const summaryPath = generateArchiveSummary(archivedPath, changeId);
481
- const removedFiles = cleanupProcessFiles(archivedPath);
482
-
483
- const archivedRelBase = path.relative(targetDir, archivedPath);
484
274
  const currentRun = readJsonIfExists(path.join(targetDir, '.ai-spec', 'current-run.json'));
485
275
 
486
276
  const result = {
@@ -488,17 +278,8 @@ function archiveChange(options = {}) {
488
278
  status: 'success',
489
279
  target: targetDir,
490
280
  change_id: changeId,
491
- archived_to: archivedRelBase,
492
- summary: path.relative(targetDir, summaryPath),
493
- removed_process_files: removedFiles,
494
- archived_artifacts: {
495
- summary: path.relative(targetDir, summaryPath),
496
- specs: fs.existsSync(path.join(archivedPath, 'specs'))
497
- ? path.relative(targetDir, path.join(archivedPath, 'specs'))
498
- : null,
499
- additional: buildArchivedAdditionalArtifacts(currentRun?.artifacts || null, changeId, archivedRelBase),
500
- },
501
- merged,
281
+ specs_stored_at: path.relative(targetDir, targetSpecFile),
282
+ archived,
502
283
  task_completion: taskCompletionStats,
503
284
  };
504
285
 
@@ -515,13 +296,10 @@ function printPretty(result) {
515
296
  console.log('archive-change');
516
297
  console.log(` target: ${result.target}`);
517
298
  console.log(` change_id: ${result.change_id}`);
518
- console.log(` archived_to: ${result.archived_to}`);
519
- console.log(` summary: ${result.summary}`);
520
- console.log(` cleaned: ${result.removed_process_files.join(', ') || '(none)'}`);
521
- console.log(` added_domains: ${result.merged.added_domains.length}`);
522
- console.log(` added_specs: ${result.merged.added_specs.length}`);
523
- console.log(` updated_specs: ${result.merged.updated_specs.length}`);
524
- console.log(` unchanged_specs: ${result.merged.unchanged_specs.length}`);
299
+ console.log(` specs_stored_at: ${result.specs_stored_at}`);
300
+ console.log(` added_specs: ${result.archived.added_specs.length}`);
301
+ console.log(` updated_specs: ${result.archived.updated_specs.length}`);
302
+ console.log(` task_completion: ${result.task_completion.completed}/${result.task_completion.total}`);
525
303
  if (result.runtime_transition?.state?.status) {
526
304
  console.log(` run_status: ${result.runtime_transition.state.status}`);
527
305
  }
package/bin/cli.js CHANGED
@@ -154,6 +154,11 @@ function shouldUseIntegrityCommand(args, cwd) {
154
154
  process.exit(await repairCommand.main(args.slice(1)));
155
155
  }
156
156
 
157
+ if (args[0] === 'refresh') {
158
+ const refreshCommand = require('./refresh-command');
159
+ process.exit(refreshCommand.main(args.slice(1)));
160
+ }
161
+
157
162
  if (args[0] === 'report') {
158
163
  const reportCommand = require('./report-command');
159
164
  process.exit(await reportCommand.main(args.slice(1)));
@@ -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);