@zhuan-ai/zhuanspec 2.2.4 → 2.4.8

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 (103) hide show
  1. package/bin/zhuanspec-hook.js +3 -0
  2. package/dist/cli/hooks.d.ts +14 -0
  3. package/dist/cli/hooks.js +465 -0
  4. package/dist/cli/index.js +100 -0
  5. package/dist/commands/artifact-workflow.js +15 -34
  6. package/dist/commands/design.d.ts +42 -0
  7. package/dist/commands/design.js +337 -0
  8. package/dist/commands/progress.d.ts +32 -0
  9. package/dist/commands/progress.js +278 -0
  10. package/dist/commands/review.d.ts +32 -0
  11. package/dist/commands/review.js +472 -0
  12. package/dist/commands/validate.d.ts +14 -0
  13. package/dist/commands/validate.js +161 -10
  14. package/dist/core/archive.d.ts +1 -0
  15. package/dist/core/archive.js +45 -3
  16. package/dist/core/completions/command-registry.js +67 -0
  17. package/dist/core/configurators/slash/amazon-q.js +32 -2
  18. package/dist/core/configurators/slash/antigravity.js +8 -2
  19. package/dist/core/configurators/slash/auggie.js +16 -1
  20. package/dist/core/configurators/slash/base.js +1 -1
  21. package/dist/core/configurators/slash/claude.d.ts +4 -0
  22. package/dist/core/configurators/slash/claude.js +56 -1
  23. package/dist/core/configurators/slash/cline.js +8 -2
  24. package/dist/core/configurators/slash/codebuddy.js +22 -1
  25. package/dist/core/configurators/slash/codex.js +21 -0
  26. package/dist/core/configurators/slash/costrict.js +15 -0
  27. package/dist/core/configurators/slash/crush.js +22 -1
  28. package/dist/core/configurators/slash/cursor.js +22 -1
  29. package/dist/core/configurators/slash/factory.js +16 -1
  30. package/dist/core/configurators/slash/gemini.js +8 -2
  31. package/dist/core/configurators/slash/github-copilot.js +19 -1
  32. package/dist/core/configurators/slash/iflow.js +22 -1
  33. package/dist/core/configurators/slash/kilocode.js +4 -1
  34. package/dist/core/configurators/slash/opencode.js +27 -0
  35. package/dist/core/configurators/slash/qoder.d.ts +4 -0
  36. package/dist/core/configurators/slash/qoder.js +59 -1
  37. package/dist/core/configurators/slash/qwen.js +8 -2
  38. package/dist/core/configurators/slash/roocode.js +8 -2
  39. package/dist/core/configurators/slash/windsurf.js +8 -2
  40. package/dist/core/dashboard/metrics.d.ts +33 -0
  41. package/dist/core/dashboard/metrics.js +114 -0
  42. package/dist/core/hooks/collect-knowledge.d.ts +16 -0
  43. package/dist/core/hooks/collect-knowledge.js +203 -0
  44. package/dist/core/hooks/context-load-hook.d.ts +25 -0
  45. package/dist/core/hooks/context-load-hook.js +159 -0
  46. package/dist/core/hooks/deviation-check.d.ts +27 -0
  47. package/dist/core/hooks/deviation-check.js +403 -0
  48. package/dist/core/hooks/deviation-handler.d.ts +43 -0
  49. package/dist/core/hooks/deviation-handler.js +98 -0
  50. package/dist/core/hooks/init.d.ts +14 -0
  51. package/dist/core/hooks/init.js +244 -0
  52. package/dist/core/hooks/notify-milestone.d.ts +14 -0
  53. package/dist/core/hooks/notify-milestone.js +170 -0
  54. package/dist/core/hooks/post-apply.d.ts +29 -0
  55. package/dist/core/hooks/post-apply.js +173 -0
  56. package/dist/core/hooks/post-archive.d.ts +7 -0
  57. package/dist/core/hooks/post-archive.js +208 -0
  58. package/dist/core/hooks/pre-apply.d.ts +34 -0
  59. package/dist/core/hooks/pre-apply.js +139 -0
  60. package/dist/core/hooks/pre-archive.d.ts +7 -0
  61. package/dist/core/hooks/pre-archive.js +50 -0
  62. package/dist/core/hooks/record-progress.d.ts +49 -0
  63. package/dist/core/hooks/record-progress.js +494 -0
  64. package/dist/core/hooks/review-hooks.d.ts +89 -0
  65. package/dist/core/hooks/review-hooks.js +345 -0
  66. package/dist/core/hooks/review-orchestrator.d.ts +40 -0
  67. package/dist/core/hooks/review-orchestrator.js +146 -0
  68. package/dist/core/hooks/summarize.d.ts +15 -0
  69. package/dist/core/hooks/summarize.js +282 -0
  70. package/dist/core/hooks/user-input-hook.d.ts +25 -0
  71. package/dist/core/hooks/user-input-hook.js +179 -0
  72. package/dist/core/init.d.ts +8 -0
  73. package/dist/core/init.js +251 -23
  74. package/dist/core/parsers/requirement-blocks.js +13 -10
  75. package/dist/core/templates/agents-template.d.ts +1 -1
  76. package/dist/core/templates/agents-template.js +510 -243
  77. package/dist/core/templates/index.d.ts +1 -0
  78. package/dist/core/templates/index.js +1 -0
  79. package/dist/core/templates/skill-templates.js +46 -152
  80. package/dist/core/templates/slash-command-templates.d.ts +1 -1
  81. package/dist/core/templates/slash-command-templates.js +352 -20
  82. package/dist/core/templates/tasks-template.d.ts +7 -0
  83. package/dist/core/templates/tasks-template.js +130 -24
  84. package/dist/core/templates/tdd-tasks-template.d.ts +3 -0
  85. package/dist/core/templates/tdd-tasks-template.js +91 -38
  86. package/dist/core/templates/test-cases-template.d.ts +41 -0
  87. package/dist/core/templates/test-cases-template.js +128 -0
  88. package/dist/core/validation/strict-rules.d.ts +44 -5
  89. package/dist/core/validation/strict-rules.js +302 -8
  90. package/dist/core/validation/validator.js +52 -2
  91. package/dist/core/view.d.ts +1 -0
  92. package/dist/core/view.js +60 -2
  93. package/dist/mcp/index.d.ts +28 -0
  94. package/dist/mcp/index.js +31 -0
  95. package/dist/utils/file-system.d.ts +1 -0
  96. package/dist/utils/file-system.js +11 -0
  97. package/dist/utils/item-discovery.js +24 -2
  98. package/dist/utils/phase-utils.d.ts +36 -0
  99. package/dist/utils/phase-utils.js +117 -0
  100. package/package.json +22 -23
  101. package/schemas/spec-driven/schema.yaml +45 -31
  102. package/schemas/spec-driven/templates/spec.md +142 -5
  103. package/schemas/spec-driven/templates/tasks.md +73 -9
