@guilz-dev/belay 0.3.0 → 0.6.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 (242) hide show
  1. package/README.md +7 -1
  2. package/dist/adapters/cursor/runtime-entry.js +1 -0
  3. package/dist/adapters/layouts/scope.js +3 -2
  4. package/dist/adapters/shared/gate-runtime.d.ts +12 -16
  5. package/dist/adapters/shared/gate-runtime.js +499 -43
  6. package/dist/bundle/claude-runtime.mjs +8004 -2676
  7. package/dist/bundle/codex-runtime.mjs +8021 -2683
  8. package/dist/bundle/cursor-runtime.mjs +7993 -2667
  9. package/dist/cli.js +180 -6
  10. package/dist/commands/approve.d.ts +1 -0
  11. package/dist/commands/approve.js +45 -26
  12. package/dist/commands/classify-for-report.js +1 -1
  13. package/dist/commands/config.d.ts +8 -0
  14. package/dist/commands/config.js +264 -58
  15. package/dist/commands/doctor.js +47 -2
  16. package/dist/commands/explain.js +17 -2
  17. package/dist/commands/harvest.d.ts +29 -0
  18. package/dist/commands/harvest.js +72 -0
  19. package/dist/commands/judge.d.ts +11 -1
  20. package/dist/commands/judge.js +24 -4
  21. package/dist/commands/metrics.js +34 -2
  22. package/dist/commands/quality.d.ts +38 -0
  23. package/dist/commands/quality.js +105 -0
  24. package/dist/commands/session.d.ts +20 -0
  25. package/dist/commands/session.js +51 -0
  26. package/dist/commands/simulate.d.ts +1 -0
  27. package/dist/commands/simulate.js +7 -2
  28. package/dist/commands/standing-allow.d.ts +10 -0
  29. package/dist/commands/standing-allow.js +26 -0
  30. package/dist/commands/tui.d.ts +48 -0
  31. package/dist/commands/tui.js +150 -0
  32. package/dist/config-io.js +59 -17
  33. package/dist/conformance/guarantee-posture.d.ts +21 -0
  34. package/dist/conformance/guarantee-posture.js +46 -0
  35. package/dist/conformance/guarantee-table.d.ts +1 -0
  36. package/dist/conformance/guarantee-table.js +14 -9
  37. package/dist/core/approval-replay-cli.d.ts +7 -0
  38. package/dist/core/approval-replay-cli.js +36 -0
  39. package/dist/core/approval-replay.d.ts +45 -0
  40. package/dist/core/approval-replay.js +141 -0
  41. package/dist/core/approval-service.d.ts +15 -0
  42. package/dist/core/approval-service.js +53 -4
  43. package/dist/core/approval.d.ts +26 -3
  44. package/dist/core/approval.js +61 -7
  45. package/dist/core/audit-analysis.d.ts +7 -1
  46. package/dist/core/audit-analysis.js +111 -0
  47. package/dist/core/audit-metrics.d.ts +7 -0
  48. package/dist/core/audit-metrics.js +24 -4
  49. package/dist/core/audit-query.d.ts +1 -0
  50. package/dist/core/audit-query.js +3 -0
  51. package/dist/core/audit-replay-context.d.ts +35 -0
  52. package/dist/core/audit-replay-context.js +88 -0
  53. package/dist/core/audit-types.d.ts +24 -1
  54. package/dist/core/audit-types.js +1 -1
  55. package/dist/core/capability/approval-state-mutation.d.ts +13 -0
  56. package/dist/core/capability/approval-state-mutation.js +92 -0
  57. package/dist/core/capability/approval-v3.d.ts +15 -0
  58. package/dist/core/capability/approval-v3.js +74 -0
  59. package/dist/core/capability/attestation.d.ts +13 -0
  60. package/dist/core/capability/attestation.js +55 -0
  61. package/dist/core/capability/boundary-attestation-sign.d.ts +21 -0
  62. package/dist/core/capability/boundary-attestation-sign.js +84 -0
  63. package/dist/core/capability/boundary-driver-container.d.ts +7 -0
  64. package/dist/core/capability/boundary-driver-container.js +122 -0
  65. package/dist/core/capability/boundary-driver.d.ts +29 -0
  66. package/dist/core/capability/boundary-driver.js +43 -0
  67. package/dist/core/capability/boundary-egress.d.ts +21 -0
  68. package/dist/core/capability/boundary-egress.js +109 -0
  69. package/dist/core/capability/boundary-grant-materialize.d.ts +13 -0
  70. package/dist/core/capability/boundary-grant-materialize.js +78 -0
  71. package/dist/core/capability/boundary-profile.d.ts +15 -0
  72. package/dist/core/capability/boundary-profile.js +25 -0
  73. package/dist/core/capability/boundary-run.d.ts +23 -0
  74. package/dist/core/capability/boundary-run.js +23 -0
  75. package/dist/core/capability/boundary-session.d.ts +57 -0
  76. package/dist/core/capability/boundary-session.js +129 -0
  77. package/dist/core/capability/capability-request-hash.d.ts +2 -0
  78. package/dist/core/capability/capability-request-hash.js +8 -0
  79. package/dist/core/capability/gate-latency-ratchet.d.ts +7 -0
  80. package/dist/core/capability/gate-latency-ratchet.js +18 -0
  81. package/dist/core/capability/gate-policy-shadow.d.ts +25 -0
  82. package/dist/core/capability/gate-policy-shadow.js +108 -0
  83. package/dist/core/capability/gate-shadow-audit.d.ts +1 -0
  84. package/dist/core/capability/gate-shadow-audit.js +1 -0
  85. package/dist/core/capability/gate-shadow-ratchet.d.ts +14 -0
  86. package/dist/core/capability/gate-shadow-ratchet.js +81 -0
  87. package/dist/core/capability/grant-lease.d.ts +9 -0
  88. package/dist/core/capability/grant-lease.js +46 -0
  89. package/dist/core/capability/grant-loader.d.ts +12 -0
  90. package/dist/core/capability/grant-loader.js +23 -0
  91. package/dist/core/capability/grant-match.d.ts +6 -0
  92. package/dist/core/capability/grant-match.js +93 -0
  93. package/dist/core/capability/grant.d.ts +16 -0
  94. package/dist/core/capability/grant.js +16 -0
  95. package/dist/core/capability/index.d.ts +10 -1
  96. package/dist/core/capability/index.js +8 -0
  97. package/dist/core/capability/limits.d.ts +7 -0
  98. package/dist/core/capability/limits.js +55 -0
  99. package/dist/core/capability/paths.d.ts +2 -2
  100. package/dist/core/capability/paths.js +9 -9
  101. package/dist/core/capability/policy-bridge.d.ts +18 -0
  102. package/dist/core/capability/policy-bridge.js +51 -0
  103. package/dist/core/capability/policy-engine.d.ts +70 -0
  104. package/dist/core/capability/policy-engine.js +476 -0
  105. package/dist/core/capability/policy-types.d.ts +20 -0
  106. package/dist/core/capability/policy-types.js +1 -0
  107. package/dist/core/capability/request.d.ts +45 -0
  108. package/dist/core/capability/request.js +1 -0
  109. package/dist/core/capability/resolver.d.ts +4 -0
  110. package/dist/core/capability/resolver.js +4 -0
  111. package/dist/core/capability/trusted-workspace-roots.d.ts +25 -0
  112. package/dist/core/capability/trusted-workspace-roots.js +149 -0
  113. package/dist/core/capability/types.d.ts +11 -1
  114. package/dist/core/capability-approval.d.ts +2 -1
  115. package/dist/core/capability-approval.js +100 -2
  116. package/dist/core/classify-subagent.d.ts +2 -1
  117. package/dist/core/classify-subagent.js +40 -22
  118. package/dist/core/classify-tool.js +157 -64
  119. package/dist/core/config.d.ts +32 -3
  120. package/dist/core/config.js +105 -3
  121. package/dist/core/gate-contract.d.ts +3 -0
  122. package/dist/core/gate-contract.js +3 -0
  123. package/dist/core/gate-engine.js +12 -7
  124. package/dist/core/harvest.d.ts +53 -0
  125. package/dist/core/harvest.js +276 -0
  126. package/dist/core/index.d.ts +9 -3
  127. package/dist/core/index.js +6 -2
  128. package/dist/core/judge-doctor.d.ts +3 -0
  129. package/dist/core/judge-doctor.js +90 -11
  130. package/dist/core/judge-fallback-hints.d.ts +13 -0
  131. package/dist/core/judge-fallback-hints.js +108 -0
  132. package/dist/core/path-utils.d.ts +12 -0
  133. package/dist/core/path-utils.js +60 -7
  134. package/dist/core/reclassify.d.ts +3 -0
  135. package/dist/core/reclassify.js +44 -14
  136. package/dist/core/recovery/capability.d.ts +3 -0
  137. package/dist/core/recovery/capability.js +22 -0
  138. package/dist/core/recovery/fail-closed.d.ts +6 -0
  139. package/dist/core/recovery/fail-closed.js +25 -0
  140. package/dist/core/recovery/index.d.ts +3 -0
  141. package/dist/core/recovery/index.js +2 -0
  142. package/dist/core/recovery/types.d.ts +20 -0
  143. package/dist/core/recovery/types.js +1 -0
  144. package/dist/core/replay-scrub.d.ts +9 -0
  145. package/dist/core/replay-scrub.js +43 -0
  146. package/dist/core/shell-tokenizer.d.ts +2 -0
  147. package/dist/core/shell-tokenizer.js +82 -23
  148. package/dist/core/standing-allow.d.ts +50 -0
  149. package/dist/core/standing-allow.js +178 -0
  150. package/dist/core/transactional/diff-evaluator.js +4 -1
  151. package/dist/core/transactional/eligibility.js +4 -0
  152. package/dist/core/transactional/git-worktree.d.ts +10 -3
  153. package/dist/core/transactional/git-worktree.js +81 -11
  154. package/dist/core/transactional/reasons.d.ts +1 -0
  155. package/dist/core/transactional/reasons.js +4 -0
  156. package/dist/core/transactional/runner.js +22 -6
  157. package/dist/core/transactional/types.d.ts +4 -0
  158. package/dist/core/types.d.ts +36 -3
  159. package/dist/core/verdict/adapter.d.ts +3 -4
  160. package/dist/core/verdict/adapter.js +19 -6
  161. package/dist/core/verdict/containment.d.ts +4 -3
  162. package/dist/core/verdict/containment.js +17 -12
  163. package/dist/core/verdict/cursor-acp-client.d.ts +67 -0
  164. package/dist/core/verdict/cursor-acp-client.js +345 -0
  165. package/dist/core/verdict/egress-classify.d.ts +2 -2
  166. package/dist/core/verdict/egress-classify.js +2 -2
  167. package/dist/core/verdict/git-classifier.d.ts +26 -0
  168. package/dist/core/verdict/git-classifier.js +533 -0
  169. package/dist/core/verdict/judge-audit.d.ts +1 -0
  170. package/dist/core/verdict/judge-audit.js +33 -1
  171. package/dist/core/verdict/judge-baseline.d.ts +20 -0
  172. package/dist/core/verdict/judge-baseline.js +49 -0
  173. package/dist/core/verdict/judge-broker-service.d.ts +42 -0
  174. package/dist/core/verdict/judge-broker-service.js +291 -0
  175. package/dist/core/verdict/judge-catalog.js +1 -0
  176. package/dist/core/verdict/judge-cli-fingerprint.d.ts +6 -0
  177. package/dist/core/verdict/judge-cli-fingerprint.js +67 -0
  178. package/dist/core/verdict/judge-cli.d.ts +23 -0
  179. package/dist/core/verdict/judge-cli.js +139 -16
  180. package/dist/core/verdict/judge-factory.js +13 -4
  181. package/dist/core/verdict/judge-provider-matrix.d.ts +20 -0
  182. package/dist/core/verdict/judge-provider-matrix.js +66 -0
  183. package/dist/core/verdict/judge-runtime-config.d.ts +44 -0
  184. package/dist/core/verdict/judge-runtime-config.js +96 -0
  185. package/dist/core/verdict/judge-session-broker.d.ts +48 -0
  186. package/dist/core/verdict/judge-session-broker.js +200 -0
  187. package/dist/core/verdict/judge-session-guard.d.ts +27 -0
  188. package/dist/core/verdict/judge-session-guard.js +91 -0
  189. package/dist/core/verdict/judge-session-kill-switch.d.ts +11 -0
  190. package/dist/core/verdict/judge-session-kill-switch.js +43 -0
  191. package/dist/core/verdict/judge-session-mutex.d.ts +9 -0
  192. package/dist/core/verdict/judge-session-mutex.js +23 -0
  193. package/dist/core/verdict/judge-shadow.d.ts +24 -0
  194. package/dist/core/verdict/judge-shadow.js +116 -0
  195. package/dist/core/verdict/judge-transport.d.ts +44 -0
  196. package/dist/core/verdict/judge-transport.js +268 -0
  197. package/dist/core/verdict/judge.d.ts +14 -14
  198. package/dist/core/verdict/judge.js +5 -67
  199. package/dist/core/verdict/launcher-resolve.d.ts +2 -0
  200. package/dist/core/verdict/launcher-resolve.js +16 -0
  201. package/dist/core/verdict/parser.js +6 -1
  202. package/dist/core/verdict/prescan.d.ts +15 -0
  203. package/dist/core/verdict/prescan.js +68 -0
  204. package/dist/core/verdict/shell-policy.d.ts +25 -0
  205. package/dist/core/verdict/shell-policy.js +40 -0
  206. package/dist/core/verdict/shell-semantics.d.ts +24 -0
  207. package/dist/core/verdict/shell-semantics.js +225 -0
  208. package/dist/core/verdict/types.d.ts +26 -1
  209. package/dist/core/verdict/verdict.js +300 -179
  210. package/dist/corpus/adversarial-probe.d.ts +86 -0
  211. package/dist/corpus/adversarial-probe.js +220 -0
  212. package/dist/corpus/evaluate.d.ts +45 -12
  213. package/dist/corpus/evaluate.js +61 -8
  214. package/dist/corpus/gate-latency-budget.d.ts +39 -0
  215. package/dist/corpus/gate-latency-budget.js +33 -0
  216. package/dist/corpus/gates.d.ts +35 -0
  217. package/dist/corpus/gates.js +81 -0
  218. package/dist/corpus/judge-accuracy.d.ts +8 -0
  219. package/dist/corpus/judge-accuracy.js +39 -0
  220. package/dist/corpus/must-allow-commands.d.ts +5 -0
  221. package/dist/corpus/must-allow-commands.js +13 -0
  222. package/dist/corpus/mutators.d.ts +28 -0
  223. package/dist/corpus/mutators.js +162 -0
  224. package/dist/corpus/ratchet.d.ts +42 -0
  225. package/dist/corpus/ratchet.js +116 -0
  226. package/dist/corpus/runtime-match.d.ts +19 -0
  227. package/dist/corpus/runtime-match.js +43 -0
  228. package/dist/corpus/standing-allow-catalog.generated.d.ts +13 -0
  229. package/dist/corpus/standing-allow-catalog.generated.js +99 -0
  230. package/dist/corpus/types.d.ts +47 -0
  231. package/dist/corpus/types.js +157 -0
  232. package/dist/installer.js +4 -1
  233. package/dist/judge-broker-daemon.d.ts +1 -0
  234. package/dist/judge-broker-daemon.js +137 -0
  235. package/dist/services/sandbox-service.d.ts +5 -0
  236. package/dist/services/sandbox-service.js +30 -2
  237. package/dist/types.d.ts +2 -2
  238. package/dist/version.d.ts +1 -1
  239. package/dist/version.js +1 -1
  240. package/package.json +7 -3
  241. package/skills/belay/SKILL.md +7 -1
  242. package/skills/belay/belay-approve.md +17 -0
