@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.
Files changed (50) hide show
  1. package/README.md +7 -2
  2. package/dist/harness/kernels/deny-pipeline.d.ts +21 -0
  3. package/dist/harness/kernels/deny-pipeline.d.ts.map +1 -0
  4. package/dist/harness/kernels/deny-pipeline.js +225 -0
  5. package/dist/harness/kernels/deny-pipeline.js.map +1 -0
  6. package/dist/harness/kernels/hook-file.d.ts +16 -0
  7. package/dist/harness/kernels/hook-file.d.ts.map +1 -0
  8. package/dist/harness/kernels/hook-file.js +229 -0
  9. package/dist/harness/kernels/hook-file.js.map +1 -0
  10. package/dist/harness/kernels/json-merge.d.ts +45 -0
  11. package/dist/harness/kernels/json-merge.d.ts.map +1 -0
  12. package/dist/harness/kernels/json-merge.js +123 -0
  13. package/dist/harness/kernels/json-merge.js.map +1 -0
  14. package/dist/harness/kernels/mcp-only.d.ts +23 -0
  15. package/dist/harness/kernels/mcp-only.d.ts.map +1 -0
  16. package/dist/harness/kernels/mcp-only.js +215 -0
  17. package/dist/harness/kernels/mcp-only.js.map +1 -0
  18. package/dist/harness/kernels/protocol-reject.d.ts +18 -0
  19. package/dist/harness/kernels/protocol-reject.d.ts.map +1 -0
  20. package/dist/harness/kernels/protocol-reject.js +117 -0
  21. package/dist/harness/kernels/protocol-reject.js.map +1 -0
  22. package/dist/harness/kernels/registry.d.ts +56 -0
  23. package/dist/harness/kernels/registry.d.ts.map +1 -0
  24. package/dist/harness/kernels/registry.js +139 -0
  25. package/dist/harness/kernels/registry.js.map +1 -0
  26. package/dist/harness/kernels/types.d.ts +86 -0
  27. package/dist/harness/kernels/types.d.ts.map +1 -0
  28. package/dist/harness/kernels/types.js +21 -0
  29. package/dist/harness/kernels/types.js.map +1 -0
  30. package/dist/mcp-output/builder.d.ts +24 -0
  31. package/dist/mcp-output/builder.d.ts.map +1 -0
  32. package/dist/mcp-output/builder.js +99 -0
  33. package/dist/mcp-output/builder.js.map +1 -0
  34. package/dist/mcp-output/deny-review.d.ts +64 -0
  35. package/dist/mcp-output/deny-review.d.ts.map +1 -0
  36. package/dist/mcp-output/deny-review.js +212 -0
  37. package/dist/mcp-output/deny-review.js.map +1 -0
  38. package/dist/mcp-output/types.d.ts +64 -0
  39. package/dist/mcp-output/types.d.ts.map +1 -0
  40. package/dist/mcp-output/types.js +21 -0
  41. package/dist/mcp-output/types.js.map +1 -0
  42. package/dist/skill-md/template.d.ts +30 -0
  43. package/dist/skill-md/template.d.ts.map +1 -0
  44. package/dist/skill-md/template.js +194 -0
  45. package/dist/skill-md/template.js.map +1 -0
  46. package/dist/skill-md/writer.d.ts +30 -0
  47. package/dist/skill-md/writer.d.ts.map +1 -0
  48. package/dist/skill-md/writer.js +129 -0
  49. package/dist/skill-md/writer.js.map +1 -0
  50. 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 -y @skillfm/local@latest start
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 -y @skillfm/local@latest start &`
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":"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"}
@@ -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"}