dev-playbooks 1.0.16 → 1.0.18

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/bin/devbooks.js CHANGED
@@ -16,6 +16,7 @@
16
16
  * --dry-run 模拟运行,不实际修改文件
17
17
  * --keep-old 迁移后保留原目录
18
18
  * --help 显示帮助信息
19
+ * --version Show version
19
20
  */
20
21
 
21
22
  import fs from 'fs';
@@ -179,6 +180,50 @@ function expandPath(p) {
179
180
  return p;
180
181
  }
181
182
 
183
+ /**
184
+ * Update content between DEVBOOKS:START/END markers in a file
185
+ * Preserves user customizations outside the markers
186
+ */
187
+ function updateManagedContent(filePath, newManagedContent) {
188
+ if (!fs.existsSync(filePath)) {
189
+ return false;
190
+ }
191
+
192
+ const content = fs.readFileSync(filePath, 'utf-8');
193
+ const startMarker = DEVBOOKS_MARKERS.start;
194
+ const endMarker = DEVBOOKS_MARKERS.end;
195
+
196
+ const startIdx = content.indexOf(startMarker);
197
+ const endIdx = content.indexOf(endMarker);
198
+
199
+ if (startIdx === -1 || endIdx === -1 || startIdx >= endIdx) {
200
+ // No valid markers found, cannot update
201
+ return false;
202
+ }
203
+
204
+ // Extract the managed block from new content
205
+ const newStartIdx = newManagedContent.indexOf(startMarker);
206
+ const newEndIdx = newManagedContent.indexOf(endMarker);
207
+
208
+ if (newStartIdx === -1 || newEndIdx === -1) {
209
+ return false;
210
+ }
211
+
212
+ const newManagedBlock = newManagedContent.slice(newStartIdx, newEndIdx + endMarker.length);
213
+
214
+ // Replace the old managed block
215
+ const before = content.slice(0, startIdx);
216
+ const after = content.slice(endIdx + endMarker.length);
217
+ const updatedContent = before + newManagedBlock + after;
218
+
219
+ if (updatedContent !== content) {
220
+ fs.writeFileSync(filePath, updatedContent);
221
+ return true;
222
+ }
223
+
224
+ return false;
225
+ }
226
+
182
227
  function copyDirSync(src, dest) {
183
228
  if (!fs.existsSync(src)) return 0;
184
229
  fs.mkdirSync(dest, { recursive: true });
@@ -232,6 +277,21 @@ function getSkillsSupportDescription(level) {
232
277
  }
233
278
  }
234
279
 
280
+ function getCliVersion() {
281
+ const packagePath = path.join(__dirname, '..', 'package.json');
282
+ try {
283
+ const raw = fs.readFileSync(packagePath, 'utf-8');
284
+ const pkg = JSON.parse(raw);
285
+ return pkg.version || 'unknown';
286
+ } catch {
287
+ return 'unknown';
288
+ }
289
+ }
290
+
291
+ function showVersion() {
292
+ console.log(`${CLI_COMMAND} v${getCliVersion()}`);
293
+ }
294
+
235
295
  // ============================================================================
236
296
  // Skills 支持说明
237
297
  // ============================================================================
