@outerlayer/cli 0.17.0 → 0.19.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 (160) hide show
  1. package/CHANGELOG.md +72 -0
  2. package/README.md +36 -2
  3. package/dist/agent-setup-TPR4BBFV.js +27 -0
  4. package/dist/{artifact-link-cmd-2F47LT7F.js → artifact-link-cmd-W7XF5BFB.js} +4 -3
  5. package/dist/{build-BG2YENGK.js → build-TQ6X5PYN.js} +5 -4
  6. package/dist/build-info.json +1 -1
  7. package/dist/{builder-2D3WSOOY.js → builder-C4QNTYMS.js} +5 -6
  8. package/dist/{check-5XE7XMC4.js → check-UWQAL2MN.js} +56 -52
  9. package/dist/{chunk-R7PF5OOU.js → chunk-3CWUOFV6.js} +21 -14
  10. package/dist/{chunk-4NPZVMJU.js → chunk-3UZTIRLF.js} +60 -4
  11. package/dist/chunk-452TM7J4.js +502 -0
  12. package/dist/{chunk-NNRTPG7X.js → chunk-5GHGAF6P.js} +4 -4
  13. package/dist/{chunk-WVUIRTWA.js → chunk-5MYFOW5V.js} +196 -37
  14. package/dist/{chunk-HGECYFEI.js → chunk-5TN7GLYX.js} +138 -109
  15. package/dist/{chunk-VQYBXPI7.js → chunk-6D7GT22T.js} +5 -5
  16. package/dist/{chunk-G5NPRL2K.js → chunk-6U3MV7TZ.js} +687 -76
  17. package/dist/{chunk-42ZI52JW.js → chunk-6X4SQ5MY.js} +4 -3
  18. package/dist/{chunk-MAK5LYRF.js → chunk-7KSSBQTS.js} +278 -535
  19. package/dist/{chunk-NWQTTSBB.js → chunk-7SJPQWBM.js} +41 -16
  20. package/dist/{chunk-J5M7BSJQ.js → chunk-7VUK5RFS.js} +23 -4
  21. package/dist/{chunk-OZGHL5Y5.js → chunk-7YP7T6GV.js} +2 -2
  22. package/dist/{chunk-TI7B7UEO.js → chunk-AMFWYESY.js} +1 -1
  23. package/dist/{chunk-YRYMNBGQ.js → chunk-ANPNT6PM.js} +2 -1
  24. package/dist/{chunk-4KSQKQXE.js → chunk-BM63HB2M.js} +20 -16
  25. package/dist/{chunk-4Q7OMKAT.js → chunk-BQXFBI7T.js} +81 -5
  26. package/dist/{chunk-ADJRGVZC.js → chunk-BWSUPRDK.js} +1 -1
  27. package/dist/{chunk-3NSNIIBE.js → chunk-CG36IONK.js} +18 -17
  28. package/dist/chunk-CJJDJ7AT.js +56 -0
  29. package/dist/chunk-CRB3POP4.js +34 -0
  30. package/dist/{chunk-LWSJQJC6.js → chunk-DADWEUH3.js} +7 -7
  31. package/dist/{chunk-KG5ECZGJ.js → chunk-DJ3ZUSC3.js} +11 -11
  32. package/dist/{chunk-F47JBIAW.js → chunk-DSF2FWAZ.js} +2 -1
  33. package/dist/{chunk-N5FOE5PS.js → chunk-ECODQ7II.js} +4 -33
  34. package/dist/{chunk-HHJDN6AU.js → chunk-ESVW4MJY.js} +1 -1
  35. package/dist/{chunk-YO4PSLVB.js → chunk-FVMYKL6L.js} +1 -1
  36. package/dist/chunk-G2YNJIGF.js +44 -0
  37. package/dist/{chunk-WUYTWNMA.js → chunk-G3IJ3N6F.js} +5 -3
  38. package/dist/{chunk-QKFB4K7X.js → chunk-GGOMT6KF.js} +1 -1
  39. package/dist/{chunk-N6LURUEF.js → chunk-GOSFARMS.js} +1 -1
  40. package/dist/{chunk-S3NYLI2P.js → chunk-HPJNLETG.js} +93 -38
  41. package/dist/{chunk-IP7BIBXJ.js → chunk-IF2OSI5T.js} +35 -1
  42. package/dist/{chunk-C5Y5ZJJZ.js → chunk-IXYZUUYQ.js} +5 -4
  43. package/dist/chunk-JBJTC3UU.js +35 -0
  44. package/dist/chunk-KYS3TQAG.js +296 -0
  45. package/dist/chunk-LLM3I5YU.js +89 -0
  46. package/dist/{chunk-YU3VKZ5F.js → chunk-LRG3ZMZD.js} +6 -7
  47. package/dist/{chunk-V7NIVOFD.js → chunk-M2ZWWTWZ.js} +1 -1
  48. package/dist/{chunk-ERI5GBNI.js → chunk-MLAAT4S6.js} +3 -3
  49. package/dist/{chunk-O765JR4W.js → chunk-MTP2QEVO.js} +1 -1
  50. package/dist/{chunk-ZM2IMMYN.js → chunk-N2PXGQRL.js} +9 -1
  51. package/dist/{chunk-BWQ66M6N.js → chunk-NFIR3GEE.js} +98 -24
  52. package/dist/{chunk-POJMEEJJ.js → chunk-NNLA7LLQ.js} +1 -1
  53. package/dist/{chunk-PLMTLGYH.js → chunk-NTRCK4NH.js} +5 -4
  54. package/dist/{chunk-22RMZBGW.js → chunk-P53VEAOF.js} +1 -1
  55. package/dist/{chunk-RLYZLUQ7.js → chunk-QEFKA2QD.js} +2 -2
  56. package/dist/{chunk-JXYVA45C.js → chunk-QNLX477Q.js} +1200 -322
  57. package/dist/{chunk-UGP76IZ5.js → chunk-S5PCA3SN.js} +1 -1
  58. package/dist/{chunk-RMWLBUCJ.js → chunk-SADUQSO4.js} +1 -1
  59. package/dist/{chunk-GQD22KKN.js → chunk-T2Q7ISPE.js} +2 -1
  60. package/dist/chunk-TUSYOL2O.js +113 -0
  61. package/dist/{chunk-NBGDBTO2.js → chunk-TWF3RICI.js} +9 -8
  62. package/dist/{chunk-ONKJUM6R.js → chunk-UVG35MWF.js} +1 -1
  63. package/dist/{chunk-KMDLFI4P.js → chunk-VMJPFQNE.js} +1 -1
  64. package/dist/chunk-VRRGENS4.js +144 -0
  65. package/dist/{chunk-PTW6W5NR.js → chunk-VYAL7PUE.js} +1 -1
  66. package/dist/{chunk-OHVJA253.js → chunk-WFSUUUAN.js} +47 -30
  67. package/dist/{chunk-RSNSZ2LJ.js → chunk-X5H2WYMB.js} +1 -1
  68. package/dist/{chunk-HYZRY4KZ.js → chunk-XNGMQ27R.js} +16 -6
  69. package/dist/chunk-XSZD4AZ6.js +490 -0
  70. package/dist/chunk-YQR6E6OV.js +109 -0
  71. package/dist/{chunk-XCK2YAY5.js → chunk-YVZJ6GKF.js} +3 -2
  72. package/dist/{chunk-2MNPHL23.js → chunk-ZHAUSEEN.js} +38 -15
  73. package/dist/claude-launcher-FPPHZPMR.js +115 -0
  74. package/dist/{cli-A76AXELQ.js → cli-HROW5T7X.js} +567 -285
  75. package/dist/{command-R2QCVX55.js → command-OEAV7BVX.js} +42 -40
  76. package/dist/{config-N3C6DFQA.js → config-CLSGABD5.js} +2 -3
  77. package/dist/{connect-cmd-JBKAQNSF.js → connect-cmd-UNYKGDAL.js} +10 -9
  78. package/dist/context-cleanup-HSVYDGMI.js +9 -0
  79. package/dist/control-plane-launch-NYVDJESK.js +216 -0
  80. package/dist/{dist-LKDWQWZ7.js → dist-WO7NOJNG.js} +2 -2
  81. package/dist/{docker-4J77Q2BF.js → docker-BFU3AQGY.js} +7 -8
  82. package/dist/{doctor-4TAIARIG.js → doctor-23BBJWBX.js} +48 -42
  83. package/dist/{emit-artifact-cmd-MPZVR56N.js → emit-artifact-cmd-O6SCM7TV.js} +27 -24
  84. package/dist/emit-cmd-7SP3I6UZ.js +14 -0
  85. package/dist/{emit-criteria-cmd-EFGYI23V.js → emit-criteria-cmd-LQNERZRV.js} +17 -16
  86. package/dist/{emit-finding-cmd-5GF3V4LR.js → emit-finding-cmd-AJPKHIOV.js} +18 -17
  87. package/dist/{emit-result-cmd-I4OADKMD.js → emit-result-cmd-RUICOKWQ.js} +22 -19
  88. package/dist/emit-step-cmd-347XNGPQ.js +26 -0
  89. package/dist/{exec-client-CQV746UF.js → exec-client-7UMESIE2.js} +3 -4
  90. package/dist/governing-source-V5EEGPTA.js +34 -0
  91. package/dist/guest-init-KPBQOFE5.js +9 -0
  92. package/dist/{hook-fast-R4FVSZ2H.js → hook-fast-YAZG5MDX.js} +5 -2
  93. package/dist/host-key-U4B5QETR.js +8 -0
  94. package/dist/{import-capture-cmd-EQ35EARA.js → import-capture-cmd-KKFFOGWP.js} +7 -5
  95. package/dist/{import-ruler-cmd-BGAZXCQM.js → import-ruler-cmd-5P6JOXQN.js} +5 -3
  96. package/dist/index.js +8 -4
  97. package/dist/{init-PWU5YVL7.js → init-LWHDMZQG.js} +40 -38
  98. package/dist/init-MOINY2DD.js +53 -0
  99. package/dist/{init-cmd-XAVO7HYZ.js → init-cmd-HSMRID6A.js} +75 -30
  100. package/dist/{init-update-CVCWD6VS.js → init-update-LWX5MPPR.js} +29 -29
  101. package/dist/{install-HDBIPHTX.js → install-4SEFIIW2.js} +4 -5
  102. package/dist/{install-cmd-5HYMXLEW.js → install-cmd-QDOTR4OM.js} +5 -4
  103. package/dist/{login-browser-QZCS3WW6.js → login-browser-DX35MARZ.js} +5 -4
  104. package/dist/{logout-cmd-QAUFDJ6J.js → logout-cmd-5V5MMBSY.js} +3 -2
  105. package/dist/{logs-H55NWW4H.js → logs-AMLKC3VP.js} +3 -1
  106. package/dist/loop-SYYPVFYW.js +77 -0
  107. package/dist/machine-6X63G2KH.js +48 -0
  108. package/dist/{mcp-install-cmd-FDTPQQS6.js → mcp-install-cmd-GGWB3JJ2.js} +3 -2
  109. package/dist/{mcp-serve-cmd-3KVYEUXG.js → mcp-serve-cmd-N3UTY6EE.js} +11 -9
  110. package/dist/paths-7XP63OAX.js +7 -0
  111. package/dist/pending-532ABZYC.js +8 -0
  112. package/dist/{pidfile-UZRH774M.js → pidfile-3DM4OBKO.js} +4 -2
  113. package/dist/{record-C43U4PKS.js → record-3WP6V2K5.js} +3 -2
  114. package/dist/{runner-env-EN7YE7QX.js → runner-env-FRZFFHDF.js} +5 -6
  115. package/dist/{runner-key-6SLBI7PP.js → runner-key-276SJU5R.js} +40 -38
  116. package/dist/session-sender-GGIY5OAX.js +36 -0
  117. package/dist/{pending-MXLSRYMO.js → shell-function-NRD4KVUK.js} +1 -2
  118. package/dist/spec-cmd-SIZBRN7D.js +115 -0
  119. package/dist/{starter-pack-KF66HGZR.js → starter-pack-6LQVLBIA.js} +7 -5
  120. package/dist/{status-7VNQN5PW.js → status-NAK6DYSF.js} +46 -42
  121. package/dist/{statusline-fast-SVTMBMD7.js → statusline-fast-KN3BX67H.js} +2 -1
  122. package/dist/sync-cmd-G7GF4UTW.js +35 -0
  123. package/dist/{watch-23HSAEI6.js → watch-EVM5GSMP.js} +22 -15
  124. package/dist/{work-claim-cmd-LKL3GHFQ.js → work-claim-cmd-3J3QW56U.js} +26 -29
  125. package/dist/work-cmd-5EPPD33Y.js +24 -0
  126. package/dist/{work-comment-cmd-AWHP56M2.js → work-comment-cmd-2ACOZQAE.js} +16 -15
  127. package/dist/{work-launch-R5HTAP6X.js → work-launch-LEQ2MPM3.js} +3 -1
  128. package/dist/work-logs-cmd-QDD2NMJL.js +60 -0
  129. package/dist/{work-open-pr-cmd-IOM4AVQQ.js → work-open-pr-cmd-XNCEI4ZM.js} +18 -17
  130. package/dist/work-pr-cmd-H4OQR26N.js +25 -0
  131. package/package.json +3 -2
  132. package/skill-pack/maintained/outerlayer-amend/SKILL.md +4 -2
  133. package/skill-pack/maintained/outerlayer-cli/SKILL.md +9 -2
  134. package/skill-pack/maintained/outerlayer-cli/references/find-out-why-a-build-ended.md +76 -0
  135. package/skill-pack/shipped.json +18 -2
  136. package/skill-pack/template/build/SKILL.md +23 -10
  137. package/skill-pack/template/build/references/agent-briefs.md +3 -1
  138. package/skill-pack/template/build/references/criteria-judge.md +27 -0
  139. package/skill-pack/template/build/references/release.md +22 -29
  140. package/skill-pack/template/spec/SKILL.md +35 -10
  141. package/skill-pack/validators/acceptance-criteria.yaml +25 -26
  142. package/dist/agent-setup-T624CWPD.js +0 -27
  143. package/dist/chunk-6Y5RUTHO.js +0 -39
  144. package/dist/chunk-BYT5O5RV.js +0 -61
  145. package/dist/chunk-WTE3MQML.js +0 -133
  146. package/dist/chunk-XCCLFVXM.js +0 -401
  147. package/dist/chunk-YZ5F4PIS.js +0 -797
  148. package/dist/context-adopt-NFHCLVUB.js +0 -108
  149. package/dist/context-materialize-IV2TUUFO.js +0 -22
  150. package/dist/emit-cmd-IJ3GVRFD.js +0 -12
  151. package/dist/guest-init-LJP5GPZF.js +0 -9
  152. package/dist/host-key-YMLXY6QB.js +0 -7
  153. package/dist/init-6XPDRDTC.js +0 -51
  154. package/dist/loop-FK3LPMMZ.js +0 -73
  155. package/dist/machine-SQXEBAM2.js +0 -46
  156. package/dist/paths-OYKMVYJP.js +0 -6
  157. package/dist/session-sender-FO3LCREV.js +0 -29
  158. package/dist/sync-cmd-GVPPFZ3G.js +0 -28
  159. package/dist/work-cmd-Z2WXLZPH.js +0 -23
  160. package/dist/work-pr-cmd-HNEUIEN7.js +0 -24
