@amsterdamdatalabs/adl-agent-control-linux-x64 0.1.7 → 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.
- package/bin/adl +2 -2
- package/bin/lean-ctx-observe-strip +76 -3
- package/bin/reconcile +175 -5
- package/bin/validate-manifest +19 -1
- package/package.json +1 -1
- package/packages/agent-hooks/src/deploy/deploy.ts +98 -18
- package/packages/agent-hooks/src/deploy/doctor.ts +61 -11
- package/packages/agent-hooks/src/deploy/json-merge.ts +430 -18
- package/packages/agent-hooks/src/deploy/lock.ts +36 -2
- package/packages/agent-hooks/src/lean-ctx/env.ts +17 -0
- package/packages/agent-hooks/src/lean-ctx/mcp.ts +4 -2
- package/packages/agent-settings/assets/lean-ctx/config.toml +3 -0
- package/packages/agent-settings/assets/lean-ctx/gitignore +4 -0
- package/packages/agent-settings/assets/lean-ctx/layout.toml +2 -0
- package/packages/agent-settings/src/deploy/agents-deploy.ts +10 -5
- package/packages/agent-settings/src/deploy/doctor.ts +39 -3
- package/packages/agent-settings/src/deploy/lean-ctx-deploy.ts +104 -12
- package/packages/agent-settings/src/deploy/lean-ctx-merge.ts +580 -0
- package/packages/agent-settings/src/deploy/lock.ts +4 -1
- package/packages/agent-settings/src/deploy/mcp-deploy.ts +9 -6
- package/packages/agent-settings/src/deploy/settings.ts +6 -3
- package/packages/agent-skills/dist/deploy.d.ts +2 -0
- package/packages/agent-skills/dist/deploy.d.ts.map +1 -1
- package/packages/agent-skills/dist/deploy.js +15 -8
- package/packages/agent-skills/dist/deploy.js.map +1 -1
- package/packages/agent-skills/dist/manifest.d.ts +1 -0
- package/packages/agent-skills/dist/manifest.d.ts.map +1 -1
- package/packages/agent-skills/dist/manifest.js +6 -2
- package/packages/agent-skills/dist/manifest.js.map +1 -1
- package/packages/agent-skills/src/deploy.ts +15 -8
- package/packages/agent-skills/src/manifest.ts +5 -2
- package/packages/azdo/src/azdo-repo-doctor.ts +401 -91
- package/packages/azdo/src/lib/graph-identity.ts +190 -3
- package/packages/fleet/jobs/git-maintain/src/main.ts +305 -45
- package/packages/fleet/manifest.json +2 -0
- package/packages/fleet/manifest.schema.json +9 -0
- package/packages/repo-controls/README.md +13 -6
- package/packages/repo-controls/assets/adl-config.default.toml +1 -0
- package/packages/repo-controls/src/cli.ts +6 -1
- package/packages/repo-controls/src/install.ts +88 -6
|
@@ -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
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
-
|
|
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
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
version = 1
|
|
@@ -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
|
-
|
|
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': {
|
|
@@ -212,9 +212,20 @@ export interface InstallResult {
|
|
|
212
212
|
dryRun: boolean
|
|
213
213
|
}
|
|
214
214
|
|
|
215
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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)
|