From 82ca178b1c1f3d936849a705d2709c70c525f840 Mon Sep 17 00:00:00 2001 From: Sebastien Giband Date: Tue, 20 Oct 2026 22:58:14 +0200 Subject: [PATCH 1/3] refactor(aidd-orchestrator): load only the selected delivery mode Keep one routing contract while reducing the content read for setup, run, and review. Preserve the existing action order and trigger aliases. Refs #420 --- .../evidence.md | 28 ++++ .../phase-1.md | 69 ++++++++ .../phase-2.md | 65 ++++++++ .../2026_10_20_delivery-loop-router/plan.md | 26 +++ plugins/aidd-orchestrator/CATALOG.md | 4 + .../skills/00-async-dev/SKILL.md | 148 +++--------------- .../skills/00-async-dev/actions/review.md | 19 +++ .../skills/00-async-dev/actions/route.md | 19 +++ .../skills/00-async-dev/actions/run.md | 23 +++ .../skills/00-async-dev/actions/setup.md | 33 ++++ .../skills/00-async-dev/references/routing.md | 14 +- scripts/__tests__/async-dev-router.test.js | 57 +++++++ 12 files changed, 375 insertions(+), 130 deletions(-) create mode 100644 aidd_docs/tasks/2026_10/2026_10_20_delivery-loop-router/evidence.md create mode 100644 aidd_docs/tasks/2026_10/2026_10_20_delivery-loop-router/phase-1.md create mode 100644 aidd_docs/tasks/2026_10/2026_10_20_delivery-loop-router/phase-2.md create mode 100644 aidd_docs/tasks/2026_10/2026_10_20_delivery-loop-router/plan.md create mode 100644 plugins/aidd-orchestrator/skills/00-async-dev/actions/review.md create mode 100644 plugins/aidd-orchestrator/skills/00-async-dev/actions/route.md create mode 100644 plugins/aidd-orchestrator/skills/00-async-dev/actions/run.md create mode 100644 plugins/aidd-orchestrator/skills/00-async-dev/actions/setup.md create mode 100644 scripts/__tests__/async-dev-router.test.js diff --git a/aidd_docs/tasks/2026_10/2026_10_20_delivery-loop-router/evidence.md b/aidd_docs/tasks/2026_10/2026_10_20_delivery-loop-router/evidence.md new file mode 100644 index 000000000..33445453f --- /dev/null +++ b/aidd_docs/tasks/2026_10/2026_10_20_delivery-loop-router/evidence.md @@ -0,0 +1,28 @@ +# Router reading cost + +Count physical lines, including blank lines. The measured path reads `SKILL.md`, `references/routing.md`, the selected mode sequence and all action files in that sequence. After the change it also reads `actions/route.md`. Setup templates and conditional references are excluded from both sides. The baseline is the merge base of the feature branch and `origin/next`, so the command remains valid after commits. Fetch `origin/next` before running it. + +| Mode | Before | After | Change | +| --- | ---: | ---: | ---: | +| Setup | 796 | 751 | -45 | +| Run | 508 | 453 | -55 | +| Review | 424 | 365 | -59 | + +Shortest: review, 424 → 365 lines. Longest: setup, 796 → 751 lines. + +Reproduce from the repository root: + +```python +from pathlib import Path +import subprocess +base = 'plugins/aidd-orchestrator/skills/00-async-dev/' +subprocess.run(['git', 'fetch', 'origin', 'next'], check=True) +baseline = subprocess.check_output(['git', 'merge-base', 'origin/next', 'HEAD'], text=True).strip() +old = lambda path: len(subprocess.check_output(['git', 'show', f'{baseline}:{base}{path}']).splitlines()) +new = lambda path: len((Path(base) / path).read_bytes().splitlines()) +for mode in ('setup', 'run', 'review'): + actions = [str(path.relative_to(Path(base))) for path in sorted((Path(base) / 'actions' / mode).glob('*.md'))] + before = old('SKILL.md') + old('references/routing.md') + sum(old(path) for path in actions) + after = sum(new(path) for path in ['SKILL.md', 'actions/route.md', 'references/routing.md', f'actions/{mode}.md', *actions]) + print(mode, before, after, after - before) +``` diff --git a/aidd_docs/tasks/2026_10/2026_10_20_delivery-loop-router/phase-1.md b/aidd_docs/tasks/2026_10/2026_10_20_delivery-loop-router/phase-1.md new file mode 100644 index 000000000..14d37ebaa --- /dev/null +++ b/aidd_docs/tasks/2026_10/2026_10_20_delivery-loop-router/phase-1.md @@ -0,0 +1,69 @@ +--- +status: done +--- + +# Instruction: Separate routing and mode sequences + +## Architecture projection + +> Tree of the final files. ✅ create · ✏️ modify · ❌ delete + +```txt +plugins/aidd-orchestrator/skills/00-async-dev/ +├── ✏️ SKILL.md # flow, action table, transversal rules only +├── ✏️ references/routing.md # sole signal precedence, aliases, conflict handling +└── actions/ + ├── ✅ route.md # resolve mode using routing reference + ├── ✅ setup.md # load ordered setup actions after selection + ├── ✅ run.md # load ordered run actions after selection + └── ✅ review.md # load ordered review actions after selection +``` + +## User Journey + +```mermaid +flowchart TD + A[Invoke 00-async-dev] --> B[Route using routing reference] + B --> C{Selected mode} + C -->|setup| D[Load setup sequence] + C -->|run| E[Load run sequence] + C -->|review| F[Load review sequence] +``` + +## Test Scope + +```mermaid +--- +title: Test scope +--- +journey + section Setup + Provide existing routing signals => mode can be resolved: 5: cli + section Happy path + Invoke each mode => only its existing sequence loads in order: 5: cli + section Edge case - conflicting signals + Provide contradictory intent and state => apply existing routing conflict rule: 1: cli +``` + +## Tasks to do + +### `1)` Consolidate routing + +> Keep selection precedence and aliases in one reference. + +1. Reconcile current differences between `SKILL.md` and `references/routing.md`, retaining intended explicit `action=` and arguments behavior. +2. Put all detailed selection and conflict rules in `references/routing.md`; have `route.md` load and apply it. + +### `2)` Establish lazy mode boundaries + +> Keep root routing compact and mode actions ordered. + +1. Replace root detail with Mermaid flow, four-row action table, and transversal rules. +2. Move each current setup, run, and review sequence to its respective internal action; preserve action order, conditional skips, loops, and dynamic SDLC discovery. + +## Test acceptance criteria + +| Task | Acceptance criteria | +| --- | --- | +| 1 | Existing trigger aliases and signal conflicts select the same mode. | +| 2 | Root has only flow, action table, and transversal rules; a selected mode loads only its sequence; runtime SDLC discovery remains in the run action. | diff --git a/aidd_docs/tasks/2026_10/2026_10_20_delivery-loop-router/phase-2.md b/aidd_docs/tasks/2026_10/2026_10_20_delivery-loop-router/phase-2.md new file mode 100644 index 000000000..180f45793 --- /dev/null +++ b/aidd_docs/tasks/2026_10/2026_10_20_delivery-loop-router/phase-2.md @@ -0,0 +1,65 @@ +--- +status: done +--- + +# Instruction: Prove compatibility and reading cost + +## Architecture projection + +> Tree of the final files. ✅ create · ✏️ modify · ❌ delete + +```txt +scripts/__tests__/ +└── ✅ async-dev-router.test.js # routing and lazy-load regressions +aidd_docs/tasks/2026_10/2026_10_20_delivery-loop-router/ +└── ✅ evidence.md # shortest and longest path line counts +``` + +## User Journey + +```mermaid +flowchart TD + A[Provide routing scenario] --> B[Select mode] + B --> C[Read selected sequence] + C --> D[Compare behavior and read-line counts] +``` + +## Test Scope + +```mermaid +--- +title: Test scope +--- +journey + section Setup + Capture baseline router and path files => before counts available: 5: cli + section Happy path + Exercise setup run and review signals => same modes and ordered actions: 5: cli + Measure shortest and longest selected paths => before and after counts recorded: 5: cli + section Edge case - provider discovery + Enter run path => SDLC provider discovered from runtime description: 1: cli +``` + +## Tasks to do + +### `1)` Guard observable routing + +> Verify mode selection, action order, and exclusions without changing automation policy. + +1. Test explicit arguments, CI labels and comment aliases, repo state, natural language, tie breaks, conflicts, and unresolved signals. +2. Assert only the selected mode sequence loads; retain dynamic provider discovery in the existing run actions. + +### `2)` Record reading cost + +> Supply reproducible before and after line totals for the PR. + +1. Count lines read on shortest and longest paths with a documented inclusion rule and the pre-change revision. +2. Record commands and counts in `evidence.md`; carry totals to PR description. +3. Run architecture checks and relevant repository test gate. + +## Test acceptance criteria + +| Task | Acceptance criteria | +| --- | --- | +| 1 | Setup, run, and review route and execute their existing ordered action sequences for the same inputs; aliases and conflicts retain behavior. | +| 2 | Before and after shortest and longest path line counts are reproducible and available for the PR; repository checks pass. | diff --git a/aidd_docs/tasks/2026_10/2026_10_20_delivery-loop-router/plan.md b/aidd_docs/tasks/2026_10/2026_10_20_delivery-loop-router/plan.md new file mode 100644 index 000000000..61d61e91e --- /dev/null +++ b/aidd_docs/tasks/2026_10/2026_10_20_delivery-loop-router/plan.md @@ -0,0 +1,26 @@ +--- +objective: "The async-dev skill routes through one contract-compliant entry point and loads only the selected mode while preserving behavior." +status: implemented +--- + +# Plan: Delivery loop router + +## Overview + +| Field | Value | +| --- | --- | +| **Goal** | Route setup, run, and review lazily through one routing source. | +| **Source** | [GitHub issue #420](https://github.com/ai-driven-dev/framework/issues/420) | + +## Phases + +| # | Phase | File | +| --- | --- | --- | +| 1 | Separate routing and mode sequences | [`phase-1.md`](./phase-1.md) | +| 2 | Prove compatibility and reading cost | [`phase-2.md`](./phase-2.md) | + +## Resources + +| Source | Verified | +| --- | --- | +| [GitHub issue #420](https://github.com/ai-driven-dev/framework/issues/420) | Scope, exclusions, and acceptance criteria. | diff --git a/plugins/aidd-orchestrator/CATALOG.md b/plugins/aidd-orchestrator/CATALOG.md index 61a6aa78f..b108e174e 100644 --- a/plugins/aidd-orchestrator/CATALOG.md +++ b/plugins/aidd-orchestrator/CATALOG.md @@ -26,6 +26,10 @@ Auto-generated index of skills, agents, references and assets shipped by the `ai | Group | File | Description | |-------|------|---| +| `actions` | [review.md](skills/00-async-dev/actions/review.md) | - | +| `actions` | [route.md](skills/00-async-dev/actions/route.md) | - | +| `actions` | [run.md](skills/00-async-dev/actions/run.md) | - | +| `actions` | [setup.md](skills/00-async-dev/actions/setup.md) | - | | `references` | [routing.md](skills/00-async-dev/references/routing.md) | - | | `-` | [SKILL.md](skills/00-async-dev/SKILL.md) | `Drive the async-dev pipeline from one entry point, whether setup, run, or review. Use when the user wants to install async dev, run a ready issue, or address PR review comments, or on a webhook trigger. Not for plain status checks.` | diff --git a/plugins/aidd-orchestrator/skills/00-async-dev/SKILL.md b/plugins/aidd-orchestrator/skills/00-async-dev/SKILL.md index b5bc4849d..0f63b2b58 100644 --- a/plugins/aidd-orchestrator/skills/00-async-dev/SKILL.md +++ b/plugins/aidd-orchestrator/skills/00-async-dev/SKILL.md @@ -6,126 +6,28 @@ argument-hint: setup | run | review # Async-dev -Single skill that drives the async development pipeline end to end. Three sub-flows live inside, accessed through one router: - -| Sub-flow | When | -| -------- | --------------------------------------------------------------------------------------------- | -| Setup | Repo not yet configured for async dev; user wants to install. | -| Run | An issue is labeled `to-implement` (or commented `@claude /implement`); no open PR closes it. | -| Review | A PR is labeled `to-review` (or commented `@claude /review`); change requests pending. | - -## Iron rule - -**You are the router until you commit to a sub-flow. Once committed, run that sub-flow's actions in order; do not jump back to routing mid-flow.** - -## Routing: hybrid contract - -Walk in order. First match wins. - -1. **Argument override.** If the arguments contain exactly `setup`, `run`, or `review` (case-insensitive, standalone token), route there immediately. This is the explicit override the CI workflow uses. -2. **Trigger env.** When invoked from CI (`GITHUB_EVENT_NAME` set): - - Label payload `to-implement` → `run`. - - Label payload `to-review` → `review`. - - Comment body matches `@claude /implement` (or `/aidd-dev:02-implement`) → `run`. - - Comment body matches `@claude /review` → `review`. -3. **Repo state.** - - Missing `.github/workflows/aidd-async.yml` AND missing `.claude/aidd-orchestrator.json` → `setup`. - - Open PR closes the referenced issue → `review`. - - Fresh issue with no linked PR + intent to implement → `run`. -4. **Natural-language intent.** Verbs in the user prompt: - - `install / configure / set up / bootstrap / rotate config` → `setup`. - - `implement / run / process / handle queue / claude on issue` → `run`. - - `address review / iterate on PR / fix comments / handle review` → `review`. - -**Tie-break:** most-specific signal wins (PR number > label > free-text keyword > config absence). - -**If none of the above resolves**, surface the three sub-flows with one-line triggers and ask the human which to run. **Never proceed blindly.** - -**If repo state contradicts intent** (e.g. user says "run" but `.claude/aidd-orchestrator.json` is absent), surface the conflict before delegating; never silently switch. - -See `references/routing.md` for the full decision tree, signal precedence, and edge cases. - -## Sub-flows - -### Setup (11 actions) - -Sets up async-dev in a repo. Detects context, asks for runtime parameters, generates artefacts, bootstraps labels, installs user-scope plugins (local mode), configures secrets (remote mode), schedules the poll routine (local mode), commits and pushes the generated files, and offers a smoke test on the chosen issue. - -| # | Action | Role | -| --- | --------------------------------- | --------------------------------------------------------------------------------------------- | -| 01 | `detect-context` | Identify repo, default branch, existing config, CI permissions | -| 02 | `ask-config` | Collect runtime parameters (mode, auth, labels, schedule, agents) | -| 03 | `generate-workflow` | Emit `.github/workflows/aidd-async.yml` from `assets/setup/workflow-template.yml` | -| 04 | `generate-local-script` | Emit poll/daemon scripts (local mode only) from `assets/setup/local-*-template.sh` | -| 05 | `write-config` | Persist `.claude/aidd-orchestrator.json` from `assets/setup/config-template.json` | -| 06 | `bootstrap-labels` | Create `to-implement` / `to-review` / `claude/*` labels via `gh` | -| 07 | `install-user-scope-plugins` | Install `aidd-orchestrator` + `aidd-dev` at user scope (local mode) | -| 08 | `configure-remote-secrets` | Sync `CLAUDE_CODE_OAUTH_TOKEN`, `AIDD_BOT_TOKEN`, etc. (remote mode) | -| 09 | `bootstrap-scheduling` | Schedule the poll routine (local) or rely on workflow webhook (remote) | -| 10 | `commit-and-push` | Stage generated files, conventional-commit, push | -| 11 | `smoke-test` | Label a throwaway issue with `to-implement`; verify the pipeline reacts | - -Files: `actions/setup/01-detect-context.md` ... `actions/setup/11-smoke-test.md`. - -Default flow: `01 → 02 → 03 → 04 → 05 → 06 → 07 → 08 → 09 → 10 → 11`. Actions self-skip when their preconditions are not met (e.g. `07` skips in remote mode, `08` skips in local mode). - -### Run (6 actions) - -Executes one orchestration cycle on a fresh issue. Reads ready issues, resolves blockers, acquires the lock label, hands the implementation to the active SDLC orchestration capability, observes the resulting git and VCS state, and emits a `run-result.json` summary the workflow's post-job consumes. - -| # | Action | Role | -| --- | ----------------- | ---------------------------------------------------------------------------------------------- | -| 01 | `poll-ready` | Find the next issue with `to-implement`, no `claude/working`, no open closing PR | -| 02 | `resolve-deps` | Check linked issues / dependencies; abort if blocked | -| 03 | `acquire-lock` | Apply `claude/working` label; refuse if already held | -| 04 | `check-sdlc` | Verify an SDLC orchestration capability is loaded | -| 05 | `delegate-sdlc` | Hand the issue to the SDLC capability; observe outcome | -| 06 | `write-audit` | Emit `run-result.json` for the workflow's post-job | - -Files: `actions/run/01-poll-ready.md` ... `actions/run/06-write-audit.md`. - -Default flow: `01 → 02 → 03 → 04 → 05 → 06`. One cycle per ready issue. - -### Review (4 actions) - -Closes the loop after a PR is opened by the run flow. Detects when to keep auto-fixing and when to hand off to a human. Idempotent on re-runs. - -| # | Action | Role | -| --- | -------------------- | ------------------------------------------------------------------------------------------ | -| 01 | `collect-comments` | Read all PR + linked-issue comments newer than the last bot activity | -| 02 | `detect-stop` | Decide stop vs continue using `references/review/stop-conditions.md` | -| 03 | `fix-iteration` | Delegate fixes to the SDLC capability; reply to each addressed comment | -| 04 | `finalize` | Resolve threads, post the structured summary, set `claude/awaiting-review` or `blocked` | - -Files: `actions/review/01-collect-comments.md` ... `actions/review/04-finalize.md`. - -Default flow: `01 → 02 → (03 → 01 loop if continue) → 04`. Stop conditions in `references/review/stop-conditions.md`. - -## Rules - -- Exactly one sub-flow runs per invocation. The router commits, then runs that sub-flow to completion. -- Never inline a sub-flow's logic at the router level. The router decides; the actions execute. -- Each action enforces its own pre-flight checks and exit codes. The router never enforces post-conditions. -- If a sub-flow aborts, surface the exit state; do not retry by switching sub-flows. -- For batch invocations (e.g. multiple ready issues), the workflow re-invokes the skill once per item; the router does not loop. - -## References - -- `references/routing.md` - full decision tree, signal precedence, conflict resolution. -- `references/setup/auth-modes.md` - local vs remote auth contracts. -- `references/setup/claude-action-auth.md` - `claude-code-action` token setup. -- `references/setup/local-mode-scheduling.md` - poll routine options. -- `references/review/stop-conditions.md` - when the review loop hands off to a human. - -## Assets - -Setup-only templates copied into the target repo by the setup sub-flow: - -- `assets/setup/workflow-template.yml` - `.github/workflows/aidd-async.yml` skeleton. -- `assets/setup/local-poll-template.sh` - local-mode poll script. -- `assets/setup/local-daemon-template.sh` - local-mode daemon script. -- `assets/setup/config-template.json` - `.claude/aidd-orchestrator.json` skeleton. - -## Test - -Each sub-flow's final action carries a `## Test` block that exercises the full sub-flow with mocked dependencies. +```mermaid +flowchart TD + request([Request or webhook]) --> route[Route] + route -->|setup| setup[Setup sequence] + route -->|run| run[Run sequence] + route -->|review| review[Review sequence] +``` + +## Actions + +Run `route` first. Read only the selected mode action afterward. + +| Action | Does | +| --- | --- | +| [`route`](actions/route.md) | Resolve the mode from arguments, trigger, repository state, and intent. | +| [`setup`](actions/setup.md) | Install and configure the async development pipeline. | +| [`run`](actions/run.md) | Process one ready issue through the discovered SDLC capability. | +| [`review`](actions/review.md) | Handle PR feedback until the stop condition or completion. | + +## Transversal rules + +- Exactly one mode runs per invocation. Once selected, complete that mode or surface its abort state; never reroute mid-flow. +- Mode actions own their preconditions, outputs, and postconditions. Route only selects a mode. +- For batches, the workflow invokes this skill once per item. +- Read a selected mode's action files in order; read its references and assets only when the action needs them. diff --git a/plugins/aidd-orchestrator/skills/00-async-dev/actions/review.md b/plugins/aidd-orchestrator/skills/00-async-dev/actions/review.md new file mode 100644 index 000000000..7e6f1e82c --- /dev/null +++ b/plugins/aidd-orchestrator/skills/00-async-dev/actions/review.md @@ -0,0 +1,19 @@ +# Review + +Closes the loop after a PR is opened by the run flow. Detects when to keep auto-fixing and when to hand off to a human. Idempotent on re-runs. + +| # | Action | Role | +| --- | -------------------- | ------------------------------------------------------------------------------------------ | +| 01 | `collect-comments` | Read all PR + linked-issue comments newer than the last bot activity | +| 02 | `detect-stop` | Decide stop vs continue using `references/review/stop-conditions.md` | +| 03 | `fix-iteration` | Delegate fixes to the SDLC capability; reply to each addressed comment | +| 04 | `finalize` | Resolve threads, post the structured summary, set `claude/awaiting-review` or `blocked` | + +Action files, read in the order above: + +- `actions/review/01-collect-comments.md` +- `actions/review/02-detect-stop.md` +- `actions/review/03-fix-iteration.md` +- `actions/review/04-finalize.md` + +Default flow: `01 → 02 → (03 → 01 loop if continue) → 04`. Stop conditions in `references/review/stop-conditions.md`. diff --git a/plugins/aidd-orchestrator/skills/00-async-dev/actions/route.md b/plugins/aidd-orchestrator/skills/00-async-dev/actions/route.md new file mode 100644 index 000000000..c6014b4ac --- /dev/null +++ b/plugins/aidd-orchestrator/skills/00-async-dev/actions/route.md @@ -0,0 +1,19 @@ +# Route + +## Input + +The skill arguments, prompt, CI trigger payload, and repository state. + +## Output + +One selected mode: `setup`, `run`, or `review`; or an unresolved/conflict report. + +## Process + +1. Read `references/routing.md` and apply its signal precedence, aliases, and conflict rules. +2. On a resolved mode, read exactly one of `actions/setup.md`, `actions/run.md`, or `actions/review.md` and follow it to completion. Do not load another mode's sequence. +3. On an unresolved or conflicting request, surface the decision from the routing reference and stop before loading a mode. + +## Test + +Explicit arguments, CI labels and comments, repository state, and natural language select the same modes as the routing reference. A conflict or unresolved signal does not load a mode. diff --git a/plugins/aidd-orchestrator/skills/00-async-dev/actions/run.md b/plugins/aidd-orchestrator/skills/00-async-dev/actions/run.md new file mode 100644 index 000000000..57ffc7ca1 --- /dev/null +++ b/plugins/aidd-orchestrator/skills/00-async-dev/actions/run.md @@ -0,0 +1,23 @@ +# Run + +Executes one orchestration cycle on a fresh issue. Reads ready issues, resolves blockers, acquires the lock label, hands the implementation to the active SDLC orchestration capability, observes the resulting git and VCS state, and emits a `run-result.json` summary the workflow's post-job consumes. + +| # | Action | Role | +| --- | ----------------- | ---------------------------------------------------------------------------------------------- | +| 01 | `poll-ready` | Find the next issue with `to-implement`, no `claude/working`, no open closing PR | +| 02 | `resolve-deps` | Check linked issues / dependencies; abort if blocked | +| 03 | `acquire-lock` | Apply `claude/working` label; refuse if already held | +| 04 | `check-sdlc` | Verify an SDLC orchestration capability is loaded | +| 05 | `delegate-sdlc` | Hand the issue to the SDLC capability; observe outcome | +| 06 | `write-audit` | Emit `run-result.json` for the workflow's post-job | + +Action files, read in the order above: + +- `actions/run/01-poll-ready.md` +- `actions/run/02-resolve-deps.md` +- `actions/run/03-acquire-lock.md` +- `actions/run/04-check-sdlc.md` +- `actions/run/05-delegate-sdlc.md` +- `actions/run/06-write-audit.md` + +Default flow: `01 → 02 → 03 → 04 → 05 → 06`. One cycle per ready issue. diff --git a/plugins/aidd-orchestrator/skills/00-async-dev/actions/setup.md b/plugins/aidd-orchestrator/skills/00-async-dev/actions/setup.md new file mode 100644 index 000000000..784fc7cc2 --- /dev/null +++ b/plugins/aidd-orchestrator/skills/00-async-dev/actions/setup.md @@ -0,0 +1,33 @@ +# Setup + +Sets up async-dev in a repo. Detects context, asks for runtime parameters, generates artefacts, bootstraps labels, installs user-scope plugins (local mode), configures secrets (remote mode), schedules the poll routine (local mode), commits and pushes the generated files, and offers a smoke test on the chosen issue. + +| # | Action | Role | +| --- | --------------------------------- | --------------------------------------------------------------------------------------------- | +| 01 | `detect-context` | Identify repo, default branch, existing config, CI permissions | +| 02 | `ask-config` | Collect runtime parameters (mode, auth, labels, schedule, agents) | +| 03 | `generate-workflow` | Emit `.github/workflows/aidd-async.yml` from `assets/setup/workflow-template.yml` | +| 04 | `generate-local-script` | Emit poll/daemon scripts (local mode only) from `assets/setup/local-*-template.sh` | +| 05 | `write-config` | Persist `.claude/aidd-orchestrator.json` from `assets/setup/config-template.json` | +| 06 | `bootstrap-labels` | Create `to-implement` / `to-review` / `claude/*` labels via `gh` | +| 07 | `install-user-scope-plugins` | Install `aidd-orchestrator` + `aidd-dev` at user scope (local mode) | +| 08 | `configure-remote-secrets` | Sync `CLAUDE_CODE_OAUTH_TOKEN`, `AIDD_BOT_TOKEN`, etc. (remote mode) | +| 09 | `bootstrap-scheduling` | Schedule the poll routine (local) or rely on workflow webhook (remote) | +| 10 | `commit-and-push` | Stage generated files, conventional-commit, push | +| 11 | `smoke-test` | Label a throwaway issue with `to-implement`; verify the pipeline reacts | + +Action files, read in the order above: + +- `actions/setup/01-detect-context.md` +- `actions/setup/02-ask-config.md` +- `actions/setup/03-generate-workflow.md` +- `actions/setup/04-generate-local-script.md` +- `actions/setup/05-write-config.md` +- `actions/setup/06-bootstrap-labels.md` +- `actions/setup/07-install-user-scope-plugins.md` +- `actions/setup/08-configure-remote-secrets.md` +- `actions/setup/09-bootstrap-scheduling.md` +- `actions/setup/10-commit-and-push.md` +- `actions/setup/11-smoke-test.md` + +Default flow: `01 → 02 → 03 → 04 → 05 → 06 → 07 → 08 → 09 → 10 → 11`. Actions self-skip when their preconditions are not met (e.g. `07` skips in remote mode, `08` skips in local mode). diff --git a/plugins/aidd-orchestrator/skills/00-async-dev/references/routing.md b/plugins/aidd-orchestrator/skills/00-async-dev/references/routing.md index 2e4c86bdf..d48c1f788 100644 --- a/plugins/aidd-orchestrator/skills/00-async-dev/references/routing.md +++ b/plugins/aidd-orchestrator/skills/00-async-dev/references/routing.md @@ -6,7 +6,7 @@ Full contract for picking a sub-flow inside `aidd-orchestrator:00-async-dev`. Th ## 1. Explicit override: the arguments keyword -If the arguments contain exactly `setup`, `run`, or `review` as a standalone case-insensitive token, route there immediately. No fallback consideration. +If the arguments are exactly `setup`, `run`, or `review` (case-insensitive), or the prompt contains `action=` as a standalone token, route there immediately, subject to conflict resolution below. This is the contract the bundled CI workflow relies on: @@ -15,7 +15,7 @@ prompt: "Use skill aidd-orchestrator:00-async-dev with action=run on issue #..." prompt: "Use skill aidd-orchestrator:00-async-dev with action=review on PR #..." ``` -The router parses `action=` from the prompt body. Plain `setup` / `run` / `review` words elsewhere in the body do NOT match, only the explicit `action=` form, or the arguments set to exactly one of the three keywords. +The router parses `action=` from the prompt body. Plain `setup` / `run` / `review` words elsewhere in the body do not count as an override; they may count as natural-language intent. The arguments also match when they consist of exactly one mode keyword. --- @@ -72,7 +72,7 @@ Arguments keyword (1) > trigger env (2) > repo state (3) > NL intent (4) Within a category, the most specific concrete signal wins: -- A PR number in the arguments beats a label payload. +- An explicit mode argument beats a label payload. A PR number identifies the active review surface when repo state is considered. - A label beats a free-text keyword. - A specific config file presence beats a generic verb. @@ -112,10 +112,10 @@ Never proceed blindly. ## Sub-flow handoff -Once committed, the router invokes the first action of the chosen sub-flow: +Once committed, the router invokes the sequence action of the chosen sub-flow: -- `setup` → `actions/setup/01-detect-context.md` -- `run` → `actions/run/01-poll-ready.md` -- `review` → `actions/review/01-collect-comments.md` +- `setup` → `actions/setup.md` +- `run` → `actions/run.md` +- `review` → `actions/review.md` The sub-flow runs to completion (or abort on a pre-flight failure). The router does not re-dispatch mid-flow. diff --git a/scripts/__tests__/async-dev-router.test.js b/scripts/__tests__/async-dev-router.test.js new file mode 100644 index 000000000..372e3a4e5 --- /dev/null +++ b/scripts/__tests__/async-dev-router.test.js @@ -0,0 +1,57 @@ +const assert = require('node:assert/strict'); +const { readFileSync } = require('node:fs'); +const { join } = require('node:path'); +const test = require('node:test'); + +const root = join(__dirname, '../../plugins/aidd-orchestrator/skills/00-async-dev'); +const read = (path) => readFileSync(join(root, path), 'utf8'); +const modeNames = ['setup', 'run', 'review']; +const expectedActions = { + setup: ['detect-context', 'ask-config', 'generate-workflow', 'generate-local-script', 'write-config', 'bootstrap-labels', 'install-user-scope-plugins', 'configure-remote-secrets', 'bootstrap-scheduling', 'commit-and-push', 'smoke-test'], + run: ['poll-ready', 'resolve-deps', 'acquire-lock', 'check-sdlc', 'delegate-sdlc', 'write-audit'], + review: ['collect-comments', 'detect-stop', 'fix-iteration', 'finalize'], +}; + +test('root loads routing before exactly one selected mode', () => { + const skill = read('SKILL.md'); + assert.match(skill, /actions\/route\.md/); + for (const mode of modeNames) { + assert.match(skill, new RegExp(`actions/${mode}\\.md`)); + assert.doesNotMatch(skill, new RegExp(`actions/${mode}/01-`)); + } + assert.doesNotMatch(skill, /## Routing: hybrid contract|### Setup \(11 actions\)|### Run \(6 actions\)|### Review \(4 actions\)/); +}); + +test('routing reference owns aliases, precedence, and conflict decisions', () => { + const route = read('actions/route.md'); + const reference = read('references/routing.md'); + assert.match(route, /references\/routing\.md/); + assert.match(reference, /action=/); + assert.match(reference, /@claude\\s\+\/\(implement\|aidd-dev:02-implement\|run\)/); + assert.match(reference, /@claude\\s\+\/\(review\|aidd-dev:05-review\)/); + assert.match(reference, /to-implement.*to-review/s); + assert.match(reference, /config is absent/); + assert.match(reference, /open closing PR/); + assert.match(reference, /Unresolved: ask the human/); +}); + +for (const mode of modeNames) { + test(`${mode} loads only its ordered actions`, () => { + const sequence = read(`actions/${mode}.md`); + let prior = -1; + expectedActions[mode].forEach((action, index) => { + const path = `actions/${mode}/${String(index + 1).padStart(2, '0')}-${action}.md`; + const position = sequence.indexOf(path); + assert.ok(position > prior, `${path} missing or out of order`); + prior = position; + }); + for (const other of modeNames.filter((name) => name !== mode)) { + assert.doesNotMatch(sequence, new RegExp(`actions/${other}/`)); + } + }); +} + +test('run retains runtime SDLC discovery', () => { + assert.match(read('actions/run.md'), /04-check-sdlc\.md/); + assert.match(read('actions/run/04-check-sdlc.md'), /Match by description, not by hardcoded name/); +}); From 7934872d55b309a92a3e1879a1a9827e573a38f1 Mon Sep 17 00:00:00 2001 From: Sebastien Giband Date: Tue, 20 Oct 2026 23:04:42 +0200 Subject: [PATCH 2/3] fix(aidd-orchestrator): preserve resolved routing conflicts Make the route action follow explicit conflict outcomes and record baseline parity scenarios so reviewers can trace selection rules without a duplicate resolver. Refs #420 --- .../evidence.md | 36 ++++++++++++++-- .../2026_10_20_delivery-loop-router/review.md | 25 +++++++++++ .../skills/00-async-dev/actions/route.md | 4 +- .../skills/00-async-dev/references/routing.md | 4 +- scripts/__tests__/async-dev-router.test.js | 41 +++++++++++++++---- 5 files changed, 93 insertions(+), 17 deletions(-) create mode 100644 aidd_docs/tasks/2026_10/2026_10_20_delivery-loop-router/review.md diff --git a/aidd_docs/tasks/2026_10/2026_10_20_delivery-loop-router/evidence.md b/aidd_docs/tasks/2026_10/2026_10_20_delivery-loop-router/evidence.md index 33445453f..3d9812238 100644 --- a/aidd_docs/tasks/2026_10/2026_10_20_delivery-loop-router/evidence.md +++ b/aidd_docs/tasks/2026_10/2026_10_20_delivery-loop-router/evidence.md @@ -4,11 +4,11 @@ Count physical lines, including blank lines. The measured path reads `SKILL.md`, | Mode | Before | After | Change | | --- | ---: | ---: | ---: | -| Setup | 796 | 751 | -45 | -| Run | 508 | 453 | -55 | -| Review | 424 | 365 | -59 | +| Setup | 796 | 750 | -46 | +| Run | 508 | 452 | -56 | +| Review | 424 | 364 | -60 | -Shortest: review, 424 → 365 lines. Longest: setup, 796 → 751 lines. +Shortest: review, 424 → 364 lines. Longest: setup, 796 → 750 lines. Reproduce from the repository root: @@ -26,3 +26,31 @@ for mode in ('setup', 'run', 'review'): after = sum(new(path) for path in ['SKILL.md', 'actions/route.md', 'references/routing.md', f'actions/{mode}.md', *actions]) print(mode, before, after, after - before) ``` + +# Routing parity scenarios + +Compared the pre-change `SKILL.md` and `references/routing.md` at the merge base with the new `actions/route.md` and `references/routing.md`. These are **documented decision outcomes**, not executions by a skill host. The scenarios cover the named signals and the exceptions to ordinary precedence. + +| Signal | Concrete input | Baseline decision | New decision | +| --- | --- | --- | --- | +| Explicit argument | Arguments exactly `RUN` | run | run | +| Explicit action | Prompt `action=review on PR #17` | review | review | +| CI label | `GITHUB_EVENT_NAME=issues`, label `to-implement` | run | run | +| CI label | `GITHUB_EVENT_NAME=issues`, label `to-review` | review | review | +| CI comment alias | `issue_comment`, `@claude /aidd-dev:02-implement` | run | run | +| CI comment alias | `issue_comment`, `@claude /aidd-dev:05-review` | review | review | +| CI dispatch | `workflow_dispatch`, input `action=setup` | setup | setup | +| Repository state | Workflow and config both absent | setup | setup | +| Repository state | Workflow present, config absent | setup | setup | +| Repository state | Open PR closes referenced issue | review | review | +| Repository state | Fresh issue, no linked PR, implementation intent | run | run | +| Natural language | `bootstrap async dev` | setup | setup | +| Natural language | `pick up the queue` | run | run | +| Natural language | `apply feedback` | review | review | +| Tie break | `action=review` and label `to-implement` | review | review | +| Blocking conflict | User requests run, config absent | stop, request setup | stop, request setup | +| Resolved conflict | Webhook `to-implement`, open closing PR | review, comment | review, comment | +| Resolved conflict | Both `to-implement` and `to-review` labels | review, comment | review, comment | +| Unresolved | No matching arguments, trigger, state, or intent | ask which mode | ask which mode | + +The original root described a standalone mode token inside arguments as an override, while the original routing reference required arguments to equal a mode or the prompt to contain `action=`. The new reference follows the explicit argument contract used by CI: exact mode arguments or a standalone `action=` token. Plain mode words in a longer prompt remain natural-language signals. Runtime LLM selection and mode execution remain unverified; the static tests below guard the written contract and action boundaries. diff --git a/aidd_docs/tasks/2026_10/2026_10_20_delivery-loop-router/review.md b/aidd_docs/tasks/2026_10/2026_10_20_delivery-loop-router/review.md new file mode 100644 index 000000000..ea6e18da9 --- /dev/null +++ b/aidd_docs/tasks/2026_10/2026_10_20_delivery-loop-router/review.md @@ -0,0 +1,25 @@ +# Review: Delivery loop router + +- Rounds: 1 +- Started: 2026-10-20 + +## Round 1 · r-420a + +- Date: 2026-10-20 +- By: Sebastien Giband +- Diff: `da3a99c5903f911df5e4e6201d946a28c7937efd...82ca178b1c1f3d936849a705d2709c70c525f840` +- Axes: code, functional, relevancy +- Verdict: changes-requested +- Score: 3/4 met, 1 unmet, 0 unverified at runtime, 0 out of the diff + +### Criteria + +- [x] Phase 1, routing has one detailed source and a route action — `actions/route.md:13-15` loads `references/routing.md`; the root has no signal rules. +- [x] Phase 1, root and selected mode boundaries preserve action order and dynamic discovery — `SKILL.md:9-33`, `actions/setup.md`, `actions/run.md`, `actions/review.md`, and unchanged `actions/run/04-check-sdlc.md`. +- [ ] Phase 2, routing parity is guarded for explicit arguments, CI labels and comment aliases, repo state, natural language, tie breaks, conflicts, and unresolved signals — `scripts/__tests__/async-dev-router.test.js:25-35` only checks that words occur in the reference. No input scenario selects a mode or checks an expected outcome. +- [x] Phase 2, before and after path counts and checks are recorded — `evidence.md:1-28` gives the inclusion rule, revision procedure, and 424 → 365 shortest / 796 → 751 longest totals. + +### Findings + +- `plugins/aidd-orchestrator/skills/00-async-dev/actions/route.md:15` : it says a conflicting request always stops before loading a mode, while `references/routing.md:89-92` explicitly routes two conflict cases to `review`; two agents can choose different outcomes for the same webhook → distinguish blocking conflicts from resolved conflict overrides, and make the route action defer to the reference's resolved mode — 🟡 major, architecture +- `scripts/__tests__/async-dev-router.test.js:25` : the routing test proves only that strings exist, so a changed precedence or lost alias behavior would still pass; the plan requires exercised input cases → test the decision outcomes for each signal class and conflict, or provide a deterministic resolver that the scenarios can invoke — 🟡 major, code-health diff --git a/plugins/aidd-orchestrator/skills/00-async-dev/actions/route.md b/plugins/aidd-orchestrator/skills/00-async-dev/actions/route.md index c6014b4ac..8a5e656a6 100644 --- a/plugins/aidd-orchestrator/skills/00-async-dev/actions/route.md +++ b/plugins/aidd-orchestrator/skills/00-async-dev/actions/route.md @@ -12,8 +12,8 @@ One selected mode: `setup`, `run`, or `review`; or an unresolved/conflict report 1. Read `references/routing.md` and apply its signal precedence, aliases, and conflict rules. 2. On a resolved mode, read exactly one of `actions/setup.md`, `actions/run.md`, or `actions/review.md` and follow it to completion. Do not load another mode's sequence. -3. On an unresolved or conflicting request, surface the decision from the routing reference and stop before loading a mode. +3. If the routing reference resolves a conflict to a mode, load that mode. Stop before loading a mode only when the reference declares the conflict blocking or the signals unresolved. ## Test -Explicit arguments, CI labels and comments, repository state, and natural language select the same modes as the routing reference. A conflict or unresolved signal does not load a mode. +Explicit arguments, CI labels and comments, repository state, and natural language select the same modes as the routing reference. A resolved webhook conflict loads `review`; a blocking conflict or unresolved signal does not load a mode. diff --git a/plugins/aidd-orchestrator/skills/00-async-dev/references/routing.md b/plugins/aidd-orchestrator/skills/00-async-dev/references/routing.md index d48c1f788..0aca19c28 100644 --- a/plugins/aidd-orchestrator/skills/00-async-dev/references/routing.md +++ b/plugins/aidd-orchestrator/skills/00-async-dev/references/routing.md @@ -1,6 +1,6 @@ # Routing: decision tree -Full contract for picking a sub-flow inside `aidd-orchestrator:00-async-dev`. The router walks these signals in order; the first match wins. +Full contract for picking a sub-flow inside `aidd-orchestrator:00-async-dev`. The router walks these signals in precedence order, then applies the conflict resolutions below. A resolved conflict overrides the ordinary signal result. --- @@ -80,7 +80,7 @@ Within a category, the most specific concrete signal wins: ## Conflict resolution -The router NEVER silently switches sub-flow when the request and the state disagree. +The router applies these explicit conflict outcomes when the request and state disagree. Only a conflict marked as blocking stops delegation; a conflict resolved to a mode continues through that mode. Examples: diff --git a/scripts/__tests__/async-dev-router.test.js b/scripts/__tests__/async-dev-router.test.js index 372e3a4e5..15bfc4e77 100644 --- a/scripts/__tests__/async-dev-router.test.js +++ b/scripts/__tests__/async-dev-router.test.js @@ -22,17 +22,40 @@ test('root loads routing before exactly one selected mode', () => { assert.doesNotMatch(skill, /## Routing: hybrid contract|### Setup \(11 actions\)|### Run \(6 actions\)|### Review \(4 actions\)/); }); -test('routing reference owns aliases, precedence, and conflict decisions', () => { +test('documented scenarios preserve the baseline selection outcomes', () => { const route = read('actions/route.md'); const reference = read('references/routing.md'); - assert.match(route, /references\/routing\.md/); - assert.match(reference, /action=/); - assert.match(reference, /@claude\\s\+\/\(implement\|aidd-dev:02-implement\|run\)/); - assert.match(reference, /@claude\\s\+\/\(review\|aidd-dev:05-review\)/); - assert.match(reference, /to-implement.*to-review/s); - assert.match(reference, /config is absent/); - assert.match(reference, /open closing PR/); - assert.match(reference, /Unresolved: ask the human/); + const evidence = readFileSync(join(__dirname, '../../aidd_docs/tasks/2026_10/2026_10_20_delivery-loop-router/evidence.md'), 'utf8'); + const scenarios = [ + ['Arguments exactly `RUN`', 'run', /arguments are exactly `setup`, `run`, or `review` \(case-insensitive\)/], + ['Prompt `action=review on PR #17`', 'review', /prompt contains `action=` as a standalone token/], + ['label `to-implement`', 'run', /label `to-implement`\s+\| `run`/], + ['label `to-review`', 'review', /label `to-review`\s+\| `review`/], + ['`@claude \/aidd-dev:02-implement`', 'run', /@claude\\s\+\/\(implement\|aidd-dev:02-implement\|run\)/], + ['`@claude \/aidd-dev:05-review`', 'review', /@claude\\s\+\/\(review\|aidd-dev:05-review\)/], + ['input `action=setup`', 'setup', /workflow_dispatch` \+ input `action=`/], + ['Workflow and config both absent', 'setup', /aidd-async\.yml` missing AND `.claude\/aidd-orchestrator\.json` missing\s+\| `setup`/], + ['Workflow present, config absent', 'setup', /orchestrator\.json` missing but workflow present.*`setup` \(incomplete install\)/], + ['Open PR closes referenced issue', 'review', /Open PR closes the referenced issue\s+\| `review`/], + ['Fresh issue, no linked PR', 'run', /Fresh issue with no linked PR \+ intent to implement\s+\| `run`/], + ['`bootstrap async dev`', 'setup', /`bootstrap`.*`setup`/], + ['`pick up the queue`', 'run', /`pick up the queue`.*`run`/], + ['`apply feedback`', 'review', /`apply feedback`.*`review`/], + ['`action=review` and label `to-implement`', 'review', /Arguments keyword \(1\) > trigger env \(2\)/], + ['User requests run, config absent', 'stop, request setup', /User says "run" but config is absent[\s\S]*?Surface the conflict and stop/], + ['Webhook `to-implement`, open closing PR', 'review, comment', /Workflow webhook fires `to-implement` but the issue already has an open closing PR[\s\S]*?Route to `review`/], + ['Both `to-implement` and `to-review` labels', 'review, comment', /Label `to-implement` AND label `to-review` both present[\s\S]*?Route to `review`/], + ['No matching arguments, trigger, state, or intent', 'ask which mode', /If none of the above produces a single match[\s\S]*?ask/], + ]; + for (const [input, outcome, contract] of scenarios) { + const row = evidence.split('\n').find((line) => line.startsWith('| ') && line.includes(input)); + assert.ok(row, `missing scenario: ${input}`); + assert.ok(row.endsWith(`| ${outcome} | ${outcome} |`), `baseline/new mismatch: ${input}`); + assert.match(reference, contract, `missing selection contract: ${input}`); + } + assert.match(route, /resolves a conflict to a mode, load that mode/); + assert.match(route, /Stop before loading a mode only when.*blocking or the signals unresolved/); + assert.match(evidence, /Runtime LLM selection and mode execution remain unverified/); }); for (const mode of modeNames) { From 47561b2adf28654b5f70544006f18f011f2c69a0 Mon Sep 17 00:00:00 2001 From: Sebastien Giband Date: Wed, 21 Oct 2026 17:30:29 +0200 Subject: [PATCH 3/3] docs(aidd-orchestrator): record independent delivery review Keep the review rounds and outcome challenge with the #420 delivery evidence, including the runtime verification limit. Refs #420 --- .../challenge.md | 11 +++++++++++ .../2026_10_20_delivery-loop-router/review.md | 18 +++++++++++++++++- 2 files changed, 28 insertions(+), 1 deletion(-) create mode 100644 aidd_docs/tasks/2026_10/2026_10_20_delivery-loop-router/challenge.md diff --git a/aidd_docs/tasks/2026_10/2026_10_20_delivery-loop-router/challenge.md b/aidd_docs/tasks/2026_10/2026_10_20_delivery-loop-router/challenge.md new file mode 100644 index 000000000..a81a3c82a --- /dev/null +++ b/aidd_docs/tasks/2026_10/2026_10_20_delivery-loop-router/challenge.md @@ -0,0 +1,11 @@ +My confidence level of correctness now: 85% + +# Correctness (100%) +- The router keeps one detailed routing source (`references/routing.md`), loads one selected mode, and retains the ordered mode action files. The round-1 conflict was repaired: explicit resolved conflicts select `review`; blocking conflicts stop. +- The shortest and longest documented paths fall from 424 to 364 and 796 to 750 lines. `evidence.md` states the inclusion rule and a reproducible counting command. + +# Deal breakers +- + +# Suggestions (enhancements only) +- Run one invocation per mode in a skill host before release. The Markdown contract and static tests cover written routing decisions; they cannot demonstrate the host's runtime selection or outputs. diff --git a/aidd_docs/tasks/2026_10/2026_10_20_delivery-loop-router/review.md b/aidd_docs/tasks/2026_10/2026_10_20_delivery-loop-router/review.md index ea6e18da9..c35fb93d8 100644 --- a/aidd_docs/tasks/2026_10/2026_10_20_delivery-loop-router/review.md +++ b/aidd_docs/tasks/2026_10/2026_10_20_delivery-loop-router/review.md @@ -1,6 +1,6 @@ # Review: Delivery loop router -- Rounds: 1 +- Rounds: 2 - Started: 2026-10-20 ## Round 1 · r-420a @@ -23,3 +23,19 @@ - `plugins/aidd-orchestrator/skills/00-async-dev/actions/route.md:15` : it says a conflicting request always stops before loading a mode, while `references/routing.md:89-92` explicitly routes two conflict cases to `review`; two agents can choose different outcomes for the same webhook → distinguish blocking conflicts from resolved conflict overrides, and make the route action defer to the reference's resolved mode — 🟡 major, architecture - `scripts/__tests__/async-dev-router.test.js:25` : the routing test proves only that strings exist, so a changed precedence or lost alias behavior would still pass; the plan requires exercised input cases → test the decision outcomes for each signal class and conflict, or provide a deterministic resolver that the scenarios can invoke — 🟡 major, code-health + +## Round 2 · r-7934 + +- Date: 2026-10-21 +- By: Sebastien Giband +- Diff: `da3a99c5903f911df5e4e6201d946a28c7937efd...7934872d` +- Axes: code, functional, relevancy +- Verdict: approve +- Score: 3/4 met, 0 unmet, 1 unverified at runtime, 0 out of the diff + +### Criteria + +- [x] Phase 1, routing has one detailed source and a route action — `actions/route.md:13-15` loads `references/routing.md`; the root has no signal rules. +- [x] Phase 1, root and selected mode boundaries preserve action order and dynamic discovery — `SKILL.md:9-33`, the three mode files, and unchanged `actions/run/04-check-sdlc.md`. The route/reference conflict from round 1 is repaired: resolved conflicts load their selected mode. +- Unverified at runtime: Phase 2, setup, run, and review select the same mode for the same inputs — `async-dev-router.test.js:25-66` and `evidence.md:31-63` statically compare the documented decisions and action boundaries. The skill is Markdown, with no executable resolver or host harness, so these files cannot prove runtime selection. +- [x] Phase 2, before and after path counts and checks are recorded — `evidence.md:1-28` gives the inclusion rule and 424 → 364 shortest / 796 → 750 longest totals.