package/CHANGELOG.md CHANGED
@@ -1,5 +1,77 @@
1
1
  # @outerlayer/cli
2
2
 
3
+ ## 0.19.0
4
+
5
+ ### Minor Changes
6
+
7
+ - 5153995: Add `outerlayer emit step <name>`. An agent reports that it started a step, such as "Review round 2", and the work item's Activity tab shows it.
8
+
9
+ The name is 1 to 80 characters on one line. If the gateway cannot be reached, the command queues the step with the time you ran it and exits 0, and `outerlayer sync` sends it later. If the gateway refuses the step, the command prints the reason and exits 1.
10
+
11
+ - 3d15dba: On a laptop, `outerlayer claude` starts Claude Code with your control plane attached, without writing its files into your repository. It keeps a copy of the control plane under `~/.outerlayer/context/` and starts Claude Code with that copy added.
12
+
13
+ A governed repository can now commit its own `CLAUDE.md`, `AGENTS.md` and skills. `outerlayer init` can add a `claude` function to your zsh or bash startup file, so plain `claude` starts the same way. A session that did not start through `outerlayer claude` says the shared instructions are missing.
14
+
15
+ `outerlayer context materialize` and the pre-commit guard are removed. A session start removes the files, the exclude block and the guard that an earlier CLI added to a checkout.
16
+
17
+ - 3210eaa: `outerlayer work logs` prints the runner's log for a host attempt. A runner now sends that log with every release, and gives a reason when a build command dies of a signal it did not send.
18
+
19
+ ### Patch Changes
20
+
21
+ - ff302c8: The build skill says a pull request closes a GitHub issue, and that a person completes a Linear or Jira issue in its tracker after merge.
22
+ - de29d22: `outerlayer context emit` deletes a generated file whose source is gone, so every orphan `--check` reports is one a plain emit clears. Before, `--check` reported these files and emit left them, so the check kept failing.
23
+
24
+ Neither form reads inside a nested git repository, a submodule, or a governed clone any more. Generated files there belong to that tree, not to the repository being emitted.
25
+
26
+ - 13ee333: Update the bundled `@noble/hashes` library from 1.8 to 2.4. The CLI's pricing code depends on it. Token costs the CLI reports are unchanged.
27
+
28
+ ## 0.18.0
29
+
30
+ ### Minor Changes
31
+
32
+ - 2ef9133: Add `outerlayer claude`, which starts Claude Code with the repository's control plane attached. A governed build reads its control plane from a directory outside the checkout, so nothing from it is written into the checkout.
33
+ - e92982d: A session now takes its control plane from the one the factory stores, chosen in the factory's Settings with Use as control plane in the Repositories list. Nothing reads a `governs` list in a control plane's config any more, and `doctor` reports a list left there as unused. A gateway older than 0.17.0 does not store the answer, and the CLI says so instead of guessing.
34
+
35
+ A validator or policy `when` can name `repositories`, so a check applies only to pull requests in those repositories.
36
+
37
+ A session that could not reach the gateway, or reached a gateway too old to answer, no longer reads the retry guess it wrote as a confirmed answer on the next start. A build that cannot write the staged control-plane files now says so, and no longer reports that a credential cannot read the control plane.
38
+
39
+ - 2cb58e8: A container or microVM build now runs on the devcontainer of the repository that governs it. For a governed repository that is the control plane's `.devcontainer/devcontainer.json` (or `.devcontainer.json`), read at the commit the build's instructions come from. A governed repository's own devcontainer is not read, and the build log says so. A repository that governs itself builds from its own devcontainer, as before. `outerlayer runner image --repository <name>` builds the same image a build of that repository uses.
40
+
41
+ Before upgrading a runner host, copy the devcontainer your builds rely on into the control plane. A control plane with no devcontainer gives its builds the runner's default image. A factory whose only repository is its own control plane needs no move.
42
+
43
+ In a build with more than one repository, every repository is at `/build/work/repos/<owner>/<repo>`, the primary included. The primary is also at `/build/work/checkout`. The control plane devcontainer's setup commands run once, in the control plane's checkout, so they can name each repository by its fixed path. Host checks check out every repository at the same paths, each at the branch the build pushed there or else at its default branch.
44
+
45
+ - 07bb510: A `where: host` check can declare `run.judge.brief` instead of `run.command`. After a build ends well, the runner stops everything the build left running, checks that the image's Claude Code is unchanged, and runs a read-only Claude Code session with that brief. It records the verdict under the check's name with the host's own key and links the judge's report.
46
+
47
+ The starter `acceptance-criteria` check now uses this form, and the starter `/build` skill no longer records its own `acceptance-criteria` pass. Run `outerlayer doctor` to see an unedited copy of the old file as outdated, and `outerlayer init --update` to replace it.
48
+
49
+ - d776d34: `outerlayer spec` files and edits specs through OuterLayer, with no tracker tool of your own. `spec target` says where a spec would be filed. `spec search <query>` finds open and closed issues that match. `spec file --title <text> --body-file <path>` creates the issue and prints its key, URL and the `work build` command for it. `spec edit <key> --body-file <path>` replaces an issue's body, and `--updated-at` refuses the edit if the issue changed since you read it.
50
+
51
+ Filing works for GitHub today. An API key needs the new `spec.insert` permission to file and `spec.update` to edit, so re-mint keys that should do either. A GitHub organization admin must also approve the OuterLayer App's Issues write permission once.
52
+
53
+ ### Patch Changes
54
+
55
+ - 8f46b70: The starter `/build` skill reads the issue from `outerlayer work status --json`, under the new `issue` field, instead of from a tracker tool. A build on a host holds no Linear or Jira credential, so it could not read those issues before. The build saves the issue to `build-reports/issue.md` and gives every agent that file. `/outerlayer-amend` reads the issue the same way.
56
+
57
+ To pick up the new `/build`, run `outerlayer init --update`. A copy you edited is left as it is.
58
+
59
+ - fe8bb20: Printed command hints now name the command the way you ran the CLI. Run it through `npx @outerlayer/cli` (or `pnpm dlx`, `yarn dlx` or `bunx`) and a hint says `npx @outerlayer/cli login`, not an `outerlayer login` that is not on your PATH. `--help` usage lines follow the same rule.
60
+
61
+ `work build --local` no longer prints a branch name. The session it hands off to works on a branch of its own, so the printed name was never used. `--json` still carries the `branch` field.
62
+
63
+ `doctor` no longer warns when Claude Code is newer than the newest version this release was tested with. It passes, and names that tested version. A Claude Code older than the tested range still warns.
64
+
65
+ - 9b898aa: Session cost estimates price Claude Haiku 5.5 requests over 100,000 input tokens at Haiku 5.5's long-context rates. Token buckets now start a new input band at 100,000 tokens.
66
+ - 5cc77dc: Builds no longer print a Node warning on the stderr of every Node process. The runner sets `NODE_USE_ENV_PROXY` so Node uses the build's proxy, and Node 22 then warns `[UNDICI-EHPA] EnvHttpProxyAgent is experimental`. A test that expects a child process's stderr to be empty failed in a build and passed on a laptop.
67
+
68
+ The runner now adds `--disable-warning=UNDICI-EHPA` to `NODE_OPTIONS` in container, microVM and process builds. It keeps the options the image, the recipe's `containerEnv` or a named host variable already set. A build run through a provision hook's `exec` is left as it was. Node before 20.11 does not know the flag and refuses to start with it.
69
+
70
+ - daac363: Uploaded sessions record the input band boundaries their token buckets were grouped by. The server uses them to tell when a bucket cannot be priced at a single rate, and prices that model from its individual requests instead.
71
+ - 7a16e3f: `outerlayer work status`, `work remove`, `work claim`, `work renew` and `work release` take a Linear or Jira key in `--issue`, such as `--issue ENG-142`. Pass `--tracker linear` or `--tracker jira` when both trackers hold the key. Before, `--issue` took only a GitHub issue number, so a Linear or Jira item could be named only with `--item`.
72
+
73
+ A runner's `repos.include` and `repos.exclude` now match a Linear or Jira item by the repository it was added with (`work build --repo`), instead of skipping it. A runner using `builtin:process`, or its own provision hook, now clones the repository the claim chose, so it can build an item that was added without a repository.
74
+
3
75
  ## 0.17.0
