kld-sdd 2.6.21 → 2.7.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 (71) hide show
  1. package/README.md +12 -5
  2. package/USABILITY.md +84 -0
  3. package/bin/kld-sdd-init.js +3 -9
  4. package/kld-sdd-guide.html +16 -4
  5. package/lib/hook-gate-core.js +327 -0
  6. package/lib/init.js +241 -94
  7. package/lib/scale-thresholds.json +19 -0
  8. package/lib/skills-bundle.js +2 -1
  9. package/package.json +5 -3
  10. package/skywalk-sdd/context-client.cjs +50 -30
  11. package/skywalk-sdd/index.cjs +49 -16
  12. package/skywalk-sdd/kb-sync-identity.cjs +99 -451
  13. package/skywalk-sdd/kb-upload.cjs +67 -44
  14. package/skywalk-sdd/lib/git-identity.cjs +270 -0
  15. package/skywalk-sdd/lib/usage-contract.cjs +13 -81
  16. package/skywalk-sdd/lib/usage-reporter.cjs +235 -104
  17. package/skywalk-sdd/lib/user-config.cjs +21 -46
  18. package/skywalk-sdd/metrics-v3.cjs +2 -2
  19. package/skywalk-sdd/ontology/active-changes.cjs +2 -1
  20. package/skywalk-sdd/ontology/archive-package.cjs +1 -1
  21. package/skywalk-sdd/ontology/artifact-parser.cjs +8 -6
  22. package/skywalk-sdd/ontology/id.cjs +5 -4
  23. package/skywalk-sdd/ontology/identity-index.cjs +4 -3
  24. package/skywalk-sdd/ontology/list-changes.cjs +1 -1
  25. package/skywalk-sdd/ontology/runtime.cjs +7 -3
  26. package/skywalk-sdd/ontology/schema.cjs +2 -0
  27. package/skywalk-sdd/ontology/traceability-validator.cjs +74 -4
  28. package/skywalk-sdd/ontology/workspace-layout.cjs +25 -5
  29. package/skywalk-sdd/reporting/change-report-model.cjs +4 -3
  30. package/templates/git-hooks/commit-msg +71 -27
  31. package/templates/git-hooks/consistency-check-core.cjs +1087 -0
  32. package/templates/git-hooks/hooks.config +38 -0
  33. package/templates/git-hooks/pre-commit +62 -27
  34. package/templates/git-hooks/pre-commit-consistency-check.cjs +29 -332
  35. package/templates/git-hooks/pre-commit-sdd-check.cjs +98 -0
  36. package/templates/git-hooks/pre-push +70 -27
  37. package/templates/git-hooks/pre-push-consistency-check.cjs +61 -299
  38. package/templates/hooks/codebuddy/hooks/sdd-tdd-rhythm-gate.cjs +1 -1
  39. package/templates/openspec/tasks.md +3 -3
  40. package/templates/skills/kld-sdd/opsx-apply/SKILL.md +43 -6
  41. package/templates/skills/kld-sdd/opsx-apply/checklist.md +1 -1
  42. package/templates/skills/kld-sdd/opsx-apply/reference.md +1 -1
  43. package/templates/skills/kld-sdd/opsx-archive/SKILL.md +9 -13
  44. package/templates/skills/kld-sdd/opsx-check/SKILL.md +54 -328
  45. package/templates/skills/kld-sdd/opsx-check/checklist.md +7 -4
  46. package/templates/skills/kld-sdd/opsx-check/reference.md +58 -0
  47. package/templates/skills/kld-sdd/opsx-check/result-template.json +52 -0
  48. package/templates/skills/kld-sdd/opsx-check/reviewer.md +100 -0
  49. package/templates/skills/kld-sdd/opsx-consistency-check/SKILL.md +82 -451
  50. package/templates/skills/kld-sdd/opsx-consistency-check/{reference.md → references/reference.md} +0 -1
  51. package/templates/skills/kld-sdd/opsx-consistency-check/scripts/scripts.cjs +517 -0
  52. package/templates/skills/kld-sdd/opsx-design/SKILL.md +10 -30
  53. package/templates/skills/kld-sdd/opsx-design/checklist.md +3 -4
  54. package/templates/skills/kld-sdd/opsx-design/reference.md +1 -1
  55. package/templates/skills/kld-sdd/opsx-kb-ingest/SKILL.md +12 -74
  56. package/templates/skills/kld-sdd/opsx-ontology-query/SKILL.md +7 -5
  57. package/templates/skills/kld-sdd/opsx-ontology-query/phase-1-prechange.md +2 -2
  58. package/templates/skills/kld-sdd/opsx-ontology-query/reference.md +1 -1
  59. package/templates/skills/kld-sdd/opsx-propose/SKILL.md +28 -73
  60. package/templates/skills/kld-sdd/opsx-propose/checklist.md +8 -8
  61. package/templates/skills/kld-sdd/opsx-propose/interaction-policy.md +28 -0
  62. package/templates/skills/kld-sdd/opsx-propose/reference.md +12 -46
  63. package/templates/skills/kld-sdd/opsx-spec/SKILL.md +14 -61
  64. package/templates/skills/kld-sdd/opsx-spec/checklist.md +3 -3
  65. package/templates/skills/kld-sdd/opsx-task/SKILL.md +38 -32
  66. package/templates/skills/kld-sdd/opsx-task/checklist.md +5 -6
  67. package/templates/skills/kld-sdd/opsx-test/SKILL.md +2 -0
  68. package/templates/skills/kld-sdd/tdd-rules/rules/tdd-strategy-selection.md +1 -1
  69. package/lib/device-auth-cli.js +0 -130
  70. package/lib/device-auth.js +0 -345
  71. package/lib/init-account-binding.js +0 -108
