@zhuan-ai/zhuanspec 2.9.5 → 2.11.2
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/cli/hooks.js +8 -8
- package/dist/cli/index.js +1 -0
- package/dist/commands/design.d.ts +12 -2
- package/dist/commands/design.js +83 -10
- package/dist/commands/progress.js +11 -0
- package/dist/commands/review.d.ts +1 -25
- package/dist/commands/review.js +51 -408
- package/dist/core/completions/command-registry.js +4 -0
- package/dist/core/hooks/collect-knowledge.d.ts +13 -5
- package/dist/core/hooks/collect-knowledge.js +56 -164
- package/dist/core/hooks/deviation-check.js +159 -55
- package/dist/core/hooks/init.js +46 -0
- package/dist/core/hooks/post-apply.js +2 -0
- package/dist/core/hooks/pre-archive.js +13 -14
- package/dist/core/hooks/pre-review.d.ts +27 -0
- package/dist/core/hooks/pre-review.js +169 -0
- package/dist/core/hooks/record-progress.d.ts +25 -0
- package/dist/core/hooks/record-progress.js +88 -1
- package/dist/core/hooks/review-hooks.js +176 -7
- package/dist/core/hooks/review-orchestrator.js +58 -11
- package/dist/core/hooks/tdd-phase-hook.d.ts +59 -0
- package/dist/core/hooks/tdd-phase-hook.js +313 -0
- package/dist/core/init.d.ts +1 -0
- package/dist/core/init.js +45 -2
- package/dist/core/templates/agents-template.d.ts +1 -1
- package/dist/core/templates/agents-template.js +139 -23
- package/dist/core/templates/skill-templates.js +21 -1
- package/dist/core/templates/slash-command-templates.js +163 -63
- package/dist/core/templates/tasks-template.js +89 -0
- package/dist/core/update.d.ts +3 -0
- package/dist/core/update.js +85 -2
- package/dist/core/validation/strict-rules.d.ts +47 -0
- package/dist/core/validation/strict-rules.js +589 -42
- package/dist/utils/git-repo-detector.js +1 -1
- package/dist/utils/phase-utils.js +14 -1
- package/package.json +22 -20
package/dist/cli/hooks.js
CHANGED
|
@@ -240,7 +240,7 @@ program
|
|
|
240
240
|
codeReview: codeReviewResult,
|
|
241
241
|
unitTest: unitTestResult,
|
|
242
242
|
specConsistency: specConsistencyResult,
|
|
243
|
-
reportPath: `zhuanspec/changes/${changeId}/review-report.md`,
|
|
243
|
+
reportPath: `zhuanspec/changes/${changeId}/review/review-report.md`,
|
|
244
244
|
}));
|
|
245
245
|
}
|
|
246
246
|
else {
|
|
@@ -355,10 +355,10 @@ async function runPhaseTransitionCheck(options) {
|
|
|
355
355
|
};
|
|
356
356
|
case 'archive':
|
|
357
357
|
// Review → Archive: Check for review completion
|
|
358
|
-
const
|
|
359
|
-
const reviewPath = path.join(
|
|
360
|
-
const testsPath = path.join(
|
|
361
|
-
const reportPath = path.join(
|
|
358
|
+
const reviewDir = path.join(changeDir, 'review');
|
|
359
|
+
const reviewPath = path.join(reviewDir, 'code-review-result.json');
|
|
360
|
+
const testsPath = path.join(reviewDir, 'unit-test-result.json');
|
|
361
|
+
const reportPath = path.join(reviewDir, 'review-report.md');
|
|
362
362
|
const checks = {
|
|
363
363
|
reviewCompleted: false,
|
|
364
364
|
testsCompleted: false,
|
|
@@ -370,11 +370,11 @@ async function runPhaseTransitionCheck(options) {
|
|
|
370
370
|
if (!checks.reviewCompleted || !checks.testsCompleted || !checks.reportExists) {
|
|
371
371
|
const missing = [];
|
|
372
372
|
if (!checks.reviewCompleted)
|
|
373
|
-
missing.push('review.json');
|
|
373
|
+
missing.push('review/code-review-result.json');
|
|
374
374
|
if (!checks.testsCompleted)
|
|
375
|
-
missing.push('
|
|
375
|
+
missing.push('review/unit-test-result.json');
|
|
376
376
|
if (!checks.reportExists)
|
|
377
|
-
missing.push('review-report.md');
|
|
377
|
+
missing.push('review/review-report.md');
|
|
378
378
|
return {
|
|
379
379
|
continue: false,
|
|
380
380
|
systemMessage: `✗ Phase transition blocked: ${fromPhase} → ${targetPhase}\n\nMissing review artifacts:\n${missing.map(m => `- ${m}`).join('\n')}\n\nPlease complete Review phase before archiving.`,
|
package/dist/cli/index.js
CHANGED
|
@@ -220,6 +220,7 @@ program
|
|
|
220
220
|
.command('review [change-name]')
|
|
221
221
|
.description('Run ZhuanSpec review and unit test verification')
|
|
222
222
|
.option('--json', 'Output review report as JSON')
|
|
223
|
+
.option('--force', 'Skip task completion check and force review')
|
|
223
224
|
.option('--test-command <command>', 'Test command to execute (default: npm test)')
|
|
224
225
|
.option('--coverage-threshold <n>', 'Coverage threshold percentage (default: 80)')
|
|
225
226
|
.action(async (changeName, options) => {
|
|
@@ -18,13 +18,22 @@ export declare class DesignCommand {
|
|
|
18
18
|
private bindDesignSourceToChange;
|
|
19
19
|
private selectChangeInteractively;
|
|
20
20
|
/**
|
|
21
|
-
* Validate design.md for required sections and optionally auto-fix
|
|
21
|
+
* Validate design.md for required sections and optionally auto-fix.
|
|
22
|
+
* Uses dynamic section derivation: always-required sections are checked for all proposals,
|
|
23
|
+
* while conditional sections are only required when techDesign/tech-spec.md content matches.
|
|
22
24
|
*/
|
|
23
25
|
private validateDesign;
|
|
24
26
|
/**
|
|
25
27
|
* Auto-fix design content by adding missing section skeletons
|
|
26
28
|
*/
|
|
27
29
|
private autoFixDesign;
|
|
30
|
+
/**
|
|
31
|
+
* Derive required design.md sections dynamically.
|
|
32
|
+
* Always-required: 背景/Background, 决策/Decision, 风险/Risks.
|
|
33
|
+
* Conditional sections are added based on techDesign/tech-spec.md content.
|
|
34
|
+
* When no techDesign directory exists, only always-required sections are enforced (backward compat).
|
|
35
|
+
*/
|
|
36
|
+
private getRequiredSections;
|
|
28
37
|
/**
|
|
29
38
|
* Generate a kebab-case change name from design context
|
|
30
39
|
*/
|
|
@@ -32,7 +41,8 @@ export declare class DesignCommand {
|
|
|
32
41
|
/**
|
|
33
42
|
* Create a new change directory for techDesign phase
|
|
34
43
|
* - Creates change directory using createChange()
|
|
35
|
-
* - Creates .tech-design marker file
|
|
44
|
+
* - Creates .tech-design marker file (backward compat)
|
|
45
|
+
* - Creates techDesign/ directory with tech-spec.md placeholder
|
|
36
46
|
* - Initializes progress.json with phase=techDesign
|
|
37
47
|
* - Creates design.md skeleton
|
|
38
48
|
* - Creates doc/ request file
|
package/dist/commands/design.js
CHANGED
|
@@ -14,6 +14,25 @@ const DEFAULT_DESIGN_TEMPLATE = `## 背景
|
|
|
14
14
|
- Decision: [内容和原因]
|
|
15
15
|
- Alternatives considered: [选项 + 理由]
|
|
16
16
|
|
|
17
|
+
## 接口设计
|
|
18
|
+
<!-- 接口签名、入参出参字段类型、枚举值、SCF 方法名 -->
|
|
19
|
+
|
|
20
|
+
## 数据模型
|
|
21
|
+
<!-- DB schema 变更、实体类字段、索引设计 -->
|
|
22
|
+
|
|
23
|
+
## 业务流程
|
|
24
|
+
<!-- Mermaid 流程图或时序图 -->
|
|
25
|
+
\`\`\`mermaid
|
|
26
|
+
flowchart TD
|
|
27
|
+
A[开始] --> B[...]
|
|
28
|
+
\`\`\`
|
|
29
|
+
|
|
30
|
+
## 实现细节
|
|
31
|
+
<!-- 分层实现说明(Component→Application→Domain→Assemble→DAO)、关键逻辑、复用点 -->
|
|
32
|
+
|
|
33
|
+
## 边界条件 / 异常处理
|
|
34
|
+
<!-- 参数校验规则、错误码、降级策略 -->
|
|
35
|
+
|
|
17
36
|
## 风险 / 权衡
|
|
18
37
|
- [风险] -> 缓解措施
|
|
19
38
|
|
|
@@ -190,7 +209,9 @@ export class DesignCommand {
|
|
|
190
209
|
}
|
|
191
210
|
}
|
|
192
211
|
/**
|
|
193
|
-
* Validate design.md for required sections and optionally auto-fix
|
|
212
|
+
* Validate design.md for required sections and optionally auto-fix.
|
|
213
|
+
* Uses dynamic section derivation: always-required sections are checked for all proposals,
|
|
214
|
+
* while conditional sections are only required when techDesign/tech-spec.md content matches.
|
|
194
215
|
*/
|
|
195
216
|
async validateDesign(changeName, autoFix) {
|
|
196
217
|
const changeDir = path.join(process.cwd(), 'zhuanspec', 'changes', changeName);
|
|
@@ -200,13 +221,8 @@ export class DesignCommand {
|
|
|
200
221
|
}
|
|
201
222
|
const content = await FileSystemUtils.readFile(designPath);
|
|
202
223
|
const issues = [];
|
|
203
|
-
//
|
|
204
|
-
const requiredSections =
|
|
205
|
-
{ pattern: /^##\s+(背景|Background)/im, name: '背景/Background', skeleton: '## 背景\n\n[请补充背景、约束、利益相关者]\n' },
|
|
206
|
-
{ pattern: /^##\s+(目标|Goals|非目标|Non-Goals)/im, name: '目标/Goals', skeleton: '## 目标 / 非目标\n\n- Goals: [...]\n- Non-Goals: [...]\n' },
|
|
207
|
-
{ pattern: /^##\s+(决策|Decision)/im, name: '决策/Decision', skeleton: '## 决策\n\n- Decision: [内容和原因]\n- Alternatives considered: [选项 + 理由]\n' },
|
|
208
|
-
{ pattern: /^##\s+(风险|权衡|Risks)/im, name: '风险/Risks', skeleton: '## 风险 / 权衡\n\n- [风险] -> 缓解措施\n' },
|
|
209
|
-
];
|
|
224
|
+
// Dynamically derive required sections based on techDesign content
|
|
225
|
+
const requiredSections = await this.getRequiredSections(changeDir);
|
|
210
226
|
for (const section of requiredSections) {
|
|
211
227
|
if (!section.pattern.test(content)) {
|
|
212
228
|
issues.push({
|
|
@@ -270,6 +286,58 @@ export class DesignCommand {
|
|
|
270
286
|
}
|
|
271
287
|
return result;
|
|
272
288
|
}
|
|
289
|
+
/**
|
|
290
|
+
* Derive required design.md sections dynamically.
|
|
291
|
+
* Always-required: 背景/Background, 决策/Decision, 风险/Risks.
|
|
292
|
+
* Conditional sections are added based on techDesign/tech-spec.md content.
|
|
293
|
+
* When no techDesign directory exists, only always-required sections are enforced (backward compat).
|
|
294
|
+
*/
|
|
295
|
+
async getRequiredSections(changePath) {
|
|
296
|
+
// Always-required sections for every proposal
|
|
297
|
+
const sections = [
|
|
298
|
+
{ pattern: /^##\s+(背景|Background)/im, name: '背景/Background', skeleton: '## 背景\n\n[请补充背景、约束、利益相关者]\n' },
|
|
299
|
+
{ pattern: /^##\s+(决策|Decision)/im, name: '决策/Decision', skeleton: '## 决策\n\n- Decision: [内容和原因]\n- Alternatives considered: [选项 + 理由]\n' },
|
|
300
|
+
{ pattern: /^##\s+(风险|权衡|Risks)/im, name: '风险/Risks', skeleton: '## 风险 / 权衡\n\n- [风险] -> 缓解措施\n' },
|
|
301
|
+
];
|
|
302
|
+
// Conditionally add sections based on tech-spec.md content
|
|
303
|
+
const techSpecPath = path.join(changePath, 'techDesign', 'tech-spec.md');
|
|
304
|
+
if (await FileSystemUtils.fileExists(techSpecPath)) {
|
|
305
|
+
const techContent = await fs.readFile(techSpecPath, 'utf-8');
|
|
306
|
+
// Tech spec has DDL / table creation → require Database Design
|
|
307
|
+
if (/CREATE\s+TABLE|DDL|建表|ALTER\s+TABLE/i.test(techContent)) {
|
|
308
|
+
sections.push({
|
|
309
|
+
pattern: /^##\s+(数据模型|数据库设计|Database\s*Design)/im,
|
|
310
|
+
name: '数据模型/Database Design',
|
|
311
|
+
skeleton: '## 数据模型\n\n<!-- DB schema 变更、实体类字段、索引设计 -->\n',
|
|
312
|
+
});
|
|
313
|
+
}
|
|
314
|
+
// Tech spec has API / interface definitions → require API Contract
|
|
315
|
+
if (/接口|API|SCF|RPC|Controller|@RequestMapping/i.test(techContent)) {
|
|
316
|
+
sections.push({
|
|
317
|
+
pattern: /^##\s+(接口设计|接口契约|API\s*Contract|Interface)/im,
|
|
318
|
+
name: '接口设计/API Contract',
|
|
319
|
+
skeleton: '## 接口设计\n\n<!-- 接口签名、入参出参字段类型、枚举值、SCF 方法名 -->\n',
|
|
320
|
+
});
|
|
321
|
+
}
|
|
322
|
+
// Tech spec has ES / MQ / Cache → require Infrastructure
|
|
323
|
+
if (/ElasticSearch|ES.*索引|mapping|MQ|RocketMQ|Consumer|Redis|缓存|Cache/i.test(techContent)) {
|
|
324
|
+
sections.push({
|
|
325
|
+
pattern: /^##\s+(基础设施|Infrastructure)/im,
|
|
326
|
+
name: '基础设施/Infrastructure',
|
|
327
|
+
skeleton: '## 基础设施\n\n<!-- ES / MQ / Cache 等基础设施设计 -->\n',
|
|
328
|
+
});
|
|
329
|
+
}
|
|
330
|
+
// Tech spec has frontend design → require Frontend Design
|
|
331
|
+
if (/前端|页面|组件|路由|Frontend|Component|React|Vue/i.test(techContent)) {
|
|
332
|
+
sections.push({
|
|
333
|
+
pattern: /^##\s+(前端设计|Frontend\s*Design)/im,
|
|
334
|
+
name: '前端设计/Frontend Design',
|
|
335
|
+
skeleton: '## 前端设计\n\n<!-- 页面布局、组件结构、路由设计 -->\n',
|
|
336
|
+
});
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
return sections;
|
|
340
|
+
}
|
|
273
341
|
/**
|
|
274
342
|
* Generate a kebab-case change name from design context
|
|
275
343
|
*/
|
|
@@ -307,7 +375,8 @@ export class DesignCommand {
|
|
|
307
375
|
/**
|
|
308
376
|
* Create a new change directory for techDesign phase
|
|
309
377
|
* - Creates change directory using createChange()
|
|
310
|
-
* - Creates .tech-design marker file
|
|
378
|
+
* - Creates .tech-design marker file (backward compat)
|
|
379
|
+
* - Creates techDesign/ directory with tech-spec.md placeholder
|
|
311
380
|
* - Initializes progress.json with phase=techDesign
|
|
312
381
|
* - Creates design.md skeleton
|
|
313
382
|
* - Creates doc/ request file
|
|
@@ -319,7 +388,11 @@ export class DesignCommand {
|
|
|
319
388
|
const changeDir = path.join(projectRoot, 'zhuanspec', 'changes', changeName);
|
|
320
389
|
// 2. Initialize progress.json with phase=techDesign
|
|
321
390
|
await initializeProgress(changeName, 'techDesign');
|
|
322
|
-
// 3. Create
|
|
391
|
+
// 3. Create techDesign/ directory and tech-spec.md placeholder
|
|
392
|
+
const techDesignDir = path.join(changeDir, 'techDesign');
|
|
393
|
+
await FileSystemUtils.createDirectory(techDesignDir);
|
|
394
|
+
await FileSystemUtils.writeFile(path.join(techDesignDir, 'tech-spec.md'), `# Tech Spec\n\ncreated: ${new Date().toISOString()}\nsource: ${context.desc || context.dashenPageId || 'manual'}\n\n> This file will be populated by the techDesign skill.\n`);
|
|
395
|
+
// 3b. Also create .tech-design marker for backward compatibility
|
|
323
396
|
await FileSystemUtils.writeFile(path.join(changeDir, '.tech-design'), `created: ${new Date().toISOString()}\nsource: ${context.desc || context.dashenPageId || 'manual'}\n`);
|
|
324
397
|
// 4. Create design.md skeleton
|
|
325
398
|
await FileSystemUtils.writeFile(path.join(changeDir, 'design.md'), DEFAULT_DESIGN_TEMPLATE);
|
|
@@ -312,6 +312,17 @@ export class ProgressCommand {
|
|
|
312
312
|
else if (completedCount === totalTasks && totalTasks > 0) {
|
|
313
313
|
console.log('✅ 全部任务完成,准备进入 Review 阶段');
|
|
314
314
|
}
|
|
315
|
+
// Proposal changes history
|
|
316
|
+
if (progress?.proposalChanges && progress.proposalChanges.length > 0) {
|
|
317
|
+
console.log('');
|
|
318
|
+
console.log('📝 提案修改历史:');
|
|
319
|
+
for (const change of progress.proposalChanges) {
|
|
320
|
+
console.log(` [${change.timestamp}] ${change.triggeredBy} (${change.phase})${change.relatedDeviationId ? ` ← ${change.relatedDeviationId}` : ''}`);
|
|
321
|
+
for (const file of change.modifiedFiles) {
|
|
322
|
+
console.log(` ${file.changeType}: ${file.filePath} - ${file.summary}`);
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
}
|
|
315
326
|
}
|
|
316
327
|
generateProgressBar(percentage, blocks) {
|
|
317
328
|
const filled = Math.round((percentage / 100) * blocks);
|
|
@@ -1,32 +1,8 @@
|
|
|
1
1
|
export declare class ReviewCommand {
|
|
2
2
|
execute(changeName?: string, options?: {
|
|
3
3
|
json?: boolean;
|
|
4
|
-
|
|
5
|
-
coverageThreshold?: string;
|
|
4
|
+
force?: boolean;
|
|
6
5
|
}): Promise<void>;
|
|
7
6
|
private selectChangeInteractively;
|
|
8
|
-
private collectRequirementNames;
|
|
9
|
-
private collectTouchedFileContent;
|
|
10
|
-
private runTests;
|
|
11
|
-
private buildSummary;
|
|
12
|
-
private buildSpecConsistency;
|
|
13
|
-
private printSummary;
|
|
14
|
-
/**
|
|
15
|
-
* Fetch Sonar issues using MCP
|
|
16
|
-
* Uses the first detected repository's branch for SonarQube queries
|
|
17
|
-
*/
|
|
18
|
-
private fetchSonarIssues;
|
|
19
|
-
/**
|
|
20
|
-
* Collect all Scenarios from delta specs
|
|
21
|
-
*/
|
|
22
|
-
private collectScenarios;
|
|
23
|
-
/**
|
|
24
|
-
* Find test files in the project
|
|
25
|
-
*/
|
|
26
|
-
private findTestFiles;
|
|
27
|
-
/**
|
|
28
|
-
* Convert Scenario name to possible test method names
|
|
29
|
-
*/
|
|
30
|
-
private scenarioToTestMethodNames;
|
|
31
7
|
}
|
|
32
8
|
//# sourceMappingURL=review.d.ts.map
|