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
5 changes: 3 additions & 2 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -266,6 +266,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 in the Agent job so mcp.rs finds a diff base on shallow-default pools — issue #1413)
│ └── 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 Expand Up @@ -381,8 +382,8 @@ index to jump to the right page.
- [`docs/ado-script.md`](docs/ado-script.md) — `ado-script` workspace
(`scripts/ado-script/`): the bundled TypeScript runtime helpers
(`gate.js`, `import.js`, the execution-context `exec-context-*.js`
bundles, `conclusion.js`, `approval-summary.js`, and
`github-app-token.js`), schemars-driven
bundles, `conclusion.js`, `approval-summary.js`,
`github-app-token.js`, and `prepare-pr-base.js`), schemars-driven
type codegen, the A2 design decision, and the bundle env contract
modelled in `src/compile/ado_bundle.rs`.
- [`docs/local-development.md`](docs/local-development.md) — local development
Expand Down
36 changes: 29 additions & 7 deletions docs/ado-script.md
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,24 @@ pipeline** as runtime helpers. Today it produces thirteen bundles:
env vars, so a pipeline variable can't shadow them (only the private key /
minted token ride in masked env). Runs outside AWF. See
[`engine.md`](engine.md#github-app-backed-copilot-engine-auth).
- `prepare-pr-base.js` — create-pull-request base-ref preparer that runs in the
**Agent job** before the Copilot invocation when `create-pull-request` is
configured (issue #1413). 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 the host-side SafeOutputs MCP server can
compute a diff base 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
[`safe-outputs.md`](safe-outputs.md#create-pull-request).

> **Internal-only.** `ado-script` is not a user-facing front-matter
> feature. Authors never write an `ado-script:` block in their agent
Expand Down Expand Up @@ -520,12 +538,15 @@ scripts/ado-script/
│ ├── exec-context-repo/ # exec-context-repo.js entry point + repository identity context
│ │ ├── index.ts # main(): stage branch/SHA/tag/commits-since-tag facts → prompt
│ │ └── __tests__/ # unit tests for identity / tag fallback / sanitisation paths
│ └── conclusion/ # conclusion.js entry point + Conclusion-job reporter
│ ├── index.ts # main(): inspect upstream results + safe-outputs manifest → file/append work items
│ └── __tests__/ # unit tests for signal detection and work-item filing behaviour
│ └── github-app-token/ # github-app-token.js entry point + GitHub App token minter
│ ├── index.ts # main(): RS256 JWT → resolve installation → mint installation token → masked GITHUB_APP_TOKEN
│ └── __tests__/ # unit tests for JWT signing / installation resolution / token minting
│ ├── conclusion/ # conclusion.js entry point + Conclusion-job reporter
│ │ ├── index.ts # main(): inspect upstream results + safe-outputs manifest → file/append work items
│ │ └── __tests__/ # unit tests for signal detection and work-item filing behaviour
│ ├── github-app-token/ # github-app-token.js entry point + GitHub App token minter
│ │ ├── index.ts # main(): RS256 JWT → resolve installation → mint installation token → masked GITHUB_APP_TOKEN
│ │ └── __tests__/ # unit tests for JWT signing / installation resolution / token minting
│ └── prepare-pr-base/ # prepare-pr-base.js entry point + create-pull-request base-ref fetch/deepen
│ ├── index.ts # main(): fetch/deepen target branch + set origin/HEAD so mcp.rs finds a diff base
│ └── __tests__/ # unit tests for fetch/deepen + origin/HEAD + benign-failure paths
├── test/ # End-to-end smoke tests (gate, import, exec-context-pr)
├── gate.js # ncc bundle output (gitignored)
├── import.js # ncc bundle output (gitignored)
Expand All @@ -540,7 +561,8 @@ scripts/ado-script/
├── exec-context-repo.js # ncc bundle output (gitignored)
├── conclusion.js # ncc bundle output (gitignored)
├── approval-summary.js # ncc bundle output (gitignored)
└── github-app-token.js # ncc bundle output (gitignored)
├── github-app-token.js # ncc bundle output (gitignored)
└── prepare-pr-base.js # ncc bundle output (gitignored)
```

The release workflow (`.github/workflows/release.yml`) runs
Expand Down
8 changes: 8 additions & 0 deletions docs/execution-context.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,14 @@ The execution-context plugin owns that step centrally — but does
The agent does its own diff/show/log/stat work — it has the objects
locally and `git` is added to its bash allow-list automatically.

> **Related:** the `create-pull-request` safe output uses the *same*
> credentialed fetch/deepen approach — via the `prepare-pr-base.js`
> bundle (which reuses `shared/merge-base.ts::ensureTargetRefFetched`) —
> so its diff base resolves on shallow-default pools without forcing a
> full-history `checkout: self`. See
> [`safe-outputs.md`](safe-outputs.md#create-pull-request) and
> [`ado-script.md`](ado-script.md).

## v1 contributors

| Contributor | Trigger | Output layout |
Expand Down
55 changes: 54 additions & 1 deletion docs/safe-outputs.md
Original file line number Diff line number Diff line change
Expand Up @@ -263,6 +263,27 @@ 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** in the Agent job
(before the agent runs) 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.
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) (`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).

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

```
Expand Down Expand Up @@ -306,7 +327,39 @@ This hybrid approach combines:
Note: The source branch name is auto-generated from a sanitized version of the PR title plus a unique suffix (e.g., `agent/fix-bug-in-parser-a1b2c3`). This format is human-readable while preventing injection attacks.

**Configuration options (front matter):**
- `target-branch` - Target branch to merge into (default: "main")
- `target-branch` - Target (base) branch the PR merges into (default: "main"). A
plain literal branch name, applied to every repo unless overridden below.
- `target-branches` - Optional map of per-repository target-branch overrides,
keyed by the repository alias the agent passes to `create-pull-request` (`self`
or a `checkout:` alias). Highest precedence. Lets a multi-checkout ("meta repo")
agent open a PR into a different base branch per repo.
- `infer-target-from-checkout-ref` - Optional bool (default: false). When `true`,
a checkout repo with no explicit `target-branches` entry targets its own
`repos: ref` (the branch it was checked out at). `self` and repos without a
known ref fall back to `target-branch`. It is a separate boolean (not a magic
`target-branch` value) so a real branch name can never be mistaken for a
directive. Only branch refs (`refs/heads/*`) are valid PR targets — if an
inferred repo is checked out at a **tag** (`refs/tags/*`) the compiler warns
and you should give it an explicit `target-branches` entry (a PR cannot target
a tag).

**Per-repo target resolution precedence** (for a repo `R`): `target-branches[R]`
→ (if `infer-target-from-checkout-ref`) `R`'s checkout ref → `target-branch` →
`main`. The same resolution drives both the credentialed base-ref deepening (so
the branch that is fetched/deepened matches the branch the PR targets) and the
Stage 3 PR creation. Example (meta repo):
```yaml
repos:
- name: my-org/service # checked out at refs/heads/main
- name: my-org/docs
ref: refs/heads/gh-pages
safe-outputs:
create-pull-request:
target-branch: main # self + fallback
infer-target-from-checkout-ref: true # service → main, docs → gh-pages (from their refs)
target-branches:
docs: gh-pages # (redundant here; shown as an explicit override)
```
- `draft` - Whether to create the PR as a draft (default: **true**). Set to `false` to publish the PR immediately. **Note:** `auto-complete` is silently skipped on draft PRs — set `draft: false` when using `auto-complete: true`.
- `auto-complete` - Set auto-complete on the PR (default: false). Requires `draft: false` to take effect.
- `delete-source-branch` - Delete source branch after merge (default: true)
Expand Down
1 change: 1 addition & 0 deletions scripts/ado-script/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ exec-context-repo.js
approval-summary.js
conclusion.js
github-app-token.js
prepare-pr-base.js
schema
*.tsbuildinfo
test-bin
5 changes: 3 additions & 2 deletions scripts/ado-script/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@
"node": ">=20.0.0"
},
"scripts": {
"build": "npm run codegen && npm run clean && npm run build:gate && npm run build:import && npm run build:exec-context-pr && npm run build:exec-context-pr-synth && npm run build:exec-context-manual && npm run build:exec-context-pipeline && npm run build:exec-context-ci-push && npm run build:exec-context-workitem && npm run build:exec-context-schedule && npm run build:exec-context-pr-checks && npm run build:exec-context-repo && npm run build:conclusion && npm run build:approval-summary && npm run build:github-app-token",
"clean": "node -e \"const fs=require('node:fs'); fs.rmSync('.ado-build',{recursive:true,force:true}); for (const n of ['gate','import','exec-context-pr','exec-context-pr-synth','exec-context-manual','exec-context-pipeline','exec-context-ci-push','exec-context-workitem','exec-context-schedule','exec-context-pr-checks','exec-context-repo','conclusion','approval-summary','github-app-token']) fs.rmSync(n+'.js',{force:true});\"",
"build": "npm run codegen && npm run clean && npm run build:gate && npm run build:import && npm run build:exec-context-pr && npm run build:exec-context-pr-synth && npm run build:exec-context-manual && npm run build:exec-context-pipeline && npm run build:exec-context-ci-push && npm run build:exec-context-workitem && npm run build:exec-context-schedule && npm run build:exec-context-pr-checks && npm run build:exec-context-repo && npm run build:conclusion && npm run build:approval-summary && npm run build:github-app-token && npm run build:prepare-pr-base",
"clean": "node -e \"const fs=require('node:fs'); fs.rmSync('.ado-build',{recursive:true,force:true}); for (const n of ['gate','import','exec-context-pr','exec-context-pr-synth','exec-context-manual','exec-context-pipeline','exec-context-ci-push','exec-context-workitem','exec-context-schedule','exec-context-pr-checks','exec-context-repo','conclusion','approval-summary','github-app-token','prepare-pr-base']) fs.rmSync(n+'.js',{force:true});\"",
"build:gate": "ncc build src/gate/index.ts -o .ado-build/gate -m -t && node -e \"const fs=require('node:fs'); fs.copyFileSync('.ado-build/gate/index.js','gate.js'); fs.rmSync('.ado-build/gate',{recursive:true,force:true});\"",
"build:import": "ncc build src/import/index.ts -o .ado-build/import -m -t && node -e \"const fs=require('node:fs'); fs.copyFileSync('.ado-build/import/index.js','import.js'); fs.rmSync('.ado-build/import',{recursive:true,force:true});\"",
"build:exec-context-pr": "ncc build src/exec-context-pr/index.ts -o .ado-build/exec-context-pr -m -t && node -e \"const fs=require('node:fs'); fs.copyFileSync('.ado-build/exec-context-pr/index.js','exec-context-pr.js'); fs.rmSync('.ado-build/exec-context-pr',{recursive:true,force:true});\"",
Expand All @@ -23,6 +23,7 @@
"build:conclusion": "ncc build src/conclusion/index.ts -o .ado-build/conclusion -m -t && node -e \"const fs=require('node:fs'); fs.copyFileSync('.ado-build/conclusion/index.js','conclusion.js'); fs.rmSync('.ado-build/conclusion',{recursive:true,force:true});\"",
"build:approval-summary": "ncc build src/approval-summary/index.ts -o .ado-build/approval-summary -m -t && node -e \"const fs=require('node:fs'); fs.copyFileSync('.ado-build/approval-summary/index.js','approval-summary.js'); fs.rmSync('.ado-build/approval-summary',{recursive:true,force:true});\"",
"build:github-app-token": "ncc build src/github-app-token/index.ts -o .ado-build/github-app-token -m -t && node -e \"const fs=require('node:fs'); fs.copyFileSync('.ado-build/github-app-token/index.js','github-app-token.js'); fs.rmSync('.ado-build/github-app-token',{recursive:true,force:true});\"",
"build:prepare-pr-base": "ncc build src/prepare-pr-base/index.ts -o .ado-build/prepare-pr-base -m -t && node -e \"const fs=require('node:fs'); fs.copyFileSync('.ado-build/prepare-pr-base/index.js','prepare-pr-base.js'); fs.rmSync('.ado-build/prepare-pr-base',{recursive:true,force:true});\"",
"build:executor-e2e": "ncc build src/executor-e2e/index.ts -o .ado-build/executor-e2e -m -t && node -e \"const fs=require('node:fs'); fs.mkdirSync('test-bin',{recursive:true}); fs.copyFileSync('.ado-build/executor-e2e/index.js','test-bin/executor-e2e.js'); fs.rmSync('.ado-build/executor-e2e',{recursive:true,force:true});\"",
"build:check": "ls -lh gate.js && wc -c gate.js",
"codegen": "node -e \"require('node:fs').mkdirSync('schema', { recursive: true })\" && cargo run --quiet --manifest-path ../../Cargo.toml -- export-gate-schema --output schema/gate-spec.schema.json && npx json2ts schema/gate-spec.schema.json -o src/shared/types.gen.ts --bannerComment \"// AUTO-GENERATED from Rust IR via cargo run -- export-gate-schema. Do not edit; run npm run codegen.\"",
Expand Down
Loading
Loading