package/lib/init.js CHANGED
@@ -30,7 +30,6 @@ const {
30
30
  const { deployCodebuddyHookPack } = require('./deploy-codebuddy-hooks');
31
31
  const workspaceLayout = require('./workspace-layout');
32
32
  const sddConfig = require('../skywalk-sdd/ontology/sdd-config.cjs');
33
- const { runAccountBinding } = require('./init-account-binding');
34
33
 
35
34
  let rl = null;
36
35
 
@@ -113,6 +112,139 @@ function parseSelectedTools(args) {
113
112
  return selected;
114
113
  }
115
114
 
115
+ /**
116
+ * 从命令行参数读取自定义 spec 目录
117
+ * 支持:--spec-path erp-sdd-specs、--spec-path=./specs、--spec-path=/abs/path/specs
118
+ * 返回相对/绝对路径字符串,未指定时返回 null。
119
+ */
120
+ function parseSpecPathArg(args) {
121
+ const index = args.findIndex(
122
+ (arg) => arg === '--spec-path' || arg.startsWith('--spec-path='),
123
+ );
124
+ if (index === -1) return null;
125
+
126
+ const rawValue = args[index] === '--spec-path'
127
+ ? args[index + 1]
128
+ : args[index].slice('--spec-path='.length);
129
+
130
+ if (!rawValue || rawValue.startsWith('--')) {
131
+ throw new Error('缺少 --spec-path 参数值,请指定 spec 仓库目录(相对或绝对路径)');
132
+ }
133
+ return rawValue.trim();
134
+ }
135
+
136
+ /**
137
+ * 将 --spec-path 解析为 { name, abs }。
138
+ * - 绝对路径:直接使用
139
+ * - 相对路径:相对 workspaceRoot 解析
140
+ * 仅取末段目录名作为包裹包名,abs 落在 workspaceRoot 下(保持包裹包在工作区/仓内的定位)。
141
+ */
142
+ function resolveSpecPathOption(specPathArg, workspaceRoot) {
143
+ if (!specPathArg) return null;
144
+ const root = path.resolve(workspaceRoot);
145
+ const abs = path.resolve(root, specPathArg);
146
+ const rel = path.relative(root, abs);
147
+ if (!rel || rel.startsWith('..') || path.isAbsolute(rel)) {
148
+ throw new Error(
149
+ `--spec-path 必须落在当前目录内(相对包裹包定位):${specPathArg}\n` +
150
+ ` 当前目录: ${root}\n 解析结果: ${abs}`,
151
+ );
152
+ }
153
+ const relPosix = rel.replace(/\\/g, '/');
154
+ // name 用相对路径(支持嵌套),供 ensureSpecPackage 与 .sdd.yaml spec_path 使用
155
+ return { name: relPosix, abs, rel: relPosix, display: path.basename(abs) };
156
+ }
157
+
158
+ /**
159
+ * 交互提示前的 spec 目录兜底检测:
160
+ * detectWorkspaceLayout 的 specRepo 只覆盖「子目录是 Git 仓」的场景,
161
+ * 这里补充两种常见形态,避免已有 spec 产物被漏检:
162
+ * 1. .sdd-spec-root 指针文件(历史初始化产物)
163
+ * 2. 非 Git 子目录的 spec 产物(findNestedSpecPackage 不要求子目录是 Git 仓)
164
+ */
165
+ function detectSpecCandidateForPrompt(workspaceRoot) {
166
+ try {
167
+ const hintPath = path.join(workspaceRoot, '.sdd-spec-root');
168
+ if (fs.existsSync(hintPath)) {
169
+ const rel = String(fs.readFileSync(hintPath, 'utf8') || '')
170
+ .trim()
171
+ .split(/\r?\n/)[0]
172
+ .trim();
173
+ if (rel) {
174
+ const abs = path.resolve(workspaceRoot, rel);
175
+ if (fs.existsSync(abs) && workspaceLayout.looksLikeSpecRepo(abs)) {
176
+ return { name: path.basename(abs), abs };
177
+ }
178
+ }
179
+ }
180
+ } catch { /* 指针读取异常时继续兜底 */ }
181
+
182
+ const nested = workspaceLayout.findNestedSpecPackage(workspaceRoot);
183
+ return nested ? { name: nested.name, abs: nested.abs } : null;
184
+ }
185
+
186
+ /**
187
+ * 交互式确定 spec 包裹包目录(--spec-path 未显式指定时):
188
+ * 1. 自动检测到已有 spec 产物(modules.yaml / openspec/changes / *-sdd-specs)→ 提醒用户确认是否使用
189
+ * 2. 未检测到 → 要求用户输入相对路径指定(回车则用默认推导名 <当前目录名>-sdd-specs)
190
+ *
191
+ * 非交互环境(CI / 管道 / --tool 自动化)不询问,保持原自动推导行为。
192
+ *
193
+ * @param {object} [options] - 测试注入:{ askFn, interactive }
194
+ * @returns {{ name: string, abs: string, rel: string, display: string } | null}
195
+ * null 表示未选择自定义目录,走默认推导逻辑
196
+ */
197
+ async function promptSpecPath(workspaceRoot, detectedSpec, options = {}) {
198
+ const askUser = options.askFn || ask;
199
+ let interactive = options.interactive;
200
+ if (typeof interactive !== 'boolean') {
201
+ // 非交互环境:无 TTY 或显式给了 --tool(自动化场景),不询问
202
+ const isTTY = process.stdin && process.stdin.isTTY;
203
+ const hasToolArg = process.argv.slice(2).some((arg) => arg === '--tool' || arg.startsWith('--tool='));
204
+ interactive = Boolean(isTTY) && !hasToolArg;
205
+ }
206
+ if (!interactive) return null;
207
+
208
+ if (detectedSpec) {
209
+ console.log(`\n🔍 检测到已有 spec 目录(命名约定或完整结构标记): ${detectedSpec.name}`);
210
+ console.log(` 位置: ${detectedSpec.abs}`);
211
+ while (true) {
212
+ const answer = await askUser(`是否使用该 spec 仓库?(Y=使用 / n=重新指定路径,默认 Y): `);
213
+ const normalized = (answer || '').trim().toLowerCase();
214
+ if (!normalized || normalized === 'y' || normalized === 'yes') {
215
+ const relPosix = path.relative(workspaceRoot, detectedSpec.abs).replace(/\\/g, '/') || detectedSpec.name;
216
+ return { name: relPosix, abs: detectedSpec.abs, rel: relPosix, display: detectedSpec.name };
217
+ }
218
+ if (normalized === 'n' || normalized === 'no') break;
219
+ console.log(`❌ 无效输入 "${answer}",请输入 Y 或 n`);
220
+ }
221
+ } else {
222
+ console.log('\n🔍 未检测到已有 spec 目录(目录名 *-sdd-specs,或 modules.yaml + sdd.config.yaml + openspec/changes + openspec/specs/overview.md 齐全)');
223
+ }
224
+
225
+ // 未确认使用检测目录(或未检测到):要求输入相对路径
226
+ const defaultName = workspaceLayout.deriveSpecPackageName(workspaceRoot);
227
+ while (true) {
228
+ const input = await askUser(`请输入 spec 目录的相对路径(直接回车使用默认 ${defaultName}): `);
229
+ const trimmed = (input || '').trim();
230
+ if (!trimmed) {
231
+ // 回车 = 使用默认推导名。
232
+ // 若用户刚用 n 拒绝了检测到的目录,必须显式返回默认目录:
233
+ // 返回 null 会让主流程走自动探测,重新命中刚被拒绝的目录。
234
+ if (detectedSpec) {
235
+ return resolveSpecPathOption(defaultName, workspaceRoot);
236
+ }
237
+ return null; // 未检测到场景:走主流程默认创建
238
+ }
239
+ try {
240
+ return resolveSpecPathOption(trimmed, workspaceRoot);
241
+ } catch (error) {
242
+ console.log(`❌ ${error.message}`);
243
+ // 循环重新输入
244
+ }
245
+ }
246
+ }
247
+
116
248
  /**
117
249
  * 让用户选择编辑器
118
250
  * 无效输入时重新询问,不允许默认
@@ -896,13 +1028,16 @@ function deployTelemetryDataDir(targetCwd = process.cwd()) {
896
1028
  console.log(' ⚠️ lib/shared.cjs 源文件缺失,多个脚本将缺少共用工具依赖');
897
1029
  }
898
1030
 
899
- // kld-fixback: 部署使用统计上报模块(usage-contract/usage-reporter/user-config)
1031
+ // kld-fixback: 部署使用统计上报模块(usage-contract/usage-reporter/user-config/git-identity
900
1032
  // 这些模块被部署后的 skywalk-sdd/log.cjs(即 index.cjs 副本)在 cmdStart 中 require,
901
- // 缺失时会上报静默失效。三件套必须同时部署,路径与 npm 包内布局一致。
1033
+ // 缺失时会上报静默失效。四件套必须同时部署,路径与 npm 包内布局一致。
1034
+ // ⚠️ usage-reporter.cjs 顶层 require('./git-identity.cjs'):缺它时模块加载即抛
1035
+ // MODULE_NOT_FOUND,被 cmdStart try/catch 吞掉后上报 100% 静默丢失。
902
1036
  const usageModules = [
903
- { src: 'usage-contract.cjs', desc: '使用统计契约(七值/Device/ApiResponse/HTTP)' },
904
- { src: 'usage-reporter.cjs', desc: '使用事件上报器(3s deadline + pending 队列)' },
905
- { src: 'user-config.cjs', desc: '用户级配置(~/.kld-sdd/config.json + cli_token)' },
1037
+ { src: 'usage-contract.cjs', desc: '使用统计契约(七值/ApiResponse/HTTP)' },
1038
+ { src: 'usage-reporter.cjs', desc: '使用事件上报器(git 身份缓存 + 3s deadline + pending 队列)' },
1039
+ { src: 'user-config.cjs', desc: '用户级配置(~/.kld-sdd/config.json,仅 server)' },
1040
+ { src: 'git-identity.cjs', desc: 'git 身份持久缓存与后台刷新(usage-reporter 的硬依赖)' },
906
1041
  ];
907
1042
  for (const mod of usageModules) {
908
1043
  const src = path.join(pkgPath, 'skywalk-sdd', 'lib', mod.src);
@@ -916,6 +1051,17 @@ function deployTelemetryDataDir(targetCwd = process.cwd()) {
916
1051
  }
917
1052
  }
918
1053
 
1054
+ // 部署 scale-thresholds.json(Simple/Full 模式判定阈值,opsx-propose §6.8 自动规模判定依赖)
1055
+ const thresholdsSrc = path.join(pkgPath, 'lib', 'scale-thresholds.json');
1056
+ const thresholdsDst = path.join(sharedLibDir, 'scale-thresholds.json');
1057
+ if (fs.existsSync(thresholdsSrc)) {
1058
+ fs.copyFileSync(thresholdsSrc, thresholdsDst);
1059
+ deployedModules += 1;
1060
+ vlog(' ✓ 部署 skywalk-sdd/lib/scale-thresholds.json(Simple/Full 判定阈值)');
1061
+ } else {
1062
+ console.log(' ⚠️ scale-thresholds.json 源文件缺失,propose 自动规模判定将使用内置默认值');
1063
+ }
1064
+
919
1065
  const packageVersion = require(path.join(pkgPath, 'package.json')).version;
920
1066
  const runtimeMetadataDst = path.join(dataDir, 'runtime-metadata.cjs');
921
1067
  fs.writeFileSync(
@@ -1041,7 +1187,8 @@ function installSddShellHook(cwd, hookName, pkgPath = getPackagePath()) {
1041
1187
  // 已是 SDD hook:允许覆盖升级
1042
1188
  fs.writeFileSync(hookPath, content, 'utf8');
1043
1189
  try {
1044
- fs.chmodSync(hookPath, 0o755);
1190
+ // 0o700:仅属主可执行(git 以属主身份执行 hook),避免 S2612 对组/其他用户放权的告警
1191
+ fs.chmodSync(hookPath, 0o700);
1045
1192
  } catch {
1046
1193
  // ignore
1047
1194
  }
@@ -1095,22 +1242,17 @@ function deployCodeRepoHooks(codeRepoRoot, specRepoRoot, options = {}) {
1095
1242
  if (!fs.existsSync(specHooksDir)) {
1096
1243
  fs.mkdirSync(specHooksDir, { recursive: true });
1097
1244
  }
1098
- const hookScripts = [
1099
- 'pre-commit-sdd-check.cjs',
1100
- 'pre-commit-consistency-check.cjs',
1101
- 'pre-push-sdd-check.cjs',
1102
- 'pre-push-consistency-check.cjs',
1103
- ];
1245
+ // 复制全部 .cjs 脚本 + hooks.config 配置文件
1246
+ const hookScripts = fs.existsSync(gitHooksTemplateDir)
1247
+ ? fs.readdirSync(gitHooksTemplateDir).filter(f => f.endsWith('.cjs') || f === 'hooks.config')
1248
+ : [];
1104
1249
  for (const script of hookScripts) {
1105
1250
  const src = path.join(gitHooksTemplateDir, script);
1106
1251
  const dst = path.join(specHooksDir, script);
1107
- // 删除 !fs.existsSync(dst) 条件:原逻辑只在目标不存在时复制,导致模板升级后 spec 仓不会拿到最新脚本。
1108
- // 现在始终覆盖,与 shell hook 的 marker 覆盖策略保持一致。
1109
- if (fs.existsSync(src)) {
1110
- fs.copyFileSync(src, dst);
1111
- }
1252
+ // 始终覆盖,与 shell hook marker 覆盖策略保持一致
1253
+ fs.copyFileSync(src, dst);
1112
1254
  }
1113
- console.log(` ✓ 确保 spec 仓 skywalk-sdd/git-hooks/ 脚本齐全`);
1255
+ console.log(` ✓ 确保 spec 仓 skywalk-sdd/git-hooks/ 脚本齐全(${hookScripts.length} 个文件)`);
1114
1256
 
1115
1257
  // ── 2. 安装三个 shell hooks 到代码仓 .git/hooks/ ──
1116
1258
  // 模板 hook 运行时自动定位 spec 仓的 .cjs 脚本
@@ -1169,16 +1311,17 @@ function deployQualityGateTemplates(targetCwd = process.cwd(), options = {}) {
1169
1311
  const targetCiDir = path.join(dataDir, 'ci');
1170
1312
 
1171
1313
  if (fs.existsSync(gitHooksTemplateDir)) {
1172
- // 只复制 .cjs 脚本到 skywalk-sdd/git-hooks/,shell hook 模板(commit-msg/pre-commit/pre-push)不落此目录
1314
+ // 复制 .cjs 脚本 + hooks.config 到 skywalk-sdd/git-hooks/
1315
+ // shell hook 模板(commit-msg/pre-commit/pre-push)不落此目录
1173
1316
  if (!fs.existsSync(targetGitHooksDir)) {
1174
1317
  fs.mkdirSync(targetGitHooksDir, { recursive: true });
1175
1318
  }
1176
1319
  for (const file of fs.readdirSync(gitHooksTemplateDir)) {
1177
- if (file.endsWith('.cjs')) {
1320
+ if (file.endsWith('.cjs') || file === 'hooks.config') {
1178
1321
  fs.copyFileSync(path.join(gitHooksTemplateDir, file), path.join(targetGitHooksDir, file));
1179
1322
  }
1180
1323
  }
1181
- console.log(' ✓ 部署 skywalk-sdd/git-hooks/(.cjs 脚本)');
1324
+ console.log(' ✓ 部署 skywalk-sdd/git-hooks/(.cjs 脚本 + hooks.config)');
1182
1325
  } else {
1183
1326
  console.log(` ⚠️ Git hooks 模板缺失: ${gitHooksTemplateDir}`);
1184
1327
  }
@@ -1546,20 +1689,6 @@ skywalk-sdd/state/
1546
1689
  }
1547
1690
  }
1548
1691
 
1549
- /**
1550
- * B1 汇总块绑定文案:已绑定显示用户,未绑定引导 auth login;不出现“使用统计”字样
1551
- */
1552
- function formatBindingSummaryLine(bindingResult) {
1553
- if (
1554
- bindingResult
1555
- && (bindingResult.status === 'bound' || bindingResult.status === 'kept')
1556
- && bindingResult.userName
1557
- ) {
1558
- return ` ✓ 平台账号已绑定:${bindingResult.userName}`;
1559
- }
1560
- return ' 6. 绑定平台账号:npx kld-sdd auth login';
1561
- }
1562
-
1563
1692
  /**
1564
1693
  * 显示帮助信息
1565
1694
  */
@@ -1579,6 +1708,11 @@ KLD SDD 项目初始化工具
1579
1708
  --skip-openspec 跳过 openspec init 步骤
1580
1709
  --skip-template 跳过复制内置模版
1581
1710
  --tool <name> 指定编辑器,可用值: cursor, claude, codebuddy, qoder, opencode, kunlunzhima, workbuddy, codex, all
1711
+ --spec-path <dir> 指定 spec 包裹包目录(必须是当前目录的子目录)。
1712
+ 不指定时(交互终端):自动检测已有 spec 目录(目录名 *-sdd-specs,
1713
+ 或 modules.yaml + sdd.config.yaml + openspec/changes + openspec/specs/overview.md 齐全),
1714
+ 检测到则询问是否使用;未检测到则要求输入相对路径(回车用默认 <当前目录名>-sdd-specs)。
1715
+ 例: 在 erp-klny-all 下 --spec-path erp-sdd-specs
1582
1716
 
1583
1717
  工作区模式(在个人工作目录执行):
1584
1718
  - skills / 编辑器配置只部署一次到当前工作目录
@@ -1591,6 +1725,7 @@ KLD SDD 项目初始化工具
1591
1725
  kld-sdd-init # 完整初始化流程
1592
1726
  kld-sdd-init --skip-openspec # 跳过 openspec,直接部署 skills
1593
1727
  kld-sdd-init --tool codex # 仅部署 Codex 配置
1728
+ kld-sdd-init --spec-path erp-sdd-specs # 指定 spec 目录(当前目录的子目录)
1594
1729
  kld-sdd sync-repos # 新代码仓加入后同步接入
1595
1730
  `);
1596
1731
  }
@@ -1658,6 +1793,17 @@ async function main() {
1658
1793
  const skipOpenspec = args.includes('--skip-openspec');
1659
1794
  const skipTemplate = args.includes('--skip-template');
1660
1795
 
1796
+ // 自定义 spec 目录(--spec-path):未指定时交互询问(自动检测确认 / 输入相对路径)
1797
+ let customSpec = null;
1798
+ try {
1799
+ const specPathArg = parseSpecPathArg(args);
1800
+ if (specPathArg) customSpec = resolveSpecPathOption(specPathArg, process.cwd());
1801
+ } catch (error) {
1802
+ console.error(`❌ ${error.message}`);
1803
+ closeRl();
1804
+ process.exit(1);
1805
+ }
1806
+
1661
1807
  console.log('╔════════════════════════════════════════╗');
1662
1808
  console.log('║ KLD SDD 项目初始化工具 ║');
1663
1809
  console.log('║ 内置 openSpec 标准模版 ║');
@@ -1680,10 +1826,47 @@ async function main() {
1680
1826
  ? { name: layout.specRepo.name, abs: layout.specRepo.abs, created: false, isGit: true }
1681
1827
  : null;
1682
1828
 
1829
+ // 统一在此确定 spec 包裹包目录,后续所有分支直接复用 specPackage,不再各自 ensureSpecPackage。
1830
+ // - 显式 --spec-path:以指定目录为准(复用或创建),优先于同级自动探测
1831
+ // - 未显式指定:交互环境先自动检测已有 spec 产物,检测到则询问确认,未检测到则要求输入相对路径
1832
+ // (非交互环境 / 用户回车跳过:保持原默认推导行为)
1833
+ // - 否则未探测到 spec 仓:创建默认包裹包(--skip-template + --skip-openspec 时不创建)
1834
+ // initGit:多仓工作区的包裹包是独立 Git 仓;单仓/非 Git 则共用外层 Git。
1835
+ const specInitGit = layout.isWorkspace;
1836
+ if (customSpec) {
1837
+ specPackage = workspaceLayout.ensureSpecPackage(workspaceRoot, {
1838
+ initGit: specInitGit,
1839
+ name: customSpec.name,
1840
+ });
1841
+ console.log(`📌 使用自定义 spec 目录: ${customSpec.rel}`);
1842
+ console.log();
1843
+ } else {
1844
+ // 交互提示前兜底检测:Git 仓探测(layout.specRepo)漏掉的非 Git spec 产物
1845
+ const promptCandidate = layout.specRepo
1846
+ ? { name: layout.specRepo.name, abs: layout.specRepo.abs }
1847
+ : detectSpecCandidateForPrompt(workspaceRoot);
1848
+ const interactiveSpec = await promptSpecPath(workspaceRoot, promptCandidate);
1849
+ if (interactiveSpec) {
1850
+ customSpec = interactiveSpec;
1851
+ specPackage = workspaceLayout.ensureSpecPackage(workspaceRoot, {
1852
+ initGit: specInitGit,
1853
+ name: customSpec.name,
1854
+ });
1855
+ console.log(`📌 使用 spec 目录: ${customSpec.rel}`);
1856
+ console.log();
1857
+ } else if (!specPackage && !(skipTemplate && skipOpenspec)) {
1858
+ specPackage = workspaceLayout.ensureSpecPackage(workspaceRoot, { initGit: specInitGit });
1859
+ }
1860
+ }
1861
+
1683
1862
  if (layout.isWorkspace) {
1863
+ // 显示层同步过滤:用户交互选择的 spec 目录可能原被分类为代码仓
1864
+ const displayCodeRepos = specPackage
1865
+ ? layout.codeRepos.filter((c) => path.resolve(c.abs) !== path.resolve(specPackage.abs))
1866
+ : layout.codeRepos;
1684
1867
  console.log('📂 检测到个人工作目录布局(子目录 Git 仓库)');
1685
1868
  console.log(` Spec: ${specPackage ? specPackage.name : '(未发现,将自动创建包裹包)'}`);
1686
- console.log(` 代码仓: ${layout.codeRepos.map((c) => c.name).join(', ') || '(无)'}`);
1869
+ console.log(` 代码仓: ${displayCodeRepos.map((c) => c.name).join(', ') || '(无)'}`);
1687
1870
  console.log(' → skills 只部署到本工作目录;SDD 文档统一进 *-sdd-specs 包裹包');
1688
1871
  console.log();
1689
1872
  }
@@ -1697,12 +1880,6 @@ async function main() {
1697
1880
  deployProfileArtifacts(selectedTools, workspaceRoot, getPackagePath());
1698
1881
 
1699
1882
  if (layout.isWorkspace) {
1700
- if (!specPackage) {
1701
- console.log('📦 未发现 spec 仓,正在创建统一 SDD 包裹包(独立 Git)...');
1702
- specPackage = workspaceLayout.ensureSpecPackage(workspaceRoot, { initGit: true });
1703
- console.log(` ✓ ${specPackage.created ? '已创建' : '已复用'}: ${specPackage.name}`);
1704
- }
1705
-
1706
1883
  await populateSpecPackage(specPackage.abs, {
1707
1884
  skipOpenspec,
1708
1885
  asIndependentGit: true,
@@ -1710,10 +1887,17 @@ async function main() {
1710
1887
  writeSddSpecRootHint(workspaceRoot, specPackage.abs);
1711
1888
  removeOuterSkywalk(workspaceRoot);
1712
1889
 
1713
- // 重新探测,刷新 layout.specRepo / codeRepos
1890
+ // 重新探测,刷新 layout.specRepo / codeRepos
1891
+ // 用户交互中可能指定了与自动探测不同的 spec 目录(旧目录仍在盘上),
1892
+ // 此时以用户选择为准,避免重探测把旧目录选为 specRepo、把新目录误分类为代码仓。
1714
1893
  layout = workspaceLayout.detectWorkspaceLayout(workspaceRoot);
1715
- if (!layout.specRepo) {
1716
- layout.specRepo = { name: specPackage.name, abs: specPackage.abs };
1894
+ if (specPackage) {
1895
+ if (!layout.specRepo || path.resolve(layout.specRepo.abs) !== path.resolve(specPackage.abs)) {
1896
+ layout.specRepo = { name: specPackage.name, abs: specPackage.abs };
1897
+ }
1898
+ layout.codeRepos = layout.codeRepos.filter(
1899
+ (c) => path.resolve(c.abs) !== path.resolve(specPackage.abs),
1900
+ );
1717
1901
  }
1718
1902
 
1719
1903
  console.log();
@@ -1728,10 +1912,6 @@ async function main() {
1728
1912
  const isGit = workspaceLayout.isGitRepo(workspaceRoot);
1729
1913
 
1730
1914
  if (isGit) {
1731
- console.log('📦 单仓模式:创建/复用仓内统一 SDD 包裹目录...');
1732
- specPackage = workspaceLayout.ensureSpecPackage(workspaceRoot, { initGit: false });
1733
- console.log(` ✓ ${specPackage.created ? '已创建' : '已复用'}: ${specPackage.name}`);
1734
-
1735
1915
  await populateSpecPackage(specPackage.abs, {
1736
1916
  skipOpenspec,
1737
1917
  asIndependentGit: false,
@@ -1753,8 +1933,7 @@ async function main() {
1753
1933
  }
1754
1934
  } else {
1755
1935
  // 非 Git 目录:仍创建包裹包,便于随后 git init
1756
- console.log('📦 当前目录不是 Git 仓:仍创建 SDD 包裹包(请稍后 git init)...');
1757
- specPackage = workspaceLayout.ensureSpecPackage(workspaceRoot, { initGit: false });
1936
+ console.log('📦 当前目录不是 Git 仓:包裹包已就绪(请稍后 git init)...');
1758
1937
  await populateSpecPackage(specPackage.abs, {
1759
1938
  skipOpenspec,
1760
1939
  asIndependentGit: false,
@@ -1766,26 +1945,13 @@ async function main() {
1766
1945
  }
1767
1946
  } else if (!skipOpenspec) {
1768
1947
  // 仅 openspec、跳过模版时:仍尊重包裹包,避免在工作区根平铺
1769
- if (layout.isWorkspace) {
1770
- if (!specPackage) {
1771
- specPackage = workspaceLayout.ensureSpecPackage(workspaceRoot, { initGit: true });
1772
- }
1773
- const prev = process.cwd();
1774
- try {
1775
- process.chdir(specPackage.abs);
1776
- await runOpenspecInit([]);
1777
- } finally {
1778
- process.chdir(prev);
1779
- }
1780
- } else {
1781
- specPackage = workspaceLayout.ensureSpecPackage(workspaceRoot, { initGit: false });
1782
- const prev = process.cwd();
1783
- try {
1784
- process.chdir(specPackage.abs);
1785
- await runOpenspecInit([]);
1786
- } finally {
1787
- process.chdir(prev);
1788
- }
1948
+ // specPackage 已在顶部统一 ensureSpecPackage 创建/复用
1949
+ const prev = process.cwd();
1950
+ try {
1951
+ process.chdir(specPackage.abs);
1952
+ await runOpenspecInit([]);
1953
+ } finally {
1954
+ process.chdir(prev);
1789
1955
  }
1790
1956
  }
1791
1957
 
@@ -1798,23 +1964,6 @@ async function main() {
1798
1964
  }
1799
1965
  removeOuterSkywalk(workspaceRoot);
1800
1966
 
1801
- // 9. B1 账号绑定向导(部署完成后、最终汇总前;--skip-auth/非 TTY 自动跳过;失败不阻塞 init)
1802
- const bindingController = new AbortController();
1803
- const onBindingSigint = () => bindingController.abort();
1804
- process.on('SIGINT', onBindingSigint);
1805
- let bindingResult = null;
1806
- try {
1807
- bindingResult = await runAccountBinding({
1808
- ask,
1809
- skipAuth: args.includes('--skip-auth'),
1810
- signal: bindingController.signal,
1811
- });
1812
- } catch (error) {
1813
- console.log(`⚠️ 账号绑定向导异常(${error && error.message ? error.message : error}),已跳过`);
1814
- } finally {
1815
- process.removeListener('SIGINT', onBindingSigint);
1816
- }
1817
-
1818
1967
  const selectedNames = selectedTools.map(t => TOOL_CONFIGS[t].name).join(', ');
1819
1968
 
1820
1969
  console.log();
@@ -1894,8 +2043,6 @@ async function main() {
1894
2043
  console.log(' 4. 激活 opsx-apply 申请实施,opsx-archive 归档完成');
1895
2044
  console.log(' 5. 随时入库:node skywalk-sdd/kb-upload.cjs --folder=<变更目录> --minimum-level=propose-spec');
1896
2045
  console.log(' (需先运行 /opsx-kb-config 配置知识库)');
1897
- // B1: 汇总块绑定文案(不出现“使用统计”字样)
1898
- console.log(formatBindingSummaryLine(bindingResult));
1899
2046
  if (selectedTools.includes('codebuddy')) {
1900
2047
  console.log(' - CodeBuddy 用户:SDD Hook Pack 已随初始化自动部署,门禁自动生效,无需手动配置');
1901
2048
  }
@@ -1919,8 +2066,7 @@ async function main() {
1919
2066
  }
1920
2067
 
1921
2068
  // kld-T06: Progress Runtime 已删除,用户级 workspace 注册函数一并移除。
1922
- // 用户级 Device Flow 绑定由 lib/device-auth.js + skywalk-sdd/lib/user-config.cjs 负责,
1923
- // 通过 `kld-sdd auth login` 显式触发。
2069
+ // 使用统计按本机 git 身份(user.name/user.email)上报,无需任何账号绑定。
1924
2070
 
1925
2071
  module.exports = {
1926
2072
  main,
@@ -1930,7 +2076,6 @@ module.exports = {
1930
2076
  selectEditor,
1931
2077
  isVerbose,
1932
2078
  vlog,
1933
- formatBindingSummaryLine,
1934
2079
  deployOpsxSkills,
1935
2080
  copyTemplatesToProject,
1936
2081
  deployProfileArtifacts,
@@ -1949,6 +2094,8 @@ module.exports = {
1949
2094
  syncWorkspaceRepos,
1950
2095
  copyDirRendered,
1951
2096
  parseSelectedTools,
2097
+ promptSpecPath,
2098
+ detectSpecCandidateForPrompt,
1952
2099
  installSddShellHook,
1953
2100
  deployCodeRepoHooks,
1954
2101
  TOOL_CONFIGS,
@@ -0,0 +1,19 @@
1
+ {
2
+ "$schema": "https://kld-sdd.dev/schemas/scale-thresholds/v1",
3
+ "description": "SDD Simple 模式自动规模判定阈值。临界时优先选 Full(保守策略)。",
4
+ "version": "1.0.0",
5
+ "simple": {
6
+ "max_files": 5,
7
+ "max_lines": 200,
8
+ "max_modules": 1,
9
+ "allow_new_table": false,
10
+ "allow_new_api": false
11
+ },
12
+ "fallback": {
13
+ "max_files": 5,
14
+ "max_lines": 200,
15
+ "max_modules": 1,
16
+ "allow_new_table": false,
17
+ "allow_new_api": false
18
+ }
19
+ }
@@ -132,7 +132,8 @@ function transformKunlunSlashCommands(content) {
132
132
 
133
133
  let result = content;
134
134
  for (const cmd of KUNLUN_SLASH_COMMANDS) {
135
- result = result.replace(new RegExp(`/opsx-${cmd}(?![a-z-])`, 'g'), `/opsx:${cmd}`);
135
+ // Command syntax changes for Kunlun; skill directory paths remain portable.
136
+ result = result.replace(new RegExp(`/opsx-${cmd}(?![a-z-/])`, 'g'), `/opsx:${cmd}`);
136
137
  }
137
138
  return result;
138
139
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "kld-sdd",
3
- "version": "2.6.21",
3
+ "version": "2.7.8",
4
4
  "description": "KLD SDD OpenSpec 项目初始化工具 - 一键部署 SDD skills",
5
5
  "main": "index.js",
6
6
  "bin": {
@@ -8,7 +8,8 @@
8
8
  "kld-sdd-init": "bin/kld-sdd-init.js"
9
9
  },
10
10
  "scripts": {
11
- "test": "node test/external-key.cjs && node test/change-key.cjs && node test/modules-and-sdd-config.cjs && node test/active-changes.cjs && node test/hook-layouts.cjs && node test/spec-package-init.cjs && node test/workspace-layout.cjs && node test/ontology-release-blockers.cjs && node test/ontology-semantic-core.cjs && node test/ontology-identity-versioning.cjs && node test/ontology-identity-continuity.cjs && node test/ontology-state-transaction.cjs && node test/ontology-process-concurrency.cjs && node test/ontology-observer-convergence.cjs && node test/ontology-working-runtime.cjs && node test/ontology-stage-materialization.cjs && node test/ontology-template-contract.cjs && node test/ontology-cli-archive.cjs && node test/archive-package-producer.cjs && node --test test/evidence-integrity-oracle.cjs test/evidence-integrity-wiring.cjs test/change-report-correctness.cjs test/report-metrics-correctness.cjs test/change-report-model-ui.cjs && node test/validate-skills-bundle.cjs && node test/tool-profiles.cjs && node test/settings-merge.cjs && node test/command-bridge.cjs && node test/codebuddy-hooks.cjs && node test/skill-content-contract.cjs && node test/init-agent-profiles.cjs && node test/init-report-runtime.cjs && node test/init-output-verbosity.cjs && node test/init-account-binding.cjs && node --test test/usage-contract.test.cjs test/user-config.test.cjs test/device-auth.test.cjs test/usage-runtime-removal.test.cjs skywalk-sdd/usage-reporter.test.cjs skywalk-sdd/usage-reporting.test.cjs && node test/progress-package-artifact.test.cjs"
11
+ "bench:check": "node test/benchmark-check.cjs",
12
+ "test": "node --test test/kb-fact-lifecycle.test.cjs test/workflow-usability.test.cjs test/check-efficiency.test.cjs && node test/external-key.cjs && node test/change-key.cjs && node test/modules-and-sdd-config.cjs && node test/active-changes.cjs && node test/hook-layouts.cjs && node test/spec-package-init.cjs && node test/prompt-spec-path.cjs && node test/workspace-layout.cjs && node test/ontology-release-blockers.cjs && node test/ontology-semantic-core.cjs && node test/ontology-identity-versioning.cjs && node test/ontology-identity-continuity.cjs && node test/ontology-state-transaction.cjs && node test/ontology-process-concurrency.cjs && node test/ontology-observer-convergence.cjs && node test/ontology-working-runtime.cjs && node test/ontology-stage-materialization.cjs && node test/ontology-template-contract.cjs && node test/ontology-cli-archive.cjs && node test/archive-package-producer.cjs && node --test test/evidence-integrity-oracle.cjs test/evidence-integrity-wiring.cjs test/change-report-correctness.cjs test/report-metrics-correctness.cjs test/change-report-model-ui.cjs && node test/validate-skills-bundle.cjs && node test/tool-profiles.cjs && node test/settings-merge.cjs && node test/command-bridge.cjs && node test/codebuddy-hooks.cjs && node test/skill-content-contract.cjs && node test/init-agent-profiles.cjs && node test/init-report-runtime.cjs && node test/init-output-verbosity.cjs && node --test test/usage-contract.test.cjs test/user-config.test.cjs test/usage-runtime-removal.test.cjs skywalk-sdd/usage-reporter.test.cjs skywalk-sdd/usage-reporting.test.cjs && node test/progress-package-artifact.test.cjs && node --test test/progress-package-install.e2e.cjs && node test/consistency-check-core.cjs"
12
13
  },
13
14
  "keywords": [
14
15
  "kld",
@@ -43,6 +44,7 @@
43
44
  "skywalk-sdd/lib/",
44
45
  "skywalk-sdd/kb-upload.cjs",
45
46
  "skywalk-sdd/kb-sync-identity.cjs",
46
- "README.md"
47
+ "README.md",
48
+ "USABILITY.md"
47
49
  ]
48
50
  }