@skillfm/local 2.0.3 → 2.0.5
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 +7 -2
- package/dist/harness/kernels/deny-pipeline.d.ts +21 -0
- package/dist/harness/kernels/deny-pipeline.d.ts.map +1 -0
- package/dist/harness/kernels/deny-pipeline.js +225 -0
- package/dist/harness/kernels/deny-pipeline.js.map +1 -0
- package/dist/harness/kernels/hook-file.d.ts +16 -0
- package/dist/harness/kernels/hook-file.d.ts.map +1 -0
- package/dist/harness/kernels/hook-file.js +229 -0
- package/dist/harness/kernels/hook-file.js.map +1 -0
- package/dist/harness/kernels/json-merge.d.ts +45 -0
- package/dist/harness/kernels/json-merge.d.ts.map +1 -0
- package/dist/harness/kernels/json-merge.js +123 -0
- package/dist/harness/kernels/json-merge.js.map +1 -0
- package/dist/harness/kernels/mcp-only.d.ts +23 -0
- package/dist/harness/kernels/mcp-only.d.ts.map +1 -0
- package/dist/harness/kernels/mcp-only.js +215 -0
- package/dist/harness/kernels/mcp-only.js.map +1 -0
- package/dist/harness/kernels/protocol-reject.d.ts +18 -0
- package/dist/harness/kernels/protocol-reject.d.ts.map +1 -0
- package/dist/harness/kernels/protocol-reject.js +117 -0
- package/dist/harness/kernels/protocol-reject.js.map +1 -0
- package/dist/harness/kernels/registry.d.ts +56 -0
- package/dist/harness/kernels/registry.d.ts.map +1 -0
- package/dist/harness/kernels/registry.js +139 -0
- package/dist/harness/kernels/registry.js.map +1 -0
- package/dist/harness/kernels/types.d.ts +86 -0
- package/dist/harness/kernels/types.d.ts.map +1 -0
- package/dist/harness/kernels/types.js +21 -0
- package/dist/harness/kernels/types.js.map +1 -0
- package/dist/mcp-output/builder.d.ts +24 -0
- package/dist/mcp-output/builder.d.ts.map +1 -0
- package/dist/mcp-output/builder.js +99 -0
- package/dist/mcp-output/builder.js.map +1 -0
- package/dist/mcp-output/deny-review.d.ts +64 -0
- package/dist/mcp-output/deny-review.d.ts.map +1 -0
- package/dist/mcp-output/deny-review.js +212 -0
- package/dist/mcp-output/deny-review.js.map +1 -0
- package/dist/mcp-output/types.d.ts +64 -0
- package/dist/mcp-output/types.d.ts.map +1 -0
- package/dist/mcp-output/types.js +21 -0
- package/dist/mcp-output/types.js.map +1 -0
- package/dist/skill-md/template.d.ts +30 -0
- package/dist/skill-md/template.d.ts.map +1 -0
- package/dist/skill-md/template.js +194 -0
- package/dist/skill-md/template.js.map +1 -0
- package/dist/skill-md/writer.d.ts +30 -0
- package/dist/skill-md/writer.d.ts.map +1 -0
- package/dist/skill-md/writer.js +129 -0
- package/dist/skill-md/writer.js.map +1 -0
- package/package.json +1 -2
package/README.md
CHANGED
|
@@ -11,9 +11,14 @@ This package sidesteps the problem: instead of asking the agent to install a new
|
|
|
11
11
|
## Install and run
|
|
12
12
|
|
|
13
13
|
```bash
|
|
14
|
-
npx
|
|
14
|
+
npx --yes --package @skillfm/local@latest skillfm-local start
|
|
15
15
|
```
|
|
16
16
|
|
|
17
|
+
> Why the verbose `--package` form? This npm package ships two CLI entry points
|
|
18
|
+
> (`skillfm-local` + `skillfm-guard` for hook-based harness enforcement). Since
|
|
19
|
+
> the package name's tail `local` is a shell reserved word, we cannot expose it
|
|
20
|
+
> as a bin shortcut. The above `--package` form tells npx exactly which bin to run.
|
|
21
|
+
|
|
17
22
|
Output is a single line of JSON the agent can parse:
|
|
18
23
|
|
|
19
24
|
```json
|
|
@@ -57,7 +62,7 @@ On successful `/activate/verify`, the brain_key is persisted to `~/.skillfm/conf
|
|
|
57
62
|
|
|
58
63
|
## Agent-native flow in plain words
|
|
59
64
|
|
|
60
|
-
1. Agent spawns the sidecar: `npx
|
|
65
|
+
1. Agent spawns the sidecar: `npx --yes --package @skillfm/local@latest skillfm-local start &`
|
|
61
66
|
2. Agent reads `~/.skillfm/local.json` to get the URL.
|
|
62
67
|
3. Agent asks the user for their email and POSTs `/activate/request`.
|
|
63
68
|
4. The backend emails a 6-digit code. Agent asks the user for the code.
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* BSO M9 v0.3 — KernelB: deny-pipeline
|
|
3
|
+
*
|
|
4
|
+
* Refs:
|
|
5
|
+
* - docs/prd/PRD-BSO-M9-HARNESS-HOOKS.md §3.4.2 KernelB
|
|
6
|
+
* - docs/research/OPENCLAW-DEEP-CAPABILITY-2026-04-19.md
|
|
7
|
+
*
|
|
8
|
+
* 覆盖:OpenClaw(唯一)
|
|
9
|
+
*
|
|
10
|
+
* OpenClaw `before_tool_call` Issue #5943/#60943 OPEN,PreToolUse 不可用。
|
|
11
|
+
* 退化方案三件套:
|
|
12
|
+
* 1. MCP stdio 注册:所有 SkillFM tool 调用必经 sidecar bridge
|
|
13
|
+
* 2. tools.deny 黑名单:禁用 OpenClaw 内置 Write/Edit/Bash,强制走 SkillFM MCP
|
|
14
|
+
* 3. agent_bootstrap.sh lifecycle hook:session 启动校验 sidecar
|
|
15
|
+
* + AGENTS.md priming(已由 priming.ts + writers.writePrimingBlock 实现)
|
|
16
|
+
*
|
|
17
|
+
* 等 OpenClaw 接通 PreToolUse 后 → 切到 hook-file kernel;本 kernel 退役。
|
|
18
|
+
*/
|
|
19
|
+
import type { KernelAdapter } from './types.js';
|
|
20
|
+
export declare const denyPipelineAdapter: KernelAdapter;
|
|
21
|
+
//# sourceMappingURL=deny-pipeline.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"deny-pipeline.d.ts","sourceRoot":"","sources":["../../../src/harness/kernels/deny-pipeline.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAWH,OAAO,KAAK,EAMV,aAAa,EACd,MAAM,YAAY,CAAC;AAgHpB,eAAO,MAAM,mBAAmB,EAAE,aAyHjC,CAAC"}
|
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* BSO M9 v0.3 — KernelB: deny-pipeline
|
|
3
|
+
*
|
|
4
|
+
* Refs:
|
|
5
|
+
* - docs/prd/PRD-BSO-M9-HARNESS-HOOKS.md §3.4.2 KernelB
|
|
6
|
+
* - docs/research/OPENCLAW-DEEP-CAPABILITY-2026-04-19.md
|
|
7
|
+
*
|
|
8
|
+
* 覆盖:OpenClaw(唯一)
|
|
9
|
+
*
|
|
10
|
+
* OpenClaw `before_tool_call` Issue #5943/#60943 OPEN,PreToolUse 不可用。
|
|
11
|
+
* 退化方案三件套:
|
|
12
|
+
* 1. MCP stdio 注册:所有 SkillFM tool 调用必经 sidecar bridge
|
|
13
|
+
* 2. tools.deny 黑名单:禁用 OpenClaw 内置 Write/Edit/Bash,强制走 SkillFM MCP
|
|
14
|
+
* 3. agent_bootstrap.sh lifecycle hook:session 启动校验 sidecar
|
|
15
|
+
* + AGENTS.md priming(已由 priming.ts + writers.writePrimingBlock 实现)
|
|
16
|
+
*
|
|
17
|
+
* 等 OpenClaw 接通 PreToolUse 后 → 切到 hook-file kernel;本 kernel 退役。
|
|
18
|
+
*/
|
|
19
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync, chmodSync } from 'node:fs';
|
|
20
|
+
import { dirname, join } from 'node:path';
|
|
21
|
+
import { homedir } from 'node:os';
|
|
22
|
+
import { SKILLFM_GUARD_COMMAND } from '../templates.js';
|
|
23
|
+
import { arrayHasMarker, ensureObjectPath, mergeJsonFile, } from './json-merge.js';
|
|
24
|
+
// ============================================================================
|
|
25
|
+
// 路径
|
|
26
|
+
// ============================================================================
|
|
27
|
+
const OPENCLAW_HOME = (home) => join(home, '.openclaw');
|
|
28
|
+
const OPENCLAW_GLOBAL_CONFIG = (home) => join(OPENCLAW_HOME(home), 'openclaw.json');
|
|
29
|
+
const OPENCLAW_WORKSPACE_CONFIG = (home) => join(OPENCLAW_HOME(home), 'workspace', 'openclaw.json');
|
|
30
|
+
const OPENCLAW_BOOTSTRAP_HOOK = (home) => join(OPENCLAW_HOME(home), 'hooks', 'agent_bootstrap.sh');
|
|
31
|
+
// ============================================================================
|
|
32
|
+
// MCP server registration(OpenClaw 只支持 stdio MCP)
|
|
33
|
+
// ============================================================================
|
|
34
|
+
const SKILLFM_MCP_BRIDGE_BIN = 'skillfm-mcp-bridge';
|
|
35
|
+
function buildMcpRegistrationPatch(filePath) {
|
|
36
|
+
return {
|
|
37
|
+
filePath,
|
|
38
|
+
emptyJson: () => ({}),
|
|
39
|
+
isAlreadyRegistered: (data) => {
|
|
40
|
+
const mcp = data?.mcp;
|
|
41
|
+
const direct = data?.mcpServers;
|
|
42
|
+
return Boolean(mcp?.servers?.skillfm) || Boolean(direct?.skillfm);
|
|
43
|
+
},
|
|
44
|
+
mergePatch: (data) => {
|
|
45
|
+
// OpenClaw 接受两种 schema(mcp.servers 或顶层 mcpServers);优先 mcp.servers
|
|
46
|
+
const mcp = ensureObjectPath(data, 'mcp');
|
|
47
|
+
const servers = ensureObjectPath(mcp, 'servers');
|
|
48
|
+
servers.skillfm = {
|
|
49
|
+
type: 'stdio',
|
|
50
|
+
command: SKILLFM_MCP_BRIDGE_BIN,
|
|
51
|
+
args: [],
|
|
52
|
+
};
|
|
53
|
+
},
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
// ============================================================================
|
|
57
|
+
// tools.deny 黑名单(写到 workspace 级 openclaw.json)
|
|
58
|
+
// ============================================================================
|
|
59
|
+
const DEFAULT_DENY_TOOLS = ['Write', 'Edit', 'Bash'];
|
|
60
|
+
function buildDenyPipelinePatch(filePath) {
|
|
61
|
+
return {
|
|
62
|
+
filePath,
|
|
63
|
+
emptyJson: () => ({}),
|
|
64
|
+
isAlreadyRegistered: (data) => {
|
|
65
|
+
const tools = data?.tools;
|
|
66
|
+
return arrayHasMarker(tools?.deny, 'Write');
|
|
67
|
+
},
|
|
68
|
+
mergePatch: (data) => {
|
|
69
|
+
const tools = ensureObjectPath(data, 'tools');
|
|
70
|
+
const existing = Array.isArray(tools.deny) ? tools.deny : [];
|
|
71
|
+
const merged = Array.from(new Set([...existing, ...DEFAULT_DENY_TOOLS]));
|
|
72
|
+
tools.deny = merged;
|
|
73
|
+
},
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
// ============================================================================
|
|
77
|
+
// agent_bootstrap.sh lifecycle hook
|
|
78
|
+
// ============================================================================
|
|
79
|
+
const BOOTSTRAP_SCRIPT = `#!/usr/bin/env bash
|
|
80
|
+
# SkillFM M9 — OpenClaw lifecycle bootstrap hook
|
|
81
|
+
# Managed by @skillfm/local. Edit via \`skillfm init\`.
|
|
82
|
+
#
|
|
83
|
+
# 在 OpenClaw session 启动时校验 sidecar 是否在跑。
|
|
84
|
+
# 失败 → exit 1 阻断 session 启动(强制用户先 \`skillfm start\`)。
|
|
85
|
+
|
|
86
|
+
set -euo pipefail
|
|
87
|
+
|
|
88
|
+
if ! command -v ${SKILLFM_GUARD_COMMAND} >/dev/null 2>&1; then
|
|
89
|
+
echo "⚠️ [skillfm-bootstrap] ${SKILLFM_GUARD_COMMAND} 未安装,跳过校验" >&2
|
|
90
|
+
exit 0
|
|
91
|
+
fi
|
|
92
|
+
|
|
93
|
+
if ! ${SKILLFM_GUARD_COMMAND} session-start --harness=openclaw; then
|
|
94
|
+
echo "⛔ [skillfm-bootstrap] sidecar 未运行;请先执行 \\\`skillfm start\\\`" >&2
|
|
95
|
+
exit 1
|
|
96
|
+
fi
|
|
97
|
+
`;
|
|
98
|
+
function writeBootstrapHook(filePath) {
|
|
99
|
+
if (existsSync(filePath)) {
|
|
100
|
+
const existing = readFileSync(filePath, 'utf-8');
|
|
101
|
+
if (existing.includes('SkillFM M9 — OpenClaw lifecycle bootstrap')) {
|
|
102
|
+
return { created: false, path: filePath };
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
mkdirSync(dirname(filePath), { recursive: true });
|
|
106
|
+
writeFileSync(filePath, BOOTSTRAP_SCRIPT, { encoding: 'utf-8', mode: 0o755 });
|
|
107
|
+
try {
|
|
108
|
+
chmodSync(filePath, 0o755);
|
|
109
|
+
}
|
|
110
|
+
catch {
|
|
111
|
+
// 部分 FS 不支持,忽略
|
|
112
|
+
}
|
|
113
|
+
return { created: true, path: filePath };
|
|
114
|
+
}
|
|
115
|
+
// ============================================================================
|
|
116
|
+
// Adapter
|
|
117
|
+
// ============================================================================
|
|
118
|
+
const SUPPORTED = ['openclaw'];
|
|
119
|
+
export const denyPipelineAdapter = {
|
|
120
|
+
kind: 'deny-pipeline',
|
|
121
|
+
supportedHosts: SUPPORTED,
|
|
122
|
+
matches(host) {
|
|
123
|
+
return host.host === 'openclaw';
|
|
124
|
+
},
|
|
125
|
+
async install(ctx) {
|
|
126
|
+
const home = homedir();
|
|
127
|
+
const files = [];
|
|
128
|
+
const warnings = [];
|
|
129
|
+
// 1. global openclaw.json — 注册 SkillFM MCP server
|
|
130
|
+
files.push(mergeJsonFile({
|
|
131
|
+
...buildMcpRegistrationPatch(OPENCLAW_GLOBAL_CONFIG(home)),
|
|
132
|
+
dryRun: !ctx.apply,
|
|
133
|
+
}));
|
|
134
|
+
// 2. workspace openclaw.json — 写 tools.deny 黑名单
|
|
135
|
+
files.push(mergeJsonFile({
|
|
136
|
+
...buildDenyPipelinePatch(OPENCLAW_WORKSPACE_CONFIG(home)),
|
|
137
|
+
dryRun: !ctx.apply,
|
|
138
|
+
}));
|
|
139
|
+
// 3. agent_bootstrap.sh
|
|
140
|
+
if (ctx.apply) {
|
|
141
|
+
const r = writeBootstrapHook(OPENCLAW_BOOTSTRAP_HOOK(home));
|
|
142
|
+
files.push({
|
|
143
|
+
path: r.path,
|
|
144
|
+
action: r.created ? 'created' : 'already-registered',
|
|
145
|
+
});
|
|
146
|
+
}
|
|
147
|
+
else {
|
|
148
|
+
files.push({
|
|
149
|
+
path: OPENCLAW_BOOTSTRAP_HOOK(home),
|
|
150
|
+
action: 'noop',
|
|
151
|
+
details: 'dry-run',
|
|
152
|
+
});
|
|
153
|
+
}
|
|
154
|
+
warnings.push('OpenClaw PreToolUse 未接通(Issue #5943/#60943),强制力等价 deny-pipeline + Layer 2 兜底');
|
|
155
|
+
return {
|
|
156
|
+
kernel: 'deny-pipeline',
|
|
157
|
+
host: 'openclaw',
|
|
158
|
+
files,
|
|
159
|
+
warnings,
|
|
160
|
+
};
|
|
161
|
+
},
|
|
162
|
+
async uninstall(ctx) {
|
|
163
|
+
return {
|
|
164
|
+
kernel: 'deny-pipeline',
|
|
165
|
+
host: 'openclaw',
|
|
166
|
+
files: [],
|
|
167
|
+
warnings: [
|
|
168
|
+
'完整 uninstall 未实现;请从 ~/.skillfm/backups/<最近 ts>/ 恢复 openclaw.json,并删除 ~/.openclaw/hooks/agent_bootstrap.sh',
|
|
169
|
+
],
|
|
170
|
+
};
|
|
171
|
+
},
|
|
172
|
+
async diagnose(_ctx) {
|
|
173
|
+
const home = homedir();
|
|
174
|
+
const checks = [];
|
|
175
|
+
// MCP server registered?
|
|
176
|
+
const globalCfg = OPENCLAW_GLOBAL_CONFIG(home);
|
|
177
|
+
let mcpRegistered = false;
|
|
178
|
+
if (existsSync(globalCfg)) {
|
|
179
|
+
try {
|
|
180
|
+
const raw = readFileSync(globalCfg, 'utf-8');
|
|
181
|
+
mcpRegistered = raw.includes('skillfm-mcp-bridge') || raw.includes('skillfm');
|
|
182
|
+
}
|
|
183
|
+
catch {
|
|
184
|
+
// ignore
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
checks.push({
|
|
188
|
+
name: 'mcp-server-registered',
|
|
189
|
+
ok: mcpRegistered,
|
|
190
|
+
detail: globalCfg,
|
|
191
|
+
});
|
|
192
|
+
// workspace tools.deny present?
|
|
193
|
+
const wsCfg = OPENCLAW_WORKSPACE_CONFIG(home);
|
|
194
|
+
let denyPresent = false;
|
|
195
|
+
if (existsSync(wsCfg)) {
|
|
196
|
+
try {
|
|
197
|
+
const raw = readFileSync(wsCfg, 'utf-8');
|
|
198
|
+
denyPresent = raw.includes('"deny"') && raw.includes('Write');
|
|
199
|
+
}
|
|
200
|
+
catch {
|
|
201
|
+
// ignore
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
checks.push({
|
|
205
|
+
name: 'tools-deny-blacklist',
|
|
206
|
+
ok: denyPresent,
|
|
207
|
+
detail: wsCfg,
|
|
208
|
+
});
|
|
209
|
+
// bootstrap hook present?
|
|
210
|
+
const bootstrap = OPENCLAW_BOOTSTRAP_HOOK(home);
|
|
211
|
+
checks.push({
|
|
212
|
+
name: 'agent-bootstrap-hook',
|
|
213
|
+
ok: existsSync(bootstrap),
|
|
214
|
+
detail: bootstrap,
|
|
215
|
+
});
|
|
216
|
+
const allOk = checks.every((c) => c.ok);
|
|
217
|
+
return {
|
|
218
|
+
kernel: 'deny-pipeline',
|
|
219
|
+
host: 'openclaw',
|
|
220
|
+
level: allOk ? 'protocol-fallback' : 'absent',
|
|
221
|
+
checks,
|
|
222
|
+
};
|
|
223
|
+
},
|
|
224
|
+
};
|
|
225
|
+
//# sourceMappingURL=deny-pipeline.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"deny-pipeline.js","sourceRoot":"","sources":["../../../src/harness/kernels/deny-pipeline.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAEH,OAAO,EAAE,UAAU,EAAE,SAAS,EAAE,YAAY,EAAE,aAAa,EAAE,SAAS,EAAE,MAAM,SAAS,CAAC;AACxF,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAC1C,OAAO,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AAClC,OAAO,EAAE,qBAAqB,EAAE,MAAM,iBAAiB,CAAC;AACxD,OAAO,EACL,cAAc,EACd,gBAAgB,EAChB,aAAa,GACd,MAAM,iBAAiB,CAAC;AAUzB,+EAA+E;AAC/E,KAAK;AACL,+EAA+E;AAE/E,MAAM,aAAa,GAAG,CAAC,IAAY,EAAE,EAAE,CAAC,IAAI,CAAC,IAAI,EAAE,WAAW,CAAC,CAAC;AAChE,MAAM,sBAAsB,GAAG,CAAC,IAAY,EAAE,EAAE,CAC9C,IAAI,CAAC,aAAa,CAAC,IAAI,CAAC,EAAE,eAAe,CAAC,CAAC;AAC7C,MAAM,yBAAyB,GAAG,CAAC,IAAY,EAAE,EAAE,CACjD,IAAI,CAAC,aAAa,CAAC,IAAI,CAAC,EAAE,WAAW,EAAE,eAAe,CAAC,CAAC;AAC1D,MAAM,uBAAuB,GAAG,CAAC,IAAY,EAAE,EAAE,CAC/C,IAAI,CAAC,aAAa,CAAC,IAAI,CAAC,EAAE,OAAO,EAAE,oBAAoB,CAAC,CAAC;AAE3D,+EAA+E;AAC/E,kDAAkD;AAClD,+EAA+E;AAE/E,MAAM,sBAAsB,GAAG,oBAAoB,CAAC;AAEpD,SAAS,yBAAyB,CAAC,QAAgB;IACjD,OAAO;QACL,QAAQ;QACR,SAAS,EAAE,GAAG,EAAE,CAAC,CAAC,EAAE,CAAC;QACrB,mBAAmB,EAAE,CAAC,IAAI,EAAE,EAAE;YAC5B,MAAM,GAAG,GAAI,IAAwD,EAAE,GAAG,CAAC;YAC3E,MAAM,MAAM,GAAI,IAAiD,EAAE,UAAU,CAAC;YAC9E,OAAO,OAAO,CAAC,GAAG,EAAE,OAAO,EAAE,OAAO,CAAC,IAAI,OAAO,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;QACpE,CAAC;QACD,UAAU,EAAE,CAAC,IAAI,EAAE,EAAE;YACnB,kEAAkE;YAClE,MAAM,GAAG,GAAG,gBAAgB,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC;YAC1C,MAAM,OAAO,GAAG,gBAAgB,CAAC,GAAG,EAAE,SAAS,CAAC,CAAC;YACjD,OAAO,CAAC,OAAO,GAAG;gBAChB,IAAI,EAAE,OAAO;gBACb,OAAO,EAAE,sBAAsB;gBAC/B,IAAI,EAAE,EAAE;aACT,CAAC;QACJ,CAAC;KACF,CAAC;AACJ,CAAC;AAED,+EAA+E;AAC/E,+CAA+C;AAC/C,+EAA+E;AAE/E,MAAM,kBAAkB,GAAG,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,CAAC,CAAC;AAErD,SAAS,sBAAsB,CAAC,QAAgB;IAC9C,OAAO;QACL,QAAQ;QACR,SAAS,EAAE,GAAG,EAAE,CAAC,CAAC,EAAE,CAAC;QACrB,mBAAmB,EAAE,CAAC,IAAI,EAAE,EAAE;YAC5B,MAAM,KAAK,GAAI,IAAuC,EAAE,KAAK,CAAC;YAC9D,OAAO,cAAc,CAAC,KAAK,EAAE,IAAI,EAAE,OAAO,CAAC,CAAC;QAC9C,CAAC;QACD,UAAU,EAAE,CAAC,IAAI,EAAE,EAAE;YACnB,MAAM,KAAK,GAAG,gBAAgB,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;YAC9C,MAAM,QAAQ,GAAG,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,CAAE,KAAK,CAAC,IAAiB,CAAC,CAAC,CAAC,EAAE,CAAC;YAC3E,MAAM,MAAM,GAAG,KAAK,CAAC,IAAI,CAAC,IAAI,GAAG,CAAC,CAAC,GAAG,QAAQ,EAAE,GAAG,kBAAkB,CAAC,CAAC,CAAC,CAAC;YACzE,KAAK,CAAC,IAAI,GAAG,MAAM,CAAC;QACtB,CAAC;KACF,CAAC;AACJ,CAAC;AAED,+EAA+E;AAC/E,oCAAoC;AACpC,+EAA+E;AAE/E,MAAM,gBAAgB,GAAG;;;;;;;;;kBASP,qBAAqB;kCACL,qBAAqB;;;;OAIhD,qBAAqB;;;;CAI3B,CAAC;AAEF,SAAS,kBAAkB,CAAC,QAAgB;IAC1C,IAAI,UAAU,CAAC,QAAQ,CAAC,EAAE,CAAC;QACzB,MAAM,QAAQ,GAAG,YAAY,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC;QACjD,IAAI,QAAQ,CAAC,QAAQ,CAAC,2CAA2C,CAAC,EAAE,CAAC;YACnE,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,IAAI,EAAE,QAAQ,EAAE,CAAC;QAC5C,CAAC;IACH,CAAC;IACD,SAAS,CAAC,OAAO,CAAC,QAAQ,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IAClD,aAAa,CAAC,QAAQ,EAAE,gBAAgB,EAAE,EAAE,QAAQ,EAAE,OAAO,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC;IAC9E,IAAI,CAAC;QACH,SAAS,CAAC,QAAQ,EAAE,KAAK,CAAC,CAAC;IAC7B,CAAC;IAAC,MAAM,CAAC;QACP,eAAe;IACjB,CAAC;IACD,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,QAAQ,EAAE,CAAC;AAC3C,CAAC;AAED,+EAA+E;AAC/E,UAAU;AACV,+EAA+E;AAE/E,MAAM,SAAS,GAAsB,CAAC,UAAU,CAAC,CAAC;AAElD,MAAM,CAAC,MAAM,mBAAmB,GAAkB;IAChD,IAAI,EAAE,eAAe;IACrB,cAAc,EAAE,SAAS;IAEzB,OAAO,CAAC,IAAc;QACpB,OAAO,IAAI,CAAC,IAAI,KAAK,UAAU,CAAC;IAClC,CAAC;IAED,KAAK,CAAC,OAAO,CAAC,GAAmB;QAC/B,MAAM,IAAI,GAAG,OAAO,EAAE,CAAC;QACvB,MAAM,KAAK,GAA2B,EAAE,CAAC;QACzC,MAAM,QAAQ,GAAa,EAAE,CAAC;QAE9B,kDAAkD;QAClD,KAAK,CAAC,IAAI,CACR,aAAa,CAAC;YACZ,GAAG,yBAAyB,CAAC,sBAAsB,CAAC,IAAI,CAAC,CAAC;YAC1D,MAAM,EAAE,CAAC,GAAG,CAAC,KAAK;SACnB,CAAC,CACH,CAAC;QAEF,gDAAgD;QAChD,KAAK,CAAC,IAAI,CACR,aAAa,CAAC;YACZ,GAAG,sBAAsB,CAAC,yBAAyB,CAAC,IAAI,CAAC,CAAC;YAC1D,MAAM,EAAE,CAAC,GAAG,CAAC,KAAK;SACnB,CAAC,CACH,CAAC;QAEF,wBAAwB;QACxB,IAAI,GAAG,CAAC,KAAK,EAAE,CAAC;YACd,MAAM,CAAC,GAAG,kBAAkB,CAAC,uBAAuB,CAAC,IAAI,CAAC,CAAC,CAAC;YAC5D,KAAK,CAAC,IAAI,CAAC;gBACT,IAAI,EAAE,CAAC,CAAC,IAAI;gBACZ,MAAM,EAAE,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,oBAAoB;aACrD,CAAC,CAAC;QACL,CAAC;aAAM,CAAC;YACN,KAAK,CAAC,IAAI,CAAC;gBACT,IAAI,EAAE,uBAAuB,CAAC,IAAI,CAAC;gBACnC,MAAM,EAAE,MAAM;gBACd,OAAO,EAAE,SAAS;aACnB,CAAC,CAAC;QACL,CAAC;QAED,QAAQ,CAAC,IAAI,CACX,8EAA8E,CAC/E,CAAC;QAEF,OAAO;YACL,MAAM,EAAE,eAAe;YACvB,IAAI,EAAE,UAAU;YAChB,KAAK;YACL,QAAQ;SACT,CAAC;IACJ,CAAC;IAED,KAAK,CAAC,SAAS,CAAC,GAAmB;QACjC,OAAO;YACL,MAAM,EAAE,eAAe;YACvB,IAAI,EAAE,UAAU;YAChB,KAAK,EAAE,EAAE;YACT,QAAQ,EAAE;gBACR,2GAA2G;aAC5G;SACF,CAAC;IACJ,CAAC;IAED,KAAK,CAAC,QAAQ,CAAC,IAAoB;QACjC,MAAM,IAAI,GAAG,OAAO,EAAE,CAAC;QACvB,MAAM,MAAM,GAA6B,EAAE,CAAC;QAE5C,yBAAyB;QACzB,MAAM,SAAS,GAAG,sBAAsB,CAAC,IAAI,CAAC,CAAC;QAC/C,IAAI,aAAa,GAAG,KAAK,CAAC;QAC1B,IAAI,UAAU,CAAC,SAAS,CAAC,EAAE,CAAC;YAC1B,IAAI,CAAC;gBACH,MAAM,GAAG,GAAG,YAAY,CAAC,SAAS,EAAE,OAAO,CAAC,CAAC;gBAC7C,aAAa,GAAG,GAAG,CAAC,QAAQ,CAAC,oBAAoB,CAAC,IAAI,GAAG,CAAC,QAAQ,CAAC,SAAS,CAAC,CAAC;YAChF,CAAC;YAAC,MAAM,CAAC;gBACP,SAAS;YACX,CAAC;QACH,CAAC;QACD,MAAM,CAAC,IAAI,CAAC;YACV,IAAI,EAAE,uBAAuB;YAC7B,EAAE,EAAE,aAAa;YACjB,MAAM,EAAE,SAAS;SAClB,CAAC,CAAC;QAEH,gCAAgC;QAChC,MAAM,KAAK,GAAG,yBAAyB,CAAC,IAAI,CAAC,CAAC;QAC9C,IAAI,WAAW,GAAG,KAAK,CAAC;QACxB,IAAI,UAAU,CAAC,KAAK,CAAC,EAAE,CAAC;YACtB,IAAI,CAAC;gBACH,MAAM,GAAG,GAAG,YAAY,CAAC,KAAK,EAAE,OAAO,CAAC,CAAC;gBACzC,WAAW,GAAG,GAAG,CAAC,QAAQ,CAAC,QAAQ,CAAC,IAAI,GAAG,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC;YAChE,CAAC;YAAC,MAAM,CAAC;gBACP,SAAS;YACX,CAAC;QACH,CAAC;QACD,MAAM,CAAC,IAAI,CAAC;YACV,IAAI,EAAE,sBAAsB;YAC5B,EAAE,EAAE,WAAW;YACf,MAAM,EAAE,KAAK;SACd,CAAC,CAAC;QAEH,0BAA0B;QAC1B,MAAM,SAAS,GAAG,uBAAuB,CAAC,IAAI,CAAC,CAAC;QAChD,MAAM,CAAC,IAAI,CAAC;YACV,IAAI,EAAE,sBAAsB;YAC5B,EAAE,EAAE,UAAU,CAAC,SAAS,CAAC;YACzB,MAAM,EAAE,SAAS;SAClB,CAAC,CAAC;QAEH,MAAM,KAAK,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;QACxC,OAAO;YACL,MAAM,EAAE,eAAe;YACvB,IAAI,EAAE,UAAU;YAChB,KAAK,EAAE,KAAK,CAAC,CAAC,CAAC,mBAAmB,CAAC,CAAC,CAAC,QAAQ;YAC7C,MAAM;SACP,CAAC;IACJ,CAAC;CACF,CAAC"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* BSO M9 v0.3 — KernelA: hook-file
|
|
3
|
+
*
|
|
4
|
+
* Refs:
|
|
5
|
+
* - docs/prd/PRD-BSO-M9-HARNESS-HOOKS.md §3.4.2 KernelA
|
|
6
|
+
* - docs/research/HARNESS-HOOK-CAPABILITY-MATRIX-2026-04-19.md §1.1-1.3
|
|
7
|
+
*
|
|
8
|
+
* 覆盖:Claude Code / Cursor 1.7+ / Windsurf
|
|
9
|
+
* 共同模式:写配置文件(JSON)→ 注册 PreToolUse 风格 hook → exit 2 阻断
|
|
10
|
+
*
|
|
11
|
+
* Cursor / Windsurf schema 来自官方文档一手验证(feedback_verify_verbal_facts 教训)。
|
|
12
|
+
*/
|
|
13
|
+
import type { HostId, KernelAdapter } from './types.js';
|
|
14
|
+
export declare const hookFileAdapter: KernelAdapter;
|
|
15
|
+
export declare function userScopeHintFor(host: HostId): string | null;
|
|
16
|
+
//# sourceMappingURL=hook-file.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"hook-file.d.ts","sourceRoot":"","sources":["../../../src/harness/kernels/hook-file.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAgBH,OAAO,KAAK,EAEV,MAAM,EAIN,aAAa,EACd,MAAM,YAAY,CAAC;AAkIpB,eAAO,MAAM,eAAe,EAAE,aAsG7B,CAAC;AAOF,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAW5D"}
|
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* BSO M9 v0.3 — KernelA: hook-file
|
|
3
|
+
*
|
|
4
|
+
* Refs:
|
|
5
|
+
* - docs/prd/PRD-BSO-M9-HARNESS-HOOKS.md §3.4.2 KernelA
|
|
6
|
+
* - docs/research/HARNESS-HOOK-CAPABILITY-MATRIX-2026-04-19.md §1.1-1.3
|
|
7
|
+
*
|
|
8
|
+
* 覆盖:Claude Code / Cursor 1.7+ / Windsurf
|
|
9
|
+
* 共同模式:写配置文件(JSON)→ 注册 PreToolUse 风格 hook → exit 2 阻断
|
|
10
|
+
*
|
|
11
|
+
* Cursor / Windsurf schema 来自官方文档一手验证(feedback_verify_verbal_facts 教训)。
|
|
12
|
+
*/
|
|
13
|
+
import { join } from 'node:path';
|
|
14
|
+
import { homedir } from 'node:os';
|
|
15
|
+
import { existsSync } from 'node:fs';
|
|
16
|
+
import { SKILLFM_GUARD_COMMAND, claudeSettingsPatch, } from '../templates.js';
|
|
17
|
+
import { mergeClaudeSettings } from '../writers.js';
|
|
18
|
+
import { arrayHasMarker, ensureArrayPath, ensureObjectPath, mergeJsonFile, } from './json-merge.js';
|
|
19
|
+
const TARGETS = [
|
|
20
|
+
// --------------------------------------------------------------------------
|
|
21
|
+
// Claude Code — .claude/settings.json
|
|
22
|
+
// --------------------------------------------------------------------------
|
|
23
|
+
{
|
|
24
|
+
host: 'claude-code',
|
|
25
|
+
configPath: (cwd) => join(cwd, '.claude', 'settings.json'),
|
|
26
|
+
patch: () => {
|
|
27
|
+
// Delegate 到现有 mergeClaudeSettings — 保留 v0.2 已 ship 行为
|
|
28
|
+
// 这里仅占位;install() 内特判 claude-code 走 mergeClaudeSettings。
|
|
29
|
+
throw new Error('claude-code uses dedicated mergeClaudeSettings');
|
|
30
|
+
},
|
|
31
|
+
},
|
|
32
|
+
// --------------------------------------------------------------------------
|
|
33
|
+
// Cursor 1.7+ — .cursor/hooks.json
|
|
34
|
+
// 官方 schema 来源:调研 §1.2
|
|
35
|
+
// { version: 1, hooks: { sessionStart: [{command}], preToolUse: [{matcher, command}] } }
|
|
36
|
+
// 阻断方式:exit 2 OR stdout JSON {permission:"deny"}
|
|
37
|
+
// --------------------------------------------------------------------------
|
|
38
|
+
{
|
|
39
|
+
host: 'cursor',
|
|
40
|
+
configPath: (cwd) => join(cwd, '.cursor', 'hooks.json'),
|
|
41
|
+
patch: (cwd) => ({
|
|
42
|
+
filePath: join(cwd, '.cursor', 'hooks.json'),
|
|
43
|
+
emptyJson: () => ({ version: 1, hooks: {} }),
|
|
44
|
+
isAlreadyRegistered: (data) => {
|
|
45
|
+
const hooks = data?.hooks ?? {};
|
|
46
|
+
return arrayHasMarker(hooks.preToolUse, SKILLFM_GUARD_COMMAND);
|
|
47
|
+
},
|
|
48
|
+
mergePatch: (data) => {
|
|
49
|
+
const hooks = ensureObjectPath(data, 'hooks');
|
|
50
|
+
// sessionStart
|
|
51
|
+
const ss = ensureArrayPath(hooks, 'sessionStart');
|
|
52
|
+
if (!arrayHasMarker(ss, SKILLFM_GUARD_COMMAND)) {
|
|
53
|
+
ss.push({ command: `${SKILLFM_GUARD_COMMAND} session-start` });
|
|
54
|
+
}
|
|
55
|
+
// preToolUse — Cursor 用单 entry 含 matcher 字段
|
|
56
|
+
const pre = ensureArrayPath(hooks, 'preToolUse');
|
|
57
|
+
pre.push({
|
|
58
|
+
matcher: 'Write|Edit|run_terminal',
|
|
59
|
+
command: `${SKILLFM_GUARD_COMMAND} pre-tool-use --harness=cursor`,
|
|
60
|
+
});
|
|
61
|
+
// postToolUse — 仅 audit
|
|
62
|
+
const post = ensureArrayPath(hooks, 'postToolUse');
|
|
63
|
+
post.push({
|
|
64
|
+
matcher: 'Write|Edit|run_terminal',
|
|
65
|
+
command: `${SKILLFM_GUARD_COMMAND} post-tool-use --harness=cursor`,
|
|
66
|
+
});
|
|
67
|
+
// failClosed: false(fail-open,避免 sidecar 故障锁死)
|
|
68
|
+
if (data.failClosed === undefined) {
|
|
69
|
+
data.failClosed = false;
|
|
70
|
+
}
|
|
71
|
+
},
|
|
72
|
+
}),
|
|
73
|
+
},
|
|
74
|
+
// --------------------------------------------------------------------------
|
|
75
|
+
// Windsurf Cascade — .windsurf/hooks.json
|
|
76
|
+
// 官方 schema 来源:调研 §1.3
|
|
77
|
+
// { hooks: { pre_write_code: [{command, show_output}], pre_run_command: [...], pre_mcp_tool_use: [...] } }
|
|
78
|
+
// 阻断方式:写 stderr + exit 2
|
|
79
|
+
// --------------------------------------------------------------------------
|
|
80
|
+
{
|
|
81
|
+
host: 'windsurf',
|
|
82
|
+
configPath: (cwd) => join(cwd, '.windsurf', 'hooks.json'),
|
|
83
|
+
patch: (cwd) => ({
|
|
84
|
+
filePath: join(cwd, '.windsurf', 'hooks.json'),
|
|
85
|
+
emptyJson: () => ({ hooks: {} }),
|
|
86
|
+
isAlreadyRegistered: (data) => {
|
|
87
|
+
const hooks = data?.hooks ?? {};
|
|
88
|
+
return (arrayHasMarker(hooks.pre_write_code, SKILLFM_GUARD_COMMAND) ||
|
|
89
|
+
arrayHasMarker(hooks.pre_run_command, SKILLFM_GUARD_COMMAND));
|
|
90
|
+
},
|
|
91
|
+
mergePatch: (data) => {
|
|
92
|
+
const hooks = ensureObjectPath(data, 'hooks');
|
|
93
|
+
const preWrite = ensureArrayPath(hooks, 'pre_write_code');
|
|
94
|
+
preWrite.push({
|
|
95
|
+
command: `${SKILLFM_GUARD_COMMAND} pre-tool-use --harness=windsurf --tool=Write`,
|
|
96
|
+
show_output: true,
|
|
97
|
+
});
|
|
98
|
+
const preRun = ensureArrayPath(hooks, 'pre_run_command');
|
|
99
|
+
preRun.push({
|
|
100
|
+
command: `${SKILLFM_GUARD_COMMAND} pre-tool-use --harness=windsurf --tool=Bash`,
|
|
101
|
+
show_output: true,
|
|
102
|
+
});
|
|
103
|
+
const preMcp = ensureArrayPath(hooks, 'pre_mcp_tool_use');
|
|
104
|
+
preMcp.push({
|
|
105
|
+
command: `${SKILLFM_GUARD_COMMAND} pre-tool-use --harness=windsurf --tool=MCP`,
|
|
106
|
+
show_output: true,
|
|
107
|
+
});
|
|
108
|
+
},
|
|
109
|
+
}),
|
|
110
|
+
},
|
|
111
|
+
];
|
|
112
|
+
const SUPPORTED = ['claude-code', 'cursor', 'windsurf'];
|
|
113
|
+
// ============================================================================
|
|
114
|
+
// Adapter
|
|
115
|
+
// ============================================================================
|
|
116
|
+
export const hookFileAdapter = {
|
|
117
|
+
kind: 'hook-file',
|
|
118
|
+
supportedHosts: SUPPORTED,
|
|
119
|
+
matches(host) {
|
|
120
|
+
return SUPPORTED.includes(host.host);
|
|
121
|
+
},
|
|
122
|
+
async install(ctx) {
|
|
123
|
+
const tgt = TARGETS.find((t) => t.host === ctx.host.host);
|
|
124
|
+
if (!tgt) {
|
|
125
|
+
return {
|
|
126
|
+
kernel: 'hook-file',
|
|
127
|
+
host: ctx.host.host,
|
|
128
|
+
files: [],
|
|
129
|
+
warnings: [`hook-file: unsupported host ${ctx.host.host}`],
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
// claude-code 复用 v0.2 已 ship 的 mergeClaudeSettings(行为已验证)
|
|
133
|
+
if (ctx.host.host === 'claude-code') {
|
|
134
|
+
const filePath = tgt.configPath(ctx.cwd);
|
|
135
|
+
const r = mergeClaudeSettings(filePath, claudeSettingsPatch());
|
|
136
|
+
// mergeClaudeSettings 返回的 action 集合更宽(含 skipped-*),归一到 InstalledFile.action
|
|
137
|
+
const action = r.action === 'created' || r.action === 'merged' || r.action === 'already-registered'
|
|
138
|
+
? r.action
|
|
139
|
+
: 'noop';
|
|
140
|
+
return {
|
|
141
|
+
kernel: 'hook-file',
|
|
142
|
+
host: 'claude-code',
|
|
143
|
+
files: [{ path: filePath, action, details: r.details }],
|
|
144
|
+
warnings: [],
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
// cursor / windsurf 走通用 mergeJsonFile
|
|
148
|
+
const installed = mergeJsonFile({
|
|
149
|
+
...tgt.patch(ctx.cwd),
|
|
150
|
+
dryRun: !ctx.apply,
|
|
151
|
+
});
|
|
152
|
+
return {
|
|
153
|
+
kernel: 'hook-file',
|
|
154
|
+
host: ctx.host.host,
|
|
155
|
+
files: [installed],
|
|
156
|
+
warnings: [],
|
|
157
|
+
};
|
|
158
|
+
},
|
|
159
|
+
async uninstall(ctx) {
|
|
160
|
+
// M9 P0 不实现完整 uninstall — 提示用户走 .bak 恢复
|
|
161
|
+
return {
|
|
162
|
+
kernel: 'hook-file',
|
|
163
|
+
host: ctx.host.host,
|
|
164
|
+
files: [],
|
|
165
|
+
warnings: [
|
|
166
|
+
'完整 uninstall 未实现;请从 ~/.skillfm/backups/<最近 ts>/ 恢复原配置文件',
|
|
167
|
+
],
|
|
168
|
+
};
|
|
169
|
+
},
|
|
170
|
+
async diagnose(ctx) {
|
|
171
|
+
const tgt = TARGETS.find((t) => t.host === ctx.host.host);
|
|
172
|
+
if (!tgt) {
|
|
173
|
+
return {
|
|
174
|
+
kernel: 'hook-file',
|
|
175
|
+
host: ctx.host.host,
|
|
176
|
+
level: 'absent',
|
|
177
|
+
checks: [{ name: 'host-supported', ok: false, detail: `${ctx.host.host} 不在 hook-file 内核范围` }],
|
|
178
|
+
};
|
|
179
|
+
}
|
|
180
|
+
const filePath = tgt.configPath(ctx.cwd);
|
|
181
|
+
const exists = existsSync(filePath);
|
|
182
|
+
if (!exists) {
|
|
183
|
+
return {
|
|
184
|
+
kernel: 'hook-file',
|
|
185
|
+
host: ctx.host.host,
|
|
186
|
+
level: 'absent',
|
|
187
|
+
checks: [
|
|
188
|
+
{ name: 'config-file-exists', ok: false, detail: filePath },
|
|
189
|
+
],
|
|
190
|
+
};
|
|
191
|
+
}
|
|
192
|
+
// 简单读取检查 marker
|
|
193
|
+
let registered = false;
|
|
194
|
+
try {
|
|
195
|
+
const { readFileSync } = await import('node:fs');
|
|
196
|
+
const raw = readFileSync(filePath, 'utf-8');
|
|
197
|
+
registered = raw.includes(SKILLFM_GUARD_COMMAND);
|
|
198
|
+
}
|
|
199
|
+
catch {
|
|
200
|
+
// ignore
|
|
201
|
+
}
|
|
202
|
+
return {
|
|
203
|
+
kernel: 'hook-file',
|
|
204
|
+
host: ctx.host.host,
|
|
205
|
+
level: registered ? 'strong' : 'absent',
|
|
206
|
+
checks: [
|
|
207
|
+
{ name: 'config-file-exists', ok: true, detail: filePath },
|
|
208
|
+
{ name: 'skillfm-guard-registered', ok: registered },
|
|
209
|
+
],
|
|
210
|
+
};
|
|
211
|
+
},
|
|
212
|
+
};
|
|
213
|
+
// ============================================================================
|
|
214
|
+
// 用户级补充路径(Claude Code 用户可选 ~/.claude/settings.json fallback;
|
|
215
|
+
// Windsurf 用户级 fallback ~/.codeium/windsurf/hooks.json — M9 P0 不强写)
|
|
216
|
+
// ============================================================================
|
|
217
|
+
export function userScopeHintFor(host) {
|
|
218
|
+
switch (host) {
|
|
219
|
+
case 'claude-code':
|
|
220
|
+
return join(homedir(), '.claude', 'settings.json');
|
|
221
|
+
case 'windsurf':
|
|
222
|
+
return join(homedir(), '.codeium', 'windsurf', 'hooks.json');
|
|
223
|
+
case 'cursor':
|
|
224
|
+
return join(homedir(), '.cursor', 'hooks.json');
|
|
225
|
+
default:
|
|
226
|
+
return null;
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
//# sourceMappingURL=hook-file.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"hook-file.js","sourceRoot":"","sources":["../../../src/harness/kernels/hook-file.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* BSO M9 v0.3 — Layer 3 共用工具:JSON 文件 idempotent merge + backup
|
|
3
|
+
*
|
|
4
|
+
* 抽出原 writers.ts mergeClaudeSettings 中的通用模式,让 hook-file /
|
|
5
|
+
* deny-pipeline / mcp-only 三个 kernel 共享一套逻辑。
|
|
6
|
+
*
|
|
7
|
+
* 行为:
|
|
8
|
+
* - 文件不存在 → 创建,action='created'
|
|
9
|
+
* - 文件存在 → backup 到 ~/.skillfm/backups/<ts>/,深度合并 patch
|
|
10
|
+
* - 已注册(marker 命中)→ skip,action='already-registered'
|
|
11
|
+
* - 文件不是合法 JSON → 抛 SKILLFM.HARNESS.PARSE_FAILED
|
|
12
|
+
*/
|
|
13
|
+
import type { InstalledFile } from './types.js';
|
|
14
|
+
export interface JsonMergeOptions {
|
|
15
|
+
/** 目标文件绝对路径 */
|
|
16
|
+
filePath: string;
|
|
17
|
+
/**
|
|
18
|
+
* 已注册检测器:返回 true 表示该 patch 已经在 data 内,跳过写入。
|
|
19
|
+
* 例如 hook-file 检查 hooks.PreToolUse 数组里 command 是否含 SKILLFM_GUARD_COMMAND。
|
|
20
|
+
*/
|
|
21
|
+
isAlreadyRegistered: (data: unknown) => boolean;
|
|
22
|
+
/**
|
|
23
|
+
* 合并函数:把 patch 内容深度合并到 data。原地修改 data,返回 void。
|
|
24
|
+
* 调用方负责保证语义(数组 append vs object merge)。
|
|
25
|
+
*/
|
|
26
|
+
mergePatch: (data: Record<string, unknown>) => void;
|
|
27
|
+
/** 默认空对象时的初始 JSON(如某些文件需要 `{ "version": 1 }`) */
|
|
28
|
+
emptyJson?: () => Record<string, unknown>;
|
|
29
|
+
/** 是否 dry-run(不落盘,仅返回预期 action) */
|
|
30
|
+
dryRun?: boolean;
|
|
31
|
+
}
|
|
32
|
+
export declare function mergeJsonFile(opts: JsonMergeOptions): InstalledFile;
|
|
33
|
+
/**
|
|
34
|
+
* 检查 array 中是否已有任何 entry 的 stringify 含 marker(粗粒度去重)。
|
|
35
|
+
*/
|
|
36
|
+
export declare function arrayHasMarker(array: unknown, marker: string): boolean;
|
|
37
|
+
/**
|
|
38
|
+
* Ensure path `data[k1][k2]...` exists as object, return innermost ref.
|
|
39
|
+
*/
|
|
40
|
+
export declare function ensureObjectPath(data: Record<string, unknown>, ...keys: string[]): Record<string, unknown>;
|
|
41
|
+
/**
|
|
42
|
+
* Ensure path `data[k1][k2]...` exists as array, return ref.
|
|
43
|
+
*/
|
|
44
|
+
export declare function ensureArrayPath(data: Record<string, unknown>, ...keys: string[]): unknown[];
|
|
45
|
+
//# sourceMappingURL=json-merge.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"json-merge.d.ts","sourceRoot":"","sources":["../../../src/harness/kernels/json-merge.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAWH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAMhD,MAAM,WAAW,gBAAgB;IAC/B,eAAe;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB;;;OAGG;IACH,mBAAmB,EAAE,CAAC,IAAI,EAAE,OAAO,KAAK,OAAO,CAAC;IAChD;;;OAGG;IACH,UAAU,EAAE,CAAC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,IAAI,CAAC;IACpD,iDAAiD;IACjD,SAAS,CAAC,EAAE,MAAM,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAC1C,mCAAmC;IACnC,MAAM,CAAC,EAAE,OAAO,CAAC;CAClB;AAMD,wBAAgB,aAAa,CAAC,IAAI,EAAE,gBAAgB,GAAG,aAAa,CA4DnE;AAMD;;GAEG;AACH,wBAAgB,cAAc,CAAC,KAAK,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAStE;AAED;;GAEG;AACH,wBAAgB,gBAAgB,CAC9B,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC7B,GAAG,IAAI,EAAE,MAAM,EAAE,GAChB,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CASzB;AAED;;GAEG;AACH,wBAAgB,eAAe,CAC7B,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC7B,GAAG,IAAI,EAAE,MAAM,EAAE,GAChB,OAAO,EAAE,CAWX"}
|