@alecsibilia/luca 14.0.0-alpha.2 → 14.0.0-alpha.21

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 (321) hide show
  1. package/CHANGELOG.md +76 -0
  2. package/README.md +7 -2
  3. package/board/client/board-look.ts +31 -1
  4. package/board/client/board-panel-parts.tsx +150 -0
  5. package/board/client/board-panel-stages.ts +99 -0
  6. package/board/client/board-panel-styles.ts +223 -0
  7. package/board/client/board-panel.tsx +77 -1275
  8. package/board/client/engine-life.ts +48 -0
  9. package/board/client/final-review-stack.tsx +144 -0
  10. package/board/client/heartbeat.tsx +61 -0
  11. package/board/client/helper-lines.tsx +69 -0
  12. package/board/client/live-feed.tsx +86 -0
  13. package/board/client/live-line.ts +73 -0
  14. package/board/client/model-line.tsx +178 -0
  15. package/board/client/needs-you-section.tsx +133 -0
  16. package/board/client/one-line.tsx +193 -0
  17. package/board/client/repo-wait-banner.tsx +69 -0
  18. package/board/client/run-footer.tsx +68 -0
  19. package/board/client/run-header.tsx +189 -0
  20. package/board/client/run-picker.tsx +98 -0
  21. package/board/client/status-bar.tsx +131 -0
  22. package/board/client/step-dots.tsx +127 -0
  23. package/board/client/stuck-actions.tsx +269 -0
  24. package/board/client/ticket-card-view.tsx +238 -0
  25. package/board/client/ticket-detail.tsx +87 -0
  26. package/board/client/ticket-stack.tsx +119 -0
  27. package/board/client/ticket-wait.ts +188 -0
  28. package/board/client/timeline-rows.tsx +41 -14
  29. package/board/client/usage-line.tsx +51 -0
  30. package/board/client/wait-line.tsx +39 -0
  31. package/board/index.client.tsx +1 -1
  32. package/board/index.server.ts +7 -1
  33. package/board/package.json +1 -1
  34. package/board/server/board-panel-state-helpers.ts +67 -0
  35. package/board/server/board-server-check.ts +294 -0
  36. package/board/server/board-server-engine.ts +129 -0
  37. package/board/server/board-server-events.ts +134 -0
  38. package/board/server/board-server-read.ts +190 -0
  39. package/board/server/board-server-resume.ts +164 -0
  40. package/board/server/board-server-start.ts +174 -0
  41. package/board/server/board-server-store.ts +281 -0
  42. package/board/server/board-server-types.ts +114 -0
  43. package/board/server/board-server-util.ts +4 -0
  44. package/board/server/board-server.ts +52 -943
  45. package/board/server/board-vocabulary-engine.ts +144 -0
  46. package/board/server/board-vocabulary-review.ts +115 -0
  47. package/board/server/board-vocabulary-run.ts +145 -0
  48. package/board/server/board-vocabulary-shapes.ts +153 -0
  49. package/board/server/board-vocabulary.ts +20 -375
  50. package/board/server/current-step-panel-helpers.ts +108 -0
  51. package/board/server/describe-record.ts +403 -0
  52. package/board/server/engine-launch.ts +20 -3
  53. package/board/server/engine-restart-helpers.ts +105 -0
  54. package/board/server/engine-watch.ts +45 -5
  55. package/board/server/journal-rebuild-helpers.ts +169 -0
  56. package/board/server/live-file.ts +130 -0
  57. package/board/server/luca-version.ts +1 -1
  58. package/board/server/make-rows.ts +14 -629
  59. package/board/server/record-texts.ts +350 -0
  60. package/board/server/reduce-board-agent-start.ts +224 -0
  61. package/board/server/reduce-board-agents.ts +160 -0
  62. package/board/server/reduce-board-counters.ts +138 -0
  63. package/board/server/reduce-board-create.ts +148 -0
  64. package/board/server/reduce-board-final-review.ts +174 -0
  65. package/board/server/reduce-board-needs-you.ts +283 -0
  66. package/board/server/reduce-board-reasons.ts +59 -0
  67. package/board/server/reduce-board-run.ts +255 -0
  68. package/board/server/reduce-board-settle.ts +88 -0
  69. package/board/server/reduce-board-steps.ts +226 -0
  70. package/board/server/reduce-board-text.ts +139 -0
  71. package/board/server/reduce-board-ticket-checks.ts +168 -0
  72. package/board/server/reduce-board-ticket-join.ts +144 -0
  73. package/board/server/reduce-board-ticket-start.ts +132 -0
  74. package/board/server/reduce-board-update.ts +117 -0
  75. package/board/server/reduce-board-usage.ts +247 -0
  76. package/board/server/reduce-board.ts +100 -1917
  77. package/board/server/reply-post.ts +407 -0
  78. package/board/server/run-registry.ts +8 -4
  79. package/board/server/ticket-stages-panel-helpers.ts +296 -0
  80. package/board/shared/board-rows.ts +9 -0
  81. package/board/shared/board-rpc.ts +42 -0
  82. package/board/shared/board-state-counts.ts +77 -0
  83. package/board/shared/board-state-live.ts +99 -0
  84. package/board/shared/board-state-review.ts +59 -0
  85. package/board/shared/board-state-run.ts +236 -0
  86. package/board/shared/board-state-ticket.ts +257 -0
  87. package/board/shared/board-state-usage.ts +107 -0
  88. package/board/shared/board-state.ts +97 -567
  89. package/board/shared/reply-actions.ts +207 -0
  90. package/engine/agents/agent-launcher.ts +27 -4
  91. package/engine/agents/claude-launcher-helpers.ts +233 -0
  92. package/engine/agents/claude-launcher.ts +28 -669
  93. package/engine/agents/claude-messages.ts +212 -0
  94. package/engine/agents/claude-options.ts +43 -8
  95. package/engine/agents/common-instructions.ts +138 -0
  96. package/engine/agents/input-channel.ts +36 -0
  97. package/engine/agents/launch-paths.ts +29 -0
  98. package/engine/agents/launch-session.ts +227 -0
  99. package/engine/agents/learner-instructions.ts +31 -0
  100. package/engine/agents/open-session.ts +210 -0
  101. package/engine/agents/prompt-sections.ts +95 -0
  102. package/engine/agents/rejoin-prompts.ts +135 -0
  103. package/engine/agents/reviewer-instructions.ts +99 -0
  104. package/engine/agents/role-instructions.ts +31 -244
  105. package/engine/agents/role-prompts.ts +39 -109
  106. package/engine/agents/role-results.ts +106 -7
  107. package/engine/agents/scripted-launcher.ts +21 -6
  108. package/engine/agents/session-pool.ts +124 -0
  109. package/engine/agents/turn-progress.ts +283 -0
  110. package/engine/agents/writer-instructions.ts +151 -0
  111. package/engine/cli/computer-adapters-real.ts +15 -3
  112. package/engine/cli/computer-adapters.ts +2 -0
  113. package/engine/cli/computer-checks.ts +87 -4
  114. package/engine/cli/config-reload-text.ts +39 -0
  115. package/engine/cli/decision-model-setup.ts +70 -0
  116. package/engine/cli/doctor-command.ts +1 -0
  117. package/engine/cli/doctor.ts +21 -3
  118. package/engine/cli/going-runs.ts +192 -36
  119. package/engine/cli/init-command.ts +1 -0
  120. package/engine/cli/init.ts +23 -1
  121. package/engine/cli/live-runs.ts +73 -0
  122. package/engine/cli/luca-run.ts +42 -7
  123. package/engine/cli/luca-skills.ts +183 -0
  124. package/engine/cli/luca.ts +8 -1
  125. package/engine/cli/models-command.ts +119 -0
  126. package/engine/cli/read-run.ts +81 -0
  127. package/engine/cli/run-args.ts +33 -6
  128. package/engine/cli/run-demo.ts +143 -0
  129. package/engine/cli/run-end.ts +184 -0
  130. package/engine/cli/run-modes-helpers.ts +81 -0
  131. package/engine/cli/run-modes.ts +62 -439
  132. package/engine/cli/setup.ts +5 -48
  133. package/engine/cli/unfinished-runs.ts +122 -0
  134. package/engine/cli/upgrade-command.ts +212 -30
  135. package/engine/cli/upgrade-finish.ts +149 -0
  136. package/engine/cli/upgrade.ts +112 -71
  137. package/engine/config/config-fields.ts +127 -0
  138. package/engine/config/config-reload.ts +250 -0
  139. package/engine/config/engine-config-schema.ts +235 -0
  140. package/engine/config/engine-config.ts +72 -142
  141. package/engine/config/test-commands.ts +102 -0
  142. package/engine/core/agent-guards-helpers.ts +84 -0
  143. package/engine/core/agent-turn.ts +395 -0
  144. package/engine/core/already-done.ts +44 -1
  145. package/engine/core/build-action.ts +214 -0
  146. package/engine/core/build-context.ts +135 -0
  147. package/engine/core/build-launch.ts +259 -0
  148. package/engine/core/commit-in.ts +282 -0
  149. package/engine/core/decide-build-early.ts +108 -0
  150. package/engine/core/decide-build-ending.ts +145 -0
  151. package/engine/core/decide-build-helpers.ts +87 -0
  152. package/engine/core/decide-build-tickets.ts +207 -0
  153. package/engine/core/decide-build-view.ts +103 -0
  154. package/engine/core/decide-build.ts +101 -1368
  155. package/engine/core/decide-final-fix.ts +203 -0
  156. package/engine/core/decide-final-review.ts +55 -310
  157. package/engine/core/decide-red-green.ts +398 -0
  158. package/engine/core/decide-rejoin.ts +259 -0
  159. package/engine/core/decide-repo-wait.ts +129 -0
  160. package/engine/core/decide-run-budget.ts +86 -12
  161. package/engine/core/decide-stuck-helpers.ts +127 -0
  162. package/engine/core/decide-stuck.ts +60 -8
  163. package/engine/core/decide-ticket-review.ts +95 -0
  164. package/engine/core/decide-ticket.ts +267 -0
  165. package/engine/core/decide-waits.ts +47 -0
  166. package/engine/core/decide.ts +74 -23
  167. package/engine/core/engine-chores.ts +163 -0
  168. package/engine/core/environment-rerun.ts +83 -0
  169. package/engine/core/evidence-text.ts +84 -0
  170. package/engine/core/execute-action.ts +303 -0
  171. package/engine/core/execute-already-done-check.ts +133 -0
  172. package/engine/core/execute-build.ts +68 -944
  173. package/engine/core/execute-changeset.ts +5 -7
  174. package/engine/core/execute-final-review.ts +125 -13
  175. package/engine/core/execute-gates.ts +182 -0
  176. package/engine/core/execute-intake.ts +130 -0
  177. package/engine/core/execute-join.ts +276 -0
  178. package/engine/core/execute-memory.ts +12 -0
  179. package/engine/core/execute-pull-request.ts +84 -0
  180. package/engine/core/execute-red-check.ts +219 -0
  181. package/engine/core/execute-repo-wait.ts +129 -0
  182. package/engine/core/execute-stuck.ts +25 -10
  183. package/engine/core/execute-waits.ts +158 -0
  184. package/engine/core/execute-with-jev.ts +118 -0
  185. package/engine/core/execute.ts +124 -831
  186. package/engine/core/final-failure-cap.ts +38 -0
  187. package/engine/core/final-review-action.ts +125 -0
  188. package/engine/core/final-review-text.ts +63 -17
  189. package/engine/core/final-stuck-text.ts +86 -0
  190. package/engine/core/fix-loop-text.ts +49 -2
  191. package/engine/core/left-out.ts +120 -0
  192. package/engine/core/live-file.ts +372 -0
  193. package/engine/core/loop-caps.ts +25 -2
  194. package/engine/core/not-in-run.ts +7 -3
  195. package/engine/core/pull-request-text.ts +255 -6
  196. package/engine/core/repo-wait-text.ts +81 -0
  197. package/engine/core/review-text.ts +55 -1
  198. package/engine/core/run-crash-recovery-helpers.ts +16 -0
  199. package/engine/core/run-session-close-helpers.ts +96 -0
  200. package/engine/core/run-start.ts +148 -0
  201. package/engine/core/run-stuck-text.ts +86 -0
  202. package/engine/core/schedule-rules.ts +193 -0
  203. package/engine/core/stuck-detail-text.ts +89 -0
  204. package/engine/core/stuck-format.ts +22 -0
  205. package/engine/core/stuck-text.ts +29 -332
  206. package/engine/core/ticket-stuck-text.ts +192 -0
  207. package/engine/core/worktree-changes.ts +106 -0
  208. package/engine/gates/already-done-check.ts +120 -0
  209. package/engine/gates/feature-map.ts +96 -0
  210. package/engine/gates/gate-runner.ts +108 -108
  211. package/engine/gates/gate-schemas.ts +61 -2
  212. package/engine/gates/leftover-scan.ts +154 -31
  213. package/engine/gates/new-tests.ts +45 -0
  214. package/engine/gates/own-files-check.ts +98 -0
  215. package/engine/gates/prepare-check.ts +60 -0
  216. package/engine/gates/prepare-slots.ts +99 -0
  217. package/engine/gates/red-check.ts +184 -16
  218. package/engine/gates/shell-check.ts +64 -0
  219. package/engine/gates/slot-waits.ts +55 -0
  220. package/engine/gates/slow-test-runner.ts +59 -0
  221. package/engine/gates/test-checks.ts +161 -0
  222. package/engine/gates/test-runner.ts +164 -5
  223. package/engine/gates/test-slots.ts +48 -0
  224. package/engine/git/git-adapter.ts +55 -396
  225. package/engine/git/git-commits.ts +132 -0
  226. package/engine/git/git-join.ts +139 -0
  227. package/engine/git/git-read.ts +123 -0
  228. package/engine/git/git-run.ts +32 -0
  229. package/engine/git/git-worktrees.ts +143 -0
  230. package/engine/git/outside-links.ts +55 -0
  231. package/engine/guards/after-turn-check.ts +31 -5
  232. package/engine/guards/guard-hook.ts +60 -12
  233. package/engine/guards/guard-roles.ts +68 -0
  234. package/engine/guards/helper-rules.ts +92 -0
  235. package/engine/guards/permission-rules.ts +161 -0
  236. package/engine/guards/role-rules-helpers.ts +48 -0
  237. package/engine/guards/role-rules.ts +34 -606
  238. package/engine/guards/shell-rules.ts +344 -0
  239. package/engine/guards/worktree-git.ts +208 -0
  240. package/engine/guards/worktree-snapshot.ts +412 -0
  241. package/engine/guards/worktree-state.ts +28 -825
  242. package/engine/guards/worktree-undo.ts +260 -0
  243. package/engine/guards/write-rules.ts +149 -0
  244. package/engine/index.ts +30 -9
  245. package/engine/intake/criteria-checks.ts +114 -0
  246. package/engine/intake/intake-checks.ts +164 -30
  247. package/engine/intake/intake-schemas.ts +37 -2
  248. package/engine/jev/clef-client.ts +259 -0
  249. package/engine/jev/decision-model-credentials.ts +212 -0
  250. package/engine/jev/jev-jobs.ts +144 -60
  251. package/engine/jev/jev-schemas.ts +86 -5
  252. package/engine/jev/jev-shadow-helpers.ts +218 -0
  253. package/engine/jev/jev-shadow.ts +109 -64
  254. package/engine/jev/model-pick.ts +275 -0
  255. package/engine/journal/agent-entries.ts +167 -0
  256. package/engine/journal/agent-message-entries.ts +72 -0
  257. package/engine/journal/agent-turn-replay.ts +203 -0
  258. package/engine/journal/already-done-entries.ts +55 -0
  259. package/engine/journal/commit-entries.ts +56 -0
  260. package/engine/journal/cross-ticket-replay.ts +222 -0
  261. package/engine/journal/decision-model-entries.ts +86 -0
  262. package/engine/journal/entry-fields.ts +10 -0
  263. package/engine/journal/final-review-entries.ts +128 -0
  264. package/engine/journal/final-review-replay.ts +324 -0
  265. package/engine/journal/final-review-round.ts +320 -0
  266. package/engine/journal/final-review-state.ts +180 -0
  267. package/engine/journal/gate-entries.ts +81 -0
  268. package/engine/journal/intake-entries.ts +87 -0
  269. package/engine/journal/join-entries.ts +126 -0
  270. package/engine/journal/journal-entry-schema.ts +218 -0
  271. package/engine/journal/journal-kinds.ts +97 -0
  272. package/engine/journal/journal-record-schema.ts +218 -0
  273. package/engine/journal/journal-record.ts +43 -1400
  274. package/engine/journal/journal.ts +6 -1
  275. package/engine/journal/limit-entries.ts +90 -0
  276. package/engine/journal/memory-entries.ts +124 -0
  277. package/engine/journal/memory-replay.ts +157 -0
  278. package/engine/journal/plan-replay.ts +139 -0
  279. package/engine/journal/pull-request-entries.ts +41 -0
  280. package/engine/journal/replay.ts +157 -2144
  281. package/engine/journal/reply-replay.ts +144 -0
  282. package/engine/journal/repo-wait-entries.ts +53 -0
  283. package/engine/journal/run-entries.ts +172 -0
  284. package/engine/journal/run-state.ts +264 -0
  285. package/engine/journal/shared-failure-replay.ts +133 -0
  286. package/engine/journal/stuck-entries.ts +193 -0
  287. package/engine/journal/ticket-progress.ts +392 -0
  288. package/engine/journal/ticket-replay.ts +411 -0
  289. package/engine/journal/ticket-result-replay.ts +364 -0
  290. package/engine/journal/ticket-retry-replay.ts +142 -0
  291. package/engine/journal/worktree-entries.ts +122 -0
  292. package/engine/models/model-score.ts +512 -0
  293. package/engine/shell/run-command.ts +187 -18
  294. package/engine/testing/agent-turn-fixtures.ts +287 -0
  295. package/engine/testing/already-done-fixtures.ts +97 -0
  296. package/engine/testing/build-fixtures.ts +68 -973
  297. package/engine/testing/checkouts-fixtures.ts +48 -0
  298. package/engine/testing/final-review-fixtures.ts +62 -1
  299. package/engine/testing/final-review-steps.ts +143 -0
  300. package/engine/testing/limit-fixtures.ts +85 -0
  301. package/engine/testing/live-file-fixtures.ts +57 -0
  302. package/engine/testing/practice-repo.ts +10 -4
  303. package/engine/testing/practice-run.ts +1 -1
  304. package/engine/testing/reply-fixtures.ts +88 -0
  305. package/engine/testing/run-budget-fixtures.ts +165 -0
  306. package/engine/testing/run-fixtures.ts +219 -0
  307. package/engine/testing/sdk-play.ts +177 -0
  308. package/engine/testing/setup-repo.ts +194 -0
  309. package/engine/testing/step-record-fixtures.ts +55 -0
  310. package/engine/testing/ticket-step-fixtures.ts +272 -0
  311. package/engine/testing/wait-scenarios.ts +186 -0
  312. package/engine/tracker/gh-retry.ts +217 -0
  313. package/engine/tracker/github-tracker.ts +310 -90
  314. package/engine/tracker/in-memory-tracker.ts +8 -4
  315. package/engine/tracker/tracker.ts +131 -6
  316. package/package.json +2 -1
  317. package/skills/luca-retro/SKILL.md +74 -0
  318. package/skills/luca-retro/scripts/retro-summary.ts +1356 -0
  319. package/skills/luca-unstick/SKILL.md +153 -0
  320. package/skills/luca-unstick/scripts/stuck-summary.ts +1063 -0
  321. package/engine/jev/jev-client.ts +0 -173
