@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.
- package/bin/adl +2 -2
- package/bin/git +17 -18
- package/bin/git-ado +27 -26
- package/bin/git-defaults +173 -0
- package/bin/health-check +8 -1
- package/bin/kimi-hooks +1 -1
- package/bin/lib/adl-package.sh +257 -0
- package/bin/lib/bun-compile-build.sh +1 -1
- package/bin/lib/fleet-host.sh +62 -0
- package/bin/lib/lean-ctx-observe-hooks.sh +9 -9
- package/bin/reconcile +693 -470
- package/bin/reconcile-host +121 -0
- package/bin/setup +219 -0
- package/bin/shell-path-policy +15 -5
- package/bin/sync-and-reconcile +79 -68
- package/bin/validate-manifest +46 -51
- package/package.json +2 -12
- package/packages/agent-hooks/README.md +14 -9
- package/packages/agent-hooks/assets/hooks.json +0 -20
- package/packages/agent-hooks/package.json +1 -2
- package/packages/agent-hooks/src/cli/args.ts +1 -1
- package/packages/agent-hooks/src/cli/doctor.ts +6 -58
- package/packages/agent-hooks/src/deploy/deploy.ts +12 -41
- package/packages/agent-hooks/src/deploy/doctor.ts +8 -11
- package/packages/agent-hooks/src/deploy/index.ts +14 -14
- package/packages/agent-hooks/src/deploy/json-merge.ts +7 -8
- package/packages/agent-hooks/src/deploy/legacy-lock.ts +2 -2
- package/packages/agent-hooks/src/deploy/list.ts +1 -1
- package/packages/agent-hooks/src/deploy/lock.ts +31 -12
- package/packages/agent-hooks/src/deploy/profile.ts +3 -4
- package/packages/agent-hooks/src/deploy/source.ts +1 -1
- package/packages/agent-hooks/src/gating/config.ts +10 -24
- package/packages/agent-hooks/src/gating/root.ts +3 -3
- package/packages/agent-hooks/src/hooks/briefing/handler.ts +2 -2
- package/packages/agent-hooks/src/hooks/briefing/index.ts +27 -287
- package/packages/agent-hooks/src/hooks/contract.ts +17 -17
- package/packages/agent-hooks/src/hooks/crg/background.ts +27 -0
- package/packages/agent-hooks/src/hooks/crg/index.ts +7 -6
- package/packages/agent-hooks/src/hooks/log.ts +8 -10
- package/packages/agent-hooks/src/hooks/user-prompt-submit/match.ts +1 -1
- package/packages/agent-hooks/src/hooks/user-prompt-submit/tokenizer.ts +1 -1
- package/packages/agent-hooks/src/hooks/user-prompt-submit/vector.ts +6 -5
- package/packages/agent-hooks/src/hosts/registry.ts +1 -1
- package/packages/agent-hooks/src/log/adl-dir.ts +44 -0
- package/packages/agent-hooks/src/log/writer.ts +16 -2
- package/packages/agent-hooks/src/main.ts +7 -41
- package/packages/agent-hooks/src/pre-tool-use/handler.ts +71 -82
- package/packages/agent-hooks/src/rules/engine.ts +13 -17
- package/packages/agent-hooks/src/rules/impl/adl-build-run-denied.ts +3 -3
- package/packages/agent-hooks/src/rules/impl/adl-git-credential-bypass.ts +12 -14
- package/packages/agent-hooks/src/rules/impl/adl-git-identity.ts +28 -28
- package/packages/agent-hooks/src/rules/impl/adl-git-push-policy.ts +8 -9
- package/packages/agent-hooks/src/rules/impl/adl-human-azdo-bypass.ts +56 -59
- package/packages/agent-hooks/src/rules/impl/adl-identity.ts +16 -23
- package/packages/agent-hooks/src/rules/impl/adl-mcp-cli.ts +19 -16
- package/packages/agent-hooks/src/rules/impl/adl-pr-complete-pending.ts +2 -2
- package/packages/agent-hooks/src/rules/impl/adl-pr-review-role.ts +9 -12
- package/packages/agent-hooks/src/rules/impl/adl-pr-role.ts +5 -7
- package/packages/agent-hooks/src/rules/impl/adl-run-required.ts +411 -0
- package/packages/agent-hooks/src/rules/impl/adl-token-minting-role.ts +4 -5
- package/packages/agent-hooks/src/rules/impl/adl-workitem-role.ts +6 -7
- package/packages/agent-hooks/src/rules/impl/agent-tamper-protection.ts +77 -70
- package/packages/agent-hooks/src/rules/impl/deny-allowlist.ts +159 -0
- package/packages/agent-hooks/src/rules/impl/fs-trash.ts +2 -2
- package/packages/agent-hooks/src/rules/impl/git-helpers.ts +21 -23
- package/packages/agent-hooks/src/rules/impl/git-worktree.ts +14 -28
- package/packages/agent-hooks/src/rules/impl/mcp-tool.ts +3 -3
- package/packages/agent-hooks/src/rules/impl/messages.ts +1 -1
- package/packages/agent-hooks/src/rules/impl/pr-completion.ts +1 -1
- package/packages/agent-hooks/src/rules/impl/tool-azure-devops.ts +35 -46
- package/packages/agent-hooks/src/rules/impl/tool-find.ts +34 -7
- package/packages/agent-hooks/src/rules/impl/tool-grep.ts +34 -12
- package/packages/agent-hooks/src/rules/impl/tool-native-search.ts +52 -9
- package/packages/agent-hooks/src/rules/ports.ts +5 -7
- package/packages/agent-hooks/src/rules/registry.ts +4 -6
- package/packages/agent-hooks/src/rules/shell/git-identity.ts +86 -101
- package/packages/agent-hooks/src/rules/shell/git-internals.ts +1 -1
- package/packages/agent-hooks/src/rules/shell/invocation.ts +24 -118
- package/packages/agent-hooks/src/rules/shell/piped-script.ts +1 -1
- package/packages/agent-hooks/src/rules/shell/prefixes.ts +1 -1
- package/packages/agent-hooks/src/rules/shell/protected-carrier-writes.ts +11 -10
- package/packages/agent-hooks/src/rules/shell/protected-carriers.ts +26 -26
- package/packages/agent-hooks/src/rules/shell/substitution.ts +1 -1
- package/packages/agent-hooks/src/rules/shell/xargs.ts +2 -2
- package/packages/agent-hooks/src/rules/spec.ts +2 -2
- package/packages/agent-settings/README.md +287 -0
- package/packages/agent-settings/assets/GLOBAL-AGENTS.md +18 -18
- package/packages/agent-settings/assets/agents/architect.toml +2 -0
- package/packages/agent-settings/assets/agents/code-reviewer.toml +2 -0
- package/packages/agent-settings/assets/agents/executor.toml +2 -0
- package/packages/agent-settings/assets/agents/explore.toml +2 -0
- package/packages/agent-settings/assets/agents/planner.toml +2 -0
- package/packages/agent-settings/assets/mcp.json +0 -4
- package/packages/agent-settings/deploy-profile.json +139 -0
- package/packages/agent-settings/deploy-profile.schema.json +147 -0
- package/packages/agent-settings/managed/README.md +29 -30
- package/packages/agent-settings/managed/codex-requirements.toml +9 -9
- package/packages/agent-settings/package.json +20 -0
- package/packages/agent-settings/src/deploy/agent-render.ts +2 -2
- package/packages/agent-settings/src/deploy/agent-toml.ts +1 -1
- package/packages/agent-settings/src/deploy/agents-deploy.ts +17 -3
- package/packages/agent-settings/src/deploy/doctor.ts +3 -83
- package/packages/agent-settings/src/deploy/index.ts +6 -33
- package/packages/agent-settings/src/deploy/legacy-lock.ts +6 -6
- package/packages/agent-settings/src/deploy/list.ts +5 -19
- package/packages/agent-settings/src/deploy/lock.ts +8 -18
- package/packages/agent-settings/src/deploy/mcp-deploy.ts +10 -5
- package/packages/agent-settings/src/deploy/mcp-merge.ts +22 -3
- package/packages/agent-settings/src/deploy/mcp-source.ts +1 -1
- package/packages/agent-settings/src/deploy/profile.ts +11 -35
- package/packages/agent-settings/src/deploy/retire-lean-ctx.ts +184 -0
- package/packages/agent-settings/src/deploy/settings.ts +17 -22
- package/packages/agent-settings/src/global-surface-sweep.ts +5 -17
- package/packages/agent-settings/src/kimi-hooks.ts +339 -192
- package/packages/agent-settings/src/opencode-hooks.ts +1 -1
- package/packages/agent-settings/tsconfig.json +23 -0
- package/packages/agent-skills/assets/agent-search-tool-audit/SKILL.md +120 -0
- package/packages/agent-skills/assets/agent-search-tool-audit/scripts/audit.sh +276 -0
- package/packages/agent-skills/assets/debug-issue/SKILL.md +8 -7
- package/packages/agent-skills/assets/explore-codebase/SKILL.md +13 -13
- package/packages/agent-skills/assets/refactor-safely/SKILL.md +12 -11
- package/packages/agent-skills/assets/review-changes/SKILL.md +7 -7
- package/packages/agent-skills/assets/testing/references/e2e.md +37 -0
- package/packages/agent-skills/assets/testing/references/integration.md +37 -0
- package/packages/agent-skills/assets/testing/references/unit.md +32 -0
- package/packages/agent-skills/dist/deploy.d.ts +2 -0
- package/packages/agent-skills/dist/deploy.d.ts.map +1 -1
- package/packages/agent-skills/dist/deploy.js +27 -1
- package/packages/agent-skills/dist/deploy.js.map +1 -1
- package/packages/agent-skills/dist/doctor.d.ts +0 -2
- package/packages/agent-skills/dist/doctor.d.ts.map +1 -1
- package/packages/agent-skills/dist/doctor.js +1 -1
- package/packages/agent-skills/dist/doctor.js.map +1 -1
- package/packages/agent-skills/dist/fs-utils.d.ts +0 -1
- package/packages/agent-skills/dist/fs-utils.d.ts.map +1 -1
- package/packages/agent-skills/dist/fs-utils.js +1 -1
- package/packages/agent-skills/dist/fs-utils.js.map +1 -1
- package/packages/agent-skills/package.json +1 -1
- package/packages/agent-skills/src/deploy.ts +29 -1
- package/packages/agent-skills/src/doctor.ts +1 -1
- package/packages/agent-skills/src/fs-utils.ts +1 -1
- package/packages/azdo/README.md +10 -9
- package/packages/azdo/package.json +2 -5
- package/packages/azdo/src/azdo-pl.ts +2 -54
- package/packages/azdo/src/azdo-pr-review.ts +4 -4
- package/packages/azdo/src/azdo-repo-doctor.ts +101 -52
- package/packages/azdo/src/azdo-wit.ts +41 -2
- package/packages/azdo/src/lib/agent-identity.ts +12 -14
- package/packages/azdo/src/lib/agent-token.ts +2 -2
- package/packages/azdo/src/lib/client.ts +0 -5
- package/packages/azdo/src/lib/graph-identity.ts +4 -4
- package/packages/azdo/src/workflow-rules-matrix.csv +85 -0
- package/packages/azdo/src/workflow-rules.ts +128 -0
- package/packages/fleet/manifest.json +38 -33
- package/packages/fleet/manifest.schema.json +67 -65
- package/packages/fleet/src/default-agent-file.ts +3 -3
- package/packages/fleet/src/install-adl-package.ts +73 -15
- package/packages/fleet/src/install-embedding-model.ts +14 -14
- package/packages/fleet/src/managed-settings.ts +7 -7
- package/packages/fleet/src/repo-maintain.ts +439 -0
- package/packages/fleet/src/scratch-reap.ts +6 -6
- package/packages/fleet/src/vscode-settings.ts +5 -5
- package/packages/repo-controls/README.md +141 -43
- package/packages/repo-controls/assets/promote.ts +4 -4
- package/packages/repo-controls/assets/reconcile-guard.sh +10 -46
- package/packages/repo-controls/assets/version-bump.sh +31 -84
- package/packages/repo-controls/bunfig.toml +6 -7
- package/packages/repo-controls/package/adl-compliance/AGENTS.md +5 -6
- package/packages/repo-controls/package/adl-compliance/conftest.py +3 -1
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/__init__.py +4 -16
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/adr_lifecycle/test_check.py +9 -2
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/closed_set/fixtures/docs/notes.md +1 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/closed_set/test_check.py +11 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/directory_size/__init__.py +7 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/directory_size/check.py +266 -0
- 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
- 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
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/directory_size/fixtures/docs-size-assets/docs/guide/.hidden.md +1 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/directory_size/fixtures/docs-size-assets/docs/guide/a.md +1 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/directory_size/fixtures/docs-size-assets/docs/guide/b.md +1 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/directory_size/fixtures/docs-size-assets/docs/guide/data.csv +1 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/directory_size/fixtures/docs-size-assets/docs/guide/diagram.png +1 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/directory_size/fixtures/docs-size-assets/docs/guide/mock.png +1 -0
- 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
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/directory_size/fixtures/docs-size-assets/docs.md +1 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/directory_size/fixtures/docs-size-assets/grants.yaml +4 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/directory_size/fixtures/docs-size-assets/plans/deep/p1.md +1 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/directory_size/fixtures/docs-size-assets/plans/deep/p2.md +1 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/directory_size/fixtures/docs-size-assets/plans/deep/p3.md +1 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/directory_size/fixtures/docs-size-assets/plans/deep/p4.md +1 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/directory_size/fixtures/docs-size-assets/plans/deep/p5.md +1 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/directory_size/fixtures/docs-size-clean/docs/architecture/build.md +1 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/directory_size/fixtures/docs-size-clean/docs/architecture/overview.md +1 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/directory_size/fixtures/docs-size-clean/docs/guide/install.md +1 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/directory_size/fixtures/docs-size-clean/docs/guide/start.md +1 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/directory_size/fixtures/docs-size-clean/grants.yaml +4 -0
- 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
- 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
- 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
- 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
- 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
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/directory_size/fixtures/docs-size-decisions/docs/guide/a.md +1 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/directory_size/fixtures/docs-size-decisions/docs/guide/b.md +1 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/directory_size/fixtures/docs-size-decisions/docs/guide/c.md +1 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/directory_size/fixtures/docs-size-decisions/grants.yaml +4 -0
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/directory_size/fixtures/docs-size-default-cap/grants.yaml +4 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/directory_size/fixtures/docs-size-exempt/docs/guide/a.md +1 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/directory_size/fixtures/docs-size-exempt/docs/guide/b.md +1 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/directory_size/fixtures/docs-size-exempt/docs/guide/c.md +1 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/directory_size/fixtures/docs-size-exempt/grants.yaml +4 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/directory_size/fixtures/docs-size-grew/docs/guide/a.md +1 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/directory_size/fixtures/docs-size-grew/docs/guide/b.md +1 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/directory_size/fixtures/docs-size-grew/docs/guide/c.md +1 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/directory_size/fixtures/docs-size-grew/grants.yaml +4 -0
- 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
- 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
- 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
- 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
- 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
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/directory_size/fixtures/docs-size-no-section/grants.yaml +4 -0
- 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
- 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
- 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
- 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
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/directory_size/fixtures/docs-size-over-cap/grants.yaml +4 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/directory_size/fixtures/docs-size-stale/docs/held/a.md +1 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/directory_size/fixtures/docs-size-stale/docs/held/b.md +1 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/directory_size/fixtures/docs-size-stale/docs/held/c.md +1 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/directory_size/fixtures/docs-size-stale/docs/held/d.md +1 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/directory_size/fixtures/docs-size-stale/docs/shrunk/a.md +1 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/directory_size/fixtures/docs-size-stale/docs/shrunk/b.md +1 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/directory_size/fixtures/docs-size-stale/grants.yaml +6 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/directory_size/fixtures/docs-size-unusable/docs/guide/a.md +1 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/directory_size/fixtures/docs-size-unusable/docs/guide/b.md +1 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/directory_size/fixtures/docs-size-unusable/docs/guide/c.md +1 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/directory_size/fixtures/docs-size-unusable/grants.yaml +3 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/directory_size/test_check.py +345 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/english_only/check.py +18 -5
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/english_only/test_check.py +35 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/filename/__init__.py +7 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/filename/check.py +91 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/filename/fixtures/docs/AGENTS.md +3 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/filename/fixtures/docs/CLAUDE.md +3 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/filename/fixtures/docs/architecture/-leading-hyphen.md +3 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/filename/fixtures/docs/architecture/AGENTS.md +3 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/filename/fixtures/docs/architecture/double--hyphen.md +3 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/filename/fixtures/docs/architecture/getting started.md +3 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/filename/fixtures/docs/architecture/nested/page-here.md +3 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/filename/fixtures/docs/architecture/trailing-hyphen-.md +3 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/filename/fixtures/docs/architecture/ui-contract.md +4 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/filename/fixtures/docs/architecture/user_profile.md +3 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/filename/fixtures/docs/assets/UI-CONTRACT.png +2 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/filename/fixtures/docs/assets/diagram.png +1 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/filename/fixtures/docs/index.md +4 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/filename/fixtures/docs/notes.md +3 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/filename/fixtures/docs/reference.html +1 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/filename/fixtures/docs/research/2026-10-experiment.md +3 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/filename/fixtures/docs/research/UI-CONTRACT.md +6 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/filename/fixtures/docs.md +5 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/filename/test_check.py +194 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/indexes/check.py +14 -2
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/indexes/docs-knowledge-indexes.yaml +5 -5
- 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
- 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
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/indexes/test_check.py +58 -6
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/ledger/test_check.py +0 -1
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/okf_profile/check.py +64 -17
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/okf_profile/docs-okf-profiles.yaml +60 -9
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/okf_profile/fixtures/okf-ledger-clean/docs/notes.md +17 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/okf_profile/fixtures/okf-ledger-no-lines/docs/notes.md +13 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/okf_profile/fixtures/okf-profile-clean/docs/architecture/demo.md +0 -1
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/okf_profile/fixtures/okf-profile-clean/docs-okf-profiles.yaml +0 -1
- 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
- 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
- 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
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/okf_profile/fixtures/okf-profile-untyped/docs-okf-profiles.yaml +5 -0
- 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
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/okf_profile/fixtures/okf-profile-violation/docs-okf-profiles.yaml +0 -1
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/okf_profile/fixtures/okf-research/docs/research/experiment-ok.md +15 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/okf_profile/fixtures/okf-research/docs/research/extra-key.md +15 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/okf_profile/fixtures/okf-research/docs/research/extra-keys.md +16 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/okf_profile/fixtures/okf-research/docs/research/missing-created.md +11 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/okf_profile/fixtures/okf-research/docs/research/missing-description.md +11 -0
- 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
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/okf_profile/fixtures/okf-research/docs/research/missing-sources.md +10 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/okf_profile/fixtures/okf-research/docs/research/missing-status.md +11 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/okf_profile/fixtures/okf-research/docs/research/missing-title.md +11 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/okf_profile/fixtures/okf-research/docs/research/optional-keys.md +16 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/okf_profile/fixtures/okf-research/docs/research/research-ok.md +14 -0
- 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
- 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
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/okf_profile/fixtures/okf-research-neighbours/docs/operations/runbook.md +26 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/okf_profile/test_check.py +228 -2
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/register.py +5 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/semantics/test_check.py +11 -2
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/directory_ratchet/__init__.py +5 -3
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/directory_ratchet/check.py +160 -44
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/directory_ratchet/fixtures/directory-ratchet-clean/grants.yaml +0 -2
- 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
- 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
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/directory_ratchet/fixtures/directory-ratchet-empty-register/grants.yaml +3 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/directory_ratchet/fixtures/directory-ratchet-excluded/grants.yaml +0 -2
- 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
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/directory_ratchet/fixtures/directory-ratchet-missing-tree/grants.yaml +5 -0
- 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
- 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
- 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
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/directory_ratchet/fixtures/directory-ratchet-no-ceilings/grants.yaml +4 -0
- 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
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/directory_ratchet/fixtures/directory-ratchet-stale-entry/grants.yaml +5 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/directory_ratchet/fixtures/directory-ratchet-unbaselined/grants.yaml +0 -2
- 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
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/directory_ratchet/fixtures/directory-ratchet-unusable-register/grants.yaml +3 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/directory_ratchet/register.py +127 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/directory_ratchet/test_check.py +265 -29
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/markdown_trees/check.py +75 -2
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/markdown_trees/test_check.py +194 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/open_work_cap/check.py +56 -10
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/open_work_cap/test_check.py +256 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/placement/check.py +40 -12
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/placement/test_check.py +108 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/plans_layout/test_check.py +1 -1
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/references/check.py +2 -3
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/root_closed_set/check.py +23 -6
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/root_closed_set/test_check.py +76 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/root_markdown_allowlist/check.py +58 -17
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/root_markdown_allowlist/test_check.py +161 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/scripts_size_depth/check.py +35 -3
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/scripts_size_depth/test_check.py +108 -1
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/lfs/integrity/check.py +281 -61
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/lfs/integrity/fixtures/malformed/absolute-path.yaml +3 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/lfs/integrity/fixtures/malformed/backslash-path.yaml +3 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/lfs/integrity/fixtures/malformed/baseline-is-a-list.yaml +2 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/lfs/integrity/fixtures/malformed/boolean-size.yaml +3 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/lfs/integrity/fixtures/malformed/dot-segment.yaml +3 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/lfs/integrity/fixtures/malformed/duplicate-path.yaml +4 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/lfs/integrity/fixtures/malformed/empty-file.yaml +0 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/lfs/integrity/fixtures/malformed/empty-segment.yaml +3 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/lfs/integrity/fixtures/malformed/float-size.yaml +3 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/lfs/integrity/fixtures/malformed/negative-size.yaml +3 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/lfs/integrity/fixtures/malformed/no-over-limit-baseline.yaml +1 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/lfs/integrity/fixtures/malformed/no-schema-version.yaml +1 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/lfs/integrity/fixtures/malformed/non-string-path.yaml +3 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/lfs/integrity/fixtures/malformed/not-yaml.yaml +2 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/lfs/integrity/fixtures/malformed/null-size.yaml +3 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/lfs/integrity/fixtures/malformed/parent-segment.yaml +3 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/lfs/integrity/fixtures/malformed/string-size.yaml +3 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/lfs/integrity/fixtures/malformed/top-level-is-a-list.yaml +2 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/lfs/integrity/fixtures/malformed/trailing-slash.yaml +3 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/lfs/integrity/fixtures/malformed/unknown-key.yaml +3 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/lfs/integrity/fixtures/malformed/wrong-schema-version.yaml +2 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/lfs/integrity/fixtures/malformed/zero-size.yaml +3 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/lfs/integrity/fixtures/valid.yaml +4 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/lfs/integrity/test_check.py +758 -45
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/repository/ignored_paths/check.py +4 -3
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/repository/ignored_paths/test_check.py +2 -2
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/repository/lockfile_integrity/cargo_lock.py +490 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/repository/lockfile_integrity/check.py +368 -55
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/repository/lockfile_integrity/test_check.py +4 -2
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/repository/lockfile_integrity/test_declared_paths.py +523 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/repository/lockfile_integrity/test_stale_lock.py +1179 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/cli.py +122 -3
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/core/check.py +33 -1
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/core/constants.py +0 -11
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/core/context.py +16 -1
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/policy/config.py +1 -2
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/policy/docs_shape.py +12 -1
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/policy/gate-registry.alt.yaml +133 -147
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/policy/gate-registry.yaml +133 -147
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/policy/registry.py +1 -1
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/policy/root_shape.py +52 -10
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/runner.py +23 -4
- package/packages/repo-controls/package.json +2 -10
- package/packages/repo-controls/scripts/hooks/post-merge +13 -0
- package/packages/repo-controls/scripts/hooks/pre-push +3 -3
- package/packages/repo-controls/src/azdo-push.ts +64 -36
- package/packages/repo-controls/src/azure-devops-governance.ts +7 -9
- package/packages/repo-controls/src/azure-devops-remote.ts +1 -1
- package/packages/repo-controls/src/cli.ts +29 -8
- package/packages/repo-controls/src/compliance-config.ts +410 -2
- package/packages/repo-controls/src/config.ts +133 -60
- package/packages/repo-controls/src/constants.ts +13 -27
- package/packages/repo-controls/src/coverage-ratchet.ts +4 -4
- package/packages/repo-controls/src/doctor.ts +46 -19
- package/packages/repo-controls/src/gitignore-block.ts +5 -29
- package/packages/repo-controls/src/hook-log.ts +1 -2
- package/packages/repo-controls/src/hook.ts +201 -45
- package/packages/repo-controls/src/install.ts +14 -8
- package/packages/repo-controls/src/integrity.ts +3 -3
- package/packages/repo-controls/src/lfs.ts +111 -0
- package/packages/repo-controls/src/promote.ts +8 -8
- package/packages/repo-controls/src/runtime.ts +9 -1
- package/packages/repo-controls/src/workflows-drift.ts +4 -5
- package/packages/repo-controls/src/worktree-guard.ts +3 -3
- package/packages/fleet/jobs/git-maintain/src/main.ts +0 -848
- package/packages/fleet/systemd/adl-azure-agent-update.service +0 -13
- 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.
|
|
72
|
-
#
|
|
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),
|
|
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
|
-
|
|
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
|
-
#
|
|
386
|
-
#
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
#
|
|
392
|
-
#
|
|
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
|
|
400
|
-
|
|
401
|
-
|
|
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
|
-
|
|
405
|
-
echo "brew outdated --
|
|
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
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
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
|
-
|
|
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
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
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/
|
|
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`,
|
|
766
|
-
# in-place
|
|
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 --
|
|
799
|
-
#
|
|
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
|
|
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
|
-
|
|
950
|
-
echo "using installed
|
|
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
|
-
|
|
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
|
|
1057
|
-
#
|
|
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
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
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
|
|
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
|
-
|
|
1328
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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/
|
|
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
|
-
#
|
|
1878
|
-
#
|
|
1879
|
-
|
|
1880
|
-
|
|
1881
|
-
|
|
1882
|
-
|
|
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
|
-
|
|
1891
|
-
|
|
1892
|
-
|
|
1893
|
-
|
|
1894
|
-
|
|
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
|
|
1949
|
-
#
|
|
1950
|
-
|
|
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
|
|
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
|
-
|
|
2184
|
-
|
|
2185
|
-
|
|
2186
|
-
|
|
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
|
|
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
|
|
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,
|
|
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
|
-
#
|
|
2523
|
-
#
|
|
2524
|
-
#
|
|
2525
|
-
#
|
|
2526
|
-
|
|
2527
|
-
|
|
2528
|
-
|
|
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);
|
|
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
|