@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
package/dist/core/init.js CHANGED
@@ -287,14 +287,8 @@ export class InitCommand {
287
287
  await this.createDirectoryStructure(zhuanspecPath);
288
288
  await this.ensureTemplateFiles(zhuanspecPath, config);
289
289
  }
290
- // Step 2: Configure AI tools
291
- const toolSpinner = this.startSpinner('正在配置 AI 工具...');
292
- const rootStubStatus = await this.configureAITools(projectPath, zhuanspecDir, config.aiTools);
293
- toolSpinner.stopAndPersist({
294
- symbol: PALETTE.white('▌'),
295
- text: PALETTE.white('AI 工具已配置'),
296
- });
297
- // Step 3: Select business direction and apply architecture file
290
+ // Step 2: Select business direction and sync Claude assets BEFORE configureAITools
291
+ // This ensures .claude directory is not deleted after slash commands are generated
298
292
  await this.promptForBusinessDirection();
299
293
  if (this.businessDirection) {
300
294
  const directionMetaPath = path.join(zhuanspecPath, '.business-direction');
@@ -339,6 +333,7 @@ export class InitCommand {
339
333
  text: PALETTE.midGray(`未找到 ${this.businessDirection} 业务规范模板目录,保留默认模板`),
340
334
  });
341
335
  }
336
+ // Sync Claude assets BEFORE configureAITools to avoid deletion
342
337
  const claudeAssetSpinner = this.startSpinner(`正在同步 ${this.businessDirection} 业务 Claude 规范资产...`);
343
338
  const claudeAssetResult = this.syncBusinessClaudeAssets(projectPath);
344
339
  const claudeMdText = this.describeClaudeAssetResult('CLAUDE.md', claudeAssetResult.claudeMd);
@@ -348,8 +343,15 @@ export class InitCommand {
348
343
  text: PALETTE.white(`Claude 规范资产同步完成:${claudeMdText};${dotClaudeText}`),
349
344
  });
350
345
  }
346
+ // Step 3: Configure AI tools (after Claude assets sync to preserve generated files)
347
+ const toolSpinner = this.startSpinner('正在配置 AI 工具...');
348
+ const toolConfigResult = await this.configureAITools(projectPath, zhuanspecDir, config.aiTools);
349
+ toolSpinner.stopAndPersist({
350
+ symbol: PALETTE.white('▌'),
351
+ text: PALETTE.white('AI 工具已配置'),
352
+ });
351
353
  // Success message
352
- this.displaySuccessMessage(selectedTools, created, refreshed, skippedExisting, skipped, extendMode, rootStubStatus);
354
+ this.displaySuccessMessage(selectedTools, created, refreshed, skippedExisting, skipped, extendMode, toolConfigResult.rootStubStatus, toolConfigResult.claudeHookSummary);
353
355
  }
354
356
  async validate(projectPath, _zhuanspecPath) {
355
357
  const extendMode = await FileSystemUtils.directoryExists(_zhuanspecPath);
@@ -543,10 +545,34 @@ export class InitCommand {
543
545
  path.join(zhuanspecPath, 'specs'),
544
546
  path.join(zhuanspecPath, 'changes'),
545
547
  path.join(zhuanspecPath, 'changes', 'archive'),
548
+ // NEW: Project-level knowledge directory
549
+ path.join(zhuanspecPath, 'knowledge'),
550
+ path.join(zhuanspecPath, 'knowledge', 'troubleshooting'),
551
+ path.join(zhuanspecPath, 'knowledge', 'best-practices'),
552
+ path.join(zhuanspecPath, 'knowledge', 'implicit-conventions'),
546
553
  ];
547
554
  for (const dir of directories) {
548
555
  await FileSystemUtils.createDirectory(dir);
549
556
  }
557
+ // NEW: Create initial knowledge index file if not exists
558
+ const indexPath = path.join(zhuanspecPath, 'knowledge', 'index.md');
559
+ if (!(await FileSystemUtils.fileExists(indexPath))) {
560
+ const knowledgeIndexContent = `# Knowledge Index
561
+
562
+ ## Troubleshooting
563
+
564
+ <!-- Entries will be added by collect-knowledge hook -->
565
+
566
+ ## Best Practices
567
+
568
+ <!-- Project-specific best practices -->
569
+
570
+ ## Implicit Conventions
571
+
572
+ <!-- Conventions discovered during implementation -->
573
+ `;
574
+ await FileSystemUtils.writeFile(indexPath, knowledgeIndexContent);
575
+ }
550
576
  }