@@ -0,0 +1,68 @@
1
+ /**
2
+ * The guard's roles: which role each agent plays by, the tools each sees, and
3
+ * the shape of a guard decision.
4
+ */
5
+ import { z } from 'zod'
6
+
7
+ import type { AgentRole } from '../agents/role-results'
8
+
9
+ /**
10
+ * The roles the **guard** knows. `reviewer` covers every reviewer (the ticket
11
+ * reviewer and the final review's lenses). The learner has no agent
12
+ * role yet (#370) but its rules are here already.
13
+ */
14
+ export const GuardRoleSchema = z.enum([
15
+ 'test-writer',
16
+ 'implementer',
17
+ 'reviewer',
18
+ 'learner',
19
+ ])
20
+
21
+ export type GuardRole = z.infer<typeof GuardRoleSchema>
22
+
23
+ const GUARD_ROLE_OF: Record<AgentRole, GuardRole> = {
24
+ 'test-writer': 'test-writer',
25
+ implementer: 'implementer',
26
+ 'ticket-reviewer': 'reviewer',
27
+ 'architecture-lens': 'reviewer',
28
+ 'simplification-lens': 'reviewer',
29
+ 'security-lens': 'reviewer',
30
+ 'integration-lens': 'reviewer',
31
+ 'rules-lens': 'reviewer',
32
+ learner: 'learner',
33
+ }
34
+
35
+ /** The guard role an agent role plays by. */
36
+ export const guardRoleOf = ({ role }: { role: AgentRole }): GuardRole =>
37
+ GUARD_ROLE_OF[role]
38
+
39
+ /** Whether a guard role may change files at all. */
40
+ export const isWriter = (role: GuardRole): boolean =>
41
+ role === 'test-writer' || role === 'implementer'
42
+
43
+ /** What the guard says about one tool call. */
44
+ export type ToolDecision = { allow: true } | { allow: false; reason: string }
45
+
46
+ export const ALLOW: ToolDecision = { allow: true }
47
+
48
+ export const deny = (reason: string): ToolDecision => ({ allow: false, reason })
49
+
50
+ /** The rules an agent's guards check it against: its role, and whether it may edit tests. */
51
+ export type GuardArgs = {
52
+ role: GuardRole
53
+ /**
54
+ * Whether this agent may edit test files: true for the test-writer and a
55
+ * refactor ticket's implementer. A test-writer without it writes nothing;
56
+ * a reviewer or the learner never writes, whatever it says.
57
+ */
58
+ may_edit_tests: boolean
59
+ }
60
+
61
+ export const READ_TOOLS = ['Read', 'Grep', 'Glob']
62
+
63
+ export const TOOLS: Record<GuardRole, string[]> = {
64
+ 'test-writer': ['Read', 'Grep', 'Glob', 'Edit', 'Write', 'Bash'],
65
+ implementer: ['Read', 'Grep', 'Glob', 'Edit', 'Write', 'Bash'],
66
+ reviewer: ['Read', 'Grep', 'Glob', 'Bash'],
67
+ learner: ['Read', 'Grep', 'Glob'],
68
+ }
@@ -0,0 +1,92 @@
1
+ /**
2
+ * Pure: the rules for **helpers** (#627), the read-only Haiku sessions an
3
+ * agent may start with the SDK's `Agent` tool to search the repo.
4
+ */
5
+ import { z } from 'zod'
6
+
7
+ import { ALLOW, deny, READ_TOOLS, type ToolDecision } from './guard-roles'
8
+
9
+ /** The SDK tool that starts a helper. */
10
+ export const HELPER_TOOL = 'Agent'
11
+
12
+ /** The one helper type an agent may start. */
13
+ export const HELPER_TYPE = 'searcher'
14
+
15
+ /** Helpers always run on the newest Haiku, by its short name. */
16
+ export const HELPER_MODEL = 'haiku'
17
+
18
+ /** The only tools a helper has: it reads and searches, nothing else. */
19
+ export const HELPER_TOOLS = READ_TOOLS
20
+
21
+ /** How many helpers one agent may have running at once. */
22
+ export const MAX_HELPERS = 3
23
+
24
+ /** The `searcher` helper's definition, for the SDK's `agents` option. */
25
+ export const SEARCHER = {
26
+ description:
27
+ 'A read-only helper that searches the repo: finds files, symbols and callers, and reports what it found. It cannot edit files or run commands.',
28
+ model: HELPER_MODEL,
29
+ tools: [...HELPER_TOOLS],
30
+ prompt: 'You are a read-only searcher. You may only read and search files in the current worktree with Read, Grep and Glob. Never try to change a file or run a command. Answer with what you found: file paths, line numbers and short excerpts, as briefly as the question allows.',
31
+ }
32
+
33
+ const HelperInputSchema = z.object({
34
+ subagent_type: z.string().optional(),
35
+ model: z.string().optional(),
36
+ run_in_background: z.boolean().optional(),
37
+ isolation: z.string().optional(),
38
+ })
39
+
40
+ /**
41
+ * The guard's decision for an `Agent` call that starts a helper, apart from
42
+ * how many already run. Only a `searcher`, on Haiku (never a Fable model),
43
+ * in the foreground, so it ends inside the agent's turn and the after-turn
44
+ * check sees what it did, and in the agent's own worktree.
45
+ *
46
+ * @example
47
+ * checkHelperCall({ tool_input: { subagent_type: 'searcher', run_in_background: false } }) // { allow: true }
48
+ */
49
+ export const checkHelperCall = ({
50
+ tool_input,
51
+ }: {
52
+ tool_input: unknown
53
+ }): ToolDecision => {
54
+ const input = HelperInputSchema.safeParse(tool_input)
55
+ if (!input.success) return deny('Agent got an input of the wrong shape.')
56
+ const { subagent_type, model, run_in_background, isolation } = input.data
57
+ if (model !== undefined && /fable/i.test(model)) {
58
+ return deny(`Fable models are refused: ${model}`)
59
+ }
60
+ if (subagent_type !== HELPER_TYPE) {
61
+ return deny(
62
+ `The only helper is "${HELPER_TYPE}": ${subagent_type ?? 'no subagent_type'} is not allowed.`
63
+ )
64
+ }
65
+ if (model !== undefined && model !== HELPER_MODEL) {
66
+ return deny(`Helpers always run on ${HELPER_MODEL}: ${model}`)
67
+ }
68
+ if (run_in_background !== false) {
69
+ return deny(
70
+ 'Helpers run in the foreground: set run_in_background to false.'
71
+ )
72
+ }
73
+ if (isolation !== undefined) {
74
+ return deny('Helpers work in your own worktree: no isolation.')
75
+ }
76
+ return ALLOW
77
+ }
78
+
79
+ /**
80
+ * The guard's decision for a tool call a helper makes itself: only its
81
+ * read-only tools, whatever its agent may do.
82
+ */
83
+ export const checkHelperTool = ({
84
+ tool_name,
85
+ }: {
86
+ tool_name: string
87
+ }): ToolDecision =>
88
+ HELPER_TOOLS.includes(tool_name)
89
+ ? ALLOW
90
+ : deny(
91
+ `A helper may only use ${HELPER_TOOLS.join(', ')}: not ${tool_name}.`
92
+ )
@@ -0,0 +1,161 @@
1
+ /**
2
+ * Pure: each role's SDK permission settings, the layer under the guard hook.
3
+ */
4
+ import {
5
+ READ_TOOLS,
6
+ TOOLS,
7
+ type GuardArgs,
8
+ type GuardRole,
9
+ } from './guard-roles'
10
+ import { HELPER_TOOL, HELPER_TYPE } from './helper-rules'
11
+ import {
12
+ checkCommands,
13
+ GIT_READ_SUBCOMMANDS,
14
+ PACKAGE_MANAGERS,
15
+ wordsOf,
16
+ } from './shell-rules'
17
+ import { LOCKFILES } from './write-rules'
18
+
19
+ import {
20
+ featureMapPath,
21
+ isFeatureMapFolder,
22
+ type EngineConfig,
23
+ } from '../config/engine-config'
24
+
25
+ /** Built-in tools no agent gets, and secret files no agent reads. */
26
+ export const BASE_DISALLOWED_TOOLS = [
27
+ 'Workflow',
28
+ 'Skill',
29
+ 'WebFetch',
30
+ 'WebSearch',
31
+ 'Monitor',
32
+ 'PowerShell',
33
+ 'NotebookEdit',
34
+ 'SendMessage',
35
+ 'Artifact',
36
+ 'RemoteTrigger',
37
+ 'CronCreate',
38
+ 'PushNotification',
39
+ 'SendUserFile',
40
+ 'EnterWorktree',
41
+ 'ExitWorktree',
42
+ 'ListMcpResourcesTool',
43
+ 'ReadMcpResourceTool',
44
+ 'Bash(run_in_background:true)',
45
+ 'Read(~/.claude.json)',
46
+ 'Read(~/.claude/**)',
47
+ 'Read(~/.paseo/**)',
48
+ 'Read(~/.ssh/**)',
49
+ 'Read(~/.config/gh/**)',
50
+ ]
51
+
52
+ /**
53
+ * Deny rules every role gets, so the package install and what it writes are
54
+ * shut at the SDK's permission layer too. `bunx` is left to the guard hook:
55
+ * a config's type check may run through it.
56
+ */
57
+ export const INSTALL_DENY_RULES = [
58
+ ...['install', 'i', 'add', 'remove', 'update', 'pm'].flatMap((sub) => [
59
+ `Bash(bun ${sub})`,
60
+ `Bash(bun ${sub} *)`,
61
+ ]),
62
+ ...PACKAGE_MANAGERS.flatMap((name) => [`Bash(${name})`, `Bash(${name} *)`]),
63
+ 'Edit(node_modules/**)',
64
+ 'Edit(**/node_modules/**)',
65
+ ...LOCKFILES.flatMap((name) => [`Edit(${name})`, `Edit(**/${name})`]),
66
+ ]
67
+
68
+ const readOnlyRules = (): string[] => [
69
+ 'Bash(ls)',
70
+ 'Bash(ls *)',
71
+ 'Bash(cat *)',
72
+ 'Bash(head *)',
73
+ 'Bash(tail *)',
74
+ 'Bash(wc *)',
75
+ 'Bash(grep *)',
76
+ 'Bash(rg *)',
77
+ 'Bash(find *)',
78
+ 'Bash(pwd)',
79
+ 'Bash(sed -n *)',
80
+ ...GIT_READ_SUBCOMMANDS.flatMap((sub) => [
81
+ `Bash(git ${sub})`,
82
+ `Bash(git ${sub} *)`,
83
+ ]),
84
+ ]
85
+
86
+ /**
87
+ * A role's SDK permission settings. Under `dontAsk` only pre-approved calls
88
+ * run, so `allowed` lists exactly what the role may do; `disallowed` shuts
89
+ * the rest; `tools` is the only built-in tools the agent sees. The guard hook
90
+ * (`checkToolCall`) still checks every call these let through.
91
+ */
92
+ export const permissionRules = ({
93
+ role,
94
+ may_edit_tests,
95
+ config,
96
+ }: GuardArgs & {
97
+ config: EngineConfig
98
+ }): { tools: string[]; allowed: string[]; disallowed: string[] } => {
99
+ const commands = checkCommands({ role, config })
100
+ const shell = role === 'learner' ? [] : readOnlyRules()
101
+ // A bun test command takes extra args (a file, `-t`) only when it is one
102
+ // plain command; one with shell syntax runs as its exact copy.
103
+ const testRules = commands.tests.flatMap(({ run, results }) =>
104
+ results !== 'bun' || wordsOf(run) === null
105
+ ? [`Bash(${run})`]
106
+ : [`Bash(${run})`, `Bash(${run} *)`]
107
+ )
108
+ const checks = [
109
+ ...testRules,
110
+ ...commands.others.map((command) => `Bash(${command})`),
111
+ ]
112
+ const setup = config.test_setup_files.map((path) => `Edit(${path})`)
113
+ const tests = config.test_file_patterns.map((glob) => `Edit(${glob})`)
114
+ // The test helpers are the test-writer's too (#619).
115
+ const helpers = (config.test_helper_patterns ?? []).map(
116
+ (glob) => `Edit(${glob})`
117
+ )
118
+ // The feature map is the test-writer's too (#583): the file, or every
119
+ // page of a folder map (#597).
120
+ const map_path = featureMapPath({ config })
121
+ const map =
122
+ map_path === null
123
+ ? []
124
+ : isFeatureMapFolder({ path: map_path })
125
+ ? [`Edit(${map_path}/**/*.md)`]
126
+ : [`Edit(${map_path})`]
127
+ const writes: Record<
128
+ GuardRole,
129
+ { allowed: string[]; disallowed: string[] }
130
+ > = {
131
+ 'test-writer': may_edit_tests
132
+ ? {
133
+ allowed: [...tests, ...helpers, ...map, 'Bash(rm *)'],
134
+ disallowed: setup,
135
+ }
136
+ : { allowed: [], disallowed: ['Edit', 'Write'] },
137
+ implementer: {
138
+ allowed: ['Edit(**)', 'Bash(rm *)'],
139
+ disallowed: may_edit_tests ? setup : [...tests, ...setup],
140
+ },
141
+ reviewer: { allowed: [], disallowed: ['Edit', 'Write'] },
142
+ learner: { allowed: [], disallowed: ['Edit', 'Write', 'Bash'] },
143
+ }
144
+ // Every role may start read-only `searcher` helpers (#627).
145
+ return {
146
+ tools: [...TOOLS[role], HELPER_TOOL],
147
+ allowed: [
148
+ ...READ_TOOLS,
149
+ `${HELPER_TOOL}(${HELPER_TYPE})`,
150
+ ...writes[role].allowed,
151
+ ...checks,
152
+ ...shell,
153
+ 'mcp__luca',
154
+ ],
155
+ disallowed: [
156
+ ...BASE_DISALLOWED_TOOLS,
157
+ ...INSTALL_DENY_RULES,
158
+ ...writes[role].disallowed,
159
+ ],
160
+ }
161
+ }
@@ -0,0 +1,48 @@
1
+ import { checkToolCall, type GuardRole } from './role-rules'
2
+
3
+ import type { EngineConfig } from '../config/engine-config'
4
+
5
+ /** Seam 3: what the guard rule tests share: a config, a worktree, and `decide`. */
6
+
7
+ export const CONFIG: EngineConfig = {
8
+ checks: {
9
+ test: 'bun test',
10
+ types: 'bunx --bun tsc --noEmit',
11
+ lint: 'bun run lint',
12
+ },
13
+ test_file_patterns: ['src/**/*.test.ts'],
14
+ test_setup_files: ['src/test-setup.ts'],
15
+ rule_files: [],
16
+ }
17
+
18
+ /** `CONFIG` with a feature map (#583). */
19
+ export const WITH_MAP: EngineConfig = {
20
+ ...CONFIG,
21
+ feature_map: 'docs/feature-map.md',
22
+ }
23
+
24
+ export const WORKTREE = '/runs/run-1/tickets/11'
25
+
26
+ /** `may_edit_tests` as the engine sets it on a normal (not refactor) ticket. */
27
+ export const usual = (role: GuardRole): boolean => role === 'test-writer'
28
+
29
+ export const decide = ({
30
+ role,
31
+ may_edit_tests,
32
+ tool_name,
33
+ tool_input,
34
+ }: {
35
+ role: GuardRole
36
+ /** Defaults to `usual(role)`. */
37
+ may_edit_tests?: boolean
38
+ tool_name: string
39
+ tool_input: unknown
40
+ }): boolean =>
41
+ checkToolCall({
42
+ role,
43
+ may_edit_tests: may_edit_tests ?? usual(role),
44
+ tool_name,
45
+ tool_input,
46
+ worktree: WORKTREE,
47
+ config: CONFIG,
48
+ }).allow