4
76
 
5
77
  ### Minor Changes
package/README.md CHANGED
@@ -90,14 +90,25 @@ content:
90
90
  `release` marks it done. `outerlayer work comment` posts one comment on a
91
91
  work item's general thread or a criterion thread — the detected session id
92
92
  rides along the same way it does for `emit`, only ever narrowing what the
93
- comment may do; `outerlayer work threads` reads them back. A runner key
93
+ comment may do; `outerlayer work threads` reads them back. `outerlayer work logs`
94
+ reads the runner's log for a host attempt. A runner key
94
95
  needs `git.read` and `work.read` to
95
96
  list and read items, and `work.claim` to claim, renew or release — `work.claim`
96
97
  is not granted to a dashboard role by default, since a claim is a host's
97
98
  lease, not a person's.
99
+ - **`outerlayer spec target`, `search`, `file` and `edit`** each make one
100
+ request to your gateway (`GET /v1/specs/target`, `GET /v1/specs/search`,
101
+ `POST /v1/specs`, `PATCH /v1/specs/{key}`). `search` sends your query, and
102
+ `file` and `edit` send the title and the Markdown body you give them.
103
+ Your gateway files the issue in your tracker; the CLI never holds a tracker
104
+ credential.
98
105
  - **`outerlayer runner start`** lists, claims, renews and releases work