@@ -0,0 +1,533 @@
1
+ import path from 'node:path';
2
+ import { extractRedirectTargets } from '../shell-tokenizer.js';
3
+ const GIT_BRANCH_MUTATION_FLAGS = new Set([
4
+ '--copy',
5
+ '--create-reflog',
6
+ '--delete',
7
+ '--edit-description',
8
+ '--force',
9
+ '--move',
10
+ '--no-create-reflog',
11
+ '--no-track',
12
+ '--recurse-submodules',
13
+ '--set-upstream-to',
14
+ '--track',
15
+ '--unset-upstream',
16
+ ]);
17
+ const GIT_BRANCH_LIST_FLAGS = new Set([
18
+ '--abbrev',
19
+ '--all',
20
+ '--column',
21
+ '--color',
22
+ '--contains',
23
+ '--format',
24
+ '--ignore-case',
25
+ '--list',
26
+ '--merged',
27
+ '--no-abbrev',
28
+ '--no-column',
29
+ '--no-color',
30
+ '--no-contains',
31
+ '--no-merged',
32
+ '--points-at',
33
+ '--remotes',
34
+ '--show-current',
35
+ '--sort',
36
+ '--verbose',
37
+ ]);
38
+ const GIT_BRANCH_MUTATION_SHORT_FLAGS = new Set(['c', 'C', 'd', 'D', 'f', 'm', 'M', 't', 'u']);
39
+ const GIT_BRANCH_LIST_SHORT_FLAGS = new Set(['a', 'i', 'l', 'r', 'v']);
40
+ const READ_ONLY_SUBCOMMANDS = new Set([
41
+ 'status',
42
+ 'diff',
43
+ 'log',
44
+ 'show',
45
+ 'rev-parse',
46
+ 'describe',
47
+ 'show-ref',
48
+ 'reflog',
49
+ 'ls-files',
50
+ 'ls-tree',
51
+ 'cat-file',
52
+ 'verify-commit',
53
+ 'whatchanged',
54
+ 'shortlog',
55
+ 'help',
56
+ 'version',
57
+ ]);
58
+ const READ_ONLY_COMPOUND_SUBCOMMANDS = new Set(['worktree list', 'stash list', 'stash show']);
59
+ const LOCAL_MUTATION_SUBCOMMANDS = new Set([
60
+ 'add',
61
+ 'commit',
62
+ 'mv',
63
+ 'revert',
64
+ 'merge',
65
+ 'cherry-pick',
66
+ 'am',
67
+ 'apply',
68
+ 'format-patch',
69
+ ]);
70
+ const REMOTE_SUBCOMMANDS = new Set(['fetch', 'pull']);
71
+ const LOCAL_MUTATION_COMPOUND_SUBCOMMANDS = new Set([
72
+ 'worktree add',
73
+ 'stash push',
74
+ 'stash apply',
75
+ 'stash pop',
76
+ 'stash drop',
77
+ 'stash branch',
78
+ ]);
79
+ const FILE_OPERAND_SUBCOMMANDS = new Set([
80
+ 'add',
81
+ 'rm',
82
+ 'mv',
83
+ 'checkout',
84
+ 'restore',
85
+ 'clean',
86
+ 'apply',
87
+ 'cherry-pick',
88
+ 'merge',
89
+ 'show',
90
+ 'diff',
91
+ 'grep',
92
+ 'ls-files',
93
+ ]);
94
+ const COMPOUND_SUBCOMMAND_HEADS = new Set(['worktree', 'stash', 'tag']);
95
+ /** Positional args before `--` are refs/revisions, not worktree paths. */
96
+ const REF_ONLY_WITHOUT_TERMINATOR = new Set(['checkout', 'show', 'log']);
97
+ function isGitExecutable(token) {
98
+ return path.basename(token) === 'git';
99
+ }
100
+ function takesValue(flag) {
101
+ return (flag === '-C' ||
102
+ flag === '-c' ||
103
+ flag === '--git-dir' ||
104
+ flag === '--work-tree' ||
105
+ flag === '--exec-path' ||
106
+ flag === '--paginate' ||
107
+ flag === '--config-env' ||
108
+ flag.startsWith('-C') ||
109
+ flag.startsWith('-c') ||
110
+ flag.startsWith('--git-dir=') ||
111
+ flag.startsWith('--work-tree='));
112
+ }
113
+ function peelGlobalOptions(tokens, baseCwd) {
114
+ let effectiveCwd = baseCwd;
115
+ let gitDir;
116
+ let workTree;
117
+ const rest = [];
118
+ let index = 1;
119
+ while (index < tokens.length) {
120
+ const token = tokens[index];
121
+ if (!token) {
122
+ index += 1;
123
+ continue;
124
+ }
125
+ if (token === '--') {
126
+ rest.push(...tokens.slice(index));
127
+ break;
128
+ }
129
+ if (token === '--no-pager' || token === '--no-replace-objects') {
130
+ index += 1;
131
+ continue;
132
+ }
133
+ if (token === '-C' || token === '--work-tree' || token === '--git-dir' || token === '-c') {
134
+ const value = tokens[index + 1];
135
+ if (token === '-C' && value) {
136
+ effectiveCwd = path.resolve(baseCwd, value);
137
+ }
138
+ else if (token === '--work-tree' && value) {
139
+ workTree = value;
140
+ }
141
+ else if (token === '--git-dir' && value) {
142
+ gitDir = value;
143
+ }
144
+ index += value ? 2 : 1;
145
+ continue;
146
+ }
147
+ if (token.startsWith('-C') && token.length > 2) {
148
+ effectiveCwd = path.resolve(baseCwd, token.slice(2));
149
+ index += 1;
150
+ continue;
151
+ }
152
+ if (token.startsWith('--work-tree=')) {
153
+ workTree = token.slice('--work-tree='.length);
154
+ index += 1;
155
+ continue;
156
+ }
157
+ if (token.startsWith('--git-dir=')) {
158
+ gitDir = token.slice('--git-dir='.length);
159
+ index += 1;
160
+ continue;
161
+ }
162
+ if (token.startsWith('-c') || token.startsWith('--config')) {
163
+ index += token.includes('=') ? 1 : tokens[index + 1] ? 2 : 1;
164
+ continue;
165
+ }
166
+ if (token.startsWith('-')) {
167
+ if (takesValue(token) && !token.includes('=')) {
168
+ index += 2;
169
+ }
170
+ else {
171
+ index += 1;
172
+ }
173
+ continue;
174
+ }
175
+ rest.push(...tokens.slice(index));
176
+ break;
177
+ }
178
+ return { rest, effectiveCwd, gitDir, workTree };
179
+ }
180
+ function resolveSubcommand(rest) {
181
+ const head = rest[0] ?? '';
182
+ const second = rest[1] ?? '';
183
+ if (COMPOUND_SUBCOMMAND_HEADS.has(head) && second && !second.startsWith('-')) {
184
+ return { subcommand: `${head} ${second}`, args: rest.slice(2) };
185
+ }
186
+ return { subcommand: head, args: rest.slice(1) };
187
+ }
188
+ export function normalizeGitInvocation(tokens, baseCwd) {
189
+ if (!isGitExecutable(tokens[0] ?? '')) {
190
+ return null;
191
+ }
192
+ const peeled = peelGlobalOptions(tokens, baseCwd);
193
+ const { subcommand, args } = resolveSubcommand(peeled.rest);
194
+ if (!subcommand) {
195
+ return null;
196
+ }
197
+ return {
198
+ subcommand,
199
+ args,
200
+ effectiveCwd: peeled.effectiveCwd !== baseCwd ? peeled.effectiveCwd : undefined,
201
+ gitDir: peeled.gitDir,
202
+ workTree: peeled.workTree,
203
+ };
204
+ }
205
+ function hasGitBranchLongFlag(tokens, flags) {
206
+ return tokens.some((token) => {
207
+ const [name] = token.split('=', 1);
208
+ return name ? flags.has(name) : false;
209
+ });
210
+ }
211
+ function hasGitBranchShortFlag(tokens, flags) {
212
+ return tokens.some((token) => {
213
+ if (!/^-[^-]+$/.test(token)) {
214
+ return false;
215
+ }
216
+ return [...token.slice(1)].some((flag) => flags.has(flag));
217
+ });
218
+ }
219
+ function gitBranchEffect(args) {
220
+ const optionTerminator = args.indexOf('--');
221
+ const optionArgs = optionTerminator >= 0 ? args.slice(0, optionTerminator) : args;
222
+ if (hasGitBranchLongFlag(optionArgs, GIT_BRANCH_MUTATION_FLAGS) ||
223
+ hasGitBranchShortFlag(optionArgs, GIT_BRANCH_MUTATION_SHORT_FLAGS)) {
224
+ return 'local_mutation';
225
+ }
226
+ if (hasGitBranchLongFlag(optionArgs, GIT_BRANCH_LIST_FLAGS) ||
227
+ hasGitBranchShortFlag(optionArgs, GIT_BRANCH_LIST_SHORT_FLAGS)) {
228
+ return 'read_only';
229
+ }
230
+ const positionalArgs = args.filter((token, index) => (optionTerminator >= 0 && index > optionTerminator) ||
231
+ (token !== '--' && !token.startsWith('-')));
232
+ return positionalArgs.length === 0 ? 'read_only' : 'local_mutation';
233
+ }
234
+ function hasFlag(args, ...flags) {
235
+ return args.some((token) => flags.includes(token) || flags.some((flag) => token.startsWith(`${flag}=`)));
236
+ }
237
+ function isDryRunClean(args) {
238
+ return hasFlag(args, '-n', '--dry-run', '--dry-run=');
239
+ }
240
+ function isHardReset(args) {
241
+ return hasFlag(args, '--hard');
242
+ }
243
+ function extractMessageSkippedIndices(args) {
244
+ const skipped = new Set();
245
+ for (let index = 0; index < args.length; index += 1) {
246
+ const token = args[index];
247
+ if (!token) {
248
+ continue;
249
+ }
250
+ if (token === '-m' || token === '--message' || token === '-F' || token === '--file') {
251
+ skipped.add(index);
252
+ if (args[index + 1]) {
253
+ skipped.add(index + 1);
254
+ index += 1;
255
+ }
256
+ continue;
257
+ }
258
+ if (token.startsWith('-m') && token.length > 2) {
259
+ skipped.add(index);
260
+ }
261
+ if (token.startsWith('--message=')) {
262
+ skipped.add(index);
263
+ }
264
+ if (token.startsWith('--set-upstream-to=')) {
265
+ skipped.add(index);
266
+ }
267
+ if (token === '--set-upstream-to' && args[index + 1]) {
268
+ skipped.add(index);
269
+ skipped.add(index + 1);
270
+ index += 1;
271
+ }
272
+ }
273
+ return skipped;
274
+ }
275
+ function looksLikeFileOperand(token) {
276
+ if (!token || token === '--' || token.startsWith('-')) {
277
+ return false;
278
+ }
279
+ if (token.includes('/') || token.includes('\\')) {
280
+ return true;
281
+ }
282
+ if (token.startsWith('.')) {
283
+ return true;
284
+ }
285
+ return token.includes('.');
286
+ }
287
+ function looksLikeDiffPathOperand(token) {
288
+ if (!looksLikeFileOperand(token)) {
289
+ return false;
290
+ }
291
+ if (token.startsWith('.') || path.isAbsolute(token)) {
292
+ return true;
293
+ }
294
+ return token.includes('.');
295
+ }
296
+ function resolveGitWorkTree(baseCwd, effectiveCwd, workTree, gitDir) {
297
+ const resolveBase = effectiveCwd ?? baseCwd;
298
+ if (workTree) {
299
+ return path.resolve(resolveBase, workTree);
300
+ }
301
+ if (gitDir) {
302
+ const resolvedGitDir = path.resolve(resolveBase, gitDir);
303
+ if (path.basename(resolvedGitDir) === '.git') {
304
+ return path.dirname(resolvedGitDir);
305
+ }
306
+ }
307
+ return undefined;
308
+ }
309
+ function extractGitFileOperands(subcommand, args) {
310
+ const baseSubcommand = subcommand.split(' ')[0] ?? subcommand;
311
+ if (!FILE_OPERAND_SUBCOMMANDS.has(baseSubcommand) &&
312
+ subcommand !== 'checkout' &&
313
+ subcommand !== 'restore') {
314
+ return [];
315
+ }
316
+ const redirects = extractRedirectTargets(['git', baseSubcommand, ...args]);
317
+ const skipped = extractMessageSkippedIndices(args);
318
+ const optionTerminator = args.indexOf('--');
319
+ const operands = [...redirects];
320
+ if (REF_ONLY_WITHOUT_TERMINATOR.has(subcommand) && optionTerminator < 0) {
321
+ return operands;
322
+ }
323
+ for (let index = 0; index < args.length; index += 1) {
324
+ if (skipped.has(index)) {
325
+ continue;
326
+ }
327
+ const token = args[index];
328
+ if (!token || token.startsWith('-') || token === '--') {
329
+ continue;
330
+ }
331
+ if (optionTerminator >= 0 && index < optionTerminator) {
332
+ continue;
333
+ }
334
+ if (subcommand === 'branch' ||
335
+ subcommand === 'push' ||
336
+ subcommand === 'fetch' ||
337
+ subcommand === 'pull') {
338
+ continue;
339
+ }
340
+ if (baseSubcommand === 'diff' && optionTerminator < 0) {
341
+ if (looksLikeDiffPathOperand(token)) {
342
+ operands.push(token);
343
+ }
344
+ continue;
345
+ }
346
+ if (looksLikeFileOperand(token) || baseSubcommand === 'add' || baseSubcommand === 'rm') {
347
+ operands.push(token);
348
+ }
349
+ }
350
+ return operands;
351
+ }
352
+ function classifyDestructiveGit(subcommand, args) {
353
+ if (subcommand === 'reset' && isHardReset(args)) {
354
+ return {
355
+ reason: 'git_history_destructive',
356
+ signals: ['git_history_destructive', 'git.reset.hard'],
357
+ };
358
+ }
359
+ if (subcommand === 'clean' && !isDryRunClean(args)) {
360
+ return { reason: 'git_history_destructive', signals: ['git_history_destructive', 'git.clean'] };
361
+ }
362
+ if (subcommand === 'stash clear') {
363
+ return {
364
+ reason: 'git_history_destructive',
365
+ signals: ['git_history_destructive', 'git.stash.clear'],
366
+ };
367
+ }
368
+ if (subcommand === 'worktree remove' && hasFlag(args, '--force')) {
369
+ return {
370
+ reason: 'git_history_destructive',
371
+ signals: ['git_history_destructive', 'git.worktree.remove.force'],
372
+ };
373
+ }
374
+ if (subcommand === 'restore' && hasFlag(args, '--worktree', '-W')) {
375
+ return {
376
+ reason: 'git_history_destructive',
377
+ signals: ['git_history_destructive', 'git.restore.worktree'],
378
+ };
379
+ }
380
+ return null;
381
+ }
382
+ export function isGitReadOnlyInvocation(tokens, baseCwd) {
383
+ const semantics = classifyGitCommand(tokens, baseCwd);
384
+ return semantics?.isReadOnly === true;
385
+ }
386
+ export function classifyGitCommand(tokens, baseCwd) {
387
+ const normalized = normalizeGitInvocation(tokens, baseCwd);
388
+ if (!normalized) {
389
+ return null;
390
+ }
391
+ const { subcommand, args, effectiveCwd, gitDir, workTree } = normalized;
392
+ const normalizedKey = `git ${subcommand}`;
393
+ const gitWorkTree = resolveGitWorkTree(baseCwd, effectiveCwd, workTree, gitDir);
394
+ const effectiveGitDir = gitDir ? path.resolve(effectiveCwd ?? baseCwd, gitDir) : undefined;
395
+ const scopeTargets = [effectiveCwd, gitWorkTree, effectiveGitDir].filter((target, index, targets) => Boolean(target) && targets.indexOf(target) === index);
396
+ const pathTargets = extractGitFileOperands(subcommand, args);
397
+ const signals = [`git.${subcommand.replaceAll(' ', '.')}`];
398
+ const destructive = classifyDestructiveGit(subcommand, args);
399
+ if (destructive) {
400
+ return {
401
+ effect: 'local_mutation',
402
+ pathTargets,
403
+ scopeTargets,
404
+ signals: [...signals, ...destructive.signals],
405
+ requiresAsk: destructive,
406
+ effectiveCwd,
407
+ gitWorkTree,
408
+ normalizedKey,
409
+ isReadOnly: false,
410
+ };
411
+ }
412
+ if (subcommand === 'push') {
413
+ return {
414
+ effect: 'remote_mutation',
415
+ pathTargets,
416
+ scopeTargets,
417
+ signals: [...signals, 'git.push'],
418
+ effectiveCwd,
419
+ gitWorkTree,
420
+ normalizedKey,
421
+ isReadOnly: false,
422
+ };
423
+ }
424
+ if (REMOTE_SUBCOMMANDS.has(subcommand)) {
425
+ return {
426
+ effect: 'remote_mutation',
427
+ pathTargets: [],
428
+ scopeTargets,
429
+ signals: [...signals, 'git.remote'],
430
+ egressClass: 'ambiguous',
431
+ effectiveCwd,
432
+ gitWorkTree,
433
+ normalizedKey,
434
+ isReadOnly: false,
435
+ };
436
+ }
437
+ if (subcommand === 'branch') {
438
+ const effect = gitBranchEffect(args);
439
+ return {
440
+ effect,
441
+ pathTargets: [],
442
+ scopeTargets,
443
+ signals,
444
+ effectiveCwd,
445
+ gitWorkTree,
446
+ normalizedKey,
447
+ isReadOnly: effect === 'read_only',
448
+ };
449
+ }
450
+ if (subcommand === 'checkout') {
451
+ return {
452
+ effect: 'local_mutation',
453
+ pathTargets,
454
+ scopeTargets,
455
+ signals,
456
+ effectiveCwd,
457
+ gitWorkTree,
458
+ normalizedKey,
459
+ isReadOnly: false,
460
+ };
461
+ }
462
+ if (subcommand === 'clean' && isDryRunClean(args)) {
463
+ return {
464
+ effect: 'read_only',
465
+ pathTargets: [],
466
+ scopeTargets,
467
+ signals,
468
+ effectiveCwd,
469
+ gitWorkTree,
470
+ normalizedKey,
471
+ isReadOnly: true,
472
+ };
473
+ }
474
+ if (subcommand === 'reset' && !isHardReset(args)) {
475
+ return {
476
+ effect: 'local_mutation',
477
+ pathTargets: [],
478
+ scopeTargets,
479
+ signals,
480
+ effectiveCwd,
481
+ gitWorkTree,
482
+ normalizedKey,
483
+ isReadOnly: false,
484
+ };
485
+ }
486
+ if (subcommand === 'tag' && hasFlag(args, '--list', '-l')) {
487
+ return {
488
+ effect: 'read_only',
489
+ pathTargets: [],
490
+ scopeTargets,
491
+ signals,
492
+ effectiveCwd,
493
+ gitWorkTree,
494
+ normalizedKey,
495
+ isReadOnly: true,
496
+ };
497
+ }
498
+ if (READ_ONLY_SUBCOMMANDS.has(subcommand) || READ_ONLY_COMPOUND_SUBCOMMANDS.has(subcommand)) {
499
+ return {
500
+ effect: 'read_only',
501
+ pathTargets: subcommand === 'show' || subcommand === 'diff' ? pathTargets : [],
502
+ scopeTargets,
503
+ signals,
504
+ effectiveCwd,
505
+ gitWorkTree,
506
+ normalizedKey,
507
+ isReadOnly: true,
508
+ };
509
+ }
510
+ if (LOCAL_MUTATION_SUBCOMMANDS.has(subcommand) ||
511
+ LOCAL_MUTATION_COMPOUND_SUBCOMMANDS.has(subcommand)) {
512
+ return {
513
+ effect: 'local_mutation',
514
+ pathTargets: subcommand === 'commit' ? [] : pathTargets,
515
+ scopeTargets,
516
+ signals,
517
+ effectiveCwd,
518
+ gitWorkTree,
519
+ normalizedKey,
520
+ isReadOnly: false,
521
+ };
522
+ }
523
+ return {
524
+ effect: 'unknown',
525
+ pathTargets,
526
+ scopeTargets,
527
+ signals,
528
+ effectiveCwd,
529
+ gitWorkTree,
530
+ normalizedKey,
531
+ isReadOnly: false,
532
+ };
533
+ }
@@ -1,2 +1,3 @@
1
1
  import type { JudgeTrace } from './types.js';
