Skip to content

Feature/2582 pg 1 - #4

Merged
PolyphonyRequiem merged 6 commits into
mainfrom
feature/2582-pg-1
Apr 30, 2026
Merged

Feature/2582 pg 1#4
PolyphonyRequiem merged 6 commits into
mainfrom
feature/2582-pg-1

Conversation

@PolyphonyRequiem

Copy link
Copy Markdown
Owner

No description provided.

Daniel Green and others added 4 commits April 30, 2026 06:30
Add nullable string Tags property to the HierarchyResult sealed record.
The property defaults to null and is omitted from JSON output via
WhenWritingNull on PolyphonyJsonContext. When set, it serializes as
snake_case 'tags'. No changes needed to PolyphonyJsonContext since
the source generator picks up the new property automatically.

Also update Directory.Build.props to net11.0 to match Twig.Domain TFM.

Add 6 unit tests covering:
- Tags defaults to null
- Null tags omitted from JSON
- Tags included in JSON when set (snake_case)
- Round-trip serialization
- Empty string tags included in JSON
- Deserialization from JSON without tags field

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Read System.Tags from WorkItem.Fields in BuildNodeAsync and assign to
HierarchyResult.Tags. Added WithField helper to WorkItemBuilder and
three walker tests covering tags present, absent, and child propagation.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Add WithTags fluent method to WorkItemBuilder for tag test setup
- Refactor HierarchyWalker tag tests to use WithTags builder method
- Add HierarchyCommand tests verifying tags in JSON output

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Add HierarchyResultSerializationTests with two focused tests verifying
that the source-generated PolyphonyJsonContext correctly serializes the
Tags property as snake_case 'tags' and omits it when null.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@PolyphonyRequiem

Copy link
Copy Markdown
Owner Author

@copilot, remove the 4 conflicting files from the repo entirely and add to gitignore

Copilot AI commented Apr 30, 2026

Copy link
Copy Markdown

@copilot, remove the 4 conflicting files from the repo entirely and add to gitignore

Done in b08fbf0. All .twig/ files have been removed from tracking and .twig/ has been added to .gitignore to prevent future commits of those files.

@PolyphonyRequiem

Copy link
Copy Markdown
Owner Author

@copilot thanks for the last commit, but I still have merge conflicts. just delete these 4 conflictingfiles from the repo please.

Co-authored-by: PolyphonyRequiem <3107779+PolyphonyRequiem@users.noreply.github.com>

Copilot AI commented Apr 30, 2026

Copy link
Copy Markdown

@copilot thanks for the last commit, but I still have merge conflicts. just delete these 4 conflictingfiles from the repo please.

Done in 27b4124. Merged main into this branch and resolved all 4 modify/delete conflicts by deleting .twig/dangreen-msft/Twig/twig.db, .twig/dangreen-msft/Twig/twig.db-shm, .twig/dangreen-msft/Twig/twig.db-wal, and .twig/prompt.json. The PR should now merge cleanly.

@PolyphonyRequiem
PolyphonyRequiem merged commit 4d1f592 into main Apr 30, 2026
@PolyphonyRequiem
PolyphonyRequiem deleted the feature/2582-pg-1 branch April 30, 2026 16:49
Copilot stopped work on behalf of PolyphonyRequiem due to an error April 30, 2026 16:50
PolyphonyRequiem added a commit that referenced this pull request May 7, 2026
…ges #1) (#125)

Lays the foundation for cross-item dependency resolution. PR #1 of the
five-PR Phase 7 edges arc — definitional bucket only; conflict detection,
policy edges, planner-declared edges, and the execution-mode knob land in
later PRs.

NEW SURFACE
- EdgeGraph: immutable graph over a worklist's items + their derived
  RequirementSets + a flat list of cross-item CrossItemEdges.
  Build(items) returns the graph; ToWaves() returns Kahn's-algorithm
  waves for sequential dispatch.
- CrossItemEdge: cross-item analogue of RequirementEdge.
- CrossItemEdgeDeriver.DeriveDefinitional: emits two definitional rules
  per parent-child pair: (1) parent.children_seeded -> each child entry
  requirement (only when parent has children_seeded), (2) child.item_satisfied
  -> parent.item_satisfied (always for in-scope pairs).
- EdgeConflict + EdgeConflictKind: shape for PR #2's cycle/unknown-item
  detection. PR #1 always emits an empty conflicts list.
- EdgeGraphInput / EdgeGraphWave: input + output value records.

BEHAVIOR CHANGE: ItemSatisfied terminal
Every item now carries a synthetic RequirementKind.ItemSatisfied terminal,
emitted unconditionally by RequirementSetDeriver. Every leaf within-item
requirement (no outgoing within-item edges) gets a definitional edge
into it. This makes the graph closed -- a single uniform "this item is
done" signal that cross-item rollup can target.

