@outerlayer/cli 0.16.1 → 0.18.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 (153) hide show
  1. package/CHANGELOG.md +64 -0
  2. package/README.md +26 -11
  3. package/dist/agent-setup-RBTEACYU.js +28 -0
  4. package/dist/artifact-link-cmd-AVM2EKTV.js +25 -0
  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-DUI2ZH2H.js} +5 -6
  8. package/dist/{check-CNTMSGOG.js → check-5C4YDZYV.js} +53 -50
  9. package/dist/{chunk-R7PF5OOU.js → chunk-3CWUOFV6.js} +21 -14
  10. package/dist/{chunk-R4ZQQ7U2.js → chunk-3OFZM4X6.js} +551 -72
  11. package/dist/{chunk-2MNPHL23.js → chunk-47TTLEDX.js} +6 -5
  12. package/dist/{chunk-PLMTLGYH.js → chunk-52NC2FIU.js} +4 -3
  13. package/dist/chunk-6CHIYEZZ.js +564 -0
  14. package/dist/{chunk-TI7B7UEO.js → chunk-AMFWYESY.js} +1 -1
  15. package/dist/{chunk-YRYMNBGQ.js → chunk-ANPNT6PM.js} +2 -1
  16. package/dist/{chunk-JJEGKEFV.js → chunk-B3UKB46N.js} +6 -6
  17. package/dist/{chunk-JXYVA45C.js → chunk-BMYGAGBJ.js} +1050 -304
  18. package/dist/{chunk-4Q7OMKAT.js → chunk-BQXFBI7T.js} +81 -5
  19. package/dist/chunk-CRB3POP4.js +34 -0
  20. package/dist/{chunk-F47JBIAW.js → chunk-DSF2FWAZ.js} +2 -1
  21. package/dist/{chunk-N5FOE5PS.js → chunk-ECODQ7II.js} +4 -33
  22. package/dist/{chunk-NBGDBTO2.js → chunk-ESC6UR76.js} +6 -5
  23. package/dist/{chunk-HHJDN6AU.js → chunk-ESVW4MJY.js} +1 -1
  24. package/dist/{chunk-42ZI52JW.js → chunk-F4XSVQJ4.js} +4 -3
  25. package/dist/{chunk-FWX735W7.js → chunk-FUB7LRUE.js} +7 -7
  26. package/dist/chunk-G2YNJIGF.js +44 -0
  27. package/dist/{chunk-QKFB4K7X.js → chunk-GGOMT6KF.js} +1 -1
  28. package/dist/{chunk-CA3XMYFN.js → chunk-GOBOJMOJ.js} +6 -6
  29. package/dist/{chunk-N6LURUEF.js → chunk-GOSFARMS.js} +1 -1
  30. package/dist/chunk-JBJTC3UU.js +35 -0
  31. package/dist/{chunk-U3HXXRTY.js → chunk-JF42URPU.js} +16 -15
  32. package/dist/{chunk-ADJRGVZC.js → chunk-KDVIU23F.js} +1 -1
  33. package/dist/{chunk-6QRMLNH3.js → chunk-KK4VBOH6.js} +93 -37
  34. package/dist/{chunk-KMDLFI4P.js → chunk-L2USZT6T.js} +1 -1
  35. package/dist/{chunk-WTE3MQML.js → chunk-LXAY4A2G.js} +3 -12
  36. package/dist/{chunk-V7NIVOFD.js → chunk-M2ZWWTWZ.js} +1 -1
  37. package/dist/{chunk-ERI5GBNI.js → chunk-MLAAT4S6.js} +3 -3
  38. package/dist/{chunk-POJMEEJJ.js → chunk-MNTSH4CL.js} +1 -1
  39. package/dist/{chunk-WUYTWNMA.js → chunk-MQJSXHV7.js} +1 -1
  40. package/dist/{chunk-O765JR4W.js → chunk-MTP2QEVO.js} +1 -1
  41. package/dist/{chunk-ZM2IMMYN.js → chunk-N2PXGQRL.js} +9 -1
  42. package/dist/{chunk-J5M7BSJQ.js → chunk-N3CUDFCW.js} +9 -3
  43. package/dist/{chunk-4KSQKQXE.js → chunk-NACDBXUG.js} +19 -15
  44. package/dist/{chunk-RSPXEVDX.js → chunk-NL4XPC7W.js} +1 -1
  45. package/dist/{chunk-EHAAPRWY.js → chunk-QQM2UJ6T.js} +31 -26
  46. package/dist/{chunk-5FEK6PNE.js → chunk-RAZTWYZN.js} +10 -9
  47. package/dist/{chunk-4NPZVMJU.js → chunk-S26M3YBJ.js} +60 -4
  48. package/dist/{chunk-UGP76IZ5.js → chunk-S5PCA3SN.js} +1 -1
  49. package/dist/{chunk-YDSMROTB.js → chunk-S76AYB4M.js} +25 -20
  50. package/dist/{chunk-RMWLBUCJ.js → chunk-SADUQSO4.js} +1 -1
  51. package/dist/{chunk-OZGHL5Y5.js → chunk-SWCTGCSC.js} +2 -2
  52. package/dist/{chunk-GQD22KKN.js → chunk-T2Q7ISPE.js} +2 -1
  53. package/dist/{chunk-C5Y5ZJJZ.js → chunk-T4Y6SCG3.js} +5 -4
  54. package/dist/{chunk-6JNURGOA.js → chunk-ULSIJGLS.js} +45 -13
  55. package/dist/{chunk-ONRCEE6D.js → chunk-UYOYTKE2.js} +4 -4
  56. package/dist/{chunk-PTW6W5NR.js → chunk-VYAL7PUE.js} +1 -1
  57. package/dist/{chunk-JEHRUE3F.js → chunk-W5VPCONO.js} +166 -196
  58. package/dist/{chunk-NPQWKGUA.js → chunk-WBT5X3NH.js} +5 -4
  59. package/dist/{chunk-RLYZLUQ7.js → chunk-WGAUY5LE.js} +2 -2
  60. package/dist/{chunk-RSNSZ2LJ.js → chunk-X5H2WYMB.js} +1 -1
  61. package/dist/{chunk-OHVJA253.js → chunk-X5N6KC6V.js} +47 -29
  62. package/dist/{chunk-22RMZBGW.js → chunk-XGP3VYOG.js} +1 -1
  63. package/dist/{chunk-HYZRY4KZ.js → chunk-XNGMQ27R.js} +16 -6
  64. package/dist/{chunk-W3EKCZSQ.js → chunk-Y5GE76XC.js} +180 -53
  65. package/dist/{chunk-EWORLJMO.js → chunk-Y5MXET4M.js} +277 -526
  66. package/dist/{chunk-XCK2YAY5.js → chunk-YVZJ6GKF.js} +3 -2
  67. package/dist/{chunk-YO4PSLVB.js → chunk-ZRL6HKBY.js} +1 -1
  68. package/dist/claude-launcher-YVXI76NX.js +102 -0
  69. package/dist/{cli-AEWH62UE.js → cli-C77H54BV.js} +489 -174
  70. package/dist/{command-HHPBR6CS.js → command-H5CILO4S.js} +40 -37
  71. package/dist/{config-N3C6DFQA.js → config-FXDG4NPJ.js} +2 -3
  72. package/dist/connect-cmd-ZW5FV3ZW.js +17 -0
  73. package/dist/{context-adopt-VMMGFRUZ.js → context-adopt-O5OFXMKI.js} +13 -12
  74. package/dist/context-materialize-Y5IJ4MQT.js +23 -0
  75. package/dist/{dist-LKDWQWZ7.js → dist-QTX4RSCL.js} +2 -2
  76. package/dist/{docker-4J77Q2BF.js → docker-RC3PRX3Q.js} +7 -8
  77. package/dist/{doctor-N6Y7HKK3.js → doctor-2NQQML6P.js} +47 -39
  78. package/dist/{emit-artifact-cmd-H5MH3AOP.js → emit-artifact-cmd-7MP63KXU.js} +25 -22
  79. package/dist/emit-cmd-PPN4QQF2.js +14 -0
  80. package/dist/{emit-criteria-cmd-HBP6XS5C.js → emit-criteria-cmd-3RXPDLNK.js} +16 -14
  81. package/dist/{emit-finding-cmd-ZXDRL6PG.js → emit-finding-cmd-OL7JB6H3.js} +18 -16
  82. package/dist/{emit-result-cmd-RFKALS5Z.js → emit-result-cmd-UZVMOCF4.js} +20 -17
  83. package/dist/{exec-client-CQV746UF.js → exec-client-7UMESIE2.js} +3 -4
  84. package/dist/governing-source-BUU4IABS.js +35 -0
  85. package/dist/guest-init-KPBQOFE5.js +9 -0
  86. package/dist/hidden-prompt-UXBFMQC5.js +68 -0
  87. package/dist/{hook-fast-D7HV4IXG.js → hook-fast-W5MKHMJT.js} +4 -1
  88. package/dist/host-key-U4B5QETR.js +8 -0
  89. package/dist/{import-capture-cmd-EQ35EARA.js → import-capture-cmd-ZO6KGHI2.js} +7 -5
  90. package/dist/{import-ruler-cmd-BGAZXCQM.js → import-ruler-cmd-KJMWL5V6.js} +5 -3
  91. package/dist/index.js +8 -4
  92. package/dist/init-VWXIZEAN.js +54 -0
  93. package/dist/{init-FBCA4DRA.js → init-WNXXYMO7.js} +80 -53
  94. package/dist/{init-cmd-CAG572UE.js → init-cmd-WS6TIRYT.js} +28 -24
  95. package/dist/{init-update-VDFF4K3B.js → init-update-E3J6TVIN.js} +27 -26
  96. package/dist/{install-HDBIPHTX.js → install-CBWIEF3R.js} +4 -5
  97. package/dist/{install-cmd-5HYMXLEW.js → install-cmd-QDOTR4OM.js} +5 -4
  98. package/dist/{login-browser-QZCS3WW6.js → login-browser-O63QZN7D.js} +4 -3
  99. package/dist/{logout-cmd-QAUFDJ6J.js → logout-cmd-5V5MMBSY.js} +3 -2
  100. package/dist/{logs-H55NWW4H.js → logs-AMLKC3VP.js} +3 -1
  101. package/dist/loop-UGI2BJOT.js +76 -0
  102. package/dist/machine-JOW4RNZ7.js +49 -0
  103. package/dist/{mcp-install-cmd-FDTPQQS6.js → mcp-install-cmd-GGWB3JJ2.js} +3 -2
  104. package/dist/{mcp-serve-cmd-3KVYEUXG.js → mcp-serve-cmd-DOTY5FKY.js} +10 -8
  105. package/dist/paths-7XP63OAX.js +7 -0
  106. package/dist/pending-532ABZYC.js +8 -0
  107. package/dist/{pidfile-UZRH774M.js → pidfile-3DM4OBKO.js} +4 -2
  108. package/dist/{record-C43U4PKS.js → record-3WP6V2K5.js} +3 -2
  109. package/dist/{runner-env-EN7YE7QX.js → runner-env-4NB2GA7O.js} +5 -6
  110. package/dist/runner-key-TC3NFWFV.js +177 -0
  111. package/dist/{session-sender-24L5EW25.js → session-sender-NNVPSPY7.js} +17 -14
  112. package/dist/spec-cmd-OWVTBB4J.js +116 -0
  113. package/dist/{starter-pack-KF66HGZR.js → starter-pack-OPWUDOWX.js} +7 -5
  114. package/dist/{status-XHSGEYO5.js → status-TKCLLKFG.js} +42 -39
  115. package/dist/{statusline-fast-SVTMBMD7.js → statusline-fast-KN3BX67H.js} +2 -1
  116. package/dist/{sync-cmd-5G5EIEHD.js → sync-cmd-Q6VYNPWG.js} +16 -13
  117. package/dist/{watch-UTOXO4SE.js → watch-ARAGADTF.js} +16 -13
  118. package/dist/{work-claim-cmd-RKHYFE5Y.js → work-claim-cmd-34FERLYX.js} +25 -27
  119. package/dist/work-cmd-7S5B2H2P.js +25 -0
  120. package/dist/{work-comment-cmd-A55CUSYE.js → work-comment-cmd-SZJSPM5S.js} +15 -13
  121. package/dist/{work-launch-R5HTAP6X.js → work-launch-LEQ2MPM3.js} +3 -1
  122. package/dist/{work-open-pr-cmd-E6O53IJD.js → work-open-pr-cmd-RPXKCBWS.js} +17 -15
  123. package/dist/work-pr-cmd-5LGANL32.js +26 -0
  124. package/package.json +3 -2
  125. package/skill-pack/maintained/outerlayer-amend/SKILL.md +6 -2
  126. package/skill-pack/maintained/outerlayer-cli/SKILL.md +2 -0
  127. package/skill-pack/maintained/outerlayer-evidence/SKILL.md +4 -2
  128. package/skill-pack/maintained/outerlayer-findings/SKILL.md +2 -0
  129. package/skill-pack/shipped.json +27 -5
  130. package/skill-pack/template/build/SKILL.md +25 -10
  131. package/skill-pack/template/build/references/agent-briefs.md +3 -1
  132. package/skill-pack/template/build/references/criteria-judge.md +27 -0
  133. package/skill-pack/template/build/references/release.md +27 -34
  134. package/skill-pack/template/build/scripts/criterion-citations.mjs +8 -3
  135. package/skill-pack/template/build/scripts/record-criteria.mjs +8 -2
  136. package/skill-pack/template/spec/SKILL.md +37 -10
  137. package/skill-pack/validators/acceptance-criteria.yaml +25 -25
  138. package/skill-pack/validators/code-review.yaml +6 -5
  139. package/dist/agent-setup-K3ABU5NL.js +0 -27
  140. package/dist/chunk-BYT5O5RV.js +0 -61
  141. package/dist/connect-cmd-JUAW6MU4.js +0 -16
  142. package/dist/context-materialize-2WXTS4CW.js +0 -22
  143. package/dist/emit-cmd-IJ3GVRFD.js +0 -12
  144. package/dist/guest-init-LJP5GPZF.js +0 -9
  145. package/dist/host-key-YMLXY6QB.js +0 -7
  146. package/dist/init-MYX7YMIH.js +0 -51
  147. package/dist/loop-75J4YXLZ.js +0 -73
  148. package/dist/machine-A6ZCTUGS.js +0 -46
  149. package/dist/paths-OYKMVYJP.js +0 -6
  150. package/dist/pending-MXLSRYMO.js +0 -7
  151. package/dist/runner-key-W25ISHI3.js +0 -155
  152. package/dist/work-cmd-DJQJEZVB.js +0 -23
  153. package/dist/work-pr-cmd-L77K32WK.js +0 -24
