A. ADO Foundation & Repo Standards
Wasteology already runs every active repo inside a single Azure DevOps organization (wasteology, project Wasteology). This section codifies that structure into a company standard, extends it with templating and branching rules so every new repo starts consistent, and documents how Boards, commits, and PRs connect so the development work is visible to the whole organization — not just the engineer who wrote the code.
A.1 — ADO Org / Project Structure
Current state
One ADO organization (wasteology), one ADO project (Wasteology), all repos inside it. This is the live configuration; it has not been formally declared a standard or documented anywhere a new hire would find it.
Naming gotcha for new developers: The
azCLI was originally configured against a stale org namedkgwasteology. Git remotes correctly usewasteology. When runningazcommands always pass--org https://dev.azure.com/wasteologyexplicitly, or setAZURE_DEVOPS_EXT_PATand the org via env var. Never rely on theaz devops configuredefault — it points at the wrong org.
Recommended standard
Single ADO project, single org. Do not create additional ADO projects.
Rationale: All repos share the same ACR registry, the same Prefect Cloud workspace, the same n8n instance, and the same ADO PAT. Splitting into multiple ADO projects would require per-project service connections, separate PAT scopes, and separate pipeline agent pools — overhead that delivers no benefit at Wasteology's current scale.
Use Area Paths within the single Wasteology project to group work by team or domain instead of splitting into separate projects.
| Structure | Recommendation | Why |
|---|---|---|
| ADO organizations | 1 (wasteology) | Shared billing, shared service connections |
| ADO projects | 1 (Wasteology) | Shared Boards, shared pipelines, shared PAT |
| Work item grouping | Area Paths per team/domain | Lightweight — no separate auth, no separate settings |
Suggested area path hierarchy (starting point):
Wasteology
├── Data Platform ← wg-orchestration, wdp-palantiri, tirion
├── ETL & Import ← wdp-import-pipeline, prefect-azure-infra
├── CRM & Portals ← portal-reconciliation, cietrade flows
├── Data Warehouse ← dbt_wasteology_warehouse
├── Infrastructure ← Bicep IaC, wg-agent-infra
└── Docs & Tooling ← wasteology-docs, support projects
Area paths can be set on work items individually or inherited from a team's default.
Accept the single-project structure (recommended) or request one additional ADO project for a team/partner that needs strict permission isolation (e.g., a vendor who should not see internal repos). Creating a second project is a one-way door — work item IDs, pipelines, and service connections do not move between projects.
A.2 — Repo Creation & Template Strategy
Current state
Repos are created manually via the ADO UI or the curl REST recipe in .claude/skills/ado/SKILL.md. Each project bootstraps its Claude infrastructure (CLAUDE.md, .claude/, lat.md/) by hand, copying files from wg-orchestration or another existing repo. There is no canonical starting point, so new repos inevitably miss steps.
Recommended standard
Every new repo is seeded from a template repo named wg-template. The template is maintained in ADO, lives at dev.azure.com/wasteology/Wasteology/_git/wg-template, and contains all Tier 0 + Tier 1 scaffolding pre-filled. Creating a repo from the template takes one curl call; bootstrapping from scratch takes 14 manual steps and is error-prone.
What wg-template contains
wg-template/
├── .claude/
│ ├── settings.json # Tier 0 permissions + hooks (edit for tier)
│ ├── commands/
│ │ ├── prime.md # Load context at session start
│ │ ├── doctor.md # Health check: CLIs, .env keys, lat check
│ │ ├── session-wrap.md # End-of-session audit + lat gap detection
│ │ ├── git-update.md # Stage → commit → PAT push → PR
│ │ └── pull_request.md # Push branch + create PR with auto-complete
│ ├── hooks/
│ │ └── session_start.py # Fast env check (CLIs, .env, lat availability)
│ └── skills/
│ ├── lat-md/SKILL.md # lat.md authoring rules (copy verbatim)
│ └── ado/SKILL.md # ADO REST recipes (UPDATE ADO_REPO variable)
├── lat.md/
│ ├── lat.md # Index file (must list all child files)
│ └── architecture.md # Minimal architecture section (fill in)
├── .env.example # Tier 0 + Tier 1 keys; add tier-specific below
├── .gitignore # Excludes .env, __pycache__, .venv, dist/
├── pyproject.toml # uv + ruff defaults (Python projects only)
└── CLAUDE.md # All 6 required sections, placeholders filled
.env.example required keys (Tier 0 + 1)
# ADO — same PAT works across all wasteology repos
ADO_PAT="<personal access token — Code R/W + Work Items R/W scopes>"
# Add tier-specific keys below:
# Tier 2a (Python Service): POSTGRES_DEV_HOST, POSTGRES_DEV_PORT, etc.
# Tier 3a (Docker/ACI): ACR_NAME, ACR_PASSWORD, AZURE_CONTAINER_APP_NAME
# Tier 4a (Prefect ETL): PREFECT_API_KEY, PREFECT_API_URL
PAT scope requirement: The PAT must have Code (Read & Write) + Work Items (Read & Write). A read-only PAT makes PR creation and ADW dispatch fail with an empty-body 401 — a silent, misleading error.
Creating a repo from the template
# 1. Load PAT
export ADO_PAT=$(grep ADO_PAT .env | cut -d= -f2 | tr -d '"')
# 2. Create the repo in ADO
curl -s -u ":${ADO_PAT}" -X POST \
-H "Content-Type: application/json" \
-d "{\"name\": \"REPO_NAME\", \"project\": {\"id\": \"f598871b-da06-4acc-8748-6e70b21ff8fb\"}}" \
"https://dev.azure.com/wasteology/Wasteology/_apis/git/repositories?api-version=7.1"
# 3. Clone the template locally, rename remote, push
git clone https://dev.azure.com/wasteology/Wasteology/_git/wg-template REPO_NAME
cd REPO_NAME
git -c credential.helper= \
-c "credential.helper=!f() { echo username=x; echo password=${ADO_PAT}; }; f" \
remote set-url origin https://dev.azure.com/wasteology/Wasteology/_git/REPO_NAME
git -c credential.helper= \
-c "credential.helper=!f() { echo username=x; echo password=${ADO_PAT}; }; f" \
push -u origin main
Adding a New Project — Complete Checklist
This extends the existing checklist in lat.md/claude-standard#Adding a New Project — Checklist with the template-first step:
[ ] 0. Create repo from wg-template (see curl recipe above) — replaces steps 4–9 below
[ ] 1. Set ADO_REPO in .claude/skills/ado/SKILL.md to this repo's name
[ ] 2. Fill in CLAUDE.md placeholders (project name, stack section, key concepts)
[ ] 3. Copy tier-appropriate .env keys from 1Password / teammate into .env
[ ] 4. Add tier-specific .claude/settings.json permissions (Tier 2a: ruff/uv; Tier 3a: az/acr)
[ ] 5. Add tier-specific skills (deploy, prefect-flow-ops, validate, infra — per tier)
[ ] 6. Fill in lat.md/architecture.md with the project's real architecture
[ ] 7. Run `lat check` — must pass with zero errors before first commit
[ ] 8. Register in wg-orchestration: create manifests/builtin/<repo>.toml
[ ] 9. Verify ADO_PAT push works: run git-update from a feature branch
Should wg-template be an ADO "repository template" (enforced via UI, prevents creating non-templated repos) or simply a repo developers clone manually? ADO supports marking a repo as a template, which surfaces it in the "Create repository" dialog. Recommended: mark it as a template AND document the curl path for automation.
A.3 — Branching Strategy
Current state
wg-orchestration and wdp-palantiri use main as the default branch with feature branches merged via PRs. mars/Orbit auto-deploys prod on merge to main with one-reviewer protection. wasteology-docs uses an ADO pipeline triggered on push to main. No written standard exists; conventions vary by repo.
Recommended standard
Trunk-based development with short-lived feature branches. main is always deployable. Feature branches live no longer than one working day for small changes, three days for larger ones. No long-lived develop, staging, or release branches.
Branch naming convention
<type>/<short-description>
feat/add-cietrade-expense-sync
fix/cietrade-duplicate-upsert
chore/update-lat-md-schema
docs/docusaurus-cli-commands
Types map directly to ADO work item dispatch types:
| Branch prefix | ADO type | Dispatch mapping |
|---|---|---|
feat/ | Issue | feature → Issue |
fix/ | Issue | bug → Issue |
chore/ | Task | chore → Task |
docs/ | Task | docs → Task |
The ADW pipeline (adws/adw_plan_build.py) generates branch names from work item titles using the same type mapping — branches it creates will naturally follow this convention.
PR policies on main (required for every repo)
Set these via ADO Branch Policies (Repos → Branches → main → Branch policies):
| Policy | Setting | Rationale |
|---|---|---|
| Require a minimum number of reviewers | 1 reviewer | Catches bugs, spreads knowledge |
| Check for linked work items | Required | Enables traceability (see A.5) |
| Build validation | 1 required pipeline | Prevents broken main |
| Require comment resolution | On | PRs are not merged with open questions |
| Limit merge strategies | Squash only | Clean linear history on main |
| Automatically include reviewers | Per-repo code owners | Route PRs to the right person |
Note on squash merges: Squash merge keeps
mainhistory linear and makesgit bisecteffective. The ADW pipeline'sset_pr_auto_complete()already usesmergeStrategy: "squash"by default. Individual commit history is preserved on the feature branch until it is deleted post-merge.
The canonical ADO push pattern
Never use git push origin bare — the global git store credential helper is interactive and will hang in WSL. Always use the inline PAT helper:
ADO_PAT=$(grep ADO_PAT .env | cut -d= -f2 | tr -d '"')
BRANCH=$(git rev-parse --abbrev-ref HEAD)
git -c credential.helper= \
-c "credential.helper=!f() { echo username=x; echo password=${ADO_PAT}; }; f" \
push -u origin "${BRANCH}"
This pattern is encoded in every repo's .claude/commands/git-update.md and .claude/commands/pull_request.md. The -c credential.helper= flag (empty value) clears the inherited global helper so git never falls back to an interactive prompt.
How many required reviewers on main? One is the minimum; for repos that go directly to production (e.g., wdp-palantiri, mars/Orbit), two reviewers is safer. Admin override (bypass) should be reserved for emergency hotfixes only, and any bypass should require a follow-up review within 24 hours.
A.4 — GitHub → ADO Repo Migration Mechanics
Current state
All core Wasteology repos have already migrated to ADO as single origin. GitHub repos are treated as archival. The migration decision (whether to migrate remaining repos, including GCP-hosted tooling) lives in another section of this document — this section covers only the repo-level mechanics.
How it works (for each remaining GitHub repo)
Step 1 — Import history into ADO
ADO's built-in import preserves full git history including all branches and tags:
ADO UI: Repos → Import repository
→ Source type: Git
→ Clone URL: https://github.com/wasteology/<repo>.git
→ (check "Requires Authentication" if private)
Or via REST (automatable for bulk migration):
curl -s -u ":${ADO_PAT}" -X POST \
-H "Content-Type: application/json" \
-d '{
"parameters": {
"gitSource": {"url": "https://github.com/wasteology/REPO_NAME.git"},
"tfvcSource": null,
"deleteServiceEndpointAfterImportIsDone": true
}
}' \
"https://dev.azure.com/wasteology/Wasteology/_apis/git/repositories/REPO_ID/importRequests?api-version=7.1"
Step 2 — Re-point local remotes
git remote set-url origin https://dev.azure.com/wasteology/Wasteology/_git/REPO_NAME
Verify with:
git -c credential.helper= \
-c "credential.helper=!f() { echo username=x; echo password=${ADO_PAT}; }; f" \
fetch --dry-run
Step 3 — Port CI/CD pipelines
| Source | ADO equivalent |
|---|---|
GitHub Actions .github/workflows/*.yml | azure-pipelines.yml at repo root |
GitHub Actions on: push trigger | ADO: trigger: branches: include: [main] |
| GitHub Actions secrets | ADO: pipeline variables or variable groups (Library) |
| GitHub Actions environments | ADO: Deployment environments with approval gates |
Step 4 — Archive the GitHub repo
Mark the GitHub repo as archived (read-only) rather than deleting it. Preserves any external links, issues, and the public record of the project's history.
Step 5 — Update project manifest
In wg-orchestration/manifests/builtin/<project>.toml, update ado_repo and clear github_repo:
[project]
ado_repo = "dev.azure.com/wasteology/Wasteology/_git/REPO_NAME"
github_repo = "" # archived, not canonical
A.5 — ADO Boards + Work Item / Commit / PR Linking
Current state
The ADW pipeline creates ADO work items automatically (tagged adw;dispatch;{project}) and sets PR auto-complete with transitionWorkItems: true. Manual work items are created via ops digest runner or the ADO UI. Work item↔commit linking via AB#<id> syntax is used by the ADW pipeline but not consistently by humans.
Work item types — Basic process
Wasteology's ADO project uses the Basic process template. This is a hard constraint — the process template cannot be changed after project creation without recreating the project.
| Basic type | Maps to | When to use |
|---|---|---|
Epic | Initiative / OKR | Multi-week effort spanning multiple work items |
Issue | Feature or bug | Any user-visible change (new capability or defect fix) |
Task | Chore, docs, infra | Internal work with no direct user impact |
There is no
BugorUser Storytype. ADO will return HTTP 400 if you attempt to create one.adws/dispatch.pymapsbug → Issue,feature → Issue,chore → Task,docs → Task. Follow this mapping in the UI too.
AB#<id> linking — commit and PR syntax
ADO automatically creates a link between a work item and a commit or PR when the string AB#<id> appears in the commit message or PR title/description.
# Commit message — links to work item 247
git commit -m "feat: add portal sweep auto-clear AB#247"
# PR title — links and can auto-transition the work item
# "Closes AB#247" in PR description moves work item to Done on merge
| Syntax | Where to put it | Effect |
|---|---|---|
AB#247 | Commit message | Creates a link (no state change) |
AB#247 | PR title or description | Creates a link |
Fixes AB#247 | PR description | Closes work item on PR completion |
Closes AB#247 | PR description | Closes work item on PR completion |
The ADW pipeline uses workItemRefs: [{"id": "<id>"}] in the PR creation payload instead of inline syntax — both methods produce the same link.
PR auto-complete + work item transition
The canonical pattern (already implemented in adws/ado.py):
# Get the authenticated user's real UUID (zero UUIDs are silently rejected)
USER_ID=$(curl -s -u ":${ADO_PAT}" \
"https://dev.azure.com/wasteology/_apis/connectionData" \
| python3 -c "import sys,json; print(json.load(sys.stdin)['authenticatedUser']['id'])")
# Set auto-complete with work item transition
curl -s -u ":${ADO_PAT}" -X PATCH \
-H "Content-Type: application/json" \
-d "{
\"autoCompleteSetBy\": {\"id\": \"${USER_ID}\"},
\"completionOptions\": {
\"mergeStrategy\": \"squash\",
\"deleteSourceBranch\": true,
\"transitionWorkItems\": true
}
}" \
"https://dev.azure.com/wasteology/Wasteology/_apis/git/repositories/REPO_NAME/pullrequests/${PR_ID}?api-version=7.1"
transitionWorkItems: true moves linked Issues and Tasks to Done automatically when the PR is squash-merged. This is the primary mechanism that keeps Boards current without manual state management.
Iterations / sprints (basics)
ADO iterations map to sprint timelines. For teams not yet running formal sprints, a minimal setup is:
- Create a root iteration named
Wasteology(the project name). - Create child iterations named by date:
2026-Q3-Sprint-1,2026-Q3-Sprint-2, etc. - Assign work items to iterations in the Boards backlog view or via the work item
System.IterationPathfield.
The ADW pipeline does not set IterationPath on created work items — they land in the backlog root by default. A sprint-aware team would triage the backlog at the start of each sprint and assign items to the current iteration.
Does the product owner want formal sprint cadence with velocity tracking, or a Kanban-style flow where work items move across columns (To Do → Doing → Done) without time-boxing? Kanban is lower overhead and suits the current team size and async ADW workflow better. Either can be configured within the Basic process template without any ADO project changes.
Boards column mapping (recommended)
| Column | Work item state | What it means |
|---|---|---|
| To Do | Active | Created, not yet started |
| Doing | Active (in-progress) | Branch exists, work underway |
| Done | Closed | PR merged, transitionWorkItems closed the item |
ADO Basic process has only Active and Closed states. The Boards "Doing" column is a visual lane, not a separate state — items move there when you drag them on the board, but the underlying state stays Active until the PR merges.
Summary Reference
| Topic | Key decision | Where it lives |
|---|---|---|
| ADO org | wasteology (not kgwasteology) | All git remotes, all API calls |
| ADO project | Single project Wasteology | ADO UI, project GUID f598871b-... |
| Work item grouping | Area Paths (not separate ADO projects) | ADO UI: Project Settings → Team Configuration |
| Template repo | wg-template (seed every new repo from this) | dev.azure.com/wasteology/Wasteology/_git/wg-template |
| PAT required scopes | Code R/W + Work Items R/W | 1Password, shared across all repos |
| Default branch | main (always deployable) | Enforced via branch policy |
| Merge strategy | Squash only | Branch policy + ADW completionOptions |
| Work item types | Issue, Epic, Task (Basic process — no Bug/User Story) | adws/dispatch.py type mapping |
| Commit linking | AB#<id> in commit message or PR description | Automatic ADO recognition |
| PR completion | Auto-complete + transitionWorkItems: true | adws/ado.py:set_pr_auto_complete() |
| Push auth | Inline PAT credential helper (never bare git push) | .claude/commands/git-update.md in every repo |