@amsterdamdatalabs/adl-agent-control-linux-x64 0.1.6 → 0.1.8

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/lean-ctx-observe-strip +76 -3
  3. package/bin/reconcile +460 -240
  4. package/bin/validate-manifest +36 -16
  5. package/package.json +1 -1
  6. package/packages/agent-hooks/src/deploy/deploy.ts +98 -18
  7. package/packages/agent-hooks/src/deploy/doctor.ts +61 -11
  8. package/packages/agent-hooks/src/deploy/json-merge.ts +430 -18
  9. package/packages/agent-hooks/src/deploy/lock.ts +36 -2
  10. package/packages/agent-hooks/src/lean-ctx/env.ts +17 -0
  11. package/packages/agent-hooks/src/lean-ctx/mcp.ts +4 -2
  12. package/packages/agent-settings/assets/lean-ctx/config.toml +3 -0
  13. package/packages/agent-settings/assets/lean-ctx/gitignore +4 -0
  14. package/packages/agent-settings/assets/lean-ctx/layout.toml +2 -0
  15. package/packages/agent-settings/src/deploy/agents-deploy.ts +10 -5
  16. package/packages/agent-settings/src/deploy/doctor.ts +39 -3
  17. package/packages/agent-settings/src/deploy/lean-ctx-deploy.ts +104 -12
  18. package/packages/agent-settings/src/deploy/lean-ctx-merge.ts +580 -0
  19. package/packages/agent-settings/src/deploy/lock.ts +4 -1
  20. package/packages/agent-settings/src/deploy/mcp-deploy.ts +9 -6
  21. package/packages/agent-settings/src/deploy/settings.ts +6 -3
  22. package/packages/agent-skills/dist/deploy.d.ts +2 -0
  23. package/packages/agent-skills/dist/deploy.d.ts.map +1 -1
  24. package/packages/agent-skills/dist/deploy.js +15 -8
  25. package/packages/agent-skills/dist/deploy.js.map +1 -1
  26. package/packages/agent-skills/dist/manifest.d.ts +1 -0
  27. package/packages/agent-skills/dist/manifest.d.ts.map +1 -1
  28. package/packages/agent-skills/dist/manifest.js +6 -2
  29. package/packages/agent-skills/dist/manifest.js.map +1 -1
  30. package/packages/agent-skills/src/deploy.ts +15 -8
  31. package/packages/agent-skills/src/manifest.ts +5 -2
  32. package/packages/azdo/src/azdo-repo-doctor.ts +401 -91
  33. package/packages/azdo/src/lib/graph-identity.ts +190 -3
  34. package/packages/fleet/jobs/git-maintain/src/main.ts +305 -45
  35. package/packages/fleet/manifest.json +6 -4
  36. package/packages/fleet/manifest.schema.json +15 -5
  37. package/packages/repo-controls/README.md +13 -6
  38. package/packages/repo-controls/assets/adl-config.default.toml +1 -0
  39. package/packages/repo-controls/src/cli.ts +6 -1
  40. package/packages/repo-controls/src/gitignore-block.ts +17 -0
  41. package/packages/repo-controls/src/install.ts +88 -6
@@ -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"},
@@ -169,6 +169,7 @@
169
169
  "minimum_free_gib": 25,
170
170
  "schedule_minute": 50,
171
171
  "scratch_repos": ["/root/enact-webview"],
172
+ "git_maintain_repos": ["/opt/adl-azdo"],
172
173
  "brew_upgrade_all": true,
