@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
@@ -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:');
@@ -431,7 +431,12 @@ async function main(mode, argv) {
431
431
  }
432
432
 
433
433
  if (options.json) {
434
- process.stdout.write(`${JSON.stringify(result, null, 2)}\n`);
434
+ const output = `${JSON.stringify(result, null, 2)}\n`;
435
+ // 确保 stdout 完整写入后再退出,避免 spawnSync 截断
436
+ process.stdout.write(output);
437
+ if (process.stdout.writableLength > 0) {
438
+ await new Promise((resolve) => process.stdout.once('drain', resolve));
439
+ }
435
440
  } else {
436
441
  if (mode === 'advance') {
437
442
  printStep(result);
@@ -0,0 +1,185 @@
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
+ // IDE 的 commands/rules/skills 可能是指向 .agents/ 的指针文件(非目录),
120
+ // 这些指针文件不需要替换,因为 .agents/ 已经在步骤 1 中同步过了。
121
+ const ides = ['cursor', 'claude', 'qoder', 'trae', 'opencode'];
122
+ for (const ide of ides) {
123
+ const sourceIdeDir = path.join(sourceDir, `.${ide}`);
124
+ const targetIdeDir = path.join(targetDir, `.${ide}`);
125
+ if (!fs.existsSync(sourceIdeDir) || !fs.existsSync(targetIdeDir)) continue;
126
+
127
+ // 同步 IDE 特有配置文件(如 mcp.json)
128
+ try {
129
+ const sourceEntries = fs.readdirSync(sourceIdeDir);
130
+ for (const entry of sourceEntries) {
131
+ const srcPath = path.join(sourceIdeDir, entry);
132
+ const destPath = path.join(targetIdeDir, entry);
133
+ // 只同步文件,不同步目录(commands/rules/skills 目录由 .agents/ 指针管理)
134
+ if (fs.statSync(srcPath).isFile()) {
135
+ fs.copyFileSync(srcPath, destPath);
136
+ updated.push(`.${ide}/${entry}`);
137
+ }
138
+ }
139
+ } catch (error) {
140
+ skipped.push({ path: `.${ide}/`, reason: error.message });
141
+ }
142
+ }
143
+
144
+ return { updated, skipped };
145
+ }
146
+
147
+ function isSymlink(p) {
148
+ try {
149
+ return fs.lstatSync(p).isSymbolicLink();
150
+ } catch (_) {
151
+ return false;
152
+ }
153
+ }
154
+
155
+ function main(argv = process.argv.slice(2)) {
156
+ const options = parseArgs(argv);
157
+ if (options.help) {
158
+ printUsage();
159
+ return 0;
160
+ }
161
+
162
+ const targetDir = path.resolve(process.cwd(), options.target);
163
+ const pkgRoot = path.join(__dirname, '..');
164
+ const result = refresh(targetDir, pkgRoot);
165
+
166
+ if (options.json) {
167
+ console.log(JSON.stringify(result, null, 2));
168
+ } else {
169
+ console.log('refresh 完成');
170
+ console.log(` 更新 ${result.updated.length} 项:`);
171
+ for (const item of result.updated) {
172
+ console.log(` ✓ ${item}`);
173
+ }
174
+ if (result.skipped.length > 0) {
175
+ console.log(` 跳过 ${result.skipped.length} 项:`);
176
+ for (const item of result.skipped) {
177
+ console.log(` ✗ ${item.path} (${item.reason})`);
178
+ }
179
+ }
180
+ }
181
+
182
+ return result.skipped.length > 0 ? 1 : 0;
183
+ }
184
+
185
+ 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 = {