Skip to content

Working PR for ADR-296: Concurrent development - #78

Merged
jodavis merged 11 commits into
mainfrom
feature/ADR-296-concurrency
Jul 24, 2026
Merged

Working PR for ADR-296: Concurrent development#78
jodavis merged 11 commits into
mainfrom
feature/ADR-296-concurrency

Conversation

@jodavis

@jodavis jodavis commented Jul 21, 2026

Copy link
Copy Markdown
Owner

Working PR for ADR-296: Concurrent development

jodavis-claude and others added 5 commits July 14, 2026 20:13
…rip (#63)

* ADR-335: PipelineContext frontmatter round-trip — preserve unrecognized keys

save()/load() now read/write the full YAML frontmatter block as a dict via a new
extra_frontmatter field, so unrecognized keys (working_branch, base_branch,
parent_work_item, and future fields) survive every dev_team.py invocation
unchanged instead of being silently dropped. Handles both scalar and
multi-line list-valued unknown keys.

* ADR-335: implement PipelineContext via TDD (save()/load() have full coverage — all known frontmatter fields, extra_frontmatter (scalar+list), every known body section (including Project Configuration ordering and numbered Fix sections), the write-only Logs section, and started/last_updated parse-failure fallback (missing key and malformed value); 23/23 tests passing)

* ADR-335: wire dev_team.py to import PipelineContext from its new module

Removes the now-dead inline PipelineContext class and
KNOWN_FRONTMATTER_KEYS set from dev_team.py; dev_team.py now imports
PipelineContext from pipeline_context.py so existing `from dev_team
import PipelineContext` call sites keep working unchanged. Kept
dev_team.py's own _parse_frontmatter/_parse_sections helpers in place
since they're still used by unrelated code.

Also fixes a bug in pipeline_context.py's load(): its hand-rolled
frontmatter parser treated any empty-value scalar key (e.g.
`spec_path: `) as the start of a YAML list, returning [] instead of
"". This wasn't caught by the component's own tests (which only
round-tripped non-empty scalar values) but broke test_dev_team.py
once dev_team.py started using the shared implementation. Added a
red test demonstrating the round-trip failure, then fixed the parser
to only convert to a list once an actual '- ' item line follows,
matching dev_team.py's existing _parse_frontmatter semantics.

* ADR-335: derive KNOWN_FRONTMATTER_KEYS from PipelineContext field metadata

Addresses PR #63 review feedback worried about the hand-maintained
frontmatter-key list drifting as fields are added. Tags each
frontmatter-backed field with metadata={"frontmatter": True} inline in
the dataclass definition, then derives KNOWN_FRONTMATTER_KEYS via
dataclasses.fields() instead of a separately maintained set literal.
Adding a new frontmatter field now only requires touching its field()
call, not a second list elsewhere in the module. Verified the derived
set is identical to the prior hand-written one; all 135 existing tests
(pipeline_context + dev_team) stay green.

* ADR-335: restore save() parent directory creation

PipelineContext.save() no longer called path.parent.mkdir(parents=True,
exist_ok=True) before write_text() after the TDD rebuild, reintroducing an
unhandled FileNotFoundError when saving to a path whose parent doesn't
already exist. This was never test-covered before or after the rebuild
(every prior test wrote to tmp_path, which already exists). Restored the
mkdir call and added
test_save_to_path_with_missing_parent_directory_creates_it_and_writes_file
to pin down the behavior going forward.

* ADR-335: restore last_updated bump on save()

PipelineContext.save() no longer set self.last_updated =
datetime.datetime.now() at the top of the method after the TDD rebuild,
so last_updated stayed frozen at construction time instead of tracking
"last saved" as it did before the rebuild. No test pinned this down in
either version. Restored the bump and added
test_save_updates_last_updated_to_current_time_on_each_call to cover it.

* ADR-335: restore save() markdown heading

Cosmetic fix from PR #63 review: the pre-rebuild save() wrote a
"# {work_item_id} Dev Team Context" heading right after the frontmatter
block. Nothing parses it back (load() never reads it), but dev_team.py's
fresh-context branch creates a file directly through PipelineContext.save()
without going through init-context-file.py's template, so a context file
created that way was missing the heading after the rebuild. Restored the
heading and added test_save_writes_work_item_id_heading_after_frontmatter.

* ADR-335: Remove stale PipelineContext comment in dev_team.py

Remove the inaccurate comment above _parse_sections claiming
PipelineContext is "re-exported here for backward compatibility" --
PipelineContext was never exported from dev_team.py before the
extraction, so there is no such compatibility concern. The "Pipeline
context (serializable state)" section divider is removed too since
PipelineContext no longer lives in this file at all, and the only
function under that header (_parse_sections) is an unrelated
body-parsing helper used by dev_team.py's own Step-class logic.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011oezrcK3g7maGFvxDkXALv

* ADR-335: Remove PipelineContext round-trip tests duplicated in test_pipeline_context.py

Delete two test classes from test_dev_team.py whose entire purpose is
exercising PipelineContext's own save()/load() persistence, now fully
covered by test_pipeline_context.py:

- TestExtraFrontmatterRoundTrip: both its scalar/list-valued unknown-key
  round-trip test and its prepopulated extra_frontmatter test are
  byte-for-byte duplicated by TestPipelineContextExtraFrontmatter in
  test_pipeline_context.py.
- TestTroubleshooterInput: its default-value, non-empty round-trip, and
  empty-string round-trip assertions are all covered generically by
  TestPipelineContextDefaults and TestPipelineContextSaveLoadRoundTrip
  in test_pipeline_context.py (troubleshooter_input is included in both
  the known-scalar-fields and default-empty-string-fields parametrized
  coverage there).

Other classes that construct a PipelineContext (TestSignoffCycleCount,
TestReviewCycleCount, TestConsecutiveFailures, TestSignoffBuildResult,
TestReviewStepPrUrlExtraction, and the various Step-class test classes)
were left untouched -- each of those primarily exercises dev_team.py's
own logic (_apply_counter_updates, _handle_agent_failure/_success,
ReviewStep's removed PR-URL-section fallback, Step class instantiation)
and is not a genuine duplicate of test_pipeline_context.py's coverage.

Full regression: 105/105 test_dev_team.py tests and 27/27
test_pipeline_context.py tests pass (132 total, down from 138 before
this cleanup, reflecting the 6 removed duplicate tests).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011oezrcK3g7maGFvxDkXALv

---------

Co-authored-by: Claude on behlaf of jodavis <ElwoodMoves@hotmail.com>
Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
* ADR-307: Add Depends on task field to spec-task-breakdown

Step 1 authors a **Depends on:** line under each task's title using local
task-number references (or - none -), inferred best-effort the same way
titles/descriptions already are. Step 5 rewrites those references into real
task-work-item keys alongside the existing title rewrite, then validates the
whole Tasks section by invoking the new task_dependencies.py script and
surfacing any dangling-reference or cycle error to the user.

* ADR-307: implement Task dependency graph parser via TDD (parse_task_dependencies covers no-Depends-on-line, explicit — none —, single/multiple dependencies, dangling reference, and two/three-task cycles; all 8 tests passing)

* ADR-307: quote script path/arg in task_dependencies.py invocation for consistency with other SKILL.md script invocations

* ADR-307: anchor task_dependencies.py invocation to repo root via git rev-parse --show-toplevel instead of an implicit repo-root CWD

* ADR-307: anchor task_dependencies.py invocation to <skill-dir> instead of repo root

Reviewer (jodavis) noted the repo-root-based path assumes this skill is
always run inside the agent-plugins repo, which won't hold once it's used
as an installed plugin elsewhere. Step 5 now resolves task_dependencies.py
relative to spec-task-breakdown's own <skill-dir> instead of shelling out
to git rev-parse --show-toplevel.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TzenM31PAQmpj6ERNZjpTY

* ADR-307: link Depends on tracked work items via createIssueLink

Reviewer (jodavis) asked that, after validating the dependency tree,
step 5 also record each Depends on relationship in the tracker so it's
visible there too, not just in the spec. For Jira, this uses the Blocks
link type with the dependency as the blocker; documented as a new
createIssueLink operation on work-with-Jira-tasks. Skipped for a
provider whose adapter skill doesn't document a linking operation, or
when no tracker is configured, matching the rest of this skill's
no-tracker fallback pattern.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TzenM31PAQmpj6ERNZjpTY

---------

Co-authored-by: Claude on behlaf of jodavis <ElwoodMoves@hotmail.com>
Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
…lver (#70)

* ADR-308: implement Task readiness checker via TDD (dependency_status covers no-context-file (not_started), state=done, state=failed, pr_url-implies-ready with non-terminal state, and the in_progress fallback; is_task_eligible covers empty dependency_ids, single-dependency ready/in_progress/not_started, all-done (2+), all-but-one-done with ready-branch substitution, 2+ short of done (waiting), and a failed dependency short-circuiting to blocked — all 13 tests green, every branch of both functions exercised)

* ADR-308: extend ensure-working-branch with dependency-aware base-branch resolution and worktree-freshness check

- ensure-working-branch gains a new first action (before all other steps): the worktree-freshness
  hard-stop check (git stash list / git status --short both must be empty), guarding against the
  confirmed upstream isolation:"worktree" collision bug.
- New sub-step 4b (after the existing spec-file lookup) reads this task's own Depends on: entries
  via task_dependencies.py and calls task_readiness.py's is_task_eligible via a new CLI wrapper;
  uses the returned base_branch when eligible with a real branch, falls through to the unchanged
  feature-branch lookup when eligible with no override or no dependencies, and hard-stops with a
  clear error for waiting/blocked (resolving the brief's ambiguity #2 conservatively: never
  silently start a working branch without its actual dependency).
- Adds task_readiness.py's missing main() CLI wrapper (JSON on stdout, Error: ... on stderr) so
  the prose skill can invoke it via Bash, plus one narrow integration test for the primary
  happy-path CLI scenario.
- All existing steps renumbered (3a-3e -> 4a,4c-4f) with only additive content otherwise.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TzenM31PAQmpj6ERNZjpTY

* ADR-308: add missing python prefix to task_dependencies.py invocation in ensure-working-branch/SKILL.md 4b.1

Sub-step 4b.1 invoked task_dependencies.py as a bare path via Bash; since the
script is not chmod +x, this fails with Permission denied (exit 126). Adding
the python interpreter prefix, matching every other SKILL.md's invocation
convention, fixes this (verified: python3 <path> succeeds against a
non-executable file). Addresses PR #70 review thread 3590232708.

* ADR-308: add missing python prefix to task_readiness.py invocation in ensure-working-branch/SKILL.md 4b.3

Sub-step 4b.3 invoked task_readiness.py as a bare path via Bash; since the
script is not chmod +x, this fails with Permission denied (exit 126). Adding
the python interpreter prefix, matching every other SKILL.md's invocation
convention, fixes this (verified: python3 <path> succeeds against a
non-executable file, printing the expected JSON). Addresses PR #70 review
thread 3590232714.

* ADR-308: document non-zero-exit error path for 4b's script invocations

Sub-step 4b.1/4b.3 described only the success path (JSON on stdout) and never
said what to do if task_dependencies.py or task_readiness.py exits non-zero
(both print a clear 'Error: ...' message to stderr on failure, per their
main() implementations). Added the 'surface the error and stop' instruction
to both sub-steps, matching the existing convention elsewhere in the repo.
Addresses PR #70 review thread 3590232713.

* ADR-308: extract _dependency_status_and_context() to dedupe is_task_eligible's repeated dependency lookup

is_task_eligible's single-still-open-dependency branch re-derived get_repo_slug()
and reloaded the winning dependency's context file that dependency_status()
(via the new shared helper) had already loaded moments earlier in the same
call. Extracted _dependency_status_and_context() so the PipelineContext is
loaded once per dependency and reused for the working_branch lookup;
dependency_status() now delegates to it and discards the context. No public
signature or behavior changed — this is a pure extract-method refactor.

Verification note: the existing 14 tests in test_task_readiness.py were
manually traced case-by-case against this refactor (all return the same
tuples for the same inputs); an automated pytest run could not be completed
in this session due to a Bash-tool-classifier outage that also blocked
'python3 -c ...'/'python3 -m ...' invocations throughout this session (git/gh
commands were unaffected). This should be re-run before merge to confirm.
Addresses PR #70 review thread 3590232718 (P4/non-blocking).

---------

Co-authored-by: Claude on behlaf of jodavis <ElwoodMoves@hotmail.com>
Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
…96-concurrency

# Conflicts:
#	plugins/dev-team/skills/ensure-working-branch/SKILL.md
#	plugins/dev-team/skills/workflow-orchestrate/scripts/dev_team.py
Merge origin/main into feature/ADR-296-concurrency
@github-actions

Copy link
Copy Markdown

build-and-test: Python test results

Status: ✅ Passed

Test log

jodavis-claude and others added 6 commits July 21, 2026 20:28
`rebase_onto` fully covered — clean rebase (returns "rebased", verified force-pushed-with-lease reaches origin), genuine conflict (returns "conflict", rebase-in-progress marker left in place, no push to origin), fetch making `origin/<new_base>` the rebase source of truth over a stale local ref, self-checkout of `working_branch` when the worktree starts on a different branch, and non-conflict git failures (fetch) propagating as `CalledProcessError` rather than being swallowed. No logging, no CLI wrapper, and no additional invalid-input branches in scope for this component.

Co-authored-by: Claude on behlaf of jodavis <ElwoodMoves@hotmail.com>
… /implement multi-task dispatch (#81)

* ADR-310: implement Concurrent scheduler (concurrent_schedule.py) via TDD (closure computation for "up to" targets, explicit-list upfront validation, repo-wide concurrency-cap enforcement across multiple target files, waiting/complete/blocked status computation)

* ADR-310: add concurrent-orchestrate skill (thin spawn loop wiring concurrent_schedule.py to isolated workflow-orchestrate runs)

* ADR-310: extend /implement argument parser to recognize an "up to" target and an explicit multi-task list, dispatching either to concurrent-orchestrate

* ADR-310: add concurrency.max-parallel-tasks to project configuration schema (default 3), documented alongside testing.test-file-patterns

* ADR-310: move concurrent_schedule.py into concurrent-orchestrate's own scripts/ folder

Addresses PR #81 review comment: concurrent-orchestrate shouldn't reach into
workflow-orchestrate's scripts/ folder for its own script. Moved
concurrent_schedule.py and test_concurrent_schedule.py into
plugins/dev-team/skills/concurrent-orchestrate/scripts/, updated the script's
internal sys.path insertion to point at the sibling workflow-orchestrate/scripts/
directory for its shared-module imports (dev_team, get_context_path,
task_dependencies, task_readiness), and updated concurrent-orchestrate/SKILL.md's
invocation path to <skill-dir>/scripts/concurrent_schedule.py.

* ADR-310: add explicit stop after /implement's single-task dispatch step

Addresses PR #81 review comment: without an explicit stop, the agent could
fall through from step 2 (single-task workflow-orchestrate dispatch) into
step 3 (concurrent-orchestrate) and try to invoke a list of tasks it doesn't
have. Mirrors the existing 'Then stop.' already used in step 1's no-match
branch.

* ADR-310: move concurrency.max-parallel-tasks documentation to concurrent-orchestrate

Addresses PR #81 review comment: the general-purpose get-project-configuration
skill doc shouldn't make every agent that reads project configuration think
about task concurrency. Removed the concurrency.max-parallel-tasks subsection
from get-project-configuration/SKILL.md and added it to concurrent-orchestrate/
SKILL.md instead, next to concurrent_schedule.py where the key is actually
consumed. The schema/default itself stays unchanged in
get-project-configuration/assets/default-config.yaml, since merge_config.py's
structural merge still needs it there.

* ADR-310: widen concurrent_schedule.py main()'s except clause to match its docstring

Addresses non-blocking self-review comment: main()'s docstring promises a
clean 'Error: ...' message on 'any other failure', but the except tuple didn't
cover json.JSONDecodeError (a corrupted concurrent-*.json data file) or
subprocess.TimeoutExpired (a merge_config.py subprocess timeout inside
_max_parallel_tasks) — either would still exit non-zero but with a raw
traceback. Added both to the except tuple. Added two new failing-first unit
tests (test_main_corrupted_data_file_prints_clean_error_and_exits_nonzero,
test_main_merge_config_subprocess_timeout_prints_clean_error_and_exits_nonzero)
that exercise main() directly (not via subprocess) with monkeypatched
sys.argv, confirmed red before the fix, green after.

---------

Co-authored-by: Claude on behlaf of jodavis <ElwoodMoves@hotmail.com>
* ADR-311: implement PR event detector via TDD (`detect_pr_events` fully covered — all five event types (review_comment, ci_failure, base_updated, dependency_merged, task_merged) firing individually and in combination, no-refire guards for review_comment/ci_failure/dependency_merged, the quiet-call no-op case (with unchanged saved context), the stacked out-of-order dependency_merged branch-reporting case, early-return guards (no context file / no pr_url), and all boundary/error inputs from the exit criteria (non-JSON checks output, missing base_branch, missing/unparseable spec_path, dependency-less task, dependency with no context file or no pr_url, and a failed git ls-remote lookup))

* ADR-311: implement watch_pr_poll.py via TDD (`watch_pr_poll.py` fully covered — `poll()`'s immediate-fire branch (single and multiple simultaneous events, no sleep), the loop-until-fired branch (sleeps between empty checks, exits before `max_seconds`), the `max_seconds`-elapsed → `"no_change"` branch, and the `max_seconds=0` boundary (zero sleeps); `main()`'s CLI wrapper for both success shapes (fired-event-list and `"no_change"` JSON) and the exception → `Error: ...`/exit-1 path. All 8 tests green.)

* ADR-311: guard unhandled gh JSON parse failures in detect_pr_events

Address PR #82 review comment: _pr_state_and_base()'s `gh pr view` call and
the review-comments `gh api` call both parsed result.stdout with a bare
json.loads(...), unlike the ci_failure check right below which already wraps
the same class of gh parse in try/except json.JSONDecodeError. A transient gh
failure (rate limit, network blip, auth hiccup) raised an unhandled
JSONDecodeError out of detect_pr_events, which would kill watch_pr_poll.py's
whole bounded polling window instead of skipping that one check and retrying
next iteration. Wrap both parses the same way the CI-checks call already is,
so a flaky gh call degrades to "no signal this round" rather than a crash.

Added tests: review comments and gh pr view (both the task's own call and a
dependency's) returning non-JSON no longer raise, and the corresponding
signal is simply skipped for that call.

* ADR-311: raise a clear ValueError on a malformed pr_url

Address PR #82 review comment: `_PR_URL_RE.match(ctx.pr_url).groups()` raised
a confusing `AttributeError: 'NoneType' object has no attribute 'groups'`
when ctx.pr_url didn't match the expected
`https://github.com/<owner>/<repo>/pull/<number>` shape, instead of a clear
diagnostic naming the offending value. Extracted the match into a
`_parse_pr_url()` helper that raises `ValueError(f"pr_url does not match
expected GitHub PR URL format: {pr_url!r}")` on no match, so detect_pr_events
still fails loudly (this isn't a transient gh flake, unlike the JSON parse
issue fixed in the previous commit) but with a message a human can act on
when this runs unattended inside watch_pr_poll.py's polling loop.

Added a test asserting a malformed pr_url raises ValueError naming the URL.

* ADR-311: close signoff validation gap in no-refire test coverage

Sign-off validation flagged one exit criterion as only partially covered:
review_comment, ci_failure, and dependency_merged each had an explicit
already-seen/no-refire test, but base_updated and task_merged did not.

- Added a base_sha_unchanged_no_refire case to
  TestDetectPrEventsBaseUpdated: asserts base_updated does not fire when
  git ls-remote reports the same sha already recorded in base_branch_sha.
- Added TestDetectPrEventsTaskMergedCalledAgain: documents that
  task_merged fires on every call for as long as the PR stays merged,
  since (per the task brief's stated context-file fields) there is no
  persisted "already reported" field for it, unlike the other four
  events. This is an accepted design limitation, not a defect — adding
  a new field would be scope creep beyond the brief's named
  extra_frontmatter fields (base_branch_sha, last_seen_review_comment_id,
  last_seen_ci_conclusion, base_branch).

Both new tests pass against the existing implementation unmodified —
pr_event_detector.py already behaves correctly for both cases; this
commit only closes the test-coverage gap the signoff review found.

Full regression: 199 passed (197 pre-existing + 2 new).

---------

Co-authored-by: Claude on behlaf of jodavis <ElwoodMoves@hotmail.com>
…rness (#83)

resolve-rebase-conflict is agent-skill prose (Testable per component-taxonomy,
not a pure function): given a rebase already left in progress with conflicts
and the task's own brief/spec context, it reads each conflicted file's hunks,
resolves only what the context makes unambiguous, stages them, and drives
git rebase --continue to completion. Reports "resolved" or "unresolved";
never runs git push or git rebase --abort itself (the caller,
dev-team:watch-pr, does both as appropriate).

Verification follows the taxonomy's "whatever mechanism actually fits" for
prose Testable components (same precedent as ADR-319's harvest-playbook): a
scripted fixture-scenario harness at
plugins/dev-team/fixtures/resolve-rebase-conflict/ builds three real git
repos with a genuine rebase conflict already in progress (via
rebase_mechanic.rebase_onto(), following test_rebase_mechanic.py's
bare-origin-plus-clone construction pattern):

- single-file: one CHANGELOG.md hunk, resolvable from task context
- multi-file: CHANGELOG.md + a JSON settings file conflict together (same
  commit), both resolvable from task context
- unresolvable: a numeric setting both branches changed to different,
  equally-plausible values with no stated target — must decline to guess

build_fixture.py builds each scenario; test_build_fixture.py (18 tests, all
passing) confirms each one produces exactly the conflict it claims to.
RUN.md documents the on-demand (non-CI) run/grade procedure for the
judgment-shaped part of this check.

Dry-ran the finished skill against all three scenarios via a fresh subagent
blind to this authoring context: single-file and multi-file both correctly
merged both sides' content and reported "resolved" with a clean working
tree; unresolvable correctly stopped without editing anything and reported
"unresolved", citing the missing target value as the reason it couldn't
proceed.

No production Python file changes needed — rebase_mechanic.py is untouched
per the task brief.

Co-authored-by: Claude on behlaf of jodavis <ElwoodMoves@hotmail.com>
… command (#84)

* ADR-313: add dev-team:watch-pr, the long-lived post-hand-off PR monitor

Orchestrator-tier skill wiring together already-built components (watch_pr_poll.py,
pr_event_detector.py, rebase_mechanic.py from ADR-311/ADR-309, resolve-rebase-conflict
from ADR-312, fix-pr) into the entire post-hand-off lifecycle from hand-off to merge:
worktree-freshness check, checkout, implement-phase worktree removal, then a poll loop
reacting to review comments/CI failures (spawn fix-pr), base moves/dependency merges
(run the rebase mechanic, re-targeting base_branch first for dependency_merged), and
task_merged (remove own worktree, halt). On a rebase conflict, spawns the developer
agent to run resolve-rebase-conflict; resolved -> push --force-with-lease itself;
unresolved -> git rebase --abort then stop the agent (no AskUserQuestion fallback).

* ADR-313: add /watch-pr manual fallback command

Thin Wrapper command, same shape as implement.md's single-task dispatch branch: its whole
job is spawning dev-team:watch-pr via the Agent tool with isolation: "worktree", for when
concurrent-orchestrate's auto-start never happened (e.g. its own session was interrupted
before this task reached hand-off).

* ADR-313: extend concurrent-orchestrate to auto-start dev-team:watch-pr on hand-off

Step 1d's existing 'any spawned pipeline finishes' trigger now also runs a new step 1e:
for each task_id whose spawned workflow-orchestrate pipeline just finished successfully
(reached hand-off) and that doesn't already have a monitor, spawn dev-team:watch-pr as a
local background Agent, mirroring step 1c's own spawn pattern for workflow-orchestrate
itself. Tracks already-spawned task_ids in an in-session record (not persisted) to avoid
double-spawning. The concurrency cap is untouched -- it only ever counted active
workflow-orchestrate spawns, never this monitor.

* ADR-313: fix event-ordering bug in watch-pr's poll reaction (self-review)

task_merged now short-circuits every other event in the same pass (nothing else fired
alongside a merge is still actionable against an already-closed PR). Separately,
review_comment/ci_failure are no longer skipped when a rebase-related event in the same
pass also needs the rebase mechanic (or a conflict resolution) first -- pr_event_detector.py
marks those events 'seen' the instant it detects them, so deferring them to the next poll
would have silently dropped them instead. They're now always handled before returning to
the poll, after the rebase-related work (including any conflict resolution) concludes.

* ADR-313: watch-pr step 3 - stop and report if implement-phase worktree/branch removal fails

Addresses PR review comment on plugins/dev-team/skills/watch-pr/SKILL.md:113 - the removal
commands had no stated failure behavior, unlike every other command call in this skill.

* ADR-313: watch-pr step 4b - stop and report if own worktree/branch removal fails on task_merged

Addresses PR review comment on plugins/dev-team/skills/watch-pr/SKILL.md:159 - the success
report was unconditional even though it followed a removal that could fail, risking a failed
cleanup being reported as a clean halt.

* ADR-313: watch-pr argument-hint - match --work-item-id flag style used by both call sites

Addresses PR review comment on plugins/dev-team/skills/watch-pr/SKILL.md:10 - argument-hint
previously read <work-item-id> instead of the flag style both commands/watch-pr.md and
concurrent-orchestrate/SKILL.md actually use to invoke this skill.

* ADR-313: concurrent-orchestrate step 1e - define fallback when pr_url is empty

Addresses PR review comment on plugins/dev-team/skills/concurrent-orchestrate/SKILL.md:143 -
the pr_url sanity check had no defined behavior on failure, unlike this spec's consistent
never-silently-proceed posture elsewhere. Now skips spawning the monitor for that task_id,
reports the inconsistency in detail, and still records the task_id so a later poll doesn't
repeatedly re-report it.

---------

Co-authored-by: Claude on behlaf of jodavis <ElwoodMoves@hotmail.com>
* ADR-313: add dev-team:watch-pr, the long-lived post-hand-off PR monitor

Orchestrator-tier skill wiring together already-built components (watch_pr_poll.py,
pr_event_detector.py, rebase_mechanic.py from ADR-311/ADR-309, resolve-rebase-conflict
from ADR-312, fix-pr) into the entire post-hand-off lifecycle from hand-off to merge:
worktree-freshness check, checkout, implement-phase worktree removal, then a poll loop
reacting to review comments/CI failures (spawn fix-pr), base moves/dependency merges
(run the rebase mechanic, re-targeting base_branch first for dependency_merged), and
task_merged (remove own worktree, halt). On a rebase conflict, spawns the developer
agent to run resolve-rebase-conflict; resolved -> push --force-with-lease itself;
unresolved -> git rebase --abort then stop the agent (no AskUserQuestion fallback).

* ADR-313: add /watch-pr manual fallback command

Thin Wrapper command, same shape as implement.md's single-task dispatch branch: its whole
job is spawning dev-team:watch-pr via the Agent tool with isolation: "worktree", for when
concurrent-orchestrate's auto-start never happened (e.g. its own session was interrupted
before this task reached hand-off).

* ADR-313: extend concurrent-orchestrate to auto-start dev-team:watch-pr on hand-off

Step 1d's existing 'any spawned pipeline finishes' trigger now also runs a new step 1e:
for each task_id whose spawned workflow-orchestrate pipeline just finished successfully
(reached hand-off) and that doesn't already have a monitor, spawn dev-team:watch-pr as a
local background Agent, mirroring step 1c's own spawn pattern for workflow-orchestrate
itself. Tracks already-spawned task_ids in an in-session record (not persisted) to avoid
double-spawning. The concurrency cap is untouched -- it only ever counted active
workflow-orchestrate spawns, never this monitor.

* ADR-313: fix event-ordering bug in watch-pr's poll reaction (self-review)

task_merged now short-circuits every other event in the same pass (nothing else fired
alongside a merge is still actionable against an already-closed PR). Separately,
review_comment/ci_failure are no longer skipped when a rebase-related event in the same
pass also needs the rebase mechanic (or a conflict resolution) first -- pr_event_detector.py
marks those events 'seen' the instant it detects them, so deferring them to the next poll
would have silently dropped them instead. They're now always handled before returning to
the poll, after the rebase-related work (including any conflict resolution) concludes.

* ADR-313: watch-pr step 3 - stop and report if implement-phase worktree/branch removal fails

Addresses PR review comment on plugins/dev-team/skills/watch-pr/SKILL.md:113 - the removal
commands had no stated failure behavior, unlike every other command call in this skill.

* ADR-313: watch-pr step 4b - stop and report if own worktree/branch removal fails on task_merged

Addresses PR review comment on plugins/dev-team/skills/watch-pr/SKILL.md:159 - the success
report was unconditional even though it followed a removal that could fail, risking a failed
cleanup being reported as a clean halt.

* ADR-313: watch-pr argument-hint - match --work-item-id flag style used by both call sites

Addresses PR review comment on plugins/dev-team/skills/watch-pr/SKILL.md:10 - argument-hint
previously read <work-item-id> instead of the flag style both commands/watch-pr.md and
concurrent-orchestrate/SKILL.md actually use to invoke this skill.

* ADR-313: concurrent-orchestrate step 1e - define fallback when pr_url is empty

Addresses PR review comment on plugins/dev-team/skills/concurrent-orchestrate/SKILL.md:143 -
the pr_url sanity check had no defined behavior on failure, unlike this spec's consistent
never-silently-proceed posture elsewhere. Now skips spawning the monitor for that task_id,
reports the inconsistency in detail, and still records the task_id so a later poll doesn't
repeatedly re-report it.

* ADR-313: watch-pr - "already merged" bullet applies regardless of prior run

Addresses PR #84 review comment: whether or not there was a prior run of
this skill, if the task's PR already merged there is nothing to do.

* ADR-313: watch-pr - extract inlined rebase-mechanic invocation into a script

Addresses PR #84 review comment: step 4c's inline `python -c "..."` block
is replaced by a new CLI wrapper script, run_rebase_mechanic.py, alongside
rebase_mechanic.py in workflow-orchestrate/scripts/, with unit test
coverage for its success and error paths (rebase_onto itself is already
covered by test_rebase_mechanic.py).

* ADR-313: watch-pr - remove Todo list monitoring/mirroring support

Addresses PR #84 review comment: removed step 2's Monitor()+TodoWrite
mirroring of this session's own todo log (unreliable, complicated, and
not useful for an unattended background agent). Kept computing the todo
log path itself, since nested fix-pr/resolve-rebase-conflict spawns
(step 4b/5) still pass it through as workflow-worker's required
--todo-log argument; removing that mediated-spawn pattern entirely is
tracked separately as ADR-314 and not pre-empted here.

* ADR-313: watch-pr - document why a subagent is spawned and how it communicates

Addresses PR #84 review comment questioning the subagent spawn in
commands/watch-pr.md: confirmed this is intentional (unlike /implement's
single-task path, dev-team:watch-pr must own a guaranteed-fresh worktree
and blocks for the task's whole hand-off-to-merge lifecycle, so it cannot
run inline in the invoking session). Added a "Communicating outward"
section to watch-pr/SKILL.md making explicit the two channels this skill
already relies on for anything it needs to convey: fix-pr's PR-thread
replies for review/CI issues, and a detailed final message on any
hard-stop condition, surfaced via the harness's background-task
notification and resumable via SendMessage. commands/watch-pr.md now
references this section and explains the isolation/blocking rationale
for the spawn.

* ADR-314: remove todo-log/TodoWrite-mirroring plumbing

Removes the todo-log-tailing / TodoWrite-mirroring mechanism from
workflow-orchestrate and workflow-worker: it never surfaced well even for a
single task's pipeline, and had no clean extension to the concurrent
multi-task orchestrator this spec introduces.

- workflow-orchestrate/SKILL.md: drop the todo-log-path step and the
  Monitor/TaskStop-based tailing step, renumber remaining steps, and drop
  --todo-log from the spawn_agent prompt template.
- Delete workflow-orchestrate/scripts/get_todo_log_path.py (no dedicated
  test file existed).
- workflow-worker/SKILL.md: drop the log-redirection step and --todo-log
  argument, renumber remaining steps.
- Delete workflow-worker/scripts/append_todo_log.py (no dedicated test
  file existed).
- developer.md, researcher.md, reviewer.md: remove the "Task tracking"
  instruction to mirror updates via TodoWrite through the log file. The
  TodoWrite tool grant in each file's frontmatter is left in place since
  the exit criteria scope only the instruction text.
- watch-pr/SKILL.md: this ADR-313-added skill still depended on the exact
  plumbing being removed here (its own text explicitly deferred this
  cleanup to ADR-314); updated to drop get_todo_log_path.py from its
  script list, remove its own todo-log-path computation step, and drop
  --todo-log from both nested workflow-worker spawn prompts.

Verified via repo-wide grep that no todo_log/TaskStop/--todo-log/
get_todo_log_path/append_todo_log references remain, and the existing
workflow-orchestrate test suite (test_dev_team.py, 111 tests) still
passes unchanged.

---------

Co-authored-by: Claude on behlaf of jodavis <ElwoodMoves@hotmail.com>
@jodavis
jodavis marked this pull request as ready for review July 24, 2026 14:27
@jodavis
jodavis merged commit 2d97092 into main Jul 24, 2026
3 checks passed
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.

3 participants