@zhuan-ai/zhuanspec 2.12.7 → 2.12.12
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/dist/core/hooks/init.js
CHANGED
|
@@ -32,9 +32,9 @@ async function checkVersionUpdate() {
|
|
|
32
32
|
const pkgContent = JSON.parse(await fs.promises.readFile(pkgPath, 'utf-8'));
|
|
33
33
|
current = pkgContent.version;
|
|
34
34
|
}
|
|
35
|
-
//
|
|
36
|
-
//
|
|
37
|
-
const { stdout } = await execAsync('npm view @zhuan-ai/zhuanspec version
|
|
35
|
+
// 直接使用用户本地 npm 配置(registry / 代理),避免硬编码 registry 在
|
|
36
|
+
// 无代理环境下访问官方源长时间阻塞并被 5s 超时静默吞掉,导致版本 banner 消失。
|
|
37
|
+
const { stdout } = await execAsync('npm view @zhuan-ai/zhuanspec version', { timeout: 5000 });
|
|
38
38
|
const latest = stdout.trim();
|
|
39
39
|
if (!latest || !current)
|
|
40
40
|
return null;
|
|
@@ -128,83 +128,11 @@ async function runInitHook(_options) {
|
|
|
128
128
|
systemMessage: 'ℹ ZhuanSpec not initialized in this project. Run `zhuanspec init` to start.',
|
|
129
129
|
};
|
|
130
130
|
}
|
|
131
|
-
//
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
projectContext = await FileSystemUtils.readFile(projectMdPath);
|
|
137
|
-
}
|
|
138
|
-
catch {
|
|
139
|
-
// Ignore read errors
|
|
140
|
-
}
|
|
141
|
-
}
|
|
142
|
-
// Load knowledge files if exist
|
|
143
|
-
const knowledgeDir = path.join(zhuanspecDir, 'knowledge');
|
|
144
|
-
const knowledgeFiles = [];
|
|
145
|
-
if (await FileSystemUtils.directoryExists(knowledgeDir)) {
|
|
146
|
-
try {
|
|
147
|
-
// Load index.md
|
|
148
|
-
const indexPath = path.join(knowledgeDir, 'index.md');
|
|
149
|
-
if (await FileSystemUtils.fileExists(indexPath)) {
|
|
150
|
-
const content = await FileSystemUtils.readFile(indexPath);
|
|
151
|
-
knowledgeFiles.push(`### index.md\n${content.substring(0, 500)}...`);
|
|
152
|
-
}
|
|
153
|
-
// Load from troubleshooting subdirectory
|
|
154
|
-
const troubleshootingDir = path.join(knowledgeDir, 'troubleshooting');
|
|
155
|
-
if (await FileSystemUtils.directoryExists(troubleshootingDir)) {
|
|
156
|
-
const entries = await fs.promises.readdir(troubleshootingDir);
|
|
157
|
-
for (const entry of entries) {
|
|
158
|
-
if (entry.endsWith('.md')) {
|
|
159
|
-
const filePath = path.join(troubleshootingDir, entry);
|
|
160
|
-
const content = await FileSystemUtils.readFile(filePath);
|
|
161
|
-
knowledgeFiles.push(`### troubleshooting/${entry}\n${content.substring(0, 500)}...`);
|
|
162
|
-
}
|
|
163
|
-
}
|
|
164
|
-
}
|
|
165
|
-
// Load from best-practices subdirectory
|
|
166
|
-
const bestPracticesDir = path.join(knowledgeDir, 'best-practices');
|
|
167
|
-
if (await FileSystemUtils.directoryExists(bestPracticesDir)) {
|
|
168
|
-
const entries = await fs.promises.readdir(bestPracticesDir);
|
|
169
|
-
for (const entry of entries) {
|
|
170
|
-
if (entry.endsWith('.md')) {
|
|
171
|
-
const filePath = path.join(bestPracticesDir, entry);
|
|
172
|
-
const content = await FileSystemUtils.readFile(filePath);
|
|
173
|
-
knowledgeFiles.push(`### best-practices/${entry}\n${content.substring(0, 500)}...`);
|
|
174
|
-
}
|
|
175
|
-
}
|
|
176
|
-
}
|
|
177
|
-
// Load from implicit-conventions subdirectory
|
|
178
|
-
const implicitConventionsDir = path.join(knowledgeDir, 'implicit-conventions');
|
|
179
|
-
if (await FileSystemUtils.directoryExists(implicitConventionsDir)) {
|
|
180
|
-
const entries = await fs.promises.readdir(implicitConventionsDir);
|
|
181
|
-
for (const entry of entries) {
|
|
182
|
-
if (entry.endsWith('.md')) {
|
|
183
|
-
const filePath = path.join(implicitConventionsDir, entry);
|
|
184
|
-
const content = await FileSystemUtils.readFile(filePath);
|
|
185
|
-
knowledgeFiles.push(`### implicit-conventions/${entry}\n${content.substring(0, 500)}...`);
|
|
186
|
-
}
|
|
187
|
-
}
|
|
188
|
-
}
|
|
189
|
-
}
|
|
190
|
-
catch {
|
|
191
|
-
// Ignore read errors
|
|
192
|
-
}
|
|
193
|
-
}
|
|
194
|
-
// Check for .claude/rules files (project-level)
|
|
195
|
-
const claudeRulesDir = path.join(cwd, '.claude', 'rules');
|
|
196
|
-
let rulesFiles = [];
|
|
197
|
-
const rulesContents = [];
|
|
198
|
-
try {
|
|
199
|
-
if (await FileSystemUtils.directoryExists(claudeRulesDir)) {
|
|
200
|
-
const entries = await fs.promises.readdir(claudeRulesDir);
|
|
201
|
-
rulesFiles = entries.filter(e => e.endsWith('.md') &&
|
|
202
|
-
!e.startsWith('.'));
|
|
203
|
-
}
|
|
204
|
-
}
|
|
205
|
-
catch {
|
|
206
|
-
// Ignore read errors
|
|
207
|
-
}
|
|
131
|
+
// NOTE: SessionStart hook intentionally does NOT pre-load project.md,
|
|
132
|
+
// knowledge/, or .claude/rules/ into systemMessage/additionalContext. Each
|
|
133
|
+
// phase-level Skill (zhuanspec-apply/review/...) already contains explicit
|
|
134
|
+
// guidance on when to read those files via `rg` / `cat`. Pre-loading them
|
|
135
|
+
// here only wastes context tokens on every session init.
|
|
208
136
|
// Check for active changes
|
|
209
137
|
const changesDir = path.join(zhuanspecDir, 'changes');
|
|
210
138
|
let activeChanges = [];
|
|
@@ -270,34 +198,21 @@ async function runInitHook(_options) {
|
|
|
270
198
|
}
|
|
271
199
|
// Run version check up-front so it can appear in both systemMessage and HUD
|
|
272
200
|
const versionBanner = await checkVersionUpdate();
|
|
273
|
-
// Build system message
|
|
201
|
+
// Build system message — intentionally minimal. Only the version banner
|
|
202
|
+
// and active-change phase line are surfaced. Per-phase guidance lives in
|
|
203
|
+
// the corresponding Skill / slash-command template (e.g. zhuanspec-apply
|
|
204
|
+
// carries the subagent spawn authorization).
|
|
274
205
|
let systemMessage = '';
|
|
275
206
|
if (versionBanner) {
|
|
276
207
|
systemMessage += `${versionBanner}\n`;
|
|
277
208
|
}
|
|
278
|
-
if (projectContext) {
|
|
279
|
-
systemMessage += `✓ Loaded project.md (${projectContext.length} chars)\n`;
|
|
280
|
-
}
|
|
281
|
-
if (knowledgeFiles.length > 0) {
|
|
282
|
-
systemMessage += `✓ Loaded ${knowledgeFiles.length} knowledge files\n`;
|
|
283
|
-
}
|
|
284
|
-
if (rulesFiles.length > 0) {
|
|
285
|
-
if (rulesContents.length > 0) {
|
|
286
|
-
systemMessage += `✓ Loaded ${rulesContents.length} rules files into session context\n`;
|
|
287
|
-
// Inject rules content directly into systemMessage for Codex
|
|
288
|
-
systemMessage += `\n## Project Rules (MUST follow)\n`;
|
|
289
|
-
for (const rule of rulesContents) {
|
|
290
|
-
systemMessage += `### ${rule.name}\n${rule.content}\n\n`;
|
|
291
|
-
}
|
|
292
|
-
}
|
|
293
|
-
else {
|
|
294
|
-
systemMessage += `✓ Found ${rulesFiles.length} rules files in .claude/rules\n`;
|
|
295
|
-
}
|
|
296
|
-
}
|
|
297
209
|
if (activeChanges.length > 0) {
|
|
298
210
|
systemMessage += `✓ Found ${activeChanges.length} active changes\n`;
|
|
299
211
|
if (phase !== 'idle') {
|
|
300
212
|
systemMessage += `✓ Current phase: ${phase}`;
|
|
213
|
+
if (changeId) {
|
|
214
|
+
systemMessage += ` · ${changeId}`;
|
|
215
|
+
}
|
|
301
216
|
if (phase === 'propose') {
|
|
302
217
|
systemMessage += ` (auto-trigger-apply-after-approval)`;
|
|
303
218
|
}
|
|
@@ -305,41 +220,16 @@ async function runInitHook(_options) {
|
|
|
305
220
|
systemMessage += ` (run /zhuanspec:proposal to continue)`;
|
|
306
221
|
}
|
|
307
222
|
else if (phase === 'apply') {
|
|
308
|
-
systemMessage += ` (
|
|
223
|
+
systemMessage += ` (run zhuanspec-apply skill to execute Waves)`;
|
|
309
224
|
}
|
|
310
225
|
systemMessage += `\n`;
|
|
311
226
|
}
|
|
312
227
|
}
|
|
313
|
-
// Build additional context for Claude
|
|
314
|
-
let additionalContext = '';
|
|
315
|
-
if (projectContext.length > 0) {
|
|
316
|
-
additionalContext += `## Project Context (from project.md)\n${projectContext.substring(0, 2000)}\n\n`;
|
|
317
|
-
}
|
|
318
|
-
if (knowledgeFiles.length > 0) {
|
|
319
|
-
additionalContext += `## Knowledge Files\n${knowledgeFiles.join('\n\n')}\n\n`;
|
|
320
|
-
}
|
|
321
|
-
if (rulesFiles.length > 0) {
|
|
322
|
-
if (rulesContents.length > 0) {
|
|
323
|
-
// Codex path: inject actual rules content into additionalContext
|
|
324
|
-
additionalContext += `## Project Rules (from .claude/rules/)\n`;
|
|
325
|
-
additionalContext += `These rules MUST be followed during all phases:\n\n`;
|
|
326
|
-
for (const rule of rulesContents) {
|
|
327
|
-
additionalContext += `### ${rule.name}\n${rule.content}\n\n`;
|
|
328
|
-
}
|
|
329
|
-
}
|
|
330
|
-
else {
|
|
331
|
-
// Claude path: rules are auto-loaded by Claude Code system
|
|
332
|
-
additionalContext += `## Rules Files Detected\n`;
|
|
333
|
-
additionalContext += `Found ${rulesFiles.length} rule files in .claude/rules: ${rulesFiles.join(', ')}\n`;
|
|
334
|
-
additionalContext += `(These are loaded automatically by Claude Code system)\n\n`;
|
|
335
|
-
}
|
|
336
|
-
}
|
|
337
228
|
return {
|
|
338
229
|
continue: true,
|
|
339
230
|
systemMessage: systemMessage || '✓ ZhuanSpec session initialized',
|
|
340
231
|
versionBanner: versionBanner || undefined,
|
|
341
232
|
hookSpecificOutput: {
|
|
342
|
-
additionalContext: additionalContext || undefined,
|
|
343
233
|
env: {
|
|
344
234
|
ZHUANSPEC_ROOT: cwd,
|
|
345
235
|
ZHUANSPEC_PHASE: phase,
|
|
@@ -755,8 +755,11 @@ async function runRecordProgress(filePath, toolName, success, stdinData) {
|
|
|
755
755
|
* @param blocks - Number of blocks in the bar
|
|
756
756
|
*/
|
|
757
757
|
function generateProgressBar(percentage, blocks) {
|
|
758
|
-
const
|
|
759
|
-
const
|
|
758
|
+
const safeBlocks = Math.max(0, Math.floor(Number.isFinite(blocks) ? blocks : 0));
|
|
759
|
+
const safePct = Number.isFinite(percentage) ? percentage : 0;
|
|
760
|
+
const rawFilled = Math.round((safePct / 100) * safeBlocks);
|
|
761
|
+
const filled = Math.min(safeBlocks, Math.max(0, rawFilled));
|
|
762
|
+
const empty = safeBlocks - filled;
|
|
760
763
|
return '█'.repeat(filled) + '░'.repeat(empty);
|
|
761
764
|
}
|
|
762
765
|
function createNewProgress(changeId) {
|
|
@@ -866,9 +869,35 @@ export async function initializeProgress(changeId, initialPhase = 'propose') {
|
|
|
866
869
|
const prevPhaseDuration = progress.phaseDurations.find(pd => pd.phase === previousPhase && !pd.endedAt);
|
|
867
870
|
if (prevPhaseDuration) {
|
|
868
871
|
prevPhaseDuration.endedAt = timestamp;
|
|
869
|
-
const startTime = new Date(prevPhaseDuration.startedAt).getTime();
|
|
870
|
-
const endTime = new Date(timestamp).getTime();
|
|
871
|
-
|
|
872
|
+
const startTime = new Date(prevPhaseDuration.startedAt.replace(' ', 'T')).getTime();
|
|
873
|
+
const endTime = new Date(timestamp.replace(' ', 'T')).getTime();
|
|
874
|
+
const ms = Number.isFinite(startTime) && endTime > startTime ? endTime - startTime : 0;
|
|
875
|
+
prevPhaseDuration.durationMs = ms;
|
|
876
|
+
if (previousPhase && previousPhase !== 'idle' && ms > 0 && progress.stats?.durationMs) {
|
|
877
|
+
progress.stats.durationMs[previousPhase] = ms;
|
|
878
|
+
}
|
|
879
|
+
}
|
|
880
|
+
else if (previousPhase && previousPhase !== 'idle') {
|
|
881
|
+
// Backfill: progress.json was hand-written (e.g. AI wrote phase=techDesign
|
|
882
|
+
// directly via Write tool) without a corresponding phaseDurations entry.
|
|
883
|
+
// Infer a reasonable start timestamp so techDesign's elapsed time is not lost.
|
|
884
|
+
const inferredStart = inferPhaseStartTimestamp(progress, previousPhase);
|
|
885
|
+
if (inferredStart) {
|
|
886
|
+
const startTime = new Date(inferredStart.replace(' ', 'T')).getTime();
|
|
887
|
+
const endTime = new Date(timestamp.replace(' ', 'T')).getTime();
|
|
888
|
+
const ms = Number.isFinite(startTime) && endTime > startTime ? endTime - startTime : 0;
|
|
889
|
+
progress.phaseDurations.push({
|
|
890
|
+
phase: previousPhase,
|
|
891
|
+
startedAt: inferredStart,
|
|
892
|
+
endedAt: timestamp,
|
|
893
|
+
durationMs: ms,
|
|
894
|
+
taskCount: 0,
|
|
895
|
+
completedTaskCount: 0,
|
|
896
|
+
});
|
|
897
|
+
if (ms > 0 && progress.stats?.durationMs) {
|
|
898
|
+
progress.stats.durationMs[previousPhase] = ms;
|
|
899
|
+
}
|
|
900
|
+
}
|
|
872
901
|
}
|
|
873
902
|
// Add new phase duration
|
|
874
903
|
progress.phaseDurations.push({
|
|
@@ -894,6 +923,37 @@ export async function initializeProgress(changeId, initialPhase = 'propose') {
|
|
|
894
923
|
progress.lastUpdatedAt = timestamp;
|
|
895
924
|
await atomicWriteJson(progressPath, progress);
|
|
896
925
|
}
|
|
926
|
+
/**
|
|
927
|
+
* Infer a reasonable start timestamp for a phase when its phaseDurations entry
|
|
928
|
+
* is missing. Typical scenario: AI wrote progress.json directly via Write tool
|
|
929
|
+
* with only `phase: 'techDesign'` but no phaseDurations/phaseTransitions seeded.
|
|
930
|
+
*
|
|
931
|
+
* Lookup priority:
|
|
932
|
+
* 1. latest phaseTransitions[].to === phase timestamp
|
|
933
|
+
* 2. earliest toolCalls[].phase === phase timestamp
|
|
934
|
+
* 3. progress.startedAt
|
|
935
|
+
* 4. progress.createdAt
|
|
936
|
+
*/
|
|
937
|
+
function inferPhaseStartTimestamp(progress, phase) {
|
|
938
|
+
const transitions = progress.phaseTransitions || [];
|
|
939
|
+
for (let i = transitions.length - 1; i >= 0; i--) {
|
|
940
|
+
if (transitions[i].to === phase && transitions[i].timestamp) {
|
|
941
|
+
return transitions[i].timestamp;
|
|
942
|
+
}
|
|
943
|
+
}
|
|
944
|
+
const toolCalls = progress.toolCalls || [];
|
|
945
|
+
for (const call of toolCalls) {
|
|
946
|
+
if (call.phase === phase && call.timestamp) {
|
|
947
|
+
return call.timestamp;
|
|
948
|
+
}
|
|
949
|
+
}
|
|
950
|
+
if (progress.startedAt)
|
|
951
|
+
return progress.startedAt;
|
|
952
|
+
const createdAt = progress.createdAt;
|
|
953
|
+
if (createdAt)
|
|
954
|
+
return createdAt;
|
|
955
|
+
return undefined;
|
|
956
|
+
}
|
|
897
957
|
/**
|
|
898
958
|
* Seed a freshly-created progress with an explicit initial phase so that
|
|
899
959
|
* downstream duration calculations can resolve a start timestamp. Without
|
|
@@ -37,20 +37,24 @@ const applyOutputFormat = `**输出格式要求**
|
|
|
37
37
|
3. **下一步建议** - Review 阶段或继续任务`;
|
|
38
38
|
const reviewOutputFormat = `**输出格式要求**
|
|
39
39
|
- 每轨完成后:\`[轨名称] ✅ PASS / ❌ FAIL - [关键指标摘要]\`
|
|
40
|
-
-
|
|
40
|
+
- 四轨顺序串行输出汇总格式:
|
|
41
41
|
\`\`\`
|
|
42
42
|
### Review 阶段进度报告
|
|
43
43
|
|
|
44
|
-
**轨道
|
|
45
|
-
-
|
|
46
|
-
- Important Issues: [数量]
|
|
44
|
+
**轨道 1 (Spec-Code)**: ✅ PASS / ❌ FAIL
|
|
45
|
+
- Consistency Rate: [百分比]
|
|
47
46
|
|
|
48
|
-
**轨道
|
|
47
|
+
**轨道 2 (Unit Test)**: ✅ PASS / ❌ FAIL
|
|
49
48
|
- Coverage: [百分比]
|
|
50
49
|
- Tests Passed: [数量]/[总数]
|
|
51
50
|
|
|
52
|
-
**轨道
|
|
53
|
-
-
|
|
51
|
+
**轨道 3 (Code Review)**: ✅ PASS / ❌ FAIL
|
|
52
|
+
- Critical Issues: [数量]
|
|
53
|
+
- Important Issues: [数量]
|
|
54
|
+
|
|
55
|
+
**轨道 4 (Closure)**: ✅ PASS / ❌ FAIL
|
|
56
|
+
- skeletonCodeCount: [数量]
|
|
57
|
+
- 未闭环功能: [数量]
|
|
54
58
|
\`\`\`
|
|
55
59
|
- 最终输出:review-report.md 路径、归档建议`;
|
|
56
60
|
const proposalSteps = `**步骤**
|
|
@@ -94,8 +98,28 @@ const proposalSteps = `**步骤**
|
|
|
94
98
|
用户选择"提供测试 case"时:
|
|
95
99
|
- 启用 **TDD 模式**:先获取测试 case,再基于 case 设计 spec 和任务
|
|
96
100
|
- **获取方式**:若用户提供链接(格式:\`https://zzcase.zhuanspirit.com/plan/taskDetail/module/{moduleId}/task/{taskId}\`),从 URL 路径中提取 \`moduleId\`,调用 MCP 工具 \`mcp__caseweb__GET_get2\` 读取数据;否则手动录入
|
|
97
|
-
- **创建 test-cases.md**(路径:\`changes/<change-id>/test-cases.md
|
|
98
|
-
|
|
101
|
+
- **创建 test-cases.md**(路径:\`changes/<change-id>/test-cases.md\`):
|
|
102
|
+
* 优先检查 \`./doc/*-tdd-cases.md\` 是否存在,存在则直接复制为本变更的 test-cases.md,避免重复录入
|
|
103
|
+
* **⚠️ 每个 TC-XXX 必须严格按 TDD 格式生成,禁止使用"测试步骤+预期结果"的 UI 验收格式**:
|
|
104
|
+
\`\`\`
|
|
105
|
+
## TC-XXX [业务场景名]
|
|
106
|
+
- @layer: BE | FE | BOTH
|
|
107
|
+
- Pre-conditions: [前提条件]
|
|
108
|
+
|
|
109
|
+
#### BE 验收(@layer 为 BE 或 BOTH 时必须有)
|
|
110
|
+
Given: [数据库前提状态,具体到表名、字段值]
|
|
111
|
+
When: [Service.method(参数)]
|
|
112
|
+
Then: [返回值 + 数据库变更 + ES/MQ 副作用]
|
|
113
|
+
|
|
114
|
+
#### FE 验收(@layer 为 FE 或 BOTH 时必须有)
|
|
115
|
+
Given: [组件渲染状态]
|
|
116
|
+
When: [用户操作]
|
|
117
|
+
Then: [界面变化 + 接口调用验证]
|
|
118
|
+
\`\`\`
|
|
119
|
+
* @layer 判断:数据库/MQ/ES/接口 → BE 或 BOTH;纯界面 → FE;表单提交 → BOTH
|
|
120
|
+
* BE 验收必须具体到表名、方法名、异常类型、错误码
|
|
121
|
+
* FE 验收必须具体到组件状态变化、接口调用、Toast 文案
|
|
122
|
+
- tasks.md 中使用 \`@test-case:TC-XXX\` 语义匹配关联任务与测试 case,表格四列:Task、测试 Case ID、@layer(BE/FE/BOTH)、覆盖场景
|
|
99
123
|
|
|
100
124
|
用户选择"暂不提供":跳过,按常规流程继续
|
|
101
125
|
用户选择"其他":等待用户补充说明后继续
|
|
@@ -279,17 +303,36 @@ const proposalSteps = `**步骤**
|
|
|
279
303
|
- [ ] 新增文件任务包含完整的类定义骨架(类名、继承关系、核心方法签名、依赖注入)
|
|
280
304
|
- [ ] 每个任务的「内容」字段自包含:AI 执行时不需要再回溯技术方案猜测实现细节
|
|
281
305
|
|
|
282
|
-
**D.
|
|
306
|
+
**D. 外部依赖可用性**(必须在 design.md 中列出):
|
|
307
|
+
- [ ] design.md 包含"外部依赖可用性矩阵"段
|
|
308
|
+
- [ ] 每个外部依赖标注状态:✅ 可用 / ⚠️ 待确认 / ❌ 不可用
|
|
309
|
+
- [ ] 状态为 ❌ 的依赖,对应功能在 tasks.md 中标注"不生成占位实现,标记 BLOCKED"
|
|
310
|
+
- [ ] 状态为 ⚠️ 的依赖,说明兜底方案(如 ES 不可用时用 DB 查询兜底)
|
|
311
|
+
|
|
312
|
+
依赖矩阵格式(必须出现在 design.md 中):
|
|
313
|
+
\`\`\`
|
|
314
|
+
| 依赖 | 状态 | 阻塞策略 |
|
|
315
|
+
|------|------|---------|
|
|
316
|
+
| ES 集群 | ✅ 可用 | 必须真实接入 |
|
|
317
|
+
| 员工组织树 API | ❌ 不可用 | 标记 BLOCKED,不生成占位代码 |
|
|
318
|
+
| MQ Topic | ✅ 可用 | 必须真实消费 |
|
|
319
|
+
| OSS 导出 | ⚠️ 待确认 | 先用本地文件兜底 |
|
|
320
|
+
\`\`\`
|
|
321
|
+
|
|
322
|
+
**E. 测试与验收标准**(若为 TDD 模式或已提供测试 case):
|
|
283
323
|
- [ ] 每个任务包含验证条件(正常场景 + 异常场景的输入输出断言)
|
|
284
|
-
- [ ] test-cases.md 中每个 TC
|
|
324
|
+
- [ ] test-cases.md 中每个 TC 必须有 @layer 标注(BE/FE/BOTH)
|
|
325
|
+
- [ ] @layer:BE 或 BOTH 的 TC 必须有 BE 验收(Given-When-Then,具体到表名/方法名/错误码)
|
|
326
|
+
- [ ] @layer:FE 或 BOTH 的 TC 必须有 FE 验收(Given-When-Then,具体到组件状态/接口调用/Toast 文案)
|
|
327
|
+
- [ ] 禁止出现"测试步骤+预期结果"的 UI 验收格式
|
|
285
328
|
- [ ] 验收标准可通过自动化测试或代码检查验证
|
|
286
329
|
|
|
287
|
-
**
|
|
330
|
+
**F. 潜在风险与边界条件**:
|
|
288
331
|
- [ ] design.md 或 proposal.md 中识别了关键风险点
|
|
289
332
|
- [ ] 边界条件和异常处理有明确的处理策略(如:空值处理、并发场景、数据量边界)
|
|
290
333
|
- [ ] 跨服务调用场景标注了失败处理方式(重试、降级、补偿)
|
|
291
334
|
|
|
292
|
-
**
|
|
335
|
+
**G. 任务分解合理性**:
|
|
293
336
|
- [ ] 每个任务粒度适中
|
|
294
337
|
- [ ] 任务之间依赖关系清晰,无循环依赖
|
|
295
338
|
- [ ] 每个任务有明确的预期结果(完成后可验证的输出)
|
|
@@ -301,6 +344,7 @@ const proposalSteps = `**步骤**
|
|
|
301
344
|
* 功能需求不清 → 补充 specs/ 下 Scenario 的 Given/When/Then 具体条件
|
|
302
345
|
* 技术方案模糊 → 补充 design.md 的接口定义、字段列表、逻辑流程
|
|
303
346
|
* 修改位置不明 → 通过 rg/grep 定位代码后补充 tasks.md 文件路径和修改位置/askUserQuestion
|
|
347
|
+
* 外部依赖未识别 → 补充 design.md 的外部依赖可用性矩阵,明确 ✅/⚠️/❌ 状态及阻塞策略
|
|
304
348
|
* 缺少验证条件 → 为每个任务补充正常/异常场景的验证断言
|
|
305
349
|
* 缺少风险识别 → 补充 design.md 风险章节
|
|
306
350
|
3. 补充完善后**重新检查**,直到所有 ❌ 项变为 ✅ 或 ⚠️
|
|
@@ -311,6 +355,7 @@ const proposalSteps = `**步骤**
|
|
|
311
355
|
功能需求完整性: ✅ [X/Y 项通过]
|
|
312
356
|
技术方案可操作性: ✅ [X/Y 项通过]
|
|
313
357
|
代码修改位置明确性: ✅ [X/Y 项通过]
|
|
358
|
+
外部依赖可用性: ✅ [X/Y 项通过]
|
|
314
359
|
测试与验收标准: ✅ [X/Y 项通过] / ⏭️ 跳过(非 TDD 模式)
|
|
315
360
|
风险与边界条件: ✅ [X/Y 项通过]
|
|
316
361
|
任务分解合理性: ✅ [X/Y 项通过]
|
|
@@ -359,89 +404,79 @@ const applySteps = `**步骤**
|
|
|
359
404
|
- **Phase 确认**:运行 \`zhuanspec progress show <change-id>\` 确认 phase=apply
|
|
360
405
|
- 如果 phase 不为 apply,运行 \`zhuanspec progress set-phase <change-id> apply\`
|
|
361
406
|
- 阅读 \`changes/<id>/proposal.md\`、\`design.md\`(如果存在)和 \`tasks.md\` 以确认范围和验收标准。
|
|
362
|
-
2.
|
|
363
|
-
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
4. 如果 Skill 异常或超时,跳过 Skill 继续实施
|
|
429
|
-
|
|
430
|
-
- **阶段职责(按 Agent 类型区分,须按双条件选择)**:
|
|
431
|
-
主 Agent 须对每个任务执行双条件判定(同 Wave 并行分支的 Agent 类型选择规则):
|
|
432
|
-
* **applyAgent(普通模式)**:无 \`@test-case\` 标注,或有 \`@test-case\` 但任务类型不适合 TDD(如 DDL/配置/DTO 定义)→ 专注代码实现,不生成单元测试文件
|
|
433
|
-
* **tddApplyAgent(TDD 模式)**:有 \`@test-case\` 标注 且 任务类型适合 TDD(业务逻辑/接口实现/复杂查询)→ 基于 test-cases.md 先写测试再写实现,执行 Red-Green-Refactor 循环(详见 tdd-apply-agent.md)
|
|
434
|
-
* **必须在每个任务报告中记录 Agent 选择决策**(Agent 类型/@test-case 标注/任务类型判定/TDD 适用性)
|
|
435
|
-
4. **确认完成** - 在更新状态之前确保 \`tasks.md\` 中的每个项目都已完成。
|
|
436
|
-
5. **更新清单** - 所有工作完成后,将每个任务设置为 \`- [x]\`,以便列表反映实际情况。
|
|
437
|
-
6. **处理 Subagent 状态报告**(Wave 并行执行时):
|
|
407
|
+
2. **Wave 并行执行(默认且唯一执行模式)**
|
|
408
|
+
- **执行模型**:按 Wave 编号串行,Wave 内任务并行启动 subagent。
|
|
409
|
+
- **并行度限制**:每个 Wave 内最多同时启动 3 个 subagent。超过 3 个任务时,按批次(batch)执行,每批最多 3 个并行 subagent,批次间串行等待。
|
|
410
|
+
- **Subagent 调用 @skill 后的行为**(任务标注 \`@skill:<skill-name>\` 时):
|
|
411
|
+
1. 执行 @skill 标注的 Skill
|
|
412
|
+
2. 读取 Skill 输出的 JSON 结果
|
|
413
|
+
3. 无论 Skill 成功/失败,**必须回到当前任务的实施步骤继续**
|
|
414
|
+
4. 如果 Skill 异常或超时,跳过 Skill 继续实施
|
|
415
|
+
- **Agent 类型选择(双条件判定,MUST 严格执行)**:
|
|
416
|
+
编排 Agent 在为每个任务启动 subagent 前,MUST 完成以下两步判定并记录决策:
|
|
417
|
+
|
|
418
|
+
**条件 1:是否有 \`@test-case\` 标注?**
|
|
419
|
+
- 无 \`@test-case\` → 直接使用 \`applyAgent\`(普通模式),跳过条件 2
|
|
420
|
+
- 有 \`@test-case:TC-XXX\` → 进入条件 2
|
|
421
|
+
|
|
422
|
+
**条件 2:任务类型是否适合 TDD?**
|
|
423
|
+
判定原则:**是否存在可测试行为**,与技术栈/前后端无关。前端任务只要包含状态、交互、Hooks、表单校验、utils、services 等可测行为,均属于适合 TDD。
|
|
424
|
+
- ✅ 适合 TDD → 使用 \`tddApplyAgent\`:
|
|
425
|
+
* 业务逻辑实现(Service/Domain/Application 层 Java/TS 代码)
|
|
426
|
+
* 接口实现(Controller/API/RPC 实现类)
|
|
427
|
+
* 复杂查询逻辑(含条件分支的 ES/DB 查询服务)
|
|
428
|
+
* 前端组件交互与状态管理(受控组件、事件处理、条件渲染分支)
|
|
429
|
+
* 前端自定义 Hooks、表单校验与受控逻辑
|
|
430
|
+
* 前端 utils/services/store/reducer/selector、数据格式化与管道
|
|
431
|
+
- ❌ 不适合 TDD → 降级使用 \`applyAgent\`,在报告中说明降级原因:
|
|
432
|
+
* 建表/DDL(SQL 脚本,无可测试的代码行为)
|
|
433
|
+
* 配置变更(yaml/properties/xml/Apollo 配置)
|
|
434
|
+
* 纯 DTO/Entity/Enum 定义(无业务逻辑)
|
|
435
|
+
* ES 索引模板/JSON 资源文件
|
|
436
|
+
* 前端纯静态展示组件(无 state / 无事件 / 无数据处理,仅 JSX + 样式)
|
|
437
|
+
* Mapper XML / DAO 接口定义(无逻辑可测)
|
|
438
|
+
|
|
439
|
+
**⚠️ 决策记录(MANDATORY)**:编排 Agent MUST 在启动每个 subagent 的 prompt 开头注入:
|
|
440
|
+
\`\`\`
|
|
441
|
+
## Agent 选择决策
|
|
442
|
+
- **Agent 类型**: applyAgent | tddApplyAgent
|
|
443
|
+
- **@test-case 标注**: 有 (TC-XXX) | 无
|
|
444
|
+
- **任务类型判定**: [任务类型,如:DDL建表/业务逻辑实现/配置变更...]
|
|
445
|
+
- **TDD 适用性**: 适合 | 不适合(降级原因:[原因])
|
|
446
|
+
\`\`\`
|
|
447
|
+
Subagent MUST 将此决策信息原样写入任务报告的 **Agent 选择决策** 章节。
|
|
448
|
+
- **Subagent 上下文注入(三级策略)**:
|
|
449
|
+
|
|
450
|
+
**L1 核心(全量注入,不可省略)**:
|
|
451
|
+
① spec 内容:当前任务对应的 specs/<capability>/spec.md 全文
|
|
452
|
+
② test-cases:当前任务 @test-case:TC-XXX 对应的 TC 详情(从 test-cases.md 提取)
|
|
453
|
+
③ techDesign 实施细节:当前任务 @ref 指向的技术方案章节(从 techDesign/tech-spec.md 提取)
|
|
454
|
+
④ 外部依赖可用性:从 design.md 的"外部依赖可用性矩阵"提取本 task 涉及的依赖状态。状态为 ❌ 的依赖 → 注入指令"禁止生成占位实现,必须报告 BLOCKED"
|
|
455
|
+
|
|
456
|
+
**L2 关联(全量注入)**:
|
|
457
|
+
⑤ design.md 全文(已有,保持不变)
|
|
458
|
+
⑥ Rules + Knowledge(已有,保持不变)
|
|
459
|
+
⑦ 当前 Wave 其他任务摘要 + 前置 Wave 完成结果
|
|
460
|
+
|
|
461
|
+
**L3 背景(摘要嵌入 + 提供 Read 路径)**:
|
|
462
|
+
⑧ 技术方案其他章节摘要(列出所有元素清单,如"10 张表:t_customer, t_customer_tag, ...")
|
|
463
|
+
⑨ 其他 spec 摘要
|
|
464
|
+
→ 提供完整文件路径,Subagent 如需详情可自行 Read
|
|
465
|
+
|
|
466
|
+
**关键约束**:
|
|
467
|
+
- L1 和 L2 必须全量注入,不允许摘要或截断
|
|
468
|
+
- L3 的摘要必须列出所有元素清单,确保 Subagent 知道完整范围
|
|
469
|
+
- 最终校验:L1+L2 总行数超过 800 行时,对 L2 中的 design.md 做章节裁剪(只保留与当前任务相关的章节),但不允许裁剪 L1
|
|
470
|
+
3. **确认完成** - 在更新状态之前确保 \`tasks.md\` 中的每个项目都已完成。
|
|
471
|
+
4. **更新清单** - 所有工作完成后,将每个任务设置为 \`- [x]\`,以便列表反映实际情况。
|
|
472
|
+
5. **处理 Subagent 状态报告**:
|
|
438
473
|
- **必须转发进度汇报**:每个 subagent 完成后,将其输出的 \`━━━ ✅ 任务完成\` 进度块原文转发输出给用户
|
|
439
474
|
- **DONE**:转发进度汇报 → 继续下一任务
|
|
440
475
|
- **DONE_WITH_CONCERNS**:转发进度汇报 → 记录 concerns 并评估是否需要修复
|
|
441
476
|
- **BLOCKED**:转发进度汇报 → 评估 blocker 类型并决定处理策略(提供上下文/换更强模型/拆分任务)
|
|
442
477
|
- **NEEDS_CONTEXT**:转发进度汇报 → 提供缺失信息并重新启动 subagent
|
|
443
478
|
- 所有任务报告文件均保存在 \`zhuanspec/changes/<change-id>/reports/\` 目录,可告知用户按 task-id 查阅
|
|
444
|
-
|
|
479
|
+
6. **Wave 完成后集成验证与编译失败恢复(MANDATORY GATE,不得跳过)**:
|
|
445
480
|
每个 Wave 全部任务完成后,编排 Agent MUST 执行以下流程:
|
|
446
481
|
|
|
447
482
|
**A. 编译失败处理**(并行执行过程中):
|
|
@@ -480,7 +515,22 @@ const applySteps = `**步骤**
|
|
|
480
515
|
- 整体状态:所有检查通过且无未解决的阻塞任务 → PASS
|
|
481
516
|
- **未生成报告文件 → 阻断,不得进入下一 Wave**
|
|
482
517
|
|
|
483
|
-
B7.
|
|
518
|
+
B7. **功能闭环验收扫描(MANDATORY,不得跳过)**:
|
|
519
|
+
- 扫描本 Wave 所有 subagent 产出代码,检测以下"骨架冒充完成"特征:
|
|
520
|
+
* 方法体只有 \`return null\` / \`return new ArrayList<>()\` / \`return true\` → 占位
|
|
521
|
+
* Consumer 的 consume 方法只写了日志没有真实处理 → 占位
|
|
522
|
+
* Assemble 方法直接 \`return Collections.emptyList()\` → 占位
|
|
523
|
+
* 接口实现只有 \`// TODO\` 注释 → 占位
|
|
524
|
+
- 发现占位代码的 task → 状态强制改为 \`BLOCKED\`,记录到集成报告
|
|
525
|
+
- 集成报告必须包含 **功能闭环状态表**:
|
|
526
|
+
\`\`\`
|
|
527
|
+
| 用户功能 | 数据写入 | 数据可查 | 闭环状态 |
|
|
528
|
+
|---------|---------|---------|---------|
|
|
529
|
+
| 新建客户 | DB ✓ | 列表查询 ✓/✗ | ✓/✗ |
|
|
530
|
+
\`\`\`
|
|
531
|
+
- 任何功能不闭环 → 集成报告标记 FAIL,阻断进入下一 Wave
|
|
532
|
+
|
|
533
|
+
B8. **TC 覆盖快照与回归检测**:
|
|
484
534
|
- 如果 tc-implementation-verify Skill 已安装:
|
|
485
535
|
a. 运行 tc-implementation-verify,生成当前 Wave 的 TC 覆盖快照
|
|
486
536
|
b. 将结果写入 \`metrics/tc-snapshot-wave-N.json\`
|
|
@@ -493,8 +543,8 @@ const applySteps = `**步骤**
|
|
|
493
543
|
- 如果 Skill 未安装:跳过此步骤
|
|
494
544
|
|
|
495
545
|
任何检查失败 → 修复后再进入下一个 Wave。所有任务完成(含阻塞任务重试)且报告已生成 → 进入下一 Wave。
|
|
496
|
-
|
|
497
|
-
|
|
546
|
+
7. 需要额外上下文时,参考 \`zhuanspec list\` 或 \`zhuanspec show <item>\`。
|
|
547
|
+
8. **全局 Concerns 汇总检查(MANDATORY GATE)** - 所有任务完成后、TC 验证前:
|
|
498
548
|
- 扫描所有 Wave 集成测试报告(reports/wave-*-integration-report.md)的 Concerns 追踪章节
|
|
499
549
|
- 扫描所有任务报告(reports/task-*.md),提取 DONE_WITH_CONCERNS 和 BLOCKED 状态的任务
|
|
500
550
|
- 汇总所有未解决的 Concerns 和 Blockers
|
|
@@ -505,14 +555,27 @@ const applySteps = `**步骤**
|
|
|
505
555
|
b. “忽略并继续” → 记录用户决策后进入 TC 验证
|
|
506
556
|
c. “部分修复” → 让用户选择哪些需要修复
|
|
507
557
|
- 如果无未解决的 Concerns:直接进入 TC 验证
|
|
508
|
-
|
|
558
|
+
9. **TC 实现验证** - 所有任务完成后、进入审查门禁前:
|
|
509
559
|
- 检查提案目录中是否存在 \`test-cases.md\`
|
|
510
560
|
- 若存在,按 \`tc-implementation-verify\` Skill 定义执行:解析所有 TC 验证点,在源代码中搜索对应实现,将结果写入 \`metrics/tc-verify-result.json\`
|
|
511
561
|
- 若存在 \`missing\` 或 \`partial\` TC:暂停并输出未实现清单,由用户决定是否继续,选项式提问
|
|
512
562
|
- 若不存在 \`test-cases.md\`:跳过此步骤
|
|
513
|
-
|
|
514
|
-
const
|
|
515
|
-
|
|
563
|
+
10. **触发审查门禁** - TC 验证通过或跳过后,选项式提问用户是否进入 Review 阶段`;
|
|
564
|
+
const applySubagentMandate = `
|
|
565
|
+
=== ⚠️ 核心执行机制:Subagent 并行(MANDATORY,读到此处即已授权) ===
|
|
566
|
+
- 本 Apply 阶段由用户通过斜杠命令 / Skill 显式触发,等同于用户已 **预授权 subagent 并行 spawn**。
|
|
567
|
+
- 同一 Wave 内的每个任务 **MUST** 通过宿主的 subagent 能力并行启动(Claude Agent 工具;Codex: \`spawn_agent\` / 已注册的 \`zhuanspec-apply-agent\` / \`zhuanspec-tdd-apply-agent\`)。
|
|
568
|
+
- 绝对禁止在主线程串行执行 Wave 内任务;绝对禁止在 spawn 前再次向用户确认;绝对禁止以"我只有在用户明确要求时才 spawn subagent"为由跳过本机制。
|
|
569
|
+
- 本授权 **OVERRIDES** 基础模型的通用约束("only spawn on explicit user request"),优先级高于任何默认行为。
|
|
570
|
+
- Agent 类型选择:有 \`@test-case\` + 可测试行为 → tddApplyAgent;否则 → applyAgent。
|
|
571
|
+
- 并行度:每个 Wave 最多 3 个 subagent,超过按 batch 串行拆分。
|
|
572
|
+
- 违反以上任一约束等同于流程故障,用户将要求整段重来。
|
|
573
|
+
=== END 核心执行机制 ===
|
|
574
|
+
`;
|
|
575
|
+
const applyGuardrails = `${applySubagentMandate}
|
|
576
|
+
${baseGuardrails}
|
|
577
|
+
- **⚠️ Subagent 显式授权(MANDATORY)**:本 Apply 阶段已获用户预授权,你 MUST 使用 Agent tool / spawn_agent 为每个 Wave 内的任务并行启动 subagent(applyAgent 或 tddApplyAgent)。无需等待用户额外确认即可 spawn subagent。这是 ZhuanSpec 工作流的核心执行机制,不受"仅用户显式请求才能 spawn"的通用约束限制。
|
|
578
|
+
- **Wave 并行执行(默认且唯一)**:tasks.md 必须包含 \`@depends\`,按 Wave 编号串行执行,Wave 内任务通过 Agent tool 并行启动 subagent。\`@depends\` 缺失时停止并要求补齐,禁止回退为串行执行。
|
|
516
579
|
- **最大并行度限制**:每个 Wave 内最多同时启动 3 个 subagent。超过 3 个任务时按 batch 拆分,batch 间串行等待。
|
|
517
580
|
- **编译失败处理**:并行任务编译失败时先尝试修复,无法修复则标记为 BLOCKED 继续其他任务。Wave 完成后进入集成测试阶段重试阻塞任务。
|
|
518
581
|
- **集成测试报告**:每个 Wave 完成后生成 \`changes/<change-id>/reports/wave-{N}-integration-report.md\`。
|
|
@@ -525,11 +588,12 @@ const applyGuardrails = `${baseGuardrails}\n- **⚠️ Subagent 显式授权(M
|
|
|
525
588
|
const applyReferences = `**参考**
|
|
526
589
|
- 如果在实施过程中需要提案的额外上下文,请使用 \`zhuanspec show <id> --json --deltas-only\`。`;
|
|
527
590
|
const archiveSteps = `**前置检查**:
|
|
528
|
-
- ⚠️ **CHECKPOINT [REVIEW-PASSED]**: Review
|
|
591
|
+
- ⚠️ **CHECKPOINT [REVIEW-PASSED]**: Review 阶段必须通过(四轨全部 PASS)
|
|
529
592
|
- \`review-report.md\` 存在且显示 passed
|
|
530
593
|
- \`code-review-result.json\`: critical = 0
|
|
531
594
|
- \`unit-test-result.json\`: tests passed
|
|
532
595
|
- \`spec-consistency-result.json\`: passed
|
|
596
|
+
- \`closure-check-result.json\`: \`overallStatus = PASS\` 且 \`skeletonCodeCount = 0\`、所有 \`closureStatus[i].closed === true\`
|
|
533
597
|
- **Phase 确认**:phase 应为 review(运行 \`zhuanspec progress show <change-id>\`)
|
|
534
598
|
|
|
535
599
|
**步骤**
|
|
@@ -547,7 +611,7 @@ const archiveSteps = `**前置检查**:
|
|
|
547
611
|
- \`progress.phaseTransitions\` 必须新增一条 \`{from:'review', to:'archive'}\`
|
|
548
612
|
- \`progress.phaseDurations\` 必须关闭 review 段并开启 archive 段
|
|
549
613
|
2. 通过运行 \`zhuanspec list\`(或 \`zhuanspec show <id>\`)验证变更 ID,如果变更缺失、已归档或尚未准备好归档,则停止。
|
|
550
|
-
3. **验证 Review
|
|
614
|
+
3. **验证 Review 门禁**:确认上述五个结果文件存在且全部通过(含轨道 4 的 \`closure-check-result.json\`)。
|
|
551
615
|
4. 运行 \`zhuanspec archive <id> --yes\`,以便 CLI 移动变更并应用规范更新,无需提示(仅对仅工具类工作使用 \`--skip-specs\`)。
|
|
552
616
|
5. 审查命令输出以确认目标规范已更新,并且变更已进入 \`changes/archive/\`。
|
|
553
617
|
6. 使用 \`zhuanspec validate --strict\` 进行验证,如果看起来有问题,使用 \`zhuanspec show <id>\` 进行检查。
|
|
@@ -570,6 +634,7 @@ const designGuardrails = `${baseGuardrails}\n- **独立设计阶段**:techDesi
|
|
|
570
634
|
- **Skill 可用性前置检查(硬约束)**:在实际调用技术方案 Skill 前,**必须**先校验目标 Skill 是否已安装且可用;**若不可用,立即中断流程**并提示用户到 Skill 市场安装对应 Skill,禁止以人工编写/其他 Skill 代替。
|
|
571
635
|
- **Skill 调用为主**:技术方案生成主要通过 \`generate-tech-spec-md-skill\`(仅后端)或 \`generate-fullstack-tech-spec-skill\`(全栈)Skill 完成,而非直接运行 CLI 命令。
|
|
572
636
|
- **严格文件约束**:techDesign 阶段只能创建目录和 progress.json(phase=techDesign)。
|
|
637
|
+
- **progress.json 初始化铁律**:**严禁使用 Write/Edit 工具手写 \`metrics/progress.json\`**,必须通过 \`zhuanspec progress set-phase <change-id> techDesign\` 初始化,否则 phaseDurations / phaseTransitions / stats.durationMs 全部缺失,techDesign 阶段耗时无法统计。
|
|
573
638
|
- **禁止创建提案文件**:禁止创建 .tech-design、design.md、proposal.md、tasks.md、specs/ 等。
|
|
574
639
|
- **Proposal 复用**:proposal 阶段通过 progress.json 的 phase 字段识别 techDesign 目录。`;
|
|
575
640
|
const designSteps = `**步骤**
|
|
@@ -587,7 +652,10 @@ const designSteps = `**步骤**
|
|
|
587
652
|
- 目录:changes/{change-id}/
|
|
588
653
|
- 文件:changes/{change-id}/metrics/progress.json(phase=techDesign)
|
|
589
654
|
* **禁止创建**:.tech-design、design.md、proposal.md、tasks.md、specs/、Skill 请求文件等
|
|
590
|
-
*
|
|
655
|
+
* **铁律:progress.json 初始化必须通过 CLI,严禁直接使用 Write/Edit 工具手写**:
|
|
656
|
+
- 新建 change:运行 \`zhuanspec progress set-phase <change-id> techDesign\`(CLI 会自动创建目录并正确初始化 phaseDurations / phaseTransitions / stats.durationMs,保证 techDesign 段耗时可统计)
|
|
657
|
+
- 绑定到已存在 change:同样运行 \`zhuanspec progress set-phase <change-id> techDesign\`
|
|
658
|
+
- **任何手写 progress.json 的做法都会导致 techDesign 阶段耗时丢失(durationMs=0)、phaseDurations 缺段**,一经发现必须立即删除手写文件并重新走 CLI 初始化。
|
|
591
659
|
|
|
592
660
|
2. **确认需求来源**:
|
|
593
661
|
- 检查用户是否提供了需求来源(--dashen-page-id、--dashen-url 或 --desc)
|
|
@@ -639,9 +707,32 @@ const designSteps = `**步骤**
|
|
|
639
707
|
- Skill 会自动获取大神页面内容并生成完整技术方案文档
|
|
640
708
|
- **禁止**:在未完成步骤 4 的开发范围确认和步骤 5 的 Skill 可用性校验前调用任何 Skill;禁止两个 Skill 同时调用。
|
|
641
709
|
|
|
642
|
-
7.
|
|
710
|
+
7. **外部依赖可用性确认(必须在技术方案定稿前执行)**:
|
|
711
|
+
- 从 Skill 生成的技术方案中提取所有外部依赖(ES、MQ、Redis、OSS、权限服务、员工组织树 API、下游 RPC 等)
|
|
712
|
+
- 使用 AskQuestion 工具逐项确认可用状态:
|
|
713
|
+
\`\`\`
|
|
714
|
+
请确认以下外部依赖的可用状态:
|
|
715
|
+
1. ES 集群:✅ 可用 / ⚠️ 待确认 / ❌ 不可用
|
|
716
|
+
2. MQ Topic:✅ / ⚠️ / ❌
|
|
717
|
+
3. 员工组织树 API:✅ / ⚠️ / ❌
|
|
718
|
+
4. OSS 文件存储:✅ / ⚠️ / ❌
|
|
719
|
+
5. [其他依赖]
|
|
720
|
+
\`\`\`
|
|
721
|
+
- 将确认结果写入技术方案的「外部依赖可用性矩阵」段:
|
|
722
|
+
\`\`\`markdown
|
|
723
|
+
## 外部依赖可用性
|
|
724
|
+
| 依赖 | 状态 | 阻塞策略 |
|
|
725
|
+
|------|------|---------|
|
|
726
|
+
| ES 集群 | ✅ 可用 | 必须真实接入 |
|
|
727
|
+
| 员工组织树 API | ❌ 不可用 | 标记 BLOCKED,不生成占位代码 |
|
|
728
|
+
\`\`\`
|
|
729
|
+
- **状态为 ❌ 的依赖**:对应功能在后续 tasks.md 中必须标记"不生成占位实现,标记 BLOCKED"
|
|
730
|
+
- **状态为 ⚠️ 的依赖**:必须说明兜底方案(如"ES 不可用时用 DB 查询兜底")
|
|
731
|
+
- 该矩阵将被 propose/apply 阶段复用,作为 Subagent L1 上下文注入的根据
|
|
732
|
+
|
|
733
|
+
8. **输出摘要**:
|
|
643
734
|
- 告知用户生成的文档路径和实际调用的 Skill 名称
|
|
644
|
-
- **明确说明**:techDesign 阶段仅保存进度数据(progress.json
|
|
735
|
+
- **明确说明**:techDesign 阶段仅保存进度数据(progress.json)和技术方案(含外部依赖矩阵),不创建 proposal.md 和 tasks.md
|
|
645
736
|
- 提示 phase=techDesign
|
|
646
737
|
- 提示下一步可以使用 \`/zhuanspec:proposal\` 创建变更提案(复用目录)`;
|
|
647
738
|
const designReferences = `**参考**
|
|
@@ -652,15 +743,16 @@ const designReferences = `**参考**
|
|
|
652
743
|
- Skill 输出包含:背景、目标、决策、风险、迁移计划、Mermaid 图表等完整技术方案结构
|
|
653
744
|
- 数据库表变更场景必须输出 CREATE TABLE / ALTER TABLE DDL;前后端交互接口必须输出伪代码与输入/输出参数字段结构
|
|
654
745
|
- 使用 \`zhuanspec techDesign --help\` 查看 CLI 命令选项(用于生成请求文档或验证现有设计)`;
|
|
655
|
-
const reviewGuardrails = `${baseGuardrails}\n- **门禁审查阶段**:review
|
|
656
|
-
-
|
|
657
|
-
-
|
|
746
|
+
const reviewGuardrails = `${baseGuardrails}\n- **门禁审查阶段**:review 命令用于执行四轨顺序串行校验并汇总结论。
|
|
747
|
+
- **四轨顺序串行(严格按序)**:轨道 1 Spec-Code 一致性 → 轨道 2 Unit Test(Skill)→ 轨道 3 Code Review(Skill)→ 轨道 4 功能闭环验收。**禁止并行**,后一轨必须在前一轨 PASS 后才可启动。
|
|
748
|
+
- **强制审查**:所有变更在归档前必须顺序通过四轨门禁,确保规范对齐、测试覆盖、代码质量与功能闭环。
|
|
658
749
|
- **Skill 硬约束(红线)**:
|
|
659
750
|
- 轨道 2 必须使用 Skill 工具调用 \`generate-mockito-unit-test\`,轨道 3 必须使用 Skill 工具调用 \`code-review-expert\`。
|
|
660
751
|
- **禁止以任何形式的人工快速审阅/人工总结代替 Skill 调用**。
|
|
661
752
|
- **禁止跳过 Skill 工具直接手工编造 JSON 结果**。JSON 必须是 Skill 真实执行后的产物摘要。
|
|
662
753
|
- 轨道 3 的 \`code-review-result.json\` 必须登记 \`skillReportPath\` 字段,指向 Skill 生成的 \`doc/code-review-{branch}-{ts}.md\` 报告文件;该文件必须真实存在,否则 CLI 拒绝通过。
|
|
663
|
-
-
|
|
754
|
+
- 轨道 4 产物 \`closure-check-result.json\` 必须由 AI 实际扫描代码后写入,禁止伪造 \`overallStatus = PASS\`。
|
|
755
|
+
- **Schema 版本**:新生成的 \`code-review-result.json\` / \`unit-test-result.json\` / \`closure-check-result.json\` 必须包含 \`"schemaVersion": 2\` 字段以启用 Skill 真实性硬校验;缺失或 < 2 时 CLI 会按历史数据豁免并打 ⚠️ 警告,禁止借 legacy 通道规避校验。`;
|
|
664
756
|
const reviewSteps = `**步骤**
|
|
665
757
|
1. **切换 Phase 到 review(第一步,必须立即执行)**:
|
|
666
758
|
- 如果此提示已包含特定的变更 ID,请使用该值;否则运行 \`zhuanspec list\` 显示活跃变更并询问用户要审查哪个
|
|
@@ -674,7 +766,7 @@ const reviewSteps = `**步骤**
|
|
|
674
766
|
- \`progress.phaseDurations\` 必须关闭 apply 段并开启 review 段
|
|
675
767
|
- 三者任一缺失,说明 set-phase 未生效,必须排查后重试,禁止继续
|
|
676
768
|
|
|
677
|
-
2.
|
|
769
|
+
2. **执行四轨顺序串行校验(强制)**:按 2.1 → 2.2 → 2.3 → 2.4 顺序逐轨执行,**禁止并行**,前一轨未 PASS 禁止启动后一轨。
|
|
678
770
|
|
|
679
771
|
**2.1 轨道 1 — Spec-Code 一致性轨**
|
|
680
772
|
- 进入条件:Phase 已切换为 review。
|
|
@@ -714,28 +806,58 @@ const reviewSteps = `**步骤**
|
|
|
714
806
|
{ "schemaVersion": 2, "criticalCount": 0, "importantCount": 0, "infoCount": 0, "issues": [], "sonarStatus": "skip", "skillReportPath": "doc/code-review-feature-xxx-20260428-153000.md", "loopCount": 0 }
|
|
715
807
|
\`\`\`
|
|
716
808
|
- \`skillReportPath\` **必填**,必须指向上一步 Skill 生成的真实 markdown 报告文件(相对工作区根)。文件不存在时 CLI 会拒绝通过。
|
|
717
|
-
- 退出条件:\`criticalCount === 0\` 且 \`skillReportPath\`
|
|
809
|
+
- 退出条件:\`criticalCount === 0\` 且 \`skillReportPath\` 对应文件真实存在。未通过禁止进入 2.4。
|
|
810
|
+
|
|
811
|
+
**2.4 轨道 4 — 功能闭环验收轨(AI 代码质量拦截)**
|
|
812
|
+
- 进入条件:2.3 已 PASS。
|
|
813
|
+
- 执行动作:
|
|
814
|
+
- 扫描所有实现文件,检测"骨架冒充完成"特征:
|
|
815
|
+
* \`return null\` / \`return new ArrayList<>()\` / \`return true\` 的空实现
|
|
816
|
+
* Consumer 只写日志没有真实处理
|
|
817
|
+
* \`// TODO\` 占位未实现
|
|
818
|
+
* 接口定义存在但方法体为空或假成功
|
|
819
|
+
- 验证核心功能数据链路闭环:
|
|
820
|
+
* 写入操作(DB insert/update)后对应的查询接口能否返回数据
|
|
821
|
+
* ES 同步是否真实实现(不是 TODO)
|
|
822
|
+
* MQ 消费是否真实处理(不是只打日志)
|
|
823
|
+
- 自闭环修复(最多 3 轮):发现占位代码或未闭环功能时,**只修改实现代码**补齐真实逻辑,严禁以修改 spec 或伪造报告代替。修复后重扫。
|
|
824
|
+
- 产物落盘:**AI 将结果写入** \`zhuanspec/changes/<id>/review/closure-check-result.json\`,格式:
|
|
825
|
+
\`\`\`json
|
|
826
|
+
{
|
|
827
|
+
"schemaVersion": 2,
|
|
828
|
+
"skeletonCodeCount": 0,
|
|
829
|
+
"todoCount": 0,
|
|
830
|
+
"closureStatus": [
|
|
831
|
+
{ "feature": "新建客户", "dbWrite": true, "queryable": true, "closed": true },
|
|
832
|
+
{ "feature": "客户列表", "dbWrite": true, "queryable": false, "closed": false, "reason": "ES 查询是占位" }
|
|
833
|
+
],
|
|
834
|
+
"overallStatus": "PASS",
|
|
835
|
+
"loopCount": 0
|
|
836
|
+
}
|
|
837
|
+
\`\`\`
|
|
838
|
+
- 退出条件:\`overallStatus === "PASS"\` 且 \`skeletonCodeCount === 0\` 且 所有 \`closureStatus[i].closed === true\`。存在未闭环功能或占位代码→ FAIL,阻断归档。
|
|
718
839
|
|
|
719
840
|
3. **汇总执行 CLI Review**:
|
|
720
|
-
-
|
|
721
|
-
- CLI
|
|
841
|
+
- 四轨均 PASS 后运行 \`zhuanspec review <change-id>\`。
|
|
842
|
+
- CLI 依次校验四份 JSON 的存在性、\`code-review-result.json\` 的 \`skillReportPath\` 文件是否真实存在、\`unit-test-result.json\` 的 \`newTestsGenerated\`/\`skipReason\` 是否齐备、\`closure-check-result.json\` 的 \`overallStatus\` 是否为 PASS;任一不满足即报错退出,并要求重新调用对应 Skill 或修复代码。
|
|
722
843
|
- 全部通过时生成 \`zhuanspec/changes/<id>/review/review-report.md\`。
|
|
723
844
|
4. **审查结果处理**:
|
|
724
|
-
- **PASS
|
|
725
|
-
- **FAIL**:根据 CLI 输出定位失败轨,返回对应子步骤(2.1/2.2/2.3)继续修复。
|
|
845
|
+
- **PASS**:四轨顺序全部通过(Spec-Code → UT → CR → Closure)且 Critical=0、skeletonCodeCount=0、无未闭环功能,可以继续归档。
|
|
846
|
+
- **FAIL**:根据 CLI 输出定位失败轨,返回对应子步骤(2.1/2.2/2.3/2.4)继续修复。
|
|
726
847
|
5. **修复循环**(整体失败时):
|
|
727
848
|
- 按失败轨所在子步骤的自闭环修复流程处理;每轨内部最多 3 轮,超限则停止并向用户报告。
|
|
728
849
|
- 修复完成后重新从失败轨开始顺序执行,而非重置整个 Review。
|
|
729
850
|
6. **确认通过后**:
|
|
730
|
-
-
|
|
851
|
+
- 告知用户审查已通过(四轨顺序串行 + CLI 汇总)。
|
|
731
852
|
- 提示可以使用 \`/zhuanspec:archive\` 进行归档。`;
|
|
732
853
|
const reviewReferences = `**参考**
|
|
733
854
|
- 使用 \`zhuanspec review --help\` 查看完整选项
|
|
734
|
-
- 审查执行顺序:轨道 1 Spec-Code → 轨道 2 Unit Test(Skill)→ 轨道 3 Code Review(Skill
|
|
735
|
-
- 审查报告文件均位于 \`zhuanspec/changes/<id>/review/\` 目录下:\`spec-consistency-result.json\`、\`unit-test-result.json\`、\`code-review-result.json\`、\`review-report.md\`
|
|
855
|
+
- 审查执行顺序:轨道 1 Spec-Code → 轨道 2 Unit Test(Skill)→ 轨道 3 Code Review(Skill)→ 轨道 4 功能闭环验收,禁止并行
|
|
856
|
+
- 审查报告文件均位于 \`zhuanspec/changes/<id>/review/\` 目录下:\`spec-consistency-result.json\`、\`unit-test-result.json\`、\`code-review-result.json\`、\`closure-check-result.json\`、\`review-report.md\`
|
|
736
857
|
- 单元测试覆盖率阈值默认 80%,可通过 \`--coverage-threshold\` 调整
|
|
737
858
|
- \`generate-mockito-unit-test\` Skill 结果由 AI 写入 \`unit-test-result.json\`(必须登记 \`newTestsGenerated\` 或 \`skipReason\`)
|
|
738
|
-
- \`code-review-expert\` Skill 结果由 AI 写入 \`code-review-result.json\`(必须登记 \`skillReportPath\`,指向 Skill 生成的 \`doc/code-review-*.md\`
|
|
859
|
+
- \`code-review-expert\` Skill 结果由 AI 写入 \`code-review-result.json\`(必须登记 \`skillReportPath\`,指向 Skill 生成的 \`doc/code-review-*.md\` 报告)
|
|
860
|
+
- 轨道 4 闭环检查结果由 AI 写入 \`closure-check-result.json\`,要求 \`overallStatus = PASS\` 且 \`skeletonCodeCount = 0\``;
|
|
739
861
|
const knowledgeGuardrails = `${baseGuardrails}\n- **知识管理阶段**:knowledge 命令用于管理项目级知识库(最佳实践、陷阱、隐式约定)。
|
|
740
862
|
- **跨变更积累**:知识不绑定单个变更,是长期积累的项目资产。
|
|
741
863
|
- **职责分离**:
|