@@ -21,7 +21,7 @@ export class ValidateCommand {
21
21
  // No item and no flags
22
22
  if (!itemName) {
23
23
  if (interactive) {
24
- await this.runInteractiveSelector({ strict: !!options.strict, json: !!options.json, concurrency: options.concurrency });
24
+ await this.runInteractiveSelector({ strict: !!options.strict, json: !!options.json, concurrency: options.concurrency, autoFix: !!options.autoFix });
25
25
  return;
26
26
  }
27
27
  this.printNonInteractiveHint();
@@ -30,7 +30,7 @@ export class ValidateCommand {
30
30
  }
31
31
  // Direct item validation with type detection or override
32
32
  const typeOverride = this.normalizeType(options.type);
33
- await this.validateDirectItem(itemName, { typeOverride, strict: !!options.strict, json: !!options.json });
33
+ await this.validateDirectItem(itemName, { typeOverride, strict: !!options.strict, json: !!options.json, autoFix: !!options.autoFix });
34
34
  }
35
35
  normalizeType(value) {
36
36
  if (!value)
@@ -101,24 +101,64 @@ export class ValidateCommand {
101
101
  }
102
102
  async validateByType(type, id, opts) {
103
103
  const validator = new Validator(opts.strict);
104
+ const MAX_AUTO_FIX_ATTEMPTS = 3;
104
105
  if (type === 'change') {
105
106
  const changeDir = path.join(process.cwd(), 'zhuanspec', 'changes', id);
107
+ // Auto-fix loop
108
+ let attempt = 0;
109
+ let report;
110
+ while (attempt < MAX_AUTO_FIX_ATTEMPTS) {
111
+ const start = Date.now();
112
+ report = await validator.validateChangeDeltaSpecs(changeDir);
113
+ // Validate tasks.md dependencies if present
114
+ const taskReport = await this.validateTaskDependencies(changeDir);
115
+ // Merge task dependency issues into report
116
+ if (taskReport.issues.length > 0) {
117
+ report.issues.push(...taskReport.issues);
118
+ report.valid = report.valid && taskReport.valid;
119
+ }
120
+ // Write Mermaid diagram to tasks.md if generated
121
+ if (report.mermaidDiagram) {
122
+ await this.writeMermaidDiagramToTasks(changeDir, report.mermaidDiagram);
123
+ }
124
+ const durationMs = Date.now() - start;
125
+ // If valid or no auto-fix, break the loop
126
+ if (report.valid || !opts.autoFix) {
127
+ this.printReport('change', id, report, durationMs, opts.json, taskReport, { strict: opts.strict });
128
+ process.exitCode = report.valid ? 0 : 1;
129
+ return;
130
+ }
131
+ // Auto-fix attempt
132
+ attempt++;
133
+ if (!opts.json) {
134
+ console.log(`Auto-fix attempt ${attempt}/${MAX_AUTO_FIX_ATTEMPTS}...`);
135
+ }
136
+ // Try to fix common issues
137
+ const fixed = await this.tryAutoFix(changeDir, report.issues);
138
+ if (!fixed) {
139
+ // No fixes applied, break the loop
140
+ this.printReport('change', id, report, durationMs, opts.json, taskReport, { strict: opts.strict });
141
+ if (!opts.json) {
142
+ console.error(`Auto-fix failed after ${attempt} attempts. Manual intervention required.`);
143
+ }
144
+ process.exitCode = 1;
145
+ return;
146
+ }
147
+ // Fixes applied, continue loop to re-validate
148
+ }
149
+ // Max attempts reached
106
150
  const start = Date.now();
107
- const report = await validator.validateChangeDeltaSpecs(changeDir);
108
- // Validate tasks.md dependencies if present
151
+ report = await validator.validateChangeDeltaSpecs(changeDir);
109
152
  const taskReport = await this.validateTaskDependencies(changeDir);
110
- // Merge task dependency issues into report
111
153
  if (taskReport.issues.length > 0) {
112
154
  report.issues.push(...taskReport.issues);
113
155
  report.valid = report.valid && taskReport.valid;
114
156
  }
115
- // Write Mermaid diagram to tasks.md if generated
116
- if (report.mermaidDiagram) {
117
- await this.writeMermaidDiagramToTasks(changeDir, report.mermaidDiagram);
118
- }
119
157
  const durationMs = Date.now() - start;
120
158
  this.printReport('change', id, report, durationMs, opts.json, taskReport, { strict: opts.strict });
121
- // Non-zero exit if invalid (keeps enriched output test semantics)
159
+ if (!opts.json && !report.valid) {
160
+ console.error(`Auto-fix failed after ${MAX_AUTO_FIX_ATTEMPTS} attempts. Manual intervention required.`);
161
+ }
122
162
  process.exitCode = report.valid ? 0 : 1;
123
163
  return;
124
164
  }
@@ -129,6 +169,117 @@ export class ValidateCommand {
129
169
  this.printReport('spec', id, report, durationMs, opts.json, undefined, { strict: opts.strict });
130
170
  process.exitCode = report.valid ? 0 : 1;
131
171
  }
172
+ /**
173
+ * Attempt to auto-fix common validation issues.
174
+ * Returns true if any fixes were applied.
175
+ */
176
+ async tryAutoFix(changeDir, issues) {
177
+ let fixed = false;
178
+ for (const issue of issues) {
179
+ // Skip non-ERROR issues
180
+ if (issue.level !== 'ERROR')
181
+ continue;
182
+ // 1. Missing proposal section - add skeleton
183
+ if (issue.message.includes('Proposal 缺少必需章节')) {
184
+ const sectionMatch = issue.message.match(/缺少必需章节[::]\s*(.+)/);
185
+ if (sectionMatch) {
186
+ const sectionName = sectionMatch[1];
187
+ await this.addProposalSectionSkeleton(changeDir, sectionName);
188
+ fixed = true;
189
+ }
190
+ continue;
191
+ }
192
+ // 2. Missing review gate - add to tasks.md
193
+ if (issue.message.includes('review gate') || issue.message.includes('zhuanspec review')) {
194
+ await this.addReviewGateToTasks(changeDir);
195
+ fixed = true;
196
+ continue;
197
+ }
198
+ // 4. Missing @skill tag - add @skill:none (existing logic)
199
+ if (issue.message.includes('Skill Mapping') || issue.message.includes('@skill')) {
200
+ const tasksFile = path.join(changeDir, 'tasks.md');
201
+ try {
202
+ const content = await fs.readFile(tasksFile, 'utf-8');
203
+ const lines = content.split('\n');
204
+ const newLines = lines.map(line => {
205
+ // If line is a task checkbox without @skill, add @skill:none
206
+ if (line.match(/^- \[ \] \d+\.\d+/) && !line.includes('@skill:')) {
207
+ return line + ' @skill:none';
208
+ }
209
+ return line;
210
+ });
211
+ const newContent = newLines.join('\n');
212
+ if (newContent !== content) {
213
+ await fs.writeFile(tasksFile, newContent, 'utf-8');
214
+ fixed = true;
215
+ }
216
+ }
217
+ catch {
218
+ // Cannot read/write tasks.md
219
+ }
220
+ continue;
221
+ }
222
+ // 5. Wave ordering issues - cannot auto-fix (needs manual review)
223
+ if (issue.message.includes('appears after tasks of wave') || issue.message.includes('Wave headers')) {
224
+ // This requires understanding the full task structure - manual fix required
225
+ continue;
226
+ }
227
+ // 6. Missing scenario - cannot auto-fix (needs content)
228
+ if (issue.message.includes('must have at least one scenario')) {
229
+ continue;
230
+ }
231
+ }
232
+ return fixed;
233
+ }
234
+ /**
235
+ * Add a section skeleton to proposal.md
236
+ */
237
+ async addProposalSectionSkeleton(changeDir, sectionName) {
238
+ const proposalPath = path.join(changeDir, 'proposal.md');
239
+ try {
240
+ let content = await fs.readFile(proposalPath, 'utf-8');
241
+ const skeletons = {
242
+ 'Why/变更原因': '\n\n## 变更原因\n\n[待补充:问题背景、解决动机]\n',
243
+ 'What Changes/变更内容': '\n\n## 变更内容\n\n- [待补充:具体改动点]\n',
244
+ 'Impact/影响范围': '\n\n## 影响范围\n\n- Affected specs: [待补充]\n- Affected code: [待补充]\n',
245
+ };
246
+ // Try to match the section name
247
+ let skeleton = skeletons[sectionName];
248
+ if (!skeleton) {
249
+ // Try partial match
250
+ for (const key of Object.keys(skeletons)) {
251
+ if (sectionName.includes(key.split('/')[0]) || sectionName.includes(key.split('/')[1])) {
252
+ skeleton = skeletons[key];
253
+ break;
254
+ }
255
+ }
256
+ }
257
+ if (!skeleton) {
258
+ skeleton = `\n\n## ${sectionName}\n\n[待补充]\n`;
259
+ }
260
+ content = content.trimEnd() + skeleton;
261
+ await fs.writeFile(proposalPath, content, 'utf-8');
262
+ }
263
+ catch {
264
+ // Cannot read/write proposal.md
265
+ }
266
+ }
267
+ /**
268
+ * Add review gate section to tasks.md
269
+ */
270
+ async addReviewGateToTasks(changeDir) {
271
+ const tasksPath = path.join(changeDir, 'tasks.md');
272
+ try {
273
+ let content = await fs.readFile(tasksPath, 'utf-8');
274
+ if (!content.includes('zhuanspec review') && !content.includes('审查门禁')) {
275
+ content = content.trimEnd() + '\n\n## 审查门禁\n\n- [ ] 执行 `zhuanspec review <change-id>` 并确认通过\n';
276
+ await fs.writeFile(tasksPath, content, 'utf-8');
277
+ }
278
+ }
279
+ catch {
280
+ // Cannot read/write tasks.md
281
+ }
282
+ }
132
283
  printReport(type, id, report, durationMs, json, taskReport, opts) {
133
284
  if (json) {
134
285
  const out = {
@@ -31,6 +31,7 @@ export declare class ArchiveCommand {
31
31
  private hasGitChanges;
32
32
  private copyDirectoryContentsIfExists;
33
33
  private mirrorDirectory;
34
+ private directoryExists;
34
35
  private findFirstDirectoryByName;
35
36
  private resolveBusinessTemplateRoot;
36
37
  private resolveRemoteBusinessRoot;
@@ -7,6 +7,8 @@ import { getTaskProgressForChange, formatTaskStatus } from '../utils/task-progre
7
7
  import { Validator } from './validation/validator.js';
8
8
  import chalk from 'chalk';
9
9
  import { findSpecUpdates, buildUpdatedSpec, writeUpdatedSpec, } from './specs-apply.js';
10
+ import { preArchiveHook } from './hooks/pre-archive.js';
11
+ import { postArchiveHook } from './hooks/post-archive.js';
10
12
  const ARCH_REPO_URL = 'http://gitlab.zhuanspirit.com/zz-kf/spec_repo.git';
11
13
  const ARCH_REPO_BRANCH = 'spec_repo-feature-6612-2';
12
14
  export class ArchiveCommand {
@@ -27,6 +29,7 @@ export class ArchiveCommand {
27
29
  if (!businessDirection) {
28
30
  throw new Error('未解析到业务方向,无法执行归档前拉取与归档后回推。请使用 --business-direction 或在 zhuanspec/.business-direction、zhuanspec/project.md 中配置。');
29
31
  }
32
+ console.log(chalk.gray(`业务方向:${businessDirection}`));
30
33
  await this.refreshBusinessTemplateFromRemote(zhuanspecDir, businessDirection);
31
34
  // Get change name interactively if not provided
32
35
  if (!changeName) {
@@ -153,6 +156,8 @@ export class ArchiveCommand {
153
156
  console.log(`警告:发现 ${incompleteTasks} 个未完成的任务。由于 --yes 标志,继续执行。`);
154
157
  }
155
158
  }
159
+ // Single source of truth for archive gate: preArchiveHook (review/tests/spec-code consistency)
160
+ await preArchiveHook({ change: changeName, json: false });
156
161
  // Handle spec updates unless skipSpecs flag is set
157
162
  if (options.skipSpecs) {
158
163
  console.log('跳过规范更新(已提供 --skip-specs 标志)。');
@@ -245,7 +250,17 @@ export class ArchiveCommand {
245
250
  await this.moveDirectory(changeDir, archivePath);
246
251
  console.log(`变更 '${changeName}' 已归档为 '${archiveName}'。`);
247
252
  }
253
+ // Run post-archive hook (knowledge collection) before pushing to remote
254
+ console.log(chalk.cyan('\n━━━ 知识收集 ━━━'));
255
+ await postArchiveHook({ archived: archiveName, json: false });
256
+ // Push archive result (specs + archive + knowledge) to remote
257
+ console.log(chalk.cyan('\n━━━ 推送到远端 ━━━'));
248
258
  await this.pushArchiveResultToRemote(zhuanspecDir, businessDirection);
259
+ // Output feedback link after archive completes
260
+ console.log(chalk.cyan('\n━━━ 使用反馈 ━━━'));
261
+ console.log(chalk.yellow(' 请填写使用反馈,帮助我们改进 ZhuanSpec 工作流:'));
262
+ console.log(chalk.blue(' 📋 https://doc.weixin.qq.com/forms/AJ4AfQfgAAwACwAAwaVABsCNqDXIKn8sf'));
263
+ console.log(chalk.green('\n✅ 归档完成!'));
249
264
  }
250
265
  /**
251
266
  * Move a directory, falling back to copy+remove when rename fails
@@ -486,8 +501,11 @@ export class ArchiveCommand {
486
501
  return null;
487
502
  }
488
503
  async refreshBusinessTemplateFromRemote(zhuanspecDir, businessDirection) {
504
+ console.log(chalk.cyan('\n━━━ 拉取远端模板 ━━━'));
505
+ console.log(chalk.gray(' → 克隆远端模板仓库...'));
489
506
  const tempRepo = this.cloneRemoteRepoToTemp();
490
507
  try {
508
+ console.log(chalk.gray(' → 解析远端业务目录...'));
491
509
  const remoteBusiness = this.resolveRemoteBusinessRoot(tempRepo, businessDirection);
492
510
  if (!remoteBusiness) {
493
511
  throw new Error(`未在远端 specs 目录中找到业务方向 '${businessDirection}',归档已中止。`);
@@ -497,17 +515,21 @@ export class ArchiveCommand {
497
515
  if (!templateRoot) {
498
516
  throw new Error(`未在业务方向 '${businessDirection}' 目录下递归找到可用的 zhuanspec 目录,归档已中止。`);
499
517
  }
518
+ console.log(chalk.gray(' → 同步 specs 目录...'));
500
519
  await this.copyDirectoryContentsIfExists(path.join(templateRoot, 'specs'), path.join(zhuanspecDir, 'specs'));
520
+ console.log(chalk.gray(' → 同步 changes 目录...'));
501
521
  await this.copyDirectoryContentsIfExists(path.join(templateRoot, 'changes'), path.join(zhuanspecDir, 'changes'));
502
- console.log(chalk.gray(`已拉取 ${businessDirection} 业务模板(specs/changes)。`));
522
+ console.log(chalk.green(`✅ 已拉取 ${businessDirection} 业务模板(specs/changes)。`));
503
523
  }
504
524
  finally {
505
525
  rmSync(tempRepo, { recursive: true, force: true });
506
526
  }
507
527
  }
508
528
  async pushArchiveResultToRemote(zhuanspecDir, businessDirection) {
529
+ console.log(chalk.gray(' → 克隆远端模板仓库...'));
509
530
  const tempRepo = this.cloneRemoteRepoToTemp();
510
531
  try {
532
+ console.log(chalk.gray(' → 解析远端业务目录...'));
511
533
  const remoteBusiness = this.resolveRemoteBusinessRoot(tempRepo, businessDirection);
512
534
  if (!remoteBusiness) {
513
535
  throw new Error(`未在远端 specs 目录中找到业务方向 '${businessDirection}',归档结果回推已中止。`);
@@ -520,20 +542,31 @@ export class ArchiveCommand {
520
542
  const remoteBase = templateRoot;
521
543
  const remoteSpecsPath = path.join(remoteBase, 'specs');
522
544
  const remoteArchivePath = path.join(remoteBase, 'changes', 'archive');
545
+ const remoteKnowledgePath = path.join(remoteBase, 'knowledge');
523
546
  const localSpecsPath = path.join(zhuanspecDir, 'specs');
524
547
  const localArchivePath = path.join(zhuanspecDir, 'changes', 'archive');
548
+ const localKnowledgePath = path.join(zhuanspecDir, 'knowledge');
549
+ console.log(chalk.gray(' → 同步 specs 目录...'));
525
550
  await this.mirrorDirectory(localSpecsPath, remoteSpecsPath);
551
+ console.log(chalk.gray(' → 同步 archive 目录...'));
526
552
  await this.mirrorDirectory(localArchivePath, remoteArchivePath);
553
+ // Knowledge directory may not exist on first archive, sync only if exists
554
+ if (await this.directoryExists(localKnowledgePath)) {
555
+ console.log(chalk.gray(' → 同步 knowledge 目录...'));
556
+ await this.mirrorDirectory(localKnowledgePath, remoteKnowledgePath);
557
+ }
527
558
  const changed = this.hasGitChanges(tempRepo);
528
559
  if (!changed) {
529
560
  console.log(chalk.gray('远端模板仓库无变更,跳过 push。'));
530
561
  return;
531
562
  }
563
+ console.log(chalk.gray(' → 提交变更...'));
532
564
  this.git(tempRepo, 'git add .');
533
- const commitMsg = `chore: sync archive outputs for ${businessDirection}`;
565
+ const commitMsg = `chore: sync archive outputs (specs/archive/knowledge) for ${businessDirection}`;
534
566
  this.git(tempRepo, `git commit -m "${commitMsg}"`);
567
+ console.log(chalk.gray(' → 推送到远端...'));
535
568
  this.git(tempRepo, `git push origin ${ARCH_REPO_BRANCH}`);
536
- console.log(chalk.green(`已推送归档结果到远端 ${ARCH_REPO_BRANCH}(${businessDirection})。`));
569
+ console.log(chalk.green(`✅ 已推送归档结果到远端 ${ARCH_REPO_BRANCH}(${businessDirection})。`));
537
570
  }
538
571
  finally {
539
572
  rmSync(tempRepo, { recursive: true, force: true });
@@ -599,6 +632,15 @@ export class ArchiveCommand {
599
632
  await fs.mkdir(path.dirname(targetDir), { recursive: true });
600
633
  await fs.cp(sourceDir, targetDir, { recursive: true, force: true });
601
634
  }
635
+ async directoryExists(dirPath) {
636
+ try {
637
+ const stat = await fs.stat(dirPath);
638
+ return stat.isDirectory();
639
+ }
640
+ catch {
641
+ return false;
642
+ }
643
+ }
602
644
  findFirstDirectoryByName(searchRoot, targetName) {
603
645
  if (!existsSync(searchRoot)) {
604
646
  return null;
@@ -69,6 +69,73 @@ export const COMMAND_REGISTRY = [
69
69
  description: 'Display an interactive dashboard of specs and changes',
70
70
  flags: [],
71
71
  },
72
+ {
73
+ name: 'techDesign',
74
+ description: 'Prepare technical design generation request (techDesign phase, independent before proposal by default)',
75
+ acceptsPositional: true,
76
+ positionalType: 'change-id',
77
+ flags: [
78
+ {
79
+ name: 'change',
80
+ description: 'Optionally bind generated design source to a change',
81
+ takesValue: true,
82
+ },
83
+ {
84
+ name: 'desc',
85
+ description: 'Requirement description used for technical design generation',
86
+ takesValue: true,
87
+ },
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',
96
+ takesValue: true,
97
+ },
98
+ {
99
+ name: 'output',
100
+ description: 'Output path of generated request markdown',
101
+ takesValue: true,
102
+ },
103
+ {
104
+ name: 'init-template',
105
+ description: 'Generate empty design.md template inside a change (legacy mode)',
106
+ },
107
+ {
108
+ name: 'force',
109
+ description: 'Overwrite existing output file or bound design.md',
110
+ },
111
+ ],
112
+ },
113
+ {
114
+ name: 'design',
115
+ description: 'Deprecated alias of techDesign',
116
+ acceptsPositional: true,
117
+ positionalType: 'change-id',
118
+ flags: [],
119
+ },
120
+ {
121
+ name: 'review',
122
+ description: 'Run ZhuanSpec review and unit test verification',
123
+ acceptsPositional: true,
124
+ positionalType: 'change-id',
125
+ flags: [
126
+ COMMON_FLAGS.json,
127
+ {
128
+ name: 'test-command',
129
+ description: 'Test command to execute (default: npm test)',
130
+ takesValue: true,
131
+ },
132
+ {
133
+ name: 'coverage-threshold',
134
+ description: 'Coverage threshold percentage',
135
+ takesValue: true,
136
+ },
137
+ ],
138
+ },
72
139
  {
73
140
  name: 'validate',
74
141
  description: 'Validate changes and specs',
@@ -1,8 +1,11 @@
1
1
  import { SlashCommandConfigurator } from './base.js';
2
2
  const FILE_PATHS = {
3
3
  proposal: '.amazonq/prompts/zhuanspec-proposal.md',
4
+ design: '.amazonq/prompts/zhuanspec-design.md',
4
5
  apply: '.amazonq/prompts/zhuanspec-apply.md',
5
- archive: '.amazonq/prompts/zhuanspec-archive.md'
6
+ review: '.amazonq/prompts/zhuanspec-review.md',
7
+ archive: '.amazonq/prompts/zhuanspec-archive.md',
8
+ knowledge: '.amazonq/prompts/zhuanspec-knowledge.md'
6
9
  };
7
10
  const FRONTMATTER = {
8
11
  proposal: `---
@@ -14,12 +17,30 @@ The user has requested the following change proposal. Use the zhuanspec instruct
14
17
  <UserRequest>
15
18
  $ARGUMENTS
16
19
  </UserRequest>`,
20
+ design: `---
21
+ description: 在 proposal 之前独立生成技术设计请求文档。
22
+ ---
23
+
24
+ The user wants to generate a technical design request. Use the zhuanspec instructions to create the design document.
25
+
26
+ <Input>
27
+ $ARGUMENTS
28
+ </Input>`,
17
29
  apply: `---
18
30
  description: 实施已批准的 ZhuanSpec 变更并保持任务同步。
19
31
  ---
20
32
 
21
33
  The user wants to apply the following change. Use the zhuanspec instructions to implement the approved change.
22
34
 
35
+ <ChangeId>
36
+ $ARGUMENTS
37
+ </ChangeId>`,
38
+ review: `---
39
+ description: 在归档前执行代码审查和单元测试验证。
40
+ ---
41
+
42
+ The user wants to review the following change. Use the zhuanspec instructions to run code review and unit test verification.
43
+
23
44
  <ChangeId>
24
45
  $ARGUMENTS
25
46
  </ChangeId>`,
@@ -31,7 +52,16 @@ The user wants to archive the following deployed change. Use the zhuanspec instr
31
52
 
32
53
  <ChangeId>
33
54
  $ARGUMENTS
34
- </ChangeId>`
55
+ </ChangeId>`,
56
+ knowledge: `---
57
+ description: 管理项目知识库(最佳实践、陷阱、隐式约定)。
58
+ ---
59
+
60
+ The user wants to manage the project knowledge base. Use the zhuanspec instructions to view, add, search, or sync knowledge entries.
61
+
62
+ <Operation>
63
+ $ARGUMENTS
64
+ </Operation>`
35
65
  };
36
66
  export class AmazonQSlashCommandConfigurator extends SlashCommandConfigurator {
37
67
  toolId = 'amazon-q';
@@ -1,13 +1,19 @@
1
1
  import { SlashCommandConfigurator } from './base.js';
2
2
  const FILE_PATHS = {
3
3
  proposal: '.agent/workflows/zhuanspec-proposal.md',
4
+ design: '.agent/workflows/zhuanspec-design.md',
4
5
  apply: '.agent/workflows/zhuanspec-apply.md',
5
- archive: '.agent/workflows/zhuanspec-archive.md'
6
+ review: '.agent/workflows/zhuanspec-review.md',
7
+ archive: '.agent/workflows/zhuanspec-archive.md',
8
+ knowledge: '.agent/workflows/zhuanspec-knowledge.md'
6
9
  };
7
10
  const DESCRIPTIONS = {
8
11
  proposal: '搭建新的 ZhuanSpec 变更并严格验证。',
12
+ design: '在 proposal 之前独立生成技术设计请求文档。',
9
13
  apply: '实施已批准的 ZhuanSpec 变更并保持任务同步。',
10
- archive: '归档已部署的 ZhuanSpec 变更并更新规范。'
14
+ review: '在归档前执行代码审查和单元测试验证。',
15
+ archive: '归档已部署的 ZhuanSpec 变更并更新规范。',
16
+ knowledge: '管理项目知识库(最佳实践、陷阱、隐式约定)。'
11
17
  };
12
18
  export class AntigravitySlashCommandConfigurator extends SlashCommandConfigurator {
13
19
  toolId = 'antigravity';
@@ -1,21 +1,36 @@
1
1
  import { SlashCommandConfigurator } from './base.js';
2
2
  const FILE_PATHS = {
3
3
  proposal: '.augment/commands/zhuanspec-proposal.md',
4
+ design: '.augment/commands/zhuanspec-design.md',
4
5
  apply: '.augment/commands/zhuanspec-apply.md',
5
- archive: '.augment/commands/zhuanspec-archive.md'
6
+ review: '.augment/commands/zhuanspec-review.md',
7
+ archive: '.augment/commands/zhuanspec-archive.md',
8
+ knowledge: '.augment/commands/zhuanspec-knowledge.md'
6
9
  };
7
10
  const FRONTMATTER = {
8
11
  proposal: `---
9
12
  description: 搭建新的 ZhuanSpec 变更并严格验证。
10
13
  argument-hint: feature description or request
14
+ ---`,
15
+ design: `---
16
+ description: 在 proposal 之前独立生成技术设计请求文档。
17
+ argument-hint: dashen-page-id or description
11
18
  ---`,
12
19
  apply: `---
13
20
  description: 实施已批准的 ZhuanSpec 变更并保持任务同步。
14
21
  argument-hint: change-id
22
+ ---`,
23
+ review: `---
24
+ description: 在归档前执行代码审查和单元测试验证。
25
+ argument-hint: change-id
15
26
  ---`,
16
27
  archive: `---
17
28
  description: 归档已部署的 ZhuanSpec 变更并更新规范。
18
29
  argument-hint: change-id
30
+ ---`,
31
+ knowledge: `---
32
+ description: 管理项目知识库(最佳实践、陷阱、隐式约定)。
33
+ argument-hint: operation (view, add, search, sync)
19
34
  ---`
20
35
  };
21
36
  export class AuggieSlashCommandConfigurator extends SlashCommandConfigurator {
@@ -1,7 +1,7 @@
1
1
  import { FileSystemUtils } from '../../../utils/file-system.js';
2
2
  import { TemplateManager } from '../../templates/index.js';
3
3
  import { ZHUANSPEC_MARKERS } from '../../config.js';
4
- const ALL_COMMANDS = ['proposal', 'apply', 'archive'];
4
+ const ALL_COMMANDS = ['proposal', 'design', 'apply', 'review', 'archive', 'knowledge'];
5
5
  export class SlashCommandConfigurator {
6
6
  getTargets() {
7
7
  return ALL_COMMANDS.map((id) => ({
@@ -5,5 +5,9 @@ export declare class ClaudeSlashCommandConfigurator extends SlashCommandConfigur
5
5
  readonly isAvailable = true;
6
6
  protected getRelativePath(id: SlashCommandId): string;
7
7
  protected getFrontmatter(id: SlashCommandId): string;
8
+ generateAll(projectPath: string, zhuanspecDir: string): Promise<string[]>;
9
+ updateExisting(projectPath: string, zhuanspecDir: string): Promise<string[]>;
10
+ private migrateLegacyDesignCommand;
11
+ private removeLegacyDesignCommand;
8
12
  }
9
13
  //# sourceMappingURL=claude.d.ts.map
@@ -1,8 +1,14 @@
1
1
  import { SlashCommandConfigurator } from './base.js';
2
+ import { FileSystemUtils } from '../../../utils/file-system.js';
3
+ import { ZHUANSPEC_MARKERS } from '../../config.js';
4
+ import { promises as fs } from 'fs';
2
5
  const FILE_PATHS = {
3
6
  proposal: '.claude/commands/zhuanspec/proposal.md',
7
+ design: '.claude/commands/zhuanspec/techDesign.md',
4
8
  apply: '.claude/commands/zhuanspec/apply.md',
5
- archive: '.claude/commands/zhuanspec/archive.md'
9
+ review: '.claude/commands/zhuanspec/review.md',
10
+ archive: '.claude/commands/zhuanspec/archive.md',
11
+ knowledge: '.claude/commands/zhuanspec/knowledge.md'
6
12
  };
7
13
  const FRONTMATTER = {
8
14
  proposal: `---
@@ -10,18 +16,36 @@ name: ZhuanSpec: Proposal
10
16
  description: 搭建新的 ZhuanSpec 变更并严格验证。
11
17
  category: ZhuanSpec
12
18
  tags: [zhuanspec, change]
19
+ ---`,
20
+ design: `---
21
+ name: ZhuanSpec: TechDesign
22
+ description: 在 proposal 之前独立生成技术设计请求文档。
23
+ category: ZhuanSpec
24
+ tags: [zhuanspec, techDesign, tech-spec]
13
25
  ---`,
14
26
  apply: `---
15
27
  name: ZhuanSpec: Apply
16
28
  description: 实施已批准的 ZhuanSpec 变更并保持任务同步。
17
29
  category: ZhuanSpec
18
30
  tags: [zhuanspec, apply]
31
+ ---`,
32
+ review: `---
33
+ name: ZhuanSpec: Review
34
+ description: 在归档前执行代码审查和单元测试验证。
35
+ category: ZhuanSpec
36
+ tags: [zhuanspec, review, code-review]
19
37
  ---`,
20
38
  archive: `---
21
39
  name: ZhuanSpec: Archive
22
40
  description: 归档已部署的 ZhuanSpec 变更并更新规范。
23
41
  category: ZhuanSpec
24
42
  tags: [zhuanspec, archive]
43
+ ---`,
44
+ knowledge: `---
45
+ name: ZhuanSpec: Knowledge
46
+ description: 管理项目知识库(最佳实践、陷阱、隐式约定)。
47
+ category: ZhuanSpec
48
+ tags: [zhuanspec, knowledge]
25
49
  ---`
26
50
  };
27
51
  export class ClaudeSlashCommandConfigurator extends SlashCommandConfigurator {
@@ -33,5 +57,36 @@ export class ClaudeSlashCommandConfigurator extends SlashCommandConfigurator {
33
57
  getFrontmatter(id) {
34
58
  return FRONTMATTER[id];
35
59
  }
60
+ async generateAll(projectPath, zhuanspecDir) {
61
+ const createdOrUpdated = await super.generateAll(projectPath, zhuanspecDir);
62
+ await this.removeLegacyDesignCommand(projectPath);
63
+ return createdOrUpdated;
64
+ }
65
+ async updateExisting(projectPath, zhuanspecDir) {
66
+ const updated = await super.updateExisting(projectPath, zhuanspecDir);
67
+ await this.migrateLegacyDesignCommand(projectPath);
68
+ return updated;
69
+ }
70
+ async migrateLegacyDesignCommand(projectPath) {
71
+ const legacyPath = FileSystemUtils.joinPath(projectPath, '.claude/commands/zhuanspec/design.md');
72
+ const nextPath = FileSystemUtils.joinPath(projectPath, '.claude/commands/zhuanspec/techDesign.md');
73
+ if (!await FileSystemUtils.fileExists(legacyPath)) {
74
+ return;
75
+ }
76
+ if (!await FileSystemUtils.fileExists(nextPath)) {
77
+ const body = this.getBody('design');
78
+ const frontmatter = this.getFrontmatter('design');
79
+ const content = `${frontmatter.trim()}\n${ZHUANSPEC_MARKERS.start}\n${body}\n${ZHUANSPEC_MARKERS.end}\n`;
80
+ await FileSystemUtils.writeFile(nextPath, content);
81
+ }
82
+ await fs.unlink(legacyPath).catch(() => undefined);
83
+ }
84
+ async removeLegacyDesignCommand(projectPath) {
85
+ const legacyPath = FileSystemUtils.joinPath(projectPath, '.claude/commands/zhuanspec/design.md');
86
+ if (!await FileSystemUtils.fileExists(legacyPath)) {
87
+ return;
88
+ }
89
+ await fs.unlink(legacyPath).catch(() => undefined);
90
+ }
36
91
  }
37
92
  //# sourceMappingURL=claude.js.map