@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.
- package/.agents/commands/common/spec-continue.md +1 -1
- package/.agents/commands/common/spec-start-review.md +1 -1
- package/.agents/commands/common/spec-start.md +1 -1
- package/.agents/commands/common/spec-update.md +2 -2
- package/.agents/commands/cursor/opsx-apply.md +0 -1
- package/.agents/commands/cursor/opsx-propose.md +0 -1
- package/.agents/flows/RUN_OUTPUT.md +0 -2
- package/.agents/flows/common/prd-to-delivery.md +0 -3
- package/.agents/flows/common/requirement-to-observability.md +0 -2
- package/.agents/orchestration/task-orchestrator-run-plan-template.md +2 -2
- package/.agents/registry/flows.json +5 -5
- package/.agents/registry/roles.json +13 -27
- package/.agents/roles/common/backend-implementer.md +3 -4
- package/.agents/roles/common/code-guardian.md +2 -3
- package/.agents/roles/common/frontend-implementer.md +3 -4
- package/.agents/roles/common/requirement-analyst.md +7 -18
- package/.agents/roles/common/tooling-implementer.md +3 -4
- package/.agents/rules/common/12-Superpowers/346/211/247/350/241/214/350/247/204/350/214/203.md +1 -1
- 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
- package/.agents/rules/profiles/react/01-/351/241/271/347/233/256/346/246/202/350/277/260.md +1 -1
- package/.agents/rules/profiles/react/05-API/350/247/204/350/214/203.md +2 -2
- package/.agents/rules/profiles/vue/05-API/350/247/204/350/214/203.md +2 -2
- package/.agents/skills/common/archive-change/SKILL.md +0 -1
- package/.agents/skills/common/create-proposal/SKILL.md +7 -8
- package/.agents/skills/common/execute-task/SKILL.md +3 -3
- package/.agents/skills/profiles/react/create-api/SKILL.md +2 -2
- package/.agents/skills/profiles/vue/create-api/SKILL.md +2 -2
- package/bin/archive-change.js +37 -259
- package/bin/cli.js +5 -0
- package/bin/demo-runtime-smoke.js +11 -74
- package/bin/execution-semantics.js +4 -17
- package/bin/expert-executor.js +4 -7
- package/bin/protocol-workflow.js +1 -1
- package/bin/refresh-command.js +212 -0
- package/bin/runtime-state.js +18 -14
- package/bin/task-orchestrator-runner.js +2 -2
- package/internal/ai-protocol-workflow.js +80 -119
- package/openspec/config.yaml.template +6 -15
- package/openspec/schemas/expert-delivery/schema.yaml +0 -11
- package/openspec/schemas/expert-delivery/templates/checklist.md +12 -30
- package/openspec/schemas/expert-delivery/templates/iterations.md +6 -22
- package/openspec/schemas/expert-delivery/templates/proposal.md +7 -35
- package/openspec/schemas/expert-delivery/templates/tasks.md +7 -20
- package/package.json +1 -1
- package/scripts/post-publish-auto-fix-check.js +5 -61
- 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、
|
|
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
|
-
'- 输出结构化的
|
|
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
|
|
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
|
|
361
|
+
'- [ ] 依赖或前置条件:requirement-analyst 已产出 proposal、specs',
|
|
424
362
|
'',
|
|
425
363
|
'### 子任务 2',
|
|
426
364
|
'- [ ] 目标:补齐路由模块和 mock 数据文件',
|
|
427
|
-
'- [ ] 输入:
|
|
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、
|
|
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
|
|
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、
|
|
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、
|
|
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
|
-
'- 建议缓解方式:在业务项目中补齐
|
|
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/
|
|
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', '
|
|
116
|
-
'code-guardian': ['proposal', 'specs', '
|
|
117
|
-
'archive-change': ['proposal', 'specs', '
|
|
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', '
|
|
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
|
}
|
package/bin/expert-executor.js
CHANGED
|
@@ -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?.
|
|
822
|
-
runtimeActionPayload.artifacts = archive_result.
|
|
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?.
|
|
879
|
-
runtimeActionPayload.artifacts = archive_result.
|
|
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);
|
package/bin/protocol-workflow.js
CHANGED
|
@@ -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(`
|
|
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 };
|
package/bin/runtime-state.js
CHANGED
|
@@ -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,
|
|
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', '
|
|
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', '
|
|
1141
|
+
requiredArtifacts = ['proposal', 'specs', 'tasks'];
|
|
1138
1142
|
} else if (action === 'complete') {
|
|
1139
|
-
requiredArtifacts = ['proposal', 'specs', '
|
|
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?.
|
|
944
|
-
autoRuntimeAction.artifacts = recorded.execution.archive_result.
|
|
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 = {
|