dev-playbooks-cn 1.5.0 → 1.5.2

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/README.md CHANGED
@@ -81,10 +81,21 @@ npx dev-playbooks-cn@latest init
81
81
 
82
82
  ### 安装落点
83
83
 
84
- 初始化后:
85
- - Claude Code:`~/.claude/skills/devbooks-*`
86
- - Codex CLI:`~/.codex/skills/devbooks-*`
87
- - OpenCode:`~/.config/opencode/skill/devbooks-*`
84
+ 初始化时可选择 Skills 安装位置:
85
+
86
+ | 安装范围 | 说明 | 路径示例 |
87
+ |----------|------|----------|
88
+ | **项目级**(默认) | 仅当前项目可用 | `.claude/skills/devbooks-*` |
89
+ | **全局** | 所有项目共享 | `~/.claude/skills/devbooks-*` |
90
+
91
+ ```bash
92
+ # 交互式选择
93
+ dev-playbooks-cn init
94
+
95
+ # 非交互式指定
96
+ dev-playbooks-cn init --tools claude --scope project # 项目级
97
+ dev-playbooks-cn init --tools claude --scope global # 全局
98
+ ```
88
99
 
89
100
  ### 快速集成
90
101
 
@@ -193,6 +204,7 @@ DevBooks 使用两个目录根:
193
204
  |-------|------|
194
205
  | `devbooks-entropy-monitor` | 系统熵度量 |
195
206
  | `devbooks-brownfield-bootstrap` | 存量项目初始化 |
207
+ | `devbooks-convergence-audit` | 收敛性审计(反迷惑设计) |
196
208
 
197
209
  ---
198
210
 
package/bin/devbooks.js CHANGED
@@ -24,7 +24,7 @@ import path from 'path';
24
24
  import os from 'os';
25
25
  import { fileURLToPath } from 'url';
26
26
  import { spawn } from 'child_process';
27
- import { checkbox, confirm } from '@inquirer/prompts';
27
+ import { checkbox, confirm, select } from '@inquirer/prompts';
28
28
  import chalk from 'chalk';
29
29
  import ora from 'ora';
30
30
 
@@ -45,6 +45,15 @@ const SKILLS_SUPPORT = {
45
45
  BASIC: 'basic' // 仅基础指令(无独立 Skills 概念)
46
46
  };
47
47
 
48
+ // ============================================================================
49
+ // Skills 安装范围
50
+ // ============================================================================
51
+
52
+ const INSTALL_SCOPE = {
53
+ GLOBAL: 'global', // 全局安装(~/.claude/skills 等)
54
+ PROJECT: 'project' // 项目级安装(.claude/skills 等)
55
+ };
56
+
48
57
  // ============================================================================
49
58
  // AI 工具配置
50
59
  // ============================================================================
@@ -540,12 +549,68 @@ async function promptToolSelection(projectDir) {
540
549
  return selectedTools;
541
550
  }
542
551
 
552
+ async function promptInstallScope(projectDir, selectedTools) {
553
+ // 检查是否有需要安装 Skills 的工具
554
+ const fullSupportTools = selectedTools.filter(id => {
555
+ const tool = AI_TOOLS.find(t => t.id === id);
556
+ return tool && tool.skillsSupport === SKILLS_SUPPORT.FULL;
557
+ });
558
+
559
+ if (fullSupportTools.length === 0) {
560
+ return INSTALL_SCOPE.PROJECT; // 没有完整 Skills 支持的工具,默认项目级
561
+ }
562
+
563
+ // 读取已保存的配置
564
+ const config = loadConfig(projectDir);
565
+ const savedScope = config.installScope;
566
+
567
+ console.log();
568
+ console.log(chalk.bold('📦 Skills 安装位置'));
569
+ console.log(chalk.gray('─'.repeat(50)));
570
+ console.log();
571
+
572
+ const scope = await select({
573
+ message: 'Skills 安装到哪里?',
574
+ choices: [
575
+ {
576
+ name: `项目级 ${chalk.gray('(.claude/skills 等,仅当前项目可用)')}`,
577
+ value: INSTALL_SCOPE.PROJECT,
578
+ description: '推荐:Skills 随项目走,不影响其他项目'
579
+ },
580
+ {
581
+ name: `全局 ${chalk.gray('(~/.claude/skills 等,所有项目共享)')}`,
582
+ value: INSTALL_SCOPE.GLOBAL,
583
+ description: '所有项目共享同一套 Skills'
584
+ }
585
+ ],
586
+ default: savedScope || INSTALL_SCOPE.PROJECT
587
+ });
588
+
589
+ return scope;
590
+ }
591
+
543
592
 
