Skip to content

refactor(wrappers): extract route logic into composite actions#172

Merged
norrietaylor merged 4 commits into
mainfrom
refactor/thin-wrappers
May 29, 2026
Merged

refactor(wrappers): extract route logic into composite actions#172
norrietaylor merged 4 commits into
mainfrom
refactor/thin-wrappers

Conversation

@norrietaylor
Copy link
Copy Markdown
Owner

Problem

Wrappers totalled 6,515 lines against ADR 0004's thin-wrapper intent. Two causes:

  • Triplication. sdd-execute-{haiku,sonnet,opus} were line-identical except TIER, the model:<tier> gate, the concurrency prefix, and the lock ref — ~1,900 lines of pure copy.
  • No shared-code mechanism for wrappers. shared/*.md are gh-aw prompt imports, unusable by hand-written wrappers; there were no composite actions. Every wrapper re-inlined its github-script decide block plus shared helpers (permission gate, parent-walk, tree-walk). The actions/github-script SHA pin appeared 21 times.

Fix

Move the heavy deterministic logic into 14 composite actions in .github/actions/, referenced cross-repo by pinned ref — the same mechanism ADR 0004 uses for locks and ADR 0002 for shared/ fragments. Wrappers keep triggers, concurrency, the permission gate, and the lock call (ADR 0004 §7: the route job stays the real gate). sdd-route-execute is shared by all three tiers via a tier input, eliminating the triplication.

Extraction is verbatim; the only edits are vars.*/TIER → action inputs. Secrets never enter an action — the auto-merge App token is minted in the wrapper and passed as github-token.

wrapper before after
sdd-dispatch 1199 506
sdd-execute-{haiku,sonnet,opus} 963 ×3 245 ×3
sdd-spec 712 192
sdd-monitor 491 138
sdd-triage 255 106
sdd-validate 186 108
sdd-review 100 77
sdd-pr-sanitize 228 68
sdd-triage-promote-ready 250 62
sdd-triage-dedupe-tasks 190 42
total 6,515 2,078