99
106
  items the same way `work claim`/`renew`/`release` do, on the schedule its
100
- config sets — it sends nothing about the job it runs beyond that; the
107
+ config sets. Each release also carries the runner's own log for the attempt:
108
+ the lines the runner logged for that item, and the tail of the stage log when
109
+ the attempt ended in provision, sync, cleanup or report. It is scrubbed with
110
+ the same patterns as the build's session, capped at 64 KiB and always sent.
111
+ The runner sends nothing else about the job it runs; the
101
112
  agent it starts is a separate process with its own credentials, from the
102
113
  `runner` block, never the copy-out daemon's. It also sends a heartbeat at
103
114
  the end of every poll: the host, the CLI version, the poll interval, the
@@ -116,6 +127,15 @@ content:
116
127
  command in a new container, and sends one `POST /v1/emitted-results` for
117
128
  each: the check's name, pass or fail, and for a fail the exit status and the
118
129
  last line of its output, scrubbed.
130
+ A `where: host` check that declares a `judge` runs after a successful
131
+ build instead of a command. The runner reads the item's recorded criteria
132
+ with `GET /v1/work-items/{id}`, fetches the build's commits and the brief
133
+ with the host's own git credentials, and copies out the tests that cite the
134
+ criteria. It starts a Claude Code agent in the build's image with the host's
135
+ Claude credential. The judge sees only a read-only folder of the criteria,
136
+ citations and diff, and it has no shell. The runner uploads the judge's
137
+ `report.html` as an artifact with its own key, and sends one
138
+ `POST /v1/emitted-results` with pass or fail and a one-line reason.
119
139
  Before each check's command runs, the runner writes a read-only folder for it
120
140
  to read: the work item, from `GET /v1/work-items/{id}` with the runner's own
121
141
  key; the branch's diff, from `git fetch` of the build's commits with the
@@ -225,6 +245,11 @@ content:
225
245
  credited the member who asked for the build. The runner sets that author in
226
246
  the build's environment and adds the `Co-authored-by` line with git hooks it
227
247
  writes into the build's own directory.
248
+ - **`outerlayer claude`** asks your gateway which repository governs the
249
+ current one (`GET /v1/context/source`, with your API key). When one does, it
250
+ runs `git fetch` of that control plane into a cache under
251
+ `~/.outerlayer/context` and starts Claude Code with the files attached.
252
+ Nothing is written into your working tree.
228
253
  - **`outerlayer runner start` with `builtin:container` hooks** also asks your
229
254
  gateway which repository governs the item's repository (`GET
