kld-sdd 2.6.16 → 2.6.17

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 (58) hide show
  1. package/lib/init.js +65 -6
  2. package/lib/skills-bundle.js +20 -1
  3. package/lib/tool-profiles.js +8 -0
  4. package/package.json +5 -1
  5. package/skywalk-sdd/apply-worktree-finish.cjs +2 -23
  6. package/skywalk-sdd/context-client.cjs +38 -87
  7. package/skywalk-sdd/index.cjs +719 -108
  8. package/skywalk-sdd/kb-sync-identity.cjs +780 -0
  9. package/skywalk-sdd/kb-upload.cjs +505 -0
  10. package/skywalk-sdd/lib/shared.cjs +663 -0
  11. package/skywalk-sdd/metrics-v3.cjs +138 -8
  12. package/skywalk-sdd/ontology/archive-package.cjs +19 -34
  13. package/skywalk-sdd/ontology/change-lock.cjs +3 -7
  14. package/skywalk-sdd/ontology/external-key.cjs +18 -4
  15. package/skywalk-sdd/ontology/id.cjs +26 -5
  16. package/skywalk-sdd/ontology/identity-index.cjs +3 -7
  17. package/skywalk-sdd/ontology/resolve-spec-root.cjs +20 -6
  18. package/skywalk-sdd/ontology/runtime.cjs +16 -12
  19. package/skywalk-sdd/ontology/traceability-validator.cjs +7 -4
  20. package/skywalk-sdd/reporting/change-report-markdown.cjs +137 -19
  21. package/skywalk-sdd/reporting/change-report-model.cjs +993 -14
  22. package/skywalk-sdd/reporting/change-report-renderer.cjs +106 -41
  23. package/skywalk-sdd/reporting/change-report-view-model.cjs +272 -43
  24. package/skywalk-sdd/reporting/core-metric-definitions.cjs +192 -0
  25. package/skywalk-sdd/spec-root.cjs +31 -0
  26. package/templates/hooks/codebuddy/hooks/hook-gate-core.cjs +327 -0
  27. package/templates/hooks/codebuddy/hooks/sdd-apply-test-gate.cjs +54 -9
  28. package/templates/hooks/codebuddy/hooks/sdd-mid-checkpoint.cjs +63 -6
  29. package/templates/hooks/codebuddy/hooks/sdd-tdd-rhythm-gate.cjs +113 -65
  30. package/templates/openspec/proposal.md +7 -3
  31. package/templates/openspec/spec.md +3 -3
  32. package/templates/skills/kld-sdd/opsx-apply/SKILL.md +8 -6
  33. package/templates/skills/kld-sdd/opsx-apply/checklist.md +2 -0
  34. package/templates/skills/kld-sdd/opsx-apply/reference.md +29 -7
  35. package/templates/skills/kld-sdd/opsx-archive/SKILL.md +6 -5
  36. package/templates/skills/kld-sdd/opsx-check/SKILL.md +48 -16
  37. package/templates/skills/kld-sdd/opsx-check/checklist.md +4 -2
  38. package/templates/skills/kld-sdd/opsx-design/SKILL.md +2 -2
  39. package/templates/skills/kld-sdd/opsx-explore/SKILL.md +2 -2
  40. package/templates/skills/kld-sdd/opsx-kb-config/SKILL.md +164 -0
  41. package/templates/skills/kld-sdd/opsx-kb-config/reference.md +116 -0
  42. package/templates/skills/kld-sdd/opsx-kb-ingest/SKILL.md +218 -53
  43. package/templates/skills/kld-sdd/opsx-kb-ingest/reference.md +51 -9
  44. package/templates/skills/kld-sdd/opsx-ontology-query/SKILL.md +12 -50
  45. package/templates/skills/kld-sdd/opsx-ontology-query/phase-3-postchange.md +2 -2
  46. package/templates/skills/kld-sdd/opsx-ontology-query/reference.md +1 -1
  47. package/templates/skills/kld-sdd/opsx-propose/SKILL.md +35 -23
  48. package/templates/skills/kld-sdd/opsx-propose/checklist.md +2 -0
  49. package/templates/skills/kld-sdd/opsx-propose/reference.md +22 -17
  50. package/templates/skills/kld-sdd/opsx-rules/SKILL.md +2 -2
  51. package/templates/skills/kld-sdd/opsx-spec/SKILL.md +19 -15
  52. package/templates/skills/kld-sdd/opsx-spec/checklist.md +2 -0
  53. package/templates/skills/kld-sdd/opsx-task/SKILL.md +2 -4
  54. package/templates/skills/kld-sdd/opsx-test/SKILL.md +2 -2
  55. package/templates/skills/kld-sdd/tdd-core/reference.md +1 -1
  56. package/templates/skills/kld-sdd/tdd-rules/rules/test-skeleton-telemetry.md +1 -1
  57. package/templates/skills/kld-sdd/opsx-kb-ingest/state.example.json +0 -7
  58. package/templates/skills/kld-sdd/opsx-ontology-query/state.example.json +0 -7