544
593
  // ============================================================================
545
594
  // 安装 Skills(Claude Code, Codex CLI, Qoder)
546
595
  // ============================================================================
547
596
 
548
- function installSkills(toolIds, update = false) {
597
+ function getSkillsDestDir(tool, scope, projectDir) {
598
+ // 根据安装范围确定目标目录
599
+ if (scope === INSTALL_SCOPE.PROJECT) {
600
+ // 项目级安装:使用项目目录下的相对路径
601
+ if (tool.id === 'claude') {
602
+ return path.join(projectDir, '.claude', 'skills');
603
+ } else if (tool.id === 'codex') {
604
+ return path.join(projectDir, '.codex', 'skills');
605
+ } else if (tool.id === 'opencode') {
606
+ return path.join(projectDir, '.opencode', 'skill');
607
+ }
608
+ }
609
+ // 全局安装:使用工具定义的全局目录
610
+ return tool.skillsDir;
611
+ }
612
+
613
+ function installSkills(toolIds, projectDir, scope = INSTALL_SCOPE.GLOBAL, update = false) {
549
614
  const results = [];
550
615
 
551
616
  for (const toolId of toolIds) {
@@ -555,7 +620,7 @@ function installSkills(toolIds, update = false) {
555
620
  // Claude Code / Codex CLI / OpenCode(含 oh-my-opencode)支持相同格式的 Skills
556
621
  if ((toolId === 'claude' || toolId === 'codex' || toolId === 'opencode') && tool.skillsDir) {
557
622
  const skillsSrcDir = path.join(__dirname, '..', 'skills');
558
- const skillsDestDir = tool.skillsDir;
623
+ const skillsDestDir = getSkillsDestDir(tool, scope, projectDir);
559
624
 
560
625
  if (!fs.existsSync(skillsSrcDir)) continue;
561
626
 
@@ -601,7 +666,9 @@ function installSkills(toolIds, update = false) {
601
666
  type: 'skills',
602
667
  count: installedCount,
603
668
  total: skillDirs.length,
604
- removed: removedCount
669
+ removed: removedCount,
670
+ scope: scope,
671
+ path: skillsDestDir
605
672
  });
606
673
  }
607
674
 
@@ -1013,7 +1080,7 @@ function createProjectStructure(projectDir) {
1013
1080
  // 保存配置
1014
1081
  // ============================================================================
1015
1082
 
1016
- function saveConfig(toolIds, projectDir) {
1083
+ function saveConfig(toolIds, projectDir, installScope = INSTALL_SCOPE.PROJECT) {
1017
1084
  const configPath = path.join(projectDir, '.devbooks', 'config.yaml');
1018
1085
 
1019
1086
  // 读取现有配置或创建新配置
@@ -1033,6 +1100,17 @@ function saveConfig(toolIds, projectDir) {
1033
1100
  configContent = configContent.trimEnd() + '\n\n' + toolsYaml + '\n';
1034
1101
  }
1035
1102
 
1103
+ // 更新 install_scope 部分
1104
+ const scopeYaml = `install_scope: ${installScope}`;
1105
+
1106
+ if (configContent.includes('install_scope:')) {
1107
+ // 替换现有的 install_scope 部分
1108
+ configContent = configContent.replace(/install_scope:.*/, scopeYaml);
1109
+ } else {
1110
+ // 追加 install_scope 部分
1111
+ configContent = configContent.trimEnd() + '\n\n' + scopeYaml + '\n';
1112
+ }
1113
+
1036
1114
  fs.writeFileSync(configPath, configContent);
1037
1115
  }
1038
1116
 
@@ -1040,23 +1118,26 @@ function loadConfig(projectDir) {
1040
1118
  const configPath = path.join(projectDir, '.devbooks', 'config.yaml');
1041
1119
 
1042
1120
  if (!fs.existsSync(configPath)) {
1043
- return { aiTools: [] };
1121
+ return { aiTools: [], installScope: null };
1044
1122
  }
1045
1123
 
1046
1124
  const content = fs.readFileSync(configPath, 'utf-8');
1047
- const match = content.match(/ai_tools:\s*([\s\S]*?)(?=\n\w|\n$|$)/);
1048
-
1049
- if (!match) {
1050
- return { aiTools: [] };
1051
- }
1052
-
1053
- const tools = match[1]
1054
- .split('\n')
1055
- .map(line => line.trim())
1056
- .filter(line => line.startsWith('-'))
1057
- .map(line => line.replace(/^-\s*/, '').trim());
1058
1125
 
1059
- return { aiTools: tools };
1126
+ // 解析 ai_tools
1127
+ const toolsMatch = content.match(/ai_tools:\s*([\s\S]*?)(?=\n\w|\n$|$)/);
1128
+ const tools = toolsMatch
1129
+ ? toolsMatch[1]
1130
+ .split('\n')
1131
+ .map(line => line.trim())
1132
+ .filter(line => line.startsWith('-'))
1133
+ .map(line => line.replace(/^-\s*/, '').trim())
1134
+ : [];
1135
+
1136
+ // 解析 install_scope
1137
+ const scopeMatch = content.match(/install_scope:\s*(\w+)/);
1138
+ const installScope = scopeMatch ? scopeMatch[1] : null;
1139
+
1140
+ return { aiTools: tools, installScope };
1060
1141
  }
1061
1142
 
1062
1143
  // ============================================================================
@@ -1072,6 +1153,7 @@ async function initCommand(projectDir, options) {
1072
1153
 
1073
1154
  // 确定选择的工具
1074
1155
  let selectedTools;
1156
+ let installScope = INSTALL_SCOPE.PROJECT; // 默认项目级安装
1075
1157
 
1076
1158
  if (options.tools) {
1077
1159
  if (options.tools === 'all') {
@@ -1084,8 +1166,16 @@ async function initCommand(projectDir, options) {
1084
1166
  );
1085
1167
  }
1086
1168
  console.log(chalk.blue('ℹ') + ` 非交互式模式:${selectedTools.length > 0 ? selectedTools.join(', ') : '无'}`);
1169
+
1170
+ // 非交互式模式下,检查 --scope 选项
1171
+ if (options.scope) {
1172
+ installScope = options.scope === 'global' ? INSTALL_SCOPE.GLOBAL : INSTALL_SCOPE.PROJECT;
1173
+ }
1087
1174
  } else {
1088
1175
  selectedTools = await promptToolSelection(projectDir);
1176
+
1177
+ // 交互式选择安装范围
1178
+ installScope = await promptInstallScope(projectDir, selectedTools);
1089
1179
  }
1090
1180
 
1091
1181
  // 创建项目结构
@@ -1093,8 +1183,8 @@ async function initCommand(projectDir, options) {
1093
1183
  const templateCount = createProjectStructure(projectDir);
1094
1184
  spinner.succeed(`创建了 ${templateCount} 个模板文件`);
1095
1185
 
1096
- // 保存配置
1097
- saveConfig(selectedTools, projectDir);
1186
+ // 保存配置(包含安装范围)
1187
+ saveConfig(selectedTools, projectDir, installScope);
1098
1188
 
1099
1189
  if (selectedTools.length === 0) {
1100
1190
  console.log();
@@ -1111,12 +1201,16 @@ async function initCommand(projectDir, options) {
1111
1201
 
1112
1202
  if (fullSupportTools.length > 0) {
1113
1203
  const skillsSpinner = ora('安装 Skills...').start();
1114
- const skillsResults = installSkills(fullSupportTools);
1204
+ const skillsResults = installSkills(fullSupportTools, projectDir, installScope);
1115
1205
  skillsSpinner.succeed('Skills 安装完成');
1116
1206
 
1117
1207
  for (const result of skillsResults) {
1118
1208
  if (result.count > 0) {
1119
- console.log(chalk.gray(` └ ${result.tool}: ${result.count}/${result.total} ${result.type}`));
1209
+ const scopeLabel = result.scope === INSTALL_SCOPE.PROJECT ? '项目级' : '全局';
1210
+ console.log(chalk.gray(` └ ${result.tool}: ${result.count}/${result.total} 个 ${result.type} (${scopeLabel})`));
1211
+ if (result.path) {
1212
+ console.log(chalk.gray(` → ${result.path}`));
1213
+ }
1120
1214
  } else if (result.note) {
1121
1215
  console.log(chalk.gray(` └ ${result.tool}: ${result.note}`));
1122
1216
  }
@@ -1217,6 +1311,7 @@ async function updateCommand(projectDir) {
1217
1311
  // 加载配置
1218
1312
  const config = loadConfig(projectDir);
1219
1313
  const configuredTools = config.aiTools;
1314
+ const installScope = config.installScope || INSTALL_SCOPE.PROJECT;
1220
1315
 
1221
1316
  if (configuredTools.length === 0) {
1222
1317
  console.log(chalk.yellow('⚠') + ` 未配置任何 AI 工具。运行 \`${CLI_COMMAND} init\` 进行配置。`);
@@ -1227,13 +1322,17 @@ async function updateCommand(projectDir) {
1227
1322
  const tool = AI_TOOLS.find(t => t.id === id);
1228
1323
  return tool ? tool.name : id;
1229
1324
  });
1230
- console.log(chalk.blue('ℹ') + ` 检测到已配置的工具: ${toolNames.join(', ')}`);
1325
+ const scopeLabel = installScope === INSTALL_SCOPE.PROJECT ? '项目级' : '全局';
1326
+ console.log(chalk.blue('ℹ') + ` 检测到已配置的工具: ${toolNames.join(', ')} (${scopeLabel}安装)`);
1231
1327
 
1232
- // 更新 Skills(全局目录)
1233
- const skillsResults = installSkills(configuredTools, true);
1328
+ // 更新 Skills(使用配置中保存的安装范围)
1329
+ const skillsResults = installSkills(configuredTools, projectDir, installScope, true);
1234
1330
  for (const result of skillsResults) {
1235
1331
  if (result.count > 0) {
1236
1332
  console.log(chalk.green('✓') + ` ${result.tool} ${result.type}: 更新了 ${result.count}/${result.total} 个`);
1333
+ if (result.path) {
1334
+ console.log(chalk.gray(` → ${result.path}`));
1335
+ }
1237
1336
  }
1238
1337
  if (result.removed && result.removed > 0) {
1239
1338
  console.log(chalk.green('✓') + ` ${result.tool} ${result.type}: 清理了 ${result.removed} 个已删除的技能`);
@@ -1375,6 +1474,8 @@ function showHelp() {
1375
1474
  console.log(chalk.cyan('选项:'));
1376
1475
  console.log(' --tools <tools> 非交互式指定 AI 工具');
1377
1476
  console.log(' 可用值: all, none, 或逗号分隔的工具 ID');
1477
+ console.log(' --scope <scope> Skills 安装位置 (非交互式模式)');
1478
+ console.log(' 可用值: project (默认), global');
1378
1479
  console.log(' --from <framework> 迁移来源框架 (openspec, speckit)');
1379
1480
  console.log(' --dry-run 模拟运行,不实际修改文件');
1380
1481
  console.log(' --keep-old 迁移后保留原目录');
@@ -1419,7 +1520,8 @@ function showHelp() {
1419
1520
  console.log(chalk.cyan('示例:'));
1420
1521
  console.log(` ${CLI_COMMAND} init # 交互式初始化`);
1421
1522
  console.log(` ${CLI_COMMAND} init my-project # 在 my-project 目录初始化`);
1422
- console.log(` ${CLI_COMMAND} init --tools claude,cursor # 非交互式`);
1523
+ console.log(` ${CLI_COMMAND} init --tools claude,cursor # 非交互式(默认项目级安装)`);
1524
+ console.log(` ${CLI_COMMAND} init --tools claude --scope global # 非交互式(全局安装)`);
1423
1525
  console.log(` ${CLI_COMMAND} update # 更新已配置的工具`);
1424
1526
  console.log(` ${CLI_COMMAND} migrate --from openspec # 从 OpenSpec 迁移`);
1425
1527
  console.log(` ${CLI_COMMAND} migrate --from speckit # 从 spec-kit 迁移`);
@@ -1449,6 +1551,8 @@ async function main() {
1449
1551
  process.exit(0);
1450
1552
  } else if (arg === '--tools') {
1451
1553
  options.tools = args[++i];
1554
+ } else if (arg === '--scope') {
1555
+ options.scope = args[++i];
1452
1556
  } else if (arg === '--from') {
1453
1557
  options.from = args[++i];
1454
1558
  } else if (arg === '--dry-run') {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dev-playbooks-cn",
3
- "version": "1.5.0",
3
+ "version": "1.5.2",
4
4
  "description": "AI-driven spec-based development workflow",
5
5
  "keywords": [
6
6
  "devbooks",
@@ -357,3 +357,35 @@
357
357
  输入:`tests/**` + `dev-playbooks/changes/<change-id>/verification.md`(如有)
358
358
  输出:覆盖盲区 / 边界条件遗漏 / 可维护性风险 / 改进建议。
359
359
  ```
360
+
361
+ ---
362
+
363
+ ## `devbooks-convergence-audit`(Convergence Auditor)【新】
364
+
365
+ - 作用:以证据优先、声明存疑的原则评估 DevBooks 工作流收敛性,检测"西西弗斯反模式"和"假完成"。主动验证而非信任文档声明。
366
+ - **反迷惑设计**:不相信文档中的任何断言(Status: Done、AC 打勾等),必须通过可验证证据确认。
367
+ - 使用场景:
368
+ - 你想评估工作流的真实收敛性(而非文档声明)
369
+ - 你怀疑某个变更包是"假完成"或"过时证据"
370
+ - 你想检测西西弗斯困境(反复返工但无法收敛)
371
+ - 使用话术:
372
+ ```text
373
+ 你现在是 Convergence Auditor。请点名使用 `devbooks-convergence-audit`。
374
+ 先读:`dev-playbooks/project.md`(如存在)
375
+ 目标:对指定变更包进行收敛性审计。
376
+
377
+ 请按反迷惑原则执行:
378
+ 1) 读取 `dev-playbooks/changes/<change-id>/` 下的所有文档
379
+ 2) 对每个"声明"(Status=Done、AC 打勾等)进行证据验证
380
+ 3) 实际运行测试验证(如可能)
381
+ 4) 输出:声明 vs 证据对比表 + 可信度评分 + 迷惑检测结果
382
+
383
+ 变更包:<change-id>
384
+ truth-root:dev-playbooks/specs
385
+ change-root:dev-playbooks/changes
386
+ ```
387
+ - 可信度评分说明:
388
+ - 90-100:✅ 可信收敛,继续当前流程
389
+ - 70-89:⚠️ 部分可信,需要补充验证
390
+ - 50-69:🟠 存疑,需要返工部分环节
391
+ - < 50:🔴 不可信(西西弗斯困境),需要全面审查
@@ -28,7 +28,42 @@ allowed-tools:
28
28
 
29
29
  ## 产物落点
30
30
 
31
- - 推荐写入:`<change-root>/<change-id>/proposal.md` 的 Impact 部分(或独立分析文档后再回填)
31
+ - **必须**写入:`<change-root>/<change-id>/proposal.md` 的 Impact 部分
32
+ - 备选:独立 `impact-analysis.md` 文件(后续回填到 proposal.md)
33
+
34
+ ## 输出行为(关键约束)
35
+
36
+ > **黄金法则**:**直接写入文档,禁止输出到对话窗口**
37
+
38
+ ### 必须遵守
39
+
40
+ 1. **直接写入**:使用 `Edit` 或 `Write` 工具将分析结果直接写入目标文档
41
+ 2. **禁止回显**:不要在对话中显示完整的分析内容
42
+ 3. **简短通知**:完成后只需告知用户"影响分析已写入 `<文件路径>`"
43
+
44
+ ### 正确行为 vs 错误行为
45
+
46
+ | 场景 | ❌ 错误行为 | ✅ 正确行为 |
47
+ |------|------------|------------|
48
+ | 分析完成 | 在对话中输出完整 Impact 表格 | 使用 Edit 工具写入 proposal.md |
49
+ | 通知用户 | 复述分析内容 | "影响分析已写入 `changes/xxx/proposal.md`" |
50
+ | 大量结果 | 分页输出到对话 | 全部写入文件,告知文件位置 |
51
+
52
+ ### 示例对话
53
+
54
+ ```
55
+ 用户:分析一下修改 UserService 的影响
56
+
57
+ AI:[使用 Grep/CKB 分析引用]
58
+ [使用 Edit 工具写入 proposal.md]
59
+
60
+ 影响分析已写入 `changes/refactor-user/proposal.md` 的 Impact 部分。
61
+ - 直接影响:8 个文件
62
+ - 间接影响:12 个文件
63
+ - 风险等级:中等
64
+
65
+ 如需查看详情,请打开该文件。
66
+ ```
32
67
 
33
68
  ## 执行方式
34
69