Reducer treats ItemSatisfied as derived (never observed):
- Pure container case (only ItemSatisfied, no incoming edges): stays
  Needed pending cross-item rollup from children (out of scope for PR #1).
- All-inputs-satisfied case: promotes straight to Satisfied (skipping
  Ready) -- nothing to dispatch, the item is wholly done.

ToWaves only counts edges targeting ENTRY requirements (within-item reqs
with no incoming within-item edges, excluding ItemSatisfied) as gating
dispatch. Terminal-rollup edges gate completion, not start -- without
this, a parent would never be in wave 0 because its ItemSatisfied
depends on every child's ItemSatisfied.

ClassifyStatus in `polyphony state next-ready` recognizes the
pure-container case (single Needed ItemSatisfied) and reports "empty"
to preserve the existing contract.

TEST UPDATES
- 14 new EdgeGraph tests covering contract, wave shape, dispatch
  invariants, determinism, and PR #1 invariants.
- 3 deriver tests updated for the new ItemSatisfied surface
  (PureContainer, ImplementableLeaf, ActionableHuman).
- 1 RequirementsCommands test renamed + assertion bumped from 1 to 2 items.
- 1 NextReady pure-container test asserts the new (Needed ItemSatisfied,
  Status=empty) shape.

PR #1 INVARIANTS (intentionally not exercised yet)
- EdgeGraph.Conflicts is always empty (PR #2 populates).
- Only the definitional bucket is wired in (PRs #4-5 add policy and
  execution-mode edges).
- CrossItemEdgeDeriver currently only handles parent-child; sibling
  ordering (predecessor/successor) ships with the planner-declared
  bucket later.

Total tests: 2538 passing, 0 failing.

Co-authored-by: Daniel Green <dangreen@microsoft.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
PolyphonyRequiem added a commit that referenced this pull request May 7, 2026
…126)

Adds per-type execution_mode field (parallel|plan_then_implement, default parallel) plus resolver hookup and V-19 validator. Schema-only — PR #5 wires the actual edge injection.

- ExecutionMode constants class with IsValid guard.

- TypeConfig.ExecutionMode (YAML alias execution_mode).

- ResolvedRequirementInputs gains ExecutionMode + ExecutionModeProvenance; new ResolutionProvenance.Default constant (distinct from Inferred so AnyInferred semantics stay correct).

- ConfigValidator V-19: unknown execution_mode is a config error; whitespace mirrors resolver's unset handling.

- 20 new tests; 2558 pass total.

Co-authored-by: Daniel Green <dangreen@microsoft.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
PolyphonyRequiem added a commit that referenced this pull request May 7, 2026
GitHub-only verb that opens (or reuses) the PR promoting an evidence
branch into its parent feature branch (or main for the orphan-evidence
case where no apex is supplied). Mirrors pr create-feature-pr structure
(twig-driven title) and pr open-mg-pr (head/base validation, idempotent
reuse, GhClientPolicy-inherited retry/timeout/reconcile).

Verb signature:
  polyphony pr open-evidence-pr <work-item-id>
    [--apex-id N] [--head ref] [--base-branch ref]
    [--title text] [--body text]

Branch naming defaults:
  apex provided  -> head=evidence/<apex>-<wi>, base=feature/<apex>
  orphan/collapsed -> head=evidence/<wi>, base=main

ADO sibling deferred per session policy (GitHub-only PR-openers in
Phase 6). Workflow consumption (PR #4) and evidence floor check
(PR #7) land separately.

Tests: 15 new (2583 total). Covers argument validation, naming
defaults, override knobs, twig fallback, idempotent reuse, error
envelopes (missing branches, no slug, gh non-zero), inherited
gh-create timeout-with-empty-reconcile path, and snake_case JSON
contract.

Co-authored-by: Daniel Green <dangreen@microsoft.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
PolyphonyRequiem pushed a commit that referenced this pull request May 7, 2026
#4)

Wires the Phase 6 evidence verbs (branch ensure-evidence-branch from
PR #132 / e63d84f, pr open-evidence-pr from PR #130 / 97bd373) into a
runnable actionable.yaml conductor workflow. Routes between a polyphony
executor (full evidence chain: branch -> agent -> PR -> review -> merge)
and a human executor (satisfaction gate only) via an in-workflow
executor_router script.

Includes:
- .conductor/registry/workflows/actionable.yaml (~700 lines)
- .conductor/registry/scripts/route-actionable-executor.ps1 + 6 xUnit tests
- .conductor/registry/tests/lint-actionable.{ps1,Tests.ps1} (12 checks, 21 tests)
- .conductor/registry/index.yaml entry at 1.0.0
- docs/decisions/actionable-executor-split.md (ADR)
- docs/glossary.md "Workflows" section (3 new entries)
- .github/skills/polyphony-actionable/SKILL.md

Three deferred-wiring slots are marked with TODO(p6-pr5/7/8) markers
and pinned by lint-actionable.ps1: facet-profile composition (PR #5),
evidence floor check (PR #7), full evidence_reviewer rubric (PR #8).

Validation:
- dotnet build clean
- dotnet test 2705/2705 passing (baseline 2699 + 6 new)
- Pester registry 122/122 across 9 lint suites
- Pester root 71/71 across 4 lint suites

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
PolyphonyRequiem added a commit that referenced this pull request May 7, 2026
#4) (#136)

Wires the Phase 6 evidence verbs (branch ensure-evidence-branch from
PR #132 / e63d84f, pr open-evidence-pr from PR #130 / 97bd373) into a
runnable actionable.yaml conductor workflow. Routes between a polyphony
executor (full evidence chain: branch -> agent -> PR -> review -> merge)
and a human executor (satisfaction gate only) via an in-workflow
executor_router script.

Includes:
- .conductor/registry/workflows/actionable.yaml (~700 lines)
- .conductor/registry/scripts/route-actionable-executor.ps1 + 6 xUnit tests
- .conductor/registry/tests/lint-actionable.{ps1,Tests.ps1} (12 checks, 21 tests)
- .conductor/registry/index.yaml entry at 1.0.0
- docs/decisions/actionable-executor-split.md (ADR)
- docs/glossary.md "Workflows" section (3 new entries)
- .github/skills/polyphony-actionable/SKILL.md

Three deferred-wiring slots are marked with TODO(p6-pr5/7/8) markers
and pinned by lint-actionable.ps1: facet-profile composition (PR #5),
evidence floor check (PR #7), full evidence_reviewer rubric (PR #8).

Validation:
- dotnet build clean
- dotnet test 2705/2705 passing (baseline 2699 + 6 new)
- Pester registry 122/122 across 9 lint suites
- Pester root 71/71 across 4 lint suites

Co-authored-by: Daniel Green <dangreen@microsoft.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
PolyphonyRequiem added a commit that referenced this pull request May 7, 2026
Per docs/polyphony-tags.md:169, the tree-walker is supposed to stamp
the polyphony:root tag at entry. The MVP shipped without this wiring
(bug #4 in the audit chain). Without the stamp, every descendant's
polyphony root resolve returns fallback_required=true and the
descent gate fires for every item.

Adds a new declare_root script step between init_manifest and
�uild_worklist. Routing-style envelope (error -> preflight_failure_gate;
success -> build_worklist). Idempotent.

Tests:
- Updated init_manifest contract test (now routes to declare_root).
- New declare_root Describe block (6 tests covering presence, command,
  inputs, success route, error route, reachability).
- conductor validate 14/14, type-agnostic lint clean,
  contracts 27/27, e2e-apex-driver 56/56, lint-apex-driver 24/24.

Depends on PR #160 (DI fix that makes the verb actually runnable).

Co-authored-by: Daniel Green <dangreen@microsoft.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
PolyphonyRequiem added a commit that referenced this pull request May 8, 2026
Replaces the broken `*.plan.md` filesystem glob in `polyphony state
next-ready` with the `PlanObserver` service shipped in PR #1 (#212).
The verb now reports `plan_authored`, `plan_reviewed`, `plan_promoted`
dispositions from live PR state — fixing the smoking-gun where apex
3043 reported all three as `Needed` despite plan PR #204 being merged.

Implements row #2 of the 10-PR plan in
`files/closed-loop-state-plan.md` (§2 smoking-gun, §3.1 observer
matrix, §4 implementation order).

## Changes

### IO-sharing scaffolding (PR #3 / #4 friendly)

- New internal `NextReadyObservationScope` — a per-item context that
  carries the shared plan-kind I/O (slug, plan branch, branch-existence,
  latest plan PR, PR poll data, plus per-shell-out error fields). Built
  exactly once per verb invocation; passed to per-kind composer
  methods. Documented plug-in contract in XML doc so PR #3
  (ChildrenSeeded) and PR #4 (Implementation) can extend the scope and
  add composers without re-architecting.

### Verb wiring

- `StateCommands.NextReady.cs` rewires `ComputeObservedAsync`:
  - `BuildObservationScopeAsync` fetches each shared signal exactly
    once with try/catch around every shell-out (slug, branch-exists,
    pr-list, pr-poll). Failures captured on the scope as
    `PlanPrFetchError` / `PlanPrPollError` — never thrown.
  - `ResolveRootIdAsync` walks `WorkItem.ParentId` (with cycle
    detection and a 50-step cap) so descendant items inspect
    `plan/{root}-{item}` and root items inspect `plan/{root}`.
  - `ComposePlanAuthored` / `ComposePlanReviewed` / `ComposePlanPromoted`
    delegate to `PlanObserver.MapPlan*` static mappers but force
    `Needed` (with the captured reason) when the scope recorded an
    I/O error — distinguishing "couldn't observe" from "observed: no
    PR".
  - `ValidateDisposition` throws on unknown / `Ready` disposition
    strings (per spec — observers must never emit `Ready`; that is
    reducer-derived).

### Result schema

- New optional `observation_reasons` field on `StateNextReadyResult`
  (omitted from JSON when null/empty). Maps `RequirementKind` →
  short human-readable reason such as `"plan PR #204 merged"` or
  `"could not resolve repo slug from origin remote"`. Enables
  workflow / human debugging.
- `PolyphonyJsonContext` registers `IReadOnlyDictionary<string,string>`
  for the new field's source-generated AOT serializer.

### Removed

- `StateCommands.PlanDiscovery.cs` — `DiscoverPlan` was the
  filesystem-glob hack; only callsite was `ComputeObservedAsync`,
  which now uses the observer.

### Tests

- 8 new integration tests in `StateNextReadyPlanIntegrationTests.cs`
  covering the closed-loop spec scenarios (no plan branch, open PR,
  merged PR — including the apex 3043 regression guard, approved
  unmerged, closed unmerged, gh failure, no origin, descendant item
  walking parent chain). All pass.
- Existing tests (`StateNextReadyTests`, `JsonOutputContractTests`,
  `StateCommandsPreflightTests`, `StateValidateInputsTests`) updated
  to construct `PlanObserver` for the new ctor parameter, with
  baseline shell-out stubs that degrade cleanly to `Needed`.

## Smoke test

`polyphony state next-ready --work-item 3043` against the live cache:

```json
{"work_item_id":3043,"work_item_type":"Epic","status":"dispatchable",
 "satisfied":["plan_authored","plan_reviewed","plan_promoted"],
 "observation_reasons":{"plan_authored":"plan PR #204 merged",
                        "plan_reviewed":"plan PR #204 merged (implies approved)",
                        "plan_promoted":"plan PR #204 merged"}}
```

The plan-kind chain is now truthful. (Status remains `dispatchable`
because `children_seeded` is the next gate — that's PR #3's scope.)

## Test counts

- 3030 tests pass (8 new + 3022 existing).
- Lints: `lint-conductor-validate.ps1`, `lint-type-agnostic.ps1`,
  `lint-jinja-resolver.ps1`, `lint-psscriptroot-paths.ps1` all pass.
- `lint-version-drift.ps1` fails — pre-existing on `origin/main`,
  unrelated to this change.

## For PR #3 reviewer

The scaffolding is ready: extend `NextReadyObservationScope` with new
mutable fields (e.g. `PlannedTagPresent`), add a `ComposeChildrenSeeded`
method that reads them, and wire it into `BuildObservedFromSignals`.
Same pattern for PR #4 implementation observer.

Co-authored-by: Daniel Green <dangreen@microsoft.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
PolyphonyRequiem added a commit that referenced this pull request May 8, 2026
#216)

Replaces the broken �ny non-Done child' heuristic for children_seeded' with the canonical polyphony:planned' tag-presence check, via PlanObserver.IsParentSeededAsync (the PR #1 primitive). The new semantics correctly report:

- Tag present + children -> Satisfied

- Tag present + zero children (decomposable but indivisible' from PR #7 / closed-loop-state-plan.md section 3.4) -> Satisfied

- Tag absent (regardless of child count) -> Needed

- Twig read failure -> Needed with a non-empty diagnostic reason; no exception escapes the verb

Implementation follows the 4-step plug-in recipe documented on NextReadyObservationScope (added in PR #2):

1. Two new fields on the scope (PlannedTagPresent, PlannedTagFetchError).

2. FetchPlannedTagAsync called from BuildObservationScopeAsync up-front so children_seeded is observed even on plan-branch / slug short-circuit paths.

3. Static composer ComposeChildrenSeeded mirrors ComposePlanAuthored / ComposePlanReviewed / ComposePlanPromoted.

4. Wired into ComputeObservedAsync alongside the plan-kind composers.

Per the recipe, no new Observer class file was added (PR #1 already lifted MapChildrenSeeded onto PlanObserver). This is the deliberate deviation from the original plan section 4 row 3 wording (ChildrenSeededObserver.cs') in favor of the actual scaffolding pattern PR #2 produced.

Tests: new tests/Polyphony.Tests/Commands/StateNextReadyChildrenSeededTests.cs covers the four scenarios from the spec (tag absent regardless of child count, tag present with children, tag present with zero children, twig error and twig throw paths). Test counts: 3073 -> 3079 (+6, all green).

PR #4 (ImplementationObserver) follows next.

Refs files/closed-loop-state-plan.md section 3.1 row 4 + section 4 PR #3.

Co-authored-by: Daniel Green <dangreen@microsoft.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
PolyphonyRequiem added a commit that referenced this pull request May 8, 2026
#217)

Replaces the (item.State, childCount, allChildrenDone) heuristic for the implementation_merged disposition with canonical observation of the impl PR for impl/{root}-{item} via gh pr list + gh pr view. Closes the 'PR merged but state stays Doing' loop documented in closed-loop-state-plan.md S3.1 row 5 and PR #4 row.

Recipe scaling: 3rd plug-in (after PlanObserver in PR #2 and ChildrenSeededObserver in PR #3). Same shape as Fetch*Async + Compose* + observation-scope fields. No scope refactor needed.

MG cross-item rollup deferred to PR #5: an MG-style item with merged self-PR + unmerged children currently reports Satisfied. PR #5's reducer will demote based on worst-child disposition.

Branch grammar: canonical impl branch is impl/{root}-{item} per BranchNameBuilder.Impl and the polyphony-branch-model skill (NOT impl/{root}/{mg}/{id} as the plan prose described). The MG topology lives in the PR base branch, not the head name.

Co-authored-by: Daniel Green <dangreen@microsoft.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
PolyphonyRequiem added a commit that referenced this pull request May 8, 2026
…ing (closes #215) (#218)

Builds the cross-item rollup into `state next-ready` so per-item
dispositions reflect children's terminal state, and threads the
`polyphony:facets=...` tag (PR #7) through `RequirementInputResolver`
so non-decomposed apexes are observed against their declared facets.
Together these close the loop on closed-loop §3.1 rows 5-6 and §4 PR #5.

Cross-item rollup (rows 5-6):
- New `BuildChildRollupSnapshotsAsync` walks an item's children,
  composing each child's reduced `implementation_merged` and
  `item_satisfied` dispositions into the parent's observation scope.
- `ComposeImplementationMerged` takes the worst-of (parent self,
  every implementable child) so an MG with a merged self-PR is NOT
  Satisfied while any child PR is unmerged or absent. The reason
  string surfaces the offending child's id so callers can drill down
  without re-deriving the rollup.
- `ApplyChildItemSatisfiedRollup` mirrors the same posture for
  `item_satisfied`; cycles in the child cache degrade gracefully via
  `snap.Error` rather than throwing.
- Recursion is depth-capped (default 5, override via
  `POLYPHONY_NEXTREADY_ROLLUP_DEPTH`) — the budget is per-frame and
  the truncated frame demotes its own composer to Needed so the cap
  is observable upstream via the worst-of cascade.

apex_facets threading:
- `ExtractFacetOverride` parses the `polyphony:facets=<csv>` tag into
  a facet list via `FacetTagParser.TryExtract`, then passes it as
  `overrideFacets` to `RequirementInputResolver.Resolve`. Non-
  decomposed apexes derive against the declared subset; the facet
  set in the result envelope reflects the override.
- Malformed tag (unknown facet) routes through `EmitNextReadyError`
  → `ExitCodes.ConfigError` with the offending token in the error
  string, matching the routing-style verb contract.

Tests (13 new, all passing on top of the 3085 baseline):
- StateNextReadyCrossItemRollupTests (8): both-merged Satisfied rollup,
  one-child-no-PR demotion, three-mixed-children worst-of, three-level
  chain, default-depth truncation via demoted disposition, env-var
  shrinks cap, env-var raises cap (no truncation pair-test), cycle.
- StateNextReadyImplementationTests (2 new on top of 6): MG + 2
  children all merged → impl_merged Satisfied; MG + 2 children one
  open → not Satisfied + open child id in reason.
- StateNextReadyApexFacetsTests (3 new): tag narrows requirement set
  to declared facets, no-tag baseline falls back to type-config
  default, malformed-tag emits error envelope + ConfigError exit.

Hand-off from PR #4 (#217) — implementation_merged is now the
joint-responsibility kind across parent + descendants. Closes #215.

Co-authored-by: Daniel Green <dangreen@microsoft.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
PolyphonyRequiem added a commit that referenced this pull request May 9, 2026
…anguage (#243)

StatusCommand.cs ComputeHeadline() shipped two next-action hints that referenced
internal task IDs (`F4 lint`, `F5 lands`). Those IDs only exist inside the polish
queue — they are noise to anyone running `polyphony status`. Rewrite both to
describe the actual user action and the actual current state of the system:

- planned-tag-zero-children hint now tells the operator how to fix the plan
  (declare children structurally OR set `apex_facets: [implementable]` in
  front-matter), with no internal-task-ID reference.
- merged-feature-PR hint now states honestly that `item_satisfied`-triggered
  ADO transitions are not wired yet and points to `twig state Done` / the
  ADO web UI as the manual workaround.

No behavioral change. No tests pinned the old strings.

Resolves polish-test #4 (stale-comment sweep).

Co-authored-by: Daniel Green <dangreen@microsoft.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
PolyphonyRequiem added a commit that referenced this pull request May 9, 2026
…ons ARE wired (#245)

Polish #4 (PR #243, commit 74b74d2) replaced the stale `until F5 lands`
hint with new text claiming `Automatic item_satisfied-triggered
transitions are not wired yet`. That replacement was wrong — F5 is
actually code-complete:

  - .conductor/process-config.yaml has item_satisfied → Done mapped
    for Epic, Issue, and Task.
  - polyphony validate --event item_satisfied returns IsValid=true
    for items in the InProgress state category (verified live + via
    TransitionValidatorTests Theory over Epic/Issue/Task).
  - apex-item-dispatch.yaml > terminal_satisfied node calls
    polyphony validate then twig state to advance the per-item state.
  - apex-driver.yaml > close_mark_satisfied does the same for the
    apex itself.
  - lint-conductor-validate passes.

The one criterion from plans/plan-3064.md still genuinely open is
#5 `dogfood-verified` — every prior dogfood was hand-finished after
hitting a different bug, so the corridor's twig-state call has not yet
been observed end-to-end on a real run. That's a follow-up dogfood, not
a code change.

The corrected hint reflects reality: state advancement is automatic;
if it didn't happen, the run likely exited before reaching the close
step, so re-running apex-driver is the actionable suggestion (with
twig state Done as the manual fallback).

No behavior change. No tests pinned the old or new strings.

Co-authored-by: Daniel Green <dangreen@microsoft.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
PolyphonyRequiem added a commit that referenced this pull request May 15, 2026
* Add RepoIdentity discriminated record + multi-platform resolver

Phase 0 of the ADO-everywhere refactor (#414, #415).

* RepoIdentity: sealed discriminated record carrying GitHubRepo(Owner,Name)
  or AdoRepo(Organization,Project,Repository).
* RepoIdentityResolver: override-first / origin-URL-fallback resolver.
  Parses github.com (HTTP/HTTPS/SSH), dev.azure.com (HTTP/SSH),
  *.visualstudio.com (legacy, org-from-subdomain). Percent-decodes
  path segments and tolerates user@host / user:pat@host userinfo.
* ResolvedRepoIdentity envelope mirrors ResolvedManifestPath shape so
  consuming verbs route on (Identity, Error) without throwing.

32 unit tests cover every URL shape + override semantics + error envelopes.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Phase 1 — extend IAdoClient for impl/evidence parity

Parameterise CompletePullRequestAsync (mergeStrategy + deleteSourceBranch
required; no defaults) so the merge path can serve plan/MG (noFastForward,
keep branch) AND impl (squash, delete branch). Add four new IAdoClient
methods that mirror IGhClient shapes:

  - GetPullRequestEvidenceFloorAsync — composes from PR detail + commits
    list; returns AdoEvidenceFloorRead discriminated outcome.
  - GetPullRequestFilesAsync         — picks max iteration, lists changes;
    normalises absolute paths to repo-relative.
  - EditPullRequestBodyAsync         — PATCH the description field (NOT
    body; per ADO REST contract documented in IAdoClient XML doc).
  - ClosePullRequestAsync            — best-effort comment thread, then
    PATCH status:abandoned. Comment failure logged + swallowed.

Add wire DTOs (AdoCommitListResponse/Entry, AdoIterationListResponse/
Entry, AdoIterationChangesResponse/Entry/Item, AdoEditPullRequestRequest,
AdoAbandonPullRequestRequest), enum AdoMergeStrategy, AdoConstants
(MaxPullRequestDescriptionLength = 4000), AdoEvidenceFloorOutcome enum,
AdoEvidenceFloorRead + AdoPullRequestChangedFile records. Register all
new wire types in PolyphonyJsonContext for AOT.

Internal helpers in AdoClient:
  - MergeStrategyToWire enum -> wire string translator.
  - NormalizeChangeType lowercases ADO's combined change-type strings.
  - ComposeFailureDetailAsync formats HTTP failure snippets uniformly.

Update production callers (3) to pass NoFastForward + false explicitly,
preserving ADR Rev 4 plan/MG behaviour. Update test mocks (10) to match
the new IAdoClient signature plus stub the four new methods. Extend
AdoClientCompletePullRequestTests to cover Squash+delete=true and the
remaining strategy translators. New AdoClientPullRequestExtensionsTests
file covers happy-path + 404 + 5xx + cancellation + arg-validation for
each of the four new methods.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Phase 2+3: ADO-aware PlanObserver + state next-ready

Replaces the GitHub-only `slug` resolution path inside `PlanObserver`
and `state next-ready` with the new `RepoIdentity` + `RepoIdentityResolver`
introduced in Phase 0. Key shape changes:

* `PlanObserver` ctor now takes `IAdoClient` + `RepoIdentityResolver`;
  every Observe* method has a new identity-aware overload that branches on
  `RepoIdentity` (GitHub vs ADO) before fetching PR metadata. Legacy slug
  overloads retained as wrappers for back-compat.
* New `GhPullRequestPollAdapter.FromAdo` maps `AdoPullRequestPollData` →
  `GhPullRequestPollData` so the platform-neutral mappers stay GH-shaped.
* `IAdoClient.ListPullRequestsAsync` extended with optional
  `string? sourceBranch` filter (server-side `searchCriteria.sourceRefName`).
* `StateCommands.NextReady` gains 4 override flags
  (`--platform/--organization/--project/--repository`), wraps body in a
  top-level try/catch so internal exceptions emit routable JSON with
  `status:"error"` (#417 fix), and threads `RepoIdentity?` through the
  observation/rollup chain.
* `PlanCommands` ctor extended with `IAdoClient` + `RepoIdentityResolver`;
  `RepoIdentityResolver` registered as singleton in DI.
* New `Polyphony.Tests.Stubs.ThrowingAdoClient` — for tests that don't
  exercise the ADO branch (separate namespace from existing
  `TestHelpers` static class to avoid resolution clash).

All 3590 tests pass. Phases 4-11 still to come.

Refs #414, #415, #417

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Phase 4: ADO-aware plan detect-state

- DetectState verb gains --platform/--organization/--project/--repository
  override flags that the launcher already threads through workflow.input.*.
- Replace TryResolveSlugAsync with TryResolveRepoIdentityAsync; consume the
  RepoIdentity variant for GetLatestPlanPrAsync + GetPlanPrPollAsync.
- CheckChildrenForParentChangeRequestsAsync takes RepoIdentity and branches
  on variant inside the per-child loop: GitHub uses gh.ListPullRequestsAsync
  + gh.GetPullRequestPollDataAsync, ADO uses ado.ListPullRequestsAsync
  (sourceBranch filter) + ado.GetPullRequestPollDataAsync mapped through
  GhPullRequestPollAdapter.
- Error message updated: 'Could not resolve repo slug from origin remote' →
  'Could not resolve repo identity from origin remote (or supplied
  overrides)'. Operator-facing 'gh pr list/view' phrases dropped to
  'pr list/view' for platform neutrality.
- New PlanCommandsDetectStateAdoTests proves the wiring on three scenarios:
  not_started + awaiting_review on an ADO origin, plus a platform-override
  test that forces ADO when origin is GitHub.
- Updated DetectState_NoOriginRemote_EmitsError to assert on the new
  'identity' phrasing.
- Regenerated artifacts/verb-output-schemas.json after verb signature change.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Phase 5(a): PrCommands shared TryResolveSlugAsync uses RepoIdentityResolver

- PrCommands primary ctor gains a non-null RepoIdentityResolver dependency.
- TryResolveSlugAsync delegates to the resolver and asserts the variant is
  GitHubRepo. ADO origins (or any non-GitHub variant) return empty so the
  existing 'could not resolve slug' branch fires — defense in depth against
  a workflow router mis-routing an ADO repo to a GH-only verb. The legacy
  GitHubSlugRegex constant is retained for now (still consumed by other
  PlanCommands.* partials; Phase 6 removes it).
- Bulk-patched 22 PR test files (29 ctor calls) to thread the new resolver
  arg into existing test scaffolding. All 542 PR tests pass.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Phase 5(b): ADO sibling verbs for OpenImpl/MergeImpl/OpenEvidence

Adds three new routing-style verbs that mirror their GitHub equivalents:

- pr open-impl-ado: creates impl PRs via IAdoClient.CreatePullRequestAsync; head=impl/{root}-{item}, base=mg/{root}_{mg_path}.

- pr merge-impl-ado: completes impl PRs via IAdoClient.CompletePullRequestAsync with mergeStrategy=Squash + deleteSourceBranch=true (per ADR — impl branches are single-use, micro-history is squashed).

- pr open-evidence-ado: creates evidence PRs; orphan apex routes to evidence/{wi}->main, sub-item routes to evidence/{apex}-{wi}->feature/{apex}.

Each result model carries the org/project/repo triple plus a composite RepoSlug for cross-platform parity, and the categorical ErrorCode envelope used by the existing *Ado verbs (invalid_argument, missing_head_branch, missing_base_branch, pr_not_found, pr_state_invalid, stale_head, missing_merge_commit, ado_complete_failed, no_pat, ado_timeout, ado_failed).

Smoke tests (35 total): RequiredInput halt, invalid_argument, ado-not-configured, missing branches, happy-path PR create/merge, already-merged short-circuit, stale_head from --match-head-commit and from ADO 409 response, pr_state_invalid for CLOSED, no_pat from 401/InvalidOperationException.

Verb-output schema artifact regenerated. Full Polyphony.Tests suite green (3631 passed).

Refs #414. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* ADO branches for the three single-bodied PR-side verbs (Phase 5c)

Three verbs that previously assumed GitHub-only repo identity now branch
on RepoIdentity variant and dispatch to IGhClient or IAdoClient
accordingly. Workflows can thread the canonical
--platform/--organization/--project/--repository-override flags through
each call; backward-compat with the legacy --repo github-slug shortcut
is preserved on check-evidence-floor.

* pr check-evidence-floor: full ADO branch via
  IAdoClient.GetPullRequestEvidenceFloorAsync; outcome enum maps cleanly
  to the existing PrCheckEvidenceFloorResult envelope.
* pr validate-plan-diff: ADO branch via IAdoClient.GetPullRequestPollData
  + GetPullRequestFiles; RepoSlug field renders as
  '{org}/{project}/{repo}' for ADO. HTTP 404 from poll/files maps to
  pr_not_found; auth failures map to internal_error.
* pr assert-impl-pr-coverage: this verb is platform-neutral (compares
  local git refs only) — added the four override flags as zero-cost
  forward-compat so cross-platform workflows can pass the same vocabulary
  to every PR-side step without conditional argument lists.

Tests: 13 new ADO-branch smoke tests across two files, all using a
private nested FakeAdoClient (avoids shared-stub coupling); existing
GitHub-branch tests still pass unchanged. Verb-output-schemas.json
regenerated.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* phase 6 part 1: PullRequestReader helper + 4 small PlanCommands verbs ADO-aware

Introduces Sdlc/Observers/PullRequestReader as a single platform-dispatch
shim wrapping IGhClient + IAdoClient on a resolved RepoIdentity. ADO poll
data projects through GhPullRequestPollAdapter.FromAdo so downstream
consumers continue to read GhPullRequestPollData regardless of platform.

Refactors four PlanCommands verbs to take --platform/--organization/--project
/--repository-override flags and dispatch through the reader:
  - ExtractRenegotiationFlag
  - ValidateScope (also uses reader.GetChangedFilesAsync for files endpoint)
  - ClassifyStaleDescendants
  - Status (URL builder + 3-pass list both use reader)

No behaviour change for the github path. ADO path is wired but not yet
covered by smoke tests (Phase 6 final commit will add them).

Refs #414 #415

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Phase 6: ADO-aware Rebase/Recreate/ExtractParentPatch verbs

- RebaseStaleDescendant + RecreateStaleDescendant: resolve RepoIdentity
  via RepoIdentityResolver, accept --platform/--organization/--project/
  --repository overrides, route gh.* calls through PullRequestReader.
- RecreateStaleDescendant.TryFindFreshReplacementPrAsync replaces gh's
  server-side Base filter with an in-loop BaseRefName check (ADO REST
  has no equivalent).
- ExtractParentPatch (BLOCKER #4): now accepts ADO PR URLs (dev.azure.com
  + visualstudio.com legacy) and ADO --pr-number+identity pair; replaces
  'gh pr diff' with local-git diff (git fetch + 3-dot diff between
  origin/{baseRef}...origin/{headRef}) so rename/delete/binary edges are
  handled correctly without needing an ADO unified-diff REST composer.

Build green; tests pending in next commit.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Phase 7: ADO-aware BranchCommands + StatusCommand; PullRequestReader.ListByHeadAsync nullable head; resolver back-compat for bare --repo slug

Switches BranchCommands.LoadTree, BranchCommands.Route, and StatusCommand off
the GitHub-only slug regex onto RepoIdentityResolver + PullRequestReader.
Adds the standard 4-flag platform override surface (--platform/--organization/
--project/--repository) on every site.

Also:
- PullRequestReader.ListByHeadAsync now accepts a nullable headBranch (null
  skips the filter) so the BranchCommands callers can list all merged PRs
  without a per-branch sweep.
- RepoIdentityResolver back-compat: when no --platform override is supplied
  but --repository is in 'owner/name' shape, infer GitHubRepo. Restores the
  pre-refactor behavior for verbs that accept just --repo as a slug.
- ExtractParentPatch: keep the original (prUrl, rootId, parentItemId,
  diffSizeLimitBytes) signature to preserve positional callers + sentinel-halt
  test theory. Parses both github.com and dev.azure.com PR URLs (Phase 6
  ADO parity is preserved). GitHub leg uses gh.GetPullRequestDiffAsync;
  ADO leg uses local-git 3-dot diff (BLOCKER #4 mitigation).
- Test ctor cascade across 25 test files for the new RepoIdentityResolver +
  PullRequestReader dependencies.

Tests: 3645 pass / 4 skip / 1 known-flaky (parallel pwsh startup).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Phase 8a: thread platform overrides through slug-resolving workflow steps

Forwards workflow.input.{platform,organization,project,repository} as
--platform/--organization/--project/--repository[-override] flags into
every script step that invokes a slug-resolving polyphony verb. With
these in place, the resolver picks up the launcher-known context first
and only falls back to origin URL parsing as a last resort - which is
the path that fails on dev.azure.com/* origins (#415).

Sites threaded:
- plan-level.yaml: state_detector, validate_scope, extract_renegotiation_flag
- apex-driver.yaml: preflight_apex_state + inline pwsh state next-ready
- cascade-remedy.yaml: declared platform inputs + classify + remedy_group for_each
- remedy-stale-descendant.yaml: declared platform inputs + rebase_attempt + recreate_attempt
- implement-merge-group.yaml: assert_impl_pr_coverage
- actionable.yaml: evidence_floor_check

Regenerates tests/lint/fixtures/verb-output-schemas.json from
artifacts/, preserving the curated branch check-deps work-item required:true
field per the lint-fixtures memory (PR #266).

Platform router additions for OpenImplPr/MergeImplPr/OpenEvidencePr (which
lack platform params and need github/ado siblings routed at workflow level)
are deferred to Phase 8b.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Phase 9: launcher reruns preflight in destination worktree (#421)

After Phase 8 (assert-clean) but before metadata derivation, the
launcher now runs polyphony state preflight --work-item from inside
the per-apex worktree. Surfaces worktree-specific issues (missing twig
db, ADO credentials not visible, etc.) before handing off to conductor.

Soft-fail design - prints a yellow WARNING with the failed checks and
continues. Hard-blocking layout checks already run earlier (Phase 2
bare-repo + Phase 8 assert-clean); preflight is the wider sanity net.

Closes #421.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Phase 8b: unify open-impl-pr/merge-impl-pr/open-evidence-pr to be platform-aware

Three PR-mutation verbs that were silently github-only now accept
--platform/--organization/--project/--repository overrides and dispatch
to the existing *Ado siblings on AdoRepo identity. GitHub branch unchanged.

Implementation:
- Extracted OpenEvidenceAdoCoreAsync/OpenImplAdoCoreAsync helpers from the
  legacy *Ado verb bodies (record-struct outcomes); the legacy verbs delegate
  and emit their original ADO-only envelope.
- The *Pr verbs resolve identity via RepoIdentityResolver, branch on variant,
  and emit a unified envelope (PrOpenEvidenceResult / PrOpenImplResult)
  extended with optional Organization/Project/Repository/RepoSlug fields.
- MergeImplPr uses a stdout-capture bridge to MergeImplAdo (avoiding a
  ~280-line refactor in this PR; documented in code as future work).

Workflow YAMLs threaded:
- implement-merge-group.yaml: impl_pr_open and impl_pr_merge now forward
  workflow.input.platform/organization/project/repository.
- actionable.yaml: open_evidence_pr forwards the same.

Tests: full Pester suite green (209/209). .NET suite green (3645/3650;
1 transient WaveIntegratorScriptTests parallel-test fluke that passes on
re-run; 4 skipped pre-existing).

Schema: artifacts/verb-output-schemas.json + tests/lint/fixtures/
verb-output-schemas.json regenerated; hand-curated 'branch check-deps
work-item required:true' override preserved.

Known follow-up: actionable.yaml merge_evidence_pr is still a bare
'gh pr merge' shell-out (will fail on ADO post-evidence-review). Far
enough downstream that operator can manually merge in ADO; tracked
separately.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* fix: friction-sweep — accurate twig version diagnostics + complete launcher install

Three small papercut fixes from the cloudvault-service-api onboarding
friction report:

#422 (LOW) — preflight reports stale twig_cli version (0.72.0 vs 0.77.3
on PATH): when polyphony.exe is colocated with an older twig.exe in
~/.twig/bin, Process.Start's Win32 CreateProcess search order picks the
colocated binary BEFORE PATH, even though the operator's shell sees a
newer twig from earlier on PATH (e.g. ~/.local/bin). Fix: resolve via
where.exe / which BEFORE invoking, call --version on the resolved
absolute path, and surface the resolved path in the preflight detail
string so operators can diagnose binary-resolution mismatches.

#418 (HIGH) — install.ps1 + publish-local.ps1 + release.yml all silently
omitted bootstrap-conductor.ps1 from the launcher set, so first-run
onboarding hit 'not recognized' on the very first ceremony command.
Add it to all three install paths plus a defensive post-download
size+presence check in install.ps1 that throws on partial downloads
(GitHub raw 502s have been observed leaving zero-byte files).

#425 (skill doc) — polyphony-runtime SKILL.md referenced a nonexistent
'twig list-workspaces' verb. Replaced with 'twig workspace' (the real
canonical sprint-items lister), which serves the same prereq-check
purpose.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

---------

Co-authored-by: Daniel Green <dangreen@microsoft.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
PolyphonyRequiem added a commit that referenced this pull request May 20, 2026
…494)

First pass of the workflow-comments cleanup sweep. Targets two narrow,
verifiable categories of staleness — duplicate-paste and "deferred /
shipped in this PR" framing that no longer reflects what is on main.

implement-merge-group.yaml:

  1. Header lines 1-10 contained a 5-line copy-paste duplicate of the
     opening summary. Removed lines 6-10.

  2. Item 4 of "Transitional notes (TODO follow-up PRs)" claimed the
     ADO leg uses a human-gate stub pending the ADO MG verbs. Verified
     stale: PR #427 (ADO support across polyphony, commit f9d4aef)
     wired the real pr open-mg-ado and pr merge-mg-ado verbs into
     this same workflow at lines ~2191/2209/2253. Removed item 4.

  3. Item 3 (lock + manifest delegation to apex) is the permanent
     contract that lets parallel-MG execution work — not transitional
     scaffolding. Promoted it out of "Transitional notes" into a
     "Permanent design contract" section so future readers don't try
     to "complete" it.

  Items 1-2 (PG-N legacy tag dependency in branch next-impl / route /
  close-scope) are kept — verified still valid against
  src/Polyphony/Commands/BranchCommands.NextImpl.cs (operator-facing
  --pg-name / --pg-number flags still required) and BranchCommands.cs
  (close-scope verb still in place).

actionable.yaml:

  Header read as if the workflow YAML itself were a Phase 6 PR scaffold
  — "this PR", "Wiring landed in this PR (Phase 6 PR #5)", "PR #5 will
  wire in facet-profile context...". From main, "this PR" is ambiguous
  and the forward references are a snapshot of an in-flight stack.

  Reframed the header as a "History" log + a current-state description:
    - Phase 6 PR #4 (#136) scaffolded the executor router.
    - Phase 6 PR #5 (#142) wired facet-profile composition.
    - Phase 6 PR #7 (#139) added the evidence floor check.
    - Phase 6 PR #8 (deferred): full evidence_reviewer rubric.

  Verified each shipped commit via git log on the actionable.yaml path.
  PR #8 is the only one still pending; the placeholder reviewer rubric
  + TODO(p6-pr8) marker at �vidence_reviewer are unchanged.

  Workflow description: updated to drop the "Phase 6 PR #5 wires"
  framing in favour of the present-tense state.

Verification:
  - All 12 .conductor/registry/tests/lint-*.ps1 PASS
    (lint-actionable.ps1 explicitly checks "deferred-wiring TODOs
    present, shipped TODOs removed" — its rule #11/#11b for PR #5/#7
    "TODO MUST be absent" still holds; its rule #10 for PR #8 deferred
    marker still holds because we kept the in-line TODO(p6-pr8))
  - 513/513 .conductor/registry/tests Pester cases PASS

Co-authored-by: Daniel Green <dangreen@microsoft.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants