@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.
Files changed (36) hide show
  1. package/dist/cli/hooks.js +8 -8
  2. package/dist/cli/index.js +1 -0
  3. package/dist/commands/design.d.ts +12 -2
  4. package/dist/commands/design.js +83 -10
  5. package/dist/commands/progress.js +11 -0
  6. package/dist/commands/review.d.ts +1 -25
  7. package/dist/commands/review.js +51 -408
  8. package/dist/core/completions/command-registry.js +4 -0
  9. package/dist/core/hooks/collect-knowledge.d.ts +13 -5
  10. package/dist/core/hooks/collect-knowledge.js +56 -164
  11. package/dist/core/hooks/deviation-check.js +159 -55
  12. package/dist/core/hooks/init.js +46 -0
  13. package/dist/core/hooks/post-apply.js +2 -0
  14. package/dist/core/hooks/pre-archive.js +13 -14
  15. package/dist/core/hooks/pre-review.d.ts +27 -0
  16. package/dist/core/hooks/pre-review.js +169 -0
  17. package/dist/core/hooks/record-progress.d.ts +25 -0
  18. package/dist/core/hooks/record-progress.js +88 -1
  19. package/dist/core/hooks/review-hooks.js +176 -7
  20. package/dist/core/hooks/review-orchestrator.js +58 -11
  21. package/dist/core/hooks/tdd-phase-hook.d.ts +59 -0
  22. package/dist/core/hooks/tdd-phase-hook.js +313 -0
  23. package/dist/core/init.d.ts +1 -0
  24. package/dist/core/init.js +45 -2
  25. package/dist/core/templates/agents-template.d.ts +1 -1
  26. package/dist/core/templates/agents-template.js +139 -23
  27. package/dist/core/templates/skill-templates.js +21 -1
  28. package/dist/core/templates/slash-command-templates.js +163 -63
  29. package/dist/core/templates/tasks-template.js +89 -0
  30. package/dist/core/update.d.ts +3 -0
  31. package/dist/core/update.js +85 -2
  32. package/dist/core/validation/strict-rules.d.ts +47 -0
  33. package/dist/core/validation/strict-rules.js +589 -42
  34. package/dist/utils/git-repo-detector.js +1 -1
  35. package/dist/utils/phase-utils.js +14 -1
  36. 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 metricsDir = path.join(changeDir, 'metrics');
359
- const reviewPath = path.join(metricsDir, 'review.json');
360
- const testsPath = path.join(metricsDir, 'tests.json');
361
- const reportPath = path.join(changeDir, 'review-report.md');
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('tests.json');
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
@@ -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
- // Check required sections: 背景/目标/决策/风险
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 .tech-design marker file
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
- testCommand?: string;
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