@amsterdamdatalabs/adl-agent-control-linux-x64 0.1.9 → 0.1.12

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (41) hide show
  1. package/bin/adl +2 -2
  2. package/bin/git +1 -1
  3. package/bin/git-ado +2 -2
  4. package/bin/lib/bun-compile-build.sh +1 -1
  5. package/bin/lib/fleet-host.sh +59 -0
  6. package/bin/lib/lean-ctx-observe-hooks.sh +9 -9
  7. package/bin/reconcile +222 -249
  8. package/bin/reconcile-host +111 -0
  9. package/bin/setup +216 -0
  10. package/bin/sync-and-reconcile +52 -39
  11. package/bin/validate-manifest +15 -44
  12. package/package.json +1 -3
  13. package/packages/agent-hooks/package.json +1 -2
  14. package/packages/agent-hooks/src/hooks/crg/background.ts +179 -0
  15. package/packages/agent-hooks/src/hooks/crg/handler.ts +2 -1
  16. package/packages/agent-hooks/src/hooks/crg/index.ts +36 -5
  17. package/packages/agent-hooks/src/rules/impl/fs-trash.ts +2 -2
  18. package/packages/agent-hooks/src/rules/impl/git-paths.ts +5 -6
  19. package/packages/agent-hooks/src/rules/impl/tool-grep.ts +8 -8
  20. package/packages/agent-settings/assets/GLOBAL-AGENTS.md +4 -2
  21. package/packages/agent-settings/src/global-surface-sweep.ts +5 -15
  22. package/packages/agent-skills/package.json +1 -1
  23. package/packages/azdo/package.json +1 -2
  24. package/packages/azdo/src/azdo-repo-doctor.ts +70 -75
  25. package/packages/fleet/manifest.json +5 -24
  26. package/packages/fleet/manifest.schema.json +8 -58
  27. package/packages/fleet/src/install-embedding-model.ts +1 -1
  28. package/packages/fleet/src/repo-maintain.ts +403 -0
  29. package/packages/fleet/src/scratch-reap.ts +1 -1
  30. package/packages/repo-controls/README.md +46 -8
  31. package/packages/repo-controls/assets/promote.ts +12 -7
  32. package/packages/repo-controls/assets/reconcile-guard.sh +113 -0
  33. package/packages/repo-controls/assets/version-bump.sh +91 -51
  34. package/packages/repo-controls/package.json +0 -1
  35. package/packages/repo-controls/src/azure-devops-governance.ts +17 -16
  36. package/packages/repo-controls/src/constants.ts +8 -0
  37. package/packages/repo-controls/src/hook.ts +3 -2
  38. package/packages/repo-controls/src/integrity.ts +15 -1
  39. package/packages/fleet/jobs/git-maintain/src/main.ts +0 -848
  40. package/packages/fleet/systemd/adl-azure-agent-update.service +0 -13
  41. package/packages/fleet/systemd/adl-azure-agent-update.timer +0 -10
@@ -1,13 +1,22 @@
1
1
  #!/usr/bin/env bash
2
2
  set -euo pipefail
3
3
 
4
- # The integration build owns package version allocation, exactly as every
5
- # other repo on this standard does it (enact-extensions, enact-combined):
6
- # after a PR merges, bump the patch version on `integration`, commit it with
7
- # ***NO_CI*** and push it straight back to `integration`. The build service
8
- # has "bypass policies when pushing"; ***NO_CI*** stops the push from
9
- # triggering another pipeline run. No npm query, no second PR, no second
10
- # test run (Tarun, 2026-09-29).
4
+ # STAGE 1 of the PR pipeline (PR -> integration): the PR build owns package
5
+ # version allocation. Every PR into `integration` must carry exactly
6
+ # `<integration version> + patch 1`; this script makes it so, on the PR's
7
+ # SOURCE branch, before anything else validates it:
8
+ #
9
+ # version-bump.sh --file package.json --pr-source <feature branch> \
10
+ # --base integration --push
11
+ #
12
+ # The Azure Pipelines PR build checks out the detached refs/pull/N/merge
13
+ # commit with persisted credentials, so `git push` authenticates as the
14
+ # project build service. The bump commit is created on the source branch's own
15
+ # tip (never on the merge commit), carries no ***NO_CI*** (feature branches
16
+ # have no CI trigger, so the push re-queues the PR build, which then
17
+ # validates the bumped commit), and is pushed with a plain push — never
18
+ # forced. When the source is already at the target version nothing is
19
+ # committed and the run continues. No npm query, no second PR.
11
20
 
