@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.
- package/bin/adl +2 -2
- package/bin/git +1 -1
- package/bin/git-ado +2 -2
- package/bin/lib/bun-compile-build.sh +1 -1
- package/bin/lib/fleet-host.sh +59 -0
- package/bin/lib/lean-ctx-observe-hooks.sh +9 -9
- package/bin/reconcile +222 -249
- package/bin/reconcile-host +111 -0
- package/bin/setup +216 -0
- package/bin/sync-and-reconcile +52 -39
- package/bin/validate-manifest +15 -44
- package/package.json +1 -3
- package/packages/agent-hooks/package.json +1 -2
- package/packages/agent-hooks/src/hooks/crg/background.ts +179 -0
- package/packages/agent-hooks/src/hooks/crg/handler.ts +2 -1
- package/packages/agent-hooks/src/hooks/crg/index.ts +36 -5
- package/packages/agent-hooks/src/rules/impl/fs-trash.ts +2 -2
- package/packages/agent-hooks/src/rules/impl/git-paths.ts +5 -6
- package/packages/agent-hooks/src/rules/impl/tool-grep.ts +8 -8
- package/packages/agent-settings/assets/GLOBAL-AGENTS.md +4 -2
- package/packages/agent-settings/src/global-surface-sweep.ts +5 -15
- package/packages/agent-skills/package.json +1 -1
- package/packages/azdo/package.json +1 -2
- package/packages/azdo/src/azdo-repo-doctor.ts +70 -75
- package/packages/fleet/manifest.json +5 -24
- package/packages/fleet/manifest.schema.json +8 -58
- package/packages/fleet/src/install-embedding-model.ts +1 -1
- package/packages/fleet/src/repo-maintain.ts +403 -0
- package/packages/fleet/src/scratch-reap.ts +1 -1
- package/packages/repo-controls/README.md +46 -8
- package/packages/repo-controls/assets/promote.ts +12 -7
- package/packages/repo-controls/assets/reconcile-guard.sh +113 -0
- package/packages/repo-controls/assets/version-bump.sh +91 -51
- package/packages/repo-controls/package.json +0 -1
- package/packages/repo-controls/src/azure-devops-governance.ts +17 -16
- package/packages/repo-controls/src/constants.ts +8 -0
- package/packages/repo-controls/src/hook.ts +3 -2
- package/packages/repo-controls/src/integrity.ts +15 -1
- 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
|
@@ -1,13 +1,22 @@
|
|
|
1
1
|
#!/usr/bin/env bash
|
|
2
2
|
set -euo pipefail
|
|
3
3
|
|
|
4
|
-
#
|
|
5
|
-
#
|
|
6
|
-
#
|
|
7
|
-
#
|
|
8
|
-
#
|
|
9
|
-
#
|
|
10
|
-
#
|
|
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
|
-
|
|
24
|
-
|
|
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
|
-
--
|
|
35
|
-
[[ $# -ge 2 ]] || die '--
|
|
36
|
-
|
|
43
|
+
--pr-source)
|
|
44
|
+
[[ $# -ge 2 ]] || die '--pr-source requires a branch ref'
|
|
45
|
+
pr_source="$2"
|
|
37
46
|
shift 2
|
|
38
47
|
;;
|
|
39
|
-
--
|
|
40
|
-
|
|
41
|
-
|
|
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
|
-
[[ -
|
|
55
|
-
[[ -n "$
|
|
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
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
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
|
-
|
|
86
|
-
|
|
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
|
-
|
|
92
|
-
|
|
93
|
-
|
|
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
|
-
|
|
193
|
-
|
|
194
|
-
|
|
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.
|
|
172
|
-
*
|
|
173
|
-
*
|
|
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
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
:
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
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:
|
|
434
|
-
//
|
|
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
|
-
|
|
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 {
|