Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -268,7 +268,7 @@ fail-closed and only pauses when the agent actually proposed a reviewed output.
│ ├── approval-summary/ # Safe-outputs summary renderer (bundled to approval-summary.js; end-of-Agent-job summary tab)
│ ├── github-app-token/ # GitHub App token minter (bundled to github-app-token.js; mints installation token in Agent + Detection when engine.github-app-token is set)
│ ├── executor-e2e/ # Stage 3 safe-output E2E test harness (not a bundle; runs deterministic scenarios against a real ADO project and files a GitHub issue on failure)
│ ├── prepare-pr-base/ # create-pull-request base-ref preparer (bundled to prepare-pr-base.js; fetches/deepens target branch so mcp.rs finds a diff base on shallow-default pools — issue #1413; emitted in BOTH the Agent job and the SafeOutputs job before the executor's worktree add — issue #1453)
│ ├── prepare-pr-base/ # create-pull-request preparer (bundled to prepare-pr-base.js): Agent mode uses ADO diff metadata + bounded dual-ref fallback to make the merge-base reachable; SafeOutputs mode fetches only the target worktree tip
│ └── shared/ # Shared modules across bundles (auth, ado-client, env-facts, types.gen.ts)
├── tests/ # Integration tests and fixtures
├── docs/ # Per-concept reference documentation (see index below)
Expand Down
33 changes: 14 additions & 19 deletions docs/ado-script.md
Original file line number Diff line number Diff line change
Expand Up @@ -74,21 +74,16 @@ pipeline** as runtime helpers. Today it produces thirteen bundles:
base — issue #1413) **and** the SafeOutputs job (before `ado-aw execute`, so
the Stage 3 executor's `git worktree add` resolves `origin/<target>`; each ADO
job has an isolated checkout, so the ref must be re-fetched in the job that
builds the worktree — issue #1453). For each allowed create-PR repo dir
(`self` + every `checkout:` alias, passed as repeated `--repo-dir <dir>
--target-branch <branch>` pairs in the same dir form
`mcp.rs::resolve_git_dir_for_patch` resolves), it fetches and progressively
deepens THAT repo's resolved target branch into `refs/remotes/origin/<target>`
and points `refs/remotes/origin/HEAD` at it, so a PR can be computed/opened on
shallow-default agent pools without a full-history `checkout: self`. In a
multi-checkout ("meta repo") setup each dir may carry a
different target (see `create-pull-request`'s `target-branches` /
`infer-target-from-checkout-ref`). Reuses
`shared/merge-base.ts::ensureTargetRefFetched` (the same fetch/deepen logic as
the PR execution-context precompute). Each `--repo-dir` is a double-quoted
ADO-macro path; each `--target-branch` is a single-quoted literal; the ADO
bearer (`SYSTEM_ACCESSTOKEN`) rides in masked env for the authenticated git
fetch. Per-dir failures are isolated (logged + skipped). Runs outside AWF. See
builds the worktree — issue #1453). Agent `patch-base` mode uses ADO
`getCommitDiffs` metadata (`commonCommit`, ahead, behind) to fetch the exact
shallow source/target ranges and verifies the base locally. Ineligible or
unavailable REST falls back to bounded dual-ref depths 200/500/2000, never an
automatic full-history fetch. SafeOutputs `target-worktree` mode fetches only
the target tip at depth 1. Each allowed repo is passed as a typed
`--repo-dir` / `--source-ref` / `--target-branch` tuple; per-dir failures are
isolated and surfaced as ADO warnings. The bearer
(`SYSTEM_ACCESSTOKEN`) remains in masked env and spawned-git
`GIT_CONFIG_*`, never argv or `.git/config`. Runs outside AWF. See
[`safe-outputs.md`](safe-outputs.md#create-pull-request).

> **Internal-only.** `ado-script` is not a user-facing front-matter
Expand Down Expand Up @@ -216,10 +211,10 @@ inside `src/compile/extensions/exec_context/pr.rs`:
merge-commit (parent count ≥ 3 per ADO's PR-validation flow),
`merge-base.ts::resolveMergeBase` computes `git merge-base` over
the two parents. If that cannot resolve in a shallow checkout, it
fetches both target and source refs with progressive deepening
(`--depth=200/500/2000/--unshallow`) and retries the parent
merge-base. Otherwise it fetches the target branch with progressive
deepening and then runs `git merge-base` against `HEAD`. Same
fetches both target and source refs with bounded deepening
(`--depth=200/500/2000`) and retries the parent merge-base. Otherwise
it fetches both source and target at the same bounded depths and then
runs `git merge-base` against `HEAD`. Same
`BASE_SHA` semantics in both paths (git's true common ancestor).
3. **Stage artefacts** — writes `aw-context/pr/base.sha` and
`aw-context/pr/head.sha` so the agent can `git diff $(cat
Expand Down
10 changes: 5 additions & 5 deletions docs/execution-context.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ historically rebuilt the same ~120 lines of bash to work around this.
The execution-context plugin owns that step centrally — but does
*only* the part the agent cannot do for itself:

- Fetches the PR target branch with progressive deepening until
- Fetches the PR source and target refs with bounded deepening until
`git merge-base` resolves (requires the bearer; cannot happen
inside the agent's sandbox).
- Writes the resolved `base.sha` and `head.sha` so the agent can
Expand Down Expand Up @@ -484,11 +484,11 @@ under `scripts/ado-script/src/exec-context-pr/`. The bundle:
computes `git merge-base HEAD^1 HEAD^2` as the base — same
semantics as the deepening path. If a shallow checkout lacks
sufficient ancestry, it fetches both the target and source refs
with progressive deepening and retries; unresolved ancestry fails
with bounded deepening and retries; unresolved ancestry fails
closed into `error.txt`.
Otherwise:
4. **Fetches the PR target branch with progressive deepening** —
`--depth=200`, then `500`, then `2000`, then finally `--unshallow`.
4. **Fetches source and target with bounded deepening** —
`--depth=200`, then `500`, then `2000`.
After each successful fetch, attempts `git merge-base
origin/<target> HEAD` and continues to the next depth if it
cannot resolve yet. See `merge-base.ts`.
Expand Down Expand Up @@ -540,7 +540,7 @@ preserves the Stage 1 read-only invariant with these design choices:
| Mechanism | Decision |
|-----------------------------------------------------------|----------|
| Override `checkout: self` with `persistCredentials: true` | **Rejected.** It would write the build identity's bearer into `.git/config` inside the workspace, which is then mounted into the AWF sandbox where the agent could read and exfiltrate it. |
| Override `checkout: self` with `fetchDepth: 0` | **Rejected.** Unnecessary — the precompute fetches exactly the refs it needs. |
| Override `checkout: self` with `fetchDepth: 0` | **Not automatic.** Authors may opt into explicit full history, but it can be very expensive; the precompute normally uses bounded source/target fetches. |
| In-step `SYSTEM_ACCESSTOKEN` + `GIT_CONFIG_*` bearer env | **Adopted.** `SYSTEM_ACCESSTOKEN` is mapped from `$(System.AccessToken)` only into the `node exec-context-pr.js` step's process env. The bundle's `git.ts::bearerEnv` then injects `GIT_CONFIG_COUNT` / `GIT_CONFIG_KEY_0` / `GIT_CONFIG_VALUE_0` into the *spawned `git` child process's* env only — not into the Node process's own env, and never via `git -c` on argv. The token never appears in process listings and is never written to disk. After the Node process exits, the bearer is gone from the runtime environment the agent inherits. |

After the precompute step exits, the bearer is gone from the runtime
Expand Down
13 changes: 8 additions & 5 deletions docs/front-matter.md
Original file line number Diff line number Diff line change
Expand Up @@ -408,10 +408,11 @@ Object fields:

### Tuning checkout fetch behavior (`fetch-depth` / `fetch-tags`)

On large monorepos the checkout step can dominate the run: ADO's default
`checkout` performs a full-history clone **and** `git fetch --tags`, which may
take tens of minutes. `fetch-depth` and `fetch-tags` let you tune this
per-repository:
On large monorepos the checkout step can dominate the run. Azure DevOps can
apply a pipeline-level shallow-fetch setting (newer pipelines commonly use
depth 1), while tag syncing can also add substantial transfer. `fetch-depth`
and `fetch-tags` let source-controlled YAML override those settings per
repository:

```yaml
repos:
Expand All @@ -420,7 +421,9 @@ repos:
fetch-tags: false # skip the (often huge) tag fetch
```

- `fetch-depth: 0` means **full history** (no `fetchDepth` is emitted).
- `fetch-depth: 0` explicitly emits `fetchDepth: 0`, disabling shallow fetch
even when the pipeline UI is configured for depth 1. Full history can be
very expensive in a large or old repository.
- When a field is omitted the ADO default applies, so agents that don't set
these compile **unchanged**.
- Setting `fetch-depth`/`fetch-tags` on an entry with `checkout: false` has no
Expand Down
47 changes: 24 additions & 23 deletions docs/safe-outputs.md
Original file line number Diff line number Diff line change
Expand Up @@ -272,31 +272,32 @@ Creates a pull request with code changes made by the agent. When invoked:

During Stage 3 execution, the repository is validated against the allowed list (from `checkout:` + "self"), then the patch is applied and a PR is created in Azure DevOps.

**Shallow-clone agent pools (automatic):** The diff base for the patch is
computed at agent time from the checked-out repository. On agent pools whose
default git fetch is shallow (`fetchDepth: 1`), a bare `checkout` leaves no
`origin/<target-branch>` ref, which would otherwise prevent the diff base from
being computed. To handle this transparently, whenever `create-pull-request` is
configured the compiler emits a credentialed **prepare step** that fetches and
progressively deepens the configured `target-branch` and points `origin/HEAD` at
it — in the `self` checkout **and in each additional `checkout:` repo dir**, so a
PR to *any* allowed repository works. The prepare step runs in **both** the Agent
job (before the agent runs, so the host-side SafeOutputs MCP server can compute
the diff base) **and** the SafeOutputs job (before `ado-aw execute`, so the
Stage 3 executor's `git worktree add` resolves `origin/<target>` — each ADO job
has an isolated checkout, so the ref must be re-fetched in the job that builds
the worktree; issue #1453). This means create-pull-request works on
shallow-default pools **without** forcing a full-history checkout and **without**
hand-editing the compiled lock (so the runtime integrity check keeps passing). No
configuration is required. See [`docs/ado-script.md`](ado-script.md)
**Shallow-clone agent pools (automatic):** The diff base is computed at agent
time from the checked-out repository. For same-organization Azure Repos,
`prepare-pr-base.js` asks the ADO Diffs API for the exact `commonCommit`,
`aheadCount`, and `behindCount`, then fetches only the source and target ranges
needed to make that base locally reachable. It verifies the server result with
`git merge-base --all` before the host-side SafeOutputs MCP server can generate
a patch. Non-Azure/cross-organization/unavailable-REST cases use bounded
dual-ref depths 200/500/2000 and fail clearly rather than silently fetching full
history.

The compiler emits separate modes in the two isolated ADO jobs:

- **Agent — `patch-base`:** prepares and verifies both sides of the merge-base.
- **SafeOutputs — `target-worktree`:** fetches only `origin/<target>` at depth 1
for the executor's `git worktree add` (issue #1453).

No full-history checkout or `--unshallow` fallback is forced. Authors can
explicitly set `repos: [{ name: self, fetch-depth: 0 }]` when they accept that
potentially large cost. The generated lock remains source-controlled and the
runtime integrity check stays enabled. See [`docs/ado-script.md`](ado-script.md)
(`prepare-pr-base.js`).

> **Branch semantics.** The step deepens each repo's resolved `target-branch`
> (the PR's **destination/base**) — not the per-repo `repos:` checkout `ref` (the
> source side). By default every repo targets the single `target-branch`; enable
> `infer-target-from-checkout-ref` (and/or `target-branches`) to give each repo
> its own base branch in a multi-checkout setup. The deepened branch always
> matches the branch the PR targets (shared resolution).
> **Branch semantics.** Each repo carries its checkout source ref and its
> resolved PR destination. By default every repo targets the single
> `target-branch`; enable `infer-target-from-checkout-ref` (and/or
> `target-branches`) to give each repo its own base branch.

**Stage 3 Execution Architecture (Hybrid Git + ADO API):**

Expand Down
2 changes: 1 addition & 1 deletion prompts/create-ado-agentic-workflow.md
Original file line number Diff line number Diff line change
Expand Up @@ -496,7 +496,7 @@ When `on.pr` is set: the native ADO `pr:` trigger block is generated from `branc

**`on.pr` triggering works without a Build Validation branch policy.** By default (`mode: synthetic`), the compiler emits a Setup-job script that, on CI-triggered builds, looks up the open PR for `Build.SourceBranch` via the ADO REST API and promotes the build to PR semantics if exactly one matches `pr.branches` (and `pr.paths` if configured). Zero or multiple matches → the Agent job self-skips cleanly. Set `on.pr.mode: policy` when an operator-installed Build Validation branch policy is in place — that mode omits all synth wiring AND emits `trigger: none` so feature-branch pushes do not queue duplicate CI builds alongside the policy-driven PR build. Note that in `mode: synthetic` the top-level CI `trigger:` is **not** auto-narrowed to `pr.branches.include`: those are PR target branches, and ADO `trigger:` fires on pushes *to* listed branches, so narrowing would suppress CI on the feature branches synthPr must react to. Full reference: ["PR Triggering in Azure Repos" in `docs/front-matter.md`](../docs/front-matter.md#pr-triggering-in-azure-repos).

**PR-reviewer agents — DO NOT write your own precompute step.** When `on.pr` is set, the compiler automatically (1) fetches the PR target branch with progressive deepening, (2) resolves and stages `aw-context/pr/base.sha` + `aw-context/pr/head.sha`, (3) appends a prompt fragment listing common `git diff`/`git show`/`git log` commands and example Azure DevOps MCP tool calls (`repo_get_pull_request_by_id`, `repo_list_pull_request_threads`, `repo_create_pull_request_thread`) with the PR id / project / repo pre-filled, and (4) adds `git`, `git diff`, `git log`, `git show`, `git status`, `git rev-parse`, `git symbolic-ref` to the agent's bash allow-list. The agent runs `git diff $BASE..$HEAD` itself inside the AWF sandbox (objects are already fetched into the workspace). On failure (e.g. merge-base could not be resolved), the failure fragment tells the agent to surface the error rather than produce an empty review. Opt out via `execution-context.pr.enabled: false`. Full reference: [`docs/execution-context.md`](../docs/execution-context.md).
**PR-reviewer agents — DO NOT write your own precompute step.** When `on.pr` is set, the compiler automatically (1) fetches the PR source and target refs with bounded deepening, (2) resolves and stages `aw-context/pr/base.sha` + `aw-context/pr/head.sha`, (3) appends a prompt fragment listing common `git diff`/`git show`/`git log` commands and example Azure DevOps MCP tool calls (`repo_get_pull_request_by_id`, `repo_list_pull_request_threads`, `repo_create_pull_request_thread`) with the PR id / project / repo pre-filled, and (4) adds `git`, `git diff`, `git log`, `git show`, `git status`, `git rev-parse`, `git symbolic-ref` to the agent's bash allow-list. The agent runs `git diff $BASE..$HEAD` itself inside the AWF sandbox (objects are already fetched into the workspace). On failure (e.g. merge-base could not be resolved), the failure fragment tells the agent to surface the error rather than produce an empty review. Opt out via `execution-context.pr.enabled: false`. Full reference: [`docs/execution-context.md`](../docs/execution-context.md).

#### Pipeline Triggers (`on.pipeline`)

Expand Down
Loading
Loading