230
255
  /v1/context/source`, signed with the host key), after the recipe's
@@ -312,6 +337,13 @@ content:
312
337
  declared proof kind, from the JSON file you name. The anchoring is the same
313
338
  as `emit finding`. Nothing but the file's content and the item number is
314
339
  sent.
340
+ - **`outerlayer emit step <name>`** sends the step's name, the time you ran the
341
+ command, a random id for the step and the item number
342
+ (`POST /v1/work-items/steps`). The item is `--item`, or the item the
343
+ recorded session was launched for. When the gateway cannot be reached, the
344
+ same four values, the factory id and the directory are kept in
345
+ `~/.outerlayer/spool/steps.jsonl`, and `outerlayer sync` sends them later.
346
+ Nothing from the session itself is sent.
315
347
  - **`outerlayer mcp serve`** is the stdio MCP server your editor spawns. It
316
348
  forwards every JSON-RPC message the editor sends to the gateway and returns
317
349
  the reply.
@@ -376,9 +408,11 @@ before it gives up.
376
408
  | `outerlayer emit artifact <file> --caption <text> [--for <criterion-id>] [--pr <n>]` | Upload a proof artifact — screenshot, recording, report, or log — with its caption. Inside a recorded session it spools locally and ships on the next `sync`; otherwise it uploads immediately, anchored to a pull request or the current checkout. `--replaces` retires artifacts an earlier run uploaded. A JUnit XML file bound with `--for` proves a criterion that declares `test` proof; `--test <name[=path:line]>` binds only the named tests. |
377
409
  | `outerlayer emit <name> --result <pass\|fail> [--link <url>] [--body <text>\|--body-file <path>] --item <number>` | Record one named check's outcome on a work item. A check that ran carries the run URL as its proof (`--link`); a judgment you are making yourself carries one sentence (`--body`, or `--body-file` to read it from a file — `-` reads standard input). A `fail` needs at least one of the two; a `pass` needs neither. `--item` names the work item by the number printed when the item was created — always required, in or out of a recorded session; a session records checks on its own item like any other, stored as the session's, but cannot clear a person's fail. Prints the recorded check's id. Who recorded it comes from your API key, never from what you send. |
378
410
  | `outerlayer work comment --item <n> [--criterion <id>] --body <text> [--artifact <id>] [--pass\|--fail\|--attach\|--ready]` | Post one comment on a work item's general thread, or on one acceptance criterion's own thread with `--criterion`. `--pass`/`--fail` need a person's own credential and apply to the whole item, so they work on the general thread only and the gateway refuses them with `--criterion`; `--attach` makes the named artifact that criterion's current proof; `--ready` hands a thread back to a person. The detected session id rides along automatically, only ever narrowing what the comment may do. |
411
+ | `outerlayer work logs --item <n> [--attempt <claim id>]` | Print the runner's log for a host attempt on a work item: the newest attempt a runner made, or the one `--attempt` names (an `id` from `claims` in `work status --json`). The log is what the runner did around the agent, with secrets removed on the host. Exits 1 for an attempt that is still running or that an older runner released without a log. |
379
412
  | `outerlayer work threads --item <n>` | Read a work item's threads back — the general thread first, then one per criterion with proof or a comment, each with its `waitingOn`. |
380
413
  | `outerlayer emit finding --id <id> --category <context\|flaky-test\|tooling\|environment\|defect\|other> --title <text> --file <path> --where <label> [--line <n>] [--item <number>] [--cause <text>] [--detail <text>] [--suggestion <text>] [--command <text>] [--error <text>] [--test <name>] [--symbol <name>] [--started-at <event> --ended-at <event>] [--problem <id> … --link-reason <text> \| --new] …` | Record one problem you hit in the factory. `--category` is one of `context` (an instruction that is wrong, broken or missing), `flaky-test`, `tooling`, `environment`, `defect` (a bug outside your own change) or `other`. A `context` finding also needs `--rule-path` and `--rule-relation` (`wrong`, `broken` or `missing`), and `--rule-quote` unless the relation is `missing`. `--cause`, `--detail`, `--suggestion`, `--command`, `--error` (the first error line), `--test` and `--symbol` carry what a fix needs. Every finding joins a factory-level problem: when a key of the finding matches a known problem it joins that problem; otherwise nothing is stored, the closest problems are printed, and the command exits with code 3. Run it again with `--problem <id> --link-reason <text>` (repeat `--problem` for each problem the finding reports) or with `--new`. Without `--started-at`, it prints the session's recent events with ids and times; name the event where you hit the problem and the one where you got past it with `--started-at` and `--ended-at`. Findings are informational; nothing reads one to pass or fail the work. `--item` names the work item; without it, inside a recorded session, the item that session was launched for is used automatically. Re-emitting the same `--id` on the item replaces that finding and keeps its problems. |
381
414
  | `outerlayer emit findings <file> [--item <number>]` | Record a whole batch at once, read from a JSON file (`-` for standard input) with `"schemaVersion": 3` — the same shape and validation as `emit finding`, one record per id. A finding's `startedAt` and `endedAt` may be event ids. The findings that match a key are stored; those that need a choice are listed with their candidates and not stored, and the exit code is 3. Anchored the same way: `--item`, else a recorded session's own item. |
415
+ | `outerlayer emit step <name> [--item <number>]` | Report that you started a step. The name is free text, 1 to 80 characters on one line. The work item's Activity tab shows the step with the time you ran the command. A step has a start and nothing else, so run it again at the start of the next step. `--item` names the work item; without it, inside a recorded session, the item that session was launched for is used. If the gateway cannot be reached, or answers with a 5xx, the step is queued in `~/.outerlayer/spool/steps.jsonl`, the command exits 0, and `outerlayer sync` sends it later with its original time. If the gateway refuses the step, the command prints why and exits 1. |
382
416
  | `outerlayer emit criteria <file> [--item <number>]` | Record a work item's acceptance criteria as one list, read from a JSON file (`-` for standard input): `{"criteria": [{"id": "AC-1", "text": "Given …", "proof": null}]}`. An `id` is 1 to 64 characters of `A-Z a-z 0-9 . _ : -`, the same id `emit artifact --for` binds evidence to. `proof` is `video`, `screenshot`, `report`, `log`, `test`, `file` or `null` (any bound artifact proves it). The list holds 1 to 200 criteria, each `text` at most 2,000 characters, with no id twice. It is validated before anything is sent. This list is the only source for the item's Criteria tab. Once an item has a list, only a person can replace it: run the command outside an agent session, with your own login. Anchored the same way as `emit findings`: `--item`, else a recorded session's own item. |
383
417
  | `outerlayer mcp install [--transport stdio\|http] [--url] [--name] [--command]` | Write (or update) an `mcpServers` entry in `.mcp.json` for the OuterLayer gateway. Default `stdio`: the client spawns `outerlayer mcp serve`, which reads the API key from `~/.outerlayer/config.json` (or `OUTERLAYER_API_KEY`) each time it connects, so a reconnect picks up a newly saved or rotated key. `--transport http` writes a direct `POST /v1/mcp` entry referencing `${OUTERLAYER_API_KEY}`, resolved by the client from the environment it was launched with. Never writes an API key. Pass `--url`/`--app-id` for self-host. |
384
418
  | `outerlayer mcp serve [--url] [--app-id]` | Stdio MCP server bridging stdin/stdout JSON-RPC to the gateway's `POST /v1/mcp`. What the stdio `.mcp.json` entry runs; exits 1 with a clear message when no API key is configured. |
@@ -0,0 +1,27 @@
1
+ #!/usr/bin/env node
2
+ import { createRequire } from 'node:module';
3
+ export { keepHandWritten, runAgentSetup } from './chunk-DJ3ZUSC3.js';
4
+ import './chunk-ZHAUSEEN.js';
5
+ import './chunk-YVZJ6GKF.js';
6
+ import './chunk-6X4SQ5MY.js';
7
+ import './chunk-ANPNT6PM.js';
8
+ import './chunk-452TM7J4.js';
9
+ import './chunk-IXYZUUYQ.js';
10
+ import './chunk-FVMYKL6L.js';
11
+ import './chunk-NTRCK4NH.js';
12
+ import './chunk-ZJSNLVCE.js';
13
+ import './chunk-BJ3KTMDH.js';
14
+ import './chunk-NNLA7LLQ.js';
15
+ import './chunk-WFSUUUAN.js';
16
+ import './chunk-7VUK5RFS.js';
17
+ import './chunk-EABW6AJQ.js';
18
+ import './chunk-CRB3POP4.js';
19
+ import './chunk-YQR6E6OV.js';
20
+ import './chunk-VNVZDWO3.js';
21
+ import './chunk-RA3O54FC.js';
22
+ import './chunk-M2ZWWTWZ.js';
23
+ import './chunk-N2PXGQRL.js';
24
+ import './chunk-BQXFBI7T.js';
25
+ import './chunk-Z5GEFMRW.js';
26
+
27
+ createRequire(import.meta.url);
@@ -1,7 +1,8 @@
1
1
  #!/usr/bin/env node
2
2
  import { createRequire } from 'node:module';
3
- import { sanitizeTerminal, readEffectiveArtifactSpoolState } from './chunk-WUYTWNMA.js';
4
- import './chunk-J5M7BSJQ.js';
3
+ import { sanitizeTerminal, readEffectiveArtifactSpoolState } from './chunk-G3IJ3N6F.js';
4
+ import './chunk-7VUK5RFS.js';
5
+ import { cliInvocation } from './chunk-CRB3POP4.js';
5
6
  import { homeFromEnv } from './chunk-VNVZDWO3.js';
6
7
  import './chunk-Z5GEFMRW.js';
7
8
 
@@ -13,7 +14,7 @@ function lookupArtifactLink(artifactId, home = homeFromEnv()) {
13
14
  const entry = readEffectiveArtifactSpoolState(home).state.get(artifactId);
14
15
  if (entry === void 0) return { ok: false, reason: `no artifact ${shown} is queued or uploaded on this machine` };
15
16
  if (entry.state !== "uploaded") {
16
- return { ok: false, reason: `artifact ${shown} is ${entry.state}, not uploaded yet \u2014 run \`outerlayer sync\` first` };
17
+ return { ok: false, reason: `artifact ${shown} is ${entry.state}, not uploaded yet \u2014 run \`${cliInvocation()} sync\` first` };
17
18
  }