package/CHANGELOG.md CHANGED
@@ -1,5 +1,69 @@
1
1
  # @outerlayer/cli
2
2
 
3
+ ## 0.18.0
4
+
5
+ ### Minor Changes
6
+
7
+ - 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.
8
+ - 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.
9
+
10
+ A validator or policy `when` can name `repositories`, so a check applies only to pull requests in those repositories.
11
+
12
+ 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.
13
+
14
+ - 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.
15
+
16
+ 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.
17
+
18
+ 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.
19
+
20
+ - 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.
21
+
22
+ 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.
23
+
24
+ - 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.
25
+
26
+ 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.
27
+
28
+ ### Patch Changes
29
+
30
+ - 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.
31
+
32
+ To pick up the new `/build`, run `outerlayer init --update`. A copy you edited is left as it is.
33
+
34
+ - 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.
35
+
36
+ `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.
37
+
38
+ `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.
39
+
40
+ - 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.
41
+ - 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.
42
+
43
+ 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.
44
+
45
+ - 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.
46
+ - 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`.
47
+
48
+ 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.
49
+
50
+ ## 0.17.0
51
+
52
+ ### Minor Changes
53
+
54
+ - 420b0db: `outerlayer runner init` takes `--factory <org>/<factory>`, the same name `outerlayer init --factory` takes, so a runner host no longer needs the Factory Id copied from the dashboard. With no factory named, a login that can use exactly one factory uses it and says which. A login that can use several is refused with each `--factory` value listed. `--app-id` still works, and a `--factory` and `--app-id` that name different factories are refused.
55
+
56
+ ### Patch Changes
57
+
58
+ - a3ac4ae: Count a test title such as `WC-01: counts words` as citing criterion WC-01. The starter `/build` skill's `criterion-citations.mjs` linked no test whose title put a colon straight after the id, so a build could report 0 of 4 criteria linked. A longer id such as `WC-010`, `WC-01a` or `WC-01:b` still does not cite WC-01.
59
+ - a897c68: The README says to install the CLI globally with `npm install -g @outerlayer/cli`, because `/build` and the other skills run the `outerlayer` command.
60
+ - 2a59794: Fix review results never being recorded on a new factory. The starter code-review and acceptance-criteria checks and the starter `/build` skill told agents to copy each report's link from the pull request's evidence comment, which a factory never posts until comments are turned on. They now take the link from the line `outerlayer sync` prints. A new `outerlayer sync --link <artifact id>` prints the link of an artifact an earlier sync already uploaded.
61
+ - 8196dc9: `outerlayer runner init` names the factory once when it picks the only one the login can use. A refused pasted key no longer repeats the gateway's "Mint a fresh key" advice beside its own step to create a new key with the Runner preset.
62
+ - 8196dc9: `outerlayer runner init` no longer tries to create the runner key with your login, which the gateway never allows. With no key in the config file, it prints the link to the factory's API keys page, asks you to paste a key made with the Runner preset (nothing is shown as you paste), checks it, and goes on. With no terminal it stops and prints the link and the config-file lines to use. `--key-name` is removed. Its output now marks warnings with `!` instead of a tick, no longer doubles ticks for lines from the VM, drops the PATH note about the VM's shell, and says whether capture on the Mac is affected when the runner key moves into the VM.
63
+ - 3ba60cd: No change in behaviour: the runner's wait for a stopped session sender's exit is simplified, and its tests check that the wait ends when the exit is logged.
64
+ - 0b7e87c: The starter skills run the CLI through `npx @outerlayer/cli` when `outerlayer` is not on PATH.
65
+ - f7553df: `outerlayer context materialize` and a container build now fetch the control plane and commit that OuterLayer stores for a repository, and read no `governs` list for it. `outerlayer doctor` reports which control plane governs the repository, its ref and the stored commit. `outerlayer connect` picks the factory that owns a repository linked to two factories, the one with the oldest connection, without asking.
66
+
3
67
  ## 0.16.1
4
68
 
5
69
  ### Patch Changes
package/README.md CHANGED
@@ -13,12 +13,13 @@ network, and when, is below.
13
13
  Launch a session naming the work it's for, then sync:
14
14
 
15
15
  ```