173
174
  "surface_clis": [
174
175
  {"name": "claude", "manager": "native", "command": "claude", "update_args": ["update"]},
@@ -189,6 +190,7 @@
189
190
  "minimum_free_gib": 25,
190
191
  "schedule_minute": 20,
191
192
  "scratch_repos": ["/root/enact-webview"],
193
+ "git_maintain_repos": ["/opt/adl-azdo"],
192
194
  "brew_upgrade_all": true,
193
195
  "surface_clis": [
194
196
  {"name": "opencode", "manager": "npm", "command": "opencode", "update_args": ["upgrade"]},
@@ -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/"}}}
@@ -273,6 +274,15 @@
273
274
  "items": {"type": "string", "pattern": "^/[^\\s]*[^/\\s]$"},
274
275
  "uniqueItems": true
275
276
  },
277
+ "git_maintain_repos": {
278
+ "type": "array",
279
+ "description": "An explicit, closed list of repositories bin/reconcile passes to git-maintain's --repo argv, for a device whose enrolled checkout sits at a fixed path outside any device's home (the Linux VPSes' /opt/adl-azdo) rather than under a recursively-scanned ~/Documents. Never a scan root: each entry is one exact repository path, so this can never walk into an unrelated directory the way scanning a shared parent like /opt could. Case-insensitive azagent/vsts.agent ban: git-maintain itself refuses the same names again, by realpath too, at the point of actual use.",
280
+ "items": {
281
+ "type": "string",
282
+ "pattern": "^/(?!$)(?!.*\\s)(?!.*/$)(?!.*\\.\\.)(?!.*[aA][zZ][aA][gG][eE][nN][tT])(?!.*[vV][sS][tT][sS]\\.[aA][gG][eE][nN][tT]).+$"
283
+ },
284
+ "uniqueItems": true
285
+ },
276
286
  "tools": {"$ref": "#/$defs/tools"}
277
287
  }
278
288
  }
