@x-otto/claude-compat 0.0.1-alpha.0

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 ADDED
@@ -0,0 +1,87 @@
1
+ # @x-otto/claude-compat
2
+
3
+ > Claude Code compatibility layer: trust model, file commands, hooks, MCP servers, and tool name mapping.
4
+
5
+ `@x-otto/claude-compat` bridges Claude Code's ecosystem conventions into otto. It enables users to bring their existing `.claude/` configurations, custom slash commands, MCP servers, and hooks when migrating from Claude Code — all behind opt-in compatibility flags and a project trust model that prevents cloned repositories from auto-executing arbitrary code.
6
+
7
+ ## Installation
8
+
9
+ ```bash
10
+ pnpm add @x-otto/claude-compat
11
+ ```
12
+
13
+ ## Usage
14
+
15
+ ```typescript
16
+ import {
17
+ discoverFileCommands,
18
+ expandCommandBody,
19
+ evaluateClaudeTrust,
20
+ trustProject,
21
+ createClaudeHookBridgeHooks,
22
+ resolveClaudeMcpServers,
23
+ } from '@x-otto/claude-compat'
24
+
25
+ // Discover file commands from all layers (plugin → user → project)
26
+ const commands = discoverFileCommands('/path/to/project')
27
+
28
+ // Expand a command body (args, file references, shell exec)
29
+ const body = await expandCommandBody(
30
+ 'Review $ARGUMENTS using @CONTRIBUTING.md\n!`git diff --stat`',
31
+ { args: ['src/index.ts'], workspaceDir: '/path/to/project' },
32
+ )
33
+
34
+ // Check project trust
35
+ const { gated, surface } = evaluateClaudeTrust('/path/to/project')
36
+ if (gated) {
37
+ // Prompt user, then trust if confirmed
38
+ trustProject('/path/to/project')
39
+ }
40
+
41
+ // Bridge Claude hooks → otto HookSpecs
42
+ const hooks = createClaudeHookBridgeHooks({
43
+ workspaceDir: '/path/to/project',
44
+ })
45
+
46
+ // Load .mcp.json (trust-gated)
47
+ const servers = resolveClaudeMcpServers('/path/to/project')
48
+ ```
49
+
50
+ ## Key Exports
51
+
52
+ ### File Commands
53
+ - `discoverFileCommands()` — Scan `.claude/commands/` + `.otto/commands/` across all layers
54
+ - `expandCommandBody()` — Expand `$ARGUMENTS`, `$N`, `@file`, ``!`cmd` `` placeholders
55
+
56
+ ### Trust Model
57
+ - `evaluateClaudeTrust()` — Detect executable surface and return `gated`/`trusted` verdict
58
+ - `isProjectTrusted()` / `trustProject()` / `untrustProject()` — Read/write trust white list
59
+ - `detectClaudeExecutableSurface()` — Probe `.mcp.json` + `.claude/commands/` + `.claude/agents/` + `.claude/skills/`
60
+
61
+ ### Hooks Bridge
62
+ - `createClaudeHookBridgeHooks()` — Convert `.claude/settings.json` hooks → otto `HookSpec[]`
63
+ - `loadClaudeHooksConfig()` — Load hooks from all settings.json locations
64
+ - `interpretPreToolUse()` — Parse hook stdout for allow/deny/block decisions
65
+
66
+ ### MCP Servers
67
+ - `parseDotMcpJson()` — Parse `.mcp.json` with `${VAR}` env expansion
68
+ - `resolveClaudeMcpServers()` — Same + trust-gated (empty if untrusted)
69
+
70
+ ### Project Config Trust
71
+ - `isStrictProjectConfigEnabled()` — Env-only strict mode gate
72
+ - `resolveProjectConfigTrusted()` — Interactive-only trust check
73
+ - `evaluateWorkspaceTrustPrompt()` — Pure decision: should we prompt "trust this folder?"
74
+ - `detectProjectConfigGating()` — Probe project config for gated high-risk keys
75
+
76
+ ### Tool Name Mapping (re-exported from `@x-otto/shared`)
77
+ - `CLAUDE_TO_OTTO_TOOL` — Mapping table
78
+ - `mapClaudeToolName()` / `ottoToClaudeToolName()` — Bidirectional conversion
79
+
80
+ ## Dependencies
81
+
82
+ - **Internal**: `@x-otto/env`, `@x-otto/plugin`, `@x-otto/hooks`, `@x-otto/runtime`, `@x-otto/shared`, `@x-otto/setting`
83
+ - **External**: Node.js built-ins only
84
+
85
+ ## Related
86
+
87
+ - [Architecture](./ARCHITECTURE.md)
@@ -0,0 +1,195 @@
1
+ import { CLAUDE_TO_OTTO_TOOL, mapClaudeAgentToolNames, mapClaudeToolName, ottoToClaudeToolName } from "@x-otto/shared";
2
+ import { DiscoveredPlugin } from "@x-otto/plugin";
3
+ import { HookSpec } from "@x-otto/hooks";
4
+ import { McpServerConfig } from "@x-otto/mcp";
5
+
6
+ //#region src/claude-trust.d.ts
7
+ /**
8
+ * 信任白名单落盘路径(用户级)。`OTTO_CLAUDE_TRUST_PATH` 可覆盖(测试/隔离用,
9
+ * 避免污染真实 `~/.otto`——参照 auth-store 真路径教训)。调用时求值。
10
+ */
11
+ declare function claudeTrustStorePath(): string;
12
+ /** 该项目根的 `.claude/` 可执行面是否已被用户显式信任。 */
13
+ declare function isProjectTrusted(workspaceDir: string, storePath?: string): boolean;
14
+ /** 显式信任某项目根(幂等,持久化)。 */
15
+ declare function trustProject(workspaceDir: string, storePath?: string): void;
16
+ /** 撤销对某项目根的信任(持久化)。 */
17
+ declare function untrustProject(workspaceDir: string, storePath?: string): void;
18
+ /** 项目级 `.claude/` 受信任门面探测结果(可执行面 ① + 内容面 ②,RFC-081 O1)。 */
19
+ interface ClaudeExecutableSurface {
20
+ /** 项目根存在 `.mcp.json`(stdio server 启动即跑二进制)。 */
21
+ mcpJson: boolean;
22
+ /** 项目 `.claude/commands/` 非空(`!cmd` 展开即 shell)。 */
23
+ commands: boolean;
24
+ /** 项目 `.claude/agents/` 非空(攻击者可控 agent profile,默认继承全工具;O1 内容面)。 */
25
+ agents: boolean;
26
+ /** 项目 `.claude/skills/` 非空(攻击者可控 prompt 内容;O1 内容面)。 */
27
+ skills: boolean;
28
+ /** 任一受信任门面存在。 */
29
+ any: boolean;
30
+ }
31
+ /**
32
+ * 探测项目根 `.claude/` 的受信任门面(不读内容,只看存在性)。
33
+ * 含可执行面(.mcp.json/commands)+ 内容面(agents/skills,RFC-081 O1)——
34
+ * 后者是攻击者可控 prompt/profile,与可执行面同属克隆敌仓的注入向量(对齐 Claude Code)。
35
+ */
36
+ declare function detectClaudeExecutableSurface(workspaceDir: string): ClaudeExecutableSurface;
37
+ /** 信任门综合判定。 */
38
+ interface ClaudeTrustVerdict {
39
+ surface: ClaudeExecutableSurface;
40
+ trusted: boolean;
41
+ /** true = 存在可执行面且未信任(调用方应门控:弹窗或跳过);false = 可直接加载。 */
42
+ gated: boolean;
43
+ }
44
+ /**
45
+ * 评估项目 `.claude/` 是否需要门控。
46
+ * `gated` 为 true 仅当:兼容开 且 存在可执行面 且 该项目未被信任。
47
+ */
48
+ declare function evaluateClaudeTrust(workspaceDir: string, storePath?: string): ClaudeTrustVerdict;
49
+ //#endregion
50
+ //#region src/claude-commands.d.ts
51
+ interface FileCommand {
52
+ /** 命名空间名(子目录 `/` → `:`),如 `frontend:component` 或 `review`。 */
53
+ name: string;
54
+ description?: string;
55
+ argumentHint?: string;
56
+ /** §3.0 归一后的工具名(仅提示/将来收敛用,D2 不强制收敛回合工具集——见 RFC §10 C8)。 */
57
+ allowedTools?: string[];
58
+ model?: string;
59
+ body: string;
60
+ source: 'claude' | 'otto' | 'plugin';
61
+ scope: 'project' | 'repository' | 'user';
62
+ /** 贡献来源插件 id(`source==='plugin'` 时存在)= unregister key(RFC-044 R6)。 */
63
+ pluginId?: string;
64
+ filePath: string;
65
+ }
66
+ /**
67
+ * 发现文件式命令。优先级(低→高,同名 last-wins):
68
+ * plugin[*] < user/claude < user/otto < project/claude < project/otto。
69
+ * 插件命令最低(R7:otto/claude 永远覆盖插件同名命令)。
70
+ * `.claude/commands` 仅在兼容开且项目已信任(未 gated)时纳入;
71
+ * 项目级插件命令是可执行面(`!cmd`),未信任(gated)则跳过(RFC-044 R4)。
72
+ *
73
+ * `plugins` 缺省自动发现 `<.otto>/plugins`(项目+用户);可注入供测试。
74
+ */
75
+ declare function discoverFileCommands(workspaceDir: string, plugins?: DiscoveredPlugin[]): FileCommand[];
76
+ interface ExpandOptions {
77
+ args: string[];
78
+ workspaceDir: string;
79
+ /** `` !`cmd` `` 执行器(默认 sh -c + 超时)。 */
80
+ runShell?: (cmd: string, cwd: string) => Promise<string>;
81
+ /** `@file` 读取器(默认相对 workspaceDir 读 utf-8)。 */
82
+ readFileText?: (relPath: string, cwd: string) => Promise<string>;
83
+ }
84
+ /**
85
+ * 展开命令正文。顺序:先 `$ARGUMENTS`/`$N`(位置参数),再 `@file`,最后 `` !`cmd` ``
86
+ * (故 cmd/file 引用里可含已替换的参数)。
87
+ */
88
+ declare function expandCommandBody(body: string, opts: ExpandOptions): Promise<string>;
89
+ //#endregion
90
+ //#region src/claude-hooks.d.ts
91
+ interface ClaudeHookCommand {
92
+ type?: string;
93
+ command: string;
94
+ timeout?: number;
95
+ }
96
+ interface ClaudeHookGroup {
97
+ matcher?: string;
98
+ hooks: ClaudeHookCommand[];
99
+ }
100
+ /** event 名 → matcher 组列表。 */
101
+ type ClaudeHooksConfig = Record<string, ClaudeHookGroup[]>;
102
+ /** 从 settings.json 原始对象解析 hooks 段(容错;非法项跳过)。 */
103
+ declare function parseClaudeHooks(raw: unknown): ClaudeHooksConfig;
104
+ /** 加载 `.claude` hooks(默认关 / 未信任 → {})。 */
105
+ declare function loadClaudeHooksConfig(workspaceDir?: string): ClaudeHooksConfig;
106
+ /** matcher 匹配:空/`*` → 全匹配;否则按 Claude 名(+otto 名兜底)做正则。 */
107
+ declare function matchesHookMatcher(matcher: string | undefined, ottoToolName: string): boolean;
108
+ interface HookRunResult {
109
+ stdout: string;
110
+ exitCode: number;
111
+ }
112
+ type HookRunner = (command: string, stdin: string, opts: {
113
+ cwd: string;
114
+ timeoutMs: number;
115
+ }) => Promise<HookRunResult>;
116
+ /** 默认 runner:`sh -c command`,写 stdin、超时 kill、收 stdout(防挂死)。
117
+ * 防御纵深(endgame review B1):checkShellCommand 拦已知危险模式 + buildAllowedEnv
118
+ * 白名单过滤环境变量(不泄漏 API key/token 给外部 hook 命令)——与 plugin-hooks 同基线。 */
119
+ declare const defaultHookRunner: HookRunner;
120
+ interface PreToolUseVerdict {
121
+ decision?: 'allow' | 'deny' | 'ask';
122
+ block?: boolean;
123
+ reason?: string;
124
+ }
125
+ /** 解释 PreToolUse 钩子输出:exit 2 / decision:block / continue:false → 阻断;permissionDecision → 决策。 */
126
+ declare function interpretPreToolUse(stdout: string, exitCode: number): PreToolUseVerdict;
127
+ interface ClaudeHookBridgeDeps {
128
+ workspaceDir?: string;
129
+ sessionId?: string;
130
+ runner?: HookRunner;
131
+ /** 注入配置(缺省经 loadClaudeHooksConfig 读盘)。 */
132
+ config?: ClaudeHooksConfig;
133
+ }
134
+ /**
135
+ * 构造 Claude hooks 桥的 otto HookSpec 列表(默认关 / 无配置 → [])。
136
+ * PreToolUse = 拦截器(可 deny/ask/block);其余映射事件 = 观察器(fire-and-forget)。
137
+ */
138
+ declare function createClaudeHookBridgeHooks(deps: ClaudeHookBridgeDeps): HookSpec[];
139
+ //#endregion
140
+ //#region src/dot-mcp-json.d.ts
141
+ /** 解析项目根 `.mcp.json` 的 mcp_servers(回退兼容 mcpServers);不做信任门判断;缺失/损坏 → {}。 */
142
+ declare function parseDotMcpJson(workspaceDir: string): Record<string, McpServerConfig>;
143
+ /**
144
+ * 信任门后的 `.mcp.json` server:兼容关 / 未信任(gated)→ {};否则解析返回。
145
+ * 调用方应把它作为**低优先级**与 settings.mcp_servers 合并(otto 同名覆盖)。
146
+ */
147
+ declare function resolveClaudeMcpServers(workspaceDir: string): Record<string, McpServerConfig>;
148
+ //#endregion
149
+ //#region src/project-config-trust.d.ts
150
+ /**
151
+ * 严格项目配置门控是否开启(env-only,项目 config 不可影响,杜绝恶意仓库自关门)。
152
+ * **默认开**(安全默认):克隆仓库的 `.otto/config.json` 高危键默认不生效,须 `/compat trust`。
153
+ * `OTTO_STRICT_PROJECT_CONFIG=0` 是逃生口(关门控、恢复旧的全信任行为)。
154
+ * 注:仅交互态实际剔除(headless fail-open,见 resolveProjectConfigTrusted),故 CI/脚本/测试不受影响。
155
+ */
156
+ declare function isStrictProjectConfigEnabled(): boolean;
157
+ interface ProjectConfigTrustContext {
158
+ /** 是否交互(TUI)态。缺省 false=headless → fail-open 放行。 */
159
+ interactive?: boolean;
160
+ }
161
+ /**
162
+ * 该 workspace 的项目配置是否受信任。
163
+ * 严格模式关 → 恒 `true`;headless → 恒 `true`(fail-open);交互+严格 → 取信任白名单判定。
164
+ */
165
+ declare function resolveProjectConfigTrusted(workspaceDir: string, ctx?: ProjectConfigTrustContext): boolean;
166
+ interface ProjectConfigGating {
167
+ /** 项目 `.otto/config.json` 实际含有的高危键(空 = 无可门控内容)。 */
168
+ highRiskKeys: string[];
169
+ /** 这些高危键当前是否被剔除(严格开 + 交互 + 未信任)。 */
170
+ gated: boolean;
171
+ }
172
+ /**
173
+ * 探测项目 `.otto/config.json` 的高危键门控状态(供 `/compat status` 呈现)。
174
+ * 严格模式关 → 返空(特性未启用,无需呈现噪声)。读文件失败/无文件 → 返空。
175
+ */
176
+ declare function detectProjectConfigGating(workspaceDir: string, ctx?: ProjectConfigTrustContext): ProjectConfigGating;
177
+ /** 用户对工作区信任弹窗的选择(见 doc §8.3 三选)。 */
178
+ type WorkspaceTrustAction = 'trust' | 'readonly' | 'exit';
179
+ interface WorkspaceTrustPromptDecision {
180
+ /** 是否应弹"信任此文件夹吗"。 */
181
+ shouldPrompt: boolean;
182
+ /** 解析到的工作区根(信任按它记录)。 */
183
+ root: string;
184
+ /** 不弹的原因(诊断/测试):trusted=已信任 · not-interactive · strict-off · not-a-project · untrusted=应弹。 */
185
+ reason: 'trusted' | 'not-interactive' | 'strict-off' | 'not-a-project' | 'untrusted';
186
+ }
187
+ /**
188
+ * 工作区信任弹窗的纯决策(doc §8):启动时是否要弹"信任此文件夹吗"。
189
+ * 弹 ⟺ 交互 且 严格开 且 根未信任 且 是真项目。其余一律不弹(headless/已信任/关门控/非项目)。
190
+ * 纯函数:只读 FS + env,不做 IO 副作用、不渲染。渲染与 trust/readonly/exit 动作在 cli 层。
191
+ */
192
+ declare function evaluateWorkspaceTrustPrompt(workspaceDir: string, ctx?: ProjectConfigTrustContext): WorkspaceTrustPromptDecision;
193
+ //#endregion
194
+ export { CLAUDE_TO_OTTO_TOOL, type ClaudeExecutableSurface, type ClaudeHookBridgeDeps, type ClaudeHookCommand, type ClaudeHookGroup, type ClaudeHooksConfig, type ClaudeTrustVerdict, type ExpandOptions, type FileCommand, type HookRunResult, type HookRunner, type PreToolUseVerdict, type ProjectConfigGating, type ProjectConfigTrustContext, type WorkspaceTrustAction, type WorkspaceTrustPromptDecision, claudeTrustStorePath, createClaudeHookBridgeHooks, defaultHookRunner, detectClaudeExecutableSurface, detectProjectConfigGating, discoverFileCommands, evaluateClaudeTrust, evaluateWorkspaceTrustPrompt, expandCommandBody, interpretPreToolUse, isProjectTrusted, isStrictProjectConfigEnabled, loadClaudeHooksConfig, mapClaudeAgentToolNames, mapClaudeToolName, matchesHookMatcher, ottoToClaudeToolName, parseClaudeHooks, parseDotMcpJson, resolveClaudeMcpServers, resolveProjectConfigTrusted, trustProject, untrustProject };
195
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","names":[],"sources":["../src/claude-trust.ts","../src/claude-commands.ts","../src/claude-hooks.ts","../src/dot-mcp-json.ts","../src/project-config-trust.ts"],"mappings":";;;;;;;;;;iBA0BgB,oBAAA,CAAA;;iBAKA,gBAAA,CACd,YAAA,UACA,SAAA;;iBAMc,YAAA,CAAa,YAAA,UAAsB,SAAA;;iBAOnC,cAAA,CAAe,YAAA,UAAsB,SAAA;AAfrD;AAAA,UAsBiB,uBAAA;;EAEf,OAAA;EAtBkC;EAwBlC,QAAA;EAlB0B;EAoB1B,MAAA;EApB2B;EAsB3B,MAAA;EAfc;EAiBd,GAAA;AAAA;;;AAVF;;;iBA0BgB,6BAAA,CAA8B,YAAA,WAAuB,uBAAA;;UAUpD,kBAAA;EACf,OAAA,EAAS,uBAAA;EACT,OAAA;EA5BA;EA8BA,KAAA;AAAA;AAdF;;;;AAAA,iBAqBgB,mBAAA,CACd,YAAA,UACA,SAAA,YACC,kBAAA;;;UC1Ec,WAAA;;EAEf,IAAA;EACA,WAAA;EACA,YAAA;EDPc;ECSd,YAAA;EACA,KAAA;EACA,IAAA;EACA,MAAA;EACA,KAAA;EDR8B;ECU9B,QAAA;EACA,QAAA;AAAA;ADHF;;;;;AAOA;;;;AAPA,iBC6EgB,oBAAA,CACd,YAAA,UACA,OAAA,GAAS,gBAAA,KACR,WAAA;AAAA,UAuFc,aAAA;EACf,IAAA;EACA,YAAA;ED3JsC;EC6JtC,QAAA,IAAY,GAAA,UAAa,GAAA,aAAgB,OAAA;EDzJzC;EC2JA,YAAA,IAAgB,OAAA,UAAiB,GAAA,aAAgB,OAAA;AAAA;;;;ADrInD;iBCoJsB,iBAAA,CAAkB,IAAA,UAAc,IAAA,EAAM,aAAA,GAAgB,OAAA;;;UC3M3D,iBAAA;EACf,IAAA;EACA,OAAA;EACA,OAAA;AAAA;AAAA,UAEe,eAAA;EACf,OAAA;EACA,KAAA,EAAO,iBAAA;AAAA;;KAGG,iBAAA,GAAoB,MAAA,SAAe,eAAA;;iBAG/B,gBAAA,CAAiB,GAAA,YAAe,iBAAA;;iBAiChC,qBAAA,CAAsB,YAAA,YAAwB,iBAAA;AF/B9D;AAAA,iBE2DgB,kBAAA,CAAmB,OAAA,sBAA6B,YAAA;AAAA,UAY/C,aAAA;EACf,MAAA;EACA,QAAA;AAAA;AAAA,KAEU,UAAA,IACV,OAAA,UACA,KAAA,UACA,IAAA;EAAQ,GAAA;EAAa,SAAA;AAAA,MAClB,OAAA,CAAQ,aAAA;AFjEb;;;AAAA,cEsEa,iBAAA,EAAmB,UAAA;AAAA,UA6Cf,iBAAA;EACf,QAAA;EACA,KAAA;EACA,MAAA;AAAA;;iBAIc,mBAAA,CAAoB,MAAA,UAAgB,QAAA,WAAmB,iBAAA;AAAA,UAsCtD,oBAAA;EACf,YAAA;EACA,SAAA;EACA,MAAA,GAAS,UAAA;EFzIiF;EE2I1F,MAAA,GAAS,iBAAA;AAAA;;;;;iBAsBK,2BAAA,CAA4B,IAAA,EAAM,oBAAA,GAAuB,QAAA;;;;iBC/KzD,eAAA,CAAgB,YAAA,WAAuB,MAAA,SAAe,eAAA;;;;AHvCtE;iBGmEgB,uBAAA,CAAwB,YAAA,WAAuB,MAAA,SAAe,eAAA;;;;;;;;;iBC5D9D,4BAAA,CAAA;AAAA,UAIC,yBAAA;;EAEf,WAAA;AAAA;AJRF;;;;AAAA,iBIegB,2BAAA,CACd,YAAA,UACA,GAAA,GAAK,yBAAA;AAAA,UAOU,mBAAA;EJhBW;EIkB1B,YAAA;EJlB2B;EIoB3B,KAAA;AAAA;;;;;iBAOc,yBAAA,CACd,YAAA,UACA,GAAA,GAAK,yBAAA,GACJ,mBAAA;;KAeS,oBAAA;AAAA,UAEK,4BAAA;EJ/Bf;EIiCA,YAAA;EJ7BA;EI+BA,IAAA;EJ3BA;EI6BA,MAAA;AAAA;AJbF;;;;;AAAA,iBIuCgB,4BAAA,CACd,YAAA,UACA,GAAA,GAAK,yBAAA,GACJ,4BAAA"}
package/dist/index.js ADDED
@@ -0,0 +1,2 @@
1
+ import{CLAUDE_TO_OTTO_TOOL as e,buildAllowedEnv as t,checkShellCommand as n,createLogger as r,mapClaudeAgentToolNames as i,mapClaudeAgentToolNames as a,mapClaudeToolName as o,ottoToClaudeToolName as s,ottoToClaudeToolName as c,parseFrontmatterList as l,parseJsonc as u,splitFrontmatter as d}from"@x-otto/shared";import{existsSync as f,readFileSync as p,readdirSync as m}from"node:fs";import{join as h,relative as ee,resolve as g,sep as _}from"node:path";import{CLAUDE_HOME as v,OTTO_HOME as te,OTTO_PROJECT_CONFIG_RELPATH as ne,findWorkspaceRoot as re,isClaudeCompatEnabled as y,isClaudeHooksCompatEnabled as ie}from"@x-otto/env";import{discoverPlugins as ae,evaluatePluginTrust as oe,isPathTrusted as se,trustPath as ce,untrustPath as b}from"@x-otto/plugin";import{execFile as x,spawn as le}from"node:child_process";import{readFile as ue}from"node:fs/promises";import{homedir as S}from"node:os";import{promisify as de}from"node:util";import{defineHook as C}from"@x-otto/hooks";import{globalProcessRuntime as fe}from"@x-otto/runtime";import{sanitizeUntrustedProjectLayer as pe}from"@x-otto/setting";const w=r(`@x-otto/claude-compat:claude-trust`);function T(){return process.env.OTTO_CLAUDE_TRUST_PATH||h(te,`claude-trust.json`)}function E(e,t=T()){return se(t,e)}function D(e,t=T()){ce(t,e,`claude-trust`)&&w.info({root:g(e)},`Project .claude/ trusted`)}function O(e,t=T()){b(t,e,`claude-trust`)&&w.info({root:g(e)},`Project .claude/ trust revoked`)}function k(e){try{return m(e).length>0}catch{return!1}}function A(e){let t=g(e),n=f(h(t,`.mcp.json`)),r=k(h(t,`.claude`,`commands`)),i=k(h(t,`.claude`,`agents`)),a=k(h(t,`.claude`,`skills`));return{mcpJson:n,commands:r,agents:i,skills:a,any:n||r||i||a}}function j(e,t=T()){let n=A(e),r=E(e,t);return{surface:n,trusted:r,gated:y()&&n.any&&!r}}const M=r(`@x-otto/claude-compat:claude-commands`);function N(e){let t=[],n=r=>{let i;try{i=m(r,{withFileTypes:!0})}catch{return}for(let a of i){let i=h(r,a.name);if(a.isDirectory())n(i);else if(a.isFile()&&a.name.endsWith(`.md`)){let n=ee(e,i).slice(0,-3);t.push([n,i])}}};return n(e),t}function P(e,t,n){let r;try{r=p(t,`utf-8`)}catch{return null}let i=d(r),o=i?.fields,s=i?i.body:r.trim();if(!s)return null;let c=l(o?.get(`allowed-tools`)??o?.get(`allowedtools`));return{name:e.split(_).join(`:`),description:o?.get(`description`),argumentHint:o?.get(`argument-hint`)??o?.get(`argumenthint`),allowedTools:n.source===`claude`&&c?a(c):c,model:o?.get(`model`),body:s,source:n.source,scope:n.scope,pluginId:n.pluginId,filePath:t}}function F(e,t=ae({cwd:g(e),homedir:S()})){let n=g(e),r=S(),i=y(),a=i&&!j(n).gated,o=t.filter(e=>e.scope!==`builtin`).filter(e=>e.manifest.contributes?.commands!==!1).filter(e=>!oe(e).gated).map(e=>({dir:h(e.dir,`commands`),source:`plugin`,scope:e.scope,pluginId:e.id})),s=re(n),c=s&&g(s)!==n?{dir:h(g(s),`.otto`,`commands`),source:`otto`,scope:`repository`}:void 0,l=[...o,...i?[{dir:h(r,`.claude`,`commands`),source:`claude`,scope:`user`}]:[],{dir:h(r,`.otto`,`commands`),source:`otto`,scope:`user`},...c?[c]:[],...a?[{dir:h(n,`.claude`,`commands`),source:`claude`,scope:`project`}]:[],{dir:h(n,`.otto`,`commands`),source:`otto`,scope:`project`}],u=new Map;for(let e of l)if(f(e.dir))for(let[t,n]of N(e.dir)){let r=P(t,n,e);r&&u.set(r.name,r)}let d=[...u.values()];return d.length>0&&M.info(`Discovered %d file command(s): %s`,d.length,d.map(e=>e.name).join(`, `)),d}const I=de(x);async function L(e,t){try{let{stdout:n}=await I(`sh`,[`-c`,e],{cwd:t,timeout:1e4,maxBuffer:1024*1024});return n.trimEnd()}catch(e){return`[command failed: ${e.message}]`}}async function R(e,t){try{return await ue(g(t,e),`utf-8`)}catch{return`[file unavailable: ${e}]`}}async function z(e,t){let n=t.args,r=t.runShell??L,i=t.readFileText??R,a=e.replace(/\$ARGUMENTS\b/g,()=>n.join(` `));a=a.replace(/\$([1-9])\b/g,(e,t)=>n[Number(t)-1]??``);let o=[...a.matchAll(/(^|\s)@([^\s`]+)/g)];for(let e of o.reverse()){let n=e[2],r=await i(n,t.workspaceDir),o=`${e[1]}\n\`\`\`\n${r}\n\`\`\``;a=a.slice(0,e.index)+o+a.slice(e.index+e[0].length)}let s=[...a.matchAll(/!`([^`]+)`/g)];for(let e of s.reverse()){let n=await r(e[1],t.workspaceDir);a=a.slice(0,e.index)+n+a.slice(e.index+e[0].length)}return a}const B=r(`@x-otto/claude-compat:claude-hooks`);function V(e){let t=e?.hooks;if(!t||typeof t!=`object`)return{};let n={};for(let[e,r]of Object.entries(t)){if(!Array.isArray(r))continue;let t=[];for(let e of r){if(!e||typeof e!=`object`)continue;let n=e,r=typeof n.matcher==`string`?n.matcher:void 0,i=Array.isArray(n.hooks)?n.hooks:[],a=[];for(let e of i)if(e&&typeof e==`object`){let t=e;typeof t.command==`string`&&a.push({type:typeof t.type==`string`?t.type:void 0,command:t.command,timeout:typeof t.timeout==`number`?t.timeout:void 0})}a.length>0&&t.push({matcher:r,hooks:a})}t.length>0&&(n[e]=t)}return n}function H(e){if(!ie()||e&&j(e).gated)return{};let t=[h(v,`settings.json`),...e?[h(g(e),`.claude`,`settings.json`),h(g(e),`.claude`,`settings.local.json`)]:[]],n={};for(let e of t)if(f(e))try{let t=V(u(p(e,`utf-8`)));for(let[e,r]of Object.entries(t))n[e]=[...n[e]??[],...r]}catch(t){B.warn({file:e,err:t},`Failed to parse .claude hooks, skipped`)}return n}function U(e,t){if(!e||e===`*`)return!0;let n=c(t),r;try{r=new RegExp(e)}catch{return e===n||e===t}return r.test(n)||r.test(t)}const W=(e,r,{cwd:i,timeoutMs:a})=>new Promise(o=>{let s=!1,c=(e,t)=>{s||(s=!0,o({stdout:t,exitCode:e}))},l=n(e);if(!l.safe)return B.warn({command:e,reason:l.reason},`Claude hook command blocked by safety check`),c(126,``);let u=``,d;try{d=le(`sh`,[`-c`,e],{cwd:i,env:t()}),fe.registerChild({command:`sh`,args:[`-c`,e],owner:{type:`agent-tool`,id:`claude-hook`},category:`tool`,lifecycle:`evictable`,cwd:i},d)}catch{return c(1,``)}let f=setTimeout(()=>{d.kill(`SIGKILL`),c(124,u)},a);d.stdout?.on(`data`,e=>u+=String(e)),d.on(`error`,()=>{clearTimeout(f),c(1,u)}),d.on(`close`,e=>{clearTimeout(f),c(e??0,u)});try{d.stdin?.write(r),d.stdin?.end()}catch{}});function G(e,t){if(t===2)return{block:!0,reason:e.trim()||`hook exit 2`};let n=e.trim();if(!n)return{};let r;try{r=JSON.parse(n)}catch{return{}}if(r?.decision===`block`||r?.continue===!1)return{block:!0,reason:r.reason??r.stopReason};let i=r?.hookSpecificOutput;return i?.permissionDecision?{decision:i.permissionDecision,reason:i.permissionDecisionReason}:{}}const me={PostToolUse:`tool.execute.after`,UserPromptSubmit:`chat.message.before`,PreCompact:`compaction.before`,SessionStart:`session.created`,SessionEnd:`session.deleted`,Stop:`stream.end`,SubagentStop:`task.completed`,Notification:`notification`};function K(e,t,n,r){return JSON.stringify({session_id:n,transcript_path:``,cwd:t,hook_event_name:e,...r})}function he(e){let t=e.config??H(e.workspaceDir);if(Object.keys(t).length===0)return[];let n=e.workspaceDir?g(e.workspaceDir):process.cwd(),r=e.runner??W,i=(e,t)=>r(e.command,t,{cwd:n,timeoutMs:(e.timeout??10)*1e3}),a=[];if(t.PreToolUse){let e=t.PreToolUse;a.push(C({name:`claude-compat:PreToolUse`,timing:`tool.execute.before`,priority:40,handle:async(t,r)=>{for(let a of e)if(U(a.matcher,t.toolName))for(let e of a.hooks){let{stdout:a,exitCode:o}=await i(e,K(`PreToolUse`,n,t.sessionId,{tool_name:c(t.toolName),tool_input:t.args})),s=G(a,o);if(s.block||s.decision===`deny`){r.cancelled=!0,r.cancelReason=s.reason??`blocked by .claude hook`,r.decision=`deny`,r.decisionReason=r.cancelReason;return}s.decision===`ask`&&(r.decision=`ask`,r.decisionReason=s.reason)}}}))}for(let[r,o]of Object.entries(me)){let s=t[r];s&&a.push(C({name:`claude-compat:${r}`,timing:o,priority:40,handle:async t=>{let a=t,o=typeof a.toolName==`string`?a.toolName:void 0,l=typeof a.notification_type==`string`?a.notification_type:void 0;for(let t of s){if(r===`Notification`){if(!U(t.matcher,l??``))continue}else if(o&&!U(t.matcher,o))continue;let s=typeof a.sessionId==`string`?a.sessionId:``,u=K(r,n,e.sessionId??s,{...o?{tool_name:c(o)}:{},...r===`Notification`?{message:a.message,...a.title?{title:a.title}:{},notification_type:l}:{}});for(let e of t.hooks)try{await i(e,u)}catch(e){B.warn({event:r,err:e},`claude hook command failed`)}}}}))}return a}const q=r(`@x-otto/claude-compat:dot-mcp-json`);function J(e){return e.replace(/\$\{([A-Za-z_][A-Za-z0-9_]*)(?::-([^}]*))?\}/g,(e,t,n)=>{let r=process.env[t];return r!==void 0&&r!==``?r:n??``})}function Y(e){if(Array.isArray(e))return e.filter(e=>typeof e==`string`).map(J)}function X(e){if(!e||typeof e!=`object`)return;let t={};for(let[n,r]of Object.entries(e))typeof r==`string`&&(t[n]=J(r));return Object.keys(t).length>0?t:void 0}function ge(e){if(!e||typeof e!=`object`)return null;let t=e,n={};typeof t.type==`string`&&(n.type=t.type),typeof t.command==`string`&&(n.command=J(t.command));let r=Y(t.args);r&&(n.args=r);let i=X(t.env);i&&(n.env=i),typeof t.url==`string`&&(n.url=J(t.url));let a=X(t.headers);return a&&(n.headers=a),typeof t.managed==`boolean`&&(n.managed=t.managed),!n.command&&!n.url?null:n}function Z(e){let t=h(g(e),`.mcp.json`);if(!f(t))return{};let n;try{n=JSON.parse(p(t,`utf-8`))}catch(e){return q.warn({path:t,err:e},`Failed to parse .mcp.json, skipped`),{}}let r=n,i=r?.mcp_servers??r?.mcpServers;if(!i||typeof i!=`object`)return{};let a={};for(let[e,t]of Object.entries(i)){let n=ge(t);n?a[e]=n:q.warn({name:e},`.mcp.json server entry invalid (no command/url), skipped`)}return a}function _e(e){return!y()||j(e).gated?{}:Z(e)}function Q(){return process.env.OTTO_STRICT_PROJECT_CONFIG!==`0`}function $(e,t={}){return!Q()||!t.interactive?!0:E(e)}function ve(e,t={}){if(!Q())return{highRiskKeys:[],gated:!1};let n;try{n=u(p(h(e,ne),`utf-8`))}catch{return{highRiskKeys:[],gated:!1}}let r=pe(n).stripped;return{highRiskKeys:r,gated:r.length>0&&!$(e,t)}}function ye(e){return e===g(S())||e===g(`/`)?!1:[`.git`,`.claude`,`.mcp.json`].some(t=>f(h(e,t)))}function be(e,t={}){let n=g(e);return t.interactive?Q()?E(e)?{shouldPrompt:!1,root:n,reason:`trusted`}:ye(n)?{shouldPrompt:!0,root:n,reason:`untrusted`}:{shouldPrompt:!1,root:n,reason:`not-a-project`}:{shouldPrompt:!1,root:n,reason:`strict-off`}:{shouldPrompt:!1,root:n,reason:`not-interactive`}}export{e as CLAUDE_TO_OTTO_TOOL,T as claudeTrustStorePath,he as createClaudeHookBridgeHooks,W as defaultHookRunner,A as detectClaudeExecutableSurface,ve as detectProjectConfigGating,F as discoverFileCommands,j as evaluateClaudeTrust,be as evaluateWorkspaceTrustPrompt,z as expandCommandBody,G as interpretPreToolUse,E as isProjectTrusted,Q as isStrictProjectConfigEnabled,H as loadClaudeHooksConfig,i as mapClaudeAgentToolNames,o as mapClaudeToolName,U as matchesHookMatcher,s as ottoToClaudeToolName,V as parseClaudeHooks,Z as parseDotMcpJson,_e as resolveClaudeMcpServers,$ as resolveProjectConfigTrusted,D as trustProject,O as untrustProject};
2
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","names":["logger","logger","mapClaudeAgentToolNames","readFileAsync","logger","ottoToClaudeToolName"],"sources":["../src/claude-trust.ts","../src/claude-commands.ts","../src/claude-hooks.ts","../src/dot-mcp-json.ts","../src/project-config-trust.ts"],"sourcesContent":["/**\n * claude-trust.ts — RFC-032 §1.5:项目级 `.claude/` 信任门(与 UI 无关的纯核心).\n *\n * 威胁:克隆任意仓库时,项目级 `.claude/` 是攻击者可控输入。其中**可执行面**\n * (`.mcp.json` 启动即跑任意二进制、`.claude/commands` 的 `!cmd` 展开即 shell)必须先经\n * 用户显式信任才加载。纯文本/受控面(CLAUDE.md、skills、agents——后者只是 prompt+工具限制,\n * 不在加载时执行)**不**受此门控,与 MVP/D1 默认开一致。\n *\n * 信任决策持久化到**用户级** `~/.otto/claude-trust.json`(绝对路径白名单)——\n * 不写进项目的 `.otto/`(避免污染被克隆仓 / git 噪声),更不写 `.claude/`。\n * 用户级 `~/.claude/`(用户自有)始终视为可信,不入此门。\n *\n * 本模块只做\"判定 + 持久化\";\"未信任时是弹窗还是静默跳过\"由调用方(CLI/TUI)决定。\n */\nimport { existsSync, readdirSync } from 'node:fs'\nimport { join, resolve } from 'node:path'\nimport { OTTO_HOME, isClaudeCompatEnabled } from '@x-otto/env'\nimport { createLogger } from '@x-otto/shared'\nimport { isPathTrusted, trustPath, untrustPath } from '@x-otto/plugin'\n\nconst logger = createLogger('@x-otto/claude-compat:claude-trust')\n\n/**\n * 信任白名单落盘路径(用户级)。`OTTO_CLAUDE_TRUST_PATH` 可覆盖(测试/隔离用,\n * 避免污染真实 `~/.otto`——参照 auth-store 真路径教训)。调用时求值。\n */\nexport function claudeTrustStorePath(): string {\n return process.env['OTTO_CLAUDE_TRUST_PATH'] || join(OTTO_HOME, 'claude-trust.json')\n}\n\n/** 该项目根的 `.claude/` 可执行面是否已被用户显式信任。 */\nexport function isProjectTrusted(\n workspaceDir: string,\n storePath = claudeTrustStorePath(),\n): boolean {\n return isPathTrusted(storePath, workspaceDir)\n}\n\n/** 显式信任某项目根(幂等,持久化)。 */\nexport function trustProject(workspaceDir: string, storePath = claudeTrustStorePath()): void {\n if (trustPath(storePath, workspaceDir, 'claude-trust')) {\n logger.info({ root: resolve(workspaceDir) }, 'Project .claude/ trusted')\n }\n}\n\n/** 撤销对某项目根的信任(持久化)。 */\nexport function untrustProject(workspaceDir: string, storePath = claudeTrustStorePath()): void {\n if (untrustPath(storePath, workspaceDir, 'claude-trust')) {\n logger.info({ root: resolve(workspaceDir) }, 'Project .claude/ trust revoked')\n }\n}\n\n/** 项目级 `.claude/` 受信任门面探测结果(可执行面 ① + 内容面 ②,RFC-081 O1)。 */\nexport interface ClaudeExecutableSurface {\n /** 项目根存在 `.mcp.json`(stdio server 启动即跑二进制)。 */\n mcpJson: boolean\n /** 项目 `.claude/commands/` 非空(`!cmd` 展开即 shell)。 */\n commands: boolean\n /** 项目 `.claude/agents/` 非空(攻击者可控 agent profile,默认继承全工具;O1 内容面)。 */\n agents: boolean\n /** 项目 `.claude/skills/` 非空(攻击者可控 prompt 内容;O1 内容面)。 */\n skills: boolean\n /** 任一受信任门面存在。 */\n any: boolean\n}\n\nfunction dirHasEntries(dir: string): boolean {\n try {\n return readdirSync(dir).length > 0\n } catch {\n return false\n }\n}\n\n/**\n * 探测项目根 `.claude/` 的受信任门面(不读内容,只看存在性)。\n * 含可执行面(.mcp.json/commands)+ 内容面(agents/skills,RFC-081 O1)——\n * 后者是攻击者可控 prompt/profile,与可执行面同属克隆敌仓的注入向量(对齐 Claude Code)。\n */\nexport function detectClaudeExecutableSurface(workspaceDir: string): ClaudeExecutableSurface {\n const root = resolve(workspaceDir)\n const mcpJson = existsSync(join(root, '.mcp.json'))\n const commands = dirHasEntries(join(root, '.claude', 'commands'))\n const agents = dirHasEntries(join(root, '.claude', 'agents'))\n const skills = dirHasEntries(join(root, '.claude', 'skills'))\n return { mcpJson, commands, agents, skills, any: mcpJson || commands || agents || skills }\n}\n\n/** 信任门综合判定。 */\nexport interface ClaudeTrustVerdict {\n surface: ClaudeExecutableSurface\n trusted: boolean\n /** true = 存在可执行面且未信任(调用方应门控:弹窗或跳过);false = 可直接加载。 */\n gated: boolean\n}\n\n/**\n * 评估项目 `.claude/` 是否需要门控。\n * `gated` 为 true 仅当:兼容开 且 存在可执行面 且 该项目未被信任。\n */\nexport function evaluateClaudeTrust(\n workspaceDir: string,\n storePath = claudeTrustStorePath(),\n): ClaudeTrustVerdict {\n const surface = detectClaudeExecutableSurface(workspaceDir)\n const trusted = isProjectTrusted(workspaceDir, storePath)\n const gated = isClaudeCompatEnabled() && surface.any && !trusted\n return { surface, trusted, gated }\n}\n","/**\n * claude-commands.ts — RFC-032 D2:文件式 slash 命令(`.claude/commands` + `.otto/commands`).\n *\n * Claude Code 的 `.claude/commands/**\\/*.md`:frontmatter(description/argument-hint/allowed-tools/\n * model)+ 正文模板。子目录 → namespace(`frontend/component.md` → `frontend:component`)。\n * 正文展开:`$ARGUMENTS`、`$1..$9`、`@file`(注入文件内容)、`` !`cmd` ``(执行 shell 注入 stdout)。\n *\n * 安全(§1.5):项目 `.claude/commands` 是可执行面(`!cmd` = shell)。发现层按信任门过滤——\n * 未信任项目不加载其 `.claude/commands`。`.otto/commands`(用户自有)始终加载。\n * 展开器的 `!cmd`/`@file` 执行器可注入(测试隔离),默认实现带超时防挂死。\n */\nimport { execFile } from 'node:child_process'\nimport { existsSync, readdirSync, readFileSync } from 'node:fs'\nimport { readFile as readFileAsync } from 'node:fs/promises'\nimport { homedir } from 'node:os'\nimport { join, relative, resolve, sep } from 'node:path'\nimport { promisify } from 'node:util'\n// RFC-161 M1(R8 重路由):findWorkspaceRoot 此前经 coding/src/plugin/install-plugin.ts\n// 间接引用,该文件本身也只是对 @x-otto/env 的 re-export(RFC-130 终局审核后收敛的单一实现)\n// ——迁移后直连真源,不再经过 coding 内部模块。\nimport { isClaudeCompatEnabled, findWorkspaceRoot } from '@x-otto/env'\nimport { createLogger } from '@x-otto/shared'\nimport { splitFrontmatter, parseFrontmatterList, mapClaudeAgentToolNames } from '@x-otto/shared'\nimport { evaluateClaudeTrust } from './claude-trust'\nimport { evaluatePluginTrust } from '@x-otto/plugin'\nimport { discoverPlugins, type DiscoveredPlugin } from '@x-otto/plugin'\n\nconst logger = createLogger('@x-otto/claude-compat:claude-commands')\n\nexport interface FileCommand {\n /** 命名空间名(子目录 `/` → `:`),如 `frontend:component` 或 `review`。 */\n name: string\n description?: string\n argumentHint?: string\n /** §3.0 归一后的工具名(仅提示/将来收敛用,D2 不强制收敛回合工具集——见 RFC §10 C8)。 */\n allowedTools?: string[]\n model?: string\n body: string\n source: 'claude' | 'otto' | 'plugin'\n scope: 'project' | 'repository' | 'user'\n /** 贡献来源插件 id(`source==='plugin'` 时存在)= unregister key(RFC-044 R6)。 */\n pluginId?: string\n filePath: string\n}\n\ninterface ScanLayer {\n dir: string\n source: 'claude' | 'otto' | 'plugin'\n scope: 'project' | 'repository' | 'user'\n /** plugin 层的来源 id(source==='plugin')。 */\n pluginId?: string\n}\n\n/** 递归收集目录下的 *.md,返回 [相对路径(无扩展名), 绝对路径]。 */\nfunction walkMarkdown(root: string): [string, string][] {\n const out: [string, string][] = []\n const walk = (dir: string): void => {\n let entries: import('node:fs').Dirent[]\n try {\n entries = readdirSync(dir, { withFileTypes: true })\n } catch {\n return\n }\n for (const e of entries) {\n const full = join(dir, e.name)\n if (e.isDirectory()) walk(full)\n else if (e.isFile() && e.name.endsWith('.md')) {\n const rel = relative(root, full).slice(0, -'.md'.length)\n out.push([rel, full])\n }\n }\n }\n walk(root)\n return out\n}\n\nfunction parseCommandFile(relName: string, filePath: string, layer: ScanLayer): FileCommand | null {\n let content: string\n try {\n content = readFileSync(filePath, 'utf-8')\n } catch {\n return null\n }\n const parsed = splitFrontmatter(content)\n const fields = parsed?.fields\n const body = parsed ? parsed.body : content.trim()\n if (!body) return null\n\n const allowedRaw = parseFrontmatterList(\n fields?.get('allowed-tools') ?? fields?.get('allowedtools'),\n )\n const name = relName.split(sep).join(':')\n return {\n name,\n description: fields?.get('description'),\n argumentHint: fields?.get('argument-hint') ?? fields?.get('argumenthint'),\n allowedTools:\n layer.source === 'claude' && allowedRaw ? mapClaudeAgentToolNames(allowedRaw) : allowedRaw,\n model: fields?.get('model'),\n body,\n source: layer.source,\n scope: layer.scope,\n pluginId: layer.pluginId,\n filePath,\n }\n}\n\n/**\n * 发现文件式命令。优先级(低→高,同名 last-wins):\n * plugin[*] < user/claude < user/otto < project/claude < project/otto。\n * 插件命令最低(R7:otto/claude 永远覆盖插件同名命令)。\n * `.claude/commands` 仅在兼容开且项目已信任(未 gated)时纳入;\n * 项目级插件命令是可执行面(`!cmd`),未信任(gated)则跳过(RFC-044 R4)。\n *\n * `plugins` 缺省自动发现 `<.otto>/plugins`(项目+用户);可注入供测试。\n */\nexport function discoverFileCommands(\n workspaceDir: string,\n plugins: DiscoveredPlugin[] = discoverPlugins({ cwd: resolve(workspaceDir), homedir: homedir() }),\n): FileCommand[] {\n const ws = resolve(workspaceDir)\n const home = homedir()\n const compat = isClaudeCompatEnabled()\n const claudeAllowed = compat && !evaluateClaudeTrust(ws).gated\n\n const pluginLayers: ScanLayer[] = plugins\n // builtin scope 插件无真实磁盘目录(合成占位 dir,见 discovery.ts BUILTIN_PLUGINS 注释),\n // 天然没有 `commands/` 子目录可扫描。显式类型谓词(而非普通 boolean filter)让 TS 把窄化后的\n // scope 类型带入后续 .map()(数组方法链上 TS 不会自动跨调用传播窄化,需要类型谓词兜底)。\n .filter((p): p is DiscoveredPlugin & { scope: 'project' | 'repository' | 'user' } => p.scope !== 'builtin')\n .filter((p) => p.manifest.contributes?.commands !== false)\n .filter((p) => !evaluatePluginTrust(p).gated)\n .map((p) => ({\n dir: join(p.dir, 'commands'),\n source: 'plugin' as const,\n scope: p.scope,\n pluginId: p.id,\n }))\n\n // 仓库级 .otto/commands(RFC-130):当 cwd 在 monorepo 子目录时,额外扫描 workspace root 的命令。\n const workspaceRoot = findWorkspaceRoot(ws)\n const repoLayer: ScanLayer | undefined =\n workspaceRoot && resolve(workspaceRoot) !== ws\n ? { dir: join(resolve(workspaceRoot), '.otto', 'commands'), source: 'otto', scope: 'repository' }\n : undefined\n\n const layers: ScanLayer[] = [\n ...pluginLayers,\n ...(compat\n ? [\n {\n dir: join(home, '.claude', 'commands'),\n source: 'claude' as const,\n scope: 'user' as const,\n },\n ]\n : []),\n { dir: join(home, '.otto', 'commands'), source: 'otto', scope: 'user' },\n ...(repoLayer ? [repoLayer] : []),\n ...(claudeAllowed\n ? [\n {\n dir: join(ws, '.claude', 'commands'),\n source: 'claude' as const,\n scope: 'project' as const,\n },\n ]\n : []),\n { dir: join(ws, '.otto', 'commands'), source: 'otto', scope: 'project' },\n ]\n\n const byName = new Map<string, FileCommand>()\n for (const layer of layers) {\n if (!existsSync(layer.dir)) continue\n for (const [rel, full] of walkMarkdown(layer.dir)) {\n const cmd = parseCommandFile(rel, full, layer)\n if (cmd) byName.set(cmd.name, cmd)\n }\n }\n const commands = [...byName.values()]\n if (commands.length > 0) {\n logger.info(\n 'Discovered %d file command(s): %s',\n commands.length,\n commands.map((c) => c.name).join(', '),\n )\n }\n return commands\n}\n\nconst execFileAsync = promisify(execFile)\n\n/** 默认 shell 执行器:经 `sh -c`,带超时与输出上限,防挂死。 */\nasync function defaultRunShell(cmd: string, cwd: string): Promise<string> {\n try {\n const { stdout } = await execFileAsync('sh', ['-c', cmd], {\n cwd,\n timeout: 10_000,\n maxBuffer: 1024 * 1024,\n })\n return stdout.trimEnd()\n } catch (err) {\n return `[command failed: ${(err as Error).message}]`\n }\n}\n\nexport interface ExpandOptions {\n args: string[]\n workspaceDir: string\n /** `` !`cmd` `` 执行器(默认 sh -c + 超时)。 */\n runShell?: (cmd: string, cwd: string) => Promise<string>\n /** `@file` 读取器(默认相对 workspaceDir 读 utf-8)。 */\n readFileText?: (relPath: string, cwd: string) => Promise<string>\n}\n\nasync function defaultReadFile(relPath: string, cwd: string): Promise<string> {\n try {\n return await readFileAsync(resolve(cwd, relPath), 'utf-8')\n } catch (err) {\n return `[file unavailable: ${relPath}]`\n }\n}\n\n/**\n * 展开命令正文。顺序:先 `$ARGUMENTS`/`$N`(位置参数),再 `@file`,最后 `` !`cmd` ``\n * (故 cmd/file 引用里可含已替换的参数)。\n */\nexport async function expandCommandBody(body: string, opts: ExpandOptions): Promise<string> {\n const args = opts.args\n const runShell = opts.runShell ?? defaultRunShell\n const readText = opts.readFileText ?? defaultReadFile\n\n // 全用函数式 replace:替换串(args/文件内容/命令输出)含 $&/$1/$$ 等不会被当特殊替换模式解析(注入防御)。\n let out = body.replace(/\\$ARGUMENTS\\b/g, () => args.join(' '))\n out = out.replace(/\\$([1-9])\\b/g, (_m, d: string) => args[Number(d) - 1] ?? '')\n\n // 用 index-based 切片替换(而非 replace/replaceAll),防止已展开内容中碰巧含\n // 其他文件的 @ref 或 !`cmd` 引用时误替换(跨文件注入)。逆序遍历,前面的 index 始终有效。\n const fileRefs = [...out.matchAll(/(^|\\s)@([^\\s`]+)/g)]\n for (const m of fileRefs.reverse()) {\n const relPath = m[2]!\n const content = await readText(relPath, opts.workspaceDir)\n const replacement = `${m[1]}\\n\\`\\`\\`\\n${content}\\n\\`\\`\\``\n out = out.slice(0, m.index!) + replacement + out.slice(m.index! + m[0].length)\n }\n\n const cmdRefs = [...out.matchAll(/!`([^`]+)`/g)]\n for (const m of cmdRefs.reverse()) {\n const stdout = await runShell(m[1]!, opts.workspaceDir)\n out = out.slice(0, m.index!) + stdout + out.slice(m.index! + m[0].length)\n }\n\n return out\n}\n","/**\n * claude-hooks.ts — RFC-032 D5:Claude Code hooks → otto hook 命令桥(最高风险=任意命令执行).\n *\n * Claude hooks 是「事件 → matcher → shell command + JSON 协议」;otto hooks 是进程内 TS。\n * 本桥把 Claude 事件映射到 otto timing,命中 matcher 时按 Claude 协议 fork command。\n *\n * 实测纠正(v2 review):otto `HookSpec.timing` 单数,故**每 timing 一个 HookSpec**共享逻辑;\n * `tool.execute.before` 有三态 `decision`(allow/deny/ask)+ `cancelled`,PreToolUse 可干净映射;\n * `Stop`→`stream.end`、`SubagentStop`→`task.completed`、`Notification`→`notification`(observer,按 notification_type 匹配 matcher)。\n *\n * 安全:**默认关**(`isClaudeHooksCompatEnabled`)+ 项目信任门(未信任不桥)+ 命令带超时\n * (默认 10s,复刻 Claude `timeout`)+ spawn 串 kill 防挂死([[subagent-hang-blocking-vectors]])。\n */\nimport { spawn } from 'node:child_process'\nimport { existsSync, readFileSync } from 'node:fs'\nimport { join, resolve } from 'node:path'\nimport { isClaudeHooksCompatEnabled, CLAUDE_HOME } from '@x-otto/env'\nimport { buildAllowedEnv, checkShellCommand, createLogger, parseJsonc, ottoToClaudeToolName } from '@x-otto/shared'\nimport { defineHook, type HookSpec } from '@x-otto/hooks'\nimport { evaluateClaudeTrust } from './claude-trust'\nimport { globalProcessRuntime } from '@x-otto/runtime'\n\nconst logger = createLogger('@x-otto/claude-compat:claude-hooks')\n\nexport interface ClaudeHookCommand {\n type?: string\n command: string\n timeout?: number\n}\nexport interface ClaudeHookGroup {\n matcher?: string\n hooks: ClaudeHookCommand[]\n}\n/** event 名 → matcher 组列表。 */\nexport type ClaudeHooksConfig = Record<string, ClaudeHookGroup[]>\n\n/** 从 settings.json 原始对象解析 hooks 段(容错;非法项跳过)。 */\nexport function parseClaudeHooks(raw: unknown): ClaudeHooksConfig {\n const hooks = (raw as { hooks?: unknown })?.hooks\n if (!hooks || typeof hooks !== 'object') return {}\n const out: ClaudeHooksConfig = {}\n for (const [event, groups] of Object.entries(hooks as Record<string, unknown>)) {\n if (!Array.isArray(groups)) continue\n const parsed: ClaudeHookGroup[] = []\n for (const g of groups) {\n if (!g || typeof g !== 'object') continue\n const grp = g as Record<string, unknown>\n const matcher = typeof grp.matcher === 'string' ? grp.matcher : undefined\n const cmds = Array.isArray(grp.hooks) ? grp.hooks : []\n const commands: ClaudeHookCommand[] = []\n for (const c of cmds) {\n if (c && typeof c === 'object') {\n const cmd = c as Record<string, unknown>\n if (typeof cmd.command === 'string') {\n commands.push({\n type: typeof cmd.type === 'string' ? cmd.type : undefined,\n command: cmd.command,\n timeout: typeof cmd.timeout === 'number' ? cmd.timeout : undefined,\n })\n }\n }\n }\n if (commands.length > 0) parsed.push({ matcher, hooks: commands })\n }\n if (parsed.length > 0) out[event] = parsed\n }\n return out\n}\n\n/** 加载 `.claude` hooks(默认关 / 未信任 → {})。 */\nexport function loadClaudeHooksConfig(workspaceDir?: string): ClaudeHooksConfig {\n if (!isClaudeHooksCompatEnabled()) return {}\n if (workspaceDir && evaluateClaudeTrust(workspaceDir).gated) return {}\n const files = [\n join(CLAUDE_HOME, 'settings.json'),\n ...(workspaceDir\n ? [\n join(resolve(workspaceDir), '.claude', 'settings.json'),\n join(resolve(workspaceDir), '.claude', 'settings.local.json'),\n ]\n : []),\n ]\n const merged: ClaudeHooksConfig = {}\n for (const file of files) {\n if (!existsSync(file)) continue\n try {\n const cfg = parseClaudeHooks(parseJsonc(readFileSync(file, 'utf-8')))\n for (const [event, groups] of Object.entries(cfg)) {\n merged[event] = [...(merged[event] ?? []), ...groups]\n }\n } catch (err) {\n logger.warn({ file, err }, 'Failed to parse .claude hooks, skipped')\n }\n }\n return merged\n}\n\n/** matcher 匹配:空/`*` → 全匹配;否则按 Claude 名(+otto 名兜底)做正则。 */\nexport function matchesHookMatcher(matcher: string | undefined, ottoToolName: string): boolean {\n if (!matcher || matcher === '*') return true\n const claudeName = ottoToClaudeToolName(ottoToolName)\n let re: RegExp\n try {\n re = new RegExp(matcher)\n } catch {\n return matcher === claudeName || matcher === ottoToolName\n }\n return re.test(claudeName) || re.test(ottoToolName)\n}\n\nexport interface HookRunResult {\n stdout: string\n exitCode: number\n}\nexport type HookRunner = (\n command: string,\n stdin: string,\n opts: { cwd: string; timeoutMs: number },\n) => Promise<HookRunResult>\n\n/** 默认 runner:`sh -c command`,写 stdin、超时 kill、收 stdout(防挂死)。\n * 防御纵深(endgame review B1):checkShellCommand 拦已知危险模式 + buildAllowedEnv\n * 白名单过滤环境变量(不泄漏 API key/token 给外部 hook 命令)——与 plugin-hooks 同基线。 */\nexport const defaultHookRunner: HookRunner = (command, stdin, { cwd, timeoutMs }) =>\n new Promise<HookRunResult>((res) => {\n let done = false\n const finish = (exitCode: number, stdout: string): void => {\n if (!done) {\n done = true\n res({ stdout, exitCode })\n }\n }\n const safety = checkShellCommand(command)\n if (!safety.safe) {\n logger.warn({ command, reason: safety.reason }, 'Claude hook command blocked by safety check')\n return finish(126, '')\n }\n let stdout = ''\n let child: ReturnType<typeof spawn>\n try {\n child = spawn('sh', ['-c', command], { cwd, env: buildAllowedEnv() })\n // RFC-085 M4: register for unified kill tracking (short-lived, self-terminating)\n globalProcessRuntime.registerChild(\n { command: 'sh', args: ['-c', command], owner: { type: 'agent-tool', id: 'claude-hook' }, category: 'tool', lifecycle: 'evictable', cwd },\n child,\n )\n } catch {\n return finish(1, '')\n }\n const timer = setTimeout(() => {\n child.kill('SIGKILL')\n finish(124, stdout)\n }, timeoutMs)\n child.stdout?.on('data', (d) => (stdout += String(d)))\n child.on('error', () => {\n clearTimeout(timer)\n finish(1, stdout)\n })\n child.on('close', (code) => {\n clearTimeout(timer)\n finish(code ?? 0, stdout)\n })\n try {\n child.stdin?.write(stdin)\n child.stdin?.end()\n } catch {}\n })\n\nexport interface PreToolUseVerdict {\n decision?: 'allow' | 'deny' | 'ask'\n block?: boolean\n reason?: string\n}\n\n/** 解释 PreToolUse 钩子输出:exit 2 / decision:block / continue:false → 阻断;permissionDecision → 决策。 */\nexport function interpretPreToolUse(stdout: string, exitCode: number): PreToolUseVerdict {\n if (exitCode === 2) return { block: true, reason: stdout.trim() || 'hook exit 2' }\n const text = stdout.trim()\n if (!text) return {}\n let json: Record<string, unknown> | undefined\n try {\n json = JSON.parse(text) as Record<string, unknown>\n } catch {\n return {}\n }\n if (json?.decision === 'block' || json?.continue === false) {\n return { block: true, reason: (json.reason ?? json.stopReason) as string | undefined }\n }\n const hso = json?.hookSpecificOutput as Record<string, unknown> | undefined\n if (hso?.permissionDecision) {\n return {\n decision: hso.permissionDecision as 'allow' | 'deny' | 'ask' | undefined,\n reason: hso.permissionDecisionReason as string | undefined,\n }\n }\n return {}\n}\n\n/**\n * Claude 事件 → otto observer timing。`Stop`→`stream.end`(回合结束)、`SubagentStop`→\n * `task.completed`(子代理/任务完成)均为 fire-and-forget;`Notification`→`notification`(按 notification_type 匹配 matcher)。\n */\nconst OBSERVER_EVENTS: Record<string, string> = {\n PostToolUse: 'tool.execute.after',\n UserPromptSubmit: 'chat.message.before',\n PreCompact: 'compaction.before',\n SessionStart: 'session.created',\n SessionEnd: 'session.deleted',\n Stop: 'stream.end',\n SubagentStop: 'task.completed',\n Notification: 'notification',\n}\n\nexport interface ClaudeHookBridgeDeps {\n workspaceDir?: string\n sessionId?: string\n runner?: HookRunner\n /** 注入配置(缺省经 loadClaudeHooksConfig 读盘)。 */\n config?: ClaudeHooksConfig\n}\n\nfunction payload(\n event: string,\n cwd: string,\n sessionId: string,\n extra: Record<string, unknown>,\n): string {\n return JSON.stringify({\n session_id: sessionId,\n transcript_path: '',\n cwd,\n hook_event_name: event,\n ...extra,\n })\n}\n\n/**\n * 构造 Claude hooks 桥的 otto HookSpec 列表(默认关 / 无配置 → [])。\n * PreToolUse = 拦截器(可 deny/ask/block);其余映射事件 = 观察器(fire-and-forget)。\n */\nexport function createClaudeHookBridgeHooks(deps: ClaudeHookBridgeDeps): HookSpec[] {\n const config = deps.config ?? loadClaudeHooksConfig(deps.workspaceDir)\n if (Object.keys(config).length === 0) return []\n\n const cwd = deps.workspaceDir ? resolve(deps.workspaceDir) : process.cwd()\n const runner = deps.runner ?? defaultHookRunner\n const run = (c: ClaudeHookCommand, stdin: string): Promise<HookRunResult> =>\n runner(c.command, stdin, { cwd, timeoutMs: (c.timeout ?? 10) * 1000 })\n\n const specs: HookSpec[] = []\n\n if (config['PreToolUse']) {\n const groups = config['PreToolUse']\n specs.push(\n defineHook({\n name: 'claude-compat:PreToolUse',\n timing: 'tool.execute.before',\n priority: 40,\n handle: async (input, output) => {\n for (const g of groups) {\n if (!matchesHookMatcher(g.matcher, input.toolName)) continue\n for (const c of g.hooks) {\n const stdin = payload('PreToolUse', cwd, input.sessionId, {\n tool_name: ottoToClaudeToolName(input.toolName),\n tool_input: input.args,\n })\n const { stdout, exitCode } = await run(c, stdin)\n const v = interpretPreToolUse(stdout, exitCode)\n if (v.block || v.decision === 'deny') {\n output.cancelled = true\n output.cancelReason = v.reason ?? 'blocked by .claude hook'\n output.decision = 'deny'\n output.decisionReason = output.cancelReason\n return\n }\n if (v.decision === 'ask') {\n output.decision = 'ask'\n output.decisionReason = v.reason\n }\n }\n }\n },\n }),\n )\n }\n\n for (const [event, timing] of Object.entries(OBSERVER_EVENTS)) {\n const groups = config[event]\n if (!groups) continue\n specs.push(\n defineHook({\n name: `claude-compat:${event}`,\n timing: timing as Parameters<typeof defineHook>[0]['timing'],\n priority: 40,\n handle: async (input: unknown) => {\n const inp = input as Record<string, unknown>\n const toolName = typeof inp.toolName === 'string' ? inp.toolName : undefined\n const notifType =\n typeof inp.notification_type === 'string' ? inp.notification_type : undefined\n for (const g of groups) {\n if (event === 'Notification') {\n if (!matchesHookMatcher(g.matcher, notifType ?? '')) continue\n } else if (toolName && !matchesHookMatcher(g.matcher, toolName)) {\n continue\n }\n const sessionId = typeof inp.sessionId === 'string' ? inp.sessionId : ''\n const stdin = payload(event, cwd, deps.sessionId ?? sessionId, {\n ...(toolName ? { tool_name: ottoToClaudeToolName(toolName) } : {}),\n ...(event === 'Notification'\n ? {\n message: inp.message,\n ...(inp.title ? { title: inp.title } : {}),\n notification_type: notifType,\n }\n : {}),\n })\n for (const c of g.hooks) {\n try {\n await run(c, stdin)\n } catch (err) {\n logger.warn({ event, err }, 'claude hook command failed')\n }\n }\n }\n },\n }),\n )\n }\n\n return specs\n}\n","/**\n * dot-mcp-json.ts — RFC-032 D3:读项目根 `.mcp.json`(Claude Code 的 MCP server 配置).\n *\n * `.mcp.json` 形如 `{ \"mcp_servers\": { \"<name>\": { command/args/env/url/type/headers } } }`,\n * 字段与 otto `McpServerConfig` 基本同构。otto 自有文件用统一键 `mcp_servers`;解析时亦回退兼容\n * Claude Code 原生驼峰键 `mcpServers`(读第三方 `.mcp.json`)。支持 `${VAR}` / `${VAR:-default}` 环境变量展开(C2)。\n *\n * **信任门(§1.5)**:`.mcp.json` 的 stdio server 启动即跑任意二进制 = 可执行面。\n * 仅当兼容开、且项目已信任(`evaluateClaudeTrust().gated === false`)时才返回 server;\n * 否则返回空(静默跳过,等用户 `/compat trust`)。otto `settings.mcp_servers` 始终覆盖同名项。\n */\nimport { existsSync, readFileSync } from 'node:fs'\nimport { join, resolve } from 'node:path'\nimport type { McpServerConfig } from '@x-otto/mcp'\nimport { isClaudeCompatEnabled } from '@x-otto/env'\nimport { createLogger } from '@x-otto/shared'\nimport { evaluateClaudeTrust } from './claude-trust'\n\nconst logger = createLogger('@x-otto/claude-compat:dot-mcp-json')\n\n/** 展开 `${VAR}` 与 `${VAR:-default}`(未设或空 → default/空串),遵循 shell `:-` 语义。 */\nfunction expandEnv(value: string): string {\n return value.replace(\n /\\$\\{([A-Za-z_][A-Za-z0-9_]*)(?::-([^}]*))?\\}/g,\n (_m, name: string, def: string | undefined) => {\n const v = process.env[name]\n return v !== undefined && v !== '' ? v : (def ?? '')\n },\n )\n}\n\nfunction expandStringArray(arr: unknown): string[] | undefined {\n if (!Array.isArray(arr)) return undefined\n return arr.filter((x): x is string => typeof x === 'string').map(expandEnv)\n}\n\nfunction expandStringRecord(rec: unknown): Record<string, string> | undefined {\n if (!rec || typeof rec !== 'object') return undefined\n const out: Record<string, string> = {}\n for (const [k, v] of Object.entries(rec as Record<string, unknown>)) {\n if (typeof v === 'string') out[k] = expandEnv(v)\n }\n return Object.keys(out).length > 0 ? out : undefined\n}\n\n/** 把一条 `.mcp.json` server 原始项映射成 otto McpServerConfig(仅已知字段,含 env 展开)。 */\nfunction mapServerEntry(raw: unknown): McpServerConfig | null {\n if (!raw || typeof raw !== 'object') return null\n const r = raw as Record<string, unknown>\n const config: McpServerConfig = {}\n if (typeof r['type'] === 'string') config.type = r['type'] as McpServerConfig['type']\n if (typeof r['command'] === 'string') config.command = expandEnv(r['command'])\n const args = expandStringArray(r['args'])\n if (args) config.args = args\n const env = expandStringRecord(r['env'])\n if (env) config.env = env\n if (typeof r['url'] === 'string') config.url = expandEnv(r['url'])\n const headers = expandStringRecord(r['headers'])\n if (headers) config.headers = headers\n if (typeof r['managed'] === 'boolean') config.managed = r['managed'] as boolean\n if (!config.command && !config.url) return null\n return config\n}\n\n/** 解析项目根 `.mcp.json` 的 mcp_servers(回退兼容 mcpServers);不做信任门判断;缺失/损坏 → {}。 */\nexport function parseDotMcpJson(workspaceDir: string): Record<string, McpServerConfig> {\n const path = join(resolve(workspaceDir), '.mcp.json')\n if (!existsSync(path)) return {}\n let raw: unknown\n try {\n raw = JSON.parse(readFileSync(path, 'utf-8'))\n } catch (err) {\n logger.warn({ path, err }, 'Failed to parse .mcp.json, skipped')\n return {}\n }\n // otto 统一键 `mcp_servers` 优先;`mcpServers`(Claude Code 原生驼峰)作外部兼容回退。\n const rawObj = raw as { mcp_servers?: unknown; mcpServers?: unknown }\n const servers = rawObj?.mcp_servers ?? rawObj?.mcpServers\n if (!servers || typeof servers !== 'object') return {}\n\n const out: Record<string, McpServerConfig> = {}\n for (const [name, entry] of Object.entries(servers as Record<string, unknown>)) {\n const mapped = mapServerEntry(entry)\n if (mapped) out[name] = mapped\n else logger.warn({ name }, '.mcp.json server entry invalid (no command/url), skipped')\n }\n return out\n}\n\n/**\n * 信任门后的 `.mcp.json` server:兼容关 / 未信任(gated)→ {};否则解析返回。\n * 调用方应把它作为**低优先级**与 settings.mcp_servers 合并(otto 同名覆盖)。\n */\nexport function resolveClaudeMcpServers(workspaceDir: string): Record<string, McpServerConfig> {\n if (!isClaudeCompatEnabled()) return {}\n if (evaluateClaudeTrust(workspaceDir).gated) return {}\n return parseDotMcpJson(workspaceDir)\n}\n","/**\n * project-config-trust.ts —— 决定项目 `.otto/config.json` 是否受信任(喂给 SettingsManager).\n *\n * 缺省**严格开**(`OTTO_STRICT_PROJECT_CONFIG !== '0'`)→ 克隆仓库的高危键默认不生效。\n * 复用既有项目信任白名单(claude-trust 的 `~/.otto/claude-trust.json`,经 `/trust trust` 写入)\n * 判定:已信任 → 全量加载;未信任(如刚克隆的仓库)→ SettingsManager 剔除高危键\n * (permission_mode/mcp_servers/… 见 @x-otto/setting project-trust)。\n *\n * **仅交互态门控**:headless(print/serve/sdk/测试)一律放行——对照 CC `--print` 跳过\n * securityCheck 的 fail-open。脚本/CI 无人可应答信任,强行剔除会破坏合法的项目级 mcp_servers\n * (如 m14 测试写临时项目 mcp_servers);门控只在能给出 `/trust trust` 出口的交互态生效。\n *\n * 为何 env 门控而非 config 开关:开关本身若可被项目 config 设置,则恶意仓库可自关门\n * (`strict_project_config: false`)。env 不在项目可控面内,杜绝该自我提权。\n *\n * 本模块还产出**工作区信任弹窗**的纯决策(`evaluateWorkspaceTrustPrompt`,见 doc §8):\n * 启动时是否要弹\"信任此文件夹吗\"。纯函数(只读 FS + env),渲染/动作在 cli/tui 层。\n */\nimport { existsSync, readFileSync } from 'node:fs'\nimport { homedir } from 'node:os'\nimport { join, resolve } from 'node:path'\nimport { OTTO_PROJECT_CONFIG_RELPATH } from '@x-otto/env'\nimport { parseJsonc } from '@x-otto/shared'\nimport { sanitizeUntrustedProjectLayer } from '@x-otto/setting'\nimport type { Setting } from '@x-otto/setting'\nimport { isProjectTrusted } from './claude-trust'\n\n/**\n * 严格项目配置门控是否开启(env-only,项目 config 不可影响,杜绝恶意仓库自关门)。\n * **默认开**(安全默认):克隆仓库的 `.otto/config.json` 高危键默认不生效,须 `/compat trust`。\n * `OTTO_STRICT_PROJECT_CONFIG=0` 是逃生口(关门控、恢复旧的全信任行为)。\n * 注:仅交互态实际剔除(headless fail-open,见 resolveProjectConfigTrusted),故 CI/脚本/测试不受影响。\n */\nexport function isStrictProjectConfigEnabled(): boolean {\n return process.env['OTTO_STRICT_PROJECT_CONFIG'] !== '0'\n}\n\nexport interface ProjectConfigTrustContext {\n /** 是否交互(TUI)态。缺省 false=headless → fail-open 放行。 */\n interactive?: boolean\n}\n\n/**\n * 该 workspace 的项目配置是否受信任。\n * 严格模式关 → 恒 `true`;headless → 恒 `true`(fail-open);交互+严格 → 取信任白名单判定。\n */\nexport function resolveProjectConfigTrusted(\n workspaceDir: string,\n ctx: ProjectConfigTrustContext = {},\n): boolean {\n if (!isStrictProjectConfigEnabled()) return true\n if (!ctx.interactive) return true\n return isProjectTrusted(workspaceDir)\n}\n\nexport interface ProjectConfigGating {\n /** 项目 `.otto/config.json` 实际含有的高危键(空 = 无可门控内容)。 */\n highRiskKeys: string[]\n /** 这些高危键当前是否被剔除(严格开 + 交互 + 未信任)。 */\n gated: boolean\n}\n\n/**\n * 探测项目 `.otto/config.json` 的高危键门控状态(供 `/compat status` 呈现)。\n * 严格模式关 → 返空(特性未启用,无需呈现噪声)。读文件失败/无文件 → 返空。\n */\nexport function detectProjectConfigGating(\n workspaceDir: string,\n ctx: ProjectConfigTrustContext = {},\n): ProjectConfigGating {\n if (!isStrictProjectConfigEnabled()) return { highRiskKeys: [], gated: false }\n let raw: Partial<Setting>\n try {\n const content = readFileSync(join(workspaceDir, OTTO_PROJECT_CONFIG_RELPATH), 'utf-8')\n raw = parseJsonc<Partial<Setting>>(content)\n } catch {\n return { highRiskKeys: [], gated: false }\n }\n const highRiskKeys = sanitizeUntrustedProjectLayer(raw).stripped\n const gated = highRiskKeys.length > 0 && !resolveProjectConfigTrusted(workspaceDir, ctx)\n return { highRiskKeys, gated }\n}\n\n/** 用户对工作区信任弹窗的选择(见 doc §8.3 三选)。 */\nexport type WorkspaceTrustAction = 'trust' | 'readonly' | 'exit'\n\nexport interface WorkspaceTrustPromptDecision {\n /** 是否应弹\"信任此文件夹吗\"。 */\n shouldPrompt: boolean\n /** 解析到的工作区根(信任按它记录)。 */\n root: string\n /** 不弹的原因(诊断/测试):trusted=已信任 · not-interactive · strict-off · not-a-project · untrusted=应弹。 */\n reason: 'trusted' | 'not-interactive' | 'strict-off' | 'not-a-project' | 'untrusted'\n}\n\n/**\n * \"真项目\"判定:值得门控的目录。排除 $HOME / 文件系统根(随手 cd 不该弹),\n * 要求有 vcs/config marker(.git/.claude/.mcp.json)——即 otto/git 实际会作用的项目。\n * 纯 scratch 目录(无 marker)不弹,避免过度打扰。\n *\n * 重要:`.otto` 已从标记列表移除(根因修复,真实用户反馈:首次启动不弹信任提示,\n * 第二次才弹)。`.otto/workspace.json` 是 otto 自身建立 workspace 身份时创建的产物\n * (见 App.start()/confirmWorkspaceInit()),用它做\"是否真项目\"的判据是自我指涉——\n * 任何目录只要跑过一次 otto 就会自动\"自证\"成真项目,导致同一目录首次运行判定为\n * \"非真项目\"(不弹)、静默建立身份后 `.otto` 出现,第二次运行才判定为\"真项目\"(弹)。\n * 团队共享 workspace.json 场景(通过 git 提交)必然存在 `.git`,已被其余标记覆盖,\n * 移除 `.otto` 不影响该场景;真实项目场景下 `.git`/`.claude`/`.mcp.json` 之一必然存在。\n */\nfunction isGateworthyProject(root: string): boolean {\n if (root === resolve(homedir()) || root === resolve('/')) return false\n return ['.git', '.claude', '.mcp.json'].some((m) => existsSync(join(root, m)))\n}\n\n/**\n * 工作区信任弹窗的纯决策(doc §8):启动时是否要弹\"信任此文件夹吗\"。\n * 弹 ⟺ 交互 且 严格开 且 根未信任 且 是真项目。其余一律不弹(headless/已信任/关门控/非项目)。\n * 纯函数:只读 FS + env,不做 IO 副作用、不渲染。渲染与 trust/readonly/exit 动作在 cli 层。\n */\nexport function evaluateWorkspaceTrustPrompt(\n workspaceDir: string,\n ctx: ProjectConfigTrustContext = {},\n): WorkspaceTrustPromptDecision {\n const root = resolve(workspaceDir)\n if (!ctx.interactive) return { shouldPrompt: false, root, reason: 'not-interactive' }\n if (!isStrictProjectConfigEnabled()) return { shouldPrompt: false, root, reason: 'strict-off' }\n if (isProjectTrusted(workspaceDir)) return { shouldPrompt: false, root, reason: 'trusted' }\n if (!isGateworthyProject(root)) return { shouldPrompt: false, root, reason: 'not-a-project' }\n return { shouldPrompt: true, root, reason: 'untrusted' }\n}\n"],"mappings":"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"}
package/package.json ADDED
@@ -0,0 +1,41 @@
1
+ {
2
+ "name": "@x-otto/claude-compat",
3
+ "version": "0.0.1-alpha.0",
4
+ "files": [
5
+ "dist"
6
+ ],
7
+ "type": "module",
8
+ "main": "./dist/index.js",
9
+ "types": "./dist/index.d.ts",
10
+ "exports": {
11
+ ".": {
12
+ "types": "./dist/index.d.ts",
13
+ "import": "./dist/index.js"
14
+ }
15
+ },
16
+ "dependencies": {
17
+ "@x-otto/env": "0.1.0-alpha.1",
18
+ "@x-otto/hooks": "0.0.1-alpha.0",
19
+ "@x-otto/mcp": "0.0.1-alpha.0",
20
+ "@x-otto/plugin": "0.1.0-alpha.1",
21
+ "@x-otto/runtime": "0.0.1-alpha.0",
22
+ "@x-otto/setting": "0.0.1-alpha.0",
23
+ "@x-otto/shared": "0.1.0-alpha.1"
24
+ },
25
+ "devDependencies": {
26
+ "typescript": "6.0.2",
27
+ "vitest": "^3.2.4"
28
+ },
29
+ "private": false,
30
+ "publishConfig": {
31
+ "access": "public",
32
+ "registry": "https://registry.npmjs.org",
33
+ "tag": "alpha"
34
+ },
35
+ "scripts": {
36
+ "build": "tsdown",
37
+ "typecheck": "tsc --noEmit",
38
+ "test": "vitest run",
39
+ "clean": "rm -rf dist"
40
+ }
41
+ }