@guilz-dev/belay 0.1.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.
Files changed (266) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +268 -0
  3. package/agent-belay-logo.png +0 -0
  4. package/dist/adapters/claude/adapter.d.ts +7 -0
  5. package/dist/adapters/claude/adapter.js +114 -0
  6. package/dist/adapters/claude/hooks.d.ts +13 -0
  7. package/dist/adapters/claude/hooks.js +49 -0
  8. package/dist/adapters/claude/runtime-entry.d.ts +4 -0
  9. package/dist/adapters/claude/runtime-entry.js +260 -0
  10. package/dist/adapters/codex/adapter.d.ts +7 -0
  11. package/dist/adapters/codex/adapter.js +73 -0
  12. package/dist/adapters/codex/hooks.d.ts +21 -0
  13. package/dist/adapters/codex/hooks.js +78 -0
  14. package/dist/adapters/codex/runtime-entry.d.ts +4 -0
  15. package/dist/adapters/codex/runtime-entry.js +237 -0
  16. package/dist/adapters/cursor/adapter.d.ts +7 -0
  17. package/dist/adapters/cursor/adapter.js +29 -0
  18. package/dist/adapters/cursor/hooks.d.ts +2 -0
  19. package/dist/adapters/cursor/hooks.js +26 -0
  20. package/dist/adapters/cursor/runtime-entry.d.ts +4 -0
  21. package/dist/adapters/cursor/runtime-entry.js +143 -0
  22. package/dist/adapters/layouts/claude.d.ts +2 -0
  23. package/dist/adapters/layouts/claude.js +40 -0
  24. package/dist/adapters/layouts/codex.d.ts +2 -0
  25. package/dist/adapters/layouts/codex.js +43 -0
  26. package/dist/adapters/layouts/cursor.d.ts +2 -0
  27. package/dist/adapters/layouts/cursor.js +40 -0
  28. package/dist/adapters/layouts/index.d.ts +7 -0
  29. package/dist/adapters/layouts/index.js +23 -0
  30. package/dist/adapters/layouts/protected-paths.d.ts +3 -0
  31. package/dist/adapters/layouts/protected-paths.js +15 -0
  32. package/dist/adapters/layouts/scope.d.ts +19 -0
  33. package/dist/adapters/layouts/scope.js +86 -0
  34. package/dist/adapters/layouts/types.d.ts +14 -0
  35. package/dist/adapters/layouts/types.js +1 -0
  36. package/dist/adapters/registry.d.ts +4 -0
  37. package/dist/adapters/registry.js +14 -0
  38. package/dist/adapters/shared/gate-runtime.d.ts +51 -0
  39. package/dist/adapters/shared/gate-runtime.js +518 -0
  40. package/dist/adapters/shared/repo-root.d.ts +2 -0
  41. package/dist/adapters/shared/repo-root.js +17 -0
  42. package/dist/adapters/types.d.ts +19 -0
  43. package/dist/adapters/types.js +1 -0
  44. package/dist/branding.d.ts +3 -0
  45. package/dist/branding.js +3 -0
  46. package/dist/bundle/claude-runtime.mjs +5323 -0
  47. package/dist/bundle/codex-runtime.mjs +5310 -0
  48. package/dist/bundle/cursor-runtime.mjs +5208 -0
  49. package/dist/cleanup-orphans.d.ts +7 -0
  50. package/dist/cleanup-orphans.js +59 -0
  51. package/dist/cli.d.ts +2 -0
  52. package/dist/cli.js +631 -0
  53. package/dist/commands/approve.d.ts +14 -0
  54. package/dist/commands/approve.js +65 -0
  55. package/dist/commands/audit.d.ts +59 -0
  56. package/dist/commands/audit.js +132 -0
  57. package/dist/commands/classify-for-report.d.ts +9 -0
  58. package/dist/commands/classify-for-report.js +85 -0
  59. package/dist/commands/doctor.d.ts +3 -0
  60. package/dist/commands/doctor.js +366 -0
  61. package/dist/commands/dogfood.d.ts +5 -0
  62. package/dist/commands/dogfood.js +71 -0
  63. package/dist/commands/explain.d.ts +3 -0
  64. package/dist/commands/explain.js +133 -0
  65. package/dist/commands/health-snapshot.d.ts +2 -0
  66. package/dist/commands/health-snapshot.js +166 -0
  67. package/dist/commands/init-wizard.d.ts +16 -0
  68. package/dist/commands/init-wizard.js +50 -0
  69. package/dist/commands/metrics.d.ts +7 -0
  70. package/dist/commands/metrics.js +89 -0
  71. package/dist/commands/recover.d.ts +3 -0
  72. package/dist/commands/recover.js +105 -0
  73. package/dist/commands/report.d.ts +3 -0
  74. package/dist/commands/report.js +65 -0
  75. package/dist/commands/revoke.d.ts +5 -0
  76. package/dist/commands/revoke.js +22 -0
  77. package/dist/commands/simulate.d.ts +14 -0
  78. package/dist/commands/simulate.js +55 -0
  79. package/dist/commands/status.d.ts +5 -0
  80. package/dist/commands/status.js +107 -0
  81. package/dist/config-io.d.ts +23 -0
  82. package/dist/config-io.js +180 -0
  83. package/dist/conformance/guarantee-table.d.ts +14 -0
  84. package/dist/conformance/guarantee-table.js +95 -0
  85. package/dist/conformance/types.d.ts +6 -0
  86. package/dist/conformance/types.js +1 -0
  87. package/dist/core/approval-service.d.ts +26 -0
  88. package/dist/core/approval-service.js +41 -0
  89. package/dist/core/approval-token.d.ts +11 -0
  90. package/dist/core/approval-token.js +61 -0
  91. package/dist/core/approval.d.ts +19 -0
  92. package/dist/core/approval.js +58 -0
  93. package/dist/core/audit-analysis.d.ts +10 -0
  94. package/dist/core/audit-analysis.js +147 -0
  95. package/dist/core/audit-metrics.d.ts +51 -0
  96. package/dist/core/audit-metrics.js +155 -0
  97. package/dist/core/audit-query.d.ts +11 -0
  98. package/dist/core/audit-query.js +142 -0
  99. package/dist/core/audit-summary.d.ts +33 -0
  100. package/dist/core/audit-summary.js +111 -0
  101. package/dist/core/audit-types.d.ts +65 -0
  102. package/dist/core/audit-types.js +2 -0
  103. package/dist/core/capability/allowlist.d.ts +10 -0
  104. package/dist/core/capability/allowlist.js +53 -0
  105. package/dist/core/capability/broker.d.ts +17 -0
  106. package/dist/core/capability/broker.js +29 -0
  107. package/dist/core/capability/index.d.ts +5 -0
  108. package/dist/core/capability/index.js +4 -0
  109. package/dist/core/capability/paths.d.ts +1 -0
  110. package/dist/core/capability/paths.js +20 -0
  111. package/dist/core/capability/reasons.d.ts +2 -0
  112. package/dist/core/capability/reasons.js +4 -0
  113. package/dist/core/capability/types.d.ts +10 -0
  114. package/dist/core/capability/types.js +1 -0
  115. package/dist/core/capability-approval.d.ts +28 -0
  116. package/dist/core/capability-approval.js +43 -0
  117. package/dist/core/classify-subagent.d.ts +2 -0
  118. package/dist/core/classify-subagent.js +69 -0
  119. package/dist/core/classify-tool.d.ts +3 -0
  120. package/dist/core/classify-tool.js +311 -0
  121. package/dist/core/config-layers.d.ts +23 -0
  122. package/dist/core/config-layers.js +59 -0
  123. package/dist/core/config.d.ts +219 -0
  124. package/dist/core/config.js +720 -0
  125. package/dist/core/control-plane-isolation.d.ts +10 -0
  126. package/dist/core/control-plane-isolation.js +83 -0
  127. package/dist/core/custom-command-match.d.ts +2 -0
  128. package/dist/core/custom-command-match.js +8 -0
  129. package/dist/core/egress/allowlist.d.ts +7 -0
  130. package/dist/core/egress/allowlist.js +33 -0
  131. package/dist/core/egress/env.d.ts +3 -0
  132. package/dist/core/egress/env.js +17 -0
  133. package/dist/core/egress/fingerprint.d.ts +3 -0
  134. package/dist/core/egress/fingerprint.js +35 -0
  135. package/dist/core/egress/policy.d.ts +8 -0
  136. package/dist/core/egress/policy.js +47 -0
  137. package/dist/core/egress/proxy-server.d.ts +21 -0
  138. package/dist/core/egress/proxy-server.js +263 -0
  139. package/dist/core/egress/types.d.ts +25 -0
  140. package/dist/core/egress/types.js +1 -0
  141. package/dist/core/egress-approval.d.ts +48 -0
  142. package/dist/core/egress-approval.js +129 -0
  143. package/dist/core/fingerprint.d.ts +6 -0
  144. package/dist/core/fingerprint.js +24 -0
  145. package/dist/core/gate-contract.d.ts +48 -0
  146. package/dist/core/gate-contract.js +50 -0
  147. package/dist/core/gate-engine.d.ts +20 -0
  148. package/dist/core/gate-engine.js +172 -0
  149. package/dist/core/glob.d.ts +1 -0
  150. package/dist/core/glob.js +39 -0
  151. package/dist/core/index.d.ts +19 -0
  152. package/dist/core/index.js +15 -0
  153. package/dist/core/integrity.d.ts +15 -0
  154. package/dist/core/integrity.js +68 -0
  155. package/dist/core/judge-api-key.d.ts +4 -0
  156. package/dist/core/judge-api-key.js +11 -0
  157. package/dist/core/judge-config.d.ts +29 -0
  158. package/dist/core/judge-config.js +85 -0
  159. package/dist/core/judge-doctor.d.ts +7 -0
  160. package/dist/core/judge-doctor.js +124 -0
  161. package/dist/core/judgment.d.ts +6 -0
  162. package/dist/core/judgment.js +38 -0
  163. package/dist/core/notify.d.ts +13 -0
  164. package/dist/core/notify.js +44 -0
  165. package/dist/core/path-utils.d.ts +12 -0
  166. package/dist/core/path-utils.js +107 -0
  167. package/dist/core/reclassify.d.ts +15 -0
  168. package/dist/core/reclassify.js +82 -0
  169. package/dist/core/recover-advice.d.ts +30 -0
  170. package/dist/core/recover-advice.js +177 -0
  171. package/dist/core/recover-git-probe.d.ts +8 -0
  172. package/dist/core/recover-git-probe.js +50 -0
  173. package/dist/core/recover-select.d.ts +10 -0
  174. package/dist/core/recover-select.js +60 -0
  175. package/dist/core/scrub.d.ts +3 -0
  176. package/dist/core/scrub.js +87 -0
  177. package/dist/core/shell-substitution.d.ts +6 -0
  178. package/dist/core/shell-substitution.js +130 -0
  179. package/dist/core/shell-tokenizer.d.ts +5 -0
  180. package/dist/core/shell-tokenizer.js +129 -0
  181. package/dist/core/shell-unparseable.d.ts +4 -0
  182. package/dist/core/shell-unparseable.js +96 -0
  183. package/dist/core/transactional/diff-evaluator.d.ts +2 -0
  184. package/dist/core/transactional/diff-evaluator.js +84 -0
  185. package/dist/core/transactional/eligibility.d.ts +4 -0
  186. package/dist/core/transactional/eligibility.js +44 -0
  187. package/dist/core/transactional/git-worktree.d.ts +13 -0
  188. package/dist/core/transactional/git-worktree.js +189 -0
  189. package/dist/core/transactional/index.d.ts +5 -0
  190. package/dist/core/transactional/index.js +4 -0
  191. package/dist/core/transactional/reasons.d.ts +4 -0
  192. package/dist/core/transactional/reasons.js +8 -0
  193. package/dist/core/transactional/runner.d.ts +2 -0
  194. package/dist/core/transactional/runner.js +113 -0
  195. package/dist/core/transactional/types.d.ts +46 -0
  196. package/dist/core/transactional/types.js +1 -0
  197. package/dist/core/types.d.ts +90 -0
  198. package/dist/core/types.js +1 -0
  199. package/dist/core/v2/adapter.d.ts +14 -0
  200. package/dist/core/v2/adapter.js +118 -0
  201. package/dist/core/v2/containment.d.ts +19 -0
  202. package/dist/core/v2/containment.js +91 -0
  203. package/dist/core/v2/egress-classify.d.ts +7 -0
  204. package/dist/core/v2/egress-classify.js +216 -0
  205. package/dist/core/v2/fingerprint.d.ts +1 -0
  206. package/dist/core/v2/fingerprint.js +4 -0
  207. package/dist/core/v2/index.d.ts +12 -0
  208. package/dist/core/v2/index.js +10 -0
  209. package/dist/core/v2/judge-audit.d.ts +2 -0
  210. package/dist/core/v2/judge-audit.js +15 -0
  211. package/dist/core/v2/judge-factory.d.ts +25 -0
  212. package/dist/core/v2/judge-factory.js +75 -0
  213. package/dist/core/v2/judge-outbound.d.ts +12 -0
  214. package/dist/core/v2/judge-outbound.js +73 -0
  215. package/dist/core/v2/judge.d.ts +47 -0
  216. package/dist/core/v2/judge.js +264 -0
  217. package/dist/core/v2/launcher-resolve.d.ts +12 -0
  218. package/dist/core/v2/launcher-resolve.js +190 -0
  219. package/dist/core/v2/overrides.d.ts +7 -0
  220. package/dist/core/v2/overrides.js +37 -0
  221. package/dist/core/v2/parser.d.ts +21 -0
  222. package/dist/core/v2/parser.js +213 -0
  223. package/dist/core/v2/types.d.ts +67 -0
  224. package/dist/core/v2/types.js +1 -0
  225. package/dist/core/v2/verdict.d.ts +2 -0
  226. package/dist/core/v2/verdict.js +699 -0
  227. package/dist/corpus/evaluate.d.ts +24 -0
  228. package/dist/corpus/evaluate.js +69 -0
  229. package/dist/defaults.d.ts +18 -0
  230. package/dist/defaults.js +155 -0
  231. package/dist/egress-daemon.d.ts +1 -0
  232. package/dist/egress-daemon.js +52 -0
  233. package/dist/index.d.ts +17 -0
  234. package/dist/index.js +15 -0
  235. package/dist/installer/bootstrap.d.ts +5 -0
  236. package/dist/installer/bootstrap.js +61 -0
  237. package/dist/installer/runtime-artifacts.d.ts +3 -0
  238. package/dist/installer/runtime-artifacts.js +23 -0
  239. package/dist/installer/scope-config.d.ts +8 -0
  240. package/dist/installer/scope-config.js +25 -0
  241. package/dist/installer.d.ts +22 -0
  242. package/dist/installer.js +169 -0
  243. package/dist/node-resolution.d.ts +8 -0
  244. package/dist/node-resolution.js +237 -0
  245. package/dist/operational-insights.d.ts +19 -0
  246. package/dist/operational-insights.js +24 -0
  247. package/dist/presets.d.ts +4 -0
  248. package/dist/presets.js +95 -0
  249. package/dist/services/egress-service.d.ts +57 -0
  250. package/dist/services/egress-service.js +334 -0
  251. package/dist/services/sandbox-service.d.ts +38 -0
  252. package/dist/services/sandbox-service.js +95 -0
  253. package/dist/templates.d.ts +7 -0
  254. package/dist/templates.js +56 -0
  255. package/dist/types.d.ts +230 -0
  256. package/dist/types.js +1 -0
  257. package/dist/version.d.ts +1 -0
  258. package/dist/version.js +1 -0
  259. package/package.json +65 -0
  260. package/skills/belay/SKILL.md +52 -0
  261. package/skills/belay/belay-approve.md +7 -0
  262. package/skills/belay/belay-explain.md +11 -0
  263. package/skills/belay/belay-recover.md +13 -0
  264. package/skills/belay/belay-report.md +7 -0
  265. package/skills/belay/belay-status.md +9 -0
  266. package/skills/belay/belay-why.md +11 -0