package/lib/init.js CHANGED
@@ -831,6 +831,30 @@ function deployTelemetryDataDir(targetCwd = process.cwd()) {
831
831
  console.log(` ⚠️ V3 指标模块源文件缺失: ${metricsV3Src}(report/metrics 可能无法加载 metrics-v3)`);
832
832
  }
833
833
 
834
+ // 部署 spec-root.cjs(替代脆弱的 $(cat .sdd-spec-root),CWD 无关)
835
+ const specRootSrc = path.join(pkgPath, 'skywalk-sdd', 'spec-root.cjs');
836
+ const specRootDst = path.join(dataDir, 'spec-root.cjs');
837
+ if (fs.existsSync(specRootSrc)) {
838
+ fs.copyFileSync(specRootSrc, specRootDst);
839
+ console.log(' ✓ 部署 skywalk-sdd/spec-root.cjs(CWD 无关的 spec 根路径解析器)');
840
+ } else {
841
+ console.log(' ⚠️ spec-root.cjs 源文件缺失,$(cat .sdd-spec-root) 模式可能受影响');
842
+ }
843
+
844
+ // 部署 lib/shared.cjs(共用工具模块:parseArgs/readJson/atomicWrite/HTTP/specRoot/KB状态)
845
+ const sharedLibDir = path.join(dataDir, 'lib');
846
+ if (!fs.existsSync(sharedLibDir)) {
847
+ fs.mkdirSync(sharedLibDir, { recursive: true });
848
+ }
849
+ const sharedSrc = path.join(pkgPath, 'skywalk-sdd', 'lib', 'shared.cjs');
850
+ const sharedDst = path.join(sharedLibDir, 'shared.cjs');
851
+ if (fs.existsSync(sharedSrc)) {
852
+ fs.copyFileSync(sharedSrc, sharedDst);
853
+ console.log(' ✓ 部署 skywalk-sdd/lib/shared.cjs(共用工具:parseArgs/JSON/BOM/HTTP/specRoot/KB)');
854
+ } else {
855
+ console.log(' ⚠️ lib/shared.cjs 源文件缺失,多个脚本将缺少共用工具依赖');
856
+ }
857
+
834
858
  const packageVersion = require(path.join(pkgPath, 'package.json')).version;
835
859
  const runtimeMetadataDst = path.join(dataDir, 'runtime-metadata.cjs');
836
860
  fs.writeFileSync(
@@ -885,11 +909,31 @@ function deployTelemetryDataDir(targetCwd = process.cwd()) {
885
909
  console.log(' ⚠️ openspec-shim.cjs 源文件缺失,openspec 命令需要手动 cd 到 spec 包执行');
886
910
  }
887
911
 
912
+ // 部署 kb-upload.cjs(知识库上传脚本,支持文件夹/zip 双模式随时入库)
913
+ const kbUploadSrc = path.join(pkgPath, 'skywalk-sdd', 'kb-upload.cjs');
914
+ const kbUploadDst = path.join(dataDir, 'kb-upload.cjs');
915
+ if (fs.existsSync(kbUploadSrc)) {
916
+ fs.copyFileSync(kbUploadSrc, kbUploadDst);
917
+ console.log(' ✓ 部署 skywalk-sdd/kb-upload.cjs(知识库上传:文件夹/zip 双模式)');
918
+ } else {
919
+ console.log(' ⚠️ kb-upload.cjs 源文件缺失,随时入库功能不可用');
920
+ }
921
+
922
+ // 部署 kb-sync-identity.cjs(KB 身份同步脚本,重新入库前同步 version-id)
923
+ const kbSyncIdentitySrc = path.join(pkgPath, 'skywalk-sdd', 'kb-sync-identity.cjs');
924
+ const kbSyncIdentityDst = path.join(dataDir, 'kb-sync-identity.cjs');
925
+ if (fs.existsSync(kbSyncIdentitySrc)) {
926
+ fs.copyFileSync(kbSyncIdentitySrc, kbSyncIdentityDst);
927
+ console.log(' ✓ 部署 skywalk-sdd/kb-sync-identity.cjs(KB 身份同步:重新入库前同步 version-id)');
928
+ } else {
929
+ console.log(' ⚠️ kb-sync-identity.cjs 源文件缺失,重新入库身份同步不可用');
930
+ }
931
+
888
932
  console.log(' ✓ 调用方式: node skywalk-sdd/log.cjs start|end|metrics|semantic-identity|semantic-reconcile|semantic-check');
889
933
  console.log(' ✓ openspec: node skywalk-sdd/openspec-shim.cjs list(自动 cd 到 *-sdd-specs)');
890
934
  console.log(' ✓ Spec Context: node skywalk-sdd/context-client.cjs --query="<自然语言需求>" --target-stage=spec');
891
935
  console.log(' ✓ Apply worktree: record-base 在 §1.5;收尾 apply-worktree-finish.cjs --change=<name>');
892
- console.log(' ℹ️ project-identity.json 将在首次配置 KB(opsx-ontology-query)时自动创建');
936
+ console.log(' ℹ️ project-identity.json 将在首次配置 KB(opsx-kb-config)时自动创建');
893
937
  console.log('✅ Telemetry 已就绪(数据存储在 skywalk-sdd/,无需配置 MCP)');
894
938
  return true;
895
939
  }
