@zhuan-ai/zhuanspec 2.17.15 → 2.19.1

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/index.js CHANGED
@@ -1,4 +1,4 @@
1
- import { Command } from 'commander';
1
+ import { Command, Option } from 'commander';
2
2
  import { createRequire } from 'module';
3
3
  import ora from 'ora';
4
4
  import path from 'path';
@@ -104,8 +104,9 @@ const registerTechDesignCommand = (commandName, deprecatedAlias = false) => prog
104
104
  : 'Prepare technical design generation request (techDesign phase, independent before proposal by default)')
105
105
  .option('--change <change-id>', 'Optionally bind generated design source to a change')
106
106
  .option('--desc <text>', 'Requirement description used for technical design generation')
107
- .option('--dashen-page-id <id>', 'Dashen pageId used as requirement source')
108
- .option('--dashen-url <url>', 'Dashen document URL used as requirement source')
107
+ .option('--feishu-url <url>', 'Feishu document URL used as requirement source')
108
+ .addOption(new Option('--dashen-page-id <id>', 'Legacy pageId used as requirement source').hideHelp())
109
+ .addOption(new Option('--dashen-url <url>', 'Legacy document URL used as requirement source').hideHelp())
109
110
  .option('--output <path>', 'Output path of generated request markdown (default: doc/*技术方案生成请求.md)')
110
111
  .option('--init-template', 'Generate empty design.md template inside a change (legacy mode)')
111
112
  .option('--force', 'Overwrite existing output file or bound design.md')
@@ -117,7 +118,11 @@ const registerTechDesignCommand = (commandName, deprecatedAlias = false) => prog
117
118
  console.error('Warning: "zhuanspec design" is deprecated. Please use "zhuanspec techDesign".');
118
119
  }
119
120
  const designCommand = new DesignCommand();
120
- await designCommand.execute(changeName, options);
121
+ await designCommand.execute(changeName, {
122
+ ...options,
123
+ legacyPageId: options?.dashenPageId,
124
+ legacyUrl: options?.dashenUrl,
125
+ });
121
126
  }
