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 +124 -26
- package/package.json +1 -1
- package/skills/devbooks-test-reviewer/SKILL.md +10 -0
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
|
-
//
|
|
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,
|
|
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
|
-
//
|
|
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,
|
|
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
|
-
//
|
|
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,
|
|
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('✗') + `
|
|
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('⚠') + `
|
|
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('ℹ') + `
|
|
906
|
+
console.log(chalk.blue('ℹ') + ` Detected configured tools: ${toolNames.join(', ')}`);
|
|
823
907
|
|
|
824
|
-
//
|
|
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}:
|
|
912
|
+
console.log(chalk.green('✓') + ` ${result.tool} ${result.type}: updated ${result.count}/${result.total}`);
|
|
829
913
|
}
|
|
830
914
|
}
|
|
831
915
|
|
|
832
|
-
//
|
|
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
|
-
|
|
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,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)
|