16
- npx @outerlayer/cli login # sign in, once per machine
17
- npx @outerlayer/cli init # connect this repository to a factory, install the capture hooks
18
- npx @outerlayer/cli work build --issue 42 --local # create the work item; prints its number
19
- OUTERLAYER_WORK=7 claude # launch a session naming that number
20
- npx @outerlayer/cli sync --dry-run # see exactly what would leave your machine
21
- npx @outerlayer/cli sync # upload sessions launched that way
16
+ npm install -g @outerlayer/cli # puts `outerlayer` on your PATH, once per machine
17
+ outerlayer login # sign in, once per machine
18
+ outerlayer init # connect this repository to a factory, install the capture hooks
19
+ outerlayer work build --issue 42 --local # create the work item; prints its number
20
+ OUTERLAYER_WORK=7 claude # launch a session naming that number
21
+ outerlayer sync --dry-run # see exactly what would leave your machine
22
+ outerlayer sync # upload sessions launched that way
22
23
  ```
23
24
 
24
25
  ## Privacy, stated plainly
@@ -94,6 +95,12 @@ content:
94
95
  list and read items, and `work.claim` to claim, renew or release — `work.claim`
95
96
  is not granted to a dashboard role by default, since a claim is a host's
96
97
  lease, not a person's.
98
+ - **`outerlayer spec target`, `search`, `file` and `edit`** each make one
99
+ request to your gateway (`GET /v1/specs/target`, `GET /v1/specs/search`,
100
+ `POST /v1/specs`, `PATCH /v1/specs/{key}`). `search` sends your query, and
101
+ `file` and `edit` send the title and the Markdown body you give them.
102
+ Your gateway files the issue in your tracker; the CLI never holds a tracker
103
+ credential.
97
104
  - **`outerlayer runner start`** lists, claims, renews and releases work
98
105
  items the same way `work claim`/`renew`/`release` do, on the schedule its
99
106
  config sets — it sends nothing about the job it runs beyond that; the
@@ -115,6 +122,15 @@ content:
115
122
  command in a new container, and sends one `POST /v1/emitted-results` for
116
123
  each: the check's name, pass or fail, and for a fail the exit status and the
117
124
  last line of its output, scrubbed.
125
+ A `where: host` check that declares a `judge` runs after a successful
126
+ build instead of a command. The runner reads the item's recorded criteria
127
+ with `GET /v1/work-items/{id}`, fetches the build's commits and the brief
128
+ with the host's own git credentials, and copies out the tests that cite the
129
+ criteria. It starts a Claude Code agent in the build's image with the host's
130
+ Claude credential. The judge sees only a read-only folder of the criteria,
131
+ citations and diff, and it has no shell. The runner uploads the judge's
132
+ `report.html` as an artifact with its own key, and sends one
133
+ `POST /v1/emitted-results` with pass or fail and a one-line reason.
118
134
  Before each check's command runs, the runner writes a read-only folder for it
119
135
  to read: the work item, from `GET /v1/work-items/{id}` with the runner's own
120
136
  key; the branch's diff, from `git fetch` of the build's commits with the
@@ -144,10 +160,9 @@ content:
144
160
  to it.
145
161
  - **`outerlayer runner init`** makes no network call when the config file
146
162
  already holds a runner key. With no key there and a saved login, it
147
- creates one: `POST /v1/api-keys` with your login's token, sending the key's
148
- name (this machine's name, or `--key-name`), the Runner preset's
149
- permissions and the factory id. It then checks the new key, unsigned, with
150
- `GET /v1/runner/host-key`. `runner init --vm` on a Mac also runs `limactl`,
163
+ asks the gateway which factories the login can use (`GET /v1/me/factories`)
164
+ and prints the factory's API keys page. You create the key there and paste
165
+ it. It checks the pasted key, unsigned, with `GET /v1/runner/host-key`. `runner init --vm` on a Mac also runs `limactl`,
151
166
  and Lima downloads a Linux VM image. Inside the VM, `apt` downloads Node and
152
167
  `npm` downloads this CLI. Your runner key goes to the VM on the standard
153
168
  input of a `limactl shell` command and is never a file or an argument on the
@@ -391,7 +406,7 @@ before it gives up.
391
406
  | `outerlayer work claim --item <n>\|--issue <n> --kind implement\|amend [--repo] [--host] [--seconds]` | Records a lease for this host (default: its own hostname), so two hosts never build the same item at once. The lease belongs to the runner key that claimed it: a live lease held by a different key is refused, even under the same host name, and claiming again with the same key extends it. The first claim creates the host key (`host-key.pem` beside the config file) and binds the runner key to it; from then on every request made with that key is signed. Lease length defaults to 900 seconds, the server's own cap. |
392
407
  | `outerlayer work renew --item <n>\|--issue <n> [--repo] [--host] [--seconds]` | Extends this host's own live lease. Refused if the lease has expired, or if a different runner key holds it (403 `claim_not_held`). |
393
408
  | `outerlayer work release --item <n>\|--issue <n> [--repo] [--host] [--session-id] [--outcome]` | Marks the lease this API key holds released, optionally recording how the attempt ended. Releasing an already-released lease is a no-op that returns the recorded release time and outcome. Run inside a session with no `--host`, it releases that session's own lease instead (`--session-id` names the session when it cannot be detected), so the item's review opens without ending the session. |
394
- | `outerlayer runner init [--vm\|--no-vm] [--app-id <id>] [--key-name <name>] [--config <path>]` | Writes a `runner` block with defaults, keeping every key the config already had. With no `apiKey` in the config and a saved login, it first creates a runner key from the login with the Runner preset, named after the machine or `--key-name`, on the factory `--app-id` names, and checks it before anything else changes. It names `builtin:vm` on a Linux machine where the user can use `/dev/kvm` and Docker Engine can build images once the guest kernel and helper are published (not yet), `builtin:container` on that machine until then, `builtin:container` on any other machine with Docker Engine, and `builtin:process` on one without, and writes no hook script. Refuses rather than overwriting an existing block. On macOS it asks whether to create a Linux VM with Lima, installs Docker Engine and the runner in it, and starts both at login; `--vm` answers yes and `--no-vm` answers no, and a run with no terminal answers no. The runner key and the host key then live only in the VM: the Mac's config loses `apiKey` and `runner` and gains a `runnerVm` note, which `outerlayer doctor` reads. |
409
+ | `outerlayer runner init [--vm\|--no-vm] [--factory <org>/<factory>] [--app-id <id>] [--config <path>]` | Writes a `runner` block with defaults, keeping every key the config already had. With no `apiKey` in the config and a saved login, it prints the API keys page of the factory `--factory` or `--app-id` names (the only factory the login can use, when neither is given), asks you to paste a runner key made with the Runner preset, and checks it before anything else changes. With no terminal it stops and says what to put in the config. It names `builtin:vm` on a Linux machine where the user can use `/dev/kvm` and Docker Engine can build images once the guest kernel and helper are published (not yet), `builtin:container` on that machine until then, `builtin:container` on any other machine with Docker Engine, and `builtin:process` on one without, and writes no hook script. Refuses rather than overwriting an existing block. On macOS it asks whether to create a Linux VM with Lima, installs Docker Engine and the runner in it, and starts both at login; `--vm` answers yes and `--no-vm` answers no, and a run with no terminal answers no. The runner key and the host key then live only in the VM: the Mac's config loses `apiKey` and `runner` and gains a `runnerVm` note, which `outerlayer doctor` reads. |
395
410
  | `outerlayer runner check [--config <path>]` | Validates the config, the hooks and the key, prints the settings the runner would use, and exits non-zero on a problem. Takes no work and writes no pid file. |
396
411
  | `outerlayer runner start [--config <path>]` | Runs the loop: claim work, run it, sync, clean up, report, release with an outcome. Repeat. Reads the `runner` block from the config file (default `~/.outerlayer/config.json`). Refuses to start on a bad config, an unrunnable hook, or a refused key. |
397
412
  | `outerlayer runner image --repository <owner/name> [--remote <url>] [--config <path>] [--json]` | Builds the image a repository's builds run in. Fetches the head of the default branch into the runner's cache, builds its `.devcontainer/devcontainer.json` with the devcontainer CLI, ignores every setting that reaches the host or widens the container's privileges, and adds this CLI and a pinned Claude Code. Reuses the image until the recipe or the tools change, keeps the newest three, and writes `recipe.json` beside the image record. Refuses a Compose recipe (`recipe_needs_docker`) and a repository with only named configurations (`recipe_ambiguous`). |
@@ -0,0 +1,28 @@
1
+ #!/usr/bin/env node
2
+ import { createRequire } from 'node:module';
3
+ export { keepHandWritten, runAgentSetup } from './chunk-RAZTWYZN.js';
4
+ import './chunk-47TTLEDX.js';
5
+ import './chunk-YVZJ6GKF.js';
6
+ import './chunk-F4XSVQJ4.js';
7
+ import './chunk-ANPNT6PM.js';
8
+ import './chunk-W5VPCONO.js';
9
+ import './chunk-LXAY4A2G.js';
10
+ import './chunk-T4Y6SCG3.js';
11
+ import './chunk-ZRL6HKBY.js';
12
+ import './chunk-52NC2FIU.js';
13
+ import './chunk-MNTSH4CL.js';
14
+ import './chunk-X5N6KC6V.js';
15
+ import './chunk-N3CUDFCW.js';
16
+ import './chunk-EABW6AJQ.js';
17
+ import './chunk-RA3O54FC.js';
18
+ import './chunk-M2ZWWTWZ.js';
19
+ import './chunk-N2PXGQRL.js';
20
+ import './chunk-BQXFBI7T.js';
21
+ import './chunk-XCCLFVXM.js';
22
+ import './chunk-ZJSNLVCE.js';
23
+ import './chunk-BJ3KTMDH.js';
24
+ import './chunk-CRB3POP4.js';
25
+ import './chunk-VNVZDWO3.js';
26
+ import './chunk-Z5GEFMRW.js';
27
+
28
+ createRequire(import.meta.url);
@@ -0,0 +1,25 @@
1
+ #!/usr/bin/env node
2
+ import { createRequire } from 'node:module';
3
+ import { sanitizeTerminal, readEffectiveArtifactSpoolState } from './chunk-MQJSXHV7.js';
4
+ import './chunk-N3CUDFCW.js';
5
+ import { cliInvocation } from './chunk-CRB3POP4.js';
6
+ import { homeFromEnv } from './chunk-VNVZDWO3.js';
7
+ import './chunk-Z5GEFMRW.js';
8
+
9
+ createRequire(import.meta.url);
10
+
11
+ // src/artifact-link-cmd.ts
12
+ function lookupArtifactLink(artifactId, home = homeFromEnv()) {
13
+ const shown = sanitizeTerminal(artifactId);
14
+ const entry = readEffectiveArtifactSpoolState(home).state.get(artifactId);
15
+ if (entry === void 0) return { ok: false, reason: `no artifact ${shown} is queued or uploaded on this machine` };
16
+ if (entry.state !== "uploaded") {
17
+ return { ok: false, reason: `artifact ${shown} is ${entry.state}, not uploaded yet \u2014 run \`${cliInvocation()} sync\` first` };
18
+ }
19
+ if (entry.url === void 0) {
20
+ return { ok: false, reason: `artifact ${shown} uploaded, but the gateway returned no link for it` };
21
+ }
22
+ return { ok: true, url: entry.url };
23
+ }
24
+
25
+ export { lookupArtifactLink };
@@ -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-09T18:11:10.939Z+a3c670b","root":"/home/runner/_work/outerlayer/outerlayer","pkg":"outerlayer"}
1
+ {"id":"2026-10-13T00:18:48.455Z+4c5e4a3","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-S26M3YBJ.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-BMYGAGBJ.js';
7
+ import './chunk-N3CUDFCW.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,72 +1,75 @@
1
1
  #!/usr/bin/env node
2
2
  import { createRequire } from 'node:module';
3
- import { probeCredentials } from './chunk-EHAAPRWY.js';
4
- import './chunk-R4ZQQ7U2.js';
5
- import './chunk-O765JR4W.js';
6
- import './chunk-UGP76IZ5.js';
3
+ import { probeCredentials } from './chunk-QQM2UJ6T.js';
7
4
  import './chunk-3JVYMD4G.js';
8
- import './chunk-YETTWTAY.js';
9
- import './chunk-RMWLBUCJ.js';
10
5
  import './chunk-I2UOAV7X.js';
11
- import './chunk-HYZRY4KZ.js';
12
- import './chunk-ADJRGVZC.js';
13
- import './chunk-ONRCEE6D.js';
14
- import './chunk-U3HXXRTY.js';
15
- import './chunk-HHJDN6AU.js';
16
- import { placeholderQueues, placeholderProblem, permissionModeProblems, permissionModeProblem, needsClaudeCredential, claudeCredentialCheck, claimPermissionProblem, formerSkillQueues, formerSkillProblem } from './chunk-FWX735W7.js';
6
+ import './chunk-YETTWTAY.js';
7
+ import './chunk-3OFZM4X6.js';
8
+ import './chunk-SADUQSO4.js';
9
+ import './chunk-KDVIU23F.js';
10
+ import './chunk-MTP2QEVO.js';
11
+ import './chunk-S5PCA3SN.js';
12
+ import './chunk-UYOYTKE2.js';
13
+ import './chunk-XNGMQ27R.js';
14
+ import { placeholderQueues, placeholderProblem, permissionModeProblems, permissionModeProblem, needsClaudeCredential, claudeCredentialCheck, claimPermissionProblem, formerSkillQueues, formerSkillProblem } from './chunk-FUB7LRUE.js';
15
+ import './chunk-AMFWYESY.js';
17
16
  import './chunk-YYYRBYWG.js';
18
- import './chunk-TI7B7UEO.js';
17
+ import './chunk-GOBOJMOJ.js';
18
+ import './chunk-JF42URPU.js';
19
+ import './chunk-ESVW4MJY.js';
19
20
  import './chunk-TBT347UY.js';
20
- import './chunk-CA3XMYFN.js';
21
- import './chunk-RSPXEVDX.js';
22
- import './chunk-EWORLJMO.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-NL4XPC7W.js';
22
+ import './chunk-Y5MXET4M.js';
23
+ import './chunk-L2USZT6T.js';
24
+ import './chunk-S26M3YBJ.js';
25
+ import './chunk-GGOMT6KF.js';
26
+ import './chunk-X5H2WYMB.js';
27
+ import './chunk-6CHIYEZZ.js';
28
+ import './chunk-SWCTGCSC.js';
29
+ import './chunk-WGAUY5LE.js';
28
30
  import { parseRunnerEnv } from './chunk-6MZSX4M7.js';
29
- import './chunk-6QRMLNH3.js';
30
- import './chunk-NPQWKGUA.js';
31
+ import './chunk-KK4VBOH6.js';
32
+ import './chunk-WBT5X3NH.js';
31
33
  import './chunk-IP7BIBXJ.js';
32
- import { loadRunnerConfig, RunnerConfigError, checkHooks } from './chunk-22RMZBGW.js';
34
+ import './chunk-S76AYB4M.js';
35
+ import './chunk-MQJSXHV7.js';
36
+ import { loadRunnerConfig, RunnerConfigError, checkHooks } from './chunk-XGP3VYOG.js';
33
37
  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
38
  import './chunk-7RQ73DLO.js';
40
- import './chunk-4Q7OMKAT.js';
39
+ import './chunk-3CWUOFV6.js';
40
+ import './chunk-QOZKPLVJ.js';
41
41
  import './chunk-CX22S5BA.js';
42
+ import './chunk-ECODQ7II.js';
43
+ import './chunk-JBJTC3UU.js';
44
+ import './chunk-OGFGZQA3.js';
42
45
  import './chunk-F7CU5ABH.js';
43
46
  import './chunk-BKO6JEZI.js';
44
- import './chunk-YDSMROTB.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';
47
+ import './chunk-BMYGAGBJ.js';
48
+ import './chunk-NACDBXUG.js';
49
+ import './chunk-GOSFARMS.js';
50
+ import './chunk-ANPNT6PM.js';
51
+ import './chunk-T2Q7ISPE.js';
51
52
  import './chunk-WZBHBH3J.js';
52
53
  import './chunk-5QRP3MVS.js';
53
- import './chunk-JEHRUE3F.js';
54
- import './chunk-WTE3MQML.js';
55
- import './chunk-C5Y5ZJJZ.js';
56
- 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';
54
+ import './chunk-W5VPCONO.js';
55
+ import './chunk-LXAY4A2G.js';
56
+ import './chunk-T4Y6SCG3.js';
57
+ import './chunk-ZRL6HKBY.js';
58
+ import './chunk-52NC2FIU.js';
59
+ import './chunk-MNTSH4CL.js';
60
+ import './chunk-X5N6KC6V.js';
61
+ import './chunk-N3CUDFCW.js';
63
62
  import './chunk-EABW6AJQ.js';
64
63
  import './chunk-RA3O54FC.js';
65
- import './chunk-V7NIVOFD.js';
66
- import './chunk-ZM2IMMYN.js';
67
- import './chunk-6JNURGOA.js';
64
+ import './chunk-M2ZWWTWZ.js';
65
+ import './chunk-N2PXGQRL.js';
66
+ import './chunk-BQXFBI7T.js';
67
+ import './chunk-ULSIJGLS.js';
68
+ import './chunk-G2YNJIGF.js';
68
69
  import './chunk-XCCLFVXM.js';
70
+ import './chunk-ZJSNLVCE.js';
69
71
  import './chunk-BJ3KTMDH.js';
72
+ import './chunk-CRB3POP4.js';
70
73
  import './chunk-VNVZDWO3.js';
71
74
  import './chunk-Z5GEFMRW.js';
72
75
  import { readFileSync } from 'fs';
@@ -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);