18
19
  if (entry.url === void 0) {
19
20
  return { ok: false, reason: `artifact ${shown} uploaded, but the gateway returned no link for it` };
@@ -1,11 +1,12 @@
1
1
  #!/usr/bin/env node
2
2
  import { createRequire } from 'node:module';
3
- export { buildRepositoryImage } from './chunk-R7PF5OOU.js';
3
+ export { buildRepositoryImage } from './chunk-3CWUOFV6.js';
4
4
  export { RecipeError } from './chunk-QOZKPLVJ.js';
5
- import './chunk-N5FOE5PS.js';
6
5
  import './chunk-CX22S5BA.js';
7
- import './chunk-BYT5O5RV.js';
8
- import './chunk-ZM2IMMYN.js';
6
+ import './chunk-ECODQ7II.js';
7
+ import './chunk-JBJTC3UU.js';
8
+ import './chunk-N2PXGQRL.js';
9
+ import './chunk-BQXFBI7T.js';
9
10
  import './chunk-Z5GEFMRW.js';
10
11
 
11
12
  createRequire(import.meta.url);
@@ -1 +1 @@
1
- {"id":"2026-10-09T21:24:47.045Z+8773e8e","root":"/home/runner/_work/outerlayer/outerlayer","pkg":"outerlayer"}
1
+ {"id":"2026-10-13T19:11:37.037Z+109c2bd","root":"/home/runner/_work/outerlayer/outerlayer","pkg":"outerlayer"}
@@ -1,13 +1,12 @@
1
1
  #!/usr/bin/env node
2
2
  import { createRequire } from 'node:module';
3
- export { MIN_BUILDX_VERSION, builderName, builderSpec, buildxState, ensureBuilder, ensureFailureCount, isOutOfMemory, proxyEnvironment, trackImageBuild } from './chunk-4NPZVMJU.js';
3
+ export { MIN_BUILDX_VERSION, builderName, builderSpec, buildxState, ensureBuilder, ensureFailureCount, isOutOfMemory, proxyEnvironment, trackImageBuild } from './chunk-3UZTIRLF.js';
4
4
  import './chunk-7RQ73DLO.js';
5
- import './chunk-4Q7OMKAT.js';
6
5
  import './chunk-CX22S5BA.js';
7
- import './chunk-JXYVA45C.js';
8
- import './chunk-BYT5O5RV.js';
9
- import './chunk-J5M7BSJQ.js';
10
- import './chunk-ZM2IMMYN.js';
6
+ import './chunk-QNLX477Q.js';
7
+ import './chunk-7VUK5RFS.js';
8
+ import './chunk-N2PXGQRL.js';
9
+ import './chunk-BQXFBI7T.js';
11
10
  import './chunk-Z5GEFMRW.js';
12
11
 
13
12
  createRequire(import.meta.url);
@@ -1,73 +1,77 @@
1
1
  #!/usr/bin/env node
2
2
  import { createRequire } from 'node:module';
3
- import { probeCredentials } from './chunk-WVUIRTWA.js';
4
- import './chunk-G5NPRL2K.js';
5
- import './chunk-RMWLBUCJ.js';
6
- import './chunk-O765JR4W.js';
7
- import './chunk-UGP76IZ5.js';
3
+ import { probeCredentials } from './chunk-5MYFOW5V.js';
4
+ import './chunk-6U3MV7TZ.js';
5
+ import './chunk-MTP2QEVO.js';
6
+ import './chunk-S5PCA3SN.js';
8
7
  import './chunk-3JVYMD4G.js';
9
8
  import './chunk-YETTWTAY.js';
9
+ import './chunk-SADUQSO4.js';
10
10
  import './chunk-I2UOAV7X.js';
11
- import './chunk-NNRTPG7X.js';
12
- import './chunk-HYZRY4KZ.js';
13
- import './chunk-ADJRGVZC.js';
14
- import './chunk-NWQTTSBB.js';
15
- import './chunk-HHJDN6AU.js';
16
- import { placeholderQueues, placeholderProblem, permissionModeProblems, permissionModeProblem, needsClaudeCredential, claudeCredentialCheck, claimPermissionProblem, formerSkillQueues, formerSkillProblem } from './chunk-LWSJQJC6.js';
11
+ import './chunk-BWSUPRDK.js';
12
+ import './chunk-5GHGAF6P.js';
13
+ import './chunk-XNGMQ27R.js';
14
+ import './chunk-7SJPQWBM.js';
15
+ import './chunk-KYS3TQAG.js';
16
+ import './chunk-ESVW4MJY.js';
17
+ import { placeholderQueues, placeholderProblem, permissionModeProblems, permissionModeProblem, needsClaudeCredential, claudeCredentialCheck, claimPermissionProblem, formerSkillQueues, formerSkillProblem } from './chunk-DADWEUH3.js';
17
18
  import './chunk-YYYRBYWG.js';
18
- import './chunk-TI7B7UEO.js';
19
+ import './chunk-AMFWYESY.js';
19
20
  import './chunk-TBT347UY.js';
20
- import './chunk-VQYBXPI7.js';
21
- import './chunk-ONKJUM6R.js';
22
- import './chunk-MAK5LYRF.js';
23
- import './chunk-KMDLFI4P.js';
24
- import './chunk-QKFB4K7X.js';
25
- import './chunk-RSNSZ2LJ.js';
26
- import './chunk-OZGHL5Y5.js';
27
- import './chunk-RLYZLUQ7.js';
21
+ import './chunk-6D7GT22T.js';
22
+ import './chunk-7YP7T6GV.js';
23
+ import './chunk-UVG35MWF.js';
24
+ import './chunk-7KSSBQTS.js';
25
+ import './chunk-OGFGZQA3.js';
26
+ import './chunk-VMJPFQNE.js';
27
+ import './chunk-3UZTIRLF.js';
28
+ import './chunk-GGOMT6KF.js';
29
+ import './chunk-X5H2WYMB.js';
30
+ import './chunk-XSZD4AZ6.js';
31
+ import './chunk-QEFKA2QD.js';
28
32
  import { parseRunnerEnv } from './chunk-6MZSX4M7.js';
29
- import './chunk-S3NYLI2P.js';
30
- import './chunk-6Y5RUTHO.js';
31
- import './chunk-IP7BIBXJ.js';
32
- import { loadRunnerConfig, RunnerConfigError, checkHooks } from './chunk-22RMZBGW.js';
33
+ import './chunk-HPJNLETG.js';
34
+ import './chunk-IF2OSI5T.js';
35
+ import './chunk-CG36IONK.js';
36
+ import './chunk-G3IJ3N6F.js';
37
+ import './chunk-BM63HB2M.js';
38
+ import { loadRunnerConfig, RunnerConfigError, checkHooks } from './chunk-P53VEAOF.js';
33
39
  import './chunk-UXYLWVTQ.js';
34
- import './chunk-R7PF5OOU.js';
35
- import './chunk-QOZKPLVJ.js';
36
- import './chunk-N5FOE5PS.js';
37
- import './chunk-OGFGZQA3.js';
38
- import './chunk-4NPZVMJU.js';
39
40
  import './chunk-7RQ73DLO.js';
40
- import './chunk-4Q7OMKAT.js';
41
+ import './chunk-3CWUOFV6.js';
42
+ import './chunk-QOZKPLVJ.js';
41
43
  import './chunk-CX22S5BA.js';
44
+ import './chunk-ECODQ7II.js';
45
+ import './chunk-JBJTC3UU.js';
46
+ import './chunk-NFIR3GEE.js';
42
47
  import './chunk-F7CU5ABH.js';
43
48
  import './chunk-BKO6JEZI.js';
44
- import './chunk-3NSNIIBE.js';
45
- import './chunk-WUYTWNMA.js';
46
- import './chunk-JXYVA45C.js';
47
- import './chunk-4KSQKQXE.js';
48
- import './chunk-N6LURUEF.js';
49
- import './chunk-YRYMNBGQ.js';
50
- import './chunk-GQD22KKN.js';
49
+ import './chunk-QNLX477Q.js';
50
+ import './chunk-GOSFARMS.js';
51
+ import './chunk-ANPNT6PM.js';
52
+ import './chunk-T2Q7ISPE.js';
51
53
  import './chunk-WZBHBH3J.js';
52
54
  import './chunk-5QRP3MVS.js';
53
- import './chunk-YZ5F4PIS.js';
54
- import './chunk-WTE3MQML.js';
55
- import './chunk-C5Y5ZJJZ.js';
55
+ import './chunk-LLM3I5YU.js';
56
+ import './chunk-452TM7J4.js';
57
+ import './chunk-IXYZUUYQ.js';
58
+ import './chunk-FVMYKL6L.js';
59
+ import './chunk-NTRCK4NH.js';
56
60
  import './chunk-ZJSNLVCE.js';
57
- import './chunk-YO4PSLVB.js';
58
- import './chunk-BYT5O5RV.js';
59
- import './chunk-POJMEEJJ.js';
60
- import './chunk-OHVJA253.js';
61
- import './chunk-PLMTLGYH.js';
62
- import './chunk-J5M7BSJQ.js';
63
- import './chunk-EABW6AJQ.js';
64
- import './chunk-RA3O54FC.js';
65
- import './chunk-V7NIVOFD.js';
66
- import './chunk-ZM2IMMYN.js';
67
- import './chunk-BWQ66M6N.js';
68
- import './chunk-XCCLFVXM.js';
69
61
  import './chunk-BJ3KTMDH.js';
62
+ import './chunk-G2YNJIGF.js';
63
+ import './chunk-NNLA7LLQ.js';
64
+ import './chunk-WFSUUUAN.js';
65
+ import './chunk-7VUK5RFS.js';
66
+ import './chunk-EABW6AJQ.js';
67
+ import './chunk-CRB3POP4.js';
68
+ import './chunk-YQR6E6OV.js';
69
+ import './chunk-TUSYOL2O.js';
70
70
  import './chunk-VNVZDWO3.js';
71
+ import './chunk-RA3O54FC.js';
72
+ import './chunk-M2ZWWTWZ.js';
73
+ import './chunk-N2PXGQRL.js';
74
+ import './chunk-BQXFBI7T.js';
71
75
  import './chunk-Z5GEFMRW.js';
72
76
  import { readFileSync } from 'fs';
73
77
  import { join, dirname } from 'path';
@@ -1,10 +1,11 @@
1
1
  #!/usr/bin/env node
2
2
  import { createRequire } from 'node:module';
3
3
  import { RecipeError, locateRecipe, readRecipe, assertBuildable, writeSanitizedRecipe, classifyProperties, SANITIZED_NAME } from './chunk-QOZKPLVJ.js';
4
- import { startTimeOf, claimPidFile, RunnerAlreadyRunningError, releasePidFile } from './chunk-N5FOE5PS.js';
5
4
  import { CLAUDE_CODE_VERSION, NODE_IMAGE, DEFAULT_BASE_IMAGE, TINI_VERSION, GH_VERSION, GH_SHA256, TINI_SHA256 } from './chunk-CX22S5BA.js';
6
- import { withGitConfig, tokenHeader } from './chunk-BYT5O5RV.js';
7
- import { RUNNER_DIR_MODE, repoCacheDir, imagesDir, attemptStamp, runnerDir, rootfsPathFor, RUNNER_FILE_MODE } from './chunk-ZM2IMMYN.js';
5
+ import { claimPidFile, RunnerAlreadyRunningError, releasePidFile } from './chunk-ECODQ7II.js';
6
+ import { startTimeOf } from './chunk-JBJTC3UU.js';
7
+ import { RUNNER_DIR_MODE, repoCacheDir, imagesDir, attemptStamp, runnerDir, rootfsPathFor, RUNNER_FILE_MODE } from './chunk-N2PXGQRL.js';
8
+ import { withGitConfig, tokenHeader } from './chunk-BQXFBI7T.js';
8
9
  import { mkdirSync, rmSync, existsSync, mkdtempSync, cpSync, readFileSync, writeFileSync, readdirSync, lstatSync, readlinkSync } from 'fs';
9
10
  import { tmpdir } from 'os';
10
11
  import { dirname, join } from 'path';
@@ -36,6 +37,10 @@ function git(cwd, args, timeoutMs, extend = (env) => env) {
36
37
  );
37
38
  });