Other changes

  • scripts/quick-setup.sh rewrites .github/actions/*@<ref> alongside lock refs, or a --ref install would leave actions on @main while the lock is pinned.
  • ADR 0015 records the pattern, the uses:-can't-be-dynamic constraint, and the vars/secrets→inputs rule.

Verification

  • Parity: every extracted block proven byte-identical to its origin.
  • actionlint clean on all wrappers + hand-authored workflows; shellcheck clean; markdownlint clean (incl. ADR 0015).
  • Lock drift gate untouched — nothing changed under .github/workflows.
  • Installer ref-rewrite tested against a tag: all 26 lock+action refs pin the tag, 0 left on @main.

Notes

  • actionlint cannot lint composite action.yml (treats them as workflows), so no lint.yml glob change — actions are covered by YAML parse + the e2e run.
  • Not extracted: distillery-sync.yml (no route script) and dispatch's small matrix/label/comment scripts (tightly coupled to the job graph, low value).
  • An end-to-end run on a sandbox consumer (per-tier /execute, a CHANGES_REQUESTED auto-revise, a needs-human resume, a merged fast-path PR) is the remaining behavioral check before merge.

🤖 Generated with Claude Code

Wrappers totalled 6,515 lines against ADR 0004's thin-wrapper intent. The
sdd-execute trio was ~1,900 lines of pure copy; every wrapper re-inlined its
github-script decide block and shared helpers because there was no shared-code
mechanism for hand-written wrappers.

Move the heavy deterministic logic into composite actions hosted in
.github/actions/, referenced cross-repo by pinned ref like locks and shared
fragments. Wrappers keep triggers, concurrency, the permission gate, and the
lock call. Extraction is verbatim; only vars/TIER become action inputs.

- 14 composite actions; sdd-route-execute is shared by all three tiers via a
  tier input, eliminating the triplication
- wrappers: 6,515 -> 2,078 lines (sdd-execute 963 -> 245 each, dispatch
  1,199 -> 506)
- installer pins .github/actions refs alongside lock refs
- ADR 0015 records the pattern and the vars/secrets->inputs rule

actionlint, shellcheck, markdownlint clean; lock drift gate untouched;
extracted blocks proven byte-identical to their origins.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 29, 2026

Review Change Stack

📝 Walkthrough

Summary by CodeRabbit

Release Notes

  • Chores
    • Refactored GitHub Actions workflows to use reusable composite actions, improving code organization and maintainability.
    • Simplified workflow files by delegating complex logic to dedicated, reusable action components.
    • Updated workflow installer to pin composite action references for consistency.
    • Enhanced test scripts to validate routing across workflows and actions.

Walkthrough

Extracts routing, precondition, and utility logic into hosted composite GitHub Actions and updates wrapper workflows and installer tooling to delegate to those actions.

Changes

Refactor wrapper routing logic into composite actions

Layer / File(s) Summary
Routing decision actions
.github/actions/sdd-route-spec/action.yml, .github/actions/sdd-route-triage/action.yml, .github/actions/sdd-route-validate/action.yml, .github/actions/sdd-route-review/action.yml, .github/actions/sdd-route-dispatch/action.yml, .github/actions/sdd-route-execute/action.yml
New routing actions that parse webhook payloads/commands, check permissions and labels, and emit should_run and aw_context (plus specialized outputs like tracking_issue/trigger_kind) for downstream execution.
Dispatch compute
.github/actions/sdd-dispatch-compute/action.yml
Computes dispatch precondition and ready task matrix by walking tracking issue → units → tasks, resolving blockers (same-repo and cross-repo), detecting in-flight PRs, validating model:* labels, and emitting matrix and counts or refuse_early.
Execute routing, tier gating, and auto-revise cap
.github/actions/sdd-route-execute/action.yml, wrappers/sdd-execute-*.yml
Centralized execute routing for workflow_dispatch, /execute//revise comments, review-driven implicit revise, resume events, fast-path completion detection, model-tier ownership enforcement, and CHANGES_REQUESTED auto-revise iteration capping. Wrapper route jobs now call this action.
Spec fast-path and failure handoff
.github/actions/sdd-route-spec/action.yml, .github/actions/sdd-fastpath-approve/action.yml, .github/actions/sdd-spec-failure-handoff/action.yml, wrappers/sdd-spec.yml
sdd-route-spec routes spec commands and fast-path /approve; sdd-fastpath-approve finds plan comments, parses tier, dispatches tier executor, and updates lifecycle labels; sdd-spec-failure-handoff posts failure comments and applies needs-human for tracking issues.
Triage routing, promote-ready, and dedupe
.github/actions/sdd-route-triage/action.yml, .github/actions/sdd-triage-promote-ready/action.yml, .github/actions/sdd-triage-dedupe-tasks/action.yml, wrappers/sdd-triage*.yml
sdd-route-triage routes triage commands/merged arch PRs; sdd-triage-promote-ready promotes phase‑C tasks to sdd:ready when blockers are closed; sdd-triage-dedupe-tasks closes duplicate phase‑C tasks by validating earlier same-title siblings.
Monitor, auto-merge, and PR sanitize utilities
.github/actions/sdd-monitor/action.yml, .github/actions/sdd-auto-merge/action.yml, .github/actions/sdd-pr-sanitize/action.yml, wrappers/sdd-monitor.yml, wrappers/sdd-pr-sanitize.yml
sdd-monitor runs a scheduled backstop that posts /dispatch for eligible trackers; sdd-auto-merge attempts to arm auto-merge and falls back to direct squash merge when safe; sdd-pr-sanitize rewrites closing keywords and appends idempotent Closes lines and tracker comments.
Wrapper delegations
wrappers/*.yml
Twelve wrapper workflows replaced inline scripts with calls to the hosted composite actions, wiring tokens, app ids, and config variables as inputs while preserving outputs consumed by downstream jobs.
Docs & installer
decisions/0015-wrapper-logic-in-composite-actions.md, scripts/quick-setup.sh, scripts/test-command-table.py
ADR describing the hosting decision; quick-setup.sh updated to pin composite-action refs when installing wrappers; test script updated to treat composite actions as routing sources.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

  • norrietaylor/spectacles#97: Related dispatch ready-set computation and wrapper wiring changes now implemented as sdd-dispatch-compute and updated wrapper.
  • norrietaylor/spectacles#149: Related GraphQL parent-walk / reverse-lookup improvements for dispatch cascade resolution now centralized in sdd-route-dispatch.
  • norrietaylor/spectacles#156: Related monitor parent-walk and in-flight guard adjustments now implemented inside sdd-monitor.

Poem

"I hopped through workflows, tidy and spry,
Pulled scripts into actions under the sky.
Routers now steer, monitors hum,
Wrappers lean down — job done, hop, thrum! 🐰"

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately describes the main change: extracting route logic from wrappers into composite actions, which is the primary objective of this PR.
Description check ✅ Passed The description comprehensively details the problem (wrapper bloat and triplication), the solution (extracting to composite actions), impact metrics, verification steps, and remaining tasks.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch refactor/thin-wrappers

Comment @coderabbitai help to get the list of available commands and usage tips.

The command-table consistency check scanned only wrappers/*.yml for the
firstWord === '/<name>' routing idiom. ADR 0015 moved that idiom into the
composite actions, so the check reported every command as unrouted. Scan
.github/actions/*/action.yml alongside the wrappers.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In @.github/actions/sdd-fastpath-approve/action.yml:
- Around line 208-230: When moving lifecycle labels on the tracking issue, also
attempt to remove the alternative fast-path label 'sdd:fastpath-review' in the
same way you remove 'sdd:fastpath' so a stale label isn't left behind; update
the try/catch block(s) around github.rest.issues.removeLabel (the block that
currently targets 'sdd:fastpath') to attempt removal of both 'sdd:fastpath' and
'sdd:fastpath-review' (either two removeLabel calls each wrapped with their own
try/catch or a loop over the two names), and ensure error logging uses core.info
as done now; keep the subsequent github.rest.issues.addLabels call for
'sdd:in-progress' unchanged.

