@amsterdamdatalabs/adl-agent-control-darwin-arm64 0.1.5 → 0.1.7

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 CHANGED
@@ -1,4 +1,4 @@
1
1
  [diffend] Oversized file quarantined before diffing.
2
2
  name: package/bin/adl
3
3
  size: 63812082 bytes
4
- sha256: 48dc5bb9077ff22961d1750ce088e482420d4a9b1ccd0ad1e203b3679a865517
4
+ sha256: 9734a17000c05751fd812eed17d3ef1c1e0e1134e496e859d521d6f5f4b092d2
package/bin/reconcile CHANGED
@@ -407,12 +407,14 @@ run_brew_upgrade_all() {
407
407
  echo "brew upgrade: nothing outdated"
408
408
  return 0
409
409
  fi
410
- local upgrade_output upgrade_status=0
410
+ local upgrade_status=0
411
411
  # Routed through run_brew itself (same owner/cwd/stdin wiring as every
412
412
  # other brew mutation in this file), bounded by run_with_timeout so a
413
413
  # network-stalled `brew upgrade` cannot hang the rest of this run forever.
414
- upgrade_output="$(run_with_timeout "$BREW_UPGRADE_ALL_TIMEOUT_SECONDS" run_brew upgrade 2>&1)" || upgrade_status=$?
415
- printf '%s\n' "$upgrade_output"
414
+ # brew's output streams live -- never captured and printed at the end,
415
+ # which left a long upgrade silent for minutes and looking stuck.
416
+ echo "brew upgrade: $formulae_count formulae, $casks_count casks outdated; upgrading (timeout ${BREW_UPGRADE_ALL_TIMEOUT_SECONDS}s)"
417
+ run_with_timeout "$BREW_UPGRADE_ALL_TIMEOUT_SECONDS" run_brew upgrade 2>&1 || upgrade_status=$?
416
418
  if [[ "$upgrade_status" -eq 124 ]]; then
417
419
  echo "brew upgrade timed out after ${BREW_UPGRADE_ALL_TIMEOUT_SECONDS}s" >&2
418
420
  return 1
@@ -454,28 +456,15 @@ run_npm() {
454
456
  fi
455
457
  }
456
458
 
457
- # Whether a read-only, owner-scoped query (brew_query, npm_installed_version)
458
- # can run without asking for privilege reconcile does not already have. Apply
459
- # mode always runs as real root (checked elsewhere), which can sudo -u any
460
- # administrator with no password. A plan/audit preview run has no such
461
- # guarantee -- $administrator on a Linux VPS device is "root", and previewing
462
- # that device's plan from an unprivileged account must not demand a sudo
463
- # password it may not have; it falls back to the pre-2026-09-28 unconditional
464
- # message instead. Self-administered devices (m3: administrator=tarunrana)
465
- # need no escalation at all, so their preview can query for real.
466
- can_query_as_administrator() {
467
- [[ "$(id -un)" == "$administrator" ]] || [[ "$(id -u)" -eq 0 ]]
468
- }
469
-
470
459
  # Read-only Homebrew query, run exactly like run_brew's real mutations (same
471
460
  # owner, cwd, and stdin) but never a mutation itself. A named function so
472
461
  # tests can stub just this, instead of having to fake sudo's own argument
473
462
  # parsing the way a raw inline `sudo -u ... "$brew_bin" ...` would require.
474
- # Apply-mode only (see can_query_as_administrator): `brew list`/`brew
475
- # outdated` have been observed to take minutes under real load on a
476
- # contended box (Homebrew's own auto-update against a large git checkout),
477
- # so a plan preview never calls this -- it would turn a "quick look" into a
478
- # slow, load-dependent one for every brew formula on the device.
463
+ # Apply-mode only: `brew list`/`brew outdated` have been observed to take
464
+ # minutes under real load on a contended box (Homebrew's own auto-update
465
+ # against a large git checkout), so a plan preview never calls this -- it
466
+ # would turn a "quick look" into a slow, load-dependent one for every brew
467
+ # formula on the device.
479
468
  brew_query() {
480
469
  (cd /tmp && sudo -u "$brew_owner" -H "$brew_bin" "$@" </dev/null)
481
470
  }
@@ -798,6 +787,27 @@ install_surface_policy_file() {
798
787
  echo "installed required surface policy: $target"
799
788
  }
800
789
 
790
+ # A resolved release version against the first line of a release-archive-
791
+ # latest tool's own `--version` output. Different tools format that line
792
+ # differently -- lean-ctx: "lean-ctx 3.10.5 (official, ...)" (version padded
793
+ # by spaces on both sides); agent-vault: "agent-vault version 0.39.3" (flush
794
+ # against the end of the line, no trailing space); a plain "vX.Y.Z" line
795
+ # with no separating space before the version at all -- so the LEFT boundary
796
+ # is a string start or a real space, with an OPTIONAL literal "v" directly
797
+ # after that boundary (never "v" floating anywhere: found in review,
798
+ # 2026-09-28, `(^|[ v])` treated a bare "v" as equivalent to a space no
799
+ # matter what preceded it, so "tool dev0.39.3"/"tool rev0.39.3" -- where the
800
+ # "v" belongs to "dev"/"rev", not a version prefix -- silently matched and
801
+ # would have skipped a real upgrade). The RIGHT boundary is a space or a
802
+ # string end and never accepts "v", or "0.39.3" would incorrectly match
803
+ # inside "0.39.30". Dots in the version are escaped so they match literally,
804
+ # not "any character," in the regex below.
805
+ release_archive_version_matches() {
806
+ local version_line="$1" version="$2" escaped
807
+ escaped="$(printf '%s' "$version" | sed 's/[.[\*^$()+?{}|]/\\&/g')"
808
+ grep -Eq "(^| )v?${escaped}( |\$)" <<<"$version_line"
809
+ }
810
+
801
811
  # Resolves the release tag GitHub's own "latest" redirect currently points
802
812
  # at, without touching the api.github.com REST API (unauthenticated calls
803
813
  # there are rate-limited per source IP; this fleet's shared egress IP already
@@ -811,65 +821,33 @@ resolve_latest_release_tag() {
811
821
  basename "$location"
812
822
  }
813
823
 
814
- run_release_archive() {
815
- local package="$1"
816
- local version="$2"
817
- local url="$3"
818
- local expected_sha256="$4"
819
- local archive_executable="$5"
820
- local target="$6"
821
- if [[ "$mode" != apply ]]; then
822
- printf 'would install pinned release archive: %s %s -> %s\n' "$package" "$version" "$target"
823
- return 0
824
- fi
825
- [[ "$(id -u)" -eq 0 ]] || { echo "apply mode must run as root" >&2; exit 1; }
826
- [[ "$target" == /usr/local/bin/* ]] || { echo "release archive target must be under /usr/local/bin: $target" >&2; exit 1; }
827
- [[ "$archive_executable" != /* && "$archive_executable" != *..* ]] || {
828
- echo "unsafe archive executable path: $archive_executable" >&2
829
- exit 1
830
- }
831
-
832
- (
833
- local release_dir archive_file extract_dir actual_sha256
834
- release_dir="$(mktemp -d)"
835
- trap 'rm -rf "$release_dir"' EXIT
836
- archive_file="$release_dir/release.tar.gz"
837
- extract_dir="$release_dir/extract"
838
- mkdir -p "$extract_dir"
839
- curl --proto '=https' --proto-redir '=https' -fsSL --retry 3 -o "$archive_file" "$url"
840
- case "$(uname -s)" in
841
- Darwin) actual_sha256="$(shasum -a 256 "$archive_file" | awk '{print $1}')" ;;
842
- Linux) actual_sha256="$(sha256sum "$archive_file" | awk '{print $1}')" ;;
843
- *) echo "unsupported operating system for release archive" >&2; exit 1 ;;
844
- esac
845
- [[ "$actual_sha256" == "$expected_sha256" ]] || {
846
- echo "checksum mismatch for $package $version" >&2
847
- exit 1
848
- }
849
- tar -xzf "$archive_file" -C "$extract_dir" "$archive_executable"
850
- [[ -f "$extract_dir/$archive_executable" ]] || {
851
- echo "archive executable missing: $archive_executable" >&2
852
- exit 1
853
- }
854
- atomic_install_executable "$extract_dir/$archive_executable" "$target"
855
- )
856
- }
857
-
858
- # Like run_release_archive, but never pins a version or checksum in the
859
- # manifest: it always installs whatever GitHub's own "latest" alias currently
860
- # serves, verified against the checksums file published *in that same
861
- # release* (never a value carried over from a previous run or hardcoded
862
- # here) -- fails loud, installs nothing, if that checksums asset is missing
863
- # or doesn't list our asset. `.../releases/latest/download/<name>` is GitHub's
864
- # own stable alias for "the matching asset of whatever release is latest
865
- # right now", so the download itself never needs the resolved tag.
824
+ # A GitHub release archive, installed under /usr/local/bin, verified against
825
+ # a checksum published *in that same release* (never a value carried over
826
+ # from a previous run or hardcoded here) -- fails loud, installs nothing, if
827
+ # the checksums asset is missing or doesn't list our asset. It always
828
+ # installs whatever GitHub's own "latest" release was when install_one_tool
829
+ # resolved $tag (resolve_latest_release_tag), fetched with $tag pinned into
830
+ # BOTH download URLs -- `.../releases/download/<tag>/<name>`, never through
831
+ # GitHub's separate "always whatever is latest right now" alias for the
832
+ # downloads themselves. Fixed 2026-09-29 (review): that alias re-resolves on
833
+ # every request, so a release published between the tag resolve and the
834
+ # archive download would otherwise serve the checksums file from one release
835
+ # and the archive from a newer one -- silently mixing versions past the
836
+ # checksum check (which would still "pass" against whichever checksums file
837
+ # actually got fetched). Pinning both fetches to the one $tag closes that
838
+ # window.
839
+ # Tarun, 2026-09-29 (hard rule): versions are never pinned in the manifest --
840
+ # this replaced the old pinned run_release_archive (fixed version + url +
841
+ # sha256), which validate-manifest no longer accepts on any manager; $tag
842
+ # here is resolved fresh every run, never carried over or hardcoded.
866
843
  run_release_archive_latest() {
867
844
  local package="$1"
868
845
  local repo="$2"
869
- local asset="$3"
870
- local checksums_asset="$4"
871
- local archive_executable="$5"
872
- local target="$6"
846
+ local tag="$3"
847
+ local asset="$4"
848
+ local checksums_asset="$5"
849
+ local archive_executable="$6"
850
+ local target="$7"
873
851
  [[ "$(id -u)" -eq 0 ]] || { echo "apply mode must run as root" >&2; exit 1; }
874
852
  [[ "$target" == /usr/local/bin/* ]] || { echo "release archive target must be under /usr/local/bin: $target" >&2; exit 1; }
875
853
  [[ "$archive_executable" != /* && "$archive_executable" != *..* ]] || {
@@ -886,13 +864,13 @@ run_release_archive_latest() {
886
864
  extract_dir="$release_dir/extract"
887
865
  mkdir -p "$extract_dir"
888
866
  curl --proto '=https' --proto-redir '=https' -fsSL --retry 3 \
889
- -o "$checksums_file" "https://github.com/$repo/releases/latest/download/$checksums_asset"
867
+ -o "$checksums_file" "https://github.com/$repo/releases/download/$tag/$checksums_asset"
890
868
  expected_sha256="$(awk -v want="$asset" '$2 == want { print $1; found = 1 } END { exit !found }' "$checksums_file")" || {
891
869
  echo "no published checksum for $asset in $repo's $checksums_asset -- refusing to install unverified bytes" >&2
892
870
  exit 1
893
871
  }
894
872
  curl --proto '=https' --proto-redir '=https' -fsSL --retry 3 \
895
- -o "$archive_file" "https://github.com/$repo/releases/latest/download/$asset"
873
+ -o "$archive_file" "https://github.com/$repo/releases/download/$tag/$asset"
896
874
  case "$(uname -s)" in
897
875
  Darwin) actual_sha256="$(shasum -a 256 "$archive_file" | awk '{print $1}')" ;;
898
876
  Linux) actual_sha256="$(sha256sum "$archive_file" | awk '{print $1}')" ;;
@@ -942,7 +920,7 @@ resolve_surface_mode() {
942
920
  # root") is a real environment fault, not a flaky install, and still
943
921
  # aborts immediately -- `exit` is unconditional and is never caught here.
944
922
  install_one_tool() {
945
- local tool_id="$1" manager package executable previous_executable migrating_from_bun=0 rc=0 npm_version npm_spec npm_allow_scripts npm_target_version npm_installed_ver npm_plan_installed brew_outdated_output brew_installed_version
923
+ 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
946
924
  manager="$(jq -r --arg id "$tool_id" '.[$id].manager' "$resolved_file")"
947
925
  package="$(jq -r --arg id "$tool_id" '.[$id].package' "$resolved_file")"
948
926
  executable="$(jq -r --arg id "$tool_id" '.[$id].executable' "$resolved_file")"
@@ -998,15 +976,14 @@ install_one_tool() {
998
976
  run_apt install -y "$package" || rc=1
999
977
  ;;
1000
978
  npm)
1001
- # An entry's optional .version pins the installed release (e.g.
1002
- # adl-agent-id, Handoff 5 2026-09-26 H4) rather than tracking whatever
1003
- # npm resolves as latest for update_policy=latest-stable. Ownership
979
+ # Tarun, 2026-09-29 (hard rule): versions are never pinned -- every
980
+ # fleet tool installs latest stable. npm entries never carry a
981
+ # .version (validate-manifest rejects one outright); $npm_spec stays a
982
+ # separate variable from $package only because the exact run_npm
983
+ # invocation below is asserted at the source level. Ownership
1004
984
  # verification below still uses the bare package name: npm's global
1005
- # tree and package.json are keyed by package, never by the requested
1006
- # version spec.
1007
- npm_version="$(jq -r --arg id "$tool_id" '.[$id].version // empty' "$resolved_file")"
985
+ # tree and package.json are keyed by package, never by a version spec.
1008
986
  npm_spec="$package"
1009
- [[ -z "$npm_version" ]] || npm_spec="$package@$npm_version"
1010
987
  # Only @amsterdamdatalabs/enact-docs-search's manifest entry carries
1011
988
  # this (docs/operations/global-tools.md Topic 2, decided 2026-09-28):
1012
989
  # its own dependencies' install scripts, explicitly reviewed and
@@ -1017,41 +994,20 @@ install_one_tool() {
1017
994
  previous_executable="$(jq -r --arg id "$tool_id" '.[$id].executable' "$previous_resolved_file")"
1018
995
  echo "would remove retired Bun compilation before npm install: $tool_id ($previous_executable and $(bun_source_hash_file "$tool_id"))"
1019
996
  fi
1020
- # A pinned version is a pure local/offline fact: compare it against
1021
- # the real global install (npm_installed_version, no network) and say
1022
- # so honestly. An UNPINNED entry's "latest stable" is not knowable
1023
- # without a per-package registry round trip (npm_latest_version) --
1024
- # doing that here would make a plan preview slow and network-bound
1025
- # for every unpinned npm tool on the device, so it keeps today's
1026
- # conservative message instead of guessing. Either way, only query
1027
- # when it needs no new sudo privilege (can_query_as_administrator) --
1028
- # a preview run must not demand a password a different device's real
1029
- # administrator would need.
1030
- if [[ -n "$npm_version" ]] && can_query_as_administrator; then
1031
- if npm_plan_installed="$(npm_installed_version "$package")"; then
1032
- if [[ -n "$npm_plan_installed" && "$npm_plan_installed" == "$npm_version" ]]; then
1033
- echo "already latest: $tool_id (npm:$package@$npm_plan_installed)"
1034
- else
1035
- echo "would install or upgrade npm package: $npm_spec"
1036
- fi
1037
- else
1038
- echo "would install or upgrade npm package: $npm_spec (installed version not checked: npm ls -g failed)"
1039
- fi
1040
- else
1041
- echo "would install or upgrade npm package: $npm_spec"
1042
- fi
997
+ # "latest stable" is not knowable without a per-package registry
998
+ # round trip (npm_latest_version) -- doing that here would make a
999
+ # plan preview slow and network-bound for every npm tool on the
1000
+ # device, so plan mode always prints this unconditional message
1001
+ # instead of guessing.
1002
+ echo "would install or upgrade npm package: $npm_spec"
1043
1003
  else
1044
1004
  if is_in_place_bun_to_npm_migration "$tool_id"; then
1045
1005
  migrating_from_bun=1
1046
1006
  prepare_in_place_bun_to_npm_migration "$tool_id" || rc=1
1047
1007
  fi
1048
- if [[ "$rc" -eq 0 ]]; then
1049
- if [[ -n "$npm_version" ]]; then
1050
- npm_target_version="$npm_version"
1051
- elif ! npm_target_version="$(npm_latest_version "$package")"; then
1052
- echo "$tool_id: npm view could not resolve the latest version of $package" >&2
1053
- rc=1
1054
- fi
1008
+ if [[ "$rc" -eq 0 ]] && ! npm_target_version="$(npm_latest_version "$package")"; then
1009
+ echo "$tool_id: npm view could not resolve the latest version of $package" >&2
1010
+ rc=1
1055
1011
  fi
1056
1012
  if [[ "$rc" -eq 0 ]]; then
1057
1013
  if ! npm_installed_ver="$(npm_installed_version "$package")"; then
@@ -1079,21 +1035,8 @@ install_one_tool() {
1079
1035
  fi
1080
1036
  [[ "$rc" -eq 0 ]] && { ensure_global_pip_executable "$executable" || rc=1; }
1081
1037
  ;;
1082
- release-archive)
1083
- local version url sha256 archive_executable
1084
- version="$(jq -r --arg id "$tool_id" '.[$id].version' "$resolved_file")"
1085
- url="$(jq -r --arg id "$tool_id" '.[$id].url' "$resolved_file")"
1086
- sha256="$(jq -r --arg id "$tool_id" '.[$id].sha256' "$resolved_file")"
1087
- archive_executable="$(jq -r --arg id "$tool_id" '.[$id].archive_executable' "$resolved_file")"
1088
- if [[ "$mode" == apply && -x "$executable" ]] &&
1089
- "$executable" version 2>/dev/null | head -n 1 | grep -Fq "v$version"; then
1090
- echo "pinned release already installed: $package $version"
1091
- else
1092
- run_release_archive "$package" "$version" "$url" "$sha256" "$archive_executable" "$executable" || rc=1
1093
- fi
1094
- ;;
1095
1038
  release-archive-latest)
1096
- local repo asset checksums_asset archive_executable latest_tag latest_version
1039
+ local repo asset checksums_asset archive_executable latest_tag latest_version resolved_asset resolved_checksums_asset
1097
1040
  repo="$(jq -r --arg id "$tool_id" '.[$id].repo' "$resolved_file")"
1098
1041
  asset="$(jq -r --arg id "$tool_id" '.[$id].asset' "$resolved_file")"
1099
1042
  checksums_asset="$(jq -r --arg id "$tool_id" '.[$id].checksums_asset // "SHA256SUMS"' "$resolved_file")"
@@ -1104,10 +1047,17 @@ install_one_tool() {
1104
1047
  latest_tag="$(resolve_latest_release_tag "$repo")" || { rc=1; latest_tag=""; }
1105
1048
  if [[ "$rc" -eq 0 ]]; then
1106
1049
  latest_version="${latest_tag#v}"
1107
- if [[ -x "$executable" ]] && "$executable" --version 2>/dev/null | head -n 1 | grep -Fq " $latest_version "; then
1050
+ # A release whose asset filename bakes in the version (agent-vault:
1051
+ # agent-vault_<ver>_darwin_arm64.tar.gz) declares "{version}" in
1052
+ # .asset/.checksums_asset; lean-ctx's own asset names carry no
1053
+ # version at all, so this is a no-op substring replace for it.
1054
+ resolved_asset="${asset//\{version\}/$latest_version}"
1055
+ resolved_checksums_asset="${checksums_asset//\{version\}/$latest_version}"
1056
+ if [[ -x "$executable" ]] &&
1057
+ release_archive_version_matches "$("$executable" --version 2>/dev/null | head -n 1)" "$latest_version"; then
1108
1058
  echo "latest release already installed: $package $latest_version"
1109
1059
  else
1110
- run_release_archive_latest "$package" "$repo" "$asset" "$checksums_asset" "$archive_executable" "$executable" || rc=1
1060
+ run_release_archive_latest "$package" "$repo" "$latest_tag" "$resolved_asset" "$resolved_checksums_asset" "$archive_executable" "$executable" || rc=1
1111
1061
  fi
1112
1062
  fi
1113
1063
  fi
@@ -1652,6 +1602,212 @@ remove_one_repo_dir() {
1652
1602
  rm -rf -- "$target"
1653
1603
  }
1654
1604
 
1605
+ # Lexical-only path normalization: collapses repeated slashes ("//" ->
1606
+ # "/") and current-dir segments ("/./" -> "/"), and drops one trailing
1607
+ # slash. Never resolves ".." or symlinks, and never touches the filesystem
1608
+ # -- the target frequently doesn't exist yet when this runs. Second review
1609
+ # pass, 2026-09-29: without this, "/usr/local/bin//agent-vault" and
1610
+ # "/usr/local/bin/agent-vault" compare unequal even though they name the
1611
+ # same real file, silently defeating executable_claimed_by_current_manifest
1612
+ # below (the guard would say "not claimed" and let the old manager's `rm -f`
1613
+ # through) -- previous_resolved_file is never validated, so a malformed
1614
+ # path there is exactly the case this guard exists to protect against.
1615
+ normalize_manifest_path() {
1616
+ local path="$1"
1617
+ while [[ "$path" == *//* ]]; do path="${path//\/\//\/}"; done
1618
+ while [[ "$path" == */./* ]]; do path="${path//\/.\//\/}"; done
1619
+ path="${path%/}"
1620
+ printf '%s\n' "$path"
1621
+ }
1622
+
1623
+ # A previous entry's executable path can be EXACTLY the path a CURRENT
1624
+ # entry now owns -- e.g. agent-vault-cli migrating release-archive ->
1625
+ # release-archive-latest, both /usr/local/bin/agent-vault. Bug found in
1626
+ # review, 2026-09-29: the install loop runs BEFORE the removal loop below
1627
+ # and already wrote/verified that binary; "retain shared package after
1628
+ # entry deletion" only skips removal when manager+package are BOTH
1629
+ # unchanged, so a manager-only change (exactly this migration) still
1630
+ # selects the id for removal, and the OLD manager's case arm then does a
1631
+ # plain `rm -f` on the path the install pass upstream just installed.
1632
+ # Checked by executable path, not by tool_id/manager/package, because that
1633
+ # is the one thing the filesystem actually cares about -- both sides
1634
+ # normalized first (see normalize_manifest_path) so a malformed legacy path
1635
+ # in previous_resolved_file still compares equal to a clean one.
1636
+ #
1637
+ # Second review pass, 2026-09-29 (bare-name executables): most managers
1638
+ # store an ABSOLUTE path in .executable (release-archive-latest, shim,
1639
+ # bun-compile, repo-script all require the /usr/local/bin/ prefix), but pip,
1640
+ # npm, and brew store a bare command name instead -- pip's own
1641
+ # ensure_global_pip_executable/remove_global_pip_executable ALWAYS place (or
1642
+ # remove) that name as a fixed /usr/local/bin/<name> symlink regardless of
1643
+ # the pip package name, so a bare pip .executable maps there and IS
1644
+ # compared. npm and brew do not get the same treatment: their real install
1645
+ # location depends on the device's own npm global prefix / Homebrew prefix,
1646
+ # resolved only through a live `command -v` PATH search
1647
+ # (npm_global_executable_path) that varies by platform -- never a single
1648
+ # fixed path this function can compute without shelling out. A migration
1649
+ # INTO an npm or brew entry is not detected here and the old manager's real
1650
+ # removal proceeds exactly as before this fix. That gap is narrower than it
1651
+ # looks: npm and brew each remove through their OWN package manager (`npm
1652
+ # uninstall -g`, `brew uninstall`), never the raw `rm -f` this guard
1653
+ # protects, so nothing here relies on rm to leave an npm/brew-owned file
1654
+ # alone in the first place.
1655
+ executable_claimed_by_current_manifest() {
1656
+ local target="$1" normalized_target normalized_current current_manager current_executable
1657
+ normalized_target="$(normalize_manifest_path "$target")"
1658
+ while IFS=$'\t' read -r current_manager current_executable; do
1659
+ [[ -n "$current_executable" ]] || continue
1660
+ if [[ "$current_executable" == /* ]]; then
1661
+ normalized_current="$(normalize_manifest_path "$current_executable")"
1662
+ elif [[ "$current_manager" == pip ]]; then
1663
+ normalized_current="$(normalize_manifest_path "/usr/local/bin/$current_executable")"
1664
+ else
1665
+ continue
1666
+ fi
1667
+ [[ "$normalized_current" == "$normalized_target" ]] && return 0
1668
+ done < <(jq -r '.[] | [.manager, .executable] | @tsv' "$resolved_file")
1669
+ return 1
1670
+ }
1671
+
1672
+ # Uninstalls a single previously-applied tool the CURRENT manifest no longer
1673
+ # declares (or has migrated to a different manager/package) -- extracted
1674
+ # into its own function, like install_one_tool, so
1675
+ # packages/fleet/tests/test-manifest.sh can exercise it directly through the
1676
+ # RECONCILE_SOURCE_ONLY seam instead of only through a real apply run.
1677
+ remove_one_manifest_entry() {
1678
+ local tool_id="$1" manager package executable removed_repo_dir_target
1679
+ if jq -e --arg id "$tool_id" '.config.protected_tools | index($id)' "$manifest" >/dev/null; then
1680
+ echo "refusing to remove protected bootstrap tool: $tool_id" >&2
1681
+ exit 1
1682
+ fi
1683
+ manager="$(jq -r --arg id "$tool_id" '.[$id].manager' "$previous_resolved_file")"
1684
+ package="$(jq -r --arg id "$tool_id" '.[$id].package' "$previous_resolved_file")"
1685
+ executable="$(jq -r --arg id "$tool_id" '.[$id].executable' "$previous_resolved_file")"
1686
+ if is_in_place_bun_to_npm_migration "$tool_id"; then
1687
+ if [[ "$mode" == apply ]]; then
1688
+ echo "retired Bun compilation already removed for npm migration: $tool_id"
1689
+ fi
1690
+ return 0
1691
+ fi
1692
+ if jq -e --arg manager "$manager" --arg package "$package" 'to_entries | any(.value.manager == $manager and .value.package == $package)' "$resolved_file" >/dev/null; then
1693
+ echo "retain shared package after entry deletion: $manager:$package"
1694
+ return 0
1695
+ fi
1696
+ # Truthfulness fix (second review pass, 2026-09-29): for the managers that
1697
+ # remove via a raw filesystem `rm -f` on a fixed path (never their own
1698
+ # package manager's uninstall command), check executable_claimed_by_
1699
+ # current_manifest BEFORE claiming an "uninstall" happened at all. The
1700
+ # log line printed below this case is the only outcome a human or another
1701
+ # tool sees; printing "explicit manifest deletion: uninstall ..." and only
1702
+ # THEN discovering the file is retained (as this used to) makes the log
1703
+ # lie about what just happened, exactly backwards from "log the decision
1704
+ # as the outcome."
1705
+ case "$manager" in
1706
+ release-archive | release-archive-latest | shim | bun-compile)
1707
+ if executable_claimed_by_current_manifest "$executable"; then
1708
+ echo "retain $tool_id ($manager:$package): $executable is still declared by the current manifest"
1709
+ return 0
1710
+ fi
1711
+ ;;
1712
+ esac
1713
+ echo "explicit manifest deletion: uninstall $tool_id ($manager:$package)"
1714
+ case "$manager" in
1715
+ brew) run_brew uninstall "$package" ;;
1716
+ apt) run_apt remove -y "$package" ;;
1717
+ npm) run_npm uninstall -g "$package" ;;
1718
+ pip)
1719
+ run_pip uninstall --break-system-packages -y "$package"
1720
+ remove_global_pip_executable "$executable"
1721
+ ;;
1722
+ # "release-archive" (pinned version+url+sha256) is retired from
1723
+ # validate-manifest's accepted managers (Tarun, 2026-09-29: versions
1724
+ # are never pinned) -- no CURRENT manifest.json can declare one, so
1725
+ # install_one_tool no longer has a case for it. This removal arm stays
1726
+ # so a device whose applied-manifest.json still remembers an old
1727
+ # release-archive entry (agent-vault-cli, before this change) can
1728
+ # still cleanly uninstall it the next time it reconciles; previous
1729
+ # state is read straight from disk, never re-validated.
1730
+ release-archive)
1731
+ [[ "$executable" == /usr/local/bin/* ]] || {
1732
+ echo "refusing unsafe release archive removal target: $executable" >&2
1733
+ exit 1
1734
+ }
1735
+ if [[ "$mode" == apply ]]; then
1736
+ rm -f -- "$executable"
1737
+ else
1738
+ echo "would remove pinned release executable: $executable"
1739
+ fi
1740
+ ;;
1741
+ release-archive-latest)
1742
+ [[ "$executable" == /usr/local/bin/* ]] || {
1743
+ echo "refusing unsafe release archive removal target: $executable" >&2
1744
+ exit 1
1745
+ }
1746
+ if [[ "$mode" == apply ]]; then
1747
+ rm -f -- "$executable"
1748
+ else
1749
+ echo "would remove latest release executable: $executable"
1750
+ fi
1751
+ ;;
1752
+ shim)
1753
+ [[ "$executable" == /usr/local/bin/* ]] || {
1754
+ echo "refusing unsafe shim removal target: $executable" >&2
1755
+ exit 1
1756
+ }
1757
+ if [[ "$mode" == apply ]]; then
1758
+ rm -f -- "$executable"
1759
+ else
1760
+ echo "would remove shim: $executable"
1761
+ fi
1762
+ ;;
1763
+ repo-script)
1764
+ if [[ "$mode" == apply ]]; then
1765
+ remove_one_repo_script "$executable"
1766
+ else
1767
+ [[ "$executable" =~ ^/usr/local/bin/[A-Za-z0-9][A-Za-z0-9._-]*$ ]] || {
1768
+ echo "refusing unsafe managed script removal target: $executable" >&2
1769
+ exit 1
1770
+ }
1771
+ echo "would remove managed repo script: $executable"
1772
+ fi
1773
+ ;;
1774
+ repo-dir)
1775
+ removed_repo_dir_target="$(jq -r --arg id "$tool_id" '.[$id].target' "$previous_resolved_file")"
1776
+ if [[ "$mode" == apply ]]; then
1777
+ remove_one_repo_dir "$removed_repo_dir_target"
1778
+ else
1779
+ [[ "$removed_repo_dir_target" == "$repo_dir_libexec_root"/* ]] || {
1780
+ echo "refusing unsafe repo-dir removal target: $removed_repo_dir_target" >&2
1781
+ exit 1
1782
+ }
1783
+ echo "would remove repo dir: $removed_repo_dir_target"
1784
+ fi
1785
+ ;;
1786
+ bun-compile)
1787
+ [[ "$executable" == /usr/local/bin/* ]] || {
1788
+ echo "refusing unsafe bun binary removal target: $executable" >&2
1789
+ exit 1
1790
+ }
1791
+ if [[ "$mode" == apply ]]; then
1792
+ rm -f -- "$executable"
1793
+ else
1794
+ echo "would remove bun binary: $executable"
1795
+ fi
1796
+ ;;
1797
+ embedding-model)
1798
+ if [[ "$mode" == apply ]]; then
1799
+ [[ "$(id -u)" -eq 0 ]] || { echo "apply mode must run as root" >&2; exit 1; }
1800
+ command -v bun >/dev/null 2>&1 || { echo "embedding-model requires bun; ensure bun installs before this tool" >&2; exit 1; }
1801
+ (cd /tmp && sudo -u "$administrator" -H env PATH="${brew_bin%/*}:/usr/bin:/bin" \
1802
+ bun "$repo_root/packages/fleet/src/install-embedding-model.ts" uninstall </dev/null)
1803
+ else
1804
+ echo "would remove embedding model cache: ~/.cache/adl-embedding/model (as $administrator)"
1805
+ fi
1806
+ ;;
1807
+ *) echo "unsupported manager in previous manifest: $manager" >&2; exit 1 ;;
1808
+ esac
1809
+ }
1810
+
1655
1811
  # config.git_global_config: global git config for the administrator (the
1656
1812
  # account whose HOME the per-user deployers below also write to). Apply runs git as
1657
1813
  # that user (sudo -u -H) so ~/.gitconfig stays user-owned; plan/audit only
@@ -2194,111 +2350,7 @@ reconcile_surface_clis
2194
2350
  if [[ "$have_previous" -eq 1 ]]; then
2195
2351
  while IFS= read -r tool_id; do
2196
2352
  [[ -n "$tool_id" ]] || continue
2197
- if jq -e --arg id "$tool_id" '.config.protected_tools | index($id)' "$manifest" >/dev/null; then
2198
- echo "refusing to remove protected bootstrap tool: $tool_id" >&2
2199
- exit 1
2200
- fi
2201
- manager="$(jq -r --arg id "$tool_id" '.[$id].manager' "$previous_resolved_file")"
2202
- package="$(jq -r --arg id "$tool_id" '.[$id].package' "$previous_resolved_file")"
2203
- executable="$(jq -r --arg id "$tool_id" '.[$id].executable' "$previous_resolved_file")"
2204
- if is_in_place_bun_to_npm_migration "$tool_id"; then
2205
- if [[ "$mode" == apply ]]; then
2206
- echo "retired Bun compilation already removed for npm migration: $tool_id"
2207
- fi
2208
- continue
2209
- fi
2210
- if jq -e --arg manager "$manager" --arg package "$package" 'to_entries | any(.value.manager == $manager and .value.package == $package)' "$resolved_file" >/dev/null; then
2211
- echo "retain shared package after entry deletion: $manager:$package"
2212
- continue
2213
- fi
2214
- echo "explicit manifest deletion: uninstall $tool_id ($manager:$package)"
2215
- case "$manager" in
2216
- brew) run_brew uninstall "$package" ;;
2217
- apt) run_apt remove -y "$package" ;;
2218
- npm) run_npm uninstall -g "$package" ;;
2219
- pip)
2220
- run_pip uninstall --break-system-packages -y "$package"
2221
- remove_global_pip_executable "$executable"
2222
- ;;
2223
- release-archive)
2224
- [[ "$executable" == /usr/local/bin/* ]] || {
2225
- echo "refusing unsafe release archive removal target: $executable" >&2
2226
- exit 1
2227
- }
2228
- if [[ "$mode" == apply ]]; then
2229
- rm -f -- "$executable"
2230
- else
2231
- echo "would remove pinned release executable: $executable"
2232
- fi
2233
- ;;
2234
- release-archive-latest)
2235
- [[ "$executable" == /usr/local/bin/* ]] || {
2236
- echo "refusing unsafe release archive removal target: $executable" >&2
2237
- exit 1
2238
- }
2239
- if [[ "$mode" == apply ]]; then
2240
- rm -f -- "$executable"
2241
- else
2242
- echo "would remove latest release executable: $executable"
2243
- fi
2244
- ;;
2245
- shim)
2246
- [[ "$executable" == /usr/local/bin/* ]] || {
2247
- echo "refusing unsafe shim removal target: $executable" >&2
2248
- exit 1
2249
- }
2250
- if [[ "$mode" == apply ]]; then
2251
- rm -f -- "$executable"
2252
- else
2253
- echo "would remove shim: $executable"
2254
- fi
2255
- ;;
2256
- repo-script)
2257
- if [[ "$mode" == apply ]]; then
2258
- remove_one_repo_script "$executable"
2259
- else
2260
- [[ "$executable" =~ ^/usr/local/bin/[A-Za-z0-9][A-Za-z0-9._-]*$ ]] || {
2261
- echo "refusing unsafe managed script removal target: $executable" >&2
2262
- exit 1
2263
- }
2264
- echo "would remove managed repo script: $executable"
2265
- fi
2266
- ;;
2267
- repo-dir)
2268
- removed_repo_dir_target="$(jq -r --arg id "$tool_id" '.[$id].target' "$previous_resolved_file")"
2269
- if [[ "$mode" == apply ]]; then
2270
- remove_one_repo_dir "$removed_repo_dir_target"
2271
- else
2272
- [[ "$removed_repo_dir_target" == "$repo_dir_libexec_root"/* ]] || {
2273
- echo "refusing unsafe repo-dir removal target: $removed_repo_dir_target" >&2
2274
- exit 1
2275
- }
2276
- echo "would remove repo dir: $removed_repo_dir_target"
2277
- fi
2278
- ;;
2279
- bun-compile)
2280
- [[ "$executable" == /usr/local/bin/* ]] || {
2281
- echo "refusing unsafe bun binary removal target: $executable" >&2
2282
- exit 1
2283
- }
2284
- if [[ "$mode" == apply ]]; then
2285
- rm -f -- "$executable"
2286
- else
2287
- echo "would remove bun binary: $executable"
2288
- fi
2289
- ;;
2290
- embedding-model)
2291
- if [[ "$mode" == apply ]]; then
2292
- [[ "$(id -u)" -eq 0 ]] || { echo "apply mode must run as root" >&2; exit 1; }
2293
- command -v bun >/dev/null 2>&1 || { echo "embedding-model requires bun; ensure bun installs before this tool" >&2; exit 1; }
2294
- (cd /tmp && sudo -u "$administrator" -H env PATH="${brew_bin%/*}:/usr/bin:/bin" \
2295
- bun "$repo_root/packages/fleet/src/install-embedding-model.ts" uninstall </dev/null)
2296
- else
2297
- echo "would remove embedding model cache: ~/.cache/adl-embedding/model (as $administrator)"
2298
- fi
2299
- ;;
2300
- *) echo "unsupported manager in previous manifest: $manager" >&2; exit 1 ;;
2301
- esac
2353
+ remove_one_manifest_entry "$tool_id"
2302
2354
  # Selects an id when it was deleted outright (absent from the current
2303
2355
  # manifest) OR retained but migrated to a different manager/package (e.g.
2304
2356
  # lean-ctx moving from npm:lean-ctx-bin to release-archive-latest:lean-ctx)
@@ -70,18 +70,19 @@ if ! jq -e '
70
70
  (.mac.devices.m3.surface_clis // [] | any(.name == "codex" and .manager == "brew" and .command == "brew" and .update_args == ["upgrade", "--cask", "codex"] and (.report_only // false) == false)) and
71
71
  (.mac.devices.m5.surface_clis // [] | any(.name == "codex" and .manager == "brew" and .command == "brew" and .update_args == ["upgrade", "--cask", "codex"] and (.report_only // false) == false)) and
72
72
  ([.mac.common.tools[], .linux.common.tools[], .mac.devices[].tools[], .linux.devices[].tools[]] |
73
- all(.manager == "brew" or .manager == "apt" or .manager == "npm" or .manager == "pip" or .manager == "release-archive" or .manager == "release-archive-latest" or .manager == "shim" or .manager == "repo-script" or .manager == "repo-dir" or .manager == "bun-compile" or .manager == "embedding-model") and
73
+ all(.manager == "brew" or .manager == "apt" or .manager == "npm" or .manager == "pip" or .manager == "release-archive-latest" or .manager == "shim" or .manager == "repo-script" or .manager == "repo-dir" or .manager == "bun-compile" or .manager == "embedding-model") and
74
74
  all((.package | type) == "string" and (.package | length) > 0) and
75
75
  all((.executable | type) == "string" and (.executable | length) > 0) and
76
+ # Second review pass, 2026-09-29: the removal-path guard in bin/
77
+ # reconcile (executable_claimed_by_current_manifest) compares
78
+ # .executable paths lexically after normalizing a legacy, unvalidated
79
+ # previous-manifest path -- a CURRENT manifest must never need that
80
+ # normalization in the first place, so a "//" or "/./" segment is
81
+ # rejected here outright rather than silently accepted and normalized
82
+ # away later.
83
+ all((.executable | contains("//") | not) and (.executable | contains("/./") | not)) and
76
84
  all((.reason // null) == null or (.reason | type == "string" and length > 0)) and
77
- all(if .manager == "release-archive" then
78
- ((.version | type) == "string" and (.version | length) > 0) and
79
- ((.url | type) == "string" and (.url | startswith("https://"))) and
80
- ((.sha256 | type) == "string" and (.sha256 | test("^[0-9a-f]{64}$"))) and
81
- ((.archive_executable | type) == "string" and
82
- (.archive_executable | test("^[^/]") and (contains("..") | not))) and
83
- (.executable | startswith("/usr/local/bin/"))
84
- elif .manager == "release-archive-latest" then
85
+ all(if .manager == "release-archive-latest" then
85
86
  ((.repo | type) == "string" and (.repo | test("^[^/[:space:]]+/[^/[:space:]]+$"))) and
86
87
  ((.asset | type) == "string" and
87
88
  (.asset | test("^[^/]") and (contains("..") | not))) and
@@ -124,12 +125,13 @@ if ! jq -e '
124
125
  ((.dim | type) == "number") and
125
126
  (has("revision") | not) and (has("source_files") | not) and (has("artifact_files") | not)
126
127
  elif .manager == "npm" then
127
- # .version is optional for npm: absent means update_policy=latest-stable
128
- # tracks whatever npm resolves; present pins the install_one_tool
129
- # $package@$version spec (Handoff 5, 2026-09-26, H4). No checksum field
130
- # exists for npm -- there is no release-archive-style sha256 pin here,
131
- # and none is required or accepted.
132
- ((has("version") | not) or ((.version | type) == "string" and (.version | length) > 0)) and
128
+ # Tarun, 2026-09-29 (hard rule): versions are never pinned -- every
129
+ # fleet tool installs latest stable. .version on npm used to be an
130
+ # optional pin (Handoff 5, 2026-09-26, H4); it is refused outright now,
131
+ # the same way release-archive-latest already refuses one, and the
132
+ # same day the release-archive manager (pinned version+url+sha256) was
133
+ # removed from the accepted managers above entirely.
134
+ (has("version") | not) and
133
135
  # .allow_scripts (docs/operations/global-tools.md Topic 2, decided
134
136
  # 2026-09-28): an explicit, per-entry allow-list of dependency package
135
137
  # names whose install/postinstall scripts reconcile passes through as
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@amsterdamdatalabs/adl-agent-control-darwin-arm64",
3
- "version": "0.1.5",
3
+ "version": "0.1.7",
4
4
  "description": "adl CLI binary for darwin/arm64 (agent identity + Azure DevOps front door — see docs/architecture/cli-design.md)",
5
5
  "license": "UNLICENSED",
6
6
  "os": [
@@ -44,7 +44,7 @@
44
44
  "mac": {
45
45
  "common": {
46
46
  "tools": {
47
- "agent-vault-cli": {"manager": "release-archive", "package": "agent-vault", "executable": "/usr/local/bin/agent-vault", "version": "0.39.3", "url": "https://github.com/Infisical/agent-vault/releases/download/v0.39.3/agent-vault_0.39.3_darwin_arm64.tar.gz", "sha256": "30e312f3016a489936ae27bc380f92bf715f3be88c35eba3030d55841d97dc14", "archive_executable": "agent-vault"},
47
+ "agent-vault-cli": {"manager": "release-archive-latest", "package": "agent-vault", "executable": "/usr/local/bin/agent-vault", "repo": "Infisical/agent-vault", "asset": "agent-vault_{version}_darwin_arm64.tar.gz", "checksums_asset": "checksums.txt", "archive_executable": "agent-vault"},
48
48
  "ast-grep": {"manager": "brew", "package": "ast-grep", "executable": "ast-grep"},
49
49
  "azure-cli": {"manager": "brew", "package": "azure-cli", "executable": "az"},
50
50
  "bun": {"manager": "brew", "package": "bun", "executable": "bun"},
@@ -57,7 +57,7 @@
57
57
  "docker-compose": {"manager": "brew", "package": "docker-compose", "executable": "docker-compose"},
58
58
  "docs-search": {"manager": "npm", "package": "@amsterdamdatalabs/enact-docs-search", "executable": "enact-docs-search", "allow_scripts": ["hnswlib-node", "onnxruntime-node", "sharp", "protobufjs"], "reason": "Separate search product that provides the search tool."},
59
59
  "adl": {"manager": "npm", "package": "@amsterdamdatalabs/adl-agent-control", "executable": "adl"},
60
- "adl-agent-id": {"manager": "npm", "package": "@amsterdamdatalabs/adl-agent-id", "executable": "adl-agent-id", "version": "0.4.0", "reason": "Separate Go binary with its own trust boundary."},
60
+ "adl-agent-id": {"manager": "npm", "package": "@amsterdamdatalabs/adl-agent-id", "executable": "adl-agent-id", "reason": "Separate Go binary with its own trust boundary."},
61
61
  "fd": {"manager": "brew", "package": "fd", "executable": "fd"},
62
62
  "git": {"manager": "brew", "package": "git", "executable": "git"},
63
63
  "jq": {"manager": "brew", "package": "jq", "executable": "jq"},
@@ -134,7 +134,7 @@
134
134
  "common": {
135
135
  "os_upgrade": true,
136
136
  "tools": {
137
- "agent-vault-cli": {"manager": "release-archive", "package": "agent-vault", "executable": "/usr/local/bin/agent-vault", "version": "0.39.3", "url": "https://github.com/Infisical/agent-vault/releases/download/v0.39.3/agent-vault_0.39.3_linux_amd64.tar.gz", "sha256": "edacbddb03c6f4221515d9875c3a7b0943c71434708e7fd13be0f7635a7baecc", "archive_executable": "agent-vault"},
137
+ "agent-vault-cli": {"manager": "release-archive-latest", "package": "agent-vault", "executable": "/usr/local/bin/agent-vault", "repo": "Infisical/agent-vault", "asset": "agent-vault_{version}_linux_amd64.tar.gz", "checksums_asset": "checksums.txt", "archive_executable": "agent-vault"},
138
138
  "ast-grep": {"manager": "brew", "package": "ast-grep", "executable": "ast-grep"},
139
139
  "azure-cli": {"manager": "brew", "package": "azure-cli", "executable": "az"},
140
140
  "bun": {"manager": "brew", "package": "bun", "executable": "bun"},
@@ -147,7 +147,7 @@
147
147
  "docker-compose": {"manager": "brew", "package": "docker-compose", "executable": "docker-compose"},
148
148
  "docs-search": {"manager": "npm", "package": "@amsterdamdatalabs/enact-docs-search", "executable": "enact-docs-search", "allow_scripts": ["hnswlib-node", "onnxruntime-node", "sharp", "protobufjs"], "reason": "Separate search product that provides the search tool."},
149
149
  "adl": {"manager": "npm", "package": "@amsterdamdatalabs/adl-agent-control", "executable": "adl"},
150
- "adl-agent-id": {"manager": "npm", "package": "@amsterdamdatalabs/adl-agent-id", "executable": "adl-agent-id", "version": "0.4.0", "reason": "Separate Go binary with its own trust boundary."},
150
+ "adl-agent-id": {"manager": "npm", "package": "@amsterdamdatalabs/adl-agent-id", "executable": "adl-agent-id", "reason": "Separate Go binary with its own trust boundary."},
151
151
  "fd": {"manager": "brew", "package": "fd", "executable": "fd"},
152
152
  "git": {"manager": "brew", "package": "git", "executable": "git"},
153
153
  "jq": {"manager": "brew", "package": "jq", "executable": "jq"},
@@ -93,7 +93,7 @@
93
93
  "additionalProperties": false,
94
94
  "required": ["manager", "package", "executable"],
95
95
  "properties": {
96
- "manager": {"enum": ["apt", "brew", "npm", "pip", "release-archive", "release-archive-latest", "shim", "repo-script", "bun-compile", "embedding-model"]},
96
+ "manager": {"enum": ["apt", "brew", "npm", "pip", "release-archive-latest", "shim", "repo-script", "bun-compile", "embedding-model"]},
97
97
  "package": {"type": "string", "minLength": 1},
98
98
  "executable": {"type": "string", "minLength": 1},
99
99
  "version": {"type": "string", "minLength": 1},
@@ -110,17 +110,18 @@
110
110
  "reason": {"type": "string", "minLength": 1}
111
111
  },
112
112
  "allOf": [
113
- {
114
- "if": {"properties": {"manager": {"const": "release-archive"}}},
115
- "then": {"required": ["version", "url", "sha256", "archive_executable"]}
116
- },
117
113
  {
118
114
  "if": {"properties": {"manager": {"const": "release-archive-latest"}}},
119
115
  "then": {
120
116
  "required": ["repo", "asset", "archive_executable"],
117
+ "not": {"anyOf": [{"required": ["version"]}, {"required": ["url"]}, {"required": ["sha256"]}]},
121
118
  "properties": {"executable": {"pattern": "^/usr/local/bin/"}}
122
119
  }
123
120
  },
121
+ {
122
+ "if": {"properties": {"manager": {"const": "npm"}}},
123
+ "then": {"not": {"required": ["version"]}}
124
+ },
124
125
  {
125
126
  "if": {"properties": {"manager": {"const": "shim"}}},
126
127
  "then": {"properties": {"executable": {"pattern": "^/usr/local/bin/"}}}
@@ -120,6 +120,23 @@ export const GITIGNORE_BLOCK_LINES: readonly string[] = [
120
120
  '# adl process convention: scratch and throwaway work, never committed',
121
121
  '/.scratchpad/',
122
122
  'packages/*/.scratchpad/',
123
+ '',
124
+ '# fleet tools that write into every checkout: docs-search index, code-review-graph, compliance pytest',
125
+ '.cache/',
126
+ '.code-review-graph/',
127
+ '.codegraph/',
128
+ '.pytest_cache/',
129
+ '',
130
+ '# dependencies, OS and log noise',
131
+ 'node_modules/',
132
+ '.DS_Store',
133
+ '*.log',
134
+ '',
135
+ '# local secrets and per-user agent settings, never committed',
136
+ '.env',
137
+ '.env.*',
138
+ '!.env.example',
139
+ '.claude/settings.local.json',
123
140
  ] as const
124
141
 
125
142
  interface SplitText {