38
39
  }
40
+ function gitFailureLine(err) {
41
+ const lines = (err instanceof Error ? err.message : String(err)).replace(/^git [\w-]+ failed: /, "").split("\n").map((line) => line.trim()).filter((line) => line !== "");
42
+ return lines.find((line) => !/^warning:/i.test(line)) ?? lines[0] ?? "";
43
+ }
39
44
  function defaultBranchOf(lsRemote) {
40
45
  const match = lsRemote.match(/^ref: refs\/heads\/(\S+)\s+HEAD$/m);
41
46
  return match ? match[1] : null;
@@ -90,7 +95,7 @@ async function readBranchHead(input) {
90
95
  try {
91
96
  out = await git(input.dir, ["ls-remote", "--heads", input.remote, `refs/heads/${input.branch}`], HEAD_TIMEOUT_MS, credential);
92
97
  } catch (err) {
93
- return { kind: "unreadable", reason: (err instanceof Error ? err.message : String(err)).replace(/^git [\w-]+ failed: /, "").split("\n")[0] };
98
+ return { kind: "unreadable", reason: gitFailureLine(err) };
94
99
  }
95
100
  const sha = out.match(/^([0-9a-f]{40}|[0-9a-f]{64})\trefs\/heads\//m)?.[1];
96
101
  return sha === void 0 ? { kind: "absent" } : { kind: "found", sha };
@@ -102,9 +107,10 @@ async function fetchCommit(input) {
102
107
  await git(input.dir, ["init", "--quiet"]);
103
108
  await git(input.dir, ["fetch", "--quiet", "--depth", "1", "--no-tags", input.remote, input.commit], CLONE_TIMEOUT_MS, credential);
104
109
  await git(input.dir, ["checkout", "--quiet", "--detach", "--force", "FETCH_HEAD"]);
110
+ if (input.clean === true) await git(input.dir, ["clean", "-ffdx", "--quiet"]);
105
111
  return { ok: true };
106
112
  } catch (err) {
107
- const detail = (err instanceof Error ? err.message : String(err)).replace(/^git [\w-]+ failed: /, "").split("\n")[0];
113
+ const detail = gitFailureLine(err);
108
114
  return { ok: false, reason: `the fetch of ${input.commit.slice(0, 8)} from ${input.remote} failed: ${detail}` };
109
115
  }
110
116
  }
@@ -122,7 +128,7 @@ async function diffBetween(input) {
122
128
  );
123
129
  return { ok: true };
124
130
  } catch (err) {
125
- const detail = (err instanceof Error ? err.message : String(err)).replace(/^git [\w-]+ failed: /, "").split("\n")[0];
131
+ const detail = gitFailureLine(err);
126
132
  return { ok: false, reason: `the diff of ${input.head.slice(0, 8)} from ${input.base.slice(0, 8)} could not be read from ${input.remote}: ${detail}` };
127
133
  }
128
134
  }
@@ -189,9 +195,9 @@ function toolsDockerfile(parts) {
189
195
  " && chmod 1777 /out/build",
190
196
  ...parts.guestHelper === true ? [
191
197
  "COPY guest/outerlayer-guest /out/opt/outerlayer/bin/outerlayer-guest",
192
- // The guest mounts the build's tmpfs, the kernel's file systems and the run directory over these.
198
+ // The guest mounts the build's tmpfs, the kernel's file systems and the run directory over these. The judge's tmpfs is mounted over its directory when the judge runs.
193
199
  "RUN chmod 0755 /out/opt/outerlayer/bin/outerlayer-guest \\",
194
- " && mkdir -p /out/outerlayer/job /out/run /out/proc /out/sys /out/dev"
200
+ " && mkdir -p /out/outerlayer/job /out/outerlayer/judge /out/run /out/proc /out/sys /out/dev"
195
201
  ] : [],
196
202
  `RUN set -eu \\`,
197
203
  ` && case "$TARGETARCH" in \\`,
@@ -295,6 +301,11 @@ function splitRepository(repository) {
295
301
  }
296
302
  return { full, owner: parts[0], name: parts[1] };
297
303
  }
