@amsterdamdatalabs/adl-agent-control-linux-x64 0.1.10 → 0.1.26

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 (413) hide show
  1. package/bin/adl +2 -2
  2. package/bin/git +17 -18
  3. package/bin/git-ado +27 -26
  4. package/bin/git-defaults +173 -0
  5. package/bin/health-check +8 -1
  6. package/bin/kimi-hooks +1 -1
  7. package/bin/lib/adl-package.sh +257 -0
  8. package/bin/lib/bun-compile-build.sh +1 -1
  9. package/bin/lib/fleet-host.sh +62 -0
  10. package/bin/lib/lean-ctx-observe-hooks.sh +9 -9
  11. package/bin/reconcile +693 -470
  12. package/bin/reconcile-host +121 -0
  13. package/bin/setup +219 -0
  14. package/bin/shell-path-policy +15 -5
  15. package/bin/sync-and-reconcile +79 -68
  16. package/bin/validate-manifest +46 -51
  17. package/package.json +2 -12
  18. package/packages/agent-hooks/README.md +14 -9
  19. package/packages/agent-hooks/assets/hooks.json +0 -20
  20. package/packages/agent-hooks/package.json +1 -2
  21. package/packages/agent-hooks/src/cli/args.ts +1 -1
  22. package/packages/agent-hooks/src/cli/doctor.ts +6 -58
  23. package/packages/agent-hooks/src/deploy/deploy.ts +12 -41
  24. package/packages/agent-hooks/src/deploy/doctor.ts +8 -11
  25. package/packages/agent-hooks/src/deploy/index.ts +14 -14
  26. package/packages/agent-hooks/src/deploy/json-merge.ts +7 -8
  27. package/packages/agent-hooks/src/deploy/legacy-lock.ts +2 -2
  28. package/packages/agent-hooks/src/deploy/list.ts +1 -1
  29. package/packages/agent-hooks/src/deploy/lock.ts +31 -12
  30. package/packages/agent-hooks/src/deploy/profile.ts +3 -4
  31. package/packages/agent-hooks/src/deploy/source.ts +1 -1
  32. package/packages/agent-hooks/src/gating/config.ts +10 -24
  33. package/packages/agent-hooks/src/gating/root.ts +3 -3
  34. package/packages/agent-hooks/src/hooks/briefing/handler.ts +2 -2
  35. package/packages/agent-hooks/src/hooks/briefing/index.ts +27 -287
  36. package/packages/agent-hooks/src/hooks/contract.ts +17 -17
  37. package/packages/agent-hooks/src/hooks/crg/background.ts +27 -0
  38. package/packages/agent-hooks/src/hooks/crg/index.ts +7 -6
  39. package/packages/agent-hooks/src/hooks/log.ts +8 -10
  40. package/packages/agent-hooks/src/hooks/user-prompt-submit/match.ts +1 -1
  41. package/packages/agent-hooks/src/hooks/user-prompt-submit/tokenizer.ts +1 -1
  42. package/packages/agent-hooks/src/hooks/user-prompt-submit/vector.ts +6 -5
  43. package/packages/agent-hooks/src/hosts/registry.ts +1 -1
  44. package/packages/agent-hooks/src/log/adl-dir.ts +44 -0
  45. package/packages/agent-hooks/src/log/writer.ts +16 -2
  46. package/packages/agent-hooks/src/main.ts +7 -41
  47. package/packages/agent-hooks/src/pre-tool-use/handler.ts +71 -82
  48. package/packages/agent-hooks/src/rules/engine.ts +13 -17
  49. package/packages/agent-hooks/src/rules/impl/adl-build-run-denied.ts +3 -3
  50. package/packages/agent-hooks/src/rules/impl/adl-git-credential-bypass.ts +12 -14
  51. package/packages/agent-hooks/src/rules/impl/adl-git-identity.ts +28 -28
  52. package/packages/agent-hooks/src/rules/impl/adl-git-push-policy.ts +8 -9
  53. package/packages/agent-hooks/src/rules/impl/adl-human-azdo-bypass.ts +56 -59
  54. package/packages/agent-hooks/src/rules/impl/adl-identity.ts +16 -23
  55. package/packages/agent-hooks/src/rules/impl/adl-mcp-cli.ts +19 -16
  56. package/packages/agent-hooks/src/rules/impl/adl-pr-complete-pending.ts +2 -2
  57. package/packages/agent-hooks/src/rules/impl/adl-pr-review-role.ts +9 -12
  58. package/packages/agent-hooks/src/rules/impl/adl-pr-role.ts +5 -7
  59. package/packages/agent-hooks/src/rules/impl/adl-run-required.ts +411 -0
  60. package/packages/agent-hooks/src/rules/impl/adl-token-minting-role.ts +4 -5
  61. package/packages/agent-hooks/src/rules/impl/adl-workitem-role.ts +6 -7
  62. package/packages/agent-hooks/src/rules/impl/agent-tamper-protection.ts +77 -70
  63. package/packages/agent-hooks/src/rules/impl/deny-allowlist.ts +159 -0
  64. package/packages/agent-hooks/src/rules/impl/fs-trash.ts +2 -2
  65. package/packages/agent-hooks/src/rules/impl/git-helpers.ts +21 -23
  66. package/packages/agent-hooks/src/rules/impl/git-worktree.ts +14 -28
  67. package/packages/agent-hooks/src/rules/impl/mcp-tool.ts +3 -3
  68. package/packages/agent-hooks/src/rules/impl/messages.ts +1 -1
  69. package/packages/agent-hooks/src/rules/impl/pr-completion.ts +1 -1
  70. package/packages/agent-hooks/src/rules/impl/tool-azure-devops.ts +35 -46
  71. package/packages/agent-hooks/src/rules/impl/tool-find.ts +34 -7
  72. package/packages/agent-hooks/src/rules/impl/tool-grep.ts +34 -12
  73. package/packages/agent-hooks/src/rules/impl/tool-native-search.ts +52 -9
  74. package/packages/agent-hooks/src/rules/ports.ts +5 -7
  75. package/packages/agent-hooks/src/rules/registry.ts +4 -6
  76. package/packages/agent-hooks/src/rules/shell/git-identity.ts +86 -101
  77. package/packages/agent-hooks/src/rules/shell/git-internals.ts +1 -1
  78. package/packages/agent-hooks/src/rules/shell/invocation.ts +24 -118
  79. package/packages/agent-hooks/src/rules/shell/piped-script.ts +1 -1
  80. package/packages/agent-hooks/src/rules/shell/prefixes.ts +1 -1
  81. package/packages/agent-hooks/src/rules/shell/protected-carrier-writes.ts +11 -10
  82. package/packages/agent-hooks/src/rules/shell/protected-carriers.ts +26 -26
  83. package/packages/agent-hooks/src/rules/shell/substitution.ts +1 -1
  84. package/packages/agent-hooks/src/rules/shell/xargs.ts +2 -2
  85. package/packages/agent-hooks/src/rules/spec.ts +2 -2
  86. package/packages/agent-settings/README.md +287 -0
  87. package/packages/agent-settings/assets/GLOBAL-AGENTS.md +18 -18
  88. package/packages/agent-settings/assets/agents/architect.toml +2 -0
  89. package/packages/agent-settings/assets/agents/code-reviewer.toml +2 -0
  90. package/packages/agent-settings/assets/agents/executor.toml +2 -0
  91. package/packages/agent-settings/assets/agents/explore.toml +2 -0
  92. package/packages/agent-settings/assets/agents/planner.toml +2 -0
  93. package/packages/agent-settings/assets/mcp.json +0 -4
  94. package/packages/agent-settings/deploy-profile.json +139 -0
  95. package/packages/agent-settings/deploy-profile.schema.json +147 -0
  96. package/packages/agent-settings/managed/README.md +29 -30
  97. package/packages/agent-settings/managed/codex-requirements.toml +9 -9
  98. package/packages/agent-settings/package.json +20 -0
  99. package/packages/agent-settings/src/deploy/agent-render.ts +2 -2
  100. package/packages/agent-settings/src/deploy/agent-toml.ts +1 -1
  101. package/packages/agent-settings/src/deploy/agents-deploy.ts +17 -3
  102. package/packages/agent-settings/src/deploy/doctor.ts +3 -83
  103. package/packages/agent-settings/src/deploy/index.ts +6 -33
  104. package/packages/agent-settings/src/deploy/legacy-lock.ts +6 -6
  105. package/packages/agent-settings/src/deploy/list.ts +5 -19
  106. package/packages/agent-settings/src/deploy/lock.ts +8 -18
  107. package/packages/agent-settings/src/deploy/mcp-deploy.ts +10 -5
  108. package/packages/agent-settings/src/deploy/mcp-merge.ts +22 -3
  109. package/packages/agent-settings/src/deploy/mcp-source.ts +1 -1
  110. package/packages/agent-settings/src/deploy/profile.ts +11 -35
  111. package/packages/agent-settings/src/deploy/retire-lean-ctx.ts +184 -0
  112. package/packages/agent-settings/src/deploy/settings.ts +17 -22
  113. package/packages/agent-settings/src/global-surface-sweep.ts +5 -17
  114. package/packages/agent-settings/src/kimi-hooks.ts +339 -192
  115. package/packages/agent-settings/src/opencode-hooks.ts +1 -1
  116. package/packages/agent-settings/tsconfig.json +23 -0
  117. package/packages/agent-skills/assets/agent-search-tool-audit/SKILL.md +120 -0
  118. package/packages/agent-skills/assets/agent-search-tool-audit/scripts/audit.sh +276 -0
  119. package/packages/agent-skills/assets/debug-issue/SKILL.md +8 -7
  120. package/packages/agent-skills/assets/explore-codebase/SKILL.md +13 -13
  121. package/packages/agent-skills/assets/refactor-safely/SKILL.md +12 -11
  122. package/packages/agent-skills/assets/review-changes/SKILL.md +7 -7
  123. package/packages/agent-skills/assets/testing/references/e2e.md +37 -0
  124. package/packages/agent-skills/assets/testing/references/integration.md +37 -0
  125. package/packages/agent-skills/assets/testing/references/unit.md +32 -0
  126. package/packages/agent-skills/dist/deploy.d.ts +2 -0
  127. package/packages/agent-skills/dist/deploy.d.ts.map +1 -1
  128. package/packages/agent-skills/dist/deploy.js +27 -1
  129. package/packages/agent-skills/dist/deploy.js.map +1 -1
  130. package/packages/agent-skills/dist/doctor.d.ts +0 -2
  131. package/packages/agent-skills/dist/doctor.d.ts.map +1 -1
  132. package/packages/agent-skills/dist/doctor.js +1 -1
  133. package/packages/agent-skills/dist/doctor.js.map +1 -1
  134. package/packages/agent-skills/dist/fs-utils.d.ts +0 -1
  135. package/packages/agent-skills/dist/fs-utils.d.ts.map +1 -1
  136. package/packages/agent-skills/dist/fs-utils.js +1 -1
  137. package/packages/agent-skills/dist/fs-utils.js.map +1 -1
  138. package/packages/agent-skills/package.json +1 -1
  139. package/packages/agent-skills/src/deploy.ts +29 -1
  140. package/packages/agent-skills/src/doctor.ts +1 -1
  141. package/packages/agent-skills/src/fs-utils.ts +1 -1
  142. package/packages/azdo/README.md +10 -9
  143. package/packages/azdo/package.json +2 -5
  144. package/packages/azdo/src/azdo-pl.ts +2 -54
  145. package/packages/azdo/src/azdo-pr-review.ts +4 -4
  146. package/packages/azdo/src/azdo-repo-doctor.ts +101 -52
  147. package/packages/azdo/src/azdo-wit.ts +41 -2
  148. package/packages/azdo/src/lib/agent-identity.ts +12 -14
  149. package/packages/azdo/src/lib/agent-token.ts +2 -2
  150. package/packages/azdo/src/lib/client.ts +0 -5
  151. package/packages/azdo/src/lib/graph-identity.ts +4 -4
  152. package/packages/azdo/src/workflow-rules-matrix.csv +85 -0
  153. package/packages/azdo/src/workflow-rules.ts +128 -0
  154. package/packages/fleet/manifest.json +38 -33
  155. package/packages/fleet/manifest.schema.json +67 -65
  156. package/packages/fleet/src/default-agent-file.ts +3 -3
  157. package/packages/fleet/src/install-adl-package.ts +73 -15
  158. package/packages/fleet/src/install-embedding-model.ts +14 -14
  159. package/packages/fleet/src/managed-settings.ts +7 -7
  160. package/packages/fleet/src/repo-maintain.ts +439 -0
  161. package/packages/fleet/src/scratch-reap.ts +6 -6
  162. package/packages/fleet/src/vscode-settings.ts +5 -5
  163. package/packages/repo-controls/README.md +141 -43
  164. package/packages/repo-controls/assets/promote.ts +4 -4
  165. package/packages/repo-controls/assets/reconcile-guard.sh +10 -46
  166. package/packages/repo-controls/assets/version-bump.sh +31 -84
  167. package/packages/repo-controls/bunfig.toml +6 -7
  168. package/packages/repo-controls/package/adl-compliance/AGENTS.md +5 -6
  169. package/packages/repo-controls/package/adl-compliance/conftest.py +3 -1
  170. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/__init__.py +4 -16
  171. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/adr_lifecycle/test_check.py +9 -2
  172. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/closed_set/fixtures/docs/notes.md +1 -0
  173. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/closed_set/test_check.py +11 -0
  174. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/directory_size/__init__.py +7 -0
  175. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/directory_size/check.py +266 -0
  176. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/directory_size/fixtures/docs-size-assets/docs/guide/.drafts/c.md +1 -0
  177. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/directory_size/fixtures/docs-size-assets/docs/guide/.drafts/d.md +1 -0
  178. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/directory_size/fixtures/docs-size-assets/docs/guide/.hidden.md +1 -0
  179. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/directory_size/fixtures/docs-size-assets/docs/guide/a.md +1 -0
  180. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/directory_size/fixtures/docs-size-assets/docs/guide/b.md +1 -0
  181. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/directory_size/fixtures/docs-size-assets/docs/guide/data.csv +1 -0
  182. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/directory_size/fixtures/docs-size-assets/docs/guide/diagram.png +1 -0
  183. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/directory_size/fixtures/docs-size-assets/docs/guide/mock.png +1 -0
  184. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/directory_size/fixtures/docs-size-assets/docs/guide/sub/nested.md +1 -0
  185. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/directory_size/fixtures/docs-size-assets/docs.md +1 -0
  186. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/directory_size/fixtures/docs-size-assets/grants.yaml +4 -0
  187. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/directory_size/fixtures/docs-size-assets/plans/deep/p1.md +1 -0
  188. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/directory_size/fixtures/docs-size-assets/plans/deep/p2.md +1 -0
  189. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/directory_size/fixtures/docs-size-assets/plans/deep/p3.md +1 -0
  190. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/directory_size/fixtures/docs-size-assets/plans/deep/p4.md +1 -0
  191. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/directory_size/fixtures/docs-size-assets/plans/deep/p5.md +1 -0
  192. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/directory_size/fixtures/docs-size-clean/docs/architecture/build.md +1 -0
  193. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/directory_size/fixtures/docs-size-clean/docs/architecture/overview.md +1 -0
  194. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/directory_size/fixtures/docs-size-clean/docs/guide/install.md +1 -0
  195. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/directory_size/fixtures/docs-size-clean/docs/guide/start.md +1 -0
  196. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/directory_size/fixtures/docs-size-clean/grants.yaml +4 -0
  197. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/directory_size/fixtures/docs-size-decisions/docs/decisions/adr-001-tenancy.md +1 -0
  198. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/directory_size/fixtures/docs-size-decisions/docs/decisions/adr-002-carrier.md +1 -0
  199. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/directory_size/fixtures/docs-size-decisions/docs/decisions/adr-003-cli.md +1 -0
  200. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/directory_size/fixtures/docs-size-decisions/docs/decisions/archive/adr-000-old.md +1 -0
  201. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/directory_size/fixtures/docs-size-decisions/docs/decisions/archive/adr-000-older.md +1 -0
  202. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/directory_size/fixtures/docs-size-decisions/docs/guide/a.md +1 -0
  203. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/directory_size/fixtures/docs-size-decisions/docs/guide/b.md +1 -0
  204. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/directory_size/fixtures/docs-size-decisions/docs/guide/c.md +1 -0
  205. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/directory_size/fixtures/docs-size-decisions/grants.yaml +4 -0
  206. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/directory_size/fixtures/docs-size-default-cap/docs/guide/page-01.md +1 -0
  207. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/directory_size/fixtures/docs-size-default-cap/docs/guide/page-02.md +1 -0
  208. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/directory_size/fixtures/docs-size-default-cap/docs/guide/page-03.md +1 -0
  209. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/directory_size/fixtures/docs-size-default-cap/docs/guide/page-04.md +1 -0
  210. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/directory_size/fixtures/docs-size-default-cap/docs/guide/page-05.md +1 -0
  211. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/directory_size/fixtures/docs-size-default-cap/docs/guide/page-06.md +1 -0
  212. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/directory_size/fixtures/docs-size-default-cap/docs/guide/page-07.md +1 -0
  213. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/directory_size/fixtures/docs-size-default-cap/docs/guide/page-08.md +1 -0
  214. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/directory_size/fixtures/docs-size-default-cap/docs/guide/page-09.md +1 -0
  215. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/directory_size/fixtures/docs-size-default-cap/docs/guide/page-10.md +1 -0
  216. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/directory_size/fixtures/docs-size-default-cap/docs/guide/page-11.md +1 -0
  217. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/directory_size/fixtures/docs-size-default-cap/docs/guide/page-12.md +1 -0
  218. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/directory_size/fixtures/docs-size-default-cap/docs/guide/page-13.md +1 -0
  219. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/directory_size/fixtures/docs-size-default-cap/docs/guide/page-14.md +1 -0
  220. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/directory_size/fixtures/docs-size-default-cap/docs/guide/page-15.md +1 -0
  221. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/directory_size/fixtures/docs-size-default-cap/docs/guide/page-16.md +1 -0
  222. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/directory_size/fixtures/docs-size-default-cap/grants.yaml +4 -0
  223. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/directory_size/fixtures/docs-size-exempt/docs/guide/a.md +1 -0
  224. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/directory_size/fixtures/docs-size-exempt/docs/guide/b.md +1 -0
  225. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/directory_size/fixtures/docs-size-exempt/docs/guide/c.md +1 -0
  226. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/directory_size/fixtures/docs-size-exempt/grants.yaml +4 -0
  227. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/directory_size/fixtures/docs-size-grew/docs/guide/a.md +1 -0
  228. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/directory_size/fixtures/docs-size-grew/docs/guide/b.md +1 -0
  229. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/directory_size/fixtures/docs-size-grew/docs/guide/c.md +1 -0
  230. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/directory_size/fixtures/docs-size-grew/grants.yaml +4 -0
  231. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/directory_size/fixtures/docs-size-no-section/docs/guide/a.md +1 -0
  232. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/directory_size/fixtures/docs-size-no-section/docs/guide/b.md +1 -0
  233. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/directory_size/fixtures/docs-size-no-section/docs/guide/c.md +1 -0
  234. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/directory_size/fixtures/docs-size-no-section/docs/guide/d.md +1 -0
  235. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/directory_size/fixtures/docs-size-no-section/docs/guide/e.md +1 -0
  236. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/directory_size/fixtures/docs-size-no-section/grants.yaml +4 -0
  237. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/directory_size/fixtures/docs-size-over-cap/docs/guide/a.md +1 -0
  238. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/directory_size/fixtures/docs-size-over-cap/docs/guide/b.md +1 -0
  239. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/directory_size/fixtures/docs-size-over-cap/docs/guide/c.md +1 -0
  240. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/directory_size/fixtures/docs-size-over-cap/docs/short/only.md +1 -0
  241. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/directory_size/fixtures/docs-size-over-cap/grants.yaml +4 -0
  242. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/directory_size/fixtures/docs-size-stale/docs/held/a.md +1 -0
  243. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/directory_size/fixtures/docs-size-stale/docs/held/b.md +1 -0
  244. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/directory_size/fixtures/docs-size-stale/docs/held/c.md +1 -0
  245. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/directory_size/fixtures/docs-size-stale/docs/held/d.md +1 -0
  246. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/directory_size/fixtures/docs-size-stale/docs/shrunk/a.md +1 -0
  247. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/directory_size/fixtures/docs-size-stale/docs/shrunk/b.md +1 -0
  248. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/directory_size/fixtures/docs-size-stale/grants.yaml +6 -0
  249. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/directory_size/fixtures/docs-size-unusable/docs/guide/a.md +1 -0
  250. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/directory_size/fixtures/docs-size-unusable/docs/guide/b.md +1 -0
  251. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/directory_size/fixtures/docs-size-unusable/docs/guide/c.md +1 -0
  252. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/directory_size/fixtures/docs-size-unusable/grants.yaml +3 -0
  253. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/directory_size/test_check.py +345 -0
  254. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/english_only/check.py +18 -5
  255. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/english_only/test_check.py +35 -0
  256. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/filename/__init__.py +7 -0
  257. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/filename/check.py +91 -0
  258. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/filename/fixtures/docs/AGENTS.md +3 -0
  259. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/filename/fixtures/docs/CLAUDE.md +3 -0
  260. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/filename/fixtures/docs/architecture/-leading-hyphen.md +3 -0
  261. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/filename/fixtures/docs/architecture/AGENTS.md +3 -0
  262. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/filename/fixtures/docs/architecture/double--hyphen.md +3 -0
  263. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/filename/fixtures/docs/architecture/getting started.md +3 -0
  264. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/filename/fixtures/docs/architecture/nested/page-here.md +3 -0
  265. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/filename/fixtures/docs/architecture/trailing-hyphen-.md +3 -0
  266. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/filename/fixtures/docs/architecture/ui-contract.md +4 -0
  267. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/filename/fixtures/docs/architecture/user_profile.md +3 -0
  268. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/filename/fixtures/docs/assets/UI-CONTRACT.png +2 -0
  269. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/filename/fixtures/docs/assets/diagram.png +1 -0
  270. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/filename/fixtures/docs/index.md +4 -0
  271. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/filename/fixtures/docs/notes.md +3 -0
  272. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/filename/fixtures/docs/reference.html +1 -0
  273. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/filename/fixtures/docs/research/2026-10-experiment.md +3 -0
  274. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/filename/fixtures/docs/research/UI-CONTRACT.md +6 -0
  275. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/filename/fixtures/docs.md +5 -0
  276. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/filename/test_check.py +194 -0
  277. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/indexes/check.py +14 -2
  278. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/indexes/docs-knowledge-indexes.yaml +5 -5
  279. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/indexes/fixtures/knowledge-indexes-shipped-register-legacy/docs/compliance/index.md +10 -0
  280. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/indexes/fixtures/knowledge-indexes-shipped-register-type/docs/compliance/index.md +10 -0
  281. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/indexes/test_check.py +58 -6
  282. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/ledger/test_check.py +0 -1
  283. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/okf_profile/check.py +64 -17
  284. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/okf_profile/docs-okf-profiles.yaml +60 -9
  285. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/okf_profile/fixtures/okf-ledger-clean/docs/notes.md +17 -0
  286. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/okf_profile/fixtures/okf-ledger-no-lines/docs/notes.md +13 -0
  287. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/okf_profile/fixtures/okf-profile-clean/docs/architecture/demo.md +0 -1
  288. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/okf_profile/fixtures/okf-profile-clean/docs-okf-profiles.yaml +0 -1
  289. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/okf_profile/fixtures/okf-profile-untyped/docs/research/no-type.md +9 -0
  290. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/okf_profile/fixtures/okf-profile-untyped/docs/research/typed-experiment.md +10 -0
  291. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/okf_profile/fixtures/okf-profile-untyped/docs/research/typed-research.md +10 -0
  292. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/okf_profile/fixtures/okf-profile-untyped/docs-okf-profiles.yaml +5 -0
  293. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/okf_profile/fixtures/okf-profile-violation/docs/architecture/bad-status.md +0 -1
  294. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/okf_profile/fixtures/okf-profile-violation/docs-okf-profiles.yaml +0 -1
  295. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/okf_profile/fixtures/okf-research/docs/research/experiment-ok.md +15 -0
  296. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/okf_profile/fixtures/okf-research/docs/research/extra-key.md +15 -0
  297. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/okf_profile/fixtures/okf-research/docs/research/extra-keys.md +16 -0
  298. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/okf_profile/fixtures/okf-research/docs/research/missing-created.md +11 -0
  299. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/okf_profile/fixtures/okf-research/docs/research/missing-description.md +11 -0
  300. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/okf_profile/fixtures/okf-research/docs/research/missing-last-update.md +11 -0
  301. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/okf_profile/fixtures/okf-research/docs/research/missing-sources.md +10 -0
  302. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/okf_profile/fixtures/okf-research/docs/research/missing-status.md +11 -0
  303. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/okf_profile/fixtures/okf-research/docs/research/missing-title.md +11 -0
  304. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/okf_profile/fixtures/okf-research/docs/research/optional-keys.md +16 -0
  305. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/okf_profile/fixtures/okf-research/docs/research/research-ok.md +14 -0
  306. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/okf_profile/fixtures/okf-research-neighbours/docs/architecture/created-date.md +21 -0
  307. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/okf_profile/fixtures/okf-research-neighbours/docs/architecture/research-shaped.md +19 -0
  308. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/okf_profile/fixtures/okf-research-neighbours/docs/operations/runbook.md +26 -0
  309. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/okf_profile/test_check.py +228 -2
  310. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/register.py +5 -0
  311. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/semantics/test_check.py +11 -2
  312. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/directory_ratchet/__init__.py +5 -3
  313. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/directory_ratchet/check.py +160 -44
  314. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/directory_ratchet/fixtures/directory-ratchet-clean/grants.yaml +0 -2
  315. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/directory_ratchet/fixtures/directory-ratchet-empty-register/frontend/packages/desktop/src/a.ts +1 -0
  316. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/directory_ratchet/fixtures/directory-ratchet-empty-register/frontend/packages/desktop/src/b.ts +1 -0
  317. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/directory_ratchet/fixtures/directory-ratchet-empty-register/grants.yaml +3 -0
  318. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/directory_ratchet/fixtures/directory-ratchet-excluded/grants.yaml +0 -2
  319. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/directory_ratchet/fixtures/directory-ratchet-missing-tree/frontend/packages/desktop/src/renderer/solo/item.ts +1 -0
  320. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/directory_ratchet/fixtures/directory-ratchet-missing-tree/grants.yaml +5 -0
  321. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/directory_ratchet/fixtures/directory-ratchet-no-ceilings/frontend/packages/desktop/src/a.ts +1 -0
  322. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/directory_ratchet/fixtures/directory-ratchet-no-ceilings/frontend/packages/desktop/src/b.ts +1 -0
  323. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/directory_ratchet/fixtures/directory-ratchet-no-ceilings/frontend/packages/desktop/src/c.ts +1 -0
  324. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/directory_ratchet/fixtures/directory-ratchet-no-ceilings/grants.yaml +4 -0
  325. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/directory_ratchet/fixtures/directory-ratchet-stale-entry/frontend/packages/desktop/src/renderer/solo/item.ts +1 -0
  326. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/directory_ratchet/fixtures/directory-ratchet-stale-entry/grants.yaml +5 -0
  327. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/directory_ratchet/fixtures/directory-ratchet-unbaselined/grants.yaml +0 -2
  328. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/directory_ratchet/fixtures/directory-ratchet-unusable-register/frontend/packages/desktop/src/renderer/solo/item.ts +1 -0
  329. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/directory_ratchet/fixtures/directory-ratchet-unusable-register/grants.yaml +3 -0
  330. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/directory_ratchet/register.py +127 -0
  331. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/directory_ratchet/test_check.py +265 -29
  332. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/markdown_trees/check.py +75 -2
  333. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/markdown_trees/test_check.py +194 -0
  334. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/open_work_cap/check.py +56 -10
  335. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/open_work_cap/test_check.py +256 -0
  336. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/placement/check.py +40 -12
  337. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/placement/test_check.py +108 -0
  338. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/plans_layout/test_check.py +1 -1
  339. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/references/check.py +2 -3
  340. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/root_closed_set/check.py +23 -6
  341. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/root_closed_set/test_check.py +76 -0
  342. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/root_markdown_allowlist/check.py +58 -17
  343. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/root_markdown_allowlist/test_check.py +161 -0
  344. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/scripts_size_depth/check.py +35 -3
  345. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/scripts_size_depth/test_check.py +108 -1
  346. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/lfs/integrity/check.py +281 -61
  347. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/lfs/integrity/fixtures/malformed/absolute-path.yaml +3 -0
  348. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/lfs/integrity/fixtures/malformed/backslash-path.yaml +3 -0
  349. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/lfs/integrity/fixtures/malformed/baseline-is-a-list.yaml +2 -0
  350. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/lfs/integrity/fixtures/malformed/boolean-size.yaml +3 -0
  351. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/lfs/integrity/fixtures/malformed/dot-segment.yaml +3 -0
  352. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/lfs/integrity/fixtures/malformed/duplicate-path.yaml +4 -0
  353. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/lfs/integrity/fixtures/malformed/empty-file.yaml +0 -0
  354. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/lfs/integrity/fixtures/malformed/empty-segment.yaml +3 -0
  355. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/lfs/integrity/fixtures/malformed/float-size.yaml +3 -0
  356. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/lfs/integrity/fixtures/malformed/negative-size.yaml +3 -0
  357. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/lfs/integrity/fixtures/malformed/no-over-limit-baseline.yaml +1 -0
  358. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/lfs/integrity/fixtures/malformed/no-schema-version.yaml +1 -0
  359. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/lfs/integrity/fixtures/malformed/non-string-path.yaml +3 -0
  360. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/lfs/integrity/fixtures/malformed/not-yaml.yaml +2 -0
  361. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/lfs/integrity/fixtures/malformed/null-size.yaml +3 -0
  362. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/lfs/integrity/fixtures/malformed/parent-segment.yaml +3 -0
  363. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/lfs/integrity/fixtures/malformed/string-size.yaml +3 -0
  364. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/lfs/integrity/fixtures/malformed/top-level-is-a-list.yaml +2 -0
  365. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/lfs/integrity/fixtures/malformed/trailing-slash.yaml +3 -0
  366. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/lfs/integrity/fixtures/malformed/unknown-key.yaml +3 -0
  367. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/lfs/integrity/fixtures/malformed/wrong-schema-version.yaml +2 -0
  368. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/lfs/integrity/fixtures/malformed/zero-size.yaml +3 -0
  369. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/lfs/integrity/fixtures/valid.yaml +4 -0
  370. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/lfs/integrity/test_check.py +758 -45
  371. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/repository/ignored_paths/check.py +4 -3
  372. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/repository/ignored_paths/test_check.py +2 -2
  373. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/repository/lockfile_integrity/cargo_lock.py +490 -0
  374. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/repository/lockfile_integrity/check.py +368 -55
  375. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/repository/lockfile_integrity/test_check.py +4 -2
  376. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/repository/lockfile_integrity/test_declared_paths.py +523 -0
  377. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/repository/lockfile_integrity/test_stale_lock.py +1179 -0
  378. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/cli.py +122 -3
  379. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/core/check.py +33 -1
  380. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/core/constants.py +0 -11
  381. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/core/context.py +16 -1
  382. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/policy/config.py +1 -2
  383. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/policy/docs_shape.py +12 -1
  384. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/policy/gate-registry.alt.yaml +133 -147
  385. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/policy/gate-registry.yaml +133 -147
  386. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/policy/registry.py +1 -1
  387. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/policy/root_shape.py +52 -10
  388. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/runner.py +23 -4
  389. package/packages/repo-controls/package.json +2 -10
  390. package/packages/repo-controls/scripts/hooks/post-merge +13 -0
  391. package/packages/repo-controls/scripts/hooks/pre-push +3 -3
  392. package/packages/repo-controls/src/azdo-push.ts +64 -36
  393. package/packages/repo-controls/src/azure-devops-governance.ts +7 -9
  394. package/packages/repo-controls/src/azure-devops-remote.ts +1 -1
  395. package/packages/repo-controls/src/cli.ts +29 -8
  396. package/packages/repo-controls/src/compliance-config.ts +410 -2
  397. package/packages/repo-controls/src/config.ts +133 -60
  398. package/packages/repo-controls/src/constants.ts +13 -27
  399. package/packages/repo-controls/src/coverage-ratchet.ts +4 -4
  400. package/packages/repo-controls/src/doctor.ts +46 -19
  401. package/packages/repo-controls/src/gitignore-block.ts +5 -29
  402. package/packages/repo-controls/src/hook-log.ts +1 -2
  403. package/packages/repo-controls/src/hook.ts +201 -45
  404. package/packages/repo-controls/src/install.ts +14 -8
  405. package/packages/repo-controls/src/integrity.ts +3 -3
  406. package/packages/repo-controls/src/lfs.ts +111 -0
  407. package/packages/repo-controls/src/promote.ts +8 -8
  408. package/packages/repo-controls/src/runtime.ts +9 -1
  409. package/packages/repo-controls/src/workflows-drift.ts +4 -5
  410. package/packages/repo-controls/src/worktree-guard.ts +3 -3
  411. package/packages/fleet/jobs/git-maintain/src/main.ts +0 -848
  412. package/packages/fleet/systemd/adl-azure-agent-update.service +0 -13
  413. package/packages/fleet/systemd/adl-azure-agent-update.timer +0 -10