In @.github/actions/sdd-monitor/action.yml:
- Around line 27-28: Remove the duplicated "script: |" literal inside the with:
block so only a single "script: |" key defines the multiline script, and then
outdent all lines of the script body (reduce their indentation by two spaces) so
the script content is properly nested under the single script key; locate the
duplicated line in the action's with: block (the excessive "script: |" entry)
and adjust the following lines' indentation accordingly.

In @.github/actions/sdd-route-dispatch/action.yml:
- Around line 39-41: The yaml contains an accidental nested "script: |" inside
the actions/github-script body which becomes part of the JavaScript and causes a
syntax error; remove the inner "script: |" so the block begins directly with the
JS (e.g., the line before "const event = context.eventName;"), ensuring the
actions/github-script "script" key contains only the intended JS code.

In @.github/actions/sdd-route-execute/action.yml:
- Around line 614-616: The code sets maxIters via
parseInt(process.env.SDD_MAX_REVIEW_ITERATIONS || '3', 10) which yields NaN for
non-numeric env values and thus disables the cap; change the logic around
parseInt to validate and coerce the value: read the env var, parseInt with radix
10 into maxIters, then if Number.isNaN(maxIters) or maxIters < 1 set maxIters to
a safe default (3) — ensure priorIters comparison (priorIters >= maxIters) uses
this validated numeric maxIters so the auto-revise cap cannot be bypassed by a
bad env value.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 2c98f13e-82a5-4cec-9d44-55392d30305e

📥 Commits

Reviewing files that changed from the base of the PR and between 351b779 and f0e65a4.

📒 Files selected for processing (28)
  • .github/actions/sdd-auto-merge/action.yml
  • .github/actions/sdd-dispatch-compute/action.yml
  • .github/actions/sdd-fastpath-approve/action.yml
  • .github/actions/sdd-monitor/action.yml
  • .github/actions/sdd-pr-sanitize/action.yml
  • .github/actions/sdd-route-dispatch/action.yml
  • .github/actions/sdd-route-execute/action.yml
  • .github/actions/sdd-route-review/action.yml
  • .github/actions/sdd-route-spec/action.yml
  • .github/actions/sdd-route-triage/action.yml
  • .github/actions/sdd-route-validate/action.yml
  • .github/actions/sdd-spec-failure-handoff/action.yml
  • .github/actions/sdd-triage-dedupe-tasks/action.yml
  • .github/actions/sdd-triage-promote-ready/action.yml
  • decisions/0015-wrapper-logic-in-composite-actions.md
  • scripts/quick-setup.sh
  • wrappers/sdd-dispatch.yml
  • wrappers/sdd-execute-haiku.yml
  • wrappers/sdd-execute-opus.yml
  • wrappers/sdd-execute-sonnet.yml
  • wrappers/sdd-monitor.yml
  • wrappers/sdd-pr-sanitize.yml
  • wrappers/sdd-review.yml
  • wrappers/sdd-spec.yml
  • wrappers/sdd-triage-dedupe-tasks.yml
  • wrappers/sdd-triage-promote-ready.yml
  • wrappers/sdd-triage.yml
  • wrappers/sdd-validate.yml