@@ -0,0 +1,7 @@
1
+ import type { BelayAdapter } from '../types.js';
2
+ export declare const cursorAdapter: BelayAdapter;
3
+ export declare function cursorPaths(repoRoot: string): {
4
+ config: string;
5
+ hooks: string;
6
+ runtime: string;
7
+ };
@@ -0,0 +1,29 @@
1
+ import path from 'node:path';
2
+ import { doctorProject } from '../../commands/doctor.js';
3
+ import { getManagedHookEntries } from '../../defaults.js';
4
+ import { initCursorProject, upgradeCursorProject } from '../../installer.js';
5
+ import { cursorLayout } from '../layouts/cursor.js';
6
+ export const cursorAdapter = {
7
+ name: 'cursor',
8
+ layout: cursorLayout,
9
+ async install(repoRoot, options = {}) {
10
+ return initCursorProject({ ...options, targetDir: repoRoot });
11
+ },
12
+ async upgrade(repoRoot, options = {}) {
13
+ return upgradeCursorProject({ ...options, targetDir: repoRoot });
14
+ },
15
+ async doctor(options = {}) {
16
+ return doctorProject({ ...options, adapter: 'cursor' });
17
+ },
18
+ hookEvents() {
19
+ return getManagedHookEntries(process.platform);
20
+ },
21
+ };
22
+ export function cursorPaths(repoRoot) {
23
+ const resolved = path.resolve(repoRoot);
24
+ return {
25
+ config: cursorLayout.configPath(resolved),
26
+ hooks: cursorLayout.hooksSettingsPath(resolved),
27
+ runtime: path.join(cursorLayout.runtimeDir(resolved), 'core.mjs'),
28
+ };
29
+ }
@@ -0,0 +1,2 @@
1
+ import type { HooksFile } from '../../types.js';
2
+ export declare function mergeCursorHooksFile(current: HooksFile, platform: NodeJS.Platform, hooksDir: string, repoRoot: string): HooksFile;
@@ -0,0 +1,26 @@
1
+ import { getManagedHookEntries } from '../../defaults.js';
2
+ function entryMatches(existing, expected) {
3
+ return existing.command === expected.command && existing.matcher === expected.matcher;
4
+ }
5
+ function mergeHookEntry(current, expected, placement) {
6
+ const entries = Array.isArray(current) ? [...current] : [];
7
+ const filtered = entries.filter((entry) => !entryMatches(entry, expected));
8
+ if (placement === 'prepend') {
9
+ return [expected, ...filtered];
10
+ }
11
+ return [...filtered, expected];
12
+ }
13
+ export function mergeCursorHooksFile(current, platform, hooksDir, repoRoot) {
14
+ const next = {
15
+ version: current.version || 1,
16
+ hooks: { ...current.hooks },
17
+ };
18
+ const managedEntries = getManagedHookEntries(platform, hooksDir, repoRoot);
19
+ for (const { event, definition } of managedEntries) {
20
+ next.hooks[event] = mergeHookEntry(next.hooks[event], {
21
+ command: definition.command,
22
+ matcher: definition.matcher,
23
+ }, definition.placement);
24
+ }
25
+ return next;
26
+ }
@@ -0,0 +1,4 @@
1
+ export declare function runBeforeSubmitPromptHook(): Promise<void>;
2
+ export declare function runShellGateHook(): Promise<void>;
3
+ export declare function runToolGateHook(eventName: string): Promise<void>;
4
+ export declare function runAuditHook(eventName: string): Promise<void>;
@@ -0,0 +1,143 @@
1
+ import process from 'node:process';
2
+ import { cursorLayout } from '../layouts/cursor.js';
3
+ import { appendObservedAudit, createDefaultGateRuntimeDeps, evaluateGatedAction, gateVerdictToCursorResponse, processApprovalPrompt, resolveGateConfig, } from '../shared/gate-runtime.js';
4
+ import { findRepoRoot } from '../shared/repo-root.js';
5
+ async function readStdinJson() {
6
+ const chunks = [];
7
+ for await (const chunk of process.stdin) {
8
+ chunks.push(typeof chunk === 'string' ? chunk : chunk.toString('utf8'));
9
+ }
10
+ const raw = chunks.join('').trim();
11
+ if (!raw) {
12
+ return {};
13
+ }
14
+ try {
15
+ return JSON.parse(raw);
16
+ }
17
+ catch {
18
+ return {};
19
+ }
20
+ }
21
+ function jsonResponse(value) {
22
+ process.stdout.write(`${JSON.stringify(value)}\n`);
23
+ }
24
+ async function loadRuntimeContext(cwd) {
25
+ const repoRoot = findRepoRoot(cwd, cursorLayout);
26
+ const configPath = cursorLayout.configPath(repoRoot);
27
+ const deps = createDefaultGateRuntimeDeps();
28
+ const config = await resolveGateConfig({ layout: cursorLayout, repoRoot, configPath }, deps);
29
+ return { layout: cursorLayout, repoRoot, config, configPath };
30
+ }
31
+ function isSubagentEvent(payload, eventName) {
32
+ return eventName === 'subagentStart' || payload.subagent_type !== undefined;
33
+ }
34
+ function isFileMutationTool(toolName) {
35
+ return toolName === 'Write' || toolName === 'StrReplace' || toolName === 'Delete';
36
+ }
37
+ export async function runBeforeSubmitPromptHook() {
38
+ try {
39
+ const payload = await readStdinJson();
40
+ const prompt = String(payload.prompt ?? '');
41
+ const ctx = await loadRuntimeContext(process.cwd());
42
+ const deps = createDefaultGateRuntimeDeps();
43
+ const result = await processApprovalPrompt(ctx, deps, prompt);
44
+ jsonResponse({
45
+ continue: result.continue,
46
+ ...(result.user_message ? { user_message: result.user_message } : {}),
47
+ });
48
+ }
49
+ catch {
50
+ jsonResponse({
51
+ continue: false,
52
+ user_message: 'belay failed while processing approval state. Run belay doctor, then retry.',
53
+ });
54
+ }
55
+ }
56
+ export async function runShellGateHook() {
57
+ try {
58
+ const payload = await readStdinJson();
59
+ const command = String(payload.command ?? '').trim();
60
+ const cwd = String(payload.cwd ?? process.cwd()).trim() || process.cwd();
61
+ const ctx = await loadRuntimeContext(cwd);
62
+ const deps = createDefaultGateRuntimeDeps();
63
+ const verdict = await evaluateGatedAction(ctx, deps, {
64
+ kind: 'shell',
65
+ cwd,
66
+ command,
67
+ });
68
+ jsonResponse(gateVerdictToCursorResponse(verdict));
69
+ }
70
+ catch {
71
+ jsonResponse({
72
+ permission: 'deny',
73
+ user_message: 'belay failed while classifying this shell command. Run belay doctor, then retry.',
74
+ });
75
+ }
76
+ }
77
+ export async function runToolGateHook(eventName) {
78
+ try {
79
+ const payload = await readStdinJson();
80
+ const cwd = process.cwd();
81
+ const ctx = await loadRuntimeContext(cwd);
82
+ const deps = createDefaultGateRuntimeDeps();
83
+ const toolName = String(payload.tool_name ?? '');
84
+ if (isSubagentEvent(payload, eventName)) {
85
+ const verdict = await evaluateGatedAction(ctx, deps, {
86
+ kind: 'subagent',
87
+ cwd,
88
+ payload,
89
+ });
90
+ jsonResponse(gateVerdictToCursorResponse(verdict));
91
+ return;
92
+ }
93
+ if (toolName === 'Shell') {
94
+ const verdict = await evaluateGatedAction(ctx, deps, {
95
+ kind: 'shell',
96
+ cwd,
97
+ payload,
98
+ toolName,
99
+ });
100
+ jsonResponse(gateVerdictToCursorResponse(verdict));
101
+ return;
102
+ }
103
+ if (isFileMutationTool(toolName)) {
104
+ const verdict = await evaluateGatedAction(ctx, deps, {
105
+ kind: 'tool',
106
+ cwd,
107
+ payload,
108
+ toolName,
109
+ });
110
+ jsonResponse(gateVerdictToCursorResponse(verdict));
111
+ return;
112
+ }
113
+ if (payload.tool_name === 'Task') {
114
+ const verdict = await evaluateGatedAction(ctx, deps, {
115
+ kind: 'subagent',
116
+ cwd,
117
+ payload,
118
+ });
119
+ jsonResponse(gateVerdictToCursorResponse(verdict));
120
+ return;
121
+ }
122
+ jsonResponse({ permission: 'allow' });
123
+ }
124
+ catch {
125
+ jsonResponse({
126
+ permission: 'deny',
127
+ user_message: 'belay failed while classifying this tool action. Run belay doctor, then retry.',
128
+ });
129
+ }
130
+ }
131
+ export async function runAuditHook(eventName) {
132
+ try {
133
+ const payload = await readStdinJson();
134
+ const ctx = await loadRuntimeContext(process.cwd());
135
+ const deps = createDefaultGateRuntimeDeps();
136
+ await appendObservedAudit(ctx, deps, eventName, payload);
137
+ jsonResponse({});
138
+ }
139
+ catch (error) {
140
+ console.error('belay audit hook failed:', error instanceof Error ? error.message : String(error));
141
+ jsonResponse({});
142
+ }
143
+ }
@@ -0,0 +1,2 @@
1
+ import type { AdapterLayout } from './types.js';
2
+ export declare const claudeLayout: AdapterLayout;
@@ -0,0 +1,40 @@
1
+ import path from 'node:path';
2
+ import { DEFAULT_CONFIG_V4 } from '../../core/config.js';
3
+ import { buildRunnerInvocation } from './scope.js';
4
+ function runnerCommand(platform, repoRoot, hookName, ...args) {
5
+ const hooksDir = path.join(path.resolve(repoRoot), '.claude', 'hooks');
6
+ return buildRunnerInvocation(platform, hooksDir, repoRoot, hookName, ...args);
7
+ }
8
+ export const claudeLayout = {
9
+ name: 'claude',
10
+ configPath(repoRoot) {
11
+ return path.join(repoRoot, '.claude', 'belay.config.json');
12
+ },
13
+ hooksSettingsPath(repoRoot) {
14
+ return path.join(repoRoot, '.claude', 'settings.json');
15
+ },
16
+ hooksDir(repoRoot) {
17
+ return path.join(repoRoot, '.claude', 'hooks');
18
+ },
19
+ runtimeDir(repoRoot) {
20
+ return path.join(repoRoot, '.claude', 'belay', 'runtime');
21
+ },
22
+ repoLocalStateDir(repoRoot) {
23
+ return path.join(repoRoot, '.claude', 'belay');
24
+ },
25
+ defaultAuditLogPath(_repoRoot) {
26
+ return path.join('.claude', 'belay', 'audit.ndjson');
27
+ },
28
+ repoRootMarkers: ['.git', '.claude'],
29
+ runnerCommand,
30
+ defaultConfig(repoRoot) {
31
+ return {
32
+ ...DEFAULT_CONFIG_V4,
33
+ adapter: 'claude',
34
+ audit: {
35
+ ...DEFAULT_CONFIG_V4.audit,
36
+ logPath: claudeLayout.defaultAuditLogPath(repoRoot),
37
+ },
38
+ };
39
+ },
40
+ };
@@ -0,0 +1,2 @@
1
+ import type { AdapterLayout } from './types.js';
2
+ export declare const codexLayout: AdapterLayout;
@@ -0,0 +1,43 @@
1
+ import path from 'node:path';
2
+ import { DEFAULT_CONFIG_V4 } from '../../core/config.js';
3
+ import { buildRunnerInvocation } from './scope.js';
4
+ function runnerCommand(platform, repoRoot, hookName, ...args) {
5
+ const hooksDir = path.join(path.resolve(repoRoot), '.codex', 'hooks');
6
+ return buildRunnerInvocation(platform, hooksDir, repoRoot, hookName, ...args);
7
+ }
8
+ // Codex hook config lives in `.codex/config.toml` (TOML `[[hooks.*]]`), distinct from
9
+ // Claude's JSON `settings.json`. belay's own config stays JSON at `.codex/belay.config.json`.
10
+ export const codexLayout = {
11
+ name: 'codex',
12
+ configPath(repoRoot) {
13
+ return path.join(repoRoot, '.codex', 'belay.config.json');
14
+ },
15
+ // Codex reads lifecycle hooks from `.codex/config.toml` (project layer).
16
+ hooksSettingsPath(repoRoot) {
17
+ return path.join(repoRoot, '.codex', 'config.toml');
18
+ },
19
+ hooksDir(repoRoot) {
20
+ return path.join(repoRoot, '.codex', 'hooks');
21
+ },
22
+ runtimeDir(repoRoot) {
23
+ return path.join(repoRoot, '.codex', 'belay', 'runtime');
24
+ },
25
+ repoLocalStateDir(repoRoot) {
26
+ return path.join(repoRoot, '.codex', 'belay');
27
+ },
28
+ defaultAuditLogPath(_repoRoot) {
29
+ return path.join('.codex', 'belay', 'audit.ndjson');
30
+ },
31
+ repoRootMarkers: ['.git', '.codex'],
32
+ runnerCommand,
33
+ defaultConfig(repoRoot) {
34
+ return {
35
+ ...DEFAULT_CONFIG_V4,
36
+ adapter: 'codex',
37
+ audit: {
38
+ ...DEFAULT_CONFIG_V4.audit,
39
+ logPath: codexLayout.defaultAuditLogPath(repoRoot),
40
+ },
41
+ };
42
+ },
43
+ };
@@ -0,0 +1,2 @@
1
+ import type { AdapterLayout } from './types.js';
2
+ export declare const cursorLayout: AdapterLayout;
@@ -0,0 +1,40 @@
1
+ import path from 'node:path';
2
+ import { DEFAULT_CONFIG_V4 } from '../../core/config.js';
3
+ import { buildRunnerInvocation } from './scope.js';
4
+ function runnerCommand(platform, repoRoot, hookName, ...args) {
5
+ const hooksDir = path.join(path.resolve(repoRoot), '.cursor', 'hooks');
6
+ return buildRunnerInvocation(platform, hooksDir, repoRoot, hookName, ...args);
7
+ }
8
+ export const cursorLayout = {
9
+ name: 'cursor',
10
+ configPath(repoRoot) {
11
+ return path.join(repoRoot, '.cursor', 'belay.config.json');
12
+ },
13
+ hooksSettingsPath(repoRoot) {
14
+ return path.join(repoRoot, '.cursor', 'hooks.json');
15
+ },
16
+ hooksDir(repoRoot) {
17
+ return path.join(repoRoot, '.cursor', 'hooks');
18
+ },
19
+ runtimeDir(repoRoot) {
20
+ return path.join(repoRoot, '.cursor', 'belay', 'runtime');
21
+ },
22
+ repoLocalStateDir(repoRoot) {
23
+ return path.join(repoRoot, '.cursor', 'belay');
24
+ },
25
+ defaultAuditLogPath(_repoRoot) {
26
+ return path.join('.cursor', 'belay', 'audit.ndjson');
27
+ },
28
+ repoRootMarkers: ['.git', '.cursor'],
29
+ runnerCommand,
30
+ defaultConfig(repoRoot) {
31
+ return {
32
+ ...DEFAULT_CONFIG_V4,
33
+ adapter: 'cursor',
34
+ audit: {
35
+ ...DEFAULT_CONFIG_V4.audit,
36
+ logPath: cursorLayout.defaultAuditLogPath(repoRoot),
37
+ },
38
+ };
39
+ },
40
+ };
@@ -0,0 +1,7 @@
1
+ import type { AdapterLayout, AdapterName } from './types.js';
2
+ export { claudeLayout } from './claude.js';
3
+ export { codexLayout } from './codex.js';
4
+ export { cursorLayout } from './cursor.js';
5
+ export type { AdapterLayout, AdapterName } from './types.js';
6
+ export declare function getAdapterLayout(name?: AdapterName): AdapterLayout;
7
+ export declare function detectAdapterLayout(repoRoot: string, existsSync: (path: string) => boolean): AdapterLayout;
@@ -0,0 +1,23 @@
1
+ import { claudeLayout } from './claude.js';
2
+ import { codexLayout } from './codex.js';
3
+ import { cursorLayout } from './cursor.js';
4
+ export { claudeLayout } from './claude.js';
5
+ export { codexLayout } from './codex.js';
6
+ export { cursorLayout } from './cursor.js';
7
+ const layouts = {
8
+ cursor: cursorLayout,
9
+ claude: claudeLayout,
10
+ codex: codexLayout,
11
+ };
12
+ export function getAdapterLayout(name = 'cursor') {
13
+ return layouts[name];
14
+ }
15
+ export function detectAdapterLayout(repoRoot, existsSync) {
16
+ if (existsSync(claudeLayout.configPath(repoRoot))) {
17
+ return claudeLayout;
18
+ }
19
+ if (existsSync(codexLayout.configPath(repoRoot))) {
20
+ return codexLayout;
21
+ }
22
+ return cursorLayout;
23
+ }
@@ -0,0 +1,3 @@
1
+ import type { AdapterLayout } from './types.js';
2
+ /** Repo-local and optional out-of-repo paths that must never be mutated via overrides. */
3
+ export declare function protectedArtifactRoots(layout: AdapterLayout, repoRoot: string, controlPlaneDir?: string | null): string[];
@@ -0,0 +1,15 @@
1
+ import path from 'node:path';
2
+ /** Repo-local and optional out-of-repo paths that must never be mutated via overrides. */
3
+ export function protectedArtifactRoots(layout, repoRoot, controlPlaneDir) {
4
+ const roots = [
5
+ layout.configPath(repoRoot),
6
+ layout.hooksSettingsPath(repoRoot),
7
+ layout.hooksDir(repoRoot),
8
+ layout.repoLocalStateDir(repoRoot),
9
+ layout.runtimeDir(repoRoot),
10
+ ];
11
+ if (controlPlaneDir) {
12
+ roots.push(controlPlaneDir);
13
+ }
14
+ return roots.map((entry) => path.resolve(entry));
15
+ }
@@ -0,0 +1,19 @@
1
+ import type { AdapterLayout } from './types.js';
2
+ export type InstallScope = 'project' | 'global' | 'managed';
3
+ export interface ScopedPaths {
4
+ scope: InstallScope;
5
+ repoRoot: string;
6
+ configPath: string;
7
+ hooksSettingsPath: string;
8
+ hooksDir: string;
9
+ runtimeDir: string;
10
+ repoLocalStateDir: string;
11
+ skillsDir: string;
12
+ commandsDir?: string;
13
+ }
14
+ export declare function isPathInside(child: string, parent: string): boolean;
15
+ export declare function buildRunnerInvocation(platform: NodeJS.Platform, hooksDir: string, repoRoot: string, hookScript: string, ...args: string[]): string;
16
+ export declare function resolveScopedPaths(layout: AdapterLayout, scope: InstallScope, repoRoot: string): ScopedPaths;
17
+ export declare function resolveInstallScope(options: {
18
+ scope?: InstallScope;
19
+ }, persisted?: 'project' | 'global', fallback?: 'project' | 'global'): 'project' | 'global';
@@ -0,0 +1,86 @@
1
+ import os from 'node:os';
2
+ import path from 'node:path';
3
+ function agentHomeDir(adapter) {
4
+ const home = os.homedir();
5
+ if (adapter === 'cursor') {
6
+ return path.join(home, '.cursor');
7
+ }
8
+ if (adapter === 'claude') {
9
+ return path.join(home, '.claude');
10
+ }
11
+ return path.join(home, '.codex');
12
+ }
13
+ function projectAgentDir(adapter, repoRoot) {
14
+ if (adapter === 'cursor') {
15
+ return path.join(repoRoot, '.cursor');
16
+ }
17
+ if (adapter === 'claude') {
18
+ return path.join(repoRoot, '.claude');
19
+ }
20
+ return path.join(repoRoot, '.codex');
21
+ }
22
+ export function isPathInside(child, parent) {
23
+ const resolvedChild = path.resolve(child);
24
+ const resolvedParent = path.resolve(parent);
25
+ const relative = path.relative(resolvedParent, resolvedChild);
26
+ return relative === '' || (!relative.startsWith('..') && !path.isAbsolute(relative));
27
+ }
28
+ export function buildRunnerInvocation(platform, hooksDir, repoRoot, hookScript, ...args) {
29
+ const runnerFile = platform === 'win32' ? 'belay-runner.cmd' : 'belay-runner';
30
+ const runnerAbs = path.resolve(hooksDir, runnerFile);
31
+ const relative = path.relative(path.resolve(repoRoot), runnerAbs);
32
+ const useRelative = relative.length > 0 && !relative.startsWith('..') && !path.isAbsolute(relative);
33
+ const runnerRef = useRelative
34
+ ? platform === 'win32'
35
+ ? `.\\${relative.split(path.sep).join('\\')}`
36
+ : `./${relative.split(path.sep).join('/')}`
37
+ : runnerAbs;
38
+ return [runnerRef, hookScript, ...args].join(' ');
39
+ }
40
+ export function resolveScopedPaths(layout, scope, repoRoot) {
41
+ const resolvedRepo = path.resolve(repoRoot);
42
+ const adapter = layout.name;
43
+ if (scope === 'managed') {
44
+ throw new Error('managed install scope is not implemented yet. Use --scope project (default) or --scope global.');
45
+ }
46
+ const projectAgent = projectAgentDir(adapter, resolvedRepo);
47
+ const project = {
48
+ scope: 'project',
49
+ repoRoot: resolvedRepo,
50
+ configPath: layout.configPath(resolvedRepo),
51
+ hooksSettingsPath: layout.hooksSettingsPath(resolvedRepo),
52
+ hooksDir: layout.hooksDir(resolvedRepo),
53
+ runtimeDir: layout.runtimeDir(resolvedRepo),
54
+ repoLocalStateDir: layout.repoLocalStateDir(resolvedRepo),
55
+ skillsDir: path.join(projectAgent, 'skills', 'belay'),
56
+ commandsDir: adapter === 'cursor' ? path.join(projectAgent, 'commands') : undefined,
57
+ };
58
+ if (scope === 'project') {
59
+ return project;
60
+ }
61
+ const globalAgent = agentHomeDir(adapter);
62
+ return {
63
+ scope: 'global',
64
+ repoRoot: resolvedRepo,
65
+ configPath: project.configPath,
66
+ hooksSettingsPath: adapter === 'cursor'
67
+ ? path.join(globalAgent, 'hooks.json')
68
+ : adapter === 'claude'
69
+ ? path.join(globalAgent, 'settings.json')
70
+ : path.join(globalAgent, 'config.toml'),
71
+ hooksDir: path.join(globalAgent, 'hooks'),
72
+ runtimeDir: path.join(globalAgent, 'belay', 'runtime'),
73
+ repoLocalStateDir: project.repoLocalStateDir,
74
+ skillsDir: path.join(globalAgent, 'skills', 'belay'),
75
+ commandsDir: adapter === 'cursor' ? path.join(globalAgent, 'commands') : undefined,
76
+ };
77
+ }
78
+ export function resolveInstallScope(options, persisted, fallback = 'project') {
79
+ if (options.scope === 'managed') {
80
+ throw new Error('managed install scope is not implemented yet. Use --scope project (default) or --scope global.');
81
+ }
82
+ if (options.scope === 'global' || options.scope === 'project') {
83
+ return options.scope;
84
+ }
85
+ return persisted ?? fallback;
86
+ }
@@ -0,0 +1,14 @@
1
+ import type { BelayConfigV3 } from '../../core/config.js';
2
+ export type AdapterName = 'cursor' | 'claude' | 'codex';
3
+ export interface AdapterLayout {
4
+ name: AdapterName;
5
+ configPath(repoRoot: string): string;
6
+ hooksSettingsPath(repoRoot: string): string;
7
+ hooksDir(repoRoot: string): string;
8
+ runtimeDir(repoRoot: string): string;
9
+ repoLocalStateDir(repoRoot: string): string;
10
+ defaultAuditLogPath(repoRoot: string): string;
11
+ repoRootMarkers: string[];
12
+ runnerCommand(platform: NodeJS.Platform, repoRoot: string, hookName: string, ...args: string[]): string;
13
+ defaultConfig(repoRoot: string): Partial<BelayConfigV3>;
14
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,4 @@
1
+ import type { AdapterName } from './layouts/types.js';
2
+ import type { BelayAdapter } from './types.js';
3
+ export declare function getAdapter(name?: AdapterName): BelayAdapter;
4
+ export declare function listAdapters(): AdapterName[];
@@ -0,0 +1,14 @@
1
+ import { claudeAdapter } from './claude/adapter.js';
2
+ import { codexAdapter } from './codex/adapter.js';
3
+ import { cursorAdapter } from './cursor/adapter.js';
4
+ const adapters = {
5
+ cursor: cursorAdapter,
6
+ claude: claudeAdapter,
7
+ codex: codexAdapter,
8
+ };
9
+ export function getAdapter(name = 'cursor') {
10
+ return adapters[name];
11
+ }
12
+ export function listAdapters() {
13
+ return Object.keys(adapters);
14
+ }
@@ -0,0 +1,51 @@
1
+ import type { GatedActionKind } from '../../core/gate-contract.js';
2
+ import { type GatePermissionResponse, type GateVerdict } from '../../core/gate-contract.js';
3
+ import { GateNormalizationError } from '../../core/gate-engine.js';
4
+ import { type ApprovalStateFile, type BelayConfigV3 } from '../../core/index.js';
5
+ import type { ClassifierOptions } from '../../core/types.js';
6
+ import type { AdapterLayout } from '../layouts/types.js';
7
+ export interface GateRuntimeContext {
8
+ layout: AdapterLayout;
9
+ repoRoot: string;
10
+ config: BelayConfigV3;
11
+ configPath: string;
12
+ }
13
+ export interface GateRuntimeDeps {
14
+ readConfig: (configPath: string) => Promise<unknown>;
15
+ appendAudit: (ctx: GateRuntimeContext, event: Record<string, unknown>) => Promise<void>;
16
+ loadApprovals: (ctx: GateRuntimeContext, fileName: 'pending-approvals.json' | 'approved-approvals.json') => Promise<{
17
+ filePath: string;
18
+ state: ApprovalStateFile;
19
+ }>;
20
+ writeApprovals: (filePath: string, state: ApprovalStateFile) => Promise<void>;
21
+ }
22
+ export declare function createDefaultGateRuntimeDeps(): GateRuntimeDeps;
23
+ export declare function resolveGateConfig(ctx: Pick<GateRuntimeContext, 'layout' | 'repoRoot' | 'configPath'>, deps: GateRuntimeDeps): Promise<BelayConfigV3>;
24
+ export declare function repoShellClassifierOptions(config: BelayConfigV3, repoRoot: string, layout: AdapterLayout, extras?: ClassifierOptions): ClassifierOptions;
25
+ export declare function runtimeClassifierOptions(ctx: GateRuntimeContext, config: BelayConfigV3): ClassifierOptions;
26
+ export declare function evaluateGatedAction(ctx: GateRuntimeContext, deps: GateRuntimeDeps, params: {
27
+ kind: GatedActionKind;
28
+ cwd: string;
29
+ command?: string;
30
+ payload?: Record<string, unknown>;
31
+ toolName?: string;
32
+ }): Promise<GateVerdict>;
33
+ /** R39: unmapped Codex tools ask via pending approval — not hard deny without approval path. */
34
+ export declare function gateUnmappedToolVerdict(ctx: GateRuntimeContext, deps: GateRuntimeDeps, toolName: string, payload: Record<string, unknown>): Promise<GateVerdict>;
35
+ export declare function processApprovalPrompt(ctx: GateRuntimeContext, deps: GateRuntimeDeps, prompt: string): Promise<{
36
+ continue: boolean;
37
+ user_message?: string;
38
+ }>;
39
+ export declare function gateVerdictToCursorResponse(verdict: GateVerdict): GatePermissionResponse;
40
+ export declare function gateVerdictToClaudePreToolUseResponse(verdict: GateVerdict): Record<string, unknown>;
41
+ export declare function gateVerdictToClaudeUserPromptResponse(verdict: {
42
+ continue: boolean;
43
+ user_message?: string;
44
+ }): Record<string, unknown>;
45
+ export declare function gateVerdictToCodexPreToolUseResponse(verdict: GateVerdict): Record<string, unknown>;
46
+ export declare function gateVerdictToCodexUserPromptResponse(verdict: {
47
+ continue: boolean;
48
+ user_message?: string;
49
+ }): Record<string, unknown>;
50
+ export declare function appendObservedAudit(ctx: GateRuntimeContext, deps: GateRuntimeDeps, eventName: string, payload: Record<string, unknown>): Promise<void>;
51
+ export { GateNormalizationError };