2
+ export declare function hashJudgeSessionRef(value: string): string;
2
3
  export declare function judgeTraceAuditFields(trace?: JudgeTrace): Record<string, unknown>;
@@ -1,3 +1,7 @@
1
+ import { createHash } from 'node:crypto';
2
+ export function hashJudgeSessionRef(value) {
3
+ return createHash('sha256').update(value).digest('hex').slice(0, 16);
4
+ }
1
5
  export function judgeTraceAuditFields(trace) {
2
6
  if (!trace) {
3
7
  return {};
@@ -7,9 +11,37 @@ export function judgeTraceAuditFields(trace) {
7
11
  judgeModelRequested: trace.modelRequested,
8
12
  judgeModelResolved: trace.modelResolved,
9
13
  judgeLatencyMs: trace.latencyMs,
14
+ ...(trace.transport ? { judgeTransport: trace.transport } : {}),
10
15
  ...(trace.outboundRedacted !== undefined
11
16
  ? { judgeOutboundRedacted: trace.outboundRedacted }
12
17
  : {}),
13
- ...(trace.fallbackReason ? { judgeFallbackReason: trace.fallbackReason } : {}),
18
+ ...(trace.judgeFallbackReason
19
+ ? { judgeFallbackReason: trace.judgeFallbackReason }
20
+ : trace.fallbackReason
21
+ ? { judgeFallbackReason: trace.fallbackReason }
22
+ : {}),
23
+ ...(trace.judgeSessionUsed !== undefined ? { judgeSessionUsed: trace.judgeSessionUsed } : {}),
24
+ ...(trace.judgeSessionReused !== undefined
25
+ ? { judgeSessionReused: trace.judgeSessionReused }
26
+ : {}),
27
+ ...(trace.judgeSessionRefHash ? { judgeSessionRefHash: trace.judgeSessionRefHash } : {}),
28
+ ...(trace.judgeSessionResetReason
29
+ ? { judgeSessionResetReason: trace.judgeSessionResetReason }
30
+ : {}),
31
+ ...(trace.judgeConnectMs !== undefined ? { judgeConnectMs: trace.judgeConnectMs } : {}),
32
+ ...(trace.judgeEvalMs !== undefined ? { judgeEvalMs: trace.judgeEvalMs } : {}),
33
+ ...(trace.judgeParseMs !== undefined ? { judgeParseMs: trace.judgeParseMs } : {}),
34
+ ...(trace.judgeShadowCompared !== undefined
35
+ ? { judgeShadowCompared: trace.judgeShadowCompared }
36
+ : {}),
37
+ ...(trace.judgeShadowMismatch !== undefined
38
+ ? { judgeShadowMismatch: trace.judgeShadowMismatch }
39
+ : {}),
40
+ ...(trace.judgeShadowMismatchRateWindow !== undefined
41
+ ? { judgeShadowMismatchRateWindow: trace.judgeShadowMismatchRateWindow }
42
+ : {}),
43
+ ...(trace.judgeKillSwitchTriggered !== undefined
44
+ ? { judgeKillSwitchTriggered: trace.judgeKillSwitchTriggered }
45
+ : {}),
14
46
  };
15
47
  }
@@ -0,0 +1,20 @@
1
+ import { JUDGE_LATENCY_SLO } from './judge-runtime-config.js';
2
+ export type JudgeLatencyPhase = 'tier0' | 'gate' | 'tier1_spawn' | 'tier1_session' | 'tier1_connect' | 'tier1_eval' | 'tier1_parse';
3
+ export interface JudgeLatencySample {
4
+ phase: JudgeLatencyPhase;
5
+ latencyMs: number;
6
+ at: number;
7
+ }
8
+ export interface JudgeLatencyPercentiles {
9
+ p50: number;
10
+ p95: number;
11
+ count: number;
12
+ }
13
+ export declare function resetJudgeLatencySamples(): void;
14
+ export declare function recordJudgeLatency(phase: JudgeLatencyPhase, latencyMs: number, at?: number): void;
15
+ export declare function judgeLatencyPercentiles(phase: JudgeLatencyPhase): JudgeLatencyPercentiles;
16
+ export declare function judgeLatencySloReport(): {
17
+ slo: typeof JUDGE_LATENCY_SLO;
18
+ measured: Record<JudgeLatencyPhase, JudgeLatencyPercentiles>;
19
+ sessionMeetsTarget: boolean;
20
+ };
@@ -0,0 +1,49 @@
1
+ import { JUDGE_LATENCY_SLO } from './judge-runtime-config.js';
2
+ const samples = [];
3
+ const MAX_SAMPLES = 2_000;
4
+ export function resetJudgeLatencySamples() {
5
+ samples.length = 0;
6
+ }
7
+ export function recordJudgeLatency(phase, latencyMs, at = Date.now()) {
8
+ if (!Number.isFinite(latencyMs) || latencyMs < 0) {
9
+ return;
10
+ }
11
+ samples.push({ phase, latencyMs, at });
12
+ if (samples.length > MAX_SAMPLES) {
13
+ samples.splice(0, samples.length - MAX_SAMPLES);
14
+ }
15
+ }
16
+ function percentile(values, p) {
17
+ if (values.length === 0) {
18
+ return 0;
19
+ }
20
+ const sorted = [...values].sort((a, b) => a - b);
21
+ const index = Math.min(sorted.length - 1, Math.ceil((p / 100) * sorted.length) - 1);
22
+ return sorted[Math.max(0, index)] ?? 0;
23
+ }
24
+ export function judgeLatencyPercentiles(phase) {
25
+ const values = samples
26
+ .filter((sample) => sample.phase === phase)
27
+ .map((sample) => sample.latencyMs);
28
+ return {
29
+ p50: percentile(values, 50),
30
+ p95: percentile(values, 95),
31
+ count: values.length,
32
+ };
33
+ }
34
+ export function judgeLatencySloReport() {
35
+ const measured = {
36
+ tier0: judgeLatencyPercentiles('tier0'),
37
+ gate: judgeLatencyPercentiles('gate'),
38
+ tier1_spawn: judgeLatencyPercentiles('tier1_spawn'),
39
+ tier1_session: judgeLatencyPercentiles('tier1_session'),
40
+ tier1_connect: judgeLatencyPercentiles('tier1_connect'),
41
+ tier1_eval: judgeLatencyPercentiles('tier1_eval'),
42
+ tier1_parse: judgeLatencyPercentiles('tier1_parse'),
43
+ };
44
+ const spawnP95 = measured.tier1_spawn.count > 0 ? measured.tier1_spawn.p95 : JUDGE_LATENCY_SLO.spawnBaselineP95Ms;
45
+ const sessionP95 = measured.tier1_session.count > 0 ? measured.tier1_session.p95 : spawnP95;
46
+ const targetP95 = spawnP95 * (1 - JUDGE_LATENCY_SLO.tier1P95ReductionTarget);
47
+ const sessionMeetsTarget = sessionP95 <= targetP95 && sessionP95 <= JUDGE_LATENCY_SLO.sessionMaxP95Ms;
48
+ return { slo: JUDGE_LATENCY_SLO, measured, sessionMeetsTarget };
49
+ }
@@ -0,0 +1,42 @@
1
+ import type { CliJudgeRunCommand } from './judge-cli.js';
2
+ import type { BelayJudgeSessionConfig } from './judge-runtime-config.js';
3
+ import { type BrokerEvaluateRequest, type BrokerEvaluateResult, JudgeSessionBroker, stopRepoJudgeSessionBroker } from './judge-session-broker.js';
4
+ import type { JudgeSessionResetReason } from './judge-session-guard.js';
5
+ export declare const JUDGE_BROKER_SOCKET = "judge-broker.sock";
6
+ export declare const JUDGE_BROKER_STATUS = "judge-broker.json";
7
+ export declare const JUDGE_BROKER_PID = "judge-broker.pid";
8
+ export declare const JUDGE_BROKER_SESSION = "judge-broker-session.json";
9
+ export interface JudgeBrokerStatus {
10
+ pid: number;
11
+ repoRoot: string;
12
+ socketPath: string;
13
+ startedAt: string;
14
+ }
15
+ export interface JudgeBrokerPaths {
16
+ stateDir: string;
17
+ socketPath: string;
18
+ statusPath: string;
19
+ pidPath: string;
20
+ sessionConfigPath: string;
21
+ }
22
+ export declare function judgeBrokerPaths(stateDir: string): JudgeBrokerPaths;
23
+ export declare function ensureJudgeBrokerDaemon(stateDir: string, repoRoot: string, sessionConfig: BelayJudgeSessionConfig): Promise<JudgeBrokerPaths>;
24
+ export declare function cleanupJudgeBrokerArtifacts(paths: JudgeBrokerPaths): Promise<void>;
25
+ export declare function stopJudgeBrokerDaemon(stateDir: string): Promise<number>;
26
+ export declare function evaluateViaJudgeBroker(options: {
27
+ stateDir: string;
28
+ repoRoot: string;
29
+ sessionConfig: BelayJudgeSessionConfig;
30
+ request: BrokerEvaluateRequest;
31
+ timeoutMs: number;
32
+ runCommand?: CliJudgeRunCommand;
33
+ }): Promise<BrokerEvaluateResult>;
34
+ export declare function resetJudgeBrokerForTests(): void;
35
+ export declare function invalidateJudgeSession(options: {
36
+ stateDir: string;
37
+ repoRoot: string;
38
+ sessionConfig: BelayJudgeSessionConfig;
39
+ sessionKey: string;
40
+ reason: JudgeSessionResetReason;
41
+ }): Promise<void>;
42
+ export { JudgeSessionBroker, stopRepoJudgeSessionBroker };