@@ -376,7 +436,7 @@ function installSkills(toolIds, update = false) {
376
436
  // 安装 Rules(Cursor, Windsurf, Gemini, Antigravity, OpenCode, Continue)
377
437
  // ============================================================================
378
438
 
379
- function installRules(toolIds, projectDir) {
439
+ function installRules(toolIds, projectDir, update = false) {
380
440
  const results = [];
381
441
 
382
442
  for (const toolId of toolIds) {
@@ -387,14 +447,20 @@ function installRules(toolIds, projectDir) {
387
447
  const rulesDestDir = path.join(projectDir, tool.rulesDir);
388
448
  fs.mkdirSync(rulesDestDir, { recursive: true });
389
449
 
390
- // 创建 devbooks.md 规则文件
450
+ // Create devbooks.md rule file
391
451
  const ruleContent = generateRuleContent(toolId);
392
452
  const ruleFileName = toolId === 'gemini' ? 'GEMINI.md' : 'devbooks.md';
393
453
  const rulePath = path.join(rulesDestDir, ruleFileName);
394
454
 
395
455
  if (!fs.existsSync(rulePath)) {
396
456
  fs.writeFileSync(rulePath, ruleContent);
397
- results.push({ tool: tool.name, type: 'rules', path: rulePath });
457
+ results.push({ tool: tool.name, type: 'rules', path: rulePath, action: 'created' });
458
+ } else if (update) {
459
+ // Update existing file's DEVBOOKS:START/END content
460
+ const updated = updateManagedContent(rulePath, ruleContent);
461
+ if (updated) {
462
+ results.push({ tool: tool.name, type: 'rules', path: rulePath, action: 'updated' });
463
+ }
398
464
  }
399
465
  }
400
466
  }
@@ -456,38 +522,56 @@ ${DEVBOOKS_MARKERS.end}
456
522
  // 安装自定义指令文件
457
523
  // ============================================================================
458
524
 
459
- function installInstructionFiles(toolIds, projectDir) {
525
+ function installInstructionFiles(toolIds, projectDir, update = false) {
460
526
  const results = [];
461
527
 
462
528
  for (const toolId of toolIds) {
463
529
  const tool = AI_TOOLS.find(t => t.id === toolId);
464
530
  if (!tool) continue;
465
531
 
466
- // GitHub Copilot 特殊处理
532
+ // GitHub Copilot special handling
467
533
  if (toolId === 'github-copilot') {
468
534
  const instructionsDir = path.join(projectDir, '.github', 'instructions');
469
535
  fs.mkdirSync(instructionsDir, { recursive: true });
470
536
 
471
537
  const copilotInstructionPath = path.join(projectDir, '.github', 'copilot-instructions.md');
538
+ const copilotContent = generateCopilotInstructions();
472
539
  if (!fs.existsSync(copilotInstructionPath)) {
473
- fs.writeFileSync(copilotInstructionPath, generateCopilotInstructions());
474
- results.push({ tool: 'GitHub Copilot', type: 'instructions', path: copilotInstructionPath });
540
+ fs.writeFileSync(copilotInstructionPath, copilotContent);
541
+ results.push({ tool: 'GitHub Copilot', type: 'instructions', path: copilotInstructionPath, action: 'created' });
542
+ } else if (update) {
543
+ const updated = updateManagedContent(copilotInstructionPath, copilotContent);
544
+ if (updated) {
545
+ results.push({ tool: 'GitHub Copilot', type: 'instructions', path: copilotInstructionPath, action: 'updated' });
546
+ }
475
547
  }
476
548
 
477
- // 创建 devbooks.instructions.md
549
+ // Create devbooks.instructions.md
478
550
  const devbooksInstructionPath = path.join(instructionsDir, 'devbooks.instructions.md');
551
+ const devbooksContent = generateCopilotDevbooksInstructions();
479
552
  if (!fs.existsSync(devbooksInstructionPath)) {
480
- fs.writeFileSync(devbooksInstructionPath, generateCopilotDevbooksInstructions());
481
- results.push({ tool: 'GitHub Copilot', type: 'instructions', path: devbooksInstructionPath });
553
+ fs.writeFileSync(devbooksInstructionPath, devbooksContent);
554
+ results.push({ tool: 'GitHub Copilot', type: 'instructions', path: devbooksInstructionPath, action: 'created' });
555
+ } else if (update) {
556
+ const updated = updateManagedContent(devbooksInstructionPath, devbooksContent);
557
+ if (updated) {
558
+ results.push({ tool: 'GitHub Copilot', type: 'instructions', path: devbooksInstructionPath, action: 'updated' });
559
+ }
482
560
  }
483
561
  }
484
562
 
485
- // 创建 AGENTS.md / CLAUDE.md / GEMINI.md
563
+ // Create AGENTS.md / CLAUDE.md / GEMINI.md
486
564
  if (tool.instructionFile && !tool.instructionFile.includes('/')) {
487
565
  const instructionPath = path.join(projectDir, tool.instructionFile);
566
+ const instructionContent = generateAgentsContent(tool.instructionFile);
488
567
  if (!fs.existsSync(instructionPath)) {
489
- fs.writeFileSync(instructionPath, generateAgentsContent(tool.instructionFile));
490
- results.push({ tool: tool.name, type: 'instruction', path: instructionPath });
568
+ fs.writeFileSync(instructionPath, instructionContent);
569
+ results.push({ tool: tool.name, type: 'instruction', path: instructionPath, action: 'created' });
570
+ } else if (update) {
571
+ const updated = updateManagedContent(instructionPath, instructionContent);
572
+ if (updated) {
573
+ results.push({ tool: tool.name, type: 'instruction', path: instructionPath, action: 'updated' });
574
+ }
491
575
  }
492
576
  }
493
577
  }
@@ -791,27 +875,27 @@ async function initCommand(projectDir, options) {
791
875
  }
792
876
 
793
877
  // ============================================================================
794
- // Update 命令
878
+ // Update Command
795
879
  // ============================================================================
796
880
 
797
881
  async function updateCommand(projectDir) {
798
882
  console.log();
799
- console.log(chalk.bold('DevBooks 更新'));
883
+ console.log(chalk.bold('DevBooks Update'));
800
884
  console.log();
801
885
 
802
- // 检查是否已初始化
886
+ // Check if initialized
803
887
  const configPath = path.join(projectDir, '.devbooks', 'config.yaml');
804
888
  if (!fs.existsSync(configPath)) {
805
- console.log(chalk.red('✗') + ` 未找到 DevBooks 配置。请先运行 \`${CLI_COMMAND} init\`。`);
889
+ console.log(chalk.red('✗') + ` DevBooks config not found. Please run \`${CLI_COMMAND} init\` first.`);
806
890
  process.exit(1);
807
891
  }
808
892
 
809
- // 加载配置
893
+ // Load config
810
894
  const config = loadConfig(projectDir);
811
895
  const configuredTools = config.aiTools;
812
896
 
813
897
  if (configuredTools.length === 0) {
814
- console.log(chalk.yellow('⚠') + ` 未配置任何 AI 工具。运行 \`${CLI_COMMAND} init\` 进行配置。`);
898
+ console.log(chalk.yellow('⚠') + ` No AI tools configured. Run \`${CLI_COMMAND} init\` to configure.`);
815
899
  return;
816
900
  }
817
901
 
@@ -819,31 +903,41 @@ async function updateCommand(projectDir) {
819
903
  const tool = AI_TOOLS.find(t => t.id === id);
820
904
  return tool ? tool.name : id;
821
905
  });
822
- console.log(chalk.blue('ℹ') + ` 检测到已配置的工具: ${toolNames.join(', ')}`);
906
+ console.log(chalk.blue('ℹ') + ` Detected configured tools: ${toolNames.join(', ')}`);
823
907
 
824
- // 更新 Skills
908
+ // Update Skills (global directory)
825
909
  const skillsResults = installSkills(configuredTools, true);
826
910
  for (const result of skillsResults) {
827
911
  if (result.count > 0) {
828
- console.log(chalk.green('✓') + ` ${result.tool} ${result.type}: 更新了 ${result.count}/${result.total} 个`);
912
+ console.log(chalk.green('✓') + ` ${result.tool} ${result.type}: updated ${result.count}/${result.total}`);
829
913
  }
830
914
  }
831
915
 
832
- // 更新 Rules
916
+ // Update Rules (project directory)
833
917
  const rulesTools = configuredTools.filter(id => {
834
918
  const tool = AI_TOOLS.find(t => t.id === id);
835
919
  return tool && tool.skillsSupport === SKILLS_SUPPORT.RULES;
836
920
  });
837
921
 
838
922
  if (rulesTools.length > 0) {
839
- const rulesResults = installRules(rulesTools, projectDir);
923
+ const rulesResults = installRules(rulesTools, projectDir, true);
840
924
  for (const result of rulesResults) {
841
- console.log(chalk.green('') + ` ${result.tool}: 更新了规则文件`);
925
+ if (result.action === 'updated') {
926
+ console.log(chalk.green('✓') + ` ${result.tool}: updated rule file`);
927
+ }
928
+ }
929
+ }
930
+
931
+ // Update instruction files (project directory)
932
+ const instructionResults = installInstructionFiles(configuredTools, projectDir, true);
933
+ for (const result of instructionResults) {
934
+ if (result.action === 'updated') {
935
+ console.log(chalk.green('✓') + ` ${result.tool}: updated instruction file ${path.relative(projectDir, result.path)}`);
842
936
  }
843
937
  }
844
938
 
845
939
  console.log();
846
- console.log(chalk.green('✓') + ' 更新完成!');
940
+ console.log(chalk.green('✓') + ' Update complete!');
847
941
  }
848
942
 
849
943
  // ============================================================================
@@ -943,6 +1037,7 @@ function showHelp() {
943
1037
  console.log(' --keep-old 迁移后保留原目录');
944
1038
  console.log(' --force 强制重新执行所有步骤');
945
1039
  console.log(' -h, --help 显示此帮助信息');
1040
+ console.log(' -v, --version Show version');
946
1041
  console.log();
947
1042
  console.log(chalk.cyan('支持的 AI 工具:'));
948
1043
 
@@ -1006,6 +1101,9 @@ async function main() {
1006
1101
  if (arg === '-h' || arg === '--help') {
1007
1102
  showHelp();
1008
1103
  process.exit(0);
1104
+ } else if (arg === '-v' || arg === '--version') {
1105
+ showVersion();
1106
+ process.exit(0);
1009
1107
  } else if (arg === '--tools') {
1010
1108
  options.tools = args[++i];
1011
1109
  } else if (arg === '--from') {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dev-playbooks",
3
- "version": "1.0.16",
3
+ "version": "1.0.18",
4
4
  "description": "AI-powered spec-driven development workflow",
5
5
  "keywords": [
6
6
  "devbooks",
@@ -1,3 +1,13 @@
1
+ ---
2
+ name: devbooks-test-reviewer
3
+ description: "devbooks-test-reviewer: Reviews tests/ quality (coverage, edge cases, readability, maintainability) as the Test Reviewer role. Outputs review comments only and never changes code. Use when the user requests test review/test quality/coverage/edge cases, or when acting as the test reviewer during the DevBooks apply phase."
4
+ tools:
5
+ - Glob
6
+ - Grep
7
+ - Read
8
+ - Bash
9
+ ---
10
+
1
11
  # DevBooks: Test Reviewer
2
12
 
3
13
  ## Prerequisites: Configuration Discovery (Protocol-Agnostic)