package/bin/reconcile CHANGED
@@ -44,7 +44,7 @@ if [[ -z "${RECONCILE_REEXEC_BASH:-}" ]]; then
44
44
  export RECONCILE_REEXEC_BASH=1
45
45
  exec "$reconcile_newer_bash" "$0" "$@"
46
46
  fi
47
- echo "bin/reconcile requires bash >= 4.4 (this run is bash ${BASH_VERSION:-unknown} -- empty-array expansions are unsafe under set -u before 4.4). No newer bash was found at: $reconcile_bash_candidates. Install one (e.g. 'brew install bash') and re-run, or put a newer bash earlier on PATH -- refusing to start rather than risk an 'unbound variable' abort mid-run after state has already changed." >&2
47
+ echo "ERROR bin/reconcile requires bash >= 4.4 (this run is bash ${BASH_VERSION:-unknown} -- empty-array expansions are unsafe under set -u before 4.4). No newer bash was found at: $reconcile_bash_candidates. Install one (e.g. 'brew install bash') and re-run, or put a newer bash earlier on PATH -- refusing to start rather than risk an 'unbound variable' abort mid-run after state has already changed." >&2
48
48
  exit 1
49
49
  fi
50
50
  fi
@@ -54,7 +54,7 @@ set -euo pipefail
54
54
  export PATH="/usr/local/bin:/opt/homebrew/bin:/home/linuxbrew/.linuxbrew/bin:/usr/bin:/bin:/usr/sbin:/sbin"
55
55
 
56
56
  usage() {
57
- echo "usage: reconcile --device NAME [--source npm|checkout] [--mode audit|plan|apply] [--manifest PATH] [--state-dir PATH] [--platform mac|linux]" >&2
57
+ echo "ERROR usage: reconcile --device NAME [--source npm|checkout] [--mode audit|plan|apply] [--manifest PATH] [--state-dir PATH] [--platform mac|linux]" >&2
58
58
  exit 2
59
59
  }
60
60
 
@@ -68,8 +68,9 @@ source_mode="npm"
68
68
  # remove_one_repo_dir themselves, so packages/fleet/tests/test-manifest.sh
69
69
  # can point it at a disposable test_root directory (RECONCILE_SOURCE_ONLY=1)
70
70
  # and exercise the real sync/removal mechanics without root or a real
71
- # /usr/local. Matches packages/fleet/src/launch-agents.ts's
72
- # DEFAULT_LIBEXEC_DIR in production.
71
+ # /usr/local. The production value is the libexec dir the apps/cli
72
+ # commands (repo-controls, skills, hooks, settings) resolve the projected
73
+ # payload from.
73
74
  repo_dir_libexec_root="/usr/local/libexec/adl"
74
75
  # Test-only escape hatch for default-agent-file.ts / managed-settings.ts's own
75
76
  # `--root` (both already accept it; see their headers). Unset in every real
@@ -109,9 +110,7 @@ failed_tools=()
109
110
  # block at the very end, just before "global-tools: reconciliation complete"
110
111
  # (Tarun, 2026-09-28). Collected from every step that backs something up
111
112
  # before overwriting/removing a file: kimi-hooks (parsed from its own
112
- # captured "backed up previous config to <path>" note), lean-ctx-observe-strip
113
- # (2026-09-29, STATUS.md 12p item 13 final scope -- same note shape, parsed
114
- # by collect_lean_ctx_observe_strip_backups), the global surface sweep
113
+ # captured "backed up previous config to <path>" note), the global surface sweep
115
114
  # (parsed from ITS OWN already-printed "global-surface-sweep: backups
116
115
  # this run:" block -- never recomputed here, per Tarun's instruction not to
117
116
  # duplicate that package's backup logic), and write_one_repo_script below
@@ -156,39 +155,6 @@ collect_kimi_hooks_backups() {
156
155
  done <<<"$text"
157
156
  }
158
157
 
159
- # Same shape as collect_kimi_hooks_backups, for bin/lean-ctx-observe-strip's
160
- # own "lean-ctx-observe-strip: backed up previous config to <path>" note --
161
- # unlike kimi-hooks (always exactly one config file), this step can strip
162
- # multiple JSON/shell-rc targets in one run, so the loop below (like
163
- # collect_sweep_backups) records every match, not just the first.
164
- #
165
- # A skill-directory backup gets a SECOND, distinct line shape: "backed up
166
- # previous skill directory <original> to <backup>" (final review finding
167
- # 1: its backup path is a SIBLING of the scanned skills/ directory, e.g.
168
- # ".codex/skills.pre-lean-ctx-observe-strip-<ts>/lean-ctx" -- it no longer
169
- # ends in ".pre-<label>-<timestamp>", so strip_backup_suffix's regex can't
170
- # recover the original path from it; that note states BOTH paths directly
171
- # instead, and is matched here as its own case rather than forced through
172
- # the single-path regex above).
173
- collect_lean_ctx_observe_strip_backups() {
174
- local text="$1" line backup original
175
- while IFS= read -r line; do
176
- if [[ "$line" =~ ^lean-ctx-observe-strip:\ backed\ up\ previous\ config\ to\ (.+)$ ]]; then
177
- backup="${BASH_REMATCH[1]}"
178
- record_backup "$(strip_backup_suffix "$backup")" "$backup"
179
- elif [[ "$line" =~ ^lean-ctx-observe-strip:\ backed\ up\ previous\ skill\ directory\ (.+)\ to\ (.+)$ ]]; then
180
- original="${BASH_REMATCH[1]}"
181
- backup="${BASH_REMATCH[2]}"
182
- record_backup "$original" "$backup"
183
- elif [[ "$line" =~ ^lean-ctx-observe-strip:\ backed\ up\ previous\ hook\ script\ (.+)\ to\ (.+)$ ]]; then
184
- # ~/.claude/hooks/lean-ctx-* scripts: same two-path shape as a skill
185
- # directory (sibling "hooks.pre-lean-ctx-observe-strip-<ts>/" backup).
186
- original="${BASH_REMATCH[1]}"
187
- backup="${BASH_REMATCH[2]}"
188
- record_backup "$original" "$backup"
189
- fi
190
- done <<<"$text"
191
- }
192
158
 
193
159
  # Folds the global-surface-sweep's OWN already-computed and already-printed
194
160
  # backup lines into this run's combined summary, without recomputing
@@ -222,14 +188,16 @@ repo_root="$(cd "$script_dir/.." && pwd)"
222
188
  manifest="${manifest:-$repo_root/packages/fleet/manifest.json}"
223
189
  # shellcheck source=lib/bun-compile-build.sh
224
190
  source "$script_dir/lib/bun-compile-build.sh"
225
- "$script_dir/validate-manifest" "$manifest" >/dev/null
191
+ # shellcheck source=lib/adl-package.sh
192
+ source "$script_dir/lib/adl-package.sh"
193
+ "$BASH" "$script_dir/validate-manifest" "$manifest" >/dev/null
226
194
  if [[ "${RECONCILE_SOURCE_ONLY:-0}" != "1" ]]; then
227
195
  if [[ "$source_mode" == npm && -e "$repo_root/.git" ]]; then
228
- echo "npm source mode requires an installed package root; use --source checkout for a Git checkout" >&2
196
+ echo "ERROR npm source mode requires an installed package root; use --source checkout for a Git checkout" >&2
229
197
  exit 2
230
198
  fi
231
199
  if [[ "$source_mode" == checkout && ! -e "$repo_root/.git" ]]; then
232
- echo "checkout source mode requires a Git checkout" >&2
200
+ echo "ERROR checkout source mode requires a Git checkout" >&2
233
201
  exit 2
234
202
  fi
235
203
  fi
@@ -237,7 +205,7 @@ fi
237
205
  case "$(uname -s)" in
238
206
  Darwin) platform=mac; sha256_cmd=(shasum -a 256) ;;
239
207
  Linux) platform=linux; sha256_cmd=(sha256sum) ;;
240
- *) echo "unsupported operating system: $(uname -s)" >&2; exit 1 ;;
208
+ *) echo "ERROR unsupported operating system: $(uname -s)" >&2; exit 1 ;;
241
209
  esac
242
210
  if [[ -n "$platform_override" ]]; then
243
211
  # Applying as a foreign platform would install the wrong platform's tools, so
@@ -248,7 +216,7 @@ if [[ -n "$platform_override" ]]; then
248
216
  # agents of the mixed CI pool it dies with "device m3 is not declared under
249
217
  # linux" (build 4226).
250
218
  if [[ "$mode" == apply && "${RECONCILE_SOURCE_ONLY:-0}" != "1" ]]; then
251
- echo "--platform cannot override the host in apply mode" >&2
219
+ echo "ERROR --platform cannot override the host in apply mode" >&2
252
220
  exit 2
253
221
  fi
254
222
  [[ "$platform_override" == mac || "$platform_override" == linux ]] || usage
@@ -256,7 +224,7 @@ if [[ -n "$platform_override" ]]; then
256
224
  fi
257
225
 
258
226
  jq -e --arg platform "$platform" --arg device "$device" '.[$platform].devices[$device]' "$manifest" >/dev/null || {
259
- echo "device $device is not declared under $platform" >&2
227
+ echo "ERROR device $device is not declared under $platform" >&2
260
228
  exit 1
261
229
  }
262
230
 
@@ -292,7 +260,7 @@ path_owner() {
292
260
  elif owner="$(stat -c '%U' "$path" 2>/dev/null)"; then
293
261
  printf '%s\n' "$owner"
294
262
  else
295
- echo "cannot determine owner of Homebrew prefix: $path" >&2
263
+ echo "ERROR cannot determine owner of Homebrew prefix: $path" >&2
296
264
  return 1
297
265
  fi
298
266
  }