12
21
  # CI never carries an agent identity: the push below authenticates as the
13
22
  # project build service through the checkout's persisted System.AccessToken.
@@ -20,8 +29,8 @@ die() {
20
29
  }
21
30
 
22
31
  version_file=''
23
- integration_branch=''
24
- integration_mode=false
32
+ pr_source=''
33
+ base_branch=''
25
34
  push_bump=false
26
35
 
27
36
  while [[ $# -gt 0 ]]; do
@@ -31,14 +40,15 @@ while [[ $# -gt 0 ]]; do
31
40
  version_file="$2"
32
41
  shift 2
33
42
  ;;
34
- --branch)
35
- [[ $# -ge 2 ]] || die '--branch requires a name'
36
- integration_branch="$2"
43
+ --pr-source)
44
+ [[ $# -ge 2 ]] || die '--pr-source requires a branch ref'
45
+ pr_source="$2"
37
46
  shift 2
38
47
  ;;
39
- --integration)
40
- integration_mode=true
41
- shift
48
+ --base)
49
+ [[ $# -ge 2 ]] || die '--base requires a branch name'
50
+ base_branch="$2"
51
+ shift 2
42
52
  ;;
43
53
  --push)
44
54
  push_bump=true
@@ -51,46 +61,73 @@ while [[ $# -gt 0 ]]; do
51
61
  done
52
62
 
53
63
  [[ "$version_file" == 'package.json' ]] || die 'only --file package.json is supported'
54
- [[ -f "$version_file" ]] || die "manifest not found: $version_file"
55
- [[ -n "$integration_branch" ]] || die '--branch is required'
56
- [[ "$integration_mode" == true ]] || die '--integration is required'
64
+ [[ -n "$pr_source" ]] || die '--pr-source is required'
65
+ [[ -n "$base_branch" ]] || die '--base is required'
57
66
  [[ "$push_bump" == true ]] || die '--push is required'
58
67
 
59
- current_branch="$(git branch --show-current)"
60
- if [[ -z "$current_branch" ]]; then
61
- # Azure Pipelines' `checkout: self` leaves the working tree on a detached
62
- # HEAD (build 4822, integration, 2026-09-27: `git branch --show-current`
63
- # came back empty even though the checkout was the right commit on
64
- # `integration`). Fall back to Build.SourceBranch (env BUILD_SOURCEBRANCH,
65
- # e.g. `refs/heads/integration`) rather than requiring the pipeline to
66
- # `git checkout -B` a real branch first.
67
- case "${BUILD_SOURCEBRANCH:-}" in
68
- refs/heads/*)
69
- current_branch="${BUILD_SOURCEBRANCH#refs/heads/}"
70
- ;;
71
- '')
72
- die 'cannot determine the current branch: HEAD is detached (git branch --show-current is empty) and BUILD_SOURCEBRANCH is not set'
73
- ;;
74
- *)
75
- die "BUILD_SOURCEBRANCH '$BUILD_SOURCEBRANCH' is not a branch ref (expected 'refs/heads/<name>')"
76
- ;;
77
- esac
68
+ # Accept `refs/heads/x` (Build.SourceBranch / System.PullRequest.SourceBranch)
69
+ # or a bare `x`; anything else under refs/ is not a branch.
70
+ source_branch="${pr_source#refs/heads/}"
71
+ case "$source_branch" in
72
+ '') die '--pr-source names an empty branch' ;;
73
+ refs/*) die "--pr-source '$pr_source' is not a branch ref (expected 'refs/heads/<name>' or '<name>')" ;;
74
+ esac
75
+ base_branch="${base_branch#refs/heads/}"
76
+ if [[ "$source_branch" == "$base_branch" || "$source_branch" == 'main' || "$source_branch" == 'integration' ]]; then
77
+ die "refusing to bump '$source_branch': the PR source must be a feature branch, not the base ('$base_branch'), main or integration"
78
78
  fi
79
- [[ "$current_branch" == "$integration_branch" ]] || \
80
- die "integration mode expects branch '$integration_branch', currently on '$current_branch'"
81
-
82
- current_version="$(node -e 'const version = JSON.parse(require("node:fs").readFileSync(process.argv[1], "utf8")).version; if (typeof version !== "string") process.exit(1); process.stdout.write(version)' "$version_file")" || \
83
- die "cannot read a string version from $version_file"
84
79
 
85
- if [[ ! "$current_version" =~ ^([0-9]+)\.([0-9]+)\.([0-9]+)$ ]]; then
86
- die "cannot parse '$current_version' as a release version x.y.z"
80
+ git diff --quiet || die 'working tree is not clean before version bump'
81
+ git diff --cached --quiet || die 'index is not clean before version bump'
82
+ repo_root="$(git rev-parse --show-toplevel)" || die 'cannot resolve the repository root'
83
+ # The script runs from the package directory (e.g. apps/cli); the manifest's
84
+ # repository-relative path is how it is read from the base branch.
85
+ repo_prefix="$(git rev-parse --show-prefix)" || die 'cannot resolve the working directory inside the repository'
86
+ repo_relative_file="${repo_prefix}${version_file}"
87
+
88
+ # Read both tips fresh from origin. The PR build's checkout is the detached
89
+ # merge commit; bumping that would push a commit that is not the branch's own
90
+ # history, so the working tree is moved onto a REAL branch at the source tip.
91
+ git fetch origin \
92
+ "refs/heads/$base_branch:refs/remotes/origin/$base_branch" \
93
+ "refs/heads/$source_branch:refs/remotes/origin/$source_branch" || \
94
+ die "cannot fetch '$base_branch' and '$source_branch' from origin"
95
+
96
+ # The source must already contain the base's tip: the version it is bumped to
97
+ # is derived from that tip, so a source behind it would be validated against a
98
+ # merge the bump never saw.
99
+ if ! git merge-base --is-ancestor "refs/remotes/origin/$base_branch" "refs/remotes/origin/$source_branch"; then
100
+ missing_count="$(git rev-list --count "refs/remotes/origin/$source_branch..refs/remotes/origin/$base_branch")"
101
+ base_short="$(git rev-parse --short "refs/remotes/origin/$base_branch")"
102
+ echo "version-bump: FAIL — $source_branch does not contain $base_branch's tip ($base_short); reconcile first: git pull origin $base_branch && git push" >&2
103
+ echo "version-bump: $missing_count commit(s) on $base_branch are missing from $source_branch" >&2
104
+ exit 1
87
105
  fi
88
106
 
107
+ git checkout -q -B "$source_branch" "refs/remotes/origin/$source_branch" || \
108
+ die "cannot check out '$source_branch' at its origin tip"
109
+
110
+ [[ -f "$version_file" ]] || die "manifest not found: $version_file"
111
+
112
+ read_version() {
113
+ node -e 'const version = JSON.parse(require("node:fs").readFileSync(0, "utf8")).version; if (typeof version !== "string") process.exit(1); process.stdout.write(version)'
114
+ }
115
+
116
+ base_version="$(git show "refs/remotes/origin/$base_branch:$repo_relative_file" | read_version)" || \
117
+ die "cannot read a string version from $repo_relative_file on origin/$base_branch"
118
+ if [[ ! "$base_version" =~ ^([0-9]+)\.([0-9]+)\.([0-9]+)$ ]]; then
119
+ die "cannot parse '$base_version' (origin/$base_branch) as a release version x.y.z"
120
+ fi
89
121
  next_version="${BASH_REMATCH[1]}.${BASH_REMATCH[2]}.$((BASH_REMATCH[3] + 1))"
90
122
 
91
- git diff --quiet || die 'working tree is not clean before version bump'
92
- git diff --cached --quiet || die 'index is not clean before version bump'
93
- repo_root="$(git rev-parse --show-toplevel)" || die 'cannot resolve the repository root'
123
+ current_version="$(read_version < "$version_file")" || die "cannot read a string version from $version_file"
124
+
125
+ if [[ "$current_version" == "$next_version" ]]; then
126
+ echo "version-bump: $source_branch already at $next_version (integration $base_version); nothing to bump"
127
+ echo '##vso[task.setvariable variable=bumped;isOutput=true]false'
128
+ exit 0
129
+ fi
130
+
94
131
  node -e '
95
132
  const fs = require("node:fs")
96
133
  const [file, version] = process.argv.slice(1)
@@ -105,7 +142,7 @@ node -e '
105
142
  # package.json can reach. No file, or no `pre_bump` key: no scripts, and
106
143
  # everything below this comment is a no-op, so the resulting commit is
107
144
  # byte-identical to the behaviour before this feature existed (see
108
- # packages/repo-controls/tests/version-bump-pre-bump.test.ts).
145
+ # packages/repo-controls/tests/version-bump-pr-source-pre-bump.test.ts).
109
146
  config_path="$repo_root/adl-config.toml"
110
147
  pre_bump_list=''
111
148
  if [[ -f "$config_path" ]]; then
@@ -189,6 +226,9 @@ fi
189
226
 
190
227
  git -C "$repo_root" add -A
191
228
  git diff --cached --quiet && die "version bump produced no staged change (expected at least $version_file)"
192
- git commit -m "chore: bump version $current_version to $next_version ***NO_CI***"
193
- git push origin "HEAD:$integration_branch"
194
- echo "Bumped $version_file from $current_version to $next_version on $integration_branch."
229
+ # No ***NO_CI***: a feature branch has no CI trigger, so this push is what
230
+ # re-queues the PR build against the bumped commit.
231
+ git commit -m "chore: bump version $current_version to $next_version"
232
+ git push origin "HEAD:refs/heads/$source_branch"
233
+ echo "version-bump: bumped $source_branch from $current_version to $next_version (integration $base_version); pushed — this run ends, the re-queued PR build validates the bumped commit"
234
+ echo '##vso[task.setvariable variable=bumped;isOutput=true]true'
@@ -14,7 +14,6 @@
14
14
  "scripts": {
15
15
  "typecheck": "tsc --noEmit",
16
16
  "test": "bun test tests --coverage --timeout 30000",
17
- "test:coverage": "bun test tests --coverage --timeout 30000",
18
17
  "check": "tsc --noEmit && bun test tests --coverage --timeout 30000",
19
18
  "coverage:ratchet": "bun workflows/scripts/coverage/ci-ratchet.ts",
20
19
  "coverage:produce": "bun test tests --coverage --timeout 30000 && bun workflows/scripts/coverage/command.ts"
@@ -168,9 +168,12 @@ export interface AzureDevOpsGovernanceOptions {
168
168
  }
169
169
 
170
170
  /**
171
- * One read-only receipt per promotion guard. Unreadable evidence is a failure,
172
- * never a warning: a commit cannot claim the remote guard is armed when that
173
- * fact could not be established.
171
+ * One read-only receipt per promotion guard. The only required guard on the
172
+ * target branch is the minimum-reviewer policy (Tarun, 2026-09-30): a build
173
+ * policy is NOT required, because the integration -> main promotion PR gets no
174
+ * build validation and CD runs after the merge. Unreadable evidence is a
175
+ * failure, never a warning: a commit cannot claim the remote guard is armed
176
+ * when that fact could not be established.
174
177
  */
175
178
  export function azureDevOpsGovernanceChecks(options: AzureDevOpsGovernanceOptions): AzureDevOpsGovernanceCheck[] {
176
179
  const checks: AzureDevOpsGovernanceCheck[] = []
@@ -231,18 +234,16 @@ export function azureDevOpsGovernanceChecks(options: AzureDevOpsGovernanceOption
231
234
  message: `missing blocking minimum-reviewer policy requiring one approval and resetOnSourcePush=true for ${prefix}`,
232
235
  },
233
236
  )
234
- checks.push(
235
- receipts.build
236
- ? {
237
- id: 'azure-devops.build-policy',
238
- status: 'ok',
239
- message: `blocking build policy #${id(receipts.build)} uses definition ${receipts.build.settings?.buildDefinitionId} for ${prefix}`,
240
- }
241
- : {
242
- id: 'azure-devops.build-policy',
243
- status: 'fail',
244
- message: `missing blocking build policy with a build definition for ${prefix}`,
245
- },
246
- )
237
+ // No build-policy requirement on `main` (Tarun, 2026-09-30): the promotion PR
238
+ // gets no build validation, so an absent build policy is not a failure. A
239
+ // present one is only recorded, never flagged here (the azdo repo doctor
240
+ // owns that).
241
+ if (receipts.build) {
242
+ checks.push({
243
+ id: 'azure-devops.build-policy',
244
+ status: 'ok',
245
+ message: `blocking build policy #${id(receipts.build)} uses definition ${receipts.build.settings?.buildDefinitionId} for ${prefix}`,
246
+ })
247
+ }
247
248
  return checks
248
249
  }
@@ -63,6 +63,8 @@ export const PROMOTE_REL = '.workflows/promote.ts'
63
63
  export const AUTO_COMPLETE_REL = '.workflows/scripts/enable-integration-auto-complete.mjs'
64
64
  /** The standalone immutable package-version allocator for integration CI. */
65
65
  export const VERSION_BUMP_REL = '.workflows/version-bump.sh'
66
+ /** The standalone CI stage-2 guard: a PR branch must contain integration's tip and be at its version + 1 patch. */
67
+ export const RECONCILE_GUARD_REL = '.workflows/reconcile-guard.sh'
66
68
 
67
69
  export const MANAGED_ASSETS = [
68
70
  { rel: PROMOTE_REL, source: join(PACKAGE_ROOT, 'assets', 'promote.ts'), mode: 0o755, doctorId: 'promote' },
@@ -78,6 +80,12 @@ export const MANAGED_ASSETS = [
78
80
  mode: 0o755,
79
81
  doctorId: 'version-bump',
80
82
  },
83
+ {
84
+ rel: RECONCILE_GUARD_REL,
85
+ source: join(PACKAGE_ROOT, 'assets', 'reconcile-guard.sh'),
86
+ mode: 0o755,
87
+ doctorId: 'reconcile-guard',
88
+ },
81
89
  ] as const
82
90
  /** Shared with adl hook ([hooks]/[rules]/[repo]) and adl-extensions ([skills]); this package owns only [controls]. */
83
91
  export const CONFIG_REL = 'adl-config.toml'
@@ -430,8 +430,9 @@ function dirtyPaths(porcelain: string): string[] {
430
430
  const hasOrigin = staged === 'R' || staged === 'C' || worktree === 'R' || worktree === 'C'
431
431
  const origin = hasOrigin ? records[++index] : undefined
432
432
  // An untracked path has no index entry, so it is necessarily omitted from
433
- // this commit. Keep it: centrally managed dot paths must be staged rather
434
- // than silently left behind.
433
+ // this commit. Keep it: other top-level dot paths must be staged rather than
434
+ // silently left behind, and a protected root (.workflows, .agents, .claude,
435
+ // .codex, .cursor, .kimi-code, .opencode) is refused outright (integrity.ts).
435
436
  if (staged === '?' && worktree === '?') {
436
437
  paths.push(record.slice(3))
437
438
  continue
@@ -9,7 +9,21 @@
9
9
 
10
10
  export const WIRING: readonly string[] = ['adl-config.toml']
11
11
 
12
- const FIXED_ROOTS: readonly string[] = ['compliance', 'workflows']
12
+ // Tarun, 2026-09-30: `.workflows/` and every agent surface root are protected
13
+ // like `workflows/` — a dirty path under any of them blocks a normal commit and
14
+ // only Tarun lands it. They were "centrally managed dotfiles" an agent could
15
+ // commit alongside a related change; that let an agent land gate-defining files.
16
+ const FIXED_ROOTS: readonly string[] = [
17
+ 'compliance',
18
+ 'workflows',
19
+ '.workflows',
20
+ '.agents',
21
+ '.claude',
22
+ '.codex',
23
+ '.cursor',
24
+ '.kimi-code',
25
+ '.opencode',
26
+ ]
13
27
 
14
28
  /** A declared pattern, as a regex. `*` stays in one directory; `**` crosses them. */
15
29
  function patternToRegExp(pattern: string): RegExp {