@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/archive-change.js
CHANGED
|
@@ -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
|
-
|
|
272
|
-
const
|
|
273
|
-
|
|
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
|
-
|
|
303
|
-
|
|
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:
|
|
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:
|
|
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
|
-
|
|
435
|
-
|
|
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
|
|
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
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
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
|
-
|
|
472
|
-
|
|
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
|
-
|
|
492
|
-
|
|
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(`
|
|
519
|
-
console.log(`
|
|
520
|
-
console.log(`
|
|
521
|
-
console.log(`
|
|
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、
|
|
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);
|