@zhuan-ai/zhuanspec 2.17.13 → 2.17.15
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.
|
@@ -16,6 +16,10 @@ interface HookOutput {
|
|
|
16
16
|
reviewTriggered?: boolean;
|
|
17
17
|
deviationDetected?: boolean;
|
|
18
18
|
deviationFiles?: string[];
|
|
19
|
+
featureOmissionDetected?: boolean;
|
|
20
|
+
uncoveredFeatures?: string[];
|
|
21
|
+
missingScenarios?: string[];
|
|
22
|
+
partialScenarios?: string[];
|
|
19
23
|
};
|
|
20
24
|
}
|
|
21
25
|
export declare function postApplyHook(options: PostApplyOptions): Promise<void>;
|
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
import path from 'path';
|
|
9
9
|
import { FileSystemUtils } from '../../utils/file-system.js';
|
|
10
10
|
import { GitRepoDetector } from '../../utils/git-repo-detector.js';
|
|
11
|
+
import { promises as fs } from 'fs';
|
|
11
12
|
import { getBeijingTime, atomicWriteJson, recoverProgressJsonForWrite, } from './record-progress.js';
|
|
12
13
|
import { ensureAccuracySnapshot } from '../metrics/code-accuracy.js';
|
|
13
14
|
import { detectHookHost, sanitizeCodexEnvelope } from '../../utils/hook-host.js';
|
|
@@ -44,6 +45,32 @@ async function runPostApplyHook(changeId) {
|
|
|
44
45
|
* Users should manually run `zhuanspec review <change-id>` in a new session.
|
|
45
46
|
*/
|
|
46
47
|
export async function executePostApply(changeDir, repoRoot) {
|
|
48
|
+
// 0. Feature-omission gate(功能点遗漏闸口):检测「范围内但未实现」的 Scenario/功能点
|
|
49
|
+
// 对应问题:PRD 已有功能点被静默漏实现,Review 未拦住。此处提前拦截,afterAction=pause。
|
|
50
|
+
const omissionResult = await detectFeatureOmission(changeDir);
|
|
51
|
+
if (omissionResult.detected) {
|
|
52
|
+
const parts = [];
|
|
53
|
+
if (omissionResult.uncoveredFeatures.length > 0) {
|
|
54
|
+
parts.push(`功能点遗漏 ${omissionResult.uncoveredFeatures.length} 个: ${omissionResult.uncoveredFeatures.join(', ')}`);
|
|
55
|
+
}
|
|
56
|
+
if (omissionResult.missingScenarios.length > 0) {
|
|
57
|
+
parts.push(`未实现 Scenario ${omissionResult.missingScenarios.length} 个`);
|
|
58
|
+
}
|
|
59
|
+
if (omissionResult.partialScenarios.length > 0) {
|
|
60
|
+
parts.push(`疑似未实现(partial) Scenario ${omissionResult.partialScenarios.length} 个`);
|
|
61
|
+
}
|
|
62
|
+
return {
|
|
63
|
+
continue: false,
|
|
64
|
+
systemMessage: `Post-apply BLOCKED: 检测到范围内功能点/Scenario 未落地(${parts.join(';')})。请补齐缺失的代码实现(禁止改删 spec/技术方案/PRD),再重新进入 Review。`,
|
|
65
|
+
hookSpecificOutput: {
|
|
66
|
+
reviewTriggered: false,
|
|
67
|
+
featureOmissionDetected: true,
|
|
68
|
+
uncoveredFeatures: omissionResult.uncoveredFeatures,
|
|
69
|
+
missingScenarios: omissionResult.missingScenarios,
|
|
70
|
+
partialScenarios: omissionResult.partialScenarios,
|
|
71
|
+
},
|
|
72
|
+
};
|
|
73
|
+
}
|
|
47
74
|
// 1. Reverse Sync deviation detection
|
|
48
75
|
const deviationResult = await detectDeviation(changeDir, repoRoot);
|
|
49
76
|
// 2. Report deviation status (no auto review trigger)
|
|
@@ -75,6 +102,49 @@ export async function executePostApply(changeDir, repoRoot) {
|
|
|
75
102
|
},
|
|
76
103
|
};
|
|
77
104
|
}
|
|
105
|
+
/**
|
|
106
|
+
* 功能点遗漏检测:读取 apply 阶段 spec-code 一致性报告,识别「范围内但未实现」的遗漏。
|
|
107
|
+
*
|
|
108
|
+
* 数据来源优先级:
|
|
109
|
+
* 1. review/spec-consistency-result.json(consistency-check Skill 产出,含 uncoveredScenarios/partialScenarios/featureTrace)
|
|
110
|
+
* 2. 若报告不存在 → 视为未检测(detected=false),不阻断(交由 review 阶段兜底)
|
|
111
|
+
*
|
|
112
|
+
* 判定为遗漏(detected=true)的条件(任一命中):
|
|
113
|
+
* - featureTrace.uncoveredFeatures 非空(技术方案功能点 F 无落地 Scenario)
|
|
114
|
+
* - uncoveredScenarios 中存在 status=missing 的项
|
|
115
|
+
* - partialScenarios 非空(仅命中辅助关键词,疑似未实现)
|
|
116
|
+
*/
|
|
117
|
+
async function detectFeatureOmission(changeDir) {
|
|
118
|
+
const empty = {
|
|
119
|
+
detected: false,
|
|
120
|
+
uncoveredFeatures: [],
|
|
121
|
+
missingScenarios: [],
|
|
122
|
+
partialScenarios: [],
|
|
123
|
+
};
|
|
124
|
+
const reportPath = path.join(changeDir, 'review', 'spec-consistency-result.json');
|
|
125
|
+
let report;
|
|
126
|
+
try {
|
|
127
|
+
const raw = await fs.readFile(reportPath, 'utf-8');
|
|
128
|
+
report = JSON.parse(raw);
|
|
129
|
+
}
|
|
130
|
+
catch {
|
|
131
|
+
// 报告不存在或解析失败 → 不阻断,交由 review 阶段兜底
|
|
132
|
+
return empty;
|
|
133
|
+
}
|
|
134
|
+
const uncoveredFeatures = Array.isArray(report.featureTrace?.uncoveredFeatures)
|
|
135
|
+
? report.featureTrace.uncoveredFeatures.filter((f) => typeof f === 'string')
|
|
136
|
+
: [];
|
|
137
|
+
const missingScenarios = Array.isArray(report.uncoveredScenarios)
|
|
138
|
+
? report.uncoveredScenarios
|
|
139
|
+
.filter(s => s?.status === 'missing')
|
|
140
|
+
.map(s => s.scenario || '(未命名 Scenario)')
|
|
141
|
+
: [];
|
|
142
|
+
const partialScenarios = Array.isArray(report.partialScenarios)
|
|
143
|
+
? report.partialScenarios.map(s => s?.scenario || '(未命名 Scenario)')
|
|
144
|
+
: [];
|
|
145
|
+
const detected = uncoveredFeatures.length > 0 || missingScenarios.length > 0 || partialScenarios.length > 0;
|
|
146
|
+
return { detected, uncoveredFeatures, missingScenarios, partialScenarios };
|
|
147
|
+
}
|
|
78
148
|
/**
|
|
79
149
|
* Detect deviation by comparing proposal scope with actual git diff
|
|
80
150
|
*/
|
|
@@ -81,6 +81,25 @@ interface SpecConsistencyOutput {
|
|
|
81
81
|
scenarioId: string;
|
|
82
82
|
description: string;
|
|
83
83
|
}>;
|
|
84
|
+
/**
|
|
85
|
+
* 仅命中辅助关键词(类名/表名)、未命中核心关键词的疑似未实现项。
|
|
86
|
+
* 非空视为未通过——必须澄清为 covered 或补齐代码,禁止改删 spec。
|
|
87
|
+
*/
|
|
88
|
+
partialScenarios?: Array<{
|
|
89
|
+
scenario?: string;
|
|
90
|
+
sourceFeature?: string;
|
|
91
|
+
matchedKeyword?: string;
|
|
92
|
+
warning?: string;
|
|
93
|
+
}>;
|
|
94
|
+
/**
|
|
95
|
+
* 功能点 F 编号全链核对结果(以 feature-manifest.json 的 F 编号为分母)。
|
|
96
|
+
* uncoveredFeatures 非空 = 技术方案功能点无落地 Scenario,属功能点遗漏。
|
|
97
|
+
*/
|
|
98
|
+
featureTrace?: {
|
|
99
|
+
totalFeatures?: number;
|
|
100
|
+
coveredFeatures?: number;
|
|
101
|
+
uncoveredFeatures?: string[];
|
|
102
|
+
};
|
|
84
103
|
mapping: Array<{
|
|
85
104
|
requirementId: string;
|
|
86
105
|
scenarioId: string;
|
|
@@ -261,9 +261,17 @@ export async function specConsistencyResultCheck(changeId, skillOutput) {
|
|
|
261
261
|
};
|
|
262
262
|
}
|
|
263
263
|
const loopCount = output.loopCount || 0;
|
|
264
|
-
// Check consistency coverage
|
|
264
|
+
// Check consistency coverage —— 三类缺口任一命中即视为未通过(层4 终检):
|
|
265
|
+
// 1. missing:核心与辅助关键词均未命中的 Scenario
|
|
266
|
+
// 2. partial:仅命中辅助关键词(类名/表名)的疑似未实现项
|
|
267
|
+
// 3. uncoveredFeatures:feature-manifest 中无落地 Scenario 的功能点 F
|
|
268
|
+
const partialScenarios = output.partialScenarios || [];
|
|
269
|
+
const uncoveredFeatures = output.featureTrace?.uncoveredFeatures || [];
|
|
265
270
|
const hasUncovered = output.uncoveredScenarios.length > 0;
|
|
266
|
-
|
|
271
|
+
const hasPartial = partialScenarios.length > 0;
|
|
272
|
+
const hasUncoveredFeatures = uncoveredFeatures.length > 0;
|
|
273
|
+
const hasGap = hasUncovered || hasPartial || hasUncoveredFeatures;
|
|
274
|
+
if (hasGap) {
|
|
267
275
|
if (loopCount > MAX_LOOP_COUNT) {
|
|
268
276
|
return {
|
|
269
277
|
pass: false,
|
|
@@ -273,6 +281,8 @@ export async function specConsistencyResultCheck(changeId, skillOutput) {
|
|
|
273
281
|
consistencyRate: output.consistencyRate,
|
|
274
282
|
totalScenarios: output.totalScenarios,
|
|
275
283
|
coveredScenarios: output.coveredScenarios,
|
|
284
|
+
partialScenarios,
|
|
285
|
+
uncoveredFeatures,
|
|
276
286
|
loopExceeded: true,
|
|
277
287
|
},
|
|
278
288
|
nextAction: 'stop',
|
|
@@ -286,12 +296,14 @@ export async function specConsistencyResultCheck(changeId, skillOutput) {
|
|
|
286
296
|
metrics: {
|
|
287
297
|
consistencyRate: output.consistencyRate,
|
|
288
298
|
uncoveredScenarios: output.uncoveredScenarios,
|
|
299
|
+
partialScenarios,
|
|
300
|
+
uncoveredFeatures,
|
|
289
301
|
},
|
|
290
302
|
nextAction: 'fix',
|
|
291
303
|
fixPrompt: formatConsistencyFixPrompt(output, loopCount),
|
|
292
304
|
};
|
|
293
305
|
}
|
|
294
|
-
// PASS: All scenarios covered
|
|
306
|
+
// PASS: All scenarios covered, no partial, no uncovered features
|
|
295
307
|
return {
|
|
296
308
|
pass: true,
|
|
297
309
|
needFix: false,
|
|
@@ -301,6 +313,7 @@ export async function specConsistencyResultCheck(changeId, skillOutput) {
|
|
|
301
313
|
totalRequirements: output.totalRequirements,
|
|
302
314
|
totalScenarios: output.totalScenarios,
|
|
303
315
|
coveredScenarios: output.coveredScenarios,
|
|
316
|
+
featureTrace: output.featureTrace,
|
|
304
317
|
mapping: output.mapping,
|
|
305
318
|
},
|
|
306
319
|
nextAction: 'continue',
|
|
@@ -709,20 +722,33 @@ function formatMissingUnitTestEvidencePrompt(loopCount) {
|
|
|
709
722
|
`;
|
|
710
723
|
}
|
|
711
724
|
function formatConsistencyFixPrompt(output, loopCount) {
|
|
712
|
-
const uncoveredList = output.uncoveredScenarios.map(s => `- ${s.requirementId}/${s.scenarioId}: ${s.description}`).join('\n');
|
|
725
|
+
const uncoveredList = output.uncoveredScenarios.map(s => `- [missing] ${s.requirementId}/${s.scenarioId}: ${s.description}`).join('\n');
|
|
726
|
+
const partialScenarios = output.partialScenarios || [];
|
|
727
|
+
const partialList = partialScenarios.map(s => `- [partial] ${s.scenario || '(未命名)'}${s.sourceFeature ? ` [${s.sourceFeature}]` : ''}: ${s.warning || '仅命中辅助关键词,疑似未实现'}`).join('\n');
|
|
728
|
+
const uncoveredFeatures = output.featureTrace?.uncoveredFeatures || [];
|
|
729
|
+
const featureList = uncoveredFeatures.map(f => `- [feature] ${f}: 该功能点无落地 Scenario,属功能点遗漏`).join('\n');
|
|
730
|
+
const sections = [];
|
|
731
|
+
if (output.uncoveredScenarios.length > 0) {
|
|
732
|
+
sections.push(`未覆盖 Scenario(${output.uncoveredScenarios.length} 个):\n${uncoveredList}`);
|
|
733
|
+
}
|
|
734
|
+
if (partialScenarios.length > 0) {
|
|
735
|
+
sections.push(`疑似未实现 partial(${partialScenarios.length} 个,仅命中类名/表名等辅助关键词):\n${partialList}`);
|
|
736
|
+
}
|
|
737
|
+
if (uncoveredFeatures.length > 0) {
|
|
738
|
+
sections.push(`遗漏功能点 F(${uncoveredFeatures.length} 个,feature-manifest 有、无落地 Scenario):\n${featureList}`);
|
|
739
|
+
}
|
|
713
740
|
return `
|
|
714
741
|
🔄 Spec-Code Consistency Fix Loop (${loopCount}/${MAX_LOOP_COUNT})
|
|
715
742
|
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
${uncoveredList}
|
|
743
|
+
${sections.join('\n\n')}
|
|
719
744
|
|
|
720
745
|
⚠️ 铁律:Spec is Truth — 文档与代码冲突时,错的一定是代码。
|
|
721
|
-
🚫
|
|
746
|
+
🚫 绝对禁止为凑覆盖率修改 Spec 文件 / 技术方案 / feature-manifest(包括 Requirement 名称、Scenario 描述、Given/When/Then 条件、[F<n>] 标记)。
|
|
722
747
|
|
|
723
748
|
请通过以下方式修复:
|
|
724
|
-
1.
|
|
725
|
-
2.
|
|
749
|
+
1. 对 missing/partial 项:补齐或修改**代码实现**,使其满足 Scenario 描述的业务诉求(partial 项须命中方法名/接口路径/组件名等核心关键词并通过深度检查)
|
|
750
|
+
2. 对遗漏功能点 F:补齐对应代码实现(而非补 PRD/技术方案);确因范围裁剪不做的,登记 proposal.md 功能点覆盖对照表并注明理由
|
|
751
|
+
3. 修复后重新运行 spec-code-consistency 检查验证覆盖情况
|
|
726
752
|
`;
|
|
727
753
|
}
|
|
728
754
|
// ============================================================
|
|
@@ -797,6 +823,8 @@ ${legacySection}
|
|
|
797
823
|
| Requirements 数量 | ${results.specConsistency.metrics.totalRequirements || 0} |
|
|
798
824
|
| Scenario 数量 | ${results.specConsistency.metrics.totalScenarios || 0} |
|
|
799
825
|
| 一致性覆盖率 | ${results.specConsistency.metrics.consistencyRate || 0}% |
|
|
826
|
+
| 疑似未实现 partial 数 | ${results.specConsistency.metrics.partialScenarios?.length || 0} |
|
|
827
|
+
| 遗漏功能点 F | ${results.specConsistency.metrics.uncoveredFeatures?.join(', ') || '—'} |
|
|
800
828
|
| 循环次数 | ${results.specConsistency.loopCount} |
|
|
801
829
|
| 状态 | ${results.specConsistency.pass ? '✅ PASS' : '❌ FAIL'} |
|
|
802
830
|
|
|
@@ -5,6 +5,10 @@ const baseGuardrails = `**约束条件**
|
|
|
5
5
|
- **工作区边界(强制)**:查找文档、检索工程代码、执行 grep/glob/ls 时,范围必须限定在当前 ZhuanSpec 工作区内。禁止使用 \`../**\`、上级目录绝对路径或手动拼接到工作区外路径。Glob 返回相对路径时,必须以本次工具调用的 \`path\` 参数作为基准拼接绝对路径,不得误解为当前目录或上级目录。
|
|
6
6
|
- **知识库参考**:开始任何阶段前,先检查 \`zhuanspec/knowledge/\` 目录。使用 \`rg "[关键词]" zhuanspec/knowledge/\` 搜索相关陷阱和最佳实践,避免重复踩坑。阅读 \`zhuanspec/knowledge/index.md\` 了解项目级知识摘要。
|
|
7
7
|
- **工程代码检索顺序**:凡是需要检索工程代码、配置类、数据模型类或关键实现位置时,必须先调用 \`@skill:load-project-knowledge\` 进行渐进式加载,使用返回的 search_priority 限定检索范围,按优先路径定位代码。Skill 返回的 \`matched_services[].micro_path\` 即各服务 \`.project-wiki/\` 下的知识条目路径,按需读取其中子文档(无论 Skill 内部走 llmwiki 还是 project.md 路径,最终内容均来自各服务的 \`.project-wiki/\`)。仅当 Skill 无匹配结果时,才回退到 \`grep/glob/ls\`。
|
|
8
|
+
- **需求来源读取(红线,全阶段适用)**:读取需求来源(技术方案输入、测试 case、验收标准等)时,按链接类型选用对应读取方式,禁止张冠李戴:
|
|
9
|
+
* **大神页面**(\`zhuanspirit\` 大神文档 / \`--dashen-page-id\` / \`--dashen-url\`):用大神 MCP 工具(如 \`mcp__dashen__getPageContent\`)读取。
|
|
10
|
+
* **zzcase 测试用例**(\`zzcase.zhuanspirit.com/plan/taskDetail/module/{moduleId}/task/{taskId}\`):从 URL 提取 \`moduleId\`,用 \`mcp__caseweb__GET_get2\` 读取。
|
|
11
|
+
* **飞书链接**(\`project.feishu.cn/{space}/story/detail/{id}\` 需求工作项、\`xxx.feishu.cn/wiki/{token}\`、\`xxx.feishu.cn/docx/{id}\` 文档):**禁止用 Fetch/WebFetch 直接抓取飞书网页**(会被安全域名校验拦截而失败),必须用本机飞书 CLI \`lark-cli\` 读取正文(通过 Bash 调用;不确定子命令时先执行 \`lark-cli --help\` 确认用法)。若 lark-cli 未安装/未授权/读取失败,如实报告失败原因与用户需处理的动作(安装 \`npm install -g @larksuite/cli\` 或执行 \`lark-cli auth login\` 授权),不要编造需求内容。
|
|
8
12
|
- 如果需要额外的 ZhuanSpec 约定或澄清,请参考 \`zhuanspec/AGENTS.md\`(位于 \`zhuanspec/\` 目录内 - 如果看不到,请运行 \`ls zhuanspec\` 或 \`zhuanspec update\`)。`;
|
|
9
13
|
const proposalGuardrails = `${baseGuardrails}\n- **强制澄清要求**:在创建任何提案文件之前,必须首先分析用户请求,识别所有不确定或模糊的方面(范围、技术选择、优先级、验收标准等)。如果发现任何模糊之处,必须停止并使用**选项式交互**(如 \`AskQuestion\` 工具)提问,获得明确答复后才能继续。严禁在不确定的情况下自行推测、假设或创建提案。严禁要求用户手动输入大段文字来回答澄清问题。
|
|
10
14
|
- 识别任何模糊或歧义的细节,使用带预设选项的选择题在编辑文件之前询问必要的后续问题。
|
|
@@ -215,6 +219,13 @@ const proposalSteps = `**步骤**
|
|
|
215
219
|
|
|
216
220
|
6. 在 \`changes/<id>/specs/<capability>/spec.md\` 中起草规范增量(每个功能一个文件夹),使用 \`## ADDED|MODIFIED|REMOVED Requirements\`,每个要求至少包含一个 \`#### Scenario:\`,并在相关时交叉引用相关功能。
|
|
217
221
|
|
|
222
|
+
⚠️ **CHECKPOINT [SCENARIO-FEATURE-TRACE](提案↔技术方案主键回填)**:
|
|
223
|
+
当存在技术方案(techDesign/tech-spec.md 或 techDesign/feature-manifest.json)时,本次提案是技术方案 checkList 的下游实现,必须做到「挨个核对、逐条回填」:
|
|
224
|
+
1. **每个 \`#### Scenario:\` 标题末尾必须携带来源标记 \`[F<数字>]\`**(如 \`#### Scenario: 导出发送成员列表 [F6]\`),F 编号取自 techDesign/feature-manifest.json 或 tech-spec.md checkList 的功能点编号;一个 Scenario 覆盖多个功能点时写 \`[F3,F6]\`。
|
|
225
|
+
2. **技术方案 checkList / feature-manifest 中的每一个 F 编号,必须在本提案的 spec.md 中至少被一个 Scenario 覆盖**——这是逐项核对,不允许「整体看着差不多」就跳过。
|
|
226
|
+
3. 核对方式:列出 feature-manifest 全部 F 编号 → 逐个在 spec.md 中搜索 \`[F<n>]\` → 记录命中/缺失。存在缺失的 F 编号即为**遗漏功能点**,必须补齐 Scenario 后才能进入下一步,禁止以「补 PRD / 补技术方案」绕过(PRD/技术方案是上游真相,不得为迁就实现而反向删改)。
|
|
227
|
+
4. 若某个 F 编号本次确实不实现,必须在 proposal.md 的 **功能点覆盖对照表** 中显式标注「本期不做 + 理由 + 负责方确认」,而非静默丢弃。
|
|
228
|
+
|
|
218
229
|
⚠️ **CHECKPOINT [SPEC-REQUIREMENT-FORMAT]**:
|
|
219
230
|
每个 \`### Requirement:\` 块写完后必须自检以下两点,违反任意一条 \`zhuanspec validate\` 会报 ERROR:
|
|
220
231
|
1. **必须有需求描述文本**:header 之后、第一个 \`#### Scenario\` 之前,必须至少有一行非空的需求描述文本;不能直接从 \`### Requirement:\` 跳到 \`#### Scenario:\`。
|
|
@@ -279,14 +290,18 @@ const proposalSteps = `**步骤**
|
|
|
279
290
|
* 循环结束后仍未通过:输出错误报告,等待人工干预
|
|
280
291
|
- 验证通过后,继续下一步
|
|
281
292
|
|
|
282
|
-
9. **技术方案覆盖度自动完善**(当存在技术方案文档时:techDesign/ 目录下的 tech-spec.md,或用户指定了本地技术方案路径):
|
|
293
|
+
9. **技术方案覆盖度自动完善**(当存在技术方案文档时:techDesign/ 目录下的 tech-spec.md / feature-manifest.json,或用户指定了本地技术方案路径):
|
|
283
294
|
- 运行覆盖度检查(通过 Skill \`@skill:tech-spec-coverage-check\`)
|
|
295
|
+
- **功能点(F编号)逐项覆盖核对(强制,先于元素级补全)**:
|
|
296
|
+
* 读取 techDesign/feature-manifest.json 的全部功能点编号(F1…Fn);无 manifest 时退化为解析 tech-spec.md checkList 的编号
|
|
297
|
+
* 逐个 F 编号在本次 specs/ 各 spec.md 中检索 \`[F<n>]\` 标记,产出 \`uncoveredFeatures\`(在技术方案中存在、但提案 Scenario 未覆盖的 F 编号列表)
|
|
298
|
+
* \`uncoveredFeatures\` 非空即判定为**功能点遗漏**:必须为每个缺失 F 补齐带 \`[F<n>]\` 标记的 Scenario;确因范围裁剪不做的,登记到 proposal.md 功能点覆盖对照表并标注理由,不得静默省略
|
|
284
299
|
- 如果发现未覆盖的技术方案元素,**自动补充到对应提案文件**:
|
|
285
300
|
* 未覆盖的数据库表/API → 自动追加到 specs/ 对应 spec.md 的 Requirements 中
|
|
286
301
|
* 未覆盖的类名/接口 → 自动追加到 tasks.md 作为新任务项
|
|
287
302
|
* 未覆盖的配置项/基础设施 → 自动追加到 design.md 对应章节
|
|
288
|
-
- 补充完成后重新检查覆盖率,确认 >= 90%
|
|
289
|
-
- 将覆盖度报告写入 metrics/tech-spec-coverage.json
|
|
303
|
+
- 补充完成后重新检查覆盖率,确认 \`uncoveredFeatures\` 为空 且 元素覆盖率 >= 90%
|
|
304
|
+
- 将覆盖度报告写入 metrics/tech-spec-coverage.json(含 uncoveredFeatures 字段)
|
|
290
305
|
|
|
291
306
|
10. **提案详细度检查(AI 可执行性验证)**:
|
|
292
307
|
验证提案内容是否足够详细,确保 AI 能够仅凭提案内容完整执行所有任务,无需额外猜测或澄清。
|
|
@@ -598,6 +613,20 @@ const applySteps = `**步骤**
|
|
|
598
613
|
- **立刻移交 B5 统一执行重跑**(不得拖到全局 Concerns 汇总;不得将 NEEDS_CONTEXT 当 DONE_WITH_CONCERNS 混过)
|
|
599
614
|
- 若清单为空(本 Wave 无需重跑)→ 显式写 \`— 无需重跑\`,禁止省略该段
|
|
600
615
|
|
|
616
|
+
B4.2. **Scenario 逐项核对(本 Wave,MANDATORY GATE)——「实施代码 ↔ propose 的 Scenario 挨个核对」**:
|
|
617
|
+
- **目的**:本 Wave 涉及的每个 Scenario 实施完成即刻与代码核对,尽早暴露遗漏,避免堆积到 Review 才发现(对应「PRD 已有功能点被静默漏实现」的防线)
|
|
618
|
+
- **执行**:调用 \`spec-code-consistency-check\` Skill,**scope 限定为本 Wave 覆盖的 Scenario**(按本 Wave 任务映射的 spec.md Scenario 及其 \`[F\\d+]\` 标记筛选):
|
|
619
|
+
a. 对每个 Scenario 用**核心关键词**(方法名 / 接口路径 / 组件名 / Consumer 类名 / ES 索引名)精确搜索,仅命中类名/表名等**辅助关键词**不算覆盖(标 \`partial\`)
|
|
620
|
+
b. 对 \`covered\` 项执行深度检查(方法体 > 3 行、有业务调用链、非 stub/TODO-only)
|
|
621
|
+
c. 聚合本 Wave 涉及的 \`[F\\d+]\` 编号,核对每个 F 是否有落地的 Scenario
|
|
622
|
+
- **判定(任一不满足 → 本 Wave GATE FAIL,阻断进入下一 Wave)**:
|
|
623
|
+
* 无 \`missing\`(核心与辅助关键词均未命中的 Scenario)
|
|
624
|
+
* 无 \`partial\`(仅命中辅助关键词的疑似项必须澄清为 covered 或补齐代码)
|
|
625
|
+
* 无 \`todo-only\` / \`stub-suspected\`(骨架冒充完成)
|
|
626
|
+
* 本 Wave 的 \`featureTrace.uncoveredFeatures\` 为空
|
|
627
|
+
- **发现遗漏时的唯一正确动作**:**补齐缺失的代码实现**(回到 B5 以 \`Execution Mode = RETRY\` 重新下发对应任务);🚫 **绝对禁止**为凑覆盖率而改删 spec.md / 技术方案 / feature-manifest(上游是真相,Spec is Truth)
|
|
628
|
+
- **写回集成报告**:新增 \`## 🔍 本 Wave Scenario 核对\` 段,列出每个 Scenario 的 \`status\`(covered/partial/missing)、命中关键词类型(core/aux)、深度检查结果、sourceFeature;即使全部通过也要写 \`— 全部覆盖\`
|
|
629
|
+
|
|
601
630
|
B5. **阻塞任务重试(BLOCKED ∪ NEEDS_CONTEXT 合并处理)**:
|
|
602
631
|
- 重试对象覆盖两类:
|
|
603
632
|
a. 编译失败 / 测试失败被标记为 \`BLOCKED\` 的任务(原有范围)
|
|
@@ -742,7 +771,7 @@ const archiveReferences = `**参考**
|
|
|
742
771
|
- 会话分析 (\`session-analytics\`):记录本次会话的效率指标,用于改进工作流。`;
|
|
743
772
|
const designGuardrails = `${baseGuardrails}\n- **独立设计阶段**:techDesign 命令用于在 proposal 之前生成技术设计请求文档,不依赖变更提案。设计文档可作为后续提案的输入。
|
|
744
773
|
- **Skill 调用连续性(红线)**:在 techDesign 流程中调用任何辅助类 Skill(如 \`@skill:load-project-knowledge\`)后,**必须立即推进到下一编号步骤**,禁止把 Skill 的输出当作流程终态,禁止停顿等待用户输入“继续/下一步”等确认词。只有在显式标注的 AskUserQuestion 步骤(如开发范围确认、需求来源确认、外部依赖确认)才允许暂停等待用户。
|
|
745
|
-
-
|
|
774
|
+
- **需求澄清优先**:在生成设计请求前,必须确认需求来源(大神页面、飞书链接/文档、需求描述文本等);飞书链接的读取方式见 baseGuardrails 的「需求来源读取」红线。
|
|
746
775
|
- **开发范围前置确认(硬约束)**:在调用技术方案 Skill 前,**必须**先通过 AskUserQuestion 确认开发范围是“仅后端开发”还是“全栈开发”,根据答复选择对应 Skill(仅后端=\`generate-tech-spec-md-skill\`,全栈=\`generate-fullstack-tech-spec-skill\`),禁止默认或跳过此确认环节。
|
|
747
776
|
- **Skill 可用性前置检查(硬约束)**:在实际调用技术方案 Skill 前,**必须**先校验目标 Skill 是否已安装且可用;**若不可用,立即中断流程**并提示用户到 Skill 市场安装对应 Skill,禁止以人工编写/其他 Skill 代替。
|
|
748
777
|
- **Skill 调用为主**:技术方案生成主要通过 \`generate-tech-spec-md-skill\`(仅后端)或 \`generate-fullstack-tech-spec-skill\`(全栈)Skill 完成,而非直接运行 CLI 命令。
|
|
@@ -873,25 +902,7 @@ const designSteps = `**步骤**
|
|
|
873
902
|
- **状态为 ⚠️ 的依赖**:必须说明兜底方案(如"ES 不可用时用 DB 查询兜底")
|
|
874
903
|
- 该矩阵将被 propose/apply 阶段复用,作为 Subagent L1 上下文注入的根据
|
|
875
904
|
|
|
876
|
-
7.5.
|
|
877
|
-
- 使用 AskUserQuestion 询问用户是否需要基于当前技术方案生成测试 case:
|
|
878
|
-
* header: "测试Case生成"
|
|
879
|
-
* question: "技术方案已生成,是否需要基于当前方案生成测试 case?"
|
|
880
|
-
* 选项 A:生成测试 case(推荐) → description: 调用 test-case-generator 技能,基于技术方案和需求文档自动生成测试用例,产出将用于后续 Propose 阶段的 TDD 流程
|
|
881
|
-
* 选项 B:跳过,不生成 → description: 跳过测试 case 生成,后续 Propose 阶段仍可手动提供测试 case
|
|
882
|
-
- 用户选择"生成测试 case":
|
|
883
|
-
* 从 \`techDesign/tech-spec.md\` 的 \`## 文档信息\` 表格中提取"需求来源"字段(可能是 dashen URL 或飞书文档链接)
|
|
884
|
-
* 调用 \`@skill:test-case-generator\` 技能:
|
|
885
|
-
- 输入技术方案:\`techDesign/tech-spec.md\` 全文
|
|
886
|
-
- 输入需求来源:提取到的 URL(skill 内部根据链接类型通过 dashen MCP / 飞书文档 API 读取需求正文)
|
|
887
|
-
* 输出目录:\`changes/<change-id>/techDesign/\`(测试 case 源文件)
|
|
888
|
-
* Skill 完成后,在 progress.json 的 events 字段追加:\`{ "event": "test-case-generated", "source": "test-case-generator", "timestamp": "<ISO>" }\`
|
|
889
|
-
* ⚠️ **继续执行步骤 8(输出摘要),不得在此停留**
|
|
890
|
-
- 用户选择"跳过":
|
|
891
|
-
* 在 progress.json 的 events 字段追加:\`{ "event": "test-case-skipped", "timestamp": "<ISO>" }\`
|
|
892
|
-
* 直接进入步骤 8
|
|
893
|
-
|
|
894
|
-
7.6. **涉及工程清单输出(阶段 6 定稿产物,必须生成)**:
|
|
905
|
+
7.5. **涉及工程清单输出(阶段 6 定稿产物,必须生成)**:
|
|
895
906
|
- 在技术方案定稿时,从 Skill 生成的 \`tech-spec.md\`、\`matched_services\` 和改动点定位结果中提取涉及工程
|
|
896
907
|
- 输出到与技术方案同目录:
|
|
897
908
|
\`zhuanspec/changes/{change-id}/techDesign/affected-projects.md\`
|
|
@@ -911,7 +922,6 @@ const designSteps = `**步骤**
|
|
|
911
922
|
- 告知用户生成的文档路径和实际调用的 Skill 名称
|
|
912
923
|
- 告知用户涉及工程清单路径:\`zhuanspec/changes/{change-id}/techDesign/affected-projects.md\`
|
|
913
924
|
- **明确说明**:techDesign 阶段仅保存进度数据(progress.json)、技术方案(含外部依赖矩阵)和涉及工程清单,不创建 proposal.md 和 tasks.md
|
|
914
|
-
- 如果步骤 7.5 生成了测试 case,额外提示:「✅ 已生成测试 case 源文件,后续 Propose 阶段将自动启用 TDD 模式并调用 tdd-testcase-generator 转换为研发 TDD testcase」
|
|
915
925
|
- 提示 phase=techDesign
|
|
916
926
|
- 提示下一步可以使用 \`/zhuanspec:proposal\` 创建变更提案(复用目录)`;
|
|
917
927
|
const designReferences = `**参考**
|
|
@@ -977,17 +987,18 @@ const reviewSteps = `**步骤**
|
|
|
977
987
|
|
|
978
988
|
2. **执行最多六轨顺序串行校验(强制前四轨 + 可选后两轨)**:按 2.1 → 2.2 → 2.3 → 2.4 → 2.5 → 2.6 顺序逐轨执行,**禁止并行**,前一轨未完成禁止启动后一轨。
|
|
979
989
|
|
|
980
|
-
**2.1 轨道 1 — Spec-Code
|
|
990
|
+
**2.1 轨道 1 — Spec-Code 一致性轨(含功能点 F 编号全链终检,「再次核验 1-3 项」)**
|
|
981
991
|
- 进入条件:Phase 已切换为 review。
|
|
982
992
|
- 执行动作:
|
|
983
993
|
- 运行 \`zhuanspec validate <change-id> --strict\`,确认 \`spec-code-consistent\` 规则通过。
|
|
984
|
-
-
|
|
985
|
-
|
|
994
|
+
- **全量调用 \`spec-code-consistency-check\` Skill**(Review 触发 = 全量终检,覆盖全部 Scenario,而非仅某 Wave):对每个 Scenario 用**核心关键词**精确定位,仅命中类名/表名等辅助关键词标 \`partial\`;对 \`covered\` 项做深度检查(方法体 > 3 行、有业务调用链、非 stub/TODO-only)。
|
|
995
|
+
- **F 编号全链核对(再次核验层1-3)**:以 \`techDesign/feature-manifest.json\` 的 F 编号为分母,聚合各 Scenario 标题末尾的 \`[F\\d+]\` 标记,核对每个功能点 F 是否有落地且非 stub 的 Scenario;缺失的计入 \`featureTrace.uncoveredFeatures\`。
|
|
996
|
+
- 自闭环修复(最多 3 轮):发现 \`uncoveredScenarios\` / \`partialScenarios\` 非空或 \`uncoveredFeatures\` 非空时,遵循"Spec is Truth"铁律,**只补齐/修改代码实现**使其匹配 Scenario,**🚫 绝对禁止修改 spec 文件 / 技术方案 / feature-manifest**,修复后重跑 validate + Skill。
|
|
986
997
|
- 产物落盘:**AI 将结果写入** \`zhuanspec/changes/<id>/review/spec-consistency-result.json\`,格式:
|
|
987
998
|
\`\`\`json
|
|
988
|
-
{ "consistencyRate": 100, "totalRequirements": 0, "totalScenarios": 0, "coveredScenarios": 0, "uncoveredScenarios": [], "mapping": [], "loopCount": 0 }
|
|
999
|
+
{ "consistencyRate": 100, "totalRequirements": 0, "totalScenarios": 0, "coveredScenarios": 0, "uncoveredScenarios": [], "partialScenarios": [], "featureTrace": { "totalFeatures": 0, "coveredFeatures": 0, "uncoveredFeatures": [] }, "mapping": [], "loopCount": 0 }
|
|
989
1000
|
\`\`\`
|
|
990
|
-
-
|
|
1001
|
+
- 退出条件(全部满足):\`consistencyRate === 100\` 且 \`uncoveredScenarios.length === 0\` 且 \`partialScenarios.length === 0\` 且 \`featureTrace.uncoveredFeatures.length === 0\`。任一不满足禁止进入 2.2。
|
|
991
1002
|
|
|
992
1003
|
**2.2 轨道 2 — Unit Test 轨(必须调用 Skill)**
|
|
993
1004
|
- 进入条件:2.1 已 PASS。
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zhuan-ai/zhuanspec",
|
|
3
|
-
"version": "2.17.
|
|
3
|
+
"version": "2.17.15",
|
|
4
4
|
"description": "AI-native system for spec-driven development",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"zhuanspec",
|
|
@@ -39,6 +39,26 @@
|
|
|
39
39
|
"!dist/**/__tests__",
|
|
40
40
|
"!dist/**/*.map"
|
|
41
41
|
],
|
|
42
|
+
"scripts": {
|
|
43
|
+
"lint": "eslint src/",
|
|
44
|
+
"build": "node build.js",
|
|
45
|
+
"dev": "tsc --watch",
|
|
46
|
+
"dev:cli": "pnpm build && node bin/zhuanspec.js",
|
|
47
|
+
"test": "vitest run",
|
|
48
|
+
"test:watch": "vitest",
|
|
49
|
+
"test:ui": "vitest --ui",
|
|
50
|
+
"test:coverage": "vitest --coverage",
|
|
51
|
+
"test:postinstall": "node scripts/postinstall.js",
|
|
52
|
+
"prepare": "npm run build",
|
|
53
|
+
"prepublishOnly": "npm run build",
|
|
54
|
+
"postinstall": "node scripts/postinstall.js",
|
|
55
|
+
"check:pack-version": "node scripts/pack-version-check.mjs",
|
|
56
|
+
"diagnose:cursor": "node scripts/diagnose-cursor-commands.js",
|
|
57
|
+
"release": "pnpm run release:ci",
|
|
58
|
+
"release:ci": "pnpm run check:pack-version && pnpm exec changeset publish",
|
|
59
|
+
"release:local": "pnpm exec changeset version && pnpm run check:pack-version && pnpm exec changeset publish",
|
|
60
|
+
"changeset": "changeset"
|
|
61
|
+
},
|
|
42
62
|
"engines": {
|
|
43
63
|
"node": ">=20.19.0"
|
|
44
64
|
},
|
|
@@ -60,23 +80,5 @@
|
|
|
60
80
|
"ora": "^8.2.0",
|
|
61
81
|
"yaml": "^2.8.2",
|
|
62
82
|
"zod": "^4.0.17"
|
|
63
|
-
},
|
|
64
|
-
"scripts": {
|
|
65
|
-
"lint": "eslint src/",
|
|
66
|
-
"build": "node build.js",
|
|
67
|
-
"dev": "tsc --watch",
|
|
68
|
-
"dev:cli": "pnpm build && node bin/zhuanspec.js",
|
|
69
|
-
"test": "vitest run",
|
|
70
|
-
"test:watch": "vitest",
|
|
71
|
-
"test:ui": "vitest --ui",
|
|
72
|
-
"test:coverage": "vitest --coverage",
|
|
73
|
-
"test:postinstall": "node scripts/postinstall.js",
|
|
74
|
-
"postinstall": "node scripts/postinstall.js",
|
|
75
|
-
"check:pack-version": "node scripts/pack-version-check.mjs",
|
|
76
|
-
"diagnose:cursor": "node scripts/diagnose-cursor-commands.js",
|
|
77
|
-
"release": "pnpm run release:ci",
|
|
78
|
-
"release:ci": "pnpm run check:pack-version && pnpm exec changeset publish",
|
|
79
|
-
"release:local": "pnpm exec changeset version && pnpm run check:pack-version && pnpm exec changeset publish",
|
|
80
|
-
"changeset": "changeset"
|
|
81
83
|
}
|
|
82
|
-
}
|
|
84
|
+
}
|