551
577
  async generateFiles(zhuanspecPath, config) {
552
578
  await this.writeTemplateFiles(zhuanspecPath, config, false);
@@ -635,6 +661,11 @@ export class InitCommand {
635
661
  return ['oms', 'ass'];
636
662
  }
637
663
  async promptForBusinessDirection() {
664
+ // 处理显式的跳过标记(从命令行参数或交互式选择传入)
665
+ if (this.businessDirection === '__skip__') {
666
+ this.businessDirection = undefined;
667
+ return;
668
+ }
638
669
  if (this.businessDirection) {
639
670
  return;
640
671
  }
@@ -1098,7 +1129,9 @@ export class InitCommand {
1098
1129
  await slashConfigurator.generateAll(projectPath, zhuanspecDir);
1099
1130
  }
1100
1131
  }
1101
- return rootStubStatus;
1132
+ // Generate Claude Code hooks configuration
1133
+ const claudeHookSummary = await this.generateClaudeSettings(projectPath);
1134
+ return { rootStubStatus, claudeHookSummary };
1102
1135
  }
1103
1136
  async configureRootAgentsStub(projectPath, zhuanspecDir) {
1104
1137
  const configurator = ToolRegistry.get('agents');
@@ -1110,7 +1143,178 @@ export class InitCommand {
1110
1143
  await configurator.configure(projectPath, zhuanspecDir);
1111
1144
  return existed ? 'updated' : 'created';
1112
1145
  }
1113
- displaySuccessMessage(selectedTools, created, refreshed, skippedExisting, skipped, extendMode, rootStubStatus) {
1146
+ /**
1147
+ * Generate Claude Code hooks configuration for ZhuanSpec workflow.
1148
+ * Creates .claude/settings.json with hooks for SessionStart, PreToolUse, PostToolUse, and Stop.
1149
+ */
1150
+ async generateClaudeSettings(projectPath) {
1151
+ const claudeDir = path.join(projectPath, '.claude');
1152
+ const settingsPath = path.join(claudeDir, 'settings.json');
1153
+ // Ensure .claude directory exists
1154
+ await FileSystemUtils.createDirectory(claudeDir);
1155
+ // Check if settings.json already exists
1156
+ const existingSettings = await this.readExistingSettings(settingsPath);
1157
+ // Define ZhuanSpec hooks configuration
1158
+ const zhuanspecHooksConfig = {
1159
+ hooks: {
1160
+ SessionStart: [
1161
+ {
1162
+ hooks: [
1163
+ {
1164
+ type: 'command',
1165
+ command: 'zhuanspec-hook init',
1166
+ statusMessage: 'Initializing ZhuanSpec...',
1167
+ },
1168
+ ],
1169
+ },
1170
+ ],
1171
+ PreToolUse: [
1172
+ {
1173
+ matcher: 'Write|Edit',
1174
+ hooks: [
1175
+ {
1176
+ type: 'command',
1177
+ command: 'zhuanspec-hook deviation-check',
1178
+ statusMessage: 'Checking proposal compliance...',
1179
+ },
1180
+ ],
1181
+ },
1182
+ ],
1183
+ PostToolUse: [
1184
+ {
1185
+ matcher: 'Write|Edit|Read|Grep|Glob|Bash|Skill|Agent|mcp__.*',
1186
+ hooks: [
1187
+ {
1188
+ type: 'command',
1189
+ command: 'zhuanspec-hook record-progress',
1190
+ statusMessage: '📝 Recording progress...',
1191
+ async: false,
1192
+ },
1193
+ {
1194
+ type: 'command',
1195
+ command: 'zhuanspec-hook collect-knowledge',
1196
+ statusMessage: '📚 Collecting knowledge...',
1197
+ async: false,
1198
+ },
1199
+ ],
1200
+ },
1201
+ ],
1202
+ Stop: [
1203
+ {
1204
+ hooks: [
1205
+ {
1206
+ type: 'command',
1207
+ command: 'zhuanspec-hook summarize',
1208
+ statusMessage: 'Summarizing progress...',
1209
+ },
1210
+ ],
1211
+ },
1212
+ ],
1213
+ Notification: [
1214
+ {
1215
+ hooks: [
1216
+ {
1217
+ type: 'command',
1218
+ command: 'zhuanspec-hook notify-milestone',
1219
+ statusMessage: 'Checking milestones...',
1220
+ },
1221
+ ],
1222
+ },
1223
+ ],
1224
+ },
1225
+ env: {
1226
+ ZHUANSPEC_PHASE: 'idle',
1227
+ ZHUANSPEC_CHANGE_ID: '',
1228
+ ZHUANSPEC_CURRENT_TASK: '',
1229
+ ZHUANSPEC_HOOKS_VERSION: '1.0.0',
1230
+ },
1231
+ };
1232
+ // Merge with existing settings if present
1233
+ const mergeResult = this.mergeSettings(existingSettings, zhuanspecHooksConfig);
1234
+ // Write the settings file
1235
+ await FileSystemUtils.writeFile(settingsPath, JSON.stringify(mergeResult.settings, null, 2));
1236
+ return mergeResult.summary;
1237
+ }
1238
+ async readExistingSettings(settingsPath) {
1239
+ try {
1240
+ if (await FileSystemUtils.fileExists(settingsPath)) {
1241
+ const content = await FileSystemUtils.readFile(settingsPath);
1242
+ return JSON.parse(content);
1243
+ }
1244
+ }
1245
+ catch {
1246
+ // Ignore parse errors, will create new file
1247
+ }
1248
+ return null;
1249
+ }
1250
+ mergeSettings(existing, zhuanspecConfig) {
1251
+ const summary = {
1252
+ totalAddedHooks: 0,
1253
+ duplicateHooksSkipped: 0,
1254
+ };
1255
+ if (!existing) {
1256
+ const zhuanspecHooks = zhuanspecConfig.hooks;
1257
+ for (const hookConfigs of Object.values(zhuanspecHooks)) {
1258
+ summary.totalAddedHooks += Array.isArray(hookConfigs) ? hookConfigs.length : 0;
1259
+ }
1260
+ return { settings: zhuanspecConfig, summary };
1261
+ }
1262
+ // Deep merge: preserve existing settings while adding ZhuanSpec hooks
1263
+ const merged = { ...existing };
1264
+ // Merge hooks
1265
+ if (merged.hooks && typeof merged.hooks === 'object') {
1266
+ const existingHooks = merged.hooks;
1267
+ const zhuanspecHooks = zhuanspecConfig.hooks;
1268
+ // For each hook type, append ZhuanSpec hooks to existing array
1269
+ for (const [hookType, zhuanspecHookConfig] of Object.entries(zhuanspecHooks)) {
1270
+ if (existingHooks[hookType] && Array.isArray(existingHooks[hookType])) {
1271
+ // Append ZhuanSpec hooks to existing hooks array while de-duplicating.
1272
+ const existingArray = existingHooks[hookType];
1273
+ const zhuanspecArray = zhuanspecHookConfig;
1274
+ const existingSignatures = new Set(existingArray.map((item) => JSON.stringify(item)));
1275
+ const uniqueToAppend = [];
1276
+ for (const item of zhuanspecArray) {
1277
+ const signature = JSON.stringify(item);
1278
+ if (existingSignatures.has(signature)) {
1279
+ summary.duplicateHooksSkipped += 1;
1280
+ continue;
1281
+ }
1282
+ existingSignatures.add(signature);
1283
+ uniqueToAppend.push(item);
1284
+ }
1285
+ summary.totalAddedHooks += uniqueToAppend.length;
1286
+ existingHooks[hookType] = [...existingArray, ...uniqueToAppend];
1287
+ }
1288
+ else {
1289
+ // Create new hook type with ZhuanSpec config
1290
+ const zhuanspecArray = zhuanspecHookConfig;
1291
+ summary.totalAddedHooks += Array.isArray(zhuanspecArray)
1292
+ ? zhuanspecArray.length
1293
+ : 0;
1294
+ existingHooks[hookType] = zhuanspecHookConfig;
1295
+ }
1296
+ }
1297
+ }
1298
+ else {
1299
+ // No existing hooks, add all ZhuanSpec hooks
1300
+ const zhuanspecHooks = zhuanspecConfig.hooks;
1301
+ for (const hookConfigs of Object.values(zhuanspecHooks)) {
1302
+ summary.totalAddedHooks += Array.isArray(hookConfigs) ? hookConfigs.length : 0;
1303
+ }
1304
+ merged.hooks = zhuanspecConfig.hooks;
1305
+ }
1306
+ // Merge env variables
1307
+ if (merged.env && typeof merged.env === 'object') {
1308
+ const existingEnv = merged.env;
1309
+ const zhuanspecEnv = zhuanspecConfig.env;
1310
+ merged.env = { ...existingEnv, ...zhuanspecEnv };
1311
+ }
1312
+ else {
1313
+ merged.env = zhuanspecConfig.env;
1314
+ }
1315
+ return { settings: merged, summary };
1316
+ }
1317
+ displaySuccessMessage(selectedTools, created, refreshed, skippedExisting, skipped, extendMode, rootStubStatus, claudeHookSummary) {
1114
1318
  console.log(); // Empty line for spacing
1115
1319
  const successHeadline = extendMode
1116
1320
  ? 'ZhuanSpec 工具配置已更新!'
@@ -1150,21 +1354,27 @@ export class InitCommand {
1150
1354
  console.log(PALETTE.midGray('斜杠命令在启动时加载。请重启您的编程助手'));
1151
1355
  console.log(PALETTE.midGray('以确保新的 /zhuanspec 命令出现在命令面板中。'));
1152
1356
  }
1357
+ console.log();
1358
+ console.log(PALETTE.white('Claude Hook 注册说明'));
1359
+ console.log(PALETTE.lightGray(`- 直接注册(Claude 生命周期): SessionStart(init), PreToolUse(deviation-check), PostToolUse(record-progress/collect-knowledge), Stop(summarize)`));
1360
+ console.log(PALETTE.lightGray('- 间接触发(阶段门禁): pre-apply/post-apply/review-* 通过上述主链路与 zhuanspec review 执行'));
1361
+ console.log(PALETTE.midGray(`- 本次 hooks 变更: 新增 ${claudeHookSummary.totalAddedHooks},跳过重复 ${claudeHookSummary.duplicateHooksSkipped}`));
1153
1362
  // Get the selected tool name(s) for display
1154
1363
  const toolName = this.formatToolNames(selectedTools);
1364
+ // Display workflow diagram
1365
+ console.log();
1366
+ console.log(PALETTE.white('ZhuanSpec 工作流程'));
1367
+ console.log(PALETTE.darkGray('─'.repeat(60)));
1368
+ console.log();
1369
+ this.displayWorkflowDiagram();
1370
+ console.log();
1371
+ console.log(PALETTE.darkGray('─'.repeat(60)));
1372
+ console.log();
1373
+ // Quick start guide
1374
+ console.log(PALETTE.white(`快速开始 - 在 ${toolName} 中执行:`));
1375
+ console.log(PALETTE.lightGray(' /zhuanspec:proposal "我想添加 [您的功能]"'));
1376
+ console.log(PALETTE.midGray(' → 自动创建变更提案,开始规范化开发流程'));
1155
1377
  console.log();
1156
- console.log(`下一步 - 将这些提示复制到 ${toolName}:`);
1157
- console.log(chalk.gray('────────────────────────────────────────────────────────────'));
1158
- console.log(PALETTE.white('1. 填充您的项目上下文:'));
1159
- console.log(PALETTE.lightGray(' "请阅读 zhuanspec/project.md 并帮助我填写'));
1160
- console.log(PALETTE.lightGray(' 有关我的项目、技术栈和约定的详细信息"\n'));
1161
- console.log(PALETTE.white('2. 创建您的第一个变更提案:'));
1162
- console.log(PALETTE.lightGray(' "我想添加 [您的功能]。请为此功能'));
1163
- console.log(PALETTE.lightGray(' 创建一个 ZhuanSpec 变更提案"\n'));
1164
- console.log(PALETTE.white('3. 了解 ZhuanSpec 工作流:'));
1165
- console.log(PALETTE.lightGray(' "请解释 zhuanspec/AGENTS.md 中的 ZhuanSpec 工作流'));
1166
- console.log(PALETTE.lightGray(' 以及我应该如何在这个项目上与您合作"'));
1167
- console.log(PALETTE.darkGray('────────────────────────────────────────────────────────────\n'));
1168
1378
  // Codex heads-up: prompts installed globally
1169
1379
  const selectedToolIds = new Set(selectedTools.map((t) => t.value));
1170
1380
  if (selectedToolIds.has('codex')) {
@@ -1173,6 +1383,24 @@ export class InitCommand {
1173
1383
  console.log();
1174
1384
  }
1175
1385
  }
1386
+ displayWorkflowDiagram() {
1387
+ const gray = '\x1b[90m';
1388
+ const green = '\x1b[32m';
1389
+ const cyan = '\x1b[36m';
1390
+ const yellow = '\x1b[33m';
1391
+ const magenta = '\x1b[35m';
1392
+ const reset = '\x1b[0m';
1393
+ console.log();
1394
+ console.log(` ${gray}[技术设计]${reset} → ${green}[创建提案]${reset} → ${cyan}[实施变更]${reset} → ${yellow}[审查门禁]${reset} → ${magenta}[归档完成]${reset}`);
1395
+ console.log();
1396
+ console.log(` ${gray}/techDesign${reset} ${gray}/proposal${reset} ${cyan}/apply${reset} ${yellow}/review${reset} ${magenta}/archive${reset}`);
1397
+ console.log();
1398
+ console.log(` ${gray}techDesign${reset} : 可选阶段,生成技术设计文档`);
1399
+ console.log(` ${green}propose${reset} : 创建变更提案,等待审批,可集成测试 case`);
1400
+ console.log(` ${cyan}apply${reset} : 实施变更,执行 tasks 追踪进度`);
1401
+ console.log(` ${yellow}review${reset} : 审查门禁,代码审查 + 单测 + spec-code 一致性验证`);
1402
+ console.log(` ${magenta}archive${reset} : 归档完成,更新 specs 记录变更和知识沉淀`);
1403
+ }
1176
1404
  formatToolNames(tools) {
1177
1405
  const names = tools
1178
1406
  .map((tool) => tool.successLabel ?? tool.name)
@@ -1,7 +1,10 @@
1
1
  export function normalizeRequirementName(name) {
2
2
  return name.trim();
3
3
  }
4
- const REQUIREMENT_HEADER_REGEX = /^###\s*Requirement:\s*(.+)\s*$/;
4
+ const REQUIREMENT_HEADER_REGEX = /^###\s*(?:Requirement|需求)[::]\s*(.+)\s*$/i;
5
+ function isRequirementHeader(line) {
6
+ return /^###\s*(?:Requirement|需求)[::]/i.test(line);
7
+ }
5
8
  /**
6
9
  * Extracts the Requirements section from a spec file and parses requirement blocks.
7
10
  */
@@ -80,10 +83,10 @@ function normalizeLineEndings(content) {
80
83
  export function parseDeltaSpec(content) {
81
84
  const normalized = normalizeLineEndings(content);
82
85
  const sections = splitTopLevelSections(normalized);
83
- const addedLookup = getSectionCaseInsensitive(sections, 'ADDED Requirements');
84
- const modifiedLookup = getSectionCaseInsensitive(sections, 'MODIFIED Requirements');
85
- const removedLookup = getSectionCaseInsensitive(sections, 'REMOVED Requirements');
86
- const renamedLookup = getSectionCaseInsensitive(sections, 'RENAMED Requirements');
86
+ const addedLookup = getSectionByAliases(sections, ['ADDED Requirements', '新增要求']);
87
+ const modifiedLookup = getSectionByAliases(sections, ['MODIFIED Requirements', '修改要求']);
88
+ const removedLookup = getSectionByAliases(sections, ['REMOVED Requirements', '移除要求', '删除要求']);
89
+ const renamedLookup = getSectionByAliases(sections, ['RENAMED Requirements', '重命名要求']);
87
90
  const added = parseRequirementBlocksFromSection(addedLookup.body);
88
91
  const modified = parseRequirementBlocksFromSection(modifiedLookup.body);
89
92
  const removedNames = parseRemovedNames(removedLookup.body);
@@ -120,10 +123,10 @@ function splitTopLevelSections(content) {
120
123
  }
121
124
  return result;
122
125
  }
123
- function getSectionCaseInsensitive(sections, desired) {
124
- const target = desired.toLowerCase();
126
+ function getSectionByAliases(sections, aliases) {
127
+ const targets = new Set(aliases.map((alias) => alias.toLowerCase()));
125
128
  for (const [title, body] of Object.entries(sections)) {
126
- if (title.toLowerCase() === target)
129
+ if (targets.has(title.toLowerCase()))
127
130
  return { body, found: true };
128
131
  }
129
132
  return { body: '', found: false };
@@ -136,7 +139,7 @@ function parseRequirementBlocksFromSection(sectionBody) {
136
139
  let i = 0;
137
140
  while (i < lines.length) {
138
141
  // Seek next requirement header
139
- while (i < lines.length && !/^###\s+Requirement:/.test(lines[i]))
142
+ while (i < lines.length && !isRequirementHeader(lines[i]))
140
143
  i++;
141
144
  if (i >= lines.length)
142
145
  break;
@@ -149,7 +152,7 @@ function parseRequirementBlocksFromSection(sectionBody) {
149
152
  const name = normalizeRequirementName(m[1]);
150
153
  const buf = [headerLine];
151
154
  i++;
152
- while (i < lines.length && !/^###\s+Requirement:/.test(lines[i]) && !/^##\s+/.test(lines[i])) {
155
+ while (i < lines.length && !isRequirementHeader(lines[i]) && !/^##\s+/.test(lines[i])) {
153
156
  buf.push(lines[i]);
154
157
  i++;
155
158
  }