@@ -305,7 +273,7 @@ resolve_brew_owner() {
305
273
  fi
306
274
  if [[ ! -d "$prefix" ]]; then
307
275
  if [[ "$target_mode" == apply ]]; then
308
- echo "Homebrew prefix missing: $prefix" >&2
276
+ echo "ERROR Homebrew prefix missing: $prefix" >&2
309
277
  return 1
310
278
  fi
311
279
  printf '%s\n' '<unresolved-linuxbrew-owner>'
@@ -313,7 +281,7 @@ resolve_brew_owner() {
313
281
  fi
314
282
  owner="$(path_owner "$prefix")" || return 1
315
283
  if [[ "$owner" == root ]]; then
316
- echo "Homebrew prefix must be owned by a non-root account: $prefix" >&2
284
+ echo "ERROR Homebrew prefix must be owned by a non-root account: $prefix" >&2
317
285
  return 1
318
286
  fi
319
287
  printf '%s\n' "$owner"
@@ -329,10 +297,25 @@ run_brew() {
329
297
  printf '\n'
330
298
  return 0
331
299
  fi
332
- [[ "$(id -u)" -eq 0 ]] || { echo "apply mode must run as root" >&2; exit 1; }
300
+ [[ "$(id -u)" -eq 0 ]] || { echo "ERROR apply mode must run as root" >&2; exit 1; }
301
+ if [[ -n "${brew_noninteractive_dir:-}" ]]; then
302
+ # The upgrade step: no sudo Homebrew spawns may prompt (see run_brew_upgrade_all).
303
+ (cd /tmp && sudo -u "$brew_owner" -H env -u SUDO_ASKPASS NONINTERACTIVE=1 HOMEBREW_NO_SUDO=1 HOMEBREW_NO_ENV_HINTS=1 PATH="$brew_noninteractive_dir:${brew_bin%/*}:/usr/bin:/bin" "$brew_bin" "$@" </dev/null)
304
+ return
305
+ fi
333
306
  (cd /tmp && sudo -u "$brew_owner" -H env NONINTERACTIVE=1 PATH="${brew_bin%/*}:/usr/bin:/bin" "$brew_bin" "$@" </dev/null)
334
307
  }
335
308
 
309
+ # A directory holding a `sudo` that can never prompt: it execs /usr/bin/sudo -n. Put first on the
310
+ # PATH of the brew upgrade step, for anything that calls sudo by name.
311
+ make_brew_noninteractive_shim() {
312
+ local dir
313
+ dir="$(mktemp -d "${TMPDIR:-/tmp}/adl-brew-sudo-shim.XXXXXX")" || return 1
314
+ printf '#!/bin/sh\nexec /usr/bin/sudo -n "$@"\n' >"$dir/sudo"
315
+ chmod 0755 "$dir" "$dir/sudo"
316
+ printf '%s\n' "$dir"
317
+ }
318
+
336
319
  # Portable command timeout: neither GNU coreutils' `timeout` nor its `gtimeout`
337
320
  # alias is installed by default on a fresh mac (this manifest installs
338
321
  # neither), so a real `command -v timeout` gate would silently do nothing on
@@ -382,52 +365,126 @@ run_with_timeout() {
382
365
  BREW_UPGRADE_ALL_TIMEOUT_SECONDS="${RECONCILE_BREW_UPGRADE_TIMEOUT_SECONDS:-1200}"
383
366
  SURFACE_UPDATE_TIMEOUT_SECONDS="${RECONCILE_SURFACE_UPDATE_TIMEOUT_SECONDS:-300}"
384
367
 
385
- # Upgrades every outdated Homebrew formula AND cask in one call -- never a
386
- # per-package list, so a formula outdated for reasons unrelated to this
387
- # manifest (installed once by hand, or a transitive dependency of a manifest
388
- # formula) still converges instead of accumulating outdated versions forever
389
- # (item 1, 2026-09-28: `brew update` alone, the only brew-wide step that ran
390
- # before this, never upgrades anything). Gated by the manifest's own
391
- # brew_upgrade_all switch at the call site below, not inside this function --
392
- # this function always does the real (or plan-mode preview) work when called.
368
+ # brew's output for a cask whose upgrade needed sudo but was not allowed to ask: Homebrew refusing
369
+ # ("sudo is disabled by HOMEBREW_NO_SUDO."), or sudo -n failing fast. It never blocks.
370
+ brew_output_needs_password() {
371
+ grep -Eiq 'sudo is disabled by HOMEBREW_NO_SUDO|sudo: .*(password|terminal|tty|askpass)|^Password:' <<<"$1"
372
+ }
373
+
374
+ # Upgrades the outdated Homebrew formulae and casks, from ONE `brew outdated --json=v2` (the
375
+ # formulae and casks, with installed and available versions). A formula outdated for reasons
376
+ # unrelated to this manifest (installed once by hand, or a transitive dependency of a manifest
377
+ # formula) still converges instead of accumulating outdated versions forever (item 1,
378
+ # 2026-09-28: `brew update` alone, the only brew-wide step that ran before this, never upgrades
379
+ # anything). Gated by the manifest's own brew_upgrade_all switch at the call site below, not
380
+ # inside this function -- this function always does the real (or plan-mode preview) work when
381
+ # called.
382
+ #
383
+ # Two rules from the live tests (Tarun, 2026-10-01):
384
+ # - config.brew_upgrade_exclude names packages that are never upgraded here (git: every Homebrew
385
+ # git upgrade installs an ad-hoc-signed binary at a new Cellar path and re-prompts the
386
+ # osxkeychain ACL). Not a `brew pin`, not a version pin: an outdated excluded package is one
387
+ # info line, and the rest is upgraded BY NAME.
388
+ # - a reconcile NEVER prompts, terminal or not (m5, 2026-10-01: an interactive `sudo adl setup`
389
+ # got a nested, echoing `Password:` from the visual-studio-code cask's sudo inside brew inside
390
+ # our sudo -u). Every brew call here runs with stdin /dev/null, SUDO_ASKPASS unset,
391
+ # HOMEBREW_NO_SUDO=1 (brew refuses before spawning sudo) and a PATH-shimmed `sudo -n`. Casks are
392
+ # upgraded one at a time; one that needs a password is reported as a WARN with the command to
393
+ # run by hand and is not a failed tool. A failing formula, or a cask that fails for any other
394
+ # reason, still fails loud.
393
395
  run_brew_upgrade_all() {
396
+ if [[ "$mode" != apply ]]; then
397
+ run_brew_upgrade_all_body
398
+ return
399
+ fi
400
+ local status=0
401
+ brew_noninteractive_dir="$(make_brew_noninteractive_shim)" || { echo "ERROR could not create the brew sudo shim" >&2; return 1; }
402
+ run_brew_upgrade_all_body || status=$?
403
+ command rm -rf -- "$brew_noninteractive_dir"
404
+ brew_noninteractive_dir=""
405
+ return "$status"
406
+ }
407
+
408
+ run_brew_upgrade_all_body() {
394
409
  if [[ "$mode" != apply ]]; then
395
410
  echo "would run: brew upgrade (all outdated formulae and casks)"
411
+ local planned_excludes
412
+ planned_excludes="$(jq -r '(.config.brew_upgrade_exclude // []) | join(", ")' "$manifest")"
413
+ if [[ -n "$planned_excludes" ]]; then
414
+ echo "brew upgrade excludes (config.brew_upgrade_exclude): $planned_excludes"
415
+ fi
396
416
  return 0
397
417
  fi
398
- [[ "$(id -u)" -eq 0 ]] || { echo "apply mode must run as root" >&2; exit 1; }
399
- local outdated_formulae outdated_casks formulae_count casks_count
400
- outdated_formulae="$(brew_query outdated --formula --quiet)" || {
401
- echo "brew outdated --formula check failed" >&2
418
+ [[ "$(id -u)" -eq 0 ]] || { echo "ERROR apply mode must run as root" >&2; exit 1; }
419
+ local outdated_json excluded_names line kind name installed available
420
+ local -a formulae=() casks=()
421
+ local skipped=0
422
+ outdated_json="$(brew_query outdated --json=v2)" || {
423
+ echo "ERROR brew outdated check failed" >&2
402
424
  return 1
403
425
  }
404
- outdated_casks="$(brew_query outdated --cask --quiet)" || {
405
- echo "brew outdated --cask check failed" >&2
426
+ jq -e '(.formulae | type == "array") and (.casks | type == "array")' >/dev/null 2>&1 <<<"$outdated_json" || {
427
+ echo "ERROR brew outdated --json=v2 did not print formulae and casks" >&2
406
428
  return 1
407
429
  }
408
- formulae_count=0
409
- [[ -z "$outdated_formulae" ]] || formulae_count="$(wc -l <<<"$outdated_formulae" | tr -d ' ')"
410
- casks_count=0
411
- [[ -z "$outdated_casks" ]] || casks_count="$(wc -l <<<"$outdated_casks" | tr -d ' ')"
430
+ excluded_names="$(jq -r '(.config.brew_upgrade_exclude // [])[]' "$manifest")"
431
+ while IFS=$'\t' read -r kind name installed available; do
432
+ [[ -n "$name" ]] || continue
433
+ if [[ -n "$excluded_names" ]] && grep -Fxq -- "$name" <<<"$excluded_names"; then
434
+ echo "global-tools: brew upgrade skips excluded: $name ($installed → $available)"
435
+ skipped=$((skipped + 1))
436
+ continue
437
+ fi
438
+ if [[ "$kind" == formula ]]; then formulae+=("$name"); else casks+=("$name"); fi
439
+ done < <(jq -r '
440
+ def versions: if type == "array" then join(", ") else tostring end;
441
+ (.formulae[]? | ["formula", .name, (.installed_versions | versions), .current_version]),
442
+ (.casks[]? | ["cask", .name, (.installed_versions | versions), .current_version]) | @tsv
443
+ ' <<<"$outdated_json")
444
+ local formulae_count="${#formulae[@]}" casks_count="${#casks[@]}"
412
445
  if [[ "$formulae_count" -eq 0 && "$casks_count" -eq 0 ]]; then
413
- echo "brew upgrade: nothing outdated"
446
+ if [[ "$skipped" -gt 0 ]]; then
447
+ echo "brew upgrade: nothing left to upgrade after $skipped excluded"
448
+ else
449
+ echo "brew upgrade: nothing outdated"
450
+ fi
414
451
  return 0
415
452
  fi
416
- local upgrade_status=0
417
453
  # Routed through run_brew itself (same owner/cwd/stdin wiring as every
418
454
  # other brew mutation in this file), bounded by run_with_timeout so a
419
455
  # network-stalled `brew upgrade` cannot hang the rest of this run forever.
420
456
  # brew's output streams live -- never captured and printed at the end,
421
457
  # which left a long upgrade silent for minutes and looking stuck.
422
458
  echo "brew upgrade: $formulae_count formulae, $casks_count casks outdated; upgrading (timeout ${BREW_UPGRADE_ALL_TIMEOUT_SECONDS}s)"
423
- run_with_timeout "$BREW_UPGRADE_ALL_TIMEOUT_SECONDS" run_brew upgrade 2>&1 || upgrade_status=$?
424
- if [[ "$upgrade_status" -eq 124 ]]; then
425
- echo "brew upgrade timed out after ${BREW_UPGRADE_ALL_TIMEOUT_SECONDS}s" >&2
426
- return 1
427
- elif [[ "$upgrade_status" -ne 0 ]]; then
428
- echo "brew upgrade failed (exit $upgrade_status)" >&2
429
- return 1
459
+ local upgrade_status=0
460
+ local failed=0 cask cask_output
461
+ if [[ "$formulae_count" -gt 0 ]]; then
462
+ run_with_timeout "$BREW_UPGRADE_ALL_TIMEOUT_SECONDS" run_brew upgrade --formula "${formulae[@]}" 2>&1 || upgrade_status=$?
463
+ if [[ "$upgrade_status" -eq 124 ]]; then
464
+ echo "ERROR brew upgrade timed out after ${BREW_UPGRADE_ALL_TIMEOUT_SECONDS}s" >&2
465
+ return 1
466
+ elif [[ "$upgrade_status" -ne 0 ]]; then
467
+ echo "ERROR brew upgrade failed (exit $upgrade_status)" >&2
468
+ failed=1
469
+ fi
430
470
  fi
471
+ for cask in ${casks[@]+"${casks[@]}"}; do
472
+ upgrade_status=0
473
+ cask_output="$(run_with_timeout "$BREW_UPGRADE_ALL_TIMEOUT_SECONDS" run_brew upgrade --cask "$cask" 2>&1)" || upgrade_status=$?
474
+ [[ -z "$cask_output" ]] || printf '%s\n' "$cask_output"
475
+ if [[ "$upgrade_status" -eq 124 ]]; then
476
+ echo "ERROR brew upgrade timed out after ${BREW_UPGRADE_ALL_TIMEOUT_SECONDS}s ($cask)" >&2
477
+ return 1
478
+ elif [[ "$upgrade_status" -ne 0 ]]; then
479
+ if brew_output_needs_password "$cask_output"; then
480
+ echo "WARN brew: $cask needs an interactive password to upgrade; run: brew upgrade --cask $cask" >&2
481
+ else
482
+ echo "ERROR brew upgrade failed (exit $upgrade_status): $cask" >&2
483
+ failed=1
484
+ fi
485
+ fi
486
+ done
487
+ [[ "$failed" -eq 0 ]] || return 1
431
488
  echo "brew upgrade: $formulae_count formulae, $casks_count casks were outdated before upgrade"
432
489
  return 0
433
490
  }
@@ -439,7 +496,7 @@ run_apt() {
439
496
  printf '\n'
440
497
  return 0
441
498
  fi
442
- [[ "$(id -u)" -eq 0 ]] || { echo "apply mode must run as root" >&2; exit 1; }
499
+ [[ "$(id -u)" -eq 0 ]] || { echo "ERROR apply mode must run as root" >&2; exit 1; }
443
500
  env DEBIAN_FRONTEND=noninteractive apt-get "$@" </dev/null
444
501
  }
445
502
 
@@ -454,7 +511,7 @@ run_npm() {
454
511
  printf '\n'
455
512
  return 0
456
513
  fi
457
- [[ "$(id -u)" -eq 0 ]] || { echo "apply mode must run as root" >&2; exit 1; }
514
+ [[ "$(id -u)" -eq 0 ]] || { echo "ERROR apply mode must run as root" >&2; exit 1; }
458
515
  if [[ "$platform" == linux ]]; then
459
516
  (cd /tmp && sudo -u "$administrator" -H env npm_config_prefix=/usr/local PATH="${brew_bin%/*}:/usr/bin:/bin" npm "$@" </dev/null)
460
517
  else
@@ -538,12 +595,12 @@ canonical_parent_path() {
538
595
  npm_package_owns_executable() {
539
596
  local package="$1" executable="$2" npm_root package_manifest bin_relative actual_path actual_link expected_target actual_target
540
597
  npm_root="$(npm_global_root)" || {
541
- echo "could not resolve npm global root for $package" >&2
598
+ echo "ERROR could not resolve npm global root for $package" >&2
542
599
  return 1
543
600
  }
544
601
  package_manifest="$npm_root/$package/package.json"
545
602
  [[ -f "$package_manifest" ]] || {
546
- echo "npm package metadata missing: $package_manifest" >&2
603
+ echo "ERROR npm package metadata missing: $package_manifest" >&2
547
604
  return 1
548
605
  }
549
606
  bin_relative="$(jq -r --arg executable "$executable" '
@@ -553,20 +610,20 @@ npm_package_owns_executable() {
553
610
  end
554
611
  ' "$package_manifest")"
555
612
  [[ -n "$bin_relative" && "$bin_relative" != /* && "$bin_relative" != .. && "$bin_relative" != ../* && "$bin_relative" != */../* && "$bin_relative" != */.. ]] || {
556
- echo "npm package $package does not declare a safe bin target for $executable" >&2
613
+ echo "ERROR npm package $package does not declare a safe bin target for $executable" >&2
557
614
  return 1
558
615
  }
559
616
  actual_path="$(npm_global_executable_path "$executable")" || {
560
- echo "npm executable missing from global PATH: $executable" >&2
617
+ echo "ERROR npm executable missing from global PATH: $executable" >&2
561
618
  return 1
562
619
  }
563
620
  [[ -L "$actual_path" ]] || {
564
- echo "npm executable is not an npm-managed symlink: $actual_path" >&2
621
+ echo "ERROR npm executable is not an npm-managed symlink: $actual_path" >&2
565
622
  return 1
566
623
  }
567
624
  expected_target="$(canonical_parent_path "$npm_root/$package/$bin_relative")" || return 1
568
625
  [[ -f "$expected_target" && -x "$expected_target" ]] || {
569
- echo "npm package $package bin target is not an executable file: $expected_target" >&2
626
+ echo "ERROR npm package $package bin target is not an executable file: $expected_target" >&2
570
627
  return 1
571
628
  }
572
629
  actual_link="$(readlink "$actual_path")" || return 1
@@ -576,11 +633,27 @@ npm_package_owns_executable() {
576
633
  actual_target="$(canonical_parent_path "${actual_path%/*}/$actual_link")" || return 1
577
634
  fi
578
635
  [[ "$actual_target" == "$expected_target" ]] || {
579
- echo "npm executable $executable is not owned by $package" >&2
636
+ echo "ERROR npm executable $executable is not owned by $package" >&2
580
637
  return 1
581
638
  }
582
639
  }
583
640
 
641
+ # The published package is installed with bun (Tarun, 2026-10-01). Package mode
642
+ # proves the administrator's `adl` IS this package (bin/lib/adl-package.sh, the
643
+ # check bin/setup runs too): a different adl earlier on the administrator's PATH
644
+ # -- a legacy compiled /usr/local/bin/adl from before the package existed -- would
645
+ # keep running instead of the package this job updates, so it fails loud here with
646
+ # the one-line fix instead of being silently tolerated.
647
+ adl_package_owns_executable() {
648
+ local os package_real
649
+ case "$platform" in
650
+ mac) os=Darwin ;;
651
+ *) os=Linux ;;
652
+ esac
653
+ package_real="$(cd -P -- "$repo_root" && pwd -P)" || return 1
654
+ adl_check_adl_not_shadowed "$administrator" "$os" "$administrator_home" "$package_real" "global-tools"
655
+ }
656
+
584
657
  run_pip() {
585
658
  if [[ "$mode" != apply ]]; then
586
659
  printf 'would run pip as %s: python3 -m pip' "$administrator"
@@ -588,21 +661,21 @@ run_pip() {
588
661
  printf '\n'
589
662
  return 0
590
663
  fi
591
- [[ "$(id -u)" -eq 0 ]] || { echo "apply mode must run as root" >&2; exit 1; }
664
+ [[ "$(id -u)" -eq 0 ]] || { echo "ERROR apply mode must run as root" >&2; exit 1; }
592
665
  (cd /tmp && sudo -u "$administrator" -H env PATH="${brew_bin%/*}:/usr/bin:/bin" python3 -m pip "$@" </dev/null)
593
666
  }
594
667
 
595
668
  pip_user_executable() {
596
669
  local executable="$1"
597
670
  [[ "$executable" != */* ]] || {
598
- echo "pip executable must be a command name, not a path: $executable" >&2
671
+ echo "ERROR pip executable must be a command name, not a path: $executable" >&2
599
672
  exit 1
600
673
  }
601
674
  local user_base user_home
602
675
  user_home="$(cd /tmp && sudo -u "$administrator" -H /bin/sh -c 'printf "%s\n" "$HOME"')"
603
676
  user_base="$(cd /tmp && sudo -u "$administrator" -H env PATH="${brew_bin%/*}:/usr/bin:/bin" python3 -m site --user-base)"
604
677
  [[ "$user_base" == "$user_home" || "$user_base" == "$user_home"/* ]] || {
605
- echo "pip user base is outside the declared administrator home: $user_base" >&2
678
+ echo "ERROR pip user base is outside the declared administrator home: $user_base" >&2
606
679
  exit 1
607
680
  }
608
681
  printf '%s/bin/%s\n' "${user_base%/}" "$executable"
@@ -615,11 +688,11 @@ ensure_global_pip_executable() {
615
688
  echo "would expose pip executable globally: $target"
616
689
  return 0
617
690
  fi
618
- [[ "$(id -u)" -eq 0 ]] || { echo "apply mode must run as root" >&2; exit 1; }
691
+ [[ "$(id -u)" -eq 0 ]] || { echo "ERROR apply mode must run as root" >&2; exit 1; }
619
692
  local source existing_source user_home
620
693
  source="$(pip_user_executable "$executable")"
621
694
  user_home="$(cd /tmp && sudo -u "$administrator" -H /bin/sh -c 'printf "%s\n" "$HOME"')"
622
- [[ -x "$source" ]] || { echo "pip executable missing after install: $source" >&2; exit 1; }
695
+ [[ -x "$source" ]] || { echo "ERROR pip executable missing after install: $source" >&2; exit 1; }
623
696
  if [[ -L "$target" ]]; then
624
697
  existing_source="$(readlink "$target")"
625
698
  if [[ "$existing_source" == "$source" ]]; then
@@ -628,10 +701,10 @@ ensure_global_pip_executable() {
628
701
  fi
629
702
  case "$existing_source" in
630
703
  "$user_home"/*/bin/"$executable") ;;
631
- *) echo "refusing to replace unrelated symlink: $target -> $existing_source" >&2; exit 1 ;;
704
+ *) echo "ERROR refusing to replace unrelated symlink: $target -> $existing_source" >&2; exit 1 ;;
632
705
  esac
633
706
  elif [[ -e "$target" ]]; then
634
- echo "refusing to replace unrelated global executable: $target" >&2
707
+ echo "ERROR refusing to replace unrelated global executable: $target" >&2
635
708
  exit 1
636
709
  fi
637
710
  ln -sfn "$source" "$target"
@@ -645,17 +718,17 @@ remove_global_pip_executable() {
645
718
  echo "would remove global pip executable: $target"
646
719
  return 0
647
720
  fi
648
- [[ "$(id -u)" -eq 0 ]] || { echo "apply mode must run as root" >&2; exit 1; }
721
+ [[ "$(id -u)" -eq 0 ]] || { echo "ERROR apply mode must run as root" >&2; exit 1; }
649
722
  if [[ ! -e "$target" && ! -L "$target" ]]; then
650
723
  return 0
651
724
  fi
652
- [[ -L "$target" ]] || { echo "refusing to remove unrelated global executable: $target" >&2; exit 1; }
725
+ [[ -L "$target" ]] || { echo "ERROR refusing to remove unrelated global executable: $target" >&2; exit 1; }
653
726
  local source user_home
654
727
  source="$(readlink "$target")"
655
728
  user_home="$(cd /tmp && sudo -u "$administrator" -H /bin/sh -c 'printf "%s\n" "$HOME"')"
656
729
  case "$source" in
657
730
  "$user_home"/*/bin/"$executable") rm -f -- "$target" ;;
658
- *) echo "refusing to remove unrelated symlink: $target -> $source" >&2; exit 1 ;;
731
+ *) echo "ERROR refusing to remove unrelated symlink: $target -> $source" >&2; exit 1 ;;
659
732
  esac
660
733
  }
661
734
 
@@ -705,12 +778,12 @@ bun_source_hash() {
705
778
  local source_dir="$1" hash
706
779
  if repo_root_is_git; then
707
780
  hash="$(repo_git rev-parse --verify --quiet "HEAD:$source_dir")" || {
708
- echo "bun-compile source is not tracked at HEAD: $source_dir" >&2
781
+ echo "ERROR bun-compile source is not tracked at HEAD: $source_dir" >&2
709
782
  return 1
710
783
  }
711
784
  else
712
785
  [[ -d "$repo_root/$source_dir" ]] || {
713
- echo "bun-compile source missing from installed package: $repo_root/$source_dir" >&2
786
+ echo "ERROR bun-compile source missing from installed package: $repo_root/$source_dir" >&2
714
787
  return 1
715
788
  }
716
789
  hash="$(content_tree_hash "$repo_root/$source_dir")"
@@ -732,17 +805,17 @@ run_bun_compile() {
732
805
  printf 'would build and install bun binary: %s -> %s (source %s)\n' "$package" "$target" "$source_hash"
733
806
  return 0
734
807
  fi
735
- [[ "$(id -u)" -eq 0 ]] || { echo "apply mode must run as root" >&2; exit 1; }
736
- [[ "$target" == /usr/local/bin/* ]] || { echo "bun-compile target must be under /usr/local/bin: $target" >&2; exit 1; }
808
+ [[ "$(id -u)" -eq 0 ]] || { echo "ERROR apply mode must run as root" >&2; exit 1; }
809
+ [[ "$target" == /usr/local/bin/* ]] || { echo "ERROR bun-compile target must be under /usr/local/bin: $target" >&2; exit 1; }
737
810
  [[ "$source_dir" != /* && "$source_dir" != *..* ]] || {
738
- echo "unsafe bun-compile source path: $source_dir" >&2
811
+ echo "ERROR unsafe bun-compile source path: $source_dir" >&2
739
812
  exit 1
740
813
  }
741
814
  local project_dir="$repo_root/$source_dir"
742
- [[ -d "$project_dir" ]] || { echo "bun-compile source missing: $project_dir" >&2; exit 1; }
815
+ [[ -d "$project_dir" ]] || { echo "ERROR bun-compile source missing: $project_dir" >&2; exit 1; }
743
816
  local bun_bin
744
817
  bun_bin="$(command -v bun || true)"
745
- [[ -n "$bun_bin" ]] || { echo "bun-compile requires bun on PATH; ensure bun installs before this tool" >&2; exit 1; }
818
+ [[ -n "$bun_bin" ]] || { echo "ERROR bun-compile requires bun on PATH; ensure bun installs before this tool" >&2; exit 1; }
746
819
  (cd "$project_dir" && "$bun_bin" install --frozen-lockfile)
747
820
  bun_compile_build "$bun_bin" "$project_dir/$entry" "$target"
748
821
  }
@@ -751,20 +824,19 @@ run_embedding_model() {
751
824
  local package="$1"
752
825
  local spec_json="$2"
753
826
  if [[ "$mode" != apply ]]; then
754
- printf 'would install embedding model: %s as %s -> ~/.cache/adl-embedding/model\n' "$package" "$administrator"
827
+ printf 'would install embedding model: %s as %s -> ~/.cache/enact-embedding/model\n' "$package" "$administrator"
755
828
  return 0
756
829
  fi
757
- [[ "$(id -u)" -eq 0 ]] || { echo "apply mode must run as root" >&2; exit 1; }
758
- command -v bun >/dev/null 2>&1 || { echo "embedding-model requires bun; ensure bun installs before this tool" >&2; exit 1; }
830
+ [[ "$(id -u)" -eq 0 ]] || { echo "ERROR apply mode must run as root" >&2; exit 1; }
831
+ command -v bun >/dev/null 2>&1 || { echo "ERROR embedding-model requires bun; ensure bun installs before this tool" >&2; exit 1; }
759
832
  (cd /tmp && sudo -u "$administrator" -H env PATH="${brew_bin%/*}:/usr/bin:/bin" \
760
833
  bun "$repo_root/packages/fleet/src/install-embedding-model.ts" install --spec "$spec_json" </dev/null)
761
834
  }
762
835
 
763
836
  # Installs $src as $dest via a same-directory temp-file + rename, never a
764
837
  # direct in-place write -- so a concurrent reader (including the next agent
765
- # shell's `command -v $dest`, per the lean-ctx incident this fixed: an
766
- # in-place npm postinstall rewrite left the shim executing but printing
767
- # "binary not found" for ~44s on every scheduled reconcile) can never observe
838
+ # shell's `command -v $dest`, which could otherwise observe an executable
839
+ # partway through an in-place rewrite) can never observe
768
840
  # $dest existing but only partially written. `mv` within the same directory
769
841
  # is a single rename(2) on both Darwin and Linux -- atomic by construction,
770
842
  # not by scheduling luck.
@@ -782,9 +854,9 @@ install_surface_policy_file() {
782
854
  echo "would install required surface policy: $source -> $target"
783
855
  return 0
784
856
  fi
785
- [[ -f "$source" ]] || { echo "surface policy source missing: $source" >&2; return 1; }
857
+ [[ -f "$source" ]] || { echo "ERROR surface policy source missing: $source" >&2; return 1; }
786
858
  jq -e '(.deny.paths | arrays) and (.deny.nativeTools | arrays)' "$source" >/dev/null || {
787
- echo "surface policy source is invalid: $source" >&2
859
+ echo "ERROR surface policy source is invalid: $source" >&2
788
860
  return 1
789
861
  }
790
862
  mkdir -p /etc/adl
@@ -795,8 +867,8 @@ install_surface_policy_file() {
795
867
 
796
868
  # A resolved release version against the first line of a release-archive-
797
869
  # latest tool's own `--version` output. Different tools format that line
798
- # differently -- lean-ctx: "lean-ctx 3.10.5 (official, ...)" (version padded
799
- # by spaces on both sides); agent-vault: "agent-vault version 0.39.3" (flush
870
+ # differently -- some include parenthetical metadata; agent-vault prints
871
+ # "agent-vault version 0.39.3" (flush
800
872
  # against the end of the line, no trailing space); a plain "vX.Y.Z" line
801
873
  # with no separating space before the version at all -- so the LEFT boundary
802
874
  # is a string start or a real space, with an OPTIONAL literal "v" directly
@@ -823,7 +895,7 @@ resolve_latest_release_tag() {
823
895
  local repo="$1"
824
896
  local location
825
897
  location="$(curl --proto '=https' --proto-redir '=https' -fsS -o /dev/null -w '%{redirect_url}' "https://github.com/$repo/releases/latest")"
826
- [[ -n "$location" ]] || { echo "could not resolve latest release tag for $repo" >&2; exit 1; }
898
+ [[ -n "$location" ]] || { echo "ERROR could not resolve latest release tag for $repo" >&2; exit 1; }
827
899
  basename "$location"
828
900
  }
829
901
 
@@ -854,10 +926,10 @@ run_release_archive_latest() {
854
926
  local checksums_asset="$5"
855
927
  local archive_executable="$6"
856
928
  local target="$7"
857
- [[ "$(id -u)" -eq 0 ]] || { echo "apply mode must run as root" >&2; exit 1; }
858
- [[ "$target" == /usr/local/bin/* ]] || { echo "release archive target must be under /usr/local/bin: $target" >&2; exit 1; }
929
+ [[ "$(id -u)" -eq 0 ]] || { echo "ERROR apply mode must run as root" >&2; exit 1; }
930
+ [[ "$target" == /usr/local/bin/* ]] || { echo "ERROR release archive target must be under /usr/local/bin: $target" >&2; exit 1; }
859
931
  [[ "$archive_executable" != /* && "$archive_executable" != *..* ]] || {
860
- echo "unsafe archive executable path: $archive_executable" >&2
932
+ echo "ERROR unsafe archive executable path: $archive_executable" >&2
861
933
  exit 1
862
934
  }
863
935
 
@@ -872,7 +944,7 @@ run_release_archive_latest() {
872
944
  curl --proto '=https' --proto-redir '=https' -fsSL --retry 3 \
873
945
  -o "$checksums_file" "https://github.com/$repo/releases/download/$tag/$checksums_asset"
874
946
  expected_sha256="$(awk -v want="$asset" '$2 == want { print $1; found = 1 } END { exit !found }' "$checksums_file")" || {
875
- echo "no published checksum for $asset in $repo's $checksums_asset -- refusing to install unverified bytes" >&2
947
+ echo "ERROR no published checksum for $asset in $repo's $checksums_asset -- refusing to install unverified bytes" >&2
876
948
  exit 1
877
949
  }
878
950
  curl --proto '=https' --proto-redir '=https' -fsSL --retry 3 \
@@ -880,15 +952,15 @@ run_release_archive_latest() {
880
952
  case "$(uname -s)" in
881
953
  Darwin) actual_sha256="$(shasum -a 256 "$archive_file" | awk '{print $1}')" ;;
882
954
  Linux) actual_sha256="$(sha256sum "$archive_file" | awk '{print $1}')" ;;
883
- *) echo "unsupported operating system for release archive" >&2; exit 1 ;;
955
+ *) echo "ERROR unsupported operating system for release archive" >&2; exit 1 ;;
884
956
  esac
885
957
  [[ "$actual_sha256" == "$expected_sha256" ]] || {
886
- echo "checksum mismatch for $package (latest): expected $expected_sha256 from published $checksums_asset, got $actual_sha256" >&2
958
+ echo "ERROR checksum mismatch for $package (latest): expected $expected_sha256 from published $checksums_asset, got $actual_sha256" >&2
887
959
  exit 1
888
960
  }
889
961
  tar -xzf "$archive_file" -C "$extract_dir" "$archive_executable"
890
962
  [[ -f "$extract_dir/$archive_executable" ]] || {
891
- echo "archive executable missing: $archive_executable" >&2
963
+ echo "ERROR archive executable missing: $archive_executable" >&2
892
964
  exit 1
893
965
  }
894
966
  atomic_install_executable "$extract_dir/$archive_executable" "$target"
@@ -926,7 +998,7 @@ resolve_surface_mode() {
926
998
  # root") is a real environment fault, not a flaky install, and still
927
999
  # aborts immediately -- `exit` is unconditional and is never caught here.
928
1000
  install_one_tool() {
929
- local tool_id="$1" manager package executable previous_executable migrating_from_bun=0 rc=0 npm_spec npm_allow_scripts npm_target_version npm_installed_ver brew_outdated_output brew_installed_version
1001
+ local tool_id="$1" manager package executable previous_executable migrating_from_bun=0 rc=0 npm_spec npm_allow_scripts npm_target_version npm_installed_ver brew_outdated_output brew_installed_version trust_kind
930
1002
  manager="$(jq -r --arg id "$tool_id" '.[$id].manager' "$resolved_file")"
931
1003
  package="$(jq -r --arg id "$tool_id" '.[$id].package' "$resolved_file")"
932
1004
  executable="$(jq -r --arg id "$tool_id" '.[$id].executable' "$resolved_file")"
@@ -934,25 +1006,82 @@ install_one_tool() {
934
1006
  echo "ensure Adl from explicit checkout source: $repo_root"
935
1007
  build_checkout_adl || rc=1
936
1008
  if [[ "$mode" == apply && "$rc" -eq 0 ]]; then
937
- command -v "$executable" >/dev/null 2>&1 || { echo "checkout Adl executable missing from PATH: $executable" >&2; rc=1; }
1009
+ command -v "$executable" >/dev/null 2>&1 || { echo "ERROR checkout Adl executable missing from PATH: $executable" >&2; rc=1; }
938
1010
  fi
939
1011
  return "$rc"
940
1012
  fi
941
1013
  if [[ "$tool_id" == adl && "$source_mode" == npm ]]; then
942
1014
  if [[ "$mode" != apply ]]; then
943
- echo "would use installed npm package payload: $repo_root"
1015
+ echo "would use installed package payload: $repo_root"
944
1016
  return 0
945
1017
  fi
946
1018
  if is_in_place_bun_to_npm_migration "$tool_id"; then
947
1019
  prepare_in_place_bun_to_npm_migration "$tool_id" || return 1
948
1020
  fi
949
- npm_package_owns_executable "$package" "$executable" || return 1
950
- echo "using installed npm package payload: $repo_root"
1021
+ adl_package_owns_executable || return 1
1022
+ echo "using installed package payload: $repo_root"
951
1023
  return 0
952
1024
  fi
953
1025
  echo "ensure latest stable: $tool_id ($manager:$package)"
954
1026
  case "$manager" in
955
1027
  brew)
1028
+ # Optional declared tap (manifest .tap, owner/repo): fully-qualified
1029
+ # package names (e.g. stablyai/orca/orca) refuse to resolve until their
1030
+ # tap exists, and Homebrew will not auto-tap a non-core tap for a
1031
+ # noninteractive caller — so reconcile taps it first, idempotently.
1032
+ # Tarun 2026-10-02: orca is brew-managed from Stably's own tap; that
1033
+ # cask is auto_updates, so the per-tool path installs-if-missing and
1034
+ # the in-app updater owns versioning — no version is ever pinned here.
1035
+ tap="$(jq -r --arg id "$tool_id" '.[$id].tap // ""' "$resolved_file")"
1036
+ if [[ "$rc" -eq 0 && -n "$tap" ]]; then
1037
+ [[ "$tap" =~ ^[A-Za-z0-9_.-]+/[A-Za-z0-9_.-]+$ ]] || {
1038
+ echo "ERROR refusing unsafe brew tap name for $tool_id: $tap" >&2
1039
+ rc=1
1040
+ }
1041
+ fi
1042
+ if [[ "$rc" -eq 0 && -n "$tap" && "$mode" == apply ]]; then
1043
+ brew_query tap | grep -Fqx "$tap" || run_brew tap "$tap" || {
1044
+ echo "ERROR $tool_id: brew tap $tap failed" >&2
1045
+ rc=1
1046
+ }
1047
+ fi
1048
+ # Homebrew >=6 tap trust (brew 7.0.7 on this fleet; the 2026-10 orca
1049
+ # install failure was exactly this refusal): a command that must load an
1050
+ # untrusted non-core tap FAILS until trust is granted, noninteractively
1051
+ # or not. Grant ITEM-level trust for the declared package only --
1052
+ # brew's own automation guidance is `brew trust --formula|--cask
1053
+ # owner/repo/item`, never the whole tap. Gated on the same declared
1054
+ # .tap as the tap step above: that manifest field is the reviewed trust
1055
+ # boundary, and a bare core package (curl, ripgrep) needs no grant.
1056
+ if [[ "$rc" -eq 0 && -n "$tap" && "$mode" == apply ]]; then
1057
+ # The kind is resolved, never guessed: a fully-qualified name is
1058
+ # loadable exactly as one of formula/cask once its tap exists.
1059
+ if brew_query info --formula "$package" >/dev/null 2>&1; then
1060
+ trust_kind=formula
1061
+ elif brew_query info --cask "$package" >/dev/null 2>&1; then
1062
+ trust_kind=cask
1063
+ else
1064
+ echo "ERROR $tool_id: cannot determine whether $package is a formula or a cask — refusing to guess its trust kind" >&2
1065
+ rc=1
1066
+ fi
1067
+ # `brew trust` (list) prints each granted item on its own line,
1068
+ # indented under a "Trusted formulae:"/"Trusted casks:" header
1069
+ # (verified on brew 7.0.7) -- trim, then exact-line match so a
1070
+ # whole-tap grant ("Trusted taps: owner/repo") can never satisfy the
1071
+ # item check.
1072
+ if [[ "$rc" -eq 0 ]]; then
1073
+ brew_query trust | awk '{$1=$1};1' | grep -Fqx "$package" || run_brew trust "--$trust_kind" "$package" || {
1074
+ echo "ERROR $tool_id: brew trust --$trust_kind $package failed" >&2
1075
+ rc=1
1076
+ }
1077
+ fi
1078
+ elif [[ "$rc" -eq 0 && -n "$tap" ]]; then
1079
+ # Plan mode: brew_query is apply-only (see its own header comment),
1080
+ # so the kind is not resolved for a preview -- name the grant anyway.
1081
+ echo "would trust Homebrew item: $package (formula|cask)"
1082
+ fi
1083
+ if [[ "$rc" -ne 0 ]]; then
1084
+ :
956
1085
  # Plan mode's message is intentionally unconditional (see brew_query's
957
1086
  # own comment for why: a real up-to-date check is slow enough on a
958
1087
  # loaded box to defeat the point of a quick preview). Apply mode pays
@@ -960,7 +1089,7 @@ install_one_tool() {
960
1089
  # run `brew upgrade` on an already-current formula -- which brew itself
961
1090
  # accepts silently (prints "Warning: <pkg> <ver> already installed" and
962
1091
  # exits 0), the exact per-run noise this replaces.
963
- if [[ "$mode" != apply ]]; then
1092
+ elif [[ "$mode" != apply ]]; then
964
1093
  echo "would install or upgrade Homebrew formula: $package"
965
1094
  elif brew_query list --formula "$package" >/dev/null 2>&1; then
966
1095
  if brew_outdated_output="$(brew_query outdated --formula --quiet "$package")"; then
@@ -971,7 +1100,7 @@ install_one_tool() {
971
1100
  run_brew upgrade "$package" || rc=1
972
1101
  fi
973
1102
  else
974
- echo "$tool_id: brew outdated check failed for $package" >&2
1103
+ echo "ERROR $tool_id: brew outdated check failed for $package" >&2
975
1104
  rc=1
976
1105
  fi
977
1106
  else
@@ -1012,12 +1141,12 @@ install_one_tool() {
1012
1141
  prepare_in_place_bun_to_npm_migration "$tool_id" || rc=1
1013
1142
  fi
1014
1143
  if [[ "$rc" -eq 0 ]] && ! npm_target_version="$(npm_latest_version "$package")"; then
1015
- echo "$tool_id: npm view could not resolve the latest version of $package" >&2
1144
+ echo "ERROR $tool_id: npm view could not resolve the latest version of $package" >&2
1016
1145
  rc=1
1017
1146
  fi
1018
1147
  if [[ "$rc" -eq 0 ]]; then
1019
1148
  if ! npm_installed_ver="$(npm_installed_version "$package")"; then
1020
- echo "$tool_id: npm ls -g could not inspect the global install of $package" >&2
1149
+ echo "ERROR $tool_id: npm ls -g could not inspect the global install of $package" >&2
1021
1150
  rc=1
1022
1151
  elif [[ -n "$npm_installed_ver" && "$npm_installed_ver" == "$npm_target_version" ]]; then
1023
1152
  echo "already latest: $tool_id (npm:$package@$npm_installed_ver)"
@@ -1053,10 +1182,8 @@ install_one_tool() {
1053
1182
  latest_tag="$(resolve_latest_release_tag "$repo")" || { rc=1; latest_tag=""; }
1054
1183
  if [[ "$rc" -eq 0 ]]; then
1055
1184
  latest_version="${latest_tag#v}"
1056
- # A release whose asset filename bakes in the version (agent-vault:
1057
- # agent-vault_<ver>_darwin_arm64.tar.gz) declares "{version}" in
1058
- # .asset/.checksums_asset; lean-ctx's own asset names carry no
1059
- # version at all, so this is a no-op substring replace for it.
1185
+ # A release whose asset filename bakes in the version declares
1186
+ # "{version}" in .asset/.checksums_asset; otherwise this is a no-op.
1060
1187
  resolved_asset="${asset//\{version\}/$latest_version}"
1061
1188
  resolved_checksums_asset="${checksums_asset//\{version\}/$latest_version}"
1062
1189
  if [[ -x "$executable" ]] &&
@@ -1068,15 +1195,15 @@ install_one_tool() {
1068
1195
  fi
1069
1196
  fi
1070
1197
  ;;
1071
- *) echo "unsupported manager: $manager" >&2; rc=1 ;;
1198
+ *) echo "ERROR unsupported manager: $manager" >&2; rc=1 ;;
1072
1199
  esac
1073
1200
  if [[ "$mode" == apply && "$rc" -eq 0 ]]; then
1074
1201
  if [[ "$executable" == /* ]]; then
1075
- [[ -x "$executable" ]] || { echo "$tool_id executable missing: $executable" >&2; rc=1; }
1202
+ [[ -x "$executable" ]] || { echo "ERROR $tool_id executable missing: $executable" >&2; rc=1; }
1076
1203
  else
1077
1204
  local executable_path
1078
1205
  executable_path="$(sudo -u "$administrator" -H env PATH="/opt/homebrew/bin:/home/linuxbrew/.linuxbrew/bin:/usr/local/bin:/usr/bin:/bin" /bin/sh -c "command -v '$executable'" 2>/dev/null || true)"
1079
- [[ -n "$executable_path" ]] || { echo "$tool_id executable missing from global PATH: $executable" >&2; rc=1; }
1206
+ [[ -n "$executable_path" ]] || { echo "ERROR $tool_id executable missing from global PATH: $executable" >&2; rc=1; }
1080
1207
  fi
1081
1208
  fi
1082
1209
  return "$rc"
@@ -1102,11 +1229,11 @@ is_in_place_bun_to_npm_migration() {
1102
1229
  remove_retired_bun_compilation() {
1103
1230
  local tool_id="$1" executable="$2" hash_file
1104
1231
  [[ "$tool_id" =~ ^[A-Za-z0-9][A-Za-z0-9._-]*$ ]] || {
1105
- echo "unsafe retired Bun compilation tool id: $tool_id" >&2
1232
+ echo "ERROR unsafe retired Bun compilation tool id: $tool_id" >&2
1106
1233
  return 1
1107
1234
  }
1108
1235
  [[ "$executable" =~ ^/usr/local/bin/[A-Za-z0-9][A-Za-z0-9._-]*$ ]] || {
1109
- echo "refusing unsafe retired Bun binary removal target: $executable" >&2
1236
+ echo "ERROR refusing unsafe retired Bun binary removal target: $executable" >&2
1110
1237
  return 1
1111
1238
  }
1112
1239
  hash_file="$(bun_source_hash_file "$tool_id")"
@@ -1128,10 +1255,17 @@ prepare_in_place_bun_to_npm_migration() {
1128
1255
  if [[ -L "$migration_path" ]]; then
1129
1256
  package="$(jq -r --arg id "$tool_id" '.[$id].package' "$resolved_file")"
1130
1257
  executable="$(jq -r --arg id "$tool_id" '.[$id].executable' "$resolved_file")"
1131
- npm_package_owns_executable "$package" "$executable" || {
1132
- echo "refusing to remove an executable symlink not owned by npm: $previous_executable" >&2
1133
- return 1
1134
- }
1258
+ if [[ "$tool_id" == adl && "$source_mode" == npm ]]; then
1259
+ adl_package_owns_executable || {
1260
+ echo "ERROR refusing to remove an executable symlink not owned by the installed package: $previous_executable" >&2
1261
+ return 1
1262
+ }
1263
+ else
1264
+ npm_package_owns_executable "$package" "$executable" || {
1265
+ echo "ERROR refusing to remove an executable symlink not owned by npm: $previous_executable" >&2
1266
+ return 1
1267
+ }
1268
+ fi
1135
1269
  hash_file="$(bun_source_hash_file "$tool_id")"
1136
1270
  rm -f -- "$hash_file" || return 1
1137
1271
  echo "retained npm-owned executable and removed retired Bun marker: $previous_executable ($hash_file)"
@@ -1144,7 +1278,7 @@ failed_in_place_npm_migration() {
1144
1278
  local tool_id
1145
1279
  for tool_id in ${failed_tools[@]+"${failed_tools[@]}"}; do
1146
1280
  if is_in_place_bun_to_npm_migration "$tool_id"; then
1147
- echo "global-tools: npm migration failed for $tool_id; retired Bun command was removed and reconciliation must stop" >&2
1281
+ echo "ERROR global-tools: npm migration failed for $tool_id; retired Bun command was removed and reconciliation must stop" >&2
1148
1282
  return 0
1149
1283
  fi
1150
1284
  done
@@ -1181,7 +1315,7 @@ build_one_bun_tool() {
1181
1315
  fi
1182
1316
  run_bun_compile "$package" "$source_hash" "$source_dir" "$entry" "$executable" || rc=1
1183
1317
  if [[ "$mode" == apply && "$rc" -eq 0 ]]; then
1184
- [[ -x "$executable" ]] || { echo "$tool_id executable missing: $executable" >&2; rc=1; }
1318
+ [[ -x "$executable" ]] || { echo "ERROR $tool_id executable missing: $executable" >&2; rc=1; }
1185
1319
  fi
1186
1320
  if [[ "$mode" == apply && "$rc" -eq 0 ]]; then
1187
1321
  mkdir -p "${hash_file%/*}"
@@ -1202,7 +1336,7 @@ build_checkout_adl() {
1202
1336
  fi
1203
1337
  run_bun_compile adl "$source_hash" "$source_dir" "$entry" "$target" || rc=1
1204
1338
  if [[ "$mode" == apply && "$rc" -eq 0 ]]; then
1205
- [[ -x "$target" ]] || { echo "checkout Adl executable missing: $target" >&2; rc=1; }
1339
+ [[ -x "$target" ]] || { echo "ERROR checkout Adl executable missing: $target" >&2; rc=1; }
1206
1340
  if [[ "$rc" -eq 0 ]]; then
1207
1341
  mkdir -p "${hash_file%/*}"
1208
1342
  printf '%s\n' "$source_hash" >"$hash_file.new"
@@ -1213,7 +1347,7 @@ build_checkout_adl() {
1213
1347
  return "$rc"
1214
1348
  }
1215
1349
 
1216
- # embedding-model installs the adl-embedding static (Model2Vec) artifact
1350
+ # embedding-model installs the enact-embedding static (Model2Vec) artifact
1217
1351
  # that adl hook's (the folded-in hook engine's) skill-match hook loads
1218
1352
  # from the administrator's cache.
1219
1353
  # It runs as the declared administrator (root on Linux) so the hook and
@@ -1243,16 +1377,16 @@ write_one_shim() {
1243
1377
  executable="$(jq -r --arg id "$tool_id" '.[$id].executable' "$resolved_file")"
1244
1378
  echo "ensure shim: $tool_id (shim:$package -> $executable)"
1245
1379
  [[ "$executable" == /usr/local/bin/* ]] || {
1246
- echo "refusing unsafe shim target: $executable" >&2
1380
+ echo "ERROR refusing unsafe shim target: $executable" >&2
1247
1381
  return 1
1248
1382
  }
1249
1383
  if [[ "$mode" != apply ]]; then
1250
1384
  echo "would write shim: $executable -> $package"
1251
1385
  return 0
1252
1386
  fi
1253
- [[ "$(id -u)" -eq 0 ]] || { echo "apply mode must run as root" >&2; exit 1; }
1387
+ [[ "$(id -u)" -eq 0 ]] || { echo "ERROR apply mode must run as root" >&2; exit 1; }
1254
1388
  target_path="$(sudo -u "$administrator" -H env PATH="/opt/homebrew/bin:/home/linuxbrew/.linuxbrew/bin:/usr/local/bin:/usr/bin:/bin" /bin/sh -c "command -v '$package'" 2>/dev/null || true)"
1255
- [[ -n "$target_path" ]] || { echo "shim target not yet on PATH: $package" >&2; return 1; }
1389
+ [[ -n "$target_path" ]] || { echo "ERROR shim target not yet on PATH: $package" >&2; return 1; }
1256
1390
  content="#!/bin/sh
1257
1391
  # Generated by global-tools -- do not edit.
1258
1392
  exec '$target_path' \"\$@\"
@@ -1270,7 +1404,7 @@ exec '$target_path' \"\$@\"
1270
1404
  alias_name="$(basename "$executable")"
1271
1405
  resolved_alias="$(sudo -u "$administrator" -H env PATH="/opt/homebrew/bin:/home/linuxbrew/.linuxbrew/bin:/usr/local/bin:/usr/bin:/bin" /bin/sh -c "command -v '$alias_name'" 2>/dev/null || true)"
1272
1406
  [[ "$resolved_alias" == "$executable" ]] || {
1273
- echo "$tool_id shim written but not resolving from global PATH as '$alias_name' (got: ${resolved_alias:-nothing})" >&2
1407
+ echo "ERROR $tool_id shim written but not resolving from global PATH as '$alias_name' (got: ${resolved_alias:-nothing})" >&2
1274
1408
  rc=1
1275
1409
  }
1276
1410
  return "$rc"
@@ -1283,7 +1417,7 @@ managed_repo_script_marker='# Managed by adl global-tools.'
1283
1417
  # real, previously-self-installed files as "unmanaged", so apply mode refused
1284
1418
  # to update them ("refusing to replace unmanaged or symlinked managed script
1285
1419
  # target") instead of replacing them with the renamed source -- seen live on
1286
- # M3's `fleet-cutover.sh --apply` for /usr/local/bin/git and
1420
+ # M3's `packages/fleet/scripts/fleet-cutover.sh --apply` for /usr/local/bin/git and
1287
1421
  # /usr/local/bin/git-ado. A target carrying EITHER line is this writer's own
1288
1422
  # prior output and must still be treated as managed; a target carrying
1289
1423
  # neither (or a symlink) must keep being refused.
@@ -1316,16 +1450,18 @@ write_one_repo_script() {
1316
1450
  source="$(jq -r --arg id "$tool_id" '.[$id].source' "$resolved_file")"
1317
1451
  echo "ensure managed repo script: $tool_id (repo-script:$package -> $executable)"
1318
1452
  [[ "$executable" =~ ^/usr/local/bin/[A-Za-z0-9][A-Za-z0-9._-]*$ ]] || {
1319
- echo "unsafe managed script target: $executable" >&2
1453
+ echo "ERROR unsafe managed script target: $executable" >&2
1320
1454
  return 1
1321
1455
  }
1322
1456
  [[ "$source" != /* && "$source" != *..* ]] || {
1323
- echo "unsafe managed script source: $source" >&2
1457
+ echo "ERROR unsafe managed script source: $source" >&2
1324
1458
  return 1
1325
1459
  }
1326
1460
  source_path="$repo_root/$source"
1327
- [[ -f "$source_path" && ! -L "$source_path" && -x "$source_path" ]] || {
1328
- echo "managed script source must be an executable regular non-symlink file: $source_path" >&2
1461
+ # -f, not -x: bun leaves every shipped file but the `bin` targets 0644, and the
1462
+ # copy below is installed 0755.
1463
+ [[ -f "$source_path" && ! -L "$source_path" ]] || {
1464
+ echo "ERROR managed script source must be a regular non-symlink file: $source_path" >&2
1329
1465
  return 1
1330
1466
  }
1331
1467
  if [[ "$mode" != apply ]]; then
@@ -1339,10 +1475,10 @@ write_one_repo_script() {
1339
1475
  fi
1340
1476
  return 0
1341
1477
  fi
1342
- [[ "$(id -u)" -eq 0 ]] || { echo "apply mode must run as root" >&2; exit 1; }
1478
+ [[ "$(id -u)" -eq 0 ]] || { echo "ERROR apply mode must run as root" >&2; exit 1; }
1343
1479
  if [[ -e "$executable" || -L "$executable" ]]; then
1344
1480
  is_managed_repo_script "$executable" || {
1345
- echo "refusing to replace unmanaged or symlinked managed script target: $executable" >&2
1481
+ echo "ERROR refusing to replace unmanaged or symlinked managed script target: $executable" >&2
1346
1482
  return 1
1347
1483
  }
1348
1484
  fi
@@ -1361,7 +1497,7 @@ write_one_repo_script() {
1361
1497
  echo "backed up previous managed repo script to $repo_script_backup"
1362
1498
  record_backup "$executable" "$repo_script_backup"
1363
1499
  else
1364
- echo "could not back up managed repo script before replacing it: $executable" >&2
1500
+ echo "ERROR could not back up managed repo script before replacing it: $executable" >&2
1365
1501
  return 1
1366
1502
  fi
1367
1503
  fi
@@ -1371,7 +1507,7 @@ write_one_repo_script() {
1371
1507
  alias_name="$(basename "$executable")"
1372
1508
  resolved_alias="$(sudo -u "$administrator" -H env PATH="/usr/local/bin:/opt/homebrew/bin:/home/linuxbrew/.linuxbrew/bin:/usr/bin:/bin" /bin/sh -c 'command -v "$1"' sh "$alias_name" 2>/dev/null || true)"
1373
1509
  [[ "$resolved_alias" == "$executable" ]] || {
1374
- echo "$tool_id managed script installed but not resolving from global PATH as '$alias_name' (got: ${resolved_alias:-nothing})" >&2
1510
+ echo "ERROR $tool_id managed script installed but not resolving from global PATH as '$alias_name' (got: ${resolved_alias:-nothing})" >&2
1375
1511
  rc=1
1376
1512
  }
1377
1513
  return "$rc"
@@ -1380,14 +1516,14 @@ write_one_repo_script() {
1380
1516
  remove_one_repo_script() {
1381
1517
  local executable="$1"
1382
1518
  [[ "$executable" =~ ^/usr/local/bin/[A-Za-z0-9][A-Za-z0-9._-]*$ ]] || {
1383
- echo "refusing unsafe managed script removal target: $executable" >&2
1519
+ echo "ERROR refusing unsafe managed script removal target: $executable" >&2
1384
1520
  return 1
1385
1521
  }
1386
1522
  if [[ ! -e "$executable" && ! -L "$executable" ]]; then
1387
1523
  return 0
1388
1524
  fi
1389
1525
  is_managed_repo_script "$executable" || {
1390
- echo "refusing to remove unmanaged or symlinked managed script target: $executable" >&2
1526
+ echo "ERROR refusing to remove unmanaged or symlinked managed script target: $executable" >&2
1391
1527
  return 1
1392
1528
  }
1393
1529
  rm -f -- "$executable"
@@ -1400,7 +1536,7 @@ repo_dir_source_hash() {
1400
1536
  local source_dir="$1" hash
1401
1537
  if repo_root_is_git; then
1402
1538
  hash="$(repo_git rev-parse --verify --quiet "HEAD:$source_dir")" || {
1403
- echo "repo-dir source is not tracked at HEAD: $source_dir" >&2
1539
+ echo "ERROR repo-dir source is not tracked at HEAD: $source_dir" >&2
1404
1540
  return 1
1405
1541
  }
1406
1542
  else
@@ -1452,7 +1588,7 @@ managed_repo_dir_marker='.adl-managed-repo-dir'
1452
1588
  # have to provide.
1453
1589
  run_repo_dir_sync() {
1454
1590
  local tool_id="$1" source="$2" build_dir="$3" npm_install="$4" bun_bin
1455
- [[ "$(id -u)" -eq 0 ]] || { echo "apply mode must run as root" >&2; exit 1; }
1591
+ [[ "$(id -u)" -eq 0 ]] || { echo "ERROR apply mode must run as root" >&2; exit 1; }
1456
1592
  if repo_root_is_git; then
1457
1593
  repo_git archive "HEAD:$source" | tar -x -C "$build_dir"
1458
1594
  else
@@ -1463,8 +1599,8 @@ run_repo_dir_sync() {
1463
1599
  fi
1464
1600
  if [[ "$npm_install" == true ]]; then
1465
1601
  bun_bin="$(command -v bun || true)"
1466
- [[ -n "$bun_bin" ]] || { echo "repo-dir $tool_id requires bun on PATH for npmInstall; ensure bun installs before this tool" >&2; return 1; }
1467
- (cd "$build_dir" && "$bun_bin" install) || { echo "repo-dir $tool_id: bun install failed" >&2; return 1; }
1602
+ [[ -n "$bun_bin" ]] || { echo "ERROR repo-dir $tool_id requires bun on PATH for npmInstall; ensure bun installs before this tool" >&2; return 1; }
1603
+ (cd "$build_dir" && "$bun_bin" install) || { echo "ERROR repo-dir $tool_id: bun install failed" >&2; return 1; }
1468
1604
  # agent-skills (the only npmInstall:true source today) ships no `dist/`
1469
1605
  # in git -- every one of its script entry points imports from
1470
1606
  # `../../dist/index.js` (confirmed empirically, all four: run-sync,
@@ -1474,7 +1610,7 @@ run_repo_dir_sync() {
1474
1610
  # the package's own declared `build` script, exactly as a developer
1475
1611
  # would, whenever the synced package.json has one.
1476
1612
  if [[ "$(jq -r '.scripts.build // empty' "$build_dir/package.json" 2>/dev/null)" != "" ]]; then
1477
- (cd "$build_dir" && "$bun_bin" run build) || { echo "repo-dir $tool_id: bun run build failed" >&2; return 1; }
1613
+ (cd "$build_dir" && "$bun_bin" run build) || { echo "ERROR repo-dir $tool_id: bun run build failed" >&2; return 1; }
1478
1614
  fi
1479
1615
  fi
1480
1616
  }
@@ -1535,16 +1671,16 @@ write_one_repo_dir() {
1535
1671
  npm_install="$(jq -r --arg id "$tool_id" '.[$id].npmInstall // false' "$resolved_file")"
1536
1672
  echo "ensure repo dir: $tool_id (repo-dir:$package -> $target)"
1537
1673
  [[ "$target" == "$repo_dir_libexec_root"/* ]] || {
1538
- echo "unsafe repo-dir target: $target" >&2
1674
+ echo "ERROR unsafe repo-dir target: $target" >&2
1539
1675
  return 1
1540
1676
  }
1541
1677
  [[ "$source" != /* && "$source" != *..* ]] || {
1542
- echo "unsafe repo-dir source: $source" >&2
1678
+ echo "ERROR unsafe repo-dir source: $source" >&2
1543
1679
  return 1
1544
1680
  }
1545
1681
  source_path="$repo_root/$source"
1546
1682
  [[ -d "$source_path" ]] || {
1547
- echo "repo-dir source missing: $source_path" >&2
1683
+ echo "ERROR repo-dir source missing: $source_path" >&2
1548
1684
  return 1
1549
1685
  }
1550
1686
  source_hash="$(repo_dir_source_hash "$source")" || return 1
@@ -1588,7 +1724,7 @@ write_one_repo_dir() {
1588
1724
  fi
1589
1725
  fi
1590
1726
  [[ -e "$target/$executable" ]] || {
1591
- echo "$tool_id repo dir installed but entry missing: $target/$executable" >&2
1727
+ echo "ERROR $tool_id repo dir installed but entry missing: $target/$executable" >&2
1592
1728
  rc=1
1593
1729
  }
1594
1730
  return "$rc"
@@ -1597,12 +1733,12 @@ write_one_repo_dir() {
1597
1733
  remove_one_repo_dir() {
1598
1734
  local target="$1"
1599
1735
  [[ "$target" == "$repo_dir_libexec_root"/* ]] || {
1600
- echo "refusing unsafe repo-dir removal target: $target" >&2
1736
+ echo "ERROR refusing unsafe repo-dir removal target: $target" >&2
1601
1737
  return 1
1602
1738
  }
1603
1739
  [[ -e "$target" ]] || return 0
1604
1740
  [[ -f "$target/$managed_repo_dir_marker" ]] || {
1605
- echo "refusing to remove unmanaged repo dir: $target" >&2
1741
+ echo "ERROR refusing to remove unmanaged repo dir: $target" >&2
1606
1742
  return 1
1607
1743
  }
1608
1744
  rm -rf -- "$target"
@@ -1680,10 +1816,31 @@ executable_claimed_by_current_manifest() {
1680
1816
  # into its own function, like install_one_tool, so
1681
1817
  # packages/fleet/tests/test-manifest.sh can exercise it directly through the
1682
1818
  # RECONCILE_SOURCE_ONLY seam instead of only through a real apply run.
1819
+ remove_release_archive_executable() {
1820
+ local executable="$1" label="$2" target="$executable"
1821
+ [[ "$executable" =~ ^/usr/local/bin/[A-Za-z0-9][A-Za-z0-9._-]*$ ]] || {
1822
+ echo "ERROR refusing unsafe release archive removal target: $executable" >&2
1823
+ return 1
1824
+ }
1825
+ if [[ "${RECONCILE_SOURCE_ONLY:-0}" == 1 && -n "${RECONCILE_TEST_COMMAND_ROOT:-}" ]]; then
1826
+ target="${RECONCILE_TEST_COMMAND_ROOT%/}/${executable##*/}"
1827
+ fi
1828
+ if [[ "$mode" == apply ]]; then
1829
+ rm -f -- "$target" || return 1
1830
+ [[ ! -e "$target" && ! -L "$target" ]] || {
1831
+ echo "ERROR release archive executable remains after removal: $target" >&2
1832
+ return 1
1833
+ }
1834
+ echo "removed $label release executable: $executable"
1835
+ else
1836
+ echo "would remove $label release executable: $executable"
1837
+ fi
1838
+ }
1839
+
1683
1840
  remove_one_manifest_entry() {
1684
1841
  local tool_id="$1" manager package executable removed_repo_dir_target
1685
1842
  if jq -e --arg id "$tool_id" '.config.protected_tools | index($id)' "$manifest" >/dev/null; then
1686
- echo "refusing to remove protected bootstrap tool: $tool_id" >&2
1843
+ echo "ERROR refusing to remove protected bootstrap tool: $tool_id" >&2
1687
1844
  exit 1
1688
1845
  fi
1689
1846
  manager="$(jq -r --arg id "$tool_id" '.[$id].manager' "$previous_resolved_file")"
@@ -1734,30 +1891,14 @@ remove_one_manifest_entry() {
1734
1891
  # still cleanly uninstall it the next time it reconciles; previous
1735
1892
  # state is read straight from disk, never re-validated.
1736
1893
  release-archive)
1737
- [[ "$executable" == /usr/local/bin/* ]] || {
1738
- echo "refusing unsafe release archive removal target: $executable" >&2
1739
- exit 1
1740
- }
1741
- if [[ "$mode" == apply ]]; then
1742
- rm -f -- "$executable"
1743
- else
1744
- echo "would remove pinned release executable: $executable"
1745
- fi
1894
+ remove_release_archive_executable "$executable" pinned
1746
1895
  ;;
1747
1896
  release-archive-latest)
1748
- [[ "$executable" == /usr/local/bin/* ]] || {
1749
- echo "refusing unsafe release archive removal target: $executable" >&2
1750
- exit 1
1751
- }
1752
- if [[ "$mode" == apply ]]; then
1753
- rm -f -- "$executable"
1754
- else
1755
- echo "would remove latest release executable: $executable"
1756
- fi
1897
+ remove_release_archive_executable "$executable" latest
1757
1898
  ;;
1758
1899
  shim)
1759
1900
  [[ "$executable" == /usr/local/bin/* ]] || {
1760
- echo "refusing unsafe shim removal target: $executable" >&2
1901
+ echo "ERROR refusing unsafe shim removal target: $executable" >&2
1761
1902
  exit 1
1762
1903
  }
1763
1904
  if [[ "$mode" == apply ]]; then
@@ -1771,7 +1912,7 @@ remove_one_manifest_entry() {
1771
1912
  remove_one_repo_script "$executable"
1772
1913
  else
1773
1914
  [[ "$executable" =~ ^/usr/local/bin/[A-Za-z0-9][A-Za-z0-9._-]*$ ]] || {
1774
- echo "refusing unsafe managed script removal target: $executable" >&2
1915
+ echo "ERROR refusing unsafe managed script removal target: $executable" >&2
1775
1916
  exit 1
1776
1917
  }
1777
1918
  echo "would remove managed repo script: $executable"
@@ -1783,7 +1924,7 @@ remove_one_manifest_entry() {
1783
1924
  remove_one_repo_dir "$removed_repo_dir_target"
1784
1925
  else
1785
1926
  [[ "$removed_repo_dir_target" == "$repo_dir_libexec_root"/* ]] || {
1786
- echo "refusing unsafe repo-dir removal target: $removed_repo_dir_target" >&2
1927
+ echo "ERROR refusing unsafe repo-dir removal target: $removed_repo_dir_target" >&2
1787
1928
  exit 1
1788
1929
  }
1789
1930
  echo "would remove repo dir: $removed_repo_dir_target"
@@ -1791,7 +1932,7 @@ remove_one_manifest_entry() {
1791
1932
  ;;
1792
1933
  bun-compile)
1793
1934
  [[ "$executable" == /usr/local/bin/* ]] || {
1794
- echo "refusing unsafe bun binary removal target: $executable" >&2
1935
+ echo "ERROR refusing unsafe bun binary removal target: $executable" >&2
1795
1936
  exit 1
1796
1937
  }
1797
1938
  if [[ "$mode" == apply ]]; then
@@ -1802,34 +1943,18 @@ remove_one_manifest_entry() {
1802
1943
  ;;
1803
1944
  embedding-model)
1804
1945
  if [[ "$mode" == apply ]]; then
1805
- [[ "$(id -u)" -eq 0 ]] || { echo "apply mode must run as root" >&2; exit 1; }
1806
- command -v bun >/dev/null 2>&1 || { echo "embedding-model requires bun; ensure bun installs before this tool" >&2; exit 1; }
1946
+ [[ "$(id -u)" -eq 0 ]] || { echo "ERROR apply mode must run as root" >&2; exit 1; }
1947
+ command -v bun >/dev/null 2>&1 || { echo "ERROR embedding-model requires bun; ensure bun installs before this tool" >&2; exit 1; }
1807
1948
  (cd /tmp && sudo -u "$administrator" -H env PATH="${brew_bin%/*}:/usr/bin:/bin" \
1808
1949
  bun "$repo_root/packages/fleet/src/install-embedding-model.ts" uninstall </dev/null)
1809
1950
  else
1810
- echo "would remove embedding model cache: ~/.cache/adl-embedding/model (as $administrator)"
1951
+ echo "would remove embedding model cache: ~/.cache/enact-embedding/model (as $administrator)"
1811
1952
  fi
1812
1953
  ;;
1813
- *) echo "unsupported manager in previous manifest: $manager" >&2; exit 1 ;;
1954
+ *) echo "ERROR unsupported manager in previous manifest: $manager" >&2; exit 1 ;;
1814
1955
  esac
1815
1956
  }
1816
1957
 
1817
- # config.git_global_config: global git config for the administrator (the
1818
- # account whose HOME the per-user deployers below also write to). Apply runs git as
1819
- # that user (sudo -u -H) so ~/.gitconfig stays user-owned; plan/audit only
1820
- # read, as whoever invoked reconcile. Motivating entries: Azure DevOps SSH
1821
- # has no Git LFS support, and git-lfs's default pure-SSH `git-lfs-transfer`
1822
- # probe hangs forever against ssh.dev.azure.com, stalling every `git push` in
1823
- # the LFS pre-push hook.
1824
- run_git_config_global() {
1825
- if [[ "$mode" == apply ]]; then
1826
- [[ "$(id -u)" -eq 0 ]] || { echo "apply mode must run as root" >&2; exit 1; }
1827
- (cd /tmp && sudo -u "$git_config_user" -H git config --global "$@" </dev/null)
1828
- else
1829
- git config --global "$@" </dev/null
1830
- fi
1831
- }
1832
-
1833
1958
  # One-release migration for Linux state written by the retired two-user
1834
1959
  # contract. The previous applied manifest is the authority for the former
1835
1960
  # account and home, so no legacy identity remains configured after a successful
@@ -1848,7 +1973,7 @@ cleanup_legacy_linux_git_config() {
1848
1973
  legacy_home="$(jq -r --arg device "$device" '.linux.devices[$device].brew_user_home // empty' "$applied_manifest")"
1849
1974
  [[ -n "$legacy_user" && -n "$legacy_home" ]] || return 0
1850
1975
  [[ "$legacy_home" == /* && "$legacy_home" != / ]] || {
1851
- echo "invalid legacy per-user home in applied manifest: $legacy_home" >&2
1976
+ echo "ERROR invalid legacy per-user home in applied manifest: $legacy_home" >&2
1852
1977
  return 1
1853
1978
  }
1854
1979
  config_file="$legacy_home/.gitconfig"
@@ -1862,7 +1987,7 @@ cleanup_legacy_linux_git_config() {
1862
1987
  continue
1863
1988
  fi
1864
1989
  [[ "$rc" -eq 0 ]] || {
1865
- echo "failed to inspect legacy global git config: $key in $config_file (exit $rc)" >&2
1990
+ echo "ERROR failed to inspect legacy global git config: $key in $config_file (exit $rc)" >&2
1866
1991
  return "$rc"
1867
1992
  }
1868
1993
  if [[ "$mode" != apply ]]; then
@@ -1874,49 +1999,18 @@ cleanup_legacy_linux_git_config() {
1874
1999
  done
1875
2000
  }
1876
2001
 
1877
- # Prints the current global value of $1, or nothing when unset. git config
1878
- # --get exits 1 for a missing key; any other failure is real and propagates.
1879
- git_global_config_value() {
1880
- local key="$1" value rc=0
1881
- value="$(run_git_config_global --get "$key")" || rc=$?
1882
- if [[ "$rc" -eq 1 ]]; then
1883
- return 0
1884
- fi
1885
- [[ "$rc" -eq 0 ]] || { echo "git config --global --get $key failed (exit $rc)" >&2; return "$rc"; }
1886
- printf '%s\n' "$value"
1887
- }
1888
-
2002
+ # config.git_global_config + <platform>.common.git_global_config + the device's
2003
+ # git_user: the administrator's global git config, identical on every host
2004
+ # (Tarun, 2026-10-01). All logic lives in bin/git-defaults; undeclared keys are
2005
+ # never touched. Motivating entries: Azure DevOps SSH has no Git LFS support
2006
+ # (git-lfs's pure-SSH probe hangs against ssh.dev.azure.com), and GCM is the
2007
+ # credential helper on every host.
1889
2008
  reconcile_git_global_config() {
1890
- local key value current
1891
- while IFS=$'\t' read -r key value; do
1892
- [[ -n "$key" ]] || continue
1893
- current="$(git_global_config_value "$key")" || return 1
1894
- if [[ "$current" == "$value" ]]; then
1895
- echo "global git config already set: $key=$value"
1896
- elif [[ "$mode" != apply ]]; then
1897
- echo "would set global git config: $key=$value"
1898
- else
1899
- run_git_config_global "$key" "$value" || return 1
1900
- echo "set global git config: $key=$value"
1901
- fi
1902
- done < <(jq -r '.config.git_global_config | to_entries[] | [.key, .value] | @tsv' "$manifest")
1903
-
1904
- [[ "$have_previous" -eq 1 ]] || return 0
1905
- while IFS= read -r key; do
1906
- [[ -n "$key" ]] || continue
1907
- current="$(git_global_config_value "$key")" || return 1
1908
- [[ -n "$current" ]] || continue
1909
- if [[ "$mode" != apply ]]; then
1910
- echo "would unset global git config (explicit manifest deletion): $key"
1911
- else
1912
- run_git_config_global --unset-all "$key" || return 1
1913
- echo "explicit manifest deletion: unset global git config $key"
1914
- fi
1915
- done < <(jq -r --slurpfile current "$manifest" '
1916
- (.config.git_global_config // {}) | keys[] as $key
1917
- | select(($current[0].config.git_global_config | has($key)) | not)
1918
- | $key
1919
- ' "$applied_manifest")
2009
+ if [[ "$mode" == apply ]]; then
2010
+ [[ "$(id -u)" -eq 0 ]] || { echo "ERROR apply mode must run as root" >&2; exit 1; }
2011
+ fi
2012
+ "$BASH" "$repo_root/bin/git-defaults" --manifest "$manifest" --platform "$platform" \
2013
+ --device "$device" --mode "$mode" --administrator "$git_config_user"
1920
2014
  }
1921
2015
 
1922
2016
  # default-agent-file.ts and managed-settings.ts (Claude/Codex) MUST run
@@ -1931,7 +2025,7 @@ reconcile_git_global_config() {
1931
2025
  # "bun: command not found" abort with no explanation.
1932
2026
  require_bun_for_identity_files() {
1933
2027
  command -v bun >/dev/null 2>&1 || {
1934
- echo "global-tools: bun is required before default-agent-file/managed-settings; install bun first, then re-run reconcile" >&2
2028
+ echo "ERROR global-tools: bun is required before default-agent-file/managed-settings; install bun first, then re-run reconcile" >&2
1935
2029
  return 1
1936
2030
  }
1937
2031
  }
@@ -1945,9 +2039,11 @@ require_bun_for_identity_files() {
1945
2039
  # The PATH searched for a surface command intentionally extends past the
1946
2040
  # usual brew/system directories into the per-user install locations these
1947
2041
  # specific CLIs' own installers use (claude's and cursor's native installers,
1948
- # kimi's and opencode's curl-script installers) -- none of those are on the
1949
- # plain system PATH this file uses elsewhere for brew/npm/pip.
1950
- surface_cli_search_path="$administrator_home/.local/bin:$administrator_home/.kimi-code/bin:$administrator_home/.opencode/bin:/opt/homebrew/bin:/home/linuxbrew/.linuxbrew/bin:/usr/local/bin:/usr/bin:/bin"
2042
+ # kimi's curl-script installer) -- none of those are on the plain system PATH
2043
+ # this file uses elsewhere for brew/npm/pip. opencode is the exception (Tarun,
2044
+ # 2026-10-01): it comes from the npm registry through bun, so bun's global bin
2045
+ # directory is searched instead of the retired native ~/.opencode/bin.
2046
+ surface_cli_search_path="$administrator_home/.local/bin:$administrator_home/.kimi-code/bin:$administrator_home/.bun/bin:/opt/homebrew/bin:/home/linuxbrew/.linuxbrew/bin:/usr/local/bin:/usr/bin:/bin"
1951
2047
 
1952
2048
  # Resolves a surface to the real executable that will be run: an explicit
1953
2049
  # manifest `executable` (a fixed, known path -- e.g. hostinger's report-only
@@ -2016,6 +2112,173 @@ surface_update_error_reason() {
2016
2112
  fi
2017
2113
  }
2018
2114
 
2115
+ # bun-managed surface (Tarun, 2026-10-01, "we are not on GitHub"): opencode is installed and updated
2116
+ # from the npm registry package `opencode-ai` with `bun add -g opencode-ai@latest`, run as the
2117
+ # administrator -- never `opencode upgrade`, which asks the unauthenticated GitHub REST API for its
2118
+ # latest release and 403s under the fleet's shared-IP rate limit. Named so tests can stub the one
2119
+ # boundary that touches the machine; adl_bun_add_global (bin/lib/adl-package.sh) is the same call
2120
+ # the adl package install uses (the administrator's own ~/.bun, bun found through Homebrew).
2121
+ run_surface_bun_add() {
2122
+ local spec="$1" os
2123
+ case "$platform" in
2124
+ mac) os=Darwin ;;
2125
+ linux) os=Linux ;;
2126
+ *) echo "ERROR unsupported platform for a bun surface: $platform" >&2; return 1 ;;
2127
+ esac
2128
+ adl_bun_add_global "$administrator" "$os" "$administrator_home" "$spec" </dev/null
2129
+ }
2130
+
2131
+ # Finds every OTHER copy of a bun-managed surface command: each directory on the surface search
2132
+ # path plus the native installer's ~/.opencode/bin. Prints one TAB-separated record per distinct
2133
+ # copy, `kind<TAB>path<TAB>detail`, where kind is
2134
+ # native <home>/.opencode/bin/<command> (a regular executable the native installer wrote;
2135
+ # path is the bin DIRECTORY that is moved aside)
2136
+ # brew a symlink into <this host's Homebrew prefix>/Cellar/<command>/<version>/bin/<command>
2137
+ # npm a symlink into <prefix>/lib/node_modules/<package>/bin/<command>.exe whose
2138
+ # package.json names <package> (detail is the prefix)
2139
+ # unrecognised anything else (detail is where it really points)
2140
+ # A copy is retired only when it is positively one of the first three; the bun install itself is
2141
+ # skipped. Several PATH entries that reach one real file (/bin -> usr/bin) count once.
2142
+ classify_surface_copies() {
2143
+ local command_name="$1" package="$2" bun_exe="$3"
2144
+ local dirs=() dir path real bun_real seen=" " native_bin="$administrator_home/.opencode/bin" prefix pkg_dir brew_prefix
2145
+ brew_prefix="$(cd -P -- "${brew_bin%/bin/brew}" 2>/dev/null && pwd -P)" || brew_prefix="${brew_bin%/bin/brew}"
2146
+ IFS=: read -r -a dirs <<<"$surface_cli_search_path"
2147
+ dirs+=("$native_bin")
2148
+ bun_real="$(adl_realpath "$bun_exe" 2>/dev/null)" || bun_real="$bun_exe"
2149
+ for dir in "${dirs[@]}"; do
2150
+ [[ -n "$dir" ]] || continue
2151
+ path="$dir/$command_name"
2152
+ [[ -e "$path" || -L "$path" ]] || continue
2153
+ real="$(adl_realpath "$path" 2>/dev/null)" || real="$path"
2154
+ [[ "$real" != "$bun_real" ]] || continue
2155
+ case "$seen" in *" $real "*) continue ;; esac
2156
+ seen="$seen$real "
2157
+ if [[ "$path" == "$native_bin/$command_name" && -f "$path" && ! -L "$path" && -x "$path" ]]; then
2158
+ printf 'native\t%s\t%s\n' "$native_bin" "$path"
2159
+ elif [[ "$real" =~ ^(.*)/Cellar/${command_name}/[^/]+/bin/${command_name}$ && "${BASH_REMATCH[1]}" == "$brew_prefix" ]]; then
2160
+ printf 'brew\t%s\t%s\n' "$path" "${BASH_REMATCH[1]}"
2161
+ elif [[ "$real" =~ ^(.*)/lib/node_modules/${package}/bin/${command_name}\.exe$ ]]; then
2162
+ prefix="${BASH_REMATCH[1]}"
2163
+ pkg_dir="$prefix/lib/node_modules/$package"
2164
+ if [[ -f "$pkg_dir/package.json" ]] && jq -e --arg name "$package" '.name == $name' "$pkg_dir/package.json" >/dev/null 2>&1; then
2165
+ printf 'npm\t%s\t%s\n' "$path" "$prefix"
2166
+ else
2167
+ printf 'unrecognised\t%s\t%s\n' "$path" "$real"
2168
+ fi
2169
+ else
2170
+ printf 'unrecognised\t%s\t%s\n' "$path" "$real"
2171
+ fi
2172
+ done
2173
+ }
2174
+
2175
+ # Retires every other copy of a bun-managed surface so the host has exactly one, idempotently:
2176
+ # the native installer's ~/.opencode/bin is moved ASIDE under <state dir>/retired-opencode/<UTC
2177
+ # stamp>/native-bin (never deleted; ~/.opencode's own plugin files and ~/.config/opencode are not
2178
+ # touched), a Homebrew formula goes through `brew uninstall` as the Homebrew owner (run_brew, never
2179
+ # sudo brew), and an npm global goes through `npm uninstall -g --prefix <its real prefix>`. Anything
2180
+ # not positively identified is an ERROR and NOTHING is removed. Plan mode lists what apply would do.
2181
+ retire_other_surface_copies() {
2182
+ local name="$1" command_name="$2" package="$3" bun_exe="$4"
2183
+ local kinds=() paths=() details=() kind path detail i unrecognised=0 aside=""
2184
+ while IFS=$'\t' read -r kind path detail; do
2185
+ [[ -n "$kind" ]] || continue
2186
+ kinds+=("$kind"); paths+=("$path"); details+=("$detail")
2187
+ done < <(classify_surface_copies "$command_name" "$package" "$bun_exe")
2188
+ [[ "${#kinds[@]}" -gt 0 ]] || return 0
2189
+ for ((i = 0; i < ${#kinds[@]}; i++)); do
2190
+ [[ "${kinds[i]}" == unrecognised ]] || continue
2191
+ unrecognised=1
2192
+ if [[ "$mode" != apply ]]; then
2193
+ echo "global-tools: would FAIL: unrecognised $command_name at ${paths[i]} (-> ${details[i]}); not a copy reconcile knows how to retire"
2194
+ else
2195
+ echo "ERROR surface $name: unrecognised $command_name at ${paths[i]} (-> ${details[i]}); not a copy reconcile knows how to retire, so nothing was removed" >&2
2196
+ fi
2197
+ done
2198
+ if [[ "$unrecognised" -eq 1 ]]; then
2199
+ [[ "$mode" != apply ]] || return 1
2200
+ return 0
2201
+ fi
2202
+ if [[ "$mode" != apply ]]; then
2203
+ for ((i = 0; i < ${#kinds[@]}; i++)); do
2204
+ case "${kinds[i]}" in
2205
+ native) echo "global-tools: would retire $command_name copy: move ${paths[i]} aside (native installer)" ;;
2206
+ brew) echo "global-tools: would retire $command_name copy: brew uninstall $command_name (Homebrew formula, ${paths[i]})" ;;
2207
+ npm) echo "global-tools: would retire $command_name copy: npm uninstall -g --prefix ${details[i]} $package (${paths[i]})" ;;
2208
+ esac
2209
+ done
2210
+ return 0
2211
+ fi
2212
+ for ((i = 0; i < ${#kinds[@]}; i++)); do
2213
+ case "${kinds[i]}" in
2214
+ native)
2215
+ [[ -n "$aside" ]] || aside="$state_dir/retired-$command_name/$(date -u +%Y%m%dT%H%M%SZ)"
2216
+ mkdir -p -- "$aside" || return 1
2217
+ mv -- "${paths[i]}" "$aside/native-bin" || { echo "ERROR surface $name: could not move ${paths[i]} aside to $aside" >&2; return 1; }
2218
+ echo "global-tools: retiring $command_name copy: moved ${paths[i]} aside to $aside/native-bin"
2219
+ ;;
2220
+ brew)
2221
+ run_brew uninstall "$command_name" || { echo "ERROR surface $name: brew uninstall $command_name failed" >&2; return 1; }
2222
+ [[ ! -e "${paths[i]}" && ! -L "${paths[i]}" ]] || { echo "ERROR surface $name: ${paths[i]} is still present after brew uninstall $command_name" >&2; return 1; }
2223
+ echo "global-tools: retiring $command_name copy: brew uninstalled $command_name (${paths[i]})"
2224
+ ;;
2225
+ npm)
2226
+ run_npm uninstall -g --prefix "${details[i]}" "$package" || { echo "ERROR surface $name: npm uninstall -g $package failed" >&2; return 1; }
2227
+ [[ ! -e "${paths[i]}" && ! -L "${paths[i]}" ]] || { echo "ERROR surface $name: ${paths[i]} is still present after npm uninstall -g $package" >&2; return 1; }
2228
+ echo "global-tools: retiring $command_name copy: npm uninstalled $package from ${details[i]} (${paths[i]})"
2229
+ ;;
2230
+ esac
2231
+ done
2232
+ }
2233
+
2234
+ # Installs or updates a bun-managed surface (see run_surface_bun_add): one `bun add -g
2235
+ # <package>@latest` covers both "absent" and "present", then the other copies are retired and the
2236
+ # command the administrator's PATH resolves is checked to be bun's. Fails loud at every step; a
2237
+ # version is never pinned.
2238
+ reconcile_bun_surface_cli() {
2239
+ local name="$1" command_name="$2" package="$3"
2240
+ local bun_exe="$administrator_home/.bun/bin/$command_name" spec="$package@latest"
2241
+ local old_version="" new_version update_output update_status=0 resolved
2242
+ if [[ "$mode" != apply ]]; then
2243
+ echo "would install or update surface from the npm registry: $name (bun add -g $spec as $administrator)"
2244
+ retire_other_surface_copies "$name" "$command_name" "$package" "$bun_exe"
2245
+ return 0
2246
+ fi
2247
+ [[ "$(id -u)" -eq 0 ]] || { echo "ERROR apply mode must run as root" >&2; exit 1; }
2248
+ if [[ -x "$bun_exe" ]]; then old_version="$(surface_cli_version "$bun_exe")"; fi
2249
+ update_output="$(run_with_timeout "$SURFACE_UPDATE_TIMEOUT_SECONDS" run_surface_bun_add "$spec" 2>&1)" || update_status=$?
2250
+ printf '%s\n' "$update_output"
2251
+ if [[ "$update_status" -eq 124 ]]; then
2252
+ echo "ERROR surface update timed out: $name" >&2
2253
+ return 1
2254
+ elif [[ "$update_status" -ne 0 ]]; then
2255
+ echo "ERROR surface update failed: $name (exit $update_status)" >&2
2256
+ return 1
2257
+ fi
2258
+ # opencode-ai's bin is a stub that exits 1 until its postinstall puts the platform binary in
2259
+ # place, and bun skips an untrusted package's postinstall: a bun add that "succeeded" but left
2260
+ # a command that cannot report its version is not an install.
2261
+ new_version="$(surface_cli_version "$bun_exe")"
2262
+ if [[ -z "$new_version" ]]; then
2263
+ echo "ERROR surface $name: bun add -g $spec succeeded but '$bun_exe --version' printed nothing; the package's postinstall did not run (check 'bun pm default-trusted' lists $package, or add it to the global trustedDependencies)" >&2
2264
+ return 1
2265
+ fi
2266
+ retire_other_surface_copies "$name" "$command_name" "$package" "$bun_exe" || return 1
2267
+ resolved="$(resolve_surface_executable "$command_name" "")"
2268
+ if [[ "$resolved" != "$bun_exe" ]]; then
2269
+ echo "ERROR surface $name: $command_name on ${administrator}'s PATH is ${resolved:-missing}, not the bun-installed $bun_exe" >&2
2270
+ return 1
2271
+ fi
2272
+ if [[ -z "$old_version" ]]; then
2273
+ echo "surface $name: installed $new_version ($package from the npm registry)"
2274
+ elif [[ "$old_version" != "$new_version" ]]; then
2275
+ echo "surface $name: $old_version -> $new_version"
2276
+ else
2277
+ echo "surface $name: already latest ($new_version)"
2278
+ fi
2279
+ return 0
2280
+ }
2281
+
2019
2282
  # Reconciles one declared surface_clis entry. report_only (hostinger's loose
2020
2283
  # /root/codex, unknown install method) is version-reported only -- run_surface_update
2021
2284
  # is never called for it, by construction (this branch returns before
@@ -2027,17 +2290,35 @@ reconcile_one_surface_cli() {
2027
2290
  local name="$1" command_name="$2" executable_override="$3" report_only="$4" manager="$5"
2028
2291
  shift 5
2029
2292
  local update_args=("$@")
2293
+ if [[ "$manager" == bun ]]; then
2294
+ # For a bun surface the one trailing argument is the npm registry package, not an update argv.
2295
+ reconcile_bun_surface_cli "$name" "$command_name" "${update_args[0]}"
2296
+ return
2297
+ fi
2030
2298
  if [[ "$manager" == brew ]]; then
2031
2299
  if [[ "$mode" != apply ]]; then
2300
+ if [[ "$name" == */* ]]; then
2301
+ echo "would trust Homebrew item: $name (cask)"
2302
+ fi
2032
2303
  echo "would ensure Homebrew cask: $name (brew install --cask $name; brew upgrade --cask $name)"
2033
2304
  return 0
2034
2305
  fi
2035
- [[ "$(id -u)" -eq 0 ]] || { echo "apply mode must run as root" >&2; exit 1; }
2306
+ [[ "$(id -u)" -eq 0 ]] || { echo "ERROR apply mode must run as root" >&2; exit 1; }
2307
+ # A fully-qualified cask name comes from a non-core tap, which Homebrew
2308
+ # >=6 refuses to load until the ITEM is trusted (same tap-trust grant as
2309
+ # the per-tool brew path in install_one_tool; the kind is certain here).
2310
+ # Bare names are core casks, trusted by default -- no grant.
2311
+ if [[ "$name" == */* ]]; then
2312
+ brew_query trust | awk '{$1=$1};1' | grep -Fqx "$name" || run_brew trust --cask "$name" || {
2313
+ echo "ERROR surface $name: brew trust --cask $name failed" >&2
2314
+ return 1
2315
+ }
2316
+ fi
2036
2317
  local installed_casks query_status=0 cask_action=() update_output update_status=0
2037
2318
  installed_casks="$(brew_query list --cask --versions 2>&1)" || query_status=$?
2038
2319
  if [[ "$query_status" -ne 0 ]]; then
2039
- printf '%s\n' "$installed_casks" >&2
2040
- echo "brew list --cask --versions failed for surface $name (exit $query_status)" >&2
2320
+ printf 'ERROR brew list --cask --versions output: %s\n' "$installed_casks" >&2
2321
+ echo "ERROR brew list --cask --versions failed for surface $name (exit $query_status)" >&2
2041
2322
  return 1
2042
2323
  fi
2043
2324
  if awk -v cask="$name" '$1 == cask { found = 1 } END { exit !found }' <<<"$installed_casks"; then
@@ -2048,10 +2329,10 @@ reconcile_one_surface_cli() {
2048
2329
  update_output="$(run_with_timeout "$SURFACE_UPDATE_TIMEOUT_SECONDS" run_brew ${cask_action[@]+"${cask_action[@]}"} 2>&1)" || update_status=$?
2049
2330
  printf '%s\n' "$update_output"
2050
2331
  if [[ "$update_status" -eq 124 ]]; then
2051
- echo "surface update timed out: $name" >&2
2332
+ echo "ERROR surface update timed out: $name" >&2
2052
2333
  return 1
2053
2334
  elif [[ "$update_status" -ne 0 ]]; then
2054
- echo "surface update failed: $name (exit $update_status)" >&2
2335
+ echo "ERROR surface update failed: $name (exit $update_status)" >&2
2055
2336
  return 1
2056
2337
  fi
2057
2338
  # Same broadened check as the native/npm path below (independent
@@ -2068,9 +2349,9 @@ reconcile_one_surface_cli() {
2068
2349
  # coordinator pass, 2026-09-30): the same `cask_action[0]` that already
2069
2350
  # picks the real brew argv above also names which one failed.
2070
2351
  if [[ "${cask_action[0]}" == install ]]; then
2071
- echo "surface $name: cask install failed ($update_error_line)" >&2
2352
+ echo "ERROR surface $name: cask install failed ($update_error_line)" >&2
2072
2353
  else
2073
- echo "surface $name: cask upgrade failed ($update_error_line)" >&2
2354
+ echo "ERROR surface $name: cask upgrade failed ($update_error_line)" >&2
2074
2355
  fi
2075
2356
  return 1
2076
2357
  fi
@@ -2089,7 +2370,7 @@ reconcile_one_surface_cli() {
2089
2370
  fi
2090
2371
  return 0
2091
2372
  fi
2092
- [[ "$(id -u)" -eq 0 ]] || { echo "apply mode must run as root" >&2; exit 1; }
2373
+ [[ "$(id -u)" -eq 0 ]] || { echo "ERROR apply mode must run as root" >&2; exit 1; }
2093
2374
  local resolved
2094
2375
  resolved="$(resolve_surface_executable "$command_name" "$executable_override")"
2095
2376
  if [[ -z "$resolved" ]]; then
@@ -2107,10 +2388,10 @@ reconcile_one_surface_cli() {
2107
2388
  update_output="$(run_with_timeout "$SURFACE_UPDATE_TIMEOUT_SECONDS" run_surface_update "$resolved" ${update_args[@]+"${update_args[@]}"} 2>&1)" || update_status=$?
2108
2389
  printf '%s\n' "$update_output"
2109
2390
  if [[ "$update_status" -eq 124 ]]; then
2110
- echo "surface update timed out: $name" >&2
2391
+ echo "ERROR surface update timed out: $name" >&2
2111
2392
  return 1
2112
2393
  elif [[ "$update_status" -ne 0 ]]; then
2113
- echo "surface update failed: $name (exit $update_status)" >&2
2394
+ echo "ERROR surface update failed: $name (exit $update_status)" >&2
2114
2395
  return 1
2115
2396
  fi
2116
2397
  # A surface's update command can exit 0 while its own output says the
@@ -2139,7 +2420,7 @@ reconcile_one_surface_cli() {
2139
2420
  new_version="$(surface_cli_version "$resolved")"
2140
2421
  update_error_line="$(surface_update_error_reason "$update_output")"
2141
2422
  if [[ -z "$new_version" ]]; then
2142
- echo "surface $name: update ran but version unreadable (${update_error_line:-"--version produced no output"}); was ${old_version:-unknown}" >&2
2423
+ echo "WARN surface $name: update ran but version unreadable (${update_error_line:-"--version produced no output"}); was ${old_version:-unknown}" >&2
2143
2424
  return 1
2144
2425
  fi
2145
2426
  if [[ "$old_version" != "$new_version" ]]; then
@@ -2151,7 +2432,7 @@ reconcile_one_surface_cli() {
2151
2432
  return 0
2152
2433
  fi
2153
2434
  if [[ -n "$update_error_line" ]]; then
2154
- echo "surface $name: update check failed ($update_error_line); kept ${old_version:-unknown}" >&2
2435
+ echo "WARN surface $name: update check failed ($update_error_line); kept ${old_version:-unknown}" >&2
2155
2436
  return 1
2156
2437
  fi
2157
2438
  echo "surface $name: already latest ($new_version)"
@@ -2159,7 +2440,7 @@ reconcile_one_surface_cli() {
2159
2440
  }
2160
2441
 
2161
2442
  # Loops over this device's declared surface_clis (absent or empty -> silently
2162
- # does nothing, same convention as launch_agents/scratch_repos). A failure is
2443
+ # does nothing, same convention as scratch_repos). A failure is
2163
2444
  # collected into failed_tools by id "surface-<name>", exactly like every other
2164
2445
  # apply-mode step's continue-on-failure contract in this file.
2165
2446
  reconcile_surface_clis() {
@@ -2180,17 +2461,88 @@ reconcile_surface_clis() {
2180
2461
  report_only="$(jq -r --argjson i "$i" --arg platform "$platform" --arg device "$device" \
2181
2462
  '.[$platform].devices[$device].surface_clis[$i].report_only // false' "$manifest")"
2182
2463
  update_args=()
2183
- while IFS= read -r arg; do
2184
- [[ -n "$arg" ]] && update_args+=("$arg")
2185
- done < <(jq -r --argjson i "$i" --arg platform "$platform" --arg device "$device" \
2186
- '.[$platform].devices[$device].surface_clis[$i].update_args // [] | .[]' "$manifest")
2464
+ if [[ "$manager" == bun ]]; then
2465
+ update_args+=("$(jq -r --argjson i "$i" --arg platform "$platform" --arg device "$device" \
2466
+ '.[$platform].devices[$device].surface_clis[$i].package // empty' "$manifest")")
2467
+ else
2468
+ while IFS= read -r arg; do
2469
+ [[ -n "$arg" ]] && update_args+=("$arg")
2470
+ done < <(jq -r --argjson i "$i" --arg platform "$platform" --arg device "$device" \
2471
+ '.[$platform].devices[$device].surface_clis[$i].update_args // [] | .[]' "$manifest")
2472
+ fi
2187
2473
  if ! reconcile_one_surface_cli "$name" "$command_name" "$executable_override" "$report_only" "$manager" ${update_args[@]+"${update_args[@]}"}; then
2188
- echo "global-tools: TOOL INSTALL FAILED: surface-$name" >&2
2474
+ echo "ERROR global-tools: TOOL INSTALL FAILED: surface-$name" >&2
2189
2475
  failed_tools+=("surface-$name")
2190
2476
  fi
2191
2477
  done
2192
2478
  }
2193
2479
 
2480
+ # retire_git_maintain (2026-09-30, Tarun: "one binary controlling everything"):
2481
+ # the git-maintain user LaunchAgent (com.adl.git-maintain) and its compiled
2482
+ # binary are retired; repository maintenance is `adl reconcile`'s own step
2483
+ # (bin/reconcile-host -> `adl maintain`). M3 and M5 already have the agent
2484
+ # installed and loaded, so this cleans it up actively rather than leaving a
2485
+ # job that scans ~/Documents on its own timer:
2486
+ # - boot out gui/<administrator uid>/com.adl.git-maintain if loaded, and fail
2487
+ # loud if it is still loaded afterwards (nothing is moved in that case);
2488
+ # - move the plist, the compiled binary and its source-hash marker ASIDE
2489
+ # (never deleted) under <state dir>/retired-git-maintain/<UTC stamp>/,
2490
+ # logging each one.
2491
+ # Already gone means no change and no output; plan/audit only list what they
2492
+ # would do; Linux never had it. Returns non-zero (after an ERROR line) on a
2493
+ # failure; the caller records it like any other failed step.
2494
+ retire_git_maintain() {
2495
+ [[ "$platform" == mac ]] || return 0
2496
+ local label=com.adl.git-maintain uid loaded=0 path aside
2497
+ local leftovers=()
2498
+ local agent_plist="$administrator_home/Library/LaunchAgents/$label.plist"
2499
+ local binary="$repo_dir_libexec_root/git-maintain"
2500
+ local hash_marker="$state_dir/launch-agents/git-maintain.source-hash"
2501
+
2502
+ if uid="$(id -u "$administrator" 2>/dev/null)"; then
2503
+ if launchctl print "gui/$uid/$label" >/dev/null 2>&1; then loaded=1; fi
2504
+ elif [[ "$mode" == apply ]]; then
2505
+ echo "ERROR global-tools: cannot resolve the uid of $administrator to retire git-maintain" >&2
2506
+ return 1
2507
+ else
2508
+ # plan/audit on a machine without that account (a test, a preview): the
2509
+ # leftover files can still be listed, but there is nothing to ask launchd.
2510
+ uid="<uid of $administrator>"
2511
+ fi
2512
+ for path in "$agent_plist" "$binary" "$hash_marker"; do
2513
+ if [[ -e "$path" || -L "$path" ]]; then leftovers+=("$path"); fi
2514
+ done
2515
+ if [[ "$loaded" -eq 0 && "${#leftovers[@]}" -eq 0 ]]; then return 0; fi
2516
+
2517
+ if [[ "$mode" != apply ]]; then
2518
+ if [[ "$loaded" -eq 1 ]]; then echo "global-tools: would retire git-maintain: boot out gui/$uid/$label"; fi
2519
+ for path in ${leftovers[@]+"${leftovers[@]}"}; do
2520
+ echo "global-tools: would retire git-maintain: move $path aside"
2521
+ done
2522
+ return 0
2523
+ fi
2524
+
2525
+ if [[ "$loaded" -eq 1 ]]; then
2526
+ if ! launchctl bootout "gui/$uid/$label"; then
2527
+ echo "ERROR global-tools: could not boot out gui/$uid/$label; git-maintain is not retired" >&2
2528
+ return 1
2529
+ fi
2530
+ if launchctl print "gui/$uid/$label" >/dev/null 2>&1; then
2531
+ echo "ERROR global-tools: gui/$uid/$label is still loaded after bootout; git-maintain is not retired" >&2
2532
+ return 1
2533
+ fi
2534
+ echo "global-tools: retiring git-maintain: booted out gui/$uid/$label"
2535
+ fi
2536
+ if [[ "${#leftovers[@]}" -gt 0 ]]; then
2537
+ aside="$state_dir/retired-git-maintain/$(date -u +%Y%m%dT%H%M%SZ)"
2538
+ mkdir -p -- "$aside"
2539
+ for path in "${leftovers[@]}"; do
2540
+ mv -- "$path" "$aside/" || { echo "ERROR global-tools: could not move $path aside to $aside" >&2; return 1; }
2541
+ echo "global-tools: retiring git-maintain: moved $path aside to $aside"
2542
+ done
2543
+ fi
2544
+ }
2545
+
2194
2546
  # Test-only escape hatch: source this script with RECONCILE_SOURCE_ONLY=1 to
2195
2547
  # load every function definition above (and the arg-parsing / manifest
2196
2548
  # validation / platform-and-tool-resolution state already computed by this
@@ -2215,7 +2567,7 @@ scratch_reap_failed=0
2215
2567
  available_kib="$(df -Pk / | awk 'NR == 2 {print $4}')"
2216
2568
  required_kib="$((minimum_free_gib * 1024 * 1024))"
2217
2569
  if [[ "$available_kib" -lt "$required_kib" ]]; then
2218
- echo "global-tools: only $((available_kib / 1024 / 1024)) GiB free; $minimum_free_gib GiB required" >&2
2570
+ echo "ERROR global-tools: only $((available_kib / 1024 / 1024)) GiB free; $minimum_free_gib GiB required" >&2
2219
2571
  [[ "$mode" != apply ]] || exit 1
2220
2572
  fi
2221
2573
 
@@ -2224,7 +2576,7 @@ if [[ "$mode" == apply ]]; then
2224
2576
  fi
2225
2577
 
2226
2578
  if ! install_surface_policy_file; then
2227
- echo "global-tools: TOOL INSTALL FAILED: surface-policy-file" >&2
2579
+ echo "ERROR global-tools: TOOL INSTALL FAILED: surface-policy-file" >&2
2228
2580
  failed_tools+=("surface-policy-file")
2229
2581
  fi
2230
2582
 
@@ -2235,7 +2587,7 @@ require_bun_for_identity_files || exit 1
2235
2587
  # below that could read it (kimi-hooks resolves adl the same way; this
2236
2588
  # is the identity every consumer -- `adl run`, the managed-settings render
2237
2589
  # below, and host automation's git-ado call -- reads instead of its own
2238
- # copy). Runs for real in plan mode too (like launch-agents.ts): default-agent-file.ts's
2590
+ # copy). Runs for real in plan mode too (like the other plan-safe steps): default-agent-file.ts's
2239
2591
  # own --mode plan never writes, so this is safe to run unconditionally.
2240
2592
  default_agent="$(jq -r --arg platform "$platform" --arg device "$device" '.[$platform].devices[$device].default_agent' "$manifest")"
2241
2593
  if [[ "$mode" != apply ]]; then
@@ -2244,7 +2596,7 @@ if [[ "$mode" != apply ]]; then
2244
2596
  else
2245
2597
  echo "global-tools: default-agent file mode=apply device=$device -> $default_agent"
2246
2598
  if ! bun "$repo_root/packages/fleet/src/default-agent-file.ts" --agent "$default_agent" --platform "$platform" --mode apply ${reconcile_test_system_root_args[@]+"${reconcile_test_system_root_args[@]}"}; then
2247
- echo "global-tools: TOOL INSTALL FAILED: default-agent-file" >&2
2599
+ echo "ERROR global-tools: TOOL INSTALL FAILED: default-agent-file" >&2
2248
2600
  failed_tools+=("default-agent-file")
2249
2601
  fi
2250
2602
  fi
@@ -2261,7 +2613,7 @@ if [[ "$mode" != apply ]]; then
2261
2613
  else
2262
2614
  echo "global-tools: managed settings mode=apply device=$device -> $default_agent"
2263
2615
  if ! bun "$repo_root/packages/fleet/src/managed-settings.ts" --agent "$default_agent" --platform "$platform" --mode apply ${reconcile_test_system_root_args[@]+"${reconcile_test_system_root_args[@]}"}; then
2264
- echo "global-tools: TOOL INSTALL FAILED: managed-settings" >&2
2616
+ echo "ERROR global-tools: TOOL INSTALL FAILED: managed-settings" >&2
2265
2617
  failed_tools+=("managed-settings")
2266
2618
  fi
2267
2619
  fi
@@ -2282,7 +2634,7 @@ if jq -e 'to_entries | any(.value.manager == "brew")' "$resolved_file" >/dev/nul
2282
2634
  brew_upgrade_all="$(jq -r --arg platform "$platform" --arg device "$device" '.[$platform].devices[$device].brew_upgrade_all // false' "$manifest")"
2283
2635
  if [[ "$brew_upgrade_all" == true ]]; then
2284
2636
  if ! run_brew_upgrade_all; then
2285
- echo "global-tools: TOOL INSTALL FAILED: brew-upgrade-all" >&2
2637
+ echo "ERROR global-tools: TOOL INSTALL FAILED: brew-upgrade-all" >&2
2286
2638
  failed_tools+=("brew-upgrade-all")
2287
2639
  fi
2288
2640
  fi
@@ -2295,7 +2647,7 @@ fi
2295
2647
 
2296
2648
  while IFS= read -r tool_id; do
2297
2649
  if ! install_one_tool "$tool_id"; then
2298
- echo "global-tools: TOOL INSTALL FAILED: $tool_id" >&2
2650
+ echo "ERROR global-tools: TOOL INSTALL FAILED: $tool_id" >&2
2299
2651
  failed_tools+=("$tool_id")
2300
2652
  fi
2301
2653
  done < <(jq -r 'to_entries[] | select(.value.manager != "shim" and .value.manager != "repo-script" and .value.manager != "repo-dir" and .value.manager != "bun-compile" and .value.manager != "embedding-model") | .key' "$resolved_file")
@@ -2310,28 +2662,28 @@ fi
2310
2662
 
2311
2663
  while IFS= read -r tool_id; do
2312
2664
  if ! build_one_bun_tool "$tool_id"; then
2313
- echo "global-tools: TOOL INSTALL FAILED: $tool_id" >&2
2665
+ echo "ERROR global-tools: TOOL INSTALL FAILED: $tool_id" >&2
2314
2666
  failed_tools+=("$tool_id")
2315
2667
  fi
2316
2668
  done < <(jq -r 'to_entries[] | select(.value.manager == "bun-compile") | .key' "$resolved_file")
2317
2669
 
2318
2670
  while IFS= read -r tool_id; do
2319
2671
  if ! install_one_embedding_model_tool "$tool_id"; then
2320
- echo "global-tools: TOOL INSTALL FAILED: $tool_id" >&2
2672
+ echo "ERROR global-tools: TOOL INSTALL FAILED: $tool_id" >&2
2321
2673
  failed_tools+=("$tool_id")
2322
2674
  fi
2323
2675
  done < <(jq -r 'to_entries[] | select(.value.manager == "embedding-model") | .key' "$resolved_file")
2324
2676
 
2325
2677
  while IFS= read -r tool_id; do
2326
2678
  if ! write_one_shim "$tool_id"; then
2327
- echo "global-tools: TOOL INSTALL FAILED: $tool_id" >&2
2679
+ echo "ERROR global-tools: TOOL INSTALL FAILED: $tool_id" >&2
2328
2680
  failed_tools+=("$tool_id")
2329
2681
  fi
2330
2682
  done < <(jq -r 'to_entries[] | select(.value.manager == "shim") | .key' "$resolved_file")
2331
2683
 
2332
2684
  while IFS= read -r tool_id; do
2333
2685
  if ! write_one_repo_script "$tool_id"; then
2334
- echo "global-tools: TOOL INSTALL FAILED: $tool_id" >&2
2686
+ echo "ERROR global-tools: TOOL INSTALL FAILED: $tool_id" >&2
2335
2687
  failed_tools+=("$tool_id")
2336
2688
  fi
2337
2689
  done < <(jq -r 'to_entries[] | select(.value.manager == "repo-script") | .key' "$resolved_file")
@@ -2416,7 +2768,7 @@ if [[ "$(jq -r '.config.scratch_reap.enabled // false' "$manifest")" == true ]];
2416
2768
  # REFUSE to remove worktrees. That is the safe direction, so it must not
2417
2769
  # abort the fleet's update -- but it must not vanish either, or the
2418
2770
  # published-HEAD gate rots with nothing ever reporting it.
2419
- "$script_dir/scratch-reap" \
2771
+ "$BASH" "$script_dir/scratch-reap" \
2420
2772
  "${scratch_reap_args[@]}" \
2421
2773
  --mode "$scratch_reap_mode" \
2422
2774
  --idle-days "$scratch_reap_idle_days" \
@@ -2426,7 +2778,7 @@ fi
2426
2778
 
2427
2779
  while IFS= read -r tool_id; do
2428
2780
  if ! write_one_repo_dir "$tool_id"; then
2429
- echo "global-tools: TOOL INSTALL FAILED: $tool_id" >&2
2781
+ echo "ERROR global-tools: TOOL INSTALL FAILED: $tool_id" >&2
2430
2782
  failed_tools+=("$tool_id")
2431
2783
  fi
2432
2784
  done < <(jq -r 'to_entries[] | select(.value.manager == "repo-dir") | .key' "$resolved_file")
@@ -2445,15 +2797,10 @@ if [[ "$have_previous" -eq 1 ]]; then
2445
2797
  [[ -n "$tool_id" ]] || continue
2446
2798
  remove_one_manifest_entry "$tool_id"
2447
2799
  # Selects an id when it was deleted outright (absent from the current
2448
- # manifest) OR retained but migrated to a different manager/package (e.g.
2449
- # lean-ctx moving from npm:lean-ctx-bin to release-archive-latest:lean-ctx)
2800
+ # manifest) OR retained but migrated to a different manager/package
2450
2801
  # -- a plain "id no longer present" check would silently leave the OLD
2451
2802
  # manager's install in place forever whenever an id survives a manager
2452
- # change, which for lean-ctx meant the stale npm package (and the
2453
- # /opt/homebrew/bin/lean-ctx shim it owns) would never be uninstalled, and
2454
- # that shim's earlier position on PATH than /usr/local/bin would keep
2455
- # `command -v lean-ctx` resolving to it instead of the new release binary
2456
- # no matter how many times reconcile ran.
2803
+ # change, even though the current declaration points to a different install.
2457
2804
  done < <(jq -r --slurpfile current "$resolved_file" '
2458
2805
  keys[] as $id
2459
2806
  | ($current[0][$id]) as $cur
@@ -2466,12 +2813,12 @@ project_adl_package_payload() {
2466
2813
  local adl_package bun_bin
2467
2814
  adl_package="$(jq -r '.adl.package // empty' "$resolved_file")"
2468
2815
  [[ "$adl_package" == @amsterdamdatalabs/adl-agent-control ]] || {
2469
- echo "Adl package projection requires the published wrapper entry" >&2
2816
+ echo "ERROR Adl package projection requires the published wrapper entry" >&2
2470
2817
  return 1
2471
2818
  }
2472
2819
  for failed_tool in ${failed_tools[@]+"${failed_tools[@]}"}; do
2473
2820
  if [[ "$failed_tool" == adl ]]; then
2474
- echo "skipping Adl payload projection because Adl source verification failed" >&2
2821
+ echo "WARN skipping Adl payload projection because Adl source verification failed" >&2
2475
2822
  return 1
2476
2823
  fi
2477
2824
  done
@@ -2480,13 +2827,16 @@ project_adl_package_payload() {
2480
2827
  echo "would build agent-skills payload from checkout"
2481
2828
  fi
2482
2829
  echo "would project Adl package payload from $repo_root to /usr/local/libexec/adl and /usr/local/bin/git{,-ado}"
2830
+ if [[ "$source_mode" == npm ]]; then
2831
+ echo "would link /usr/local/bin/adl to $repo_root/bin/adl"
2832
+ fi
2483
2833
  return 0
2484
2834
  fi
2485
2835
  bun_bin="$(command -v bun || true)"
2486
- [[ -n "$bun_bin" ]] || { echo "Adl package projection requires bun on PATH" >&2; return 1; }
2836
+ [[ -n "$bun_bin" ]] || { echo "ERROR Adl package projection requires bun on PATH" >&2; return 1; }
2487
2837
  if [[ "$source_mode" == checkout ]]; then
2488
2838
  (cd "$repo_root/packages/agent-skills" && "$bun_bin" run build) || {
2489
- echo "could not build agent-skills payload from checkout" >&2
2839
+ echo "ERROR could not build agent-skills payload from checkout" >&2
2490
2840
  return 1
2491
2841
  }
2492
2842
  fi
@@ -2494,59 +2844,48 @@ project_adl_package_payload() {
2494
2844
  if [[ -n "${RECONCILE_TEST_SYSTEM_ROOT:-}" ]]; then
2495
2845
  system_root_args+=(--system-root "$RECONCILE_TEST_SYSTEM_ROOT")
2496
2846
  fi
2847
+ # Package mode also links /usr/local/bin/adl to this package's own bin/adl:
2848
+ # bun links adl into the owner's ~/.bun/bin only, yet bin/git-ado, the MCP
2849
+ # server command and the managed surface hooks run a bare `adl`.
2850
+ if [[ "$source_mode" == npm ]]; then
2851
+ system_root_args+=(--link-adl)
2852
+ fi
2497
2853
  "$bun_bin" "$repo_root/packages/fleet/src/install-adl-package.ts" install \
2498
2854
  --package-root "$repo_root" ${system_root_args[@]+"${system_root_args[@]}"}
2499
2855
  }
2500
2856
 
2501
2857
  if ! project_adl_package_payload; then
2502
- echo "global-tools: TOOL INSTALL FAILED: adl-package-projection" >&2
2858
+ echo "ERROR global-tools: TOOL INSTALL FAILED: adl-package-projection" >&2
2503
2859
  failed_tools+=("adl-package-projection")
2504
2860
  fi
2505
2861
 
2506
2862
  # Same continue-on-failure contract as the tool passes above: runs after git
2507
2863
  # itself is installed/upgraded, and a failure is recorded, not fatal.
2508
- if ! "$repo_root/bin/shell-path-policy" --home "$administrator_home" --platform "$platform" --mode "$mode" --owner "$administrator"; then
2509
- echo "global-tools: TOOL INSTALL FAILED: shell-path-policy" >&2
2864
+ if ! "$BASH" "$repo_root/bin/shell-path-policy" --home "$administrator_home" --platform "$platform" --mode "$mode" --owner "$administrator"; then
2865
+ echo "ERROR global-tools: TOOL INSTALL FAILED: shell-path-policy" >&2
2510
2866
  failed_tools+=("shell-path-policy")
2511
2867
  fi
2512
2868
 
2513
2869
  if ! reconcile_git_global_config; then
2514
- echo "global-tools: TOOL INSTALL FAILED: git-global-config" >&2
2870
+ echo "ERROR global-tools: TOOL INSTALL FAILED: git-global-config" >&2
2515
2871
  failed_tools+=("git-global-config")
2516
2872
  fi
2517
2873
  cleanup_legacy_linux_git_config || {
2518
- echo "global-tools: legacy Linux git config cleanup failed" >&2
2874
+ echo "ERROR global-tools: legacy Linux git config cleanup failed" >&2
2519
2875
  exit 1
2520
2876
  }
2521
2877
 
2522
- # launch_agents (mac only; validate-manifest rejects them on linux): user
2523
- # LaunchAgents adl owns -- see packages/fleet/src/launch-agents.ts. Runs
2524
- # before the global surface sweep, whose LaunchAgent protection is derived
2525
- # from these same manifest declarations. Apply runs as root (it installs the
2526
- # compiles each job into its own signed binary under
2527
- # /usr/local/libexec/adl, writes the administrator's plist owned by
2528
- # that user, and bootstraps gui/<uid>); plan only prints. Same
2529
- # continue-on-failure contract as the tool passes above.
2530
- if [[ "$platform" == mac ]] && jq -e --arg device "$device" \
2531
- '((.mac.common.launch_agents // {}) + (.mac.devices[$device].launch_agents // {})) | length > 0' "$manifest" >/dev/null; then
2532
- launch_agents_args=(--manifest "$manifest" --platform "$platform" --device "$device" --home "$administrator_home" --state-dir "$state_dir")
2533
- if [[ "$have_previous" -eq 1 ]]; then
2534
- launch_agents_args+=(--previous-manifest "$applied_manifest")
2535
- fi
2536
- if [[ "$mode" == apply ]]; then
2537
- echo "global-tools: launch agents mode=apply home=$administrator_home (for $administrator)"
2538
- if ! bun "$repo_root/packages/fleet/src/launch-agents.ts" --mode apply "${launch_agents_args[@]}"; then
2539
- echo "global-tools: TOOL INSTALL FAILED: launch-agents" >&2
2540
- failed_tools+=("launch-agents")
2541
- fi
2542
- else
2543
- echo "global-tools: launch agents mode=plan home=$administrator_home"
2544
- bun "$repo_root/packages/fleet/src/launch-agents.ts" --mode plan "${launch_agents_args[@]}"
2545
- fi
2878
+ # git-maintain is retired: clean it up where it is still installed (mac), ahead
2879
+ # of the global surface sweep, which would otherwise see its leftover
2880
+ # LaunchAgent as an approval-gated candidate. Same continue-on-failure
2881
+ # contract as the tool passes above.
2882
+ if ! retire_git_maintain; then
2883
+ echo "ERROR global-tools: TOOL INSTALL FAILED: git-maintain-retire" >&2
2884
+ failed_tools+=("git-maintain-retire")
2546
2885
  fi
2547
2886
 
2548
2887
  if [[ "$mode" == apply ]]; then
2549
- "$script_dir/health-check" "$device"
2888
+ "$BASH" "$script_dir/health-check" "$device"
2550
2889
  fi
2551
2890
 
2552
2891
  if [[ "$(jq -r '.config.global_surface_sweep.enabled' "$manifest")" == true ]]; then
@@ -2559,7 +2898,7 @@ if [[ "$(jq -r '.config.global_surface_sweep.enabled' "$manifest")" == true ]];
2559
2898
  # recomputes the sweep's own backupPath() logic. stderr is left
2560
2899
  # unredirected so a real failure still streams live as before.
2561
2900
  sweep_status=0
2562
- sweep_output="$("$script_dir/global-surface-sweep" \
2901
+ sweep_output="$("$BASH" "$script_dir/global-surface-sweep" \
2563
2902
  --home "$administrator_home" \
2564
2903
  --mode "$surface_mode" \
2565
2904
  --state-dir "$state_dir")" || sweep_status=$?
@@ -2597,7 +2936,7 @@ if [[ "$kimi_hooks_enabled" == true ]]; then
2597
2936
  echo "global-tools: kimi-hooks skipped: kimi not installed"
2598
2937
  elif [[ "$mode" != apply ]]; then
2599
2938
  echo "global-tools: kimi hooks mode=plan home=$administrator_home"
2600
- "$script_dir/kimi-hooks" --home "$administrator_home" --mode plan
2939
+ "$BASH" "$script_dir/kimi-hooks" --home "$administrator_home" --mode plan
2601
2940
  else
2602
2941
  # Ordering: adl (bun-compile pass, above) must already resolve as
2603
2942
  # a bare command before we write TOML entries that reference it -- Kimi
@@ -2607,12 +2946,12 @@ if [[ "$kimi_hooks_enabled" == true ]]; then
2607
2946
  # docs/specs/two-branch-standard-2026-09-27.md) -- hard cut, no compat
2608
2947
  # shim: there is no standalone hook executable to resolve any more.
2609
2948
  adl_executable="$(jq -r '."adl".executable' "$resolved_file")"
2610
- if ! adl_resolved="$(sudo -u "$administrator" -H env PATH="/opt/homebrew/bin:/home/linuxbrew/.linuxbrew/bin:/usr/local/bin:/usr/bin:/bin" /bin/sh -c "command -v '$adl_executable'" 2>/dev/null)"; then
2611
- echo "global-tools: could not resolve adl from the administrator PATH" >&2
2949
+ if ! adl_resolved="$(sudo -u "$administrator" -H env PATH="/opt/homebrew/bin:/home/linuxbrew/.linuxbrew/bin:/usr/local/bin:$administrator_home/.bun/bin:/usr/bin:/bin" /bin/sh -c "command -v '$adl_executable'" 2>/dev/null)"; then
2950
+ echo "ERROR global-tools: could not resolve adl from the administrator PATH" >&2
2612
2951
  exit 1
2613
2952
  fi
2614
2953
  [[ -n "$adl_resolved" && -x "$adl_resolved" ]] || {
2615
- echo "global-tools: kimi-hooks requires adl on PATH ($adl_executable); bun-compile installation must succeed first" >&2
2954
+ echo "ERROR global-tools: kimi-hooks requires adl on PATH ($adl_executable); the adl package installation must succeed first" >&2
2616
2955
  exit 1
2617
2956
  }
2618
2957
  echo "global-tools: kimi hooks mode=apply home=$administrator_home (as $administrator)"
@@ -2629,7 +2968,7 @@ if [[ "$kimi_hooks_enabled" == true ]]; then
2629
2968
  # streams live exactly as before.
2630
2969
  kimi_hooks_status=0
2631
2970
  kimi_hooks_output="$(cd /tmp && sudo -u "$administrator" -H env PATH="${brew_bin%/*}:/usr/bin:/bin" \
2632
- "$script_dir/kimi-hooks" --home "$administrator_home" --mode apply)" || kimi_hooks_status=$?
2971
+ "$BASH" "$script_dir/kimi-hooks" --home "$administrator_home" --mode apply)" || kimi_hooks_status=$?
2633
2972
  printf '%s\n' "$kimi_hooks_output"
2634
2973
  collect_kimi_hooks_backups "$kimi_hooks_output"
2635
2974
  [[ "$kimi_hooks_status" -eq 0 ]] || exit "$kimi_hooks_status"
@@ -2642,50 +2981,11 @@ fi
2642
2981
  # run the same hook contract twice. The exact old path remains Enact-owned.
2643
2982
  if [[ "$mode" != apply ]]; then
2644
2983
  echo "global-tools: retired OpenCode carrier mode=plan home=$administrator_home"
2645
- "$script_dir/opencode-hooks" --home "$administrator_home" --mode plan
2984
+ "$BASH" "$script_dir/opencode-hooks" --home "$administrator_home" --mode plan
2646
2985
  else
2647
2986
  echo "global-tools: retired OpenCode carrier mode=remove home=$administrator_home (as $administrator)"
2648
2987
  (cd /tmp && sudo -u "$administrator" -H env PATH="${brew_bin%/*}:/usr/bin:/bin" \
2649
- "$script_dir/opencode-hooks" --home "$administrator_home" --mode remove)
2650
- fi
2651
-
2652
- # STATUS.md 12p item 13, final scope (2026-09-29): lean-ctx hooks are "moved
2653
- # to repo level" and must be REMOVED FROM GLOBAL -- the per-repo carrier
2654
- # (`adl hook lean-ctx pre-tool-use|post-tool-use --host=<x>`, written by
2655
- # `adl hooks deploy`) already provides them with the repo's own config, so
2656
- # any raw, unwrapped `lean-ctx hook <subcommand>` entry left in a per-user
2657
- # config (observe, rewrite, redirect, read-dedup, any other) is retracted
2658
- # global wiring, never the per-repo carrier or Orca's own hooks -- see
2659
- # bin/lib/lean-ctx-observe-hooks.sh's header for the full decision trail and
2660
- # exactly which per-user files can carry one. Unconditional, like the
2661
- # retired OpenCode carrier cleanup just above: not a manifest-gated optional
2662
- # feature, a standing cleanup that runs every reconciliation so a
2663
- # hand-edited or Orca-reinstalled per-user config never lets this creep back
2664
- # in unnoticed.
2665
- if [[ "$mode" != apply ]]; then
2666
- echo "global-tools: lean-ctx observe/raw-hook strip mode=plan home=$administrator_home"
2667
- # Plan runs either unprivileged (the administrator's own shell) or under
2668
- # sudo (fleet-cutover.sh's plan). As root it must drop to the administrator
2669
- # exactly like apply: lean-ctx-observe-strip refuses root against a
2670
- # non-root home (m5 cutover plan, 2026-09-29).
2671
- if [[ "$EUID" -eq 0 && "$administrator" != root ]]; then
2672
- (cd /tmp && sudo -u "$administrator" -H env PATH="${brew_bin%/*}:/usr/bin:/bin" \
2673
- "$script_dir/lean-ctx-observe-strip" --home "$administrator_home" --mode plan)
2674
- else
2675
- "$script_dir/lean-ctx-observe-strip" --home "$administrator_home" --mode plan
2676
- fi
2677
- else
2678
- echo "global-tools: lean-ctx observe/raw-hook strip mode=apply home=$administrator_home (as $administrator)"
2679
- # stdout captured (then re-printed) so its own "backed up previous config
2680
- # to ..." note(s) can be folded into this run's combined backups list,
2681
- # exactly like kimi-hooks above; stderr left unredirected so a real
2682
- # failure (a file that fails to parse) still streams live.
2683
- lean_ctx_observe_strip_status=0
2684
- lean_ctx_observe_strip_output="$(cd /tmp && sudo -u "$administrator" -H env PATH="${brew_bin%/*}:/usr/bin:/bin" \
2685
- "$script_dir/lean-ctx-observe-strip" --home "$administrator_home" --mode apply)" || lean_ctx_observe_strip_status=$?
2686
- printf '%s\n' "$lean_ctx_observe_strip_output"
2687
- collect_lean_ctx_observe_strip_backups "$lean_ctx_observe_strip_output"
2688
- [[ "$lean_ctx_observe_strip_status" -eq 0 ]] || exit "$lean_ctx_observe_strip_status"
2988
+ "$BASH" "$script_dir/opencode-hooks" --home "$administrator_home" --mode remove)
2689
2989
  fi
2690
2990
 
2691
2991
  # surface-policy re-asserts each host's permission MODE from the single source
@@ -2708,11 +3008,11 @@ fi
2708
3008
  if [[ "$surface_policy_enabled" == true ]]; then
2709
3009
  if [[ "$mode" != apply ]]; then
2710
3010
  echo "global-tools: surface policy mode=plan home=$administrator_home"
2711
- "$script_dir/surface-policy" --home "$administrator_home" --mode plan
3011
+ "$BASH" "$script_dir/surface-policy" --home "$administrator_home" --mode plan
2712
3012
  else
2713
3013
  echo "global-tools: surface policy mode=apply home=$administrator_home (as $administrator)"
2714
3014
  (cd /tmp && sudo -u "$administrator" -H env PATH="${brew_bin%/*}:/usr/bin:/bin" \
2715
- "$script_dir/surface-policy" --home "$administrator_home" --mode apply)
3015
+ "$BASH" "$script_dir/surface-policy" --home "$administrator_home" --mode apply)
2716
3016
  fi
2717
3017
  fi
2718
3018
 
@@ -2724,24 +3024,24 @@ fi
2724
3024
  if [[ "$vscode_settings_enabled" == true ]]; then
2725
3025
  if [[ "$mode" != apply ]]; then
2726
3026
  echo "global-tools: VS Code settings mode=plan home=$administrator_home"
2727
- "$script_dir/vscode-settings" --home "$administrator_home" --platform "$platform" --mode plan
3027
+ "$BASH" "$script_dir/vscode-settings" --home "$administrator_home" --platform "$platform" --mode plan
2728
3028
  else
2729
3029
  echo "global-tools: VS Code settings mode=apply home=$administrator_home (as $administrator)"
2730
3030
  (cd /tmp && sudo -u "$administrator" -H env PATH="${brew_bin%/*}:/usr/bin:/bin" \
2731
- "$script_dir/vscode-settings" --home "$administrator_home" --platform "$platform" --mode apply)
3031
+ "$BASH" "$script_dir/vscode-settings" --home "$administrator_home" --platform "$platform" --mode apply)
2732
3032
  fi
2733
3033
  fi
2734
3034
 
2735
3035
  if [[ "$global_agents_enabled" == true ]]; then
2736
3036
  if [[ "$mode" != apply ]]; then
2737
3037
  echo "global-tools: global agents mode=status home=$administrator_home"
2738
- "$script_dir/global-agents" --home "$administrator_home" --mode status
3038
+ "$BASH" "$script_dir/global-agents" --home "$administrator_home" --mode status
2739
3039
  else
2740
3040
  echo "global-tools: global agents mode=deploy home=$administrator_home (as $administrator)"
2741
3041
  # The exact target files are authoritative global artifacts. Drift is
2742
3042
  # replaced from GLOBAL-AGENTS.md with no backup or leave-in-place branch.
2743
3043
  (cd /tmp && sudo -u "$administrator" -H env PATH="${brew_bin%/*}:/usr/bin:/bin" \
2744
- "$script_dir/global-agents" --home "$administrator_home" --mode deploy)
3044
+ "$BASH" "$script_dir/global-agents" --home "$administrator_home" --mode deploy)
2745
3045
  fi
2746
3046
  fi
2747
3047
 
@@ -2764,14 +3064,14 @@ fi
2764
3064
  if [[ "$mode" == apply ]]; then
2765
3065
  if repo_root_is_git; then
2766
3066
  applied_commit="$(sudo -u "$administrator" -H git -C "$repo_root" rev-parse HEAD)" || {
2767
- echo "could not resolve checkout commit for applied manifest identity" >&2
3067
+ echo "ERROR could not resolve checkout commit for applied manifest identity" >&2
2768
3068
  exit 1
2769
3069
  }
2770
3070
  else
2771
3071
  # az374: the installed npm package's own version is the identity of what
2772
3072
  # was applied -- there is no commit to ask for.
2773
3073
  applied_npm_version="$(jq -er '.version | select(type == "string" and length > 0)' "$repo_root/package.json")" || {
2774
- echo "installed Adl package has no valid version: $repo_root/package.json" >&2
3074
+ echo "ERROR installed Adl package has no valid version: $repo_root/package.json" >&2
2775
3075
  exit 1
2776
3076
  }
2777
3077
  applied_commit="npm:$applied_npm_version"
@@ -2785,83 +3085,6 @@ else
2785
3085
  echo "global-tools: $mode complete; no state changed"
2786
3086
  fi
2787
3087
 
2788
- # git_maintain_repos (2026-09-30, arm-only decision the same day): an
2789
- # explicit, closed list of repositories this device's own checkout lives
2790
- # at OUTSIDE any recursively-scanned home directory -- the Linux VPSes'
2791
- # root-owned /opt/adl-azdo is the confirmed case for both strato-vps-1 and
2792
- # hostinger-vps-1 (STATUS.md's 2026-09-29 first-deployment record;
2793
- # hostinger's own fleet-cutover --apply landed 2026-09-29T23:43Z, confirmed
2794
- # the same day). git-maintain's own scheduling (packages/fleet/src/
2795
- # launch-agents.ts: a dedicated compiled binary, ad-hoc signed, run by a
2796
- # launchd LaunchAgent under TCC) is macOS-only by construction, so it never
2797
- # reaches these checkouts on its own. This runs the job directly, on
2798
- # reconcile's own schedule (every platform this script runs on already has
2799
- # one), passing each declared path as `--repo` argv -- never a recursive
2800
- # scan of a shared parent like `/opt` (which also holds unrelated,
2801
- # explicitly off-limits directories), and never a new environment
2802
- # variable: the list itself comes only from the manifest, and
2803
- # GIT_MAINTAIN_DRY below is this job's own pre-existing dry/real contract,
2804
- # driven by reconcile's own --mode exactly like every other step in this
2805
- # file, not a knob an operator sets by hand. Absent or empty (every mac
2806
- # device today) is a silent no-op, the same convention
2807
- # launch_agents/scratch_repos/surface_clis already use.
2808
- #
2809
- # ARM-ONLY, always, on this path (independent review, 2026-09-30): the full
2810
- # job on this SAME checkout `bin/reconcile` runs from turned out to be new,
2811
- # unapproved, root-run behaviour once actually reasoned through, not merely
2812
- # a self-checkout read/write ordering hazard -- see the `--arm-only`
2813
- # decision recorded in jobs/git-maintain/src/main.ts's own header comment
2814
- # for the full list (a silent fast-forward-driven auto-deploy of
2815
- # `origin/integration`/main with no approval gate, colliding with
2816
- # fleet-cutover.sh re-reading fleet-retire-list.json from this same
2817
- # checkout after reconcile runs, `git branch -D` as root, and a fetch that
2818
- # still runs even in dry/plan/audit mode and exits 0 on its own failure).
2819
- # `--arm-only` narrows this call to ONLY `armHooks`: no fetch, no
2820
- # fast-forward, no branch prune, no bundle-refresh installer, no network,
2821
- # and no Azure DevOps identity resolution. The only remaining write is
2822
- # `.git/config`'s `core.hooksPath`, real only in apply mode
2823
- # (GIT_MAINTAIN_DRY=1 below makes it a `[dry] would arm ...` log line and
2824
- # nothing else, in both plan AND audit mode). That is never a tracked file
2825
- # this script (or anything downstream of it) reads, so arm-only has no
2826
- # self-checkout read/write ordering hazard left to avoid -- this step stays
2827
- # in this late position anyway, unchanged from the full-job version: it is
2828
- # still the simplest place for it, and there is no reason to special-case
2829
- # arm-only's position just because its own hazard is gone.
2830
- git_maintain_repos=()
2831
- while IFS= read -r repo; do
2832
- [[ -n "$repo" ]] && git_maintain_repos+=("$repo")
2833
- done < <(jq -r --arg platform "$platform" --arg device "$device" \
2834
- '.[$platform].devices[$device].git_maintain_repos // [] | .[]' "$manifest")
2835
- if [[ "${#git_maintain_repos[@]}" -gt 0 ]]; then
2836
- git_maintain_repo_args=(--arm-only)
2837
- for repo in "${git_maintain_repos[@]}"; do
2838
- git_maintain_repo_args+=(--repo "$repo")
2839
- done
2840
- # Unlike default-agent-file.ts/managed-settings.ts's plan branches earlier
2841
- # in this file (which never fail, by construction, so they run
2842
- # unguarded), this job's dry pass still validates each declared path is a
2843
- # real repo -- a genuine failure mode even in a non-apply mode, e.g. a
2844
- # declared checkout that has not been cloned yet. So every non-apply mode
2845
- # is guarded exactly like apply mode here, deliberately deviating from
2846
- # those sibling steps: report it, collect it, never let it abort the rest
2847
- # of this script under this file's own `set -e`. `$mode` (plan OR audit)
2848
- # is reported verbatim -- audit is a real, distinct reconcile mode, never
2849
- # relabeled "plan".
2850
- if [[ "$mode" != apply ]]; then
2851
- echo "global-tools: git-maintain (declared repos, arm-only) mode=$mode device=$device -> ${git_maintain_repos[*]}"
2852
- if ! GIT_MAINTAIN_DRY=1 bun "$repo_root/packages/fleet/jobs/git-maintain/src/main.ts" "${git_maintain_repo_args[@]}"; then
2853
- echo "global-tools: TOOL INSTALL FAILED: git-maintain-repos" >&2
2854
- failed_tools+=("git-maintain-repos")
2855
- fi
2856
- else
2857
- echo "global-tools: git-maintain (declared repos, arm-only) mode=apply device=$device -> ${git_maintain_repos[*]}"
2858
- if ! bun "$repo_root/packages/fleet/jobs/git-maintain/src/main.ts" "${git_maintain_repo_args[@]}"; then
2859
- echo "global-tools: TOOL INSTALL FAILED: git-maintain-repos" >&2
2860
- failed_tools+=("git-maintain-repos")
2861
- fi
2862
- fi
2863
- fi
2864
-
2865
3088
  # A recorded, continued-past tool failure (install_one_tool /
2866
3089
  # build_one_bun_tool / install_one_embedding_model_tool / write_one_shim)
2867
3090
  # still lets every post-pass above run -- sweep, kimi-hooks, global-agents,
@@ -2869,11 +3092,11 @@ fi
2869
3092
  # this point. The non-zero exit here is the only signal a caller (cron,
2870
3093
  # systemd, CI) needs that something needs attention.
2871
3094
  if [[ "${#failed_tools[@]}" -gt 0 ]]; then
2872
- echo "global-tools: FAILED to install/build ${#failed_tools[@]} tool(s): ${failed_tools[*]}" >&2
3095
+ echo "ERROR global-tools: FAILED to install/build ${#failed_tools[@]} tool(s): ${failed_tools[*]}" >&2
2873
3096
  exit 1
2874
3097
  fi
2875
3098
 
2876
3099
  if [[ "$scratch_reap_failed" -eq 1 ]]; then
2877
- echo "global-tools: scratch reap could not verify remotes; worktree removal was refused" >&2
3100
+ echo "ERROR global-tools: scratch reap could not verify remotes; worktree removal was refused" >&2
2878
3101
  exit 1
2879
3102
  fi