@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.
- 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/runtime-state-handoff-spec.md +93 -1
- package/.agents/orchestration/task-orchestrator-run-plan-template.md +85 -4
- 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 +32 -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 +7 -2
- package/bin/refresh-command.js +185 -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
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:');
|
|
@@ -431,7 +431,12 @@ async function main(mode, argv) {
|
|
|
431
431
|
}
|
|
432
432
|
|
|
433
433
|
if (options.json) {
|
|
434
|
-
|
|
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 };
|
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 = {
|