kld-sdd 2.7.8-1 → 2.7.8-3
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 +2 -0
- package/USABILITY.md +72 -0
- package/bin/kld-sdd-init.js +1 -1
- package/lib/hook-install.js +75 -0
- package/lib/init.js +35 -148
- package/lib/managed-hook-hashes.json +24 -0
- package/package.json +2 -2
- package/skywalk-sdd/index.cjs +124 -25
- package/skywalk-sdd/kb-upload.cjs +21 -17
- package/skywalk-sdd/lib/check-review.cjs +41 -0
- package/skywalk-sdd/lib/test-execution.cjs +130 -0
- package/templates/git-hooks/commit-msg +17 -85
- package/templates/git-hooks/commit-msg-runner.cjs +44 -0
- package/templates/git-hooks/commit-msg-sdd-trailer.cjs +60 -69
- package/templates/git-hooks/consistency-check-core.cjs +92 -261
- package/templates/git-hooks/delivery-check.cjs +231 -0
- package/templates/git-hooks/delivery-snapshot.cjs +181 -0
- package/templates/git-hooks/hooks.config +8 -6
- package/templates/git-hooks/pre-commit +19 -5
- package/templates/git-hooks/pre-commit-consistency-check.cjs +13 -31
- package/templates/git-hooks/pre-push +19 -5
- package/templates/git-hooks/pre-push-consistency-check.cjs +36 -83
- package/templates/hooks/claude/hooks/sdd-post-tool.cjs +2 -2
- package/templates/hooks/codebuddy/hooks/sdd-post-tool.cjs +2 -2
- package/templates/openspec/proposal.md +1 -1
- package/templates/skills/kld-sdd/opsx-apply/SKILL.md +25 -22
- package/templates/skills/kld-sdd/opsx-apply/reference.md +19 -1
- package/templates/skills/kld-sdd/opsx-archive/SKILL.md +10 -8
- package/templates/skills/kld-sdd/opsx-check/SKILL.md +7 -5
- package/templates/skills/kld-sdd/opsx-check/reference.md +3 -1
- package/templates/skills/kld-sdd/opsx-check/review-template.json +22 -0
- package/templates/skills/kld-sdd/opsx-check/reviewer.md +5 -5
- package/templates/skills/kld-sdd/opsx-consistency-check/SKILL.md +34 -112
- package/templates/skills/kld-sdd/opsx-consistency-check/scripts/scripts.cjs +1 -14
- package/templates/skills/kld-sdd/opsx-kb-config/SKILL.md +29 -154
- package/templates/skills/kld-sdd/opsx-kb-config/reference.md +8 -11
- package/templates/skills/kld-sdd/opsx-kb-ingest/reference.md +2 -2
- package/templates/skills/kld-sdd/opsx-propose/SKILL.md +1 -1
- package/templates/skills/kld-sdd/opsx-propose/interaction-policy.md +7 -0
- package/templates/skills/kld-sdd/opsx-task/SKILL.md +2 -2
package/README.md
CHANGED
|
@@ -4,6 +4,8 @@ KLD SDD OpenSpec 工程增强工具:一键初始化 AI 编辑器技能、语
|
|
|
4
4
|
|
|
5
5
|
本分支已调整使用方式:模式与测试策略自动选择,按已有授权连续处理,KB 断连时继续本地工作,发布时核对真实基线。新规则、设计依据与尚未完成的结构调整见 [使用摩擦分析](USABILITY.md)。旧安装副本需要更新后才会应用这些规则。
|
|
6
6
|
|
|
7
|
+
日常提交默认只保留本地关联 Hook;Apply 自动执行代码/Spec 检查、真实测试与交付验证。旧项目升级、备份回退、多仓和 CI 边界见 [Git Hook 调整说明](USABILITY.md#2026-09-10git-hook-收缩与交付验证)。
|
|
8
|
+
|
|
7
9
|
## 这是什么?
|
|
8
10
|
|
|
9
11
|
**SDD(Specification-Driven Development)** 是一种以文档链驱动 AI 编码的研发方法:先写清楚"要做什么",再让 AI 去实现,避免 AI 乱猜、反复返工。
|
package/USABILITY.md
CHANGED
|
@@ -82,3 +82,75 @@ SDD 应帮助团队留下三类东西:明确的业务约定、能够证明实
|
|
|
82
82
|
新增回归覆盖 Full/Simple 结果等价、正文增改删后的重新校验、跨 Change 身份冲突、strict profile、结束时重新解析、strict JSON 记录拒绝占位值,以及八种 Agent 的参考文件渲染。消费方需要重新安装/更新技能和 skywalk-sdd 运行时才能使用新参数;只更新技能文件会导致旧运行时缺少合并输出。
|
|
83
83
|
|
|
84
84
|
验证结果(2026-09-08,本次 Check 优化):完整 `npm test` 通过,包含新增 6 组 Check 回归与全部既有用例;`npm run bench:check` 的 7 轮结论与任务计数等价断言全部通过。
|
|
85
|
+
|
|
86
|
+
## 2026-09-10:真实执行与故障恢复
|
|
87
|
+
|
|
88
|
+
本次承接已发布的 `2.7.8-1`,继续提交在 `codex/sdd-usability`。以下新增能力尚未重新发布 npm;原有身份、入库协议、七种 Skill 使用上报保持兼容。
|
|
89
|
+
|
|
90
|
+
- `kld-sdd log` 修正为实际打包的 `index.cjs` 入口。strict 记录失败会保留 details 文件,成功持久化后才清理。
|
|
91
|
+
- `test-run` 执行真实命令,保存退出码、耗时、原始 stdout/stderr 及 SHA-256。当前自动识别 Node TAP/原生测试摘要;未识别的 runner 仍保存输出,标记计数未知,不能自动完成任务。
|
|
92
|
+
- `test-run --completion-file` 为明确声明的任务关联实际测试事件并同步 checkbox。命令执行成功不等于业务覆盖充分,任务与测试的对应关系仍需评审核验;缺少测试、路径错误、超时不能记成有效 RED。
|
|
93
|
+
- `check-record` 接收实际检查项与真实评审身份,自动计算分类统计、任务缺口和结果,可通过 `--end-event-id` 一次记录并结束。按预检 profile 重新核对正文和历史身份;变化时拒绝旧评审。结束阶段复用本次刚完成的校验,避免再跑一次同样的对账。
|
|
94
|
+
- Archive 在启动归档阶段前运行 doctor;终态通知独立记为 `run_end`,旧 `stage_end + run_ended` 在计算时兼容归一化,避免虚增阶段或孤儿日志。通知保留,原始旧日志不改写。
|
|
95
|
+
- 修复进程锁发布窗口:完整锁文件原子发布,其他进程不能把尚未写完的锁当作失效锁抢走。
|
|
96
|
+
- 文档按实际业务决策与验收行为组织,不为填模板反复复制背景、堆空章节或按每个参数值拆一次任务。
|
|
97
|
+
|
|
98
|
+
详细参数与示例在 [Apply 参考](templates/skills/kld-sdd/opsx-apply/reference.md) 和 [Check 指南](templates/skills/kld-sdd/opsx-check/SKILL.md)。新命令需要更新运行时和 Skills,旧 `record --strict` 仍可使用。
|
|
99
|
+
|
|
100
|
+
验证:完整 `npm test` 通过;新增 19 个真实执行/恢复回归,涵盖测试输出篡改、零测试、超时、RED、任务关联、旧评审拒绝、单次最终复核、真实归档和六进程竞争。打包及初始化器均部署两个新模块,安装后的真实入口也有回归覆盖。预检基准七轮中位数由 294.6 ms 降至 156.3 ms,减少 46.9%;它仅测程序预检,不代表模型评审总耗时。
|
|
101
|
+
|
|
102
|
+
本机独立后端联调确认七种 Skill 的使用事件均成功写入,断网事件保留并恢复补报,服务端统计为 8 次、覆盖 7 阶段。使用统计是独立的 usage 事件,不等于测试通过、归档完成或 Spec 已入库;当前服务端按提交次数计数,网络响应丢失后的重复提交不提供 exactly-once 保证。
|
|
103
|
+
|
|
104
|
+
配置 Skill 与参考同步去除默认首目标、截短 UUID 和旧监控项目绑定步骤,修正 KB API 字段映射;切换查询目标不再引导改写团队项目身份,权限失败也不清空其他目标可能仍有效的密钥。
|
|
105
|
+
|
|
106
|
+
## 2026-09-10:Git Hook 收缩与交付验证
|
|
107
|
+
|
|
108
|
+
日常 commit 负责保存工作,Apply 收尾负责检查交付,Spec 入库负责发布知识。新安装只自动接入一个本地 `commit-msg`,它不请求 KB、不调用模型、不扫描整个工作区,也不重复执行文档 Check。
|
|
109
|
+
|
|
110
|
+
| 场景 | 当前行为 |
|
|
111
|
+
|---|---|
|
|
112
|
+
| 本次明确指定 Change | `KLD_SDD_CHANGE` 支持逗号分隔的多个 Change,校验目录后写关联 |
|
|
113
|
+
| 未显式指定 Change | 只复用能与暂存文件内容逐项匹配的检查关联;多个候选或没有证据时提示待关联 |
|
|
114
|
+
| Spec 仓尚未提交 | 允许保存代码,不写旧 HEAD 冒充当前 `Spec-Revision` |
|
|
115
|
+
| 独立 Spec 仓干净且远端关系匹配 | 写完整不可变 Spec SHA;单仓不写自指 Revision |
|
|
116
|
+
| 路径失效、Node 缺失或关联脚本超时 | 本次提交继续,显示待关联,不表示交付检查通过 |
|
|
117
|
+
| 实施收尾 | Agent 自动进行代码/Spec 检查、真实测试和 `delivery-check verify` |
|
|
118
|
+
|
|
119
|
+
关联 worker 设置 1.5 秒超时上限。这个上限不是性能测试结果;用户自有 Hook 的耗时不受 SDD 控制。
|
|
120
|
+
|
|
121
|
+
### 一个验证入口,四个内部步骤
|
|
122
|
+
|
|
123
|
+
入口是 `<spec-root>/skywalk-sdd/git-hooks/delivery-check.cjs`。Apply 自动串联,用户不需要记忆四条命令:
|
|
124
|
+
|
|
125
|
+
1. `prepare --project=<代码仓Git根> --change=<Change> --base=<实施前SHA>`:记录输入,返回本次需要检查的文件和是否可复用旧回执。
|
|
126
|
+
2. `record-review --project=... --change=... --input=<检查JSON>`:收录实际检查结果;输入改变、漏覆盖、失败、未检查都会明确显示。
|
|
127
|
+
3. `test --project=... --change=... --session-id=... --task-ids=... --command-file=<命令数组JSON>`:内部复用原 `test-run`,保留严格测试事件、命令退出码及原始输出。
|
|
128
|
+
4. `verify --project=... --change=...`:判断当前快照的证据是否完整。`ready` 与 `pending` 分开,不把 KB 入库或合并状态混入测试结果。
|
|
129
|
+
|
|
130
|
+
机器回执绑定代码仓身份、Spec 仓身份、明确 Change、代码和 Spec 内容、规则版本与配置。当前保守地纳入本仓依赖文件及全局规格;相同指纹可复用,变化后再定向取证。它尚未实现自动跨仓依赖图或逐规则的最小重查。mtime 不再作为内容一致的证据。UTF-8 文本统一 LF 以兼容 Git autocrlf,二进制和符号链接目标按原始字节检查。
|
|
131
|
+
|
|
132
|
+
检查结果分别记录接口、业务、校验和错误处理;`pass` 需要证据,`na` 需要理由,`not_checked` 与 `fail` 都不能算通过。回执记录 Agent 做过的检查及其输入,不单靠模型自报置信度判断正确性。
|
|
133
|
+
|
|
134
|
+
测试计数支持 Node TAP 与 Maven Surefire/Failsafe 的结果摘要;未知测试格式保留实际输出并显示待验证,不编造计数。只有无业务代码变更、四个维度都经检查判为不适用,且回执有明确测试不适用理由时,才允许 `testStatus=not_applicable`,不会显示“测试通过”。
|
|
135
|
+
|
|
136
|
+
### 多仓与正式提交
|
|
137
|
+
|
|
138
|
+
每个代码仓分别存回执,同一 Change 的多仓结果互不覆盖。Spec 可以先于代码完成并入库;“规格已发布”和“实现已验证”是两个状态。
|
|
139
|
+
|
|
140
|
+
需要核对已提交代码时使用 `verify --scope=commit --ref=<目标提交> --base=<实施前提交>`;独立 Spec 仓额外提供 `--spec-ref=<完整Spec SHA>`。命令读取指定 Git 对象,可以验证非当前 HEAD,不会用工作区替代。冲突解决改变内容后需重新检查和测试。
|
|
141
|
+
|
|
142
|
+
本次没有自动启用团队 CI。现有 CI 样例不等于已接入的合并门禁。团队接入时需要在受信任的执行环境生成或提供相应回执与原始测试日志;当前测试回执包含本地路径,复制到另一机器不能直接当作可移植的验证包。可以在目标 checkout 上重新执行同一入口。
|
|
143
|
+
|
|
144
|
+
### 已有项目升级与回退
|
|
145
|
+
|
|
146
|
+
重新运行安装器会更新运行时与 Skills,并迁移已识别的原版 SDD Hook。已退役或更新的 Hook 先备份到 Git 公共目录的 `kld-sdd-hook-backups/`;回退时把对应 `.bak` 复制回 hooks 同名路径并恢复执行权限。
|
|
147
|
+
|
|
148
|
+
只含 marker 不足以证明文件受管:追加过用户逻辑、Hook 符号链接、修改过插件列表、显式旧门禁设置都会保留并提示。`core.hooksPath`(包括 Husky)交给现有管理器维护;安装器不会接管其目录。普通 worktree 使用 Git 解析出的公共 hooks 目录。`hooks.config` 保留用户内容,重新安装不重置选项。
|
|
149
|
+
|
|
150
|
+
空目录首次安装声明正确的 mono 布局;此前生成的原样多仓占位配置可备份后自动迁移,真实团队配置不会被猜测改写。只有一个无效 `.git` 目录时明确提示 Hook 未安装,本地文档与运行时仍可部署。
|
|
151
|
+
|
|
152
|
+
### 上报与知识链路的边界
|
|
153
|
+
|
|
154
|
+
原 Skill 使用上报、严格测试事件和 KB 上传/查询 API 保留。默认移除旧 pre-commit 后,不再采集那条仅凭“本次提交有代码而没有文档”推断的 bypass 事件;这类旧统计与真实 Skill 使用量不能混为一谈。
|
|
155
|
+
|
|
156
|
+
`kb-upload --validate-only` 在未连接 KB 时可做本地语义校验,并明确说明未验证权限、发布基线及入库状态;正式上传仍严格检查项目与目标配置,连接或身份异常不能伪装为入库成功。UUID 继续完整保存,仅修正帮助中残留的 8 位说明。
|
package/bin/kld-sdd-init.js
CHANGED
|
@@ -16,7 +16,7 @@ const args = process.argv.slice(2);
|
|
|
16
16
|
|
|
17
17
|
if (args[0] === 'log') {
|
|
18
18
|
// Telemetry CLI 模式
|
|
19
|
-
require('../skywalk-sdd/index.
|
|
19
|
+
require('../skywalk-sdd/index.cjs').main();
|
|
20
20
|
} else if (args[0] === 'link-spec') {
|
|
21
21
|
const sddConfig = require('../skywalk-sdd/ontology/sdd-config.cjs');
|
|
22
22
|
const pathArg = args.find((item) => item.startsWith('--path='));
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
const fs = require('fs');
|
|
3
|
+
const path = require('path');
|
|
4
|
+
const crypto = require('crypto');
|
|
5
|
+
const { execFileSync } = require('child_process');
|
|
6
|
+
const known = require('./managed-hook-hashes.json');
|
|
7
|
+
const hash = text => crypto.createHash('sha256').update(text).digest('hex');
|
|
8
|
+
|
|
9
|
+
function section(text, name) {
|
|
10
|
+
let active = false;
|
|
11
|
+
const result = [];
|
|
12
|
+
for (const raw of text.split(/\r?\n/)) {
|
|
13
|
+
const line = raw.trim();
|
|
14
|
+
if (line.startsWith('[')) active = line === `[${name}]`;
|
|
15
|
+
else if (active && line && !line.startsWith('#')) result.push(line);
|
|
16
|
+
}
|
|
17
|
+
return result.join('\n');
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function updateHook(root, name, pkgPath, configFile) {
|
|
21
|
+
const git = args => execFileSync('git', args, { cwd: root, encoding: 'utf8', stdio: ['ignore', 'pipe', 'pipe'] }).trim();
|
|
22
|
+
let configured = '';
|
|
23
|
+
try { configured = git(['config', '--get', 'core.hooksPath']); } catch { /* default path */ }
|
|
24
|
+
if (configured) {
|
|
25
|
+
console.log(` ℹ️ 保留 core.hooksPath=${configured};由现有 Hook 管理器接入,SDD 不覆盖它`);
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
let hookPath;
|
|
29
|
+
try { hookPath = path.resolve(root, git(['rev-parse', '--git-path', `hooks/${name}`])); }
|
|
30
|
+
catch { console.log(` ⚠️ ${root} 不是有效 Git 仓库,未安装 ${name} Hook`); return; }
|
|
31
|
+
const templateDir = path.join(pkgPath, 'templates/git-hooks');
|
|
32
|
+
const content = fs.readFileSync(path.join(templateDir, name));
|
|
33
|
+
const stat = fs.lstatSync(hookPath, { throwIfNoEntry: false });
|
|
34
|
+
if (stat && !stat.isFile()) { console.log(` ℹ️ 保留 Hook 链接或特殊文件:${hookPath}`); return; }
|
|
35
|
+
const existing = stat ? fs.readFileSync(hookPath) : null;
|
|
36
|
+
const customizedConfig = configFile && fs.existsSync(configFile)
|
|
37
|
+
&& section(fs.readFileSync(configFile, 'utf8'), name) !== section(fs.readFileSync(path.join(templateDir, 'hooks.config'), 'utf8'), name);
|
|
38
|
+
const explicitGate = name !== 'commit-msg' && configFile && fs.existsSync(configFile)
|
|
39
|
+
&& /^(?:strict|skip|warn_only)\s*=\s*(?:1|true|yes)$/m.test(section(fs.readFileSync(configFile, 'utf8'), 'consistency'));
|
|
40
|
+
if (existing && (explicitGate || customizedConfig ||
|
|
41
|
+
(!known[name].includes(hash(existing)) && hash(existing) !== hash(content)))) {
|
|
42
|
+
console.log(` ℹ️ 保留自定义 Hook:${hookPath};可审阅后自行移除旧 SDD 调用`);
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
if (existing && hash(existing) === hash(content) && name === 'commit-msg') return;
|
|
46
|
+
if (existing) {
|
|
47
|
+
const common = path.resolve(root, git(['rev-parse', '--git-common-dir']));
|
|
48
|
+
const backup = path.join(common, 'kld-sdd-hook-backups', `${name}.${hash(existing)}.bak`);
|
|
49
|
+
fs.mkdirSync(path.dirname(backup), { recursive: true });
|
|
50
|
+
if (!fs.existsSync(backup)) fs.copyFileSync(hookPath, backup);
|
|
51
|
+
console.log(` ✓ 旧 Hook 已备份:${backup}`);
|
|
52
|
+
}
|
|
53
|
+
if (name !== 'commit-msg') {
|
|
54
|
+
if (existing) fs.unlinkSync(hookPath);
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
fs.mkdirSync(path.dirname(hookPath), { recursive: true });
|
|
58
|
+
fs.writeFileSync(hookPath, content, { mode: 0o755 });
|
|
59
|
+
fs.chmodSync(hookPath, 0o755);
|
|
60
|
+
console.log(' ✓ commit-msg 已更新为本地关联提示;交付验证由 Apply 收尾执行');
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function deployRuntime(specRoot, pkgPath) {
|
|
64
|
+
const dir = path.join(specRoot, 'skywalk-sdd/git-hooks');
|
|
65
|
+
const source = path.join(pkgPath, 'templates/git-hooks');
|
|
66
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
67
|
+
for (const file of fs.readdirSync(source)) {
|
|
68
|
+
const target = path.join(dir, file);
|
|
69
|
+
const stat = fs.lstatSync(target, { throwIfNoEntry: false });
|
|
70
|
+
if (stat && !stat.isFile()) { console.log(` ℹ️ 保留运行时链接:${target}`); continue; }
|
|
71
|
+
if (file.endsWith('.cjs') || (file === 'hooks.config' && !stat)) fs.copyFileSync(path.join(source, file), target);
|
|
72
|
+
}
|
|
73
|
+
return path.join(dir, 'hooks.config');
|
|
74
|
+
}
|
|
75
|
+
module.exports = { updateHook, deployRuntime };
|
package/lib/init.js
CHANGED
|
@@ -1018,6 +1018,13 @@ function deployTelemetryDataDir(targetCwd = process.cwd()) {
|
|
|
1018
1018
|
if (!fs.existsSync(sharedLibDir)) {
|
|
1019
1019
|
fs.mkdirSync(sharedLibDir, { recursive: true });
|
|
1020
1020
|
}
|
|
1021
|
+
for (const name of ['test-execution.cjs', 'check-review.cjs']) {
|
|
1022
|
+
const src = path.join(pkgPath, 'skywalk-sdd', 'lib', name);
|
|
1023
|
+
if (!fs.existsSync(src)) throw new Error(`E_INIT_MISSING_CRITICAL: lib/${name} 源文件缺失`);
|
|
1024
|
+
fs.copyFileSync(src, path.join(sharedLibDir, name));
|
|
1025
|
+
deployedModules += 1;
|
|
1026
|
+
vlog(` ✓ 部署 skywalk-sdd/lib/${name}`);
|
|
1027
|
+
}
|
|
1021
1028
|
const sharedSrc = path.join(pkgPath, 'skywalk-sdd', 'lib', 'shared.cjs');
|
|
1022
1029
|
const sharedDst = path.join(sharedLibDir, 'shared.cjs');
|
|
1023
1030
|
if (fs.existsSync(sharedSrc)) {
|
|
@@ -1144,6 +1151,8 @@ function deployTelemetryDataDir(targetCwd = process.cwd()) {
|
|
|
1144
1151
|
console.log(' ⚠️ kb-sync-identity.cjs 源文件缺失,重新入库身份同步不可用');
|
|
1145
1152
|
}
|
|
1146
1153
|
|
|
1154
|
+
require('./hook-install.js').deployRuntime(cwd, pkgPath);
|
|
1155
|
+
|
|
1147
1156
|
console.log(` ✓ 部署 skywalk-sdd/ 运行时(events/state 数据目录 + ${deployedModules} 个模块)`);
|
|
1148
1157
|
vlog(' ✓ 调用方式: node skywalk-sdd/log.cjs start|end|metrics|semantic-identity|semantic-reconcile|semantic-check');
|
|
1149
1158
|
vlog(' ✓ openspec: node skywalk-sdd/openspec-shim.cjs list(自动 cd 到 *-sdd-specs)');
|
|
@@ -1154,76 +1163,12 @@ function deployTelemetryDataDir(targetCwd = process.cwd()) {
|
|
|
1154
1163
|
return true;
|
|
1155
1164
|
}
|
|
1156
1165
|
|
|
1157
|
-
/**
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
*
|
|
1161
|
-
* shell hook 运行时通过 git config sdd.specPath / .sdd-spec-root / 向上搜索
|
|
1162
|
-
* 定位 spec 仓的 skywalk-sdd/git-hooks/ 目录,兼容单仓/多仓/mono 布局。
|
|
1163
|
-
*
|
|
1164
|
-
* @param {string} cwd - Git 仓库根目录
|
|
1165
|
-
* @param {string} hookName - hook 名称(如 'pre-commit'、'pre-push')
|
|
1166
|
-
* @param {string} pkgPath - kld-sdd 包路径(用于读取模板)
|
|
1167
|
-
*/
|
|
1168
|
-
function installSddShellHook(cwd, hookName, pkgPath = getPackagePath()) {
|
|
1169
|
-
const hooksDir = path.join(cwd, '.git', 'hooks');
|
|
1170
|
-
if (!fs.existsSync(hooksDir)) {
|
|
1171
|
-
return;
|
|
1172
|
-
}
|
|
1173
|
-
|
|
1174
|
-
const templatePath = path.join(pkgPath, 'templates', 'git-hooks', hookName);
|
|
1175
|
-
if (!fs.existsSync(templatePath)) {
|
|
1176
|
-
console.log(` ⚠️ Hook 模板不存在: ${templatePath}`);
|
|
1177
|
-
return;
|
|
1178
|
-
}
|
|
1179
|
-
|
|
1180
|
-
const hookPath = path.join(hooksDir, hookName);
|
|
1181
|
-
const marker = 'KLD SDD quality gate';
|
|
1182
|
-
const content = fs.readFileSync(templatePath, 'utf8');
|
|
1183
|
-
|
|
1184
|
-
if (fs.existsSync(hookPath)) {
|
|
1185
|
-
const existing = fs.readFileSync(hookPath, 'utf8');
|
|
1186
|
-
if (existing.includes(marker)) {
|
|
1187
|
-
// 已是 SDD hook:允许覆盖升级
|
|
1188
|
-
fs.writeFileSync(hookPath, content, 'utf8');
|
|
1189
|
-
try {
|
|
1190
|
-
// 0o700:仅属主可执行(git 以属主身份执行 hook),避免 S2612 对组/其他用户放权的告警
|
|
1191
|
-
fs.chmodSync(hookPath, 0o700);
|
|
1192
|
-
} catch {
|
|
1193
|
-
// ignore
|
|
1194
|
-
}
|
|
1195
|
-
console.log(` ✓ 更新 .git/hooks/${hookName} SDD 质量门禁(多脚本)`);
|
|
1196
|
-
return;
|
|
1197
|
-
}
|
|
1198
|
-
console.log(` ℹ️ .git/hooks/${hookName} 已存在且非 SDD 受管 hook,已保留不覆盖`);
|
|
1199
|
-
return;
|
|
1200
|
-
}
|
|
1201
|
-
|
|
1202
|
-
fs.writeFileSync(hookPath, content, 'utf8');
|
|
1203
|
-
try {
|
|
1204
|
-
fs.chmodSync(hookPath, 0o755);
|
|
1205
|
-
} catch {
|
|
1206
|
-
// Windows 上 chmod 可能没有实际效果
|
|
1207
|
-
}
|
|
1208
|
-
console.log(` ✓ 安装 .git/hooks/${hookName} SDD 质量门禁(多脚本)`);
|
|
1166
|
+
/** 安全升级 commit-msg、备份并退役受管旧门禁,保留用户 Hook 与管理器。 */
|
|
1167
|
+
function installSddShellHook(cwd, hookName, pkgPath = getPackagePath(), configFile) {
|
|
1168
|
+
return require('./hook-install.js').updateHook(cwd, hookName, pkgPath, configFile);
|
|
1209
1169
|
}
|
|
1210
1170
|
|
|
1211
|
-
/**
|
|
1212
|
-
* 部署代码仓全部 Git hooks(commit-msg + pre-commit + pre-push)。
|
|
1213
|
-
*
|
|
1214
|
-
* 统一入口,封装三种 hook 的部署逻辑。代码仓**不落 skywalk-sdd/**,
|
|
1215
|
-
* 只在 .git/hooks/ 安装 shell hook 模板,远程引用 spec 仓的 .cjs 脚本:
|
|
1216
|
-
*
|
|
1217
|
-
* - **spec 仓**:确保 skywalk-sdd/git-hooks/ 下有全部 .cjs 脚本
|
|
1218
|
-
* (commit-msg-sdd-trailer + pre-commit-* + pre-push-* + ontology)。
|
|
1219
|
-
* - **代码仓 .git/hooks/**:仅安装 3 个 shell hook 模板,运行时通过
|
|
1220
|
-
* git config sdd.specPath / .sdd-spec-root / 向上搜索定位 spec 仓的脚本。
|
|
1221
|
-
*
|
|
1222
|
-
* @param {string} codeRepoRoot - 代码仓根目录
|
|
1223
|
-
* @param {string} specRepoRoot - spec 包裹包根目录
|
|
1224
|
-
* @param {object} options - { pkgPath }
|
|
1225
|
-
* @returns {{ ok: boolean, message?: string }}
|
|
1226
|
-
*/
|
|
1171
|
+
/** 代码仓只安装 commit-msg;验证脚本集中部署在 Spec 包。 */
|
|
1227
1172
|
function deployCodeRepoHooks(codeRepoRoot, specRepoRoot, options = {}) {
|
|
1228
1173
|
const cwd = path.resolve(codeRepoRoot);
|
|
1229
1174
|
const specRoot = path.resolve(specRepoRoot);
|
|
@@ -1233,32 +1178,11 @@ function deployCodeRepoHooks(codeRepoRoot, specRepoRoot, options = {}) {
|
|
|
1233
1178
|
return { ok: false, message: '不是 Git 仓库' };
|
|
1234
1179
|
}
|
|
1235
1180
|
|
|
1236
|
-
// ── 1. 确保 spec 仓有全部 hook 脚本 + ontology ──
|
|
1237
|
-
// commit-msg .cjs + ontology(ensureSpecCommitMsgScript 已处理)
|
|
1238
1181
|
ensureSpecCommitMsgScript(specRoot, pkgPath);
|
|
1239
|
-
|
|
1240
|
-
const
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
fs.mkdirSync(specHooksDir, { recursive: true });
|
|
1244
|
-
}
|
|
1245
|
-
// 复制全部 .cjs 脚本 + hooks.config 配置文件
|
|
1246
|
-
const hookScripts = fs.existsSync(gitHooksTemplateDir)
|
|
1247
|
-
? fs.readdirSync(gitHooksTemplateDir).filter(f => f.endsWith('.cjs') || f === 'hooks.config')
|
|
1248
|
-
: [];
|
|
1249
|
-
for (const script of hookScripts) {
|
|
1250
|
-
const src = path.join(gitHooksTemplateDir, script);
|
|
1251
|
-
const dst = path.join(specHooksDir, script);
|
|
1252
|
-
// 始终覆盖,与 shell hook 的 marker 覆盖策略保持一致
|
|
1253
|
-
fs.copyFileSync(src, dst);
|
|
1254
|
-
}
|
|
1255
|
-
console.log(` ✓ 确保 spec 仓 skywalk-sdd/git-hooks/ 脚本齐全(${hookScripts.length} 个文件)`);
|
|
1256
|
-
|
|
1257
|
-
// ── 2. 安装三个 shell hooks 到代码仓 .git/hooks/ ──
|
|
1258
|
-
// 模板 hook 运行时自动定位 spec 仓的 .cjs 脚本
|
|
1259
|
-
installSddShellHook(cwd, 'commit-msg', pkgPath);
|
|
1260
|
-
installSddShellHook(cwd, 'pre-commit', pkgPath);
|
|
1261
|
-
installSddShellHook(cwd, 'pre-push', pkgPath);
|
|
1182
|
+
const configFile = require('./hook-install.js').deployRuntime(specRoot, pkgPath);
|
|
1183
|
+
for (const name of ['commit-msg', 'pre-commit', 'pre-push']) {
|
|
1184
|
+
installSddShellHook(cwd, name, pkgPath, configFile);
|
|
1185
|
+
}
|
|
1262
1186
|
|
|
1263
1187
|
return { ok: true };
|
|
1264
1188
|
}
|
|
@@ -1285,64 +1209,21 @@ function ensureSpecCommitMsgScript(specRepoRoot, pkgPath = getPackagePath()) {
|
|
|
1285
1209
|
return { ok: true, scriptPath, deployed: true };
|
|
1286
1210
|
}
|
|
1287
1211
|
|
|
1288
|
-
/**
|
|
1289
|
-
* 部署 Git hooks / CI 兜底模板
|
|
1290
|
-
* 这些脚本只做质量门禁和 CI 补充记录,不替代 OPSX 主采集链路。
|
|
1291
|
-
*
|
|
1292
|
-
* @param {string} [targetCwd]
|
|
1293
|
-
* @param {{ mode?: 'full'|'commit-msg-only' }} [options]
|
|
1294
|
-
* full: spec 包裹包(pre-commit/pre-push/commit-msg + CI)
|
|
1295
|
-
* commit-msg-only: 仅安装 commit-msg hook(跳过 pre-commit/pre-push)
|
|
1296
|
-
*/
|
|
1212
|
+
/** 部署验证运行时与轻量 Hook;不自动启用 CI。options 为旧调用兼容保留。 */
|
|
1297
1213
|
function deployQualityGateTemplates(targetCwd = process.cwd(), options = {}) {
|
|
1298
1214
|
const pkgPath = getPackagePath();
|
|
1299
1215
|
const cwd = path.resolve(targetCwd);
|
|
1300
|
-
const
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
return false;
|
|
1305
|
-
}
|
|
1306
|
-
console.log(`🧰 正在部署 SDD Git hooks(${mode}): ${cwd}`);
|
|
1307
|
-
const dataDir = path.join(cwd, 'skywalk-sdd');
|
|
1308
|
-
const gitHooksTemplateDir = path.join(pkgPath, 'templates', 'git-hooks');
|
|
1309
|
-
const ciTemplateDir = path.join(pkgPath, 'templates', 'ci');
|
|
1310
|
-
const targetGitHooksDir = path.join(dataDir, 'git-hooks');
|
|
1311
|
-
const targetCiDir = path.join(dataDir, 'ci');
|
|
1312
|
-
|
|
1313
|
-
if (fs.existsSync(gitHooksTemplateDir)) {
|
|
1314
|
-
// 复制 .cjs 脚本 + hooks.config 到 skywalk-sdd/git-hooks/
|
|
1315
|
-
// shell hook 模板(commit-msg/pre-commit/pre-push)不落此目录
|
|
1316
|
-
if (!fs.existsSync(targetGitHooksDir)) {
|
|
1317
|
-
fs.mkdirSync(targetGitHooksDir, { recursive: true });
|
|
1318
|
-
}
|
|
1319
|
-
for (const file of fs.readdirSync(gitHooksTemplateDir)) {
|
|
1320
|
-
if (file.endsWith('.cjs') || file === 'hooks.config') {
|
|
1321
|
-
fs.copyFileSync(path.join(gitHooksTemplateDir, file), path.join(targetGitHooksDir, file));
|
|
1322
|
-
}
|
|
1323
|
-
}
|
|
1324
|
-
console.log(' ✓ 部署 skywalk-sdd/git-hooks/(.cjs 脚本 + hooks.config)');
|
|
1325
|
-
} else {
|
|
1326
|
-
console.log(` ⚠️ Git hooks 模板缺失: ${gitHooksTemplateDir}`);
|
|
1327
|
-
}
|
|
1328
|
-
|
|
1329
|
-
if (mode === 'full' && fs.existsSync(ciTemplateDir)) {
|
|
1330
|
-
copyDir(ciTemplateDir, targetCiDir);
|
|
1331
|
-
console.log(' ✓ 部署 skywalk-sdd/ci/');
|
|
1216
|
+
const configFile = require('./hook-install.js').deployRuntime(cwd, pkgPath);
|
|
1217
|
+
if (!workspaceLayout.isGitRepo(cwd)) return false;
|
|
1218
|
+
for (const name of ['commit-msg', 'pre-commit', 'pre-push']) {
|
|
1219
|
+
installSddShellHook(cwd, name, pkgPath, configFile);
|
|
1332
1220
|
}
|
|
1333
|
-
|
|
1334
|
-
if (mode === 'full') {
|
|
1335
|
-
installSddShellHook(cwd, 'pre-commit', pkgPath);
|
|
1336
|
-
installSddShellHook(cwd, 'pre-push', pkgPath);
|
|
1337
|
-
}
|
|
1338
|
-
installSddShellHook(cwd, 'commit-msg', pkgPath);
|
|
1339
|
-
|
|
1340
|
-
console.log('✅ Git hooks 已就绪');
|
|
1221
|
+
// CI is an explicit team integration; installing SDD does not enable a merge gate.
|
|
1341
1222
|
return true;
|
|
1342
1223
|
}
|
|
1343
1224
|
|
|
1344
1225
|
/**
|
|
1345
|
-
* 代码仓轻量接入:写 .sdd.yaml + sdd.specPath +
|
|
1226
|
+
* 代码仓轻量接入:写 .sdd.yaml + sdd.specPath + 轻量 commit-msg。
|
|
1346
1227
|
* 代码仓不落 skywalk-sdd/,shell hook 远程引用 spec 仓 skywalk-sdd/git-hooks/ 下的 .cjs 脚本。
|
|
1347
1228
|
*/
|
|
1348
1229
|
function attachCodeRepoLite(codeRepoRoot, specRepoRoot, options = {}) {
|
|
@@ -1357,7 +1238,7 @@ function attachCodeRepoLite(codeRepoRoot, specRepoRoot, options = {}) {
|
|
|
1357
1238
|
return { ok: false, repo: label, message: `spec 路径不是 Git 仓库: ${specRoot}` };
|
|
1358
1239
|
}
|
|
1359
1240
|
|
|
1360
|
-
console.log(`🔗
|
|
1241
|
+
console.log(`🔗 轻量接入代码仓(本地关联 Hook): ${label}`);
|
|
1361
1242
|
|
|
1362
1243
|
const remote = sddConfig.gitRemoteUrl(specRoot) || 'git@gitlab.example.com:biz/xxx-sdd-specs.git';
|
|
1363
1244
|
const sddYamlPath = path.join(cwd, '.sdd.yaml');
|
|
@@ -1383,7 +1264,7 @@ function attachCodeRepoLite(codeRepoRoot, specRepoRoot, options = {}) {
|
|
|
1383
1264
|
}
|
|
1384
1265
|
console.log(` ✓ sdd.specPath = ${linked.path}`);
|
|
1385
1266
|
|
|
1386
|
-
//
|
|
1267
|
+
// 部署轻量关联 Hook,并迁移可识别的旧门禁
|
|
1387
1268
|
deployCodeRepoHooks(cwd, specRoot, options);
|
|
1388
1269
|
|
|
1389
1270
|
return { ok: true, repo: label, path: cwd, specPath: linked.path };
|
|
@@ -1463,7 +1344,13 @@ function deployMonoSddYaml(cwd = process.cwd(), options = {}) {
|
|
|
1463
1344
|
: '';
|
|
1464
1345
|
|
|
1465
1346
|
if (fs.existsSync(target)) {
|
|
1466
|
-
|
|
1347
|
+
let existing = fs.readFileSync(target, 'utf8');
|
|
1348
|
+
const placeholder = fs.readFileSync(path.join(getPackagePath(), 'templates/dot-sdd.yaml'), 'utf8');
|
|
1349
|
+
if (existing === placeholder && specRel) {
|
|
1350
|
+
fs.copyFileSync(target, target + '.pre-mono.bak');
|
|
1351
|
+
existing = `version: 1\nlayout: mono\n`;
|
|
1352
|
+
fs.writeFileSync(target, existing, 'utf8');
|
|
1353
|
+
}
|
|
1467
1354
|
if (/^layout:\s*mono\s*$/m.test(existing)) {
|
|
1468
1355
|
if (specRel && !new RegExp(`^spec_path:\\s*${specRel.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}\\s*$`, 'm').test(existing)) {
|
|
1469
1356
|
// 已有 mono 但缺/错 spec_path:补写相对路径
|
|
@@ -1920,7 +1807,7 @@ async function main() {
|
|
|
1920
1807
|
removeOuterSkywalk(workspaceRoot);
|
|
1921
1808
|
|
|
1922
1809
|
deployMonoSddYaml(workspaceRoot, { specPathRelative: specPackage.name });
|
|
1923
|
-
//
|
|
1810
|
+
// 部署轻量关联 Hook,并迁移可识别的旧门禁
|
|
1924
1811
|
// 单仓 mono:hook 通过 .sdd-spec-root 定位包裹包的 skywalk-sdd/git-hooks/
|
|
1925
1812
|
deployCodeRepoHooks(workspaceRoot, specPackage.abs);
|
|
1926
1813
|
const linked = sddConfig.setSpecPath(workspaceRoot, specPackage.abs, {
|
|
@@ -1940,7 +1827,7 @@ async function main() {
|
|
|
1940
1827
|
});
|
|
1941
1828
|
writeSddSpecRootHint(workspaceRoot, specPackage.abs);
|
|
1942
1829
|
removeOuterSkywalk(workspaceRoot);
|
|
1943
|
-
|
|
1830
|
+
deployMonoSddYaml(workspaceRoot, { specPathRelative: specPackage.name });
|
|
1944
1831
|
}
|
|
1945
1832
|
}
|
|
1946
1833
|
} else if (!skipOpenspec) {
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
{
|
|
2
|
+
"pre-commit": [
|
|
3
|
+
"497f012da027df5c26af3c501126e9a1c26276b038ee3b1edaaf5ab4401aa95c",
|
|
4
|
+
"7322d9aecafb497a5f40171a6bd7daeaadd7d2056b4a253cb0ee1a128faea36e",
|
|
5
|
+
"8fcdd74a3d2afaa823f9038c477c9dfd6fe53e2bccbe48cecd01535794862e4a",
|
|
6
|
+
"c8546c6c96adc3ecc32a15a689c59189543c2454f47998c46fbd5a0bf810cf30",
|
|
7
|
+
"e3b6776af51df76ff04b528152d0049a6c45d94d96a19bd2bc5ca2cc9786b22c"
|
|
8
|
+
],
|
|
9
|
+
"pre-push": [
|
|
10
|
+
"405debb5e6f9186e95b727e6013b06ef3e06c598b6d2b96678d34554d3530e8c",
|
|
11
|
+
"461abd16663757c5a092103ac2ecae8453891c0c817d1c763369e0cdd0a8ec7b",
|
|
12
|
+
"80f710ff647ce8b216fb1c081fb3e22f6ee8be25847b6d02921668ba83fb1965",
|
|
13
|
+
"d49eb8794fde4b0d7847a876cc7083d161b290a44990ff8a036c2ebb8a00ec21",
|
|
14
|
+
"e6e125cbf9d7424c01b249ce03197cef9e1923ed2974a53656084488a0de60c4",
|
|
15
|
+
"ebf628481ae93ac0c4c6649b204449f6825291dc6b6550a71563bebec1f3ad92"
|
|
16
|
+
],
|
|
17
|
+
"commit-msg": [
|
|
18
|
+
"15083d5165bff965ab2feba9a55dfedcecd54cb577c06fd11f4d1b9672d978b6",
|
|
19
|
+
"3b0ee15f5a126d159e14c5a3681cb7820cbf9c5e0c134ee6812053bc34330b85",
|
|
20
|
+
"6db7adbf17905bf14915d7e57a3c5ac46030ee3282fea5d5b211c80065406d48",
|
|
21
|
+
"9c5a86761b36d71b655f804a822eff675a244175799736c803e1f7379c638609",
|
|
22
|
+
"fc390776720393f100712fa02d5e143c2927fbb0d605a4a0242824ff7ea84574"
|
|
23
|
+
]
|
|
24
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "kld-sdd",
|
|
3
|
-
"version": "2.7.8-
|
|
3
|
+
"version": "2.7.8-3",
|
|
4
4
|
"description": "KLD SDD OpenSpec 项目初始化工具 - 一键部署 SDD skills",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"bin": {
|
|
@@ -9,7 +9,7 @@
|
|
|
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
|
+
"test": "node --test test/delivery-hooks.test.cjs test/execution-friction.test.cjs 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"
|
|
13
13
|
},
|
|
14
14
|
"keywords": [
|
|
15
15
|
"kld",
|