304
+ async function fetchPinnedCommit(opts) {
305
+ const fetched = await fetchCommit({ remote: opts.remote, dir: opts.dir, commit: opts.commit, token: opts.token, clean: true });
306
+ if (!fetched.ok) throw new RecipeError("repository_unreadable", `cannot read ${opts.repository}: ${fetched.reason}`);
307
+ return { dir: opts.dir, defaultBranch: opts.ref, commit: opts.commit };
308
+ }
298
309
  function metadataOf(image) {
299
310
  try {
300
311
  const parsed = JSON.parse(image.labels["devcontainer.metadata"] ?? "[]");
@@ -345,12 +356,8 @@ async function buildRepositoryImage(opts, ports) {
345
356
  throw err;
346
357
  }
347
358
  try {
348
- const checkout = await fetchDefaultBranch({
349
- remote: opts.remote ?? `https://github.com/${owner}/${name}.git`,
350
- dir: cacheDir,
351
- token: opts.readToken,
352
- repository
353
- });
359
+ const remote = opts.remote ?? `https://github.com/${owner}/${name}.git`;
360
+ const checkout = opts.pinned === void 0 ? await fetchDefaultBranch({ remote, dir: cacheDir, token: opts.readToken, repository }) : await fetchPinnedCommit({ remote, dir: cacheDir, token: opts.readToken, repository, ...opts.pinned });
354
361
  const location = locateRecipe(checkout.dir);
355
362
  const recipe = readRecipe(checkout.dir, location);
356
363
  assertBuildable(checkout.dir, location, recipe);
@@ -1,15 +1,70 @@
1
1
  #!/usr/bin/env node
2
2
  import { createRequire } from 'node:module';
3
3
  import { parseSize } from './chunk-7RQ73DLO.js';
4
- import { JOB_MOUNT, GATEWAY_PORT, GATEWAY_SOCKET, CLAUDE_PORT, CLAUDE_SOCKET, RELAY_PORT, DESTINATION_BASE_PORT } from './chunk-4Q7OMKAT.js';
5
4
  import { BUILDKIT_IMAGE } from './chunk-CX22S5BA.js';
6
- import { compareVersions } from './chunk-JXYVA45C.js';
7
- import { runnerDir } from './chunk-ZM2IMMYN.js';
5
+ import { compareVersions } from './chunk-QNLX477Q.js';
6
+ import { runnerDir } from './chunk-N2PXGQRL.js';
7
+ import { CLAUDE_PORT, JOB_MOUNT, GATEWAY_PORT, GATEWAY_SOCKET, CLAUDE_SOCKET, RELAY_PORT, DESTINATION_BASE_PORT } from './chunk-BQXFBI7T.js';
8
8
  import { createHash } from 'crypto';
9
9
  import { realpathSync } from 'fs';
10
10
  import { join, dirname, resolve } from 'path';
11
11
 
12
12
  createRequire(import.meta.url);
13
+
14
+ // src/runner/judge-command.ts
15
+ var JUDGE_DIR = "/outerlayer/judge";
16
+ var JUDGE_HOME = `${JUDGE_DIR}/home`;
17
+ var JUDGE_TREE = `${JUDGE_DIR}/tree`;
18
+ var JUDGE_REPORT = `${JUDGE_DIR}/report.html`;
19
+ var JUDGE_PROGRAM = "/opt/outerlayer/claude/bin/claude";
20
+ var MAX_REPORT_BYTES = 5 * 1024 * 1024;
21
+ var JUDGE_MAX_TURNS = 60;
22
+ var JUDGE_PATH = "/opt/outerlayer/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin";
23
+ var JUDGE_TOOLS = "Read,Grep,Glob,Write";
24
+ function judgeCommand(input) {
25
+ const env = {
26
+ HOME: JUDGE_HOME,
27
+ TMPDIR: `${JUDGE_HOME}/tmp`,
28
+ PATH: JUDGE_PATH,
29
+ ANTHROPIC_BASE_URL: `http://127.0.0.1:${CLAUDE_PORT}`,
30
+ CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC: "1",
31
+ DISABLE_AUTOUPDATER: "1",
32
+ ...input.credential === void 0 ? {} : { [input.credential.variable]: input.credential.placeholder }
33
+ };
34
+ const argv = [
35
+ JUDGE_PROGRAM,
36
+ "-p",
37
+ "--output-format",
38
+ "json",
39
+ "--setting-sources",
40
+ "user",
41
+ "--strict-mcp-config",
42
+ "--disable-slash-commands",
43
+ "--no-session-persistence",
44
+ "--tools",
45
+ JUDGE_TOOLS,
46
+ "--allowedTools",
47
+ // A path that starts with two slashes is absolute in a permission rule.
48
+ `Read(/${JUDGE_DIR}/**),Edit(/${JUDGE_REPORT})`,
49
+ "--add-dir",
50
+ JUDGE_TREE,
51
+ "--permission-mode",
52
+ "dontAsk",
53
+ "--max-turns",
54
+ String(JUDGE_MAX_TURNS),
55
+ // After the separator, so a brief that starts with a dash is not read as an option.
56
+ "--",
57
+ input.brief
58
+ ];
59
+ return { argv, env, cwd: JUDGE_DIR };
60
+ }
61
+ var HASH_SCRIPT = 'const fs=require("fs"),c=require("crypto");const p=fs.realpathSync(process.argv[1]);process.stdout.write(c.createHash("sha256").update(fs.readFileSync(p)).digest("hex")+" "+p+"\\n")';
62
+ var HASH_ARGV = ["/opt/outerlayer/bin/node", "-e", HASH_SCRIPT, JUDGE_PROGRAM];
63
+ function parseProgramHash(stdout) {
64
+ return /^([0-9a-f]{64}) /.exec(stdout.trim())?.[1];
65
+ }
66
+
67
+ // src/runner/container/run-args.ts
13
68
  var BUILD_LABEL = "ai.outerlayer.build";
14
69
  var RUNNER_LABEL = "ai.outerlayer.runner";
15
70
  function resolvedRunnerDir(configPath) {
@@ -71,6 +126,7 @@ function containerRunArgs(input) {
71
126
  `type=volume,src=${input.volume},dst=/tmp,volume-subpath=tmp`,
72
127
  "--mount",
73
128
  `type=bind,src=${mountPath(input.buildDir)},dst=${JOB_MOUNT},readonly`,
129
+ ...input.judgeDir === void 0 ? [] : ["--mount", `type=bind,src=${mountPath(input.judgeDir)},dst=${JUDGE_DIR}`],
74
130
  "--entrypoint",
75
131
  "/opt/outerlayer/bin/tini",
76
132
  input.image,
@@ -261,4 +317,4 @@ function ensureBuilder(rawRun, spec, options = {}) {
261
317
  );
262
318
  }
263
319
 
264
- export { BUILD_LABEL, MIN_BUILDX_VERSION, RUNNER_LABEL, builderName, builderSpec, buildxState, containerExecArgs, containerRunArgs, ensureBuilder, ensureFailureCount, isOutOfMemory, prepareRunArgs, proxyEnvironment, runnerLabelValue, trackImageBuild };
320
+ export { BUILD_LABEL, HASH_ARGV, JUDGE_DIR, JUDGE_MAX_TURNS, JUDGE_PROGRAM, MAX_REPORT_BYTES, MIN_BUILDX_VERSION, RUNNER_LABEL, builderName, builderSpec, buildxState, containerExecArgs, containerRunArgs, ensureBuilder, ensureFailureCount, isOutOfMemory, judgeCommand, parseProgramHash, prepareRunArgs, proxyEnvironment, runnerLabelValue, trackImageBuild };