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.
- package/README.md +12 -5
- package/USABILITY.md +84 -0
- package/bin/kld-sdd-init.js +3 -9
- package/kld-sdd-guide.html +16 -4
- package/lib/hook-gate-core.js +327 -0
- package/lib/init.js +241 -94
- package/lib/scale-thresholds.json +19 -0
- package/lib/skills-bundle.js +2 -1
- package/package.json +5 -3
- package/skywalk-sdd/context-client.cjs +50 -30
- package/skywalk-sdd/index.cjs +49 -16
- package/skywalk-sdd/kb-sync-identity.cjs +99 -451
- package/skywalk-sdd/kb-upload.cjs +67 -44
- package/skywalk-sdd/lib/git-identity.cjs +270 -0
- package/skywalk-sdd/lib/usage-contract.cjs +13 -81
- package/skywalk-sdd/lib/usage-reporter.cjs +235 -104
- package/skywalk-sdd/lib/user-config.cjs +21 -46
- package/skywalk-sdd/metrics-v3.cjs +2 -2
- package/skywalk-sdd/ontology/active-changes.cjs +2 -1
- package/skywalk-sdd/ontology/archive-package.cjs +1 -1
- package/skywalk-sdd/ontology/artifact-parser.cjs +8 -6
- package/skywalk-sdd/ontology/id.cjs +5 -4
- package/skywalk-sdd/ontology/identity-index.cjs +4 -3
- package/skywalk-sdd/ontology/list-changes.cjs +1 -1
- package/skywalk-sdd/ontology/runtime.cjs +7 -3
- package/skywalk-sdd/ontology/schema.cjs +2 -0
- package/skywalk-sdd/ontology/traceability-validator.cjs +74 -4
- package/skywalk-sdd/ontology/workspace-layout.cjs +25 -5
- package/skywalk-sdd/reporting/change-report-model.cjs +4 -3
- package/templates/git-hooks/commit-msg +71 -27
- package/templates/git-hooks/consistency-check-core.cjs +1087 -0
- package/templates/git-hooks/hooks.config +38 -0
- package/templates/git-hooks/pre-commit +62 -27
- package/templates/git-hooks/pre-commit-consistency-check.cjs +29 -332
- package/templates/git-hooks/pre-commit-sdd-check.cjs +98 -0
- package/templates/git-hooks/pre-push +70 -27
- package/templates/git-hooks/pre-push-consistency-check.cjs +61 -299
- package/templates/hooks/codebuddy/hooks/sdd-tdd-rhythm-gate.cjs +1 -1
- package/templates/openspec/tasks.md +3 -3
- package/templates/skills/kld-sdd/opsx-apply/SKILL.md +43 -6
- package/templates/skills/kld-sdd/opsx-apply/checklist.md +1 -1
- package/templates/skills/kld-sdd/opsx-apply/reference.md +1 -1
- package/templates/skills/kld-sdd/opsx-archive/SKILL.md +9 -13
- package/templates/skills/kld-sdd/opsx-check/SKILL.md +54 -328
- package/templates/skills/kld-sdd/opsx-check/checklist.md +7 -4
- package/templates/skills/kld-sdd/opsx-check/reference.md +58 -0
- package/templates/skills/kld-sdd/opsx-check/result-template.json +52 -0
- package/templates/skills/kld-sdd/opsx-check/reviewer.md +100 -0
- package/templates/skills/kld-sdd/opsx-consistency-check/SKILL.md +82 -451
- package/templates/skills/kld-sdd/opsx-consistency-check/{reference.md → references/reference.md} +0 -1
- package/templates/skills/kld-sdd/opsx-consistency-check/scripts/scripts.cjs +517 -0
- package/templates/skills/kld-sdd/opsx-design/SKILL.md +10 -30
- package/templates/skills/kld-sdd/opsx-design/checklist.md +3 -4
- package/templates/skills/kld-sdd/opsx-design/reference.md +1 -1
- package/templates/skills/kld-sdd/opsx-kb-ingest/SKILL.md +12 -74
- package/templates/skills/kld-sdd/opsx-ontology-query/SKILL.md +7 -5
- package/templates/skills/kld-sdd/opsx-ontology-query/phase-1-prechange.md +2 -2
- package/templates/skills/kld-sdd/opsx-ontology-query/reference.md +1 -1
- package/templates/skills/kld-sdd/opsx-propose/SKILL.md +28 -73
- package/templates/skills/kld-sdd/opsx-propose/checklist.md +8 -8
- package/templates/skills/kld-sdd/opsx-propose/interaction-policy.md +28 -0
- package/templates/skills/kld-sdd/opsx-propose/reference.md +12 -46
- package/templates/skills/kld-sdd/opsx-spec/SKILL.md +14 -61
- package/templates/skills/kld-sdd/opsx-spec/checklist.md +3 -3
- package/templates/skills/kld-sdd/opsx-task/SKILL.md +38 -32
- package/templates/skills/kld-sdd/opsx-task/checklist.md +5 -6
- package/templates/skills/kld-sdd/opsx-test/SKILL.md +2 -0
- package/templates/skills/kld-sdd/tdd-rules/rules/tdd-strategy-selection.md +1 -1
- package/lib/device-auth-cli.js +0 -130
- package/lib/device-auth.js +0 -345
- 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
|
-
//
|
|
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: '使用统计契约(七值/
|
|
904
|
-
{ src: 'usage-reporter.cjs', desc: '使用事件上报器(3s deadline + pending 队列)' },
|
|
905
|
-
{ src: 'user-config.cjs', desc: '用户级配置(~/.kld-sdd/config.json
|
|
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
|
-
|
|
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
|
-
|
|
1099
|
-
|
|
1100
|
-
'
|
|
1101
|
-
|
|
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
|
-
//
|
|
1108
|
-
|
|
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
|
-
//
|
|
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(` 代码仓: ${
|
|
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 (
|
|
1716
|
-
layout.specRepo
|
|
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
|
|
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
|
-
|
|
1770
|
-
|
|
1771
|
-
|
|
1772
|
-
|
|
1773
|
-
|
|
1774
|
-
|
|
1775
|
-
|
|
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
|
-
//
|
|
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
|
+
}
|
package/lib/skills-bundle.js
CHANGED
|
@@ -132,7 +132,8 @@ function transformKunlunSlashCommands(content) {
|
|
|
132
132
|
|
|
133
133
|
let result = content;
|
|
134
134
|
for (const cmd of KUNLUN_SLASH_COMMANDS) {
|
|
135
|
-
|
|
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.
|
|
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
|
-
"
|
|
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
|
}
|