Comment thread .github/actions/sdd-fastpath-approve/action.yml
Comment thread .github/actions/sdd-monitor/action.yml Outdated
Comment thread .github/actions/sdd-route-dispatch/action.yml
Comment thread .github/actions/sdd-route-execute/action.yml Outdated
norrietaylor and others added 2 commits May 29, 2026 00:25
An off-by-one in the extraction prepended a stray 'script: |' line to the
github-script body in sdd-monitor and sdd-route-dispatch, which would throw a
JS syntax error at runtime. The per-action parity check compared against a
slice that included the same stray line, so it passed falsely. Both bodies now
match their pre-refactor originals byte-for-byte (base 351b779).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Two small defensive fixes, kept separate from the verbatim refactor:
- sdd-route-execute: guard a non-numeric SDD_MAX_REVIEW_ITERATIONS so a NaN
  cap cannot silently disable the auto-revise bound (falls back to 3).
- sdd-fastpath-approve: also clear sdd:fastpath-review during the lifecycle
  transition so the both-labels-present edge cannot strand a stale label;
  no-op when absent. Completes the intent noted in the existing comment.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@norrietaylor
Copy link
Copy Markdown
Owner Author

@coderabbitai review

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 29, 2026

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@norrietaylor
Copy link
Copy Markdown
Owner Author

@coderabbitai review

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 29, 2026

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
scripts/test-command-table.py (1)

201-211: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Update error messages to reflect that routing sources now include composite actions.

The error messages mention only wrappers/*.yml, but the code now scans both wrappers/*.yml and .github/actions/*/action.yml (per the refactor in ADR 0015). This creates a mismatch between the diagnostic output and the actual behavior.

📝 Proposed fix to update error messages
     for cmd in sorted(wrapper_not_in_table):
-        wrappers = ", ".join(routes[cmd])
+        sources = ", ".join(routes[cmd])
         failures.append(
-            f"wrapper routes `{cmd}` ({wrappers}) but it is not in "
+            f"routing layer routes `{cmd}` ({sources}) but it is not in "
             f"shared/sdd-interaction.md command table"
         )
     for cmd in sorted(table_not_in_wrapper):
         failures.append(
             f"shared/sdd-interaction.md command table includes `{cmd}` "
-            f"but no wrappers/*.yml routes it"
+            f"but the routing layer (wrappers + composite actions) does not route it"
         )
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@scripts/test-command-table.py` around lines 201 - 211, The diagnostic strings
are outdated: update the two failures.append messages that reference only
"wrappers/*.yml" (the ones built from variables wrapper_not_in_table/routes and
table_not_in_wrapper) to mention both sources now scanned (e.g. "wrappers/*.yml
and .github/actions/*/action.yml" or "wrappers and composite actions"), so the
error text matches the refactor that includes composite actions; change the
messages at the failures.append calls that construct those two errors
accordingly.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@scripts/test-command-table.py`:
- Around line 201-211: The diagnostic strings are outdated: update the two
failures.append messages that reference only "wrappers/*.yml" (the ones built
from variables wrapper_not_in_table/routes and table_not_in_wrapper) to mention
both sources now scanned (e.g. "wrappers/*.yml and .github/actions/*/action.yml"
or "wrappers and composite actions"), so the error text matches the refactor
that includes composite actions; change the messages at the failures.append
calls that construct those two errors accordingly.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: f008b5f9-2e41-4b1e-abf1-12d5083775e7

📥 Commits

Reviewing files that changed from the base of the PR and between f0e65a4 and c5c4c98.

📒 Files selected for processing (5)
  • .github/actions/sdd-fastpath-approve/action.yml
  • .github/actions/sdd-monitor/action.yml
  • .github/actions/sdd-route-dispatch/action.yml
  • .github/actions/sdd-route-execute/action.yml
  • scripts/test-command-table.py
💤 Files with no reviewable changes (2)
  • .github/actions/sdd-route-dispatch/action.yml
  • .github/actions/sdd-monitor/action.yml
🚧 Files skipped from review as they are similar to previous changes (2)
  • .github/actions/sdd-fastpath-approve/action.yml
  • .github/actions/sdd-route-execute/action.yml

@norrietaylor norrietaylor merged commit 9d17dcf into main May 29, 2026
10 checks passed
@norrietaylor norrietaylor deleted the refactor/thin-wrappers branch May 29, 2026 16:51
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.

1 participant