122
127
  catch (error) {
123
128
  console.log();
@@ -13,7 +13,7 @@ import { createChange, validateChangeName } from '../utils/change-utils.js';
13
13
  import { discoverSkills } from '../core/skill-discovery.js';
14
14
  import { getNewChangeSkillTemplate, getContinueChangeSkillTemplate, getApplyChangeSkillTemplate, getFfChangeSkillTemplate, getSyncSpecsSkillTemplate, getArchiveChangeSkillTemplate, getProposeChangeSkillTemplate, getExploreSkillTemplate, getVerifySkillTemplate, getOpsxNewCommandTemplate, getOpsxContinueCommandTemplate, getOpsxApplyCommandTemplate, getOpsxFfCommandTemplate, getOpsxSyncCommandTemplate, getOpsxArchiveCommandTemplate, getOpsxProposeCommandTemplate, getOpsxExploreCommandTemplate, getOpsxVerifyCommandTemplate } from '../core/templates/skill-templates.js';
15
15
  import { FileSystemUtils } from '../utils/file-system.js';
16
- import { parseTasks, generateExecutionPlan, renderExecutionPlanXml, renderMermaidDiagram, hasDependsAnnotations, } from '../core/task-graph/index.js';
16
+ import { parseTasks, generateExecutionPlan, renderExecutionPlanXml, hasDependsAnnotations, } from '../core/task-graph/index.js';
17
17
  const DEFAULT_SCHEMA = 'spec-driven';
18
18
  /**
19
19
  * Checks if color output is disabled via NO_COLOR env or --no-color flag.
@@ -427,26 +427,6 @@ async function generateApplyInstructions(projectRoot, changeName, schemaName) {
427
427
  if (hasDependencies && !allTasksComplete) {
428
428
  const executionPlan = generateExecutionPlan(parsedTasks);
429
429
  executionPlanXml = renderExecutionPlanXml(executionPlan);
430
- // Generate Mermaid diagram and embed in tasks.md
431
- if (executionPlan && !executionPlan.hasCycle) {
432
- const mermaidSection = renderMermaidDiagram(executionPlan, parsedTasks);
433
- if (mermaidSection) {
434
- // Read current tasks.md content
435
- let currentTasksContent = tasksContent;
436
- // Check if tasks.md already has Workflow Diagram section
437
- const workflowDiagramRegex = /## Workflow Diagram[\s\S]*?(?=\n## |$)/;
438
- if (workflowDiagramRegex.test(currentTasksContent)) {
439
- // Replace existing Workflow Diagram section
440
- currentTasksContent = currentTasksContent.replace(workflowDiagramRegex, mermaidSection);
441
- }
442
- else {
443
- // Append to file end
444
- currentTasksContent = currentTasksContent.trimEnd() + '\n\n' + mermaidSection;
445
- }
446
- // Write back to tasks.md
447
- fs.writeFileSync(tracksPath, currentTasksContent, 'utf-8');
448
- }
449
- }
450
430
  }
451
431
  }
452
432
  }
@@ -475,17 +455,15 @@ async function generateApplyInstructions(projectRoot, changeName, schemaName) {
475
455
  }
476
456
  else if (tracksFile && remaining === 0 && total > 0) {
477
457
  state = 'all_done';
478
- instruction = `All tasks are complete. Review should auto-run once after apply completion.
458
+ instruction = `All tasks are complete. Apply is finished.
479
459
 
480
460
  **Required Next Steps**
481
461
 
482
- 1. Verify auto review report exists (\`metrics/review.json\`, \`metrics/tests.json\`)
483
- 2. Ensure unit tests pass in the review report
484
- 3. Ensure critical issues = 0
485
- 4. If auto review failed/missing, run \`zhuanspec review ${changeName}\` manually
486
- 5. Then continue to \`zhuanspec archive ${changeName}\`
462
+ 1. Run \`zhuanspec review ${changeName}\` to verify implementation quality
463
+ 2. Fix any review findings if needed
464
+ 3. Continue to \`zhuanspec archive ${changeName}\` after review passes
487
465
 
488
- Archive may be blocked if review/tests are missing or failed.`;
466
+ Archive may be blocked if review results are missing or failed.`;
489
467
  }
490
468
  else if (!tracksFile) {
491
469
  // No tracking file (e.g., TDD schema) - ready to apply
@@ -588,16 +566,13 @@ function printApplyInstructionsText(instructions) {
588
566
  // Code review prompt for all_done state
589
567
  if (state === 'all_done') {
590
568
  console.log();
591
- console.log('### 📋 Code Review (Auto + Required)');
569
+ console.log('### Next Step');
592
570
  console.log();
593
571
  console.log('All tasks are complete. You should now:');
594
572
  console.log();
595
- console.log('1. Auto review is triggered once when all apply tasks complete');
596
- console.log('2. Confirm `metrics/review.json` and `metrics/tests.json` exist');
597
- console.log('3. Ensure review report is PASS with no critical issues');
598
- console.log('4. Ensure unit tests pass');
599
- console.log('5. If auto review failed/missing, run `zhuanspec review <change-id>` manually');
600
- console.log('6. Then proceed to archive workflow');
573
+ console.log('1. Run `zhuanspec review <change-id>`');
574
+ console.log('2. Fix review findings if needed');
575
+ console.log('3. Then proceed to archive workflow');
601
576
  }
602
577
  }
603
578
  async function templatesCommand(options) {
@@ -4,8 +4,9 @@ export declare class DesignCommand {
4
4
  initTemplate?: boolean;
5
5
  change?: string;
6
6
  desc?: string;
7
- dashenPageId?: string;
8
- dashenUrl?: string;
7
+ feishuUrl?: string;
8
+ legacyPageId?: string;
9
+ legacyUrl?: string;
9
10
  output?: string;
10
11
  validate?: boolean;
11
12
  autoFix?: boolean;
@@ -49,8 +49,9 @@ export class DesignCommand {
49
49
  if (options?.createChange && !boundChange) {
50
50
  const designContext = await this.resolveDesignContext({
51
51
  desc: options?.desc,
52
- dashenPageId: options?.dashenPageId,
53
- dashenUrl: options?.dashenUrl,
52
+ feishuUrl: options?.feishuUrl,
53
+ legacyPageId: options?.legacyPageId,
54
+ legacyUrl: options?.legacyUrl,
54
55
  });
55
56
  const newChangeName = await this.generateChangeName(designContext);
56
57
  await this.createTechDesignChange(newChangeName, designContext);
@@ -76,11 +77,12 @@ export class DesignCommand {
76
77
  }
77
78
  const designContext = await this.resolveDesignContext({
78
79
  desc: options?.desc,
79
- dashenPageId: options?.dashenPageId,
80
- dashenUrl: options?.dashenUrl,
80
+ feishuUrl: options?.feishuUrl,
81
+ legacyPageId: options?.legacyPageId,
82
+ legacyUrl: options?.legacyUrl,
81
83
  });
82
- if (!designContext.desc && !designContext.dashenPageId && !designContext.dashenUrl) {
83
- throw new Error('Missing design source. Provide --desc / --dashen-page-id / --dashen-url to generate technical design.');
84
+ if (!designContext.desc && !designContext.feishuUrl && !designContext.legacyPageId && !designContext.legacyUrl) {
85
+ throw new Error('Missing design source. Provide --desc or --feishu-url to generate technical design.');
84
86
  }
85
87
  const outputPath = await this.resolveOutputPath(options?.output, boundChange);
86
88
  const exists = await FileSystemUtils.fileExists(outputPath);
@@ -112,15 +114,16 @@ export class DesignCommand {
112
114
  async resolveDesignContext(input) {
113
115
  const result = {
114
116
  desc: input.desc?.trim() ?? '',
115
- dashenPageId: input.dashenPageId?.trim() ?? '',
116
- dashenUrl: input.dashenUrl?.trim() ?? '',
117
+ feishuUrl: input.feishuUrl?.trim() ?? '',
118
+ legacyPageId: input.legacyPageId?.trim() ?? '',
119
+ legacyUrl: input.legacyUrl?.trim() ?? '',
117
120
  };
118
- if (result.desc || result.dashenPageId || result.dashenUrl || !process.stdin.isTTY) {
121
+ if (result.desc || result.feishuUrl || result.legacyPageId || result.legacyUrl || !process.stdin.isTTY) {
119
122
  return result;
120
123
  }
121
124
  const { input: askInput } = await import('@inquirer/prompts');
122
125
  const source = await askInput({
123
- message: '请输入需求描述(或留空后继续输入 Dashen 信息)',
126
+ message: '请输入需求描述或飞书文档 URL(飞书文档将使用飞书 CLI 读取)',
124
127
  default: '',
125
128
  });
126
129
  result.desc = source.trim();
@@ -147,12 +150,12 @@ export class DesignCommand {
147
150
  '',
148
151
  '## 输入',
149
152
  `- 需求描述: ${context.desc || '(未提供)'}`,
150
- `- Dashen Page ID: ${context.dashenPageId || '(未提供)'}`,
151
- `- Dashen URL: ${context.dashenUrl || '(未提供)'}`,
153
+ `- 飞书文档 URL: ${context.feishuUrl || context.legacyUrl || '(未提供)'}`,
154
+ '- 飞书读取方式: 使用飞书 CLI `lark-cli` 读取正文',
152
155
  `- 绑定变更: ${boundChange || '(无,独立于提案阶段)'}`,
153
156
  '',
154
157
  '## 生成要求',
155
- '- 使用 Dashen 模板优先,本地模板兜底',
158
+ '- 使用飞书文档内容作为需求来源,本地输入兜底',
156
159
  '- 输出完整技术方案(含 Mermaid)',
157
160
  '- 覆盖需求功能点并附带校验结果与修复记录',
158
161
  '',
@@ -346,12 +349,17 @@ export class DesignCommand {
346
349
  if (context.desc) {
347
350
  base = context.desc;
348
351
  }
349
- else if (context.dashenPageId) {
350
- base = `tech-design-${context.dashenPageId}`;
352
+ else if (context.feishuUrl) {
353
+ const docMatch = context.feishuUrl.match(/(?:docx|wiki)\/([^/?#]+)/);
354
+ const storyMatch = context.feishuUrl.match(/story\/detail\/([^/?#]+)/);
355
+ base = `tech-design-${docMatch?.[1] || storyMatch?.[1] || Date.now()}`;
351
356
  }
352
- else if (context.dashenUrl) {
357
+ else if (context.legacyPageId) {
358
+ base = `tech-design-${context.legacyPageId}`;
359
+ }
360
+ else if (context.legacyUrl) {
353
361
  // Extract page ID from URL if possible
354
- const match = context.dashenUrl.match(/pages\/(\d+)/);
362
+ const match = context.legacyUrl.match(/pages\/(\d+)/);
355
363
  base = match ? `tech-design-${match[1]}` : `tech-design-${Date.now()}`;
356
364
  }
357
365
  else {
@@ -391,9 +399,9 @@ export class DesignCommand {
391
399
  // 3. Create techDesign/ directory and tech-spec.md placeholder
392
400
  const techDesignDir = path.join(changeDir, 'techDesign');
393
401
  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`);
402
+ await FileSystemUtils.writeFile(path.join(techDesignDir, 'tech-spec.md'), `# Tech Spec\n\ncreated: ${new Date().toISOString()}\nsource: ${context.desc || context.feishuUrl || context.legacyPageId || 'manual'}\n\n> This file will be populated by the techDesign skill.\n`);
395
403
  // 3b. Also create .tech-design marker for backward compatibility
396
- await FileSystemUtils.writeFile(path.join(changeDir, '.tech-design'), `created: ${new Date().toISOString()}\nsource: ${context.desc || context.dashenPageId || 'manual'}\n`);
404
+ await FileSystemUtils.writeFile(path.join(changeDir, '.tech-design'), `created: ${new Date().toISOString()}\nsource: ${context.desc || context.feishuUrl || context.legacyPageId || 'manual'}\n`);
397
405
  // 4. Create design.md skeleton
398
406
  await FileSystemUtils.writeFile(path.join(changeDir, 'design.md'), DEFAULT_DESIGN_TEMPLATE);
399
407
  // 5. Create doc/ request file (keep compatibility)
@@ -86,13 +86,8 @@ export const COMMAND_REGISTRY = [
86
86
  takesValue: true,
87
87
  },
88
88
  {
89
- name: 'dashen-page-id',
90
- description: 'Dashen pageId used as requirement source',
91
- takesValue: true,
92
- },
93
- {
94
- name: 'dashen-url',
95
- description: 'Dashen document URL used as requirement source',
89
+ name: 'feishu-url',
90
+ description: 'Feishu document URL used as requirement source',
96
91
  takesValue: true,
97
92
  },
98
93
  {
@@ -14,7 +14,7 @@ argument-hint: feature description or request
14
14
  ---`,
15
15
  design: `---
16
16
  description: 在 proposal 之前独立生成技术设计请求文档。
17
- argument-hint: dashen-page-id or description
17
+ argument-hint: feishu-url or description
18
18
  ---`,
19
19
  apply: `---
20
20
  description: 实施已批准的 ZhuanSpec 变更并保持任务同步。
@@ -14,7 +14,7 @@ argument-hint: feature description or request
14
14
  ---`,
15
15
  design: `---
16
16
  description: "在 proposal 之前独立生成技术设计请求文档。"
17
- argument-hint: dashen-page-id or description
17
+ argument-hint: feishu-url or description
18
18
  ---`,
19
19
  apply: `---
20
20
  description: "实施已批准的 ZhuanSpec 变更并保持任务同步。"
@@ -14,7 +14,7 @@ argument-hint: request or feature description
14
14
  ---`,
15
15
  design: `---
16
16
  description: 在 proposal 之前独立生成技术设计请求文档。
17
- argument-hint: dashen-page-id or description
17
+ argument-hint: feishu-url or description
18
18
  ---`,
19
19
  apply: `---
20
20
  description: 实施已批准的 ZhuanSpec 变更并保持任务同步。
@@ -200,8 +200,7 @@ async function runInitHook(_options) {
200
200
  const versionBanner = await checkVersionUpdate();
201
201
  // Build system message — intentionally minimal. Only the version banner
202
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).
203
+ // the corresponding Skill / slash-command template.
205
204
  let systemMessage = '';
206
205
  if (versionBanner) {
207
206
  systemMessage += `${versionBanner}\n`;
@@ -220,7 +219,7 @@ async function runInitHook(_options) {
220
219
  systemMessage += ` (run /zhuanspec:proposal to continue)`;
221
220
  }
222
221
  else if (phase === 'apply') {
223
- systemMessage += ` (run zhuanspec-apply skill to execute Waves)`;
222
+ systemMessage += ` (run zhuanspec-apply to implement pending tasks)`;
224
223
  }
225
224
  systemMessage += `\n`;
226
225
  }
@@ -3,7 +3,7 @@
3
3
  *
4
4
  * Actions after Apply phase completes:
5
5
  * 1. Reverse Sync deviation detection
6
- * 2. Report status (user manually triggers review in a new session)
6
+ * 2. Report status (user manually triggers review when ready)
7
7
  */
8
8
  interface PostApplyOptions {
9
9
  json?: boolean;
@@ -25,9 +25,8 @@ interface HookOutput {
25
25
  export declare function postApplyHook(options: PostApplyOptions): Promise<void>;
26
26
  /**
27
27
  * Execute post-apply actions: deviation detection only.
28
- * Auto review trigger has been removed to ensure Stop hook fires correctly
29
- * and apply phaseBaseline is properly marked.
30
- * Users should manually run `zhuanspec review <change-id>` in a new session.
28
+ * Auto review trigger has been removed so apply stays focused on implementation.
29
+ * Users should manually run `zhuanspec review <change-id>` when they want full validation.
31
30
  */
32
31
  export declare function executePostApply(changeDir: string, repoRoot: string): Promise<HookOutput>;
33
32
  export {};
@@ -3,7 +3,7 @@
3
3
  *
4
4
  * Actions after Apply phase completes:
5
5
  * 1. Reverse Sync deviation detection
6
- * 2. Report status (user manually triggers review in a new session)
6
+ * 2. Report status (user manually triggers review when ready)
7
7
  */
8
8
  import path from 'path';
9
9
  import { FileSystemUtils } from '../../utils/file-system.js';
@@ -40,9 +40,8 @@ async function runPostApplyHook(changeId) {
40
40
  }
41
41
  /**
42
42
  * Execute post-apply actions: deviation detection only.
43
- * Auto review trigger has been removed to ensure Stop hook fires correctly
44
- * and apply phaseBaseline is properly marked.
45
- * Users should manually run `zhuanspec review <change-id>` in a new session.
43
+ * Auto review trigger has been removed so apply stays focused on implementation.
44
+ * Users should manually run `zhuanspec review <change-id>` when they want full validation.
46
45
  */
47
46
  export async function executePostApply(changeDir, repoRoot) {
48
47
  // 0. Feature-omission gate(功能点遗漏闸口):检测「范围内但未实现」的 Scenario/功能点
@@ -84,7 +83,7 @@ export async function executePostApply(changeDir, repoRoot) {
84
83
  }
85
84
  return {
86
85
  continue: true,
87
- systemMessage: 'Post-apply: No deviation detected. Please run `zhuanspec review` in a new session to enter Review phase.',
86
+ systemMessage: 'Post-apply: No deviation detected. Please run `zhuanspec review` when you are ready to validate the change.',
88
87
  hookSpecificOutput: {
89
88
  reviewTriggered: false,
90
89
  deviationDetected: false,
@@ -89,8 +89,7 @@ async function runSummarize() {
89
89
  }
90
90
  }
91
91
  // Deviation detection only (no auto review trigger).
92
- // Apply 完成后不再自动触发 review,确保 Stop hook 正确闪断以标记 apply baseline。
93
- // 用户需在新会话中手动执行 `zhuanspec review <change-id>` 进入 review 阶段。
92
+ // Apply 完成后不再自动触发 review;用户准备验收时再手动执行 `zhuanspec review <change-id>`。
94
93
  let deviationDetected = false;
95
94
  if (phase === 'apply' && allTasksComplete) {
96
95
  // Execute post-apply hook to detect deviations only
@@ -71,6 +71,7 @@ export function generateExecutionPlan(tasks, options) {
71
71
  // Create graph and validate
72
72
  const graph = new TaskGraph(incompleteTasks);
73
73
  const validation = graph.validate();
74
+ const taskById = new Map(incompleteTasks.map(task => [task.id, task]));
74
75
  // Check for cycles
75
76
  if (validation.errors.some(e => e.includes('Circular dependency'))) {
76
77
  return {
@@ -95,7 +96,7 @@ export function generateExecutionPlan(tasks, options) {
95
96
  // Initialize
96
97
  for (const task of incompleteTasks) {
97
98
  // Only count dependencies that exist in incomplete tasks
98
- const validDeps = task.depends.filter(d => incompleteTasks.some(t => t.id === d));
99
+ const validDeps = task.depends.filter(d => taskById.has(d));
99
100
  inDegree.set(task.id, validDeps.length);
100
101
  dependents.set(task.id, []);
101
102
  }
@@ -120,7 +121,7 @@ export function generateExecutionPlan(tasks, options) {
120
121
  break;
121
122
  }
122
123
  const readyTasks = readyIds
123
- .map(id => incompleteTasks.find(t => t.id === id))
124
+ .map(id => taskById.get(id))
124
125
  .filter(Boolean);
125
126
  waves.push({
126
127
  wave: waveNumber,