@@ -190,12 +190,19 @@ adl-repo-controls --version | help
190
190
  ```
191
191
 
192
192
  **install** requires an existing root `adl-config.toml`: that is the central
193
- management marker. It deletes and rebuilds the complete adl-owned
194
- `.workflows/` root, then appends its `[controls]` table only when that table is
195
- missing. It appends the single shared root ignore `/.enact/logs/` and
196
- `/.coverage-cache/` to `.gitignore`, writes the lock, and sets
197
- `core.hooksPath=.workflows/hooks` with `git config --local`.
198
- - Re-running is state-idempotent: the rebuilt root is byte-identical.
193
+ management marker. It reconciles the complete adl-owned `.workflows/` root
194
+ against every managed path's actual bytes (independent review, 2026-09-29:
195
+ a blind wholesale delete-and-rebuild touched every file's mtime — even ones
196
+ that already matched — on every run, which broke idempotency for anything
197
+ reading those mtimes) — a path whose content is already byte- and
198
+ mode-identical is left alone untouched, and anything else (missing,
199
+ altered, or retired) is still deleted or repaired. It then appends its
200
+ `[controls]` table only when that table is missing. It appends the single
201
+ shared root ignore `/.enact/logs/` and `/.coverage-cache/` to `.gitignore`,
202
+ writes the lock, and sets `core.hooksPath=.workflows/hooks` with
203
+ `git config --local`.
204
+ - Re-running is a true no-op: the rebuilt root is byte-identical AND no
205
+ managed file's mtime changes when nothing needed correcting.
199
206
  - `--dry-run` prints every action and changes nothing (files or git config).
200
207
  - Extra, altered, missing, and retired managed files below `.workflows/` are
201
208
  deleted or repaired on the next install. Pre-hard-cut log evidence is kept in
@@ -81,7 +81,12 @@ async function dispatch(argv: string[], io: Io): Promise<number> {
81
81
  const parsed = parse(command, args, ['--dry-run'])
82
82
  const result = install(parsed.repo, { dryRun: parsed.flags.has('--dry-run') })
83
83
  printActions(io, result.actions, result.dryRun)
84
- io.out(`${result.dryRun ? '[dry-run] ' : ''}${TOOL} ${VERSION} installed into ${result.repo}`)
84
+ // Independent review, 2026-09-29, finding 8: a genuine no-op run (and
85
+ // any --dry-run preview) never claims to have "installed" anything —
86
+ // only a real run that actually changed something says so.
87
+ if (!result.dryRun && result.actions.length > 0) {
88
+ io.out(`${TOOL} ${VERSION} installed into ${result.repo}`)
89
+ }
85
90
  return 0
86
91
  }
87
92
  case 'uninstall': {
@@ -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 {
@@ -212,9 +212,20 @@ export interface InstallResult {
212
212
  dryRun: boolean
213
213
  }
214
214
 
215
- function readCentralMarker(repo: string): string {
215
+ /**
216
+ * `dryRun`: a --dry-run preview must be able to describe what a real
217
+ * install WOULD do even for a repository with no adl-config.toml on disk
218
+ * yet (independent review, 2026-09-29, finding 6 — `adl install
219
+ * --dry-run`'s own step 1 would CREATE this exact marker moments before
220
+ * this step runs for real, and a dry-run writes nothing to the real repo
221
+ * first). Simulated as the same minimal `version = 1` template that
222
+ * creation step writes, never persisted. A REAL run (dryRun=false) is
223
+ * unaffected: a missing marker still throws exactly as before.
224
+ */
225
+ function readCentralMarker(repo: string, dryRun: boolean): string {
216
226
  const configPath = join(repo, CONFIG_REL)
217
227
  if (!existsSync(configPath)) {
228
+ if (dryRun) return 'version = 1\n'
218
229
  throw new Error(`${CONFIG_REL} is missing — this repository is not centrally managed`)
219
230
  }
220
231
  return readFileSync(configPath, 'utf8')
@@ -222,11 +233,11 @@ function readCentralMarker(repo: string): string {
222
233
 
223
234
  export function install(repoArg: string, options: InstallOptions): InstallResult {
224
235
  const repo = resolveRepo(repoArg)
225
- const initialConfigText = readCentralMarker(repo)
236
+ const dryRun = options.dryRun === true
237
+ const initialConfigText = readCentralMarker(repo, dryRun)
226
238
  // Validate only repo-controls' declared tables before deleting its root.
227
239
  // Other owners' tables remain deliberately opaque.
228
240
  validateOwnedConfig(initialConfigText)
229
- const dryRun = options.dryRun === true
230
241
  // The lock is outside neither configuration nor runtime boundaries, so read
231
242
  // its bookkeeping before replacing its containing root. It is never used
232
243
  // to preserve root content.
@@ -240,8 +251,12 @@ export function install(repoArg: string, options: InstallOptions): InstallResult
240
251
  }
241
252
  // This root is a single owner, so drift, stale files, old lock formats, and
242
253
  // removed managed files are pruned before the authoritative projection is
243
- // rebuilt. Pre-hard-cut log evidence is quarantined in place.
244
- reconcileWorkflowsRoot(repo, ops, 'authoritative repo-controls root reconciliation')
254
+ // rebuilt. Pre-hard-cut log evidence is quarantined in place. Selective,
255
+ // not wholesale (independent review, 2026-09-29): only paths NOT in
256
+ // managedPaths are removed, so a managed file that has not actually
257
+ // changed is left on disk for Ops.write()'s own byte comparison below to
258
+ // correctly report 'unchanged' and leave its mtime alone.
259
+ reconcileManagedWorkflowsRoot(repo, ops, [...managedPaths, LOCK_REL], 'stale .workflows content')
245
260
 
246
261
  const files: Lock['files'] = {}
247
262
  // Standalone CI assets are copied BYTE-FOR-BYTE, executable, and locked.
@@ -452,6 +467,71 @@ function removeSymlinksBelow(repo: string, relative: string, ops: Ops): void {
452
467
  }
453
468
  }
454
469
 
470
+ interface CollectedFile {
471
+ readonly rel: string
472
+ /** A symlink sitting at a path that TEXTUALLY matches a managed path is never "the managed file, unchanged" -- it is the wrong kind of node there and must be removed so `Ops.write()` creates a real file, never writes through the link into whatever it targets. */
473
+ readonly isSymlink: boolean
474
+ }
475
+
476
+ /** Every FILE (or symlink, treated as a leaf -- never traversed) under `rel`, repo-relative, depth-first. `[]` when `rel` does not exist. */
477
+ function collectFiles(repo: string, rel: string): CollectedFile[] {
478
+ const stats = lstatSync(join(repo, rel), { throwIfNoEntry: false })
479
+ if (stats === undefined) return []
480
+ if (stats.isSymbolicLink()) return [{ rel, isSymlink: true }]
481
+ if (!stats.isDirectory()) return [{ rel, isSymlink: false }]
482
+ return readdirSync(join(repo, rel)).flatMap((entry) => collectFiles(repo, `${rel}/${entry}`))
483
+ }
484
+
485
+ /** Removes any now-empty directory under `rel` (never `rel` itself, never the preserved subtree), depth-first. An ancestor of the preserved subtree is still walked (so its OTHER children get pruned) but is never itself removed, since the preserved subtree keeps it non-empty. */
486
+ function pruneEmptyDirs(repo: string, rel: string, preserveRel: string | undefined): void {
487
+ if (preserveRel !== undefined && rel === preserveRel) return
488
+ const abs = join(repo, rel)
489
+ const stats = lstatSync(abs, { throwIfNoEntry: false })
490
+ if (stats === undefined || !stats.isDirectory() || stats.isSymbolicLink()) return
491
+ for (const entry of readdirSync(abs)) pruneEmptyDirs(repo, `${rel}/${entry}`, preserveRel)
492
+ if (rel !== WORKFLOWS_ROOT && readdirSync(abs).length === 0) rmSync(abs, { recursive: true })
493
+ }
494
+
495
+ /**
496
+ * Selective counterpart to `reconcileWorkflowsRoot`, used only by
497
+ * `install()` (independent review, 2026-09-29): the wholesale
498
+ * remove-then-rewrite that function does is correct for `uninstall()`
499
+ * (nothing should survive), but for `install()` it defeated `Ops.write()`'s
500
+ * own byte comparison — every managed file always looked freshly absent,
501
+ * so a no-op run still reported every file as newly 'create'd and touched
502
+ * every mtime. This instead removes only paths NOT in `managedPaths` (and
503
+ * never the preserved pre-hard-cut log-quarantine subtree), leaving every
504
+ * managed path exactly as it is on disk for `Ops.write()` to diff for real.
505
+ */
506
+ function reconcileManagedWorkflowsRoot(repo: string, ops: Ops, managedPaths: readonly string[], reason: string): void {
507
+ // lstat, not existsSync: existsSync follows links and reports false for a
508
+ // dangling one, which would otherwise leave a stale (possibly dangling)
509
+ // symlink AT the root itself in place -- and a later `mkdirSync(...,
510
+ // {recursive: true})` for a managed path under it throws EEXIST against
511
+ // that non-directory entry rather than creating a real .workflows/.
512
+ const rootStats = lstatSync(join(repo, WORKFLOWS_ROOT), { throwIfNoEntry: false })
513
+ if (rootStats === undefined) return
514
+ if (rootStats.isSymbolicLink()) {
515
+ ops.remove(WORKFLOWS_ROOT, 'the managed root was a symlink, not a real directory')
516
+ return
517
+ }
518
+ const workflowLogs = LEGACY_LOG_QUARANTINES[0]
519
+ const preserveRel = realDirectoryHasData(repo, workflowLogs.rel) ? workflowLogs.rel : undefined
520
+ const managed = new Set(managedPaths)
521
+
522
+ for (const { rel, isSymlink } of collectFiles(repo, WORKFLOWS_ROOT)) {
523
+ if (preserveRel !== undefined && (rel === preserveRel || rel.startsWith(`${preserveRel}/`))) continue
524
+ if (managed.has(rel) && !isSymlink) continue
525
+ ops.remove(rel, isSymlink && managed.has(rel) ? 'a managed path was a symlink, not a real file' : reason)
526
+ }
527
+ // The preserved quarantine subtree is otherwise skipped entirely above,
528
+ // but a symlink placed inside it is never legitimate quarantined
529
+ // evidence -- removed without traversing its target, same as
530
+ // reconcileWorkflowsRoot's own uninstall path.
531
+ if (preserveRel !== undefined) removeSymlinksBelow(repo, preserveRel, ops)
532
+ pruneEmptyDirs(repo, WORKFLOWS_ROOT, preserveRel)
533
+ }
534
+
455
535
  export type UninstallOptions = InstallOptions
456
536
 
457
537
  export interface UninstallResult extends InstallResult {
@@ -460,7 +540,9 @@ export interface UninstallResult extends InstallResult {
460
540
 
461
541
  export function uninstall(repoArg: string, options: UninstallOptions): UninstallResult {
462
542
  const repo = resolveRepo(repoArg)
463
- validateOwnedConfig(readCentralMarker(repo))
543
+ // Never simulated, dry-run or not: uninstalling a repo with no marker at
544
+ // all is not a "what would happen" question worth answering.
545
+ validateOwnedConfig(readCentralMarker(repo, false))
464
546
  const dryRun = options.dryRun === true
465
547
  const lock = priorLockForReconciliation(repo)
466
548
  const ops = new Ops(repo, dryRun)