@@ -990,7 +1034,9 @@ function deployCodeRepoHooks(codeRepoRoot, specRepoRoot, options = {}) {
990
1034
  for (const script of hookScripts) {
991
1035
  const src = path.join(gitHooksTemplateDir, script);
992
1036
  const dst = path.join(specHooksDir, script);
993
- if (fs.existsSync(src) && !fs.existsSync(dst)) {
1037
+ // 删除 !fs.existsSync(dst) 条件:原逻辑只在目标不存在时复制,导致模板升级后 spec 仓不会拿到最新脚本。
1038
+ // 现在始终覆盖,与 shell hook 的 marker 覆盖策略保持一致。
1039
+ if (fs.existsSync(src)) {
994
1040
  fs.copyFileSync(src, dst);
995
1041
  }
996
1042
  }
@@ -1053,8 +1099,16 @@ function deployQualityGateTemplates(targetCwd = process.cwd(), options = {}) {
1053
1099
  const targetCiDir = path.join(dataDir, 'ci');
1054
1100
 
1055
1101
  if (fs.existsSync(gitHooksTemplateDir)) {
1056
- copyDir(gitHooksTemplateDir, targetGitHooksDir);
1057
- console.log(' ✓ 部署 skywalk-sdd/git-hooks/');
1102
+ // 只复制 .cjs 脚本到 skywalk-sdd/git-hooks/,shell hook 模板(commit-msg/pre-commit/pre-push)不落此目录
1103
+ if (!fs.existsSync(targetGitHooksDir)) {
1104
+ fs.mkdirSync(targetGitHooksDir, { recursive: true });
1105
+ }
1106
+ for (const file of fs.readdirSync(gitHooksTemplateDir)) {
1107
+ if (file.endsWith('.cjs')) {
1108
+ fs.copyFileSync(path.join(gitHooksTemplateDir, file), path.join(targetGitHooksDir, file));
1109
+ }
1110
+ }
1111
+ console.log(' ✓ 部署 skywalk-sdd/git-hooks/(.cjs 脚本)');
1058
1112
  } else {
1059
1113
  console.log(` ⚠️ Git hooks 模板缺失: ${gitHooksTemplateDir}`);
1060
1114
  }
@@ -1685,7 +1739,7 @@ async function main() {
1685
1739
  console.log(' 📌 .sdd-spec-root → 包裹包;无根目录 skywalk-sdd/');
1686
1740
  }
1687
1741
  if (selectedTools.includes('kunlunzhima')) {
1688
- console.log(' 📎 .kunlunzhima/commands/opsx/ # KunlunZhima OPSX command bridge(12 个)');
1742
+ console.log(' 📎 .kunlunzhima/commands/opsx/ # KunlunZhima OPSX command bridge(13 个)');
1689
1743
  console.log(' ℹ️ KunlunZhima 通过 commands/skills 入口使用 SDD,未启用自动 Hook');
1690
1744
  }
1691
1745
  if (selectedTools.includes('codebuddy')) {
@@ -1697,7 +1751,7 @@ async function main() {
1697
1751
  console.log();
1698
1752
 
1699
1753
  // 统一的 skill 格式说明
1700
- console.log('可用 skills(opsx-* 系列,14 个 SDD skills):');
1754
+ console.log('可用 skills(opsx-* 系列,15 个 SDD skills):');
1701
1755
  console.log(' opsx-propose - 创建业务意图文档(Why)');
1702
1756
  console.log(' opsx-spec - 创建技术契约文档(What)');
1703
1757
  console.log(' opsx-design - 创建技术实现方案(How)');
@@ -1710,6 +1764,9 @@ async function main() {
1710
1764
  console.log(' opsx-knowledge - 业务知识库检索(辅助)');
1711
1765
  console.log(' opsx-rules - 生成/更新 Agent 规则(辅助)');
1712
1766
  console.log(' opsx-consistency-check - Spec 一致性校验(代码 vs spec)');
1767
+ console.log(' opsx-kb-config - 知识库配置(API Key + Space/KB 选择)');
1768
+ console.log(' opsx-ontology-query - 本体知识库查询');
1769
+ console.log(' opsx-kb-ingest - 知识库入库(zip / 文件夹上传)');
1713
1770
 
1714
1771
  console.log();
1715
1772
  console.log('后续步骤:');
@@ -1722,6 +1779,8 @@ async function main() {
1722
1779
  console.log(' 2. 按顺序执行 propose → spec → design → task');
1723
1780
  console.log(' 3. 激活 opsx-check 验证文档质量');
1724
1781
  console.log(' 4. 激活 opsx-apply 申请实施,opsx-archive 归档完成');
1782
+ console.log(' 5. 随时入库:node skywalk-sdd/kb-upload.cjs --folder=<变更目录> --minimum-level=propose-spec');
1783
+ console.log(' (需先运行 /opsx-kb-config 配置知识库)');
1725
1784
  if (selectedTools.includes('codebuddy')) {
1726
1785
  console.log(' 5. CodeBuddy 用户:SDD Hook Pack 已随初始化自动部署,门禁自动生效,无需手动配置');
1727
1786
  }
@@ -7,6 +7,22 @@ const path = require('path');
7
7
 
8
8
  const SKILLS_BUNDLE_NAME = 'kld-sdd';
9
9
 
10
+ /**
11
+ * PowerShell 禁忌 — 追加到 SHELL_GUIDANCE 后,自动注入所有 SKILL.md。
12
+ * 根因:PowerShell 的 cat/curl/Get-Content 行为与 bash 不同,导致管道断裂、
13
+ * HTTP 请求失败、JSON 文件 BOM 污染引发静默降级(sdd-output 错误 #1/#3/#4/#5)。
14
+ */
15
+ const POWERSHELL_TABOOS = [
16
+ '',
17
+ '> - **PowerShell 禁忌**(Windows 用户必读):',
18
+ '> - 禁止 `| cat`(PowerShell 将 cat 解释为 Get-Content,不接受管道输入)→ 直接执行命令',
19
+ '> - 禁止 `node -e "..."` 内联脚本(PowerShell 对 `||`/`{}`/`()` 有特殊解析,导致 JS 语法被破坏)→ 用 `write_to_file` 创建临时 `.cjs` 脚本执行后删除',
20
+ '> - 禁止 cmd.exe 语法(如 `for /L %i in (1,1,N) do ...`)→ PowerShell 不支持,改用逐个 `execute_command` 调用或 PowerShell `foreach`',
21
+ '> - 警告 CLIXML:PowerShell 捕获 `node`/`npx` 子进程输出时可能包装为 `#< CLIXML <Objs...>` 格式 → 检测到 CLIXML 时以文件系统实际产物为准,忽略 stdout',
22
+ '> - 禁止 `curl`(PowerShell 将 curl 别名为 Invoke-WebRequest)→ HTTP 请求走 `context-client.cjs` / `kb-upload.cjs`,或用 `Invoke-RestMethod`',
23
+ '> - 禁止 `Get-Content` / `Out-File` 读写 JSON(PowerShell 默认写 BOM 导致 JSON.parse 失败)→ 用 write_to_file 工具或 Node.js `fs` 模块',
24
+ ].join('\n');
25
+
10
26
  const OPSX_SKILL_DIRS = [
11
27
  'opsx-propose',
12
28
  'opsx-spec',
@@ -20,6 +36,7 @@ const OPSX_SKILL_DIRS = [
20
36
  'opsx-knowledge',
21
37
  'opsx-rules',
22
38
  // Engineering KB 依赖技能(随 init 部署;propose/spec/archive 硬依赖)
39
+ 'opsx-kb-config',
23
40
  'opsx-ontology-query',
24
41
  'opsx-kb-ingest',
25
42
  // Spec 一致性校验技能
@@ -104,6 +121,7 @@ const KUNLUN_SLASH_COMMANDS = [
104
121
  'explore',
105
122
  'ontology-query',
106
123
  'kb-ingest',
124
+ 'kb-config',
107
125
  'consistency-check',
108
126
  ];
109
127
 
@@ -128,7 +146,8 @@ function replaceProfileTokens(content, profile) {
128
146
  return content
129
147
  .replace(/\$\{SKILL_RUNTIME_DIR\}/g, vars.SKILL_RUNTIME_DIR || profile.skillsDir || '')
130
148
  .replace(/\$\{HOOK_GATE_DESCRIPTION\}/g, vars.HOOK_GATE_DESCRIPTION || '')
131
- .replace(/\$\{SHELL_GUIDANCE\}/g, vars.SHELL_GUIDANCE || '')
149
+ .replace(/\$\{SHELL_GUIDANCE\}/g, (vars.SHELL_GUIDANCE || '') + POWERSHELL_TABOOS)
150
+ .replace(/\$\{CHECK_REVIEW_DELEGATION_GUIDANCE\}/g, vars.CHECK_REVIEW_DELEGATION_GUIDANCE || '')
132
151
  .replace(/\$\{AGENT_SKILL_DIR\}/g, profile.skillsDir || '');
133
152
  }
134
153
 
@@ -24,6 +24,7 @@ const TOOL_PROFILES = Object.freeze({
24
24
  SKILL_RUNTIME_DIR: '.cursor/skills',
25
25
  HOOK_GATE_DESCRIPTION: '本项目未为此 Agent 部署自动 Hook 门禁;请遵循 Skill 自检与 CI/Git 证据门禁。',
26
26
  SHELL_GUIDANCE: '在 Windows 上优先使用 PowerShell;如需 Bash 请使用 Git Bash。',
27
+ CHECK_REVIEW_DELEGATION_GUIDANCE: '使用 Cursor 当前可用的独立 Agent/子代理能力启动一个全新评审上下文;若能力不可用,按本 Skill 的受控回退规则由主 Agent 检查。',
27
28
  },
28
29
  },
29
30
  claude: {
@@ -44,6 +45,7 @@ const TOOL_PROFILES = Object.freeze({
44
45
  SKILL_RUNTIME_DIR: '.claude/skills',
45
46
  HOOK_GATE_DESCRIPTION: 'Claude Code 已部署 SDD Hook Pack(.claude/hooks + settings.json),自动门禁与遥测增强已启用。',
46
47
  SHELL_GUIDANCE: '在 Windows Bash / Git Bash / Claude Bash 中,禁止裸写 Windows 反斜杠绝对路径;如必须使用绝对路径,请写成正斜杠路径或加引号。',
48
+ CHECK_REVIEW_DELEGATION_GUIDANCE: '使用 Claude Code 的 Agent 能力启动一个全新、无作者上下文的评审子代理;不得让评审子代理再次委派。',
47
49
  },
48
50
  },
49
51
  codebuddy: {
@@ -64,6 +66,7 @@ const TOOL_PROFILES = Object.freeze({
64
66
  SKILL_RUNTIME_DIR: '.codebuddy/skills',
65
67
  HOOK_GATE_DESCRIPTION: 'CodeBuddy CLI 已部署 SDD Hook Pack(.codebuddy/hooks + settings.json),初始化后自动生效。',
66
68
  SHELL_GUIDANCE: 'CodeBuddy Hooks 在 Windows 上通过 Git Bash 执行;命令使用 node "$CODEBUDDY_PROJECT_DIR/..." 引用项目路径。',
69
+ CHECK_REVIEW_DELEGATION_GUIDANCE: '使用 CodeBuddy 当前可用的独立 Agent 能力启动一个评审子代理;若启动失败,记录标准回退原因后由主 Agent 检查。',
67
70
  },
68
71
  },
69
72
  qoder: {
@@ -84,6 +87,7 @@ const TOOL_PROFILES = Object.freeze({
84
87
  SKILL_RUNTIME_DIR: '.qoder/skills',
85
88
  HOOK_GATE_DESCRIPTION: '本项目未为此 Agent 部署自动 Hook 门禁;请遵循 Skill 自检与 CI/Git 证据门禁。',
86
89
  SHELL_GUIDANCE: '在 Windows 上优先使用 PowerShell;如需 Bash 请使用 Git Bash。',
90
+ CHECK_REVIEW_DELEGATION_GUIDANCE: '使用 Qoder 当前可用的独立 Agent/子代理能力启动一个全新评审上下文;能力不可用时才允许主 Agent 回退。',
87
91
  },
88
92
  },
89
93
  opencode: {
@@ -107,6 +111,7 @@ const TOOL_PROFILES = Object.freeze({
107
111
  SKILL_RUNTIME_DIR: '.opencode/skills',
108
112
  HOOK_GATE_DESCRIPTION: '本项目未为此 Agent 部署自动 Hook 门禁;请遵循 Skill 自检与 CI/Git 证据门禁。',
109
113
  SHELL_GUIDANCE: '在 Windows 上优先使用 PowerShell;OpenCode 可通过环境变量 OPENCODE_GIT_BASH_PATH 指定 Git Bash;如需 Bash 请使用 Git Bash。',
114
+ CHECK_REVIEW_DELEGATION_GUIDANCE: '使用 OpenCode 当前可用的独立 Agent 能力启动一个无作者上下文的评审子代理;不得递归委派。',
110
115
  },
111
116
  },
112
117
  kunlunzhima: {
@@ -127,6 +132,7 @@ const TOOL_PROFILES = Object.freeze({
127
132
  SKILL_RUNTIME_DIR: '.kunlunzhima/skills',
128
133
  HOOK_GATE_DESCRIPTION: 'KunlunZhima IDE 不支持自动 Hook 门禁;请通过 Skill 自检、commands 入口与 CI/Git 证据门禁保障质量。',
129
134
  SHELL_GUIDANCE: 'KunlunZhima IDE 通过 commands/skills 入口使用 SDD;相对引用以 Skill 目录解析。',
135
+ CHECK_REVIEW_DELEGATION_GUIDANCE: '使用 KunlunZhima 当前可用的独立智能体能力启动一个全新评审上下文;若不支持,记录标准回退原因后由主智能体检查。',
130
136
  },
131
137
  },
132
138
  workbuddy: {
@@ -147,6 +153,7 @@ const TOOL_PROFILES = Object.freeze({
147
153
  SKILL_RUNTIME_DIR: '.workbuddy/skills',
148
154
  HOOK_GATE_DESCRIPTION: '本项目未为此 Agent 部署自动 Hook 门禁;请遵循 Skill 自检与 CI/Git 证据门禁。',
149
155
  SHELL_GUIDANCE: '在 Windows 上优先使用 PowerShell;如需 Bash 请使用 Git Bash。',
156
+ CHECK_REVIEW_DELEGATION_GUIDANCE: '使用 WorkBuddy 当前可用的独立 Agent/子代理能力启动一个全新评审上下文;能力不可用时才允许主 Agent 回退。',
150
157
  },
151
158
  },
152
159
  codex: {
@@ -167,6 +174,7 @@ const TOOL_PROFILES = Object.freeze({
167
174
  SKILL_RUNTIME_DIR: '.agents/skills',
168
175
  HOOK_GATE_DESCRIPTION: '本项目未为此 Agent 部署自动 Hook 门禁;请遵循 Skill 自检与 CI/Git 证据门禁。',
169
176
  SHELL_GUIDANCE: '在 Windows 上优先使用 PowerShell;如需 Bash 请使用 Git Bash。',
177
+ CHECK_REVIEW_DELEGATION_GUIDANCE: '使用 Codex 的多代理能力启动恰好一个独立评审子代理,并传入所需文件而不是作者对话;不得让评审子代理再次委派。',
170
178
  },
171
179
  },
172
180
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "kld-sdd",
3
- "version": "2.6.16",
3
+ "version": "2.6.17",
4
4
  "description": "KLD SDD OpenSpec 项目初始化工具 - 一键部署 SDD skills",
5
5
  "main": "index.js",
6
6
  "bin": {
@@ -39,6 +39,10 @@
39
39
  "skywalk-sdd/openspec-shim.cjs",
40
40
  "skywalk-sdd/ontology/",
41
41
  "skywalk-sdd/apply-worktree-finish.cjs",
42
+ "skywalk-sdd/spec-root.cjs",
43
+ "skywalk-sdd/lib/",
44
+ "skywalk-sdd/kb-upload.cjs",
45
+ "skywalk-sdd/kb-sync-identity.cjs",
42
46
  "README.md"
43
47
  ]
44
48
  }
@@ -11,32 +11,11 @@
11
11
  const fs = require('fs');
12
12
  const path = require('path');
13
13
  const { execFileSync } = require('child_process');
14
+ const { parseArgs } = require('./lib/shared.cjs');
14
15
 
15
16
  const TEMP_DIR_NAMES = ['node_modules', 'dist', '.turbo', '.next', 'coverage', 'build'];
16
17
 
17
- function parseArgs(argv) {
18
- const args = {};
19
- for (let i = 2; i < argv.length; i++) {
20
- const token = argv[i];
21
- if (token === '--') continue;
22
- if (token.startsWith('--')) {
23
- const eq = token.indexOf('=');
24
- if (eq !== -1) {
25
- args[token.slice(2, eq)] = token.slice(eq + 1);
26
- } else {
27
- const key = token.slice(2);
28
- const next = argv[i + 1];
29
- if (next && !next.startsWith('--')) {
30
- args[key] = next;
31
- i++;
32
- } else {
33
- args[key] = true;
34
- }
35
- }
36
- }
37
- }
38
- return args;
39
- }
18
+ // parseArgs 已迁移至 lib/shared.cjs
40
19
 
41
20
  function git(cwd, gitArgs, opts = {}) {
42
21
  return execFileSync('git', gitArgs, {
@@ -6,17 +6,18 @@ const http = require('http');
6
6
  const https = require('https');
7
7
  const { URL } = require('url');
8
8
  const path = require('path');
9
+ const {
10
+ parseArgs,
11
+ requestJson: sharedRequestJson,
12
+ loadKbState,
13
+ resolveKbParams,
14
+ stripBom,
15
+ } = require('./lib/shared.cjs');
9
16
 
10
- function parseArgs(argv) {
11
- const args = {};
12
- for (const item of argv.slice(2)) {
13
- if (!item.startsWith('--')) continue;
14
- const equal = item.indexOf('=');
15
- if (equal < 0) args[item.slice(2)] = true;
16
- else args[item.slice(2, equal)] = item.slice(equal + 1);
17
- }
18
- return args;
19
- }
17
+ // Re-export requestJson for external callers (API compatibility)
18
+ const requestJson = sharedRequestJson;
19
+
20
+ // parseArgs 已迁移至 lib/shared.cjs(统一 5 份实现)
20
21
 
21
22
  function queryFromArgs(args) {
22
23
  if (typeof args.query === 'string' && args.query.trim()) {
@@ -38,60 +39,8 @@ function buildEndpoint(apiBase, spaceId, kbId, mode) {
38
39
  return `${base}/v1/spaces/${space}/knowledge-bases/${kb}/context/match-requirement`;
39
40
  }
40
41
 
41
- function requestJson(url, payload, token, timeoutMs) {
42
- return new Promise((resolve, reject) => {
43
- const target = new URL(url);
44
- const transport = target.protocol === 'https:' ? https : http;
45
- const body = JSON.stringify(payload);
46
- const headers = {
47
- Accept: 'application/json',
48
- 'Content-Type': 'application/json',
49
- 'Content-Length': Buffer.byteLength(body),
50
- };
51
- if (token) headers.Authorization = `Bearer ${token}`;
52
-
53
- const request = transport.request(
54
- {
55
- hostname: target.hostname,
56
- port: target.port || (target.protocol === 'https:' ? 443 : 80),
57
- path: target.pathname + target.search,
58
- method: 'POST',
59
- headers,
60
- timeout: timeoutMs,
61
- },
62
- (response) => {
63
- let responseBody = '';
64
- response.on('data', (chunk) => {
65
- responseBody += chunk;
66
- });
67
- response.on('end', () => {
68
- let parsed;
69
- try {
70
- parsed = responseBody ? JSON.parse(responseBody) : null;
71
- } catch {
72
- reject(new Error(`knowledge base returned invalid JSON (HTTP ${response.statusCode})`));
73
- return;
74
- }
75
- if (response.statusCode >= 400) {
76
- reject(new Error(parsed?.message || `knowledge base HTTP ${response.statusCode}`));
77
- return;
78
- }
79
- // code 字段仅当存在且不为 0 时才视为业务错误(health/ingest 等管理 API 返回 code,
80
- // 但 resolve/search 等 context API 直接返回 data,不包含 code 字段)
81
- if (parsed?.code != null && parsed.code !== 0) {
82
- reject(new Error(parsed?.message || `knowledge base error code ${parsed.code}`));
83
- return;
84
- }
85
- resolve(parsed.data);
86
- });
87
- },
88
- );
89
- request.on('timeout', () => request.destroy(new Error(`knowledge base timeout after ${timeoutMs}ms`)));
90
- request.on('error', reject);
91
- request.write(body);
92
- request.end();
93
- });
94
- }
42
+ // requestJson 已迁移至 lib/shared.cjs(统一 HTTP 请求逻辑)
43
+ // 保留 const requestJson = sharedRequestJson 以维持 API 兼容
95
44
 
96
45
  function compactContext(data, includeCandidates, mode = 'match') {
97
46
  if (includeCandidates) return data;
@@ -157,33 +106,35 @@ function buildPayload(args, mode) {
157
106
 
158
107
  async function retrieveContext(args = parseArgs(process.argv), env = process.env) {
159
108
  // 优先从共享 state 文件读取配置(与 opsx-ontology-query / opsx-kb-ingest 共用)
160
- // 搜索多个可能的 IDE skills 目录(.codebuddy / .claude / .cursor 等)
161
109
  let stateConfig = {};
162
110
  const stateFile = args['state-file'] || env.SDD_KB_STATE_FILE;
163
- const candidatePaths = [];
164
- if (stateFile) candidatePaths.push(stateFile);
165
- // 从 skywalk-sdd 目录向上查找 skills/.shared/kb-state.json
166
- const ideDirs = ['.codebuddy', '.claude', '.cursor', '.vscode'];
167
- for (const ide of ideDirs) {
168
- candidatePaths.push(path.join(__dirname, '..', ide, 'skills', '.shared', 'kb-state.json'));
169
- }
170
- // 也检查 skywalk-sdd 同级的 .shared
171
- candidatePaths.push(path.join(__dirname, '.shared', 'kb-state.json'));
172
- for (const p of candidatePaths) {
111
+
112
+ if (stateFile) {
113
+ // 显式指定路径,直接读取
173
114
  try {
174
- if (fs.existsSync(p)) {
175
- stateConfig = JSON.parse(fs.readFileSync(p, 'utf8'));
176
- break;
115
+ if (fs.existsSync(stateFile)) {
116
+ let raw = fs.readFileSync(stateFile, 'utf8');
117
+ raw = stripBom(raw);
118
+ stateConfig = JSON.parse(raw);
177
119
  }
178
- } catch (_) { /* try next */ }
120
+ } catch (e) {
121
+ console.error(`⚠️ kb-state.json 解析失败 (${stateFile}): ${e.message}`);
122
+ }
123
+ } else {
124
+ // 从 __dirname 逐层向上搜索(复用 shared.cjs 的 loadKbState)
125
+ const { config, warnings } = loadKbState(__dirname, env);
126
+ if (warnings.length > 0) {
127
+ for (const w of warnings) console.error(`⚠️ ${w}`);
128
+ }
129
+ stateConfig = config || {};
179
130
  }
180
131
 
181
- const apiBase = args['api-base'] || stateConfig.api || env.ENGINEERING_KB_API || 'http://localhost:8090/api';
182
- const token = args.token || stateConfig.apiKey || env.ENGINEERING_KB_TOKEN || '';
183
- const spaceId = args['space-id'] || env.ENGINEERING_KB_SPACE_ID ||
184
- (stateConfig.targets && stateConfig.targets.length > 0 ? stateConfig.targets[0].spaceId : '');
185
- const kbId = args['kb-id'] || env.ENGINEERING_KB_KB_ID ||
186
- (stateConfig.targets && stateConfig.targets.length > 0 ? stateConfig.targets[0].kbId : '');
132
+ const { apiBase: kbApiBase, token: kbToken, spaceId: kbSpaceId, kbId: kbKbId, targets: kbTargets } = resolveKbParams(stateConfig, env);
133
+
134
+ const apiBase = args['api-base'] || kbApiBase;
135
+ const token = args.token || kbToken;
136
+ const spaceId = args['space-id'] || kbSpaceId;
137
+ const kbId = args['kb-id'] || kbKbId;
187
138
 
188
139
  // --check-only: 只检查 KB 配置是否就绪,不发起 API 请求
189
140
  // 用于 SDD 各阶段(propose/spec/design/task/check)统一检测 KB 可用性,消除相对路径歧义
@@ -197,13 +148,13 @@ async function retrieveContext(args = parseArgs(process.argv), env = process.env
197
148
  available: false,
198
149
  reason: 'engineering_kb_not_configured',
199
150
  missing,
200
- hint: 'Run opsx-ontology-query Session startup to configure .shared/kb-state.json',
151
+ hint: 'Run /opsx-kb-config to configure kb-state.json (in spec root)',
201
152
  };
202
153
  }
203
154
  return {
204
155
  available: true,
205
156
  api: apiBase,
206
- targets: stateConfig.targets || [],
157
+ targets: kbTargets,
207
158
  };
208
159
  }
209
160