8000 Install spectacles agentic workflow suite by norrietaylor · Pull Request #255 · gominimal/minimal · GitHub
[go: up one dir, main page]

Skip to content

Install spectacles agentic workflow suite - #255

Closed
norrietaylor wants to merge 1 commit into
mainfrom
spectacles/install
Closed

norrietaylor wants to merge 1 commit into
mainfrom
spectacles/install

Conversation

@norrietaylor
@norrietaylor norrietaylor commented May 29, 2026 •
Copy link
Copy Markdown
Member

Installs the spectacles agentic workflow agent suite onto this repo via the two-layer distribution model. Wrappers pin hosted reusable workflows at @main.

Direct-to-default-branch writes from scripts/quick-setup.sh are blocked by branch protection (PR + build check required), so the file artifacts are delivered as this PR. Labels and repo variables were applied out-of-band (non-branch APIs).

Contents

  • 13 workflow wrappers under .github/workflows/ (8 sdd-* agents, distillery-sync, sdd-pr-sanitize, sdd-triage-dedupe-tasks, sdd-triage-promote-ready, sdd-monitor)
  • 4 issue templates under .github/ISSUE_TEMPLATE/
  • .gitignore: .serena/ exclusion (Serena working-tree state)

Already applied (outside this PR)

  • 20 labels (needs-human, kind:*, priority:*, sdd:*, model:*, plan:provided)
  • Variables: DISTILLERY_PROJECT=minimal, SERENA_LANGUAGE_SERVERS=rust-analyzer

Operator config still required

Repo or org level:

  • Variables: APP_ID, optional SDD_DISPATCH_MAX_PARALLEL (default 5)
  • Secrets: APP_PRIVATE_KEY, COPILOT_GITHUB_TOKEN, LEAK_DENYLIST

Distillery (DISTILLERY_MCP_URL, DISTILLERY_OAUTH_TOKEN) is provided at the org level.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Documentation

    • Added structured issue templates for bug reports, feature requests, chores, and specifications to improve issue reporting consistency.
  • Chores

    • Added GitHub Actions workflows to automate repository operations including issue triage, validation, monitoring, and pull request management.
    • Updated repository configuration to support internal development tooling.

Review Change Stack

@coderabbitai
coderabbitai Bot commented May 29, 2026 •
Copy link
Copy Markdown
Contributor
📝 Walkthrough

Walkthrough

This PR establishes a complete GitHub Actions-based SDD orchestration layer consisting of issue templates, command-routing workflows (spec, triage, dispatch, monitor), three-tier execution wrappers (haiku, sonnet, opus), and supporting task/PR management workflows that automate specification-driven development pipelines.

Changes

SDD Pipeline Infrastructure

Layer / File(s) Summary
Issue Templates & Command Vocabulary
.github/ISSUE_TEMPLATE/bug.md, .github/ISSUE_TEMPLATE/chore.md, .github/ISSUE_TEMPLATE/feature.md, .github/ISSUE_TEMPLATE/spec.md
Four GitHub issue templates define user entry points for bugs, chores, features, and specs, each embedding a shared SDD command vocabulary footer that documents how /spec, /triage, /dispatch, /execute, /revise, and /approve commands control pipeline transitions.
Spec Routing & Fast-Path Approval
.github/workflows/sdd-spec.yml
Routes /spec, /revise, /fastpath, and /approve commands with repository permission gating. Introduces a fastpath-approve job that extracts the model:* tier from the latest plan comment, dispatches sdd-execute-{tier}.yml, and moves lifecycle labels. Includes a failure-handoff job that posts recovery comments and applies needs-human for human resumption.
Triage Routing & Label Management
.github/workflows/sdd-triage.yml
Routes /triage, /approve, and /revise commands on issues and merged arch/* PRs. Enforces repository permission checks, filters label-driven triggers to tracking issues only, and skips certain /approve commands that are handled by the fast-path workflow.
Monitor & Dispatch: Ready-Set Computation & Fan-Out
.github/workflows/sdd-dispatch.yml, .github/workflows/sdd-monitor.yml
Monitor workflow conditionally dispatches /dispatch comments to idle but armed trackers when ready tasks emerge. Dispatch workflow resolves tracking issues, computes dependency-based ready sets by parsing task body blocked by references and checking blocker closure, fans out /execute comments to each ready task, and manages the full lifecycle of tracking and task labels (sdd:ready, sdd:in-progress, sdd:dispatched, sdd:done).
Execution Tier Wrappers (Haiku, Sonnet, Opus)
.github/workflows/sdd-execute-haiku.yml, .github/workflows/sdd-execute-sonnet.yml, .github/workflows/sdd-execute-opus.yml
Three event-driven tier wrappers, each with a route job that parses /execute and /revise commands, enforces tier ownership via model:* label checks, treats CHANGES_REQUESTED reviews as implicit /revise directives (via GraphQL thread resolution), detects fast-path merged PR completions, and enforces auto-revise iteration caps with needs-human fallback. Each includes an auto-merge job that mints an App token, attempts squash+delete-branch auto-merge, and falls back to direct squash merge when the PR is unstable but all required checks pass.
PR Sanitization, Review, & Task Deduplication
.github/workflows/sdd-pr-sanitize.yml, .github/workflows/sdd-review.yml, .github/workflows/sdd-triage-dedupe-tasks.yml, .github/workflows/sdd-triage-promote-ready.yml
Sanitizes spec/* and arch/* PR bodies by rewriting closing keywords to Refs while preserving deliverable sub-issue references. Routes sdd/ PR review events. Deduplicates phase-C task sub-issues by detecting structured ## Task blocks and closing earlier duplicates. Promotes tasks to sdd:ready when all blocked by #<N> dependencies are closed.
External Integration & Configuration
.github/workflows/distillery-sync.yml, .gitignore
Thin wrapper around norrietaylor/spectacles distillery-sync.lock.yml with cron/manual triggers and explicit secret forwarding. Adds .serena/ directory to gitignore for spectacles quick-setup working state.

Sequence Diagram(s)

sequenceDiagram
  participant User
  participant IssueTemplate
  participant SpecRoute as sdd-spec route
  participant FastpathApprove as fastpath-approve
  participant Execute as sdd-execute-tier
  participant Monitor as sdd-monitor
  participant Dispatch as sdd-dispatch route
  participant TaskExec as Execute Task

  User->>IssueTemplate: Create tracking issue
  activate IssueTemplate
  IssueTemplate->>SpecRoute: /spec command or label
  deactivate IssueTemplate
  
  activate SpecRoute
  SpecRoute->>SpecRoute: Permission check
  SpecRoute->>FastpathApprove: Trigger if sdd:fastpath
  deactivate SpecRoute
  
  activate FastpathApprove
  FastpathApprove->>FastpathApprove: Extract plan comment & tier
  FastpathApprove->>Execute: Dispatch with entry:fastpath
  FastpathApprove->>IssueTemplate: Update labels (remove sdd:fastpath, add sdd:in-progress)
  deactivate FastpathApprove
  
  activate Monitor
  Monitor->>Monitor: Check armed-but-idle trackers
  Monitor->>Dispatch: Post /dispatch comment
  deactivate Monitor
  
  activate Dispatch
  Dispatch->>Dispatch: Walk sub-issues, parse blockers
  Dispatch->>Dispatch: Compute ready set
  Dispatch->>TaskExec: Post /execute to each ready task
  Dispatch->>IssueTemplate: Apply sdd:ready + sdd:dispatched labels
  deactivate Dispatch
  
  activate Execute
  Execute->>Execute: Route job: permission check
  Execute->>Execute: Resolve task & tier gate
  Execute->>TaskExec: Call reusable workflow
  TaskExec->>User: Create implementation PR
  deactivate Execute
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

  • gominimal/spectacles#12: The new distillery-sync.yml wrapper directly invokes the retrieved PR's reusable distillery-sync.lock.yml workflow with explicit secret forwarding.
  • gominimal/spectacles#97: The new sdd-dispatch.yml workflow implements the same ready-set fan-out and label-lifecycle semantics as the retrieved PR's core dispatch agent.
  • gominimal/spectacles#16: The new sdd-spec.yml wrapper routes /spec commands and invokes the sdd-spec.lock.yml reusable workflow that is implemented in the retrieved PR.

Suggested reviewers

  • twitchyliquid64

Poem

🐰 Workflows spring to life in YAML form,
Dispatching tasks through the SDD storm!
Each tier awakens (haiku, sonnet, opus too),
Chasing blockers closed till work is through.
Templates guide, monitors nudge, and PRs fly—
A pipeline of dreams beneath the CI sky! ✨

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The PR title directly and concisely describes the main change: installing the spectacles agentic workflow suite into the repository, which is exactly what the 13 workflow files, 4 issue templates, and .gitignore update accomplish.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
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.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

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

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch

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

@norrietaylor norrietaylor changed the title Install spectacles SDD suite Install spectacles agentic workflow suite May 29, 2026
@gominimal-aw-bot
Copy link
Copy Markdown
Contributor

sdd-validate findings — Implementation boundary

Resolved boundary: Implementation (changed files are not spec or architecture files)

PR: Install spectacles agentic workflow suite (spectacles/install → main)


Gate 1 — Proof artifacts re-executed and passing

Info: No task sub-issue is linked in this PR body (no Closes #N). This PR is an infrastructure setup install rather than an SDD task implementation. There are no declared proof artifacts to re-execute. No finding is filed on the absence of artifacts for a non-task PR; this is noted for transparency.


Gate 2 — Changed files within task scope

Blocker · implementation gate 2

17 of 18 changed files are under .github/:

.github/ISSUE_TEMPLATE/bug.md
.github/ISSUE_TEMPLATE/chore.md
.github/ISSUE_TEMPLATE/feature.md
.github/ISSUE_TEMPLATE/spec.md
.github/workflows/distillery-sync.yml
.github/workflows/sdd-dispatch.yml
.github/workflows/sdd-execute-haiku.yml
.github/workflows/sdd-execute-opus.yml
.github/workflows/sdd-execute-sonnet.yml
.github/workflows/sdd-monitor.yml
.github/workflows/sdd-pr-sanitize.yml
.github/workflows/sdd-review.yml
.github/workflows/sdd-spec.yml
.github/workflows/sdd-triage-dedupe-tasks.yml
.github/workflows/sdd-triage-promote-ready.yml
.github/workflows/sdd-triage.yml
.github/workflows/sdd-validate.yml

.github/ is a protected path per the validation-gates gate set. A change to a protected path is a Blocker regardless of intent. The remaining change (.gitignore) is not protected and passes.

Note: This is the spectacles install PR and the .github/ changes are clearly intentional. A human reviewer should confirm the workflow contents are trusted before merging, which is the purpose of this hand-off.


Gate 3 — No real credentials in the diff

Pass. The diff was scanned for hardcoded tokens, passwords, and secrets. None were found. Secrets referenced in the PR body (APP_PRIVATE_KEY, COPILOT_GITHUB_TOKEN, LEAK_DENYLIST) are described as applied out-of-band and are not present in the diff.


Summary

Gate Severity Result
Proof artifacts re-executed and passing Info No task / no declared artifacts (non-task install PR)
Changed files within task scope Blocker 17 files under protected path .github/
No real credentials in the diff — Pass

Action required: A Blocker was found. needs-human has been applied. A human reviewer should inspect the workflow file contents for trusted provenance and merge if satisfied.

Generated by sdd-validate for issue #255 · ● 3.3M · ◷

@gominimal-aw-bot gominimal-aw-bot Bot added the needs-human An agent handed off; a human must act, then clear this label. Set by defer and handoffs. label May 29, 2026
@coderabbitai coderabbitai Bot left a comment
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 1

Note

Due to the large number of review comments, Critical severity comments were prioritized as inline comments.

🟠 Major comments (21)
.github/workflows/sdd-triage.yml-81-81 (1)

81-81: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Pin workflow/action references to commit SHAs.

actions/github-script@v9 (line 81) and norrietaylor/spectacles/.github/workflows/sdd-triage.lock.yml@main (line 214) are mutable refs; pin both to immutable commit SHAs to avoid unexpected upstream changes.

🤖 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 @.github/workflows/sdd-triage.yml at line 81, Replace the mutable action refs
with immutable commit SHAs: locate the uses entry "uses:
actions/github-script@v9" and replace the tag with the specific commit SHA for
the actions/github-script repository, and locate the reference
"norrietaylor/spectacles/.github/workflows/sdd-triage.lock.yml@main" and replace
the branch ref with that repository's exact commit SHA; ensure you fetch the
correct commit SHAs from the respective upstream repos/tags and update both
places so the workflow pins to the immutable SHAs rather than `@v9` or `@main`.
.github/workflows/sdd-triage.yml-89-101 (1)

89-101: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Filter issue-label triggers to tracking issues only.

The issues branch routes on label changes without checking whether the issue is a tracking issue (no parent). A mislabeled sub-issue can incorrectly trigger triage routing.

Targeted fix
             if (event === 'issues') {
               const action = payload.action;
               const label = payload.label && payload.label.name;
               if (action === 'labeled' && label === 'sdd:triage') {
                 run = true;
               } else if (action === 'unlabeled' && label === 'needs-human') {
                 run = true;
               }
+              if (run) {
+                try {
+                  const { data } = await github.rest.issues.get({
+                    owner: context.repo.owner,
+                    repo: context.repo.repo,
+                    issue_number: payload.issue.number,
+                  });
+                  if (data.parent_issue_url) {
+                    run = false;
+                    core.info('Sub-issue label event ignored for sdd-triage.');
+                  }
+                } catch (err) {
+                  core.info('Could not fetch issue #' + payload.issue.number
+                    + ': ' + err.message);
+                }
+              }
               if (run) {
                 ctx = { item_type: 'issue', item_number: payload.issue.number };
               }
             }
🤖 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 @.github/workflows/sdd-triage.yml around lines 89 - 101, The issue: the
'issues' event handler sets run and ctx for any label change without ensuring
the issue is a top-level tracking issue; to fix, restrict the labeled/unlabeled
checks inside the event === 'issues' branch to only proceed when the issue is a
tracking issue (no parent). Concretely, inside that branch where action, label,
run, and ctx are used, add a guard that checks the payload.issue is top-level
(e.g., !payload.issue.parent or equivalent field your payload uses to indicate a
parent) before evaluating the 'labeled sdd:triage' or 'unlabeled needs-human'
conditions and before setting ctx = { item_type: 'issue', item_number:
payload.issue.number } so sub-issues won't trigger triage routing.
.github/workflows/sdd-spec.yml-88-88 (1)

88-88: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Pin uses: refs to commit SHAs in .github/workflows/sdd-spec.yml (and scope the minted App token)

uses: references in this workflow are mutable tags/branches, not immutable commit SHAs:

  • 88: actions/github-script@v9
  • 364: actions/create-github-app-token@v3
  • 371: actions/github-script@v9
  • 590: norrietaylor/spectacles/.github/workflows/sdd-spec.lock.yml@main
  • 640: actions/create-github-app-token@v3
  • 647: actions/github-script@v9

Additionally, the actions/create-github-app-token@v3 steps mint the installation token using only app-id/private-key/owner/repositories with no explicit permission-scope narrowing in this workflow, increasing the blast radius of the token.

🤖 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 @.github/workflows/sdd-spec.yml at line 88, The workflow uses mutable action
refs and mints an overly-broad App token; replace each mutable `uses:` reference
(e.g., actions/github-script@v9, actions/create-github-app-token@v3,
norrietaylor/spectacles/.github/workflows/sdd-spec.lock.yml@main) with the
corresponding pinned commit SHA for that action, and for the steps that call
`actions/create-github-app-token@v3` narrow the minted installation token by
adding explicit permission scope inputs (and repository list if applicable) so
the token only has the minimum required permissions (e.g., specify permissions
like contents: read or issues: write as needed) and ensure the step uses the
pinned SHA as well.
.github/workflows/sdd-spec.yml-364-369 (1)

364-369: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Narrow GitHub App token permissions to least privilege

Both actions/create-github-app-token@v3 steps in .github/workflows/sdd-spec.yml (lines 364-369, and the same pattern at 640-645) omit any permission-* inputs, so the minted installation token inherits all permissions granted to the GitHub App installation (not least-privilege).

The corresponding github-script code only uses Issues APIs (issues.get, issues.listComments, issues.createComment, issues.addLabels), so grant only permission-issues: write for each “Mint App token” step (avoid permission-actions and permission-contents here unless this workflow uses them elsewhere for the same app token).

🤖 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 @.github/workflows/sdd-spec.yml around lines 364 - 369, The GitHub App
installation token steps using actions/create-github-app-token@v3 currently omit
explicit permissions and thus inherit all app permissions; update each "Mint App
token" step (the actions/create-github-app-token@v3 invocation around the
existing app-id/private-key/owner/repositories inputs) to explicitly set
permission-issues: write so the token is least-privilege for the github-script
code that only calls issues.get, issues.listComments, issues.createComment and
issues.addLabels; apply the same change to the duplicate step later in the file.
.github/workflows/distillery-sync.yml-31-31 (1)

31-31: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Pin the reusable workflow reference to an immutable SHA (avoid @main).

.github/workflows/distillery-sync.yml line 31 uses norrietaylor/spectacles/.github/workflows/distillery-sync.lock.yml@main (mutable @main), which breaks deterministic runs/unpinned-uses policy.

Suggested change
-    uses: norrietaylor/spectacles/.github/workflows/distillery-sync.lock.yml@main
+    uses: norrietaylor/spectacles/.github/workflows/distillery-sync.lock.yml@<40-char-commit-sha>
🤖 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 @.github/workflows/distillery-sync.yml at line 31, Replace the mutable
reference "uses:
norrietaylor/spectacles/.github/workflows/distillery-sync.lock.yml@main" with an
immutable commit SHA: identify the correct commit SHA for
norrietaylor/spectacles (e.g., from the repository's commits for the referenced
workflow file) and update that uses line to reference the SHA instead of `@main`
so the reusable workflow is pinned; ensure the updated line still points to
".github/workflows/distillery-sync.lock.yml@<SHA>" to satisfy the pinned-uses
policy.
.github/workflows/sdd-execute-haiku.yml-112-112 (1)

112-112: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Use token-strict command matching here as well.

startsWith('/execute') and startsWith('/revise') also match /executefoo and /revisex. Those comments still land in the task's shared concurrency group and can cancel a legitimate in-flight run before the route script noops, which defeats the throwaway-group carve-out.

Also applies to: 134-145

🤖 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 @.github/workflows/sdd-execute-haiku.yml at line 112, The group expression is
using startsWith('/execute') and startsWith('/revise') which also match tokens
like "/executefoo" and "/revisex"; update every occurrence of that token test
(the startsWith checks in the group expression and the similar checks around
lines referenced) to use a token-strict match (e.g. a regex that enforces
end-of-token or whitespace such as matches(github.event.comment.body,
'^/execute(\\s|$)') and '^/revise(\\s|$)') so only exact commands or commands
followed by whitespace match and avoid false positives that join the shared
concurrency group.
.github/workflows/sdd-monitor.yml-331-389 (1)

331-389: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Monitor's ready-count logic ignores cross-repo blockers.

This pass only parses blocked by #123``, while sdd-dispatch also honors `blocked by owner/repo#123`. A task blocked in another repo will be counted as ready here, so the monitor can keep posting `/dispatch` comments that dispatch immediately rejects as an empty ready set.

🤖 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 @.github/workflows/sdd-monitor.yml around lines 331 - 389, The ready-count
logic only parses "blocked by `#N`" (in the block that builds blockerNums from
body.matchAll) and thus ignores cross-repo blockers; update the parser and
blocker lookup so it recognizes both "blocked by `#123`" and "blocked by
owner/repo#123" by extracting optional owner and repo plus the issue number (use
the existing body variable and matchAll call but change the regex and mapping to
produce blocker descriptors rather than plain ints), then when calling
github.rest.issues.get use the matched owner/repo if present or fall back to the
current owner/repo variables (keep checking got.data.pull_request and
got.data.state as before); ensure error messages (the core.info calls in the
catch) include the fully qualified blocker identifier and treat failures as
still blocked exactly as existing logic does.
.github/workflows/sdd-execute-sonnet.yml-112-112 (1)

112-112: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Use token-strict command matching here as well.

startsWith('/execute') and startsWith('/revise') also match /executefoo and /revisex. Those comments still land in the task's shared concurrency group and can cancel a legitimate in-flight run before the route script noops, which defeats the throwaway-group carve-out.

Also applies to: 134-145

🤖 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 @.github/workflows/sdd-execute-sonnet.yml at line 112, The group expression
currently uses startsWith('/execute') and startsWith('/revise') which also match
'/executefoo' and '/revisex'; change those to token-strict checks by replacing
startsWith('/execute') and startsWith('/revise') with contains(concat(' ',
github.event.comment.body, ' '), ' /execute ') and contains(concat(' ',
github.event.comment.body, ' '), ' /revise ') respectively (apply same fix to
the other occurrence in the expression block referenced). Update the group
expression whe
8BD0
re github.event.comment.body is checked so the shared concurrency
key only triggers on exact token matches.
.github/workflows/sdd-triage-dedupe-tasks.yml-102-123 (1)

102-123: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Don't dedupe phase-C tasks on title alone.

original is selected purely by normalized title, then only rechecked for "looks like a task". Two legitimate task sub-issues under the same Unit can share a short title while differing in repo/blockers/proof artifacts, and this would auto-close the newer one incorrectly. Compare a stronger fingerprint before closing anything.

Also applies to: 126-152

🤖 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 @.github/workflows/sdd-triage-dedupe-tasks.yml around lines 102 - 123, The
current dedupe picks `original` solely by normalized `titleKey` using
`isSameTitleEarlierTask`, which can false-match distinct phase-C tasks; update
the matching to compute and compare a stronger fingerprint (e.g., combine
normalized title plus additional distinguishing fields such as repository
identifier, blocker list, and proof-artifact markers parsed from the issue
body/labels) when filtering `siblings` before assigning `original` (adjust the
predicate used in `.filter(...)` instead of `isSameTitleEarlierTask`); apply the
same stronger-fingerprint comparison in the analogous block later (lines
referenced in the review) so we only auto-close a sibling when title plus those
extra fields match.
.github/workflows/sdd-execute-opus.yml-112-112 (1)

112-112: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Use token-strict command matching here as well.

startsWith('/execute') and startsWith('/revise') also match /executefoo and /revisex. Those comments still land in the task's shared concurrency group and can cancel a legitimate in-flight run before the route script noops, which defeats the throwaway-group carve-out.

Also applies to: 134-145

🤖 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 @.github/workflows/sdd-execute-opus.yml at line 112, The group expression
uses startsWith(github.event.comment.body, '/execute') and '/revise' which also
match prefixes like /executefoo; replace those with token-strict regex matches
using the matches operator, e.g. use github.event.comment.body =~ '^/execute\\b'
and github.event.comment.body =~ '^/revise\\b' (and update the same occurrences
referenced in the later block around lines 134-145) so only exact commands or
commands followed by whitespace/word-boundaries trigger the concurrency-group
logic.
.github/workflows/sdd-review.yml-55-55 (1)

55-55: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Pin the action and hosted reusable workflow to immutable SHAs.

.github/workflows/sdd-review.yml uses actions/github-script@v9 and norrietaylor/spectacles/.github/workflows/sdd-review.lock.yml@main; both are mutable refs, so review behavior/security can change due to upstream updates. Pin both uses: references to commit SHAs instead.

🤖 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 @.github/workflows/sdd-review.yml at line 55, The workflow currently
references mutable refs (uses: actions/github-script@v9 and uses:
norrietaylor/spectacles/.github/workflows/sdd-review.lock.yml@main); replace
each mutable tag with the corresponding immutable commit SHA for that
repository/use (pin both actions/github-script and norrietaylor/spectacles
references to their specific commit SHAs) so the workflow and hosted reusable
workflow are fixed to exact commits; update the two uses: entries (the
actions/github-script reference and the norrietaylor/spectacles reusable
workflow reference) to their respective SHAs and commit the change.
.github/workflows/sdd-execute-haiku.yml-174-174 (1)

174-174: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Pin uses: refs in .github/workflows/sdd-execute-haiku.yml to immutable SHAs

actions/github-script@v9 (174), norrietaylor/spectacles/.../sdd-execute-haiku.lock.yml@main (814), and actions/create-github-app-token@v3 (855) are mutable tags/refs, which harms reproducibility and can change behavior without a code change.

🤖 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 @.github/workflows/sdd-execute-haiku.yml at line 174, The workflow uses
mutable refs; replace the mutable tags with immutable commit SHAs: locate the
uses entries for actions/github-script@v9, actions/create-github-app-token@v3,
and the norrietaylor/spectacles.../sdd-execute-haiku.lock.yml@main reference,
and update each to the corresponding full commit SHA (e.g.,
actions/github-script@<sha>, actions/create-github-app-token@<sha>,
norrietaylor/spectacles/.../sdd-execute-haiku.lock.yml@<sha>) so the workflow
pins to immutable refs and remains reproducible.
.github/workflows/sdd-pr-sanitize.yml-66-66 (1)

66-66: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Pin actions/github-script to an immutable commit SHA

uses: actions/github-script@v9 uses a mutable tag; GitHub recommends pinning GitHub Actions to full-length commit SHAs to prevent upstream tag moves/supply-chain tampering. Prefer uses: actions/github-script@<40-char-sha> # v9 (same pattern appears in other workflows too).

🤖 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 @.github/workflows/sdd-pr-sanitize.yml at line 66, The workflow is using a
mutable tag "uses: actions/github-script@v9"; replace this with the full
40-character commit SHA for the actions/github-script action (e.g. "uses:
actions/github-script@<40-char-sha>  # v9") to pin the dependency and add the
trailing comment to indicate the intended tag; update the line containing
actions/github-script@v9 and mirror the same change for any other workflows
using the same mutable tag.
.github/workflows/sdd-execute-sonnet.yml-174-174 (1)

174-174: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Pin the actions and reusable workflow refs to immutable commit SHAs

In .github/workflows/sdd-execute-sonnet.yml, these uses: entries are mutable tag refs, so upstream changes can alter behavior without any repo change:

  • actions/github-script@v9 (174)
  • norrietaylor/spectacles/.github/workflows/sdd-execute-sonnet.lock.yml@main (814)
  • actions/create-github-app-token@v3 (855)

Pin each to a full commit SHA.

🤖 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 @.github/workflows/sdd-execute-sonnet.yml at line 174, The workflow uses
mutable tag refs for third-party actions and a reusable workflow; find the exact
commit SHAs for each referenced resource (actions/github-script@v9,
norrietaylor/spectacles/.github/workflows/sdd-execute-sonnet.lock.yml@main,
actions/create-github-app-token@v3) and replace the tag refs with their
corresponding full commit SHAs (e.g. actions/github-script@<full-sha>) so the
workflow is pinned immutably; update each `uses:` entry to the SHA form and
optionally add a short comment noting the pinned SHA and original tag for future
maintainers.
.github/workflows/sdd-triage-dedupe-tasks.yml-40-40 (1)

40-40: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Pin actions/github-script to an immutable commit SHA.

.github/workflows/sdd-triage-dedupe-tasks.yml:40 currently uses actions/github-script@v9, leaving this job dependent on a mutable tag (supply-chain risk); pin to the full-length SHA instead.

uses: actions/github-script@v9
🤖 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 @.github/workflows/sdd-triage-dedupe-tasks.yml at line 40, Replace the
mutable tag usage "uses: actions/github-script@v9" with the full immutable
commit SHA for the v9 release (e.g., "uses:
actions/github-script@<full-commit-sha>"); locate the line containing uses:
actions/github-script@v9 in the workflow and update it to reference the
full-length Git commit SHA from the actions/github-script repository to remove
supply-chain risk.
.github/workflows/sdd-dispatch.yml-849-854 (1)

849-854: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Scope GitHub App installation token permissions to the minimum needed.

All actions/create-github-app-token@v3 steps in .github/workflows/sdd-dispatch.yml mint an installation token without any permission-* inputs, so the token inherits the GitHub App installation’s full permission set (job-level permissions: only limits GITHUB_TOKEN). Since these steps only use Issues APIs (createComment, addLabels, removeLabel), restrict the minted token to only the required permission-* (e.g., permission-issues: write) to reduce blast radius.

Applies to the four minting steps at ~849-854, ~925-930, ~1094-1099, and ~1168-1173.

🤖 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 @.github/workflows/sdd-dispatch.yml around lines 849 - 854, The
create-github-app-token steps currently mint installation tokens with full app
installation permissions; update each actions/create-github-app-token@v3
invocation to scope the token to the minimal permission needed for the workflow
by adding the permission-issues input (e.g., permission-issues: write) so the
token only allows Issues API operations (createComment, addLabels, removeLabel);
locate each usage of actions/create-github-app-token@v3 in the file and add the
permission-issues: write key under with: for those steps.
.github/workflows/sdd-execute-opus.yml-174-174 (1)

174-174: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Pin Actions and the reusable workflow to immutable commit SHAs

actions/github-script@v9, actions/create-github-app-token@v3, and norrietaylor/spectacles/.github/workflows/sdd-execute-opus.lock.yml@main are mutable refs; pinning them to full commit SHAs prevents upstream retags/branch moves from changing execution.

uses: actions/github-script@v9

Also at 814 (...@main) and 855 (...@v3).

🤖 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 @.github/workflows/sdd-execute-opus.yml at line 174, The workflow is pinning
external actions and a reusable workflow to mutable tags which can change;
update the three uses entries (the action identifier strings
"actions/github-script@v9", "actions/create-github-app-token@v3", and the
reusable workflow reference
"norrietaylor/spectacles/.github/workflows/sdd-execute-opus.lock.yml@main") to
their corresponding immutable full commit SHAs (replace the `@vX/`@main refs with
@<full-commit-sha>) so the workflow consistently executes the exact intended
commits; locate and replace each occurrence (the one at the shown line and the
other occurrences around lines referenced in the review) ensuring the SHAs come
from the exact upstream repository commit you want pinned.
.github/workflows/sdd-monitor.yml-128-128 (1)

128-128: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Pin GitHub Actions to immutable SHAs (and tighten monitor token + ready-task parsing)

  • In .github/workflows/sdd-monitor.yml, actions/create-github-app-token@v3 (line 128) and actions/github-script@v9 (line 135) use mutable tags—pin to commit SHAs.
  • The App token minting step doesn’t specify any explicit permission narrowing (only app-id, private-key, owner, repositories), so it should request least-privilege for what the monitor needs.
  • The “ready” computation only considers blockers written as blocked by #<N> (blocked by #(\d+)) and checks them in the same repo; different/cross-repo blocker formats would be ignored (empty blocker set ⇒ treated as “allClosed”), potentially causing premature /dispatch spam.
🤖 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 @.github/workflows/sdd-monitor.yml at line 128, Pin the GitHub Actions to
immutable commit SHAs by replacing uses: actions/create-github-app-token@v3 and
uses: actions/github-script@v9 with their exact commit SHAs; in the
create-github-app-token step (the App token minting step) add an explicit
permissions block requesting least-privilege (e.g., issues: read, contents:
read, metadata: read — adjust to the exact minimal scopes the monitor needs)
instead of relying only on app-id/private-key/owner/repositories; and tighten
the “ready” computation by expanding the blocker regex (currently /blocked by
#(\d+)/) to also match cross-repo forms like owner/repo#123 and full issue URLs
(e.g., /([\w-]+\/[\w-]+)#(\d+)/ and URL patterns), ensure you resolve and check
issues across repos, and change the logic so an empty blocker set does NOT imply
allClosed (only mark allClosed when at least one blocker was detected and all
detected blockers are closed).
.github/workflows/sdd-dispatch.yml-78-78 (1)

78-78: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Pin GitHub Actions uses: references to immutable SHAs/digests (avoid moving @v* tags).

In .github/workflows/sdd-dispatch.yml, uses: references actions/github-script@v9 and actions/create-github-app-token@v3 (e.g., lines 78, 524, 849, 856, 886, 925, 932, 1094, 1101, 1168, 1175). Pin these to immutable SHAs/digests.

🤖 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 @.github/workflows/sdd-dispatch.yml at line 78, The workflow pins ephemeral
action references (e.g., uses: actions/github-script@v9 and uses:
actions/create-github-app-token@v3) which should be replaced with their
immutable commit SHAs/digests; locate every occurrence of those uses strings in
.github/workflows/sdd-dispatch.yml (examples: the lines referencing
actions/github-script@v9 and actions/create-github-app-token@v3) and update each
to the corresponding full SHA or digest from the action repository (e.g.,
actions/github-script@<sha>), commit the exact digests so the workflow uses a
fixed, immutable version, and verify the workflow still passes.
.github/workflows/sdd-monitor.yml-126-133 (1)

126-133: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Narrow the GitHub App installation token permissions for the monitor job.

The monitor mints the token via actions/create-github-app-token@v3 but doesn’t pass any permission-* inputs, so the generated App installation token inherits the App installation’s full permission set (it’s not controlled by the workflow permissions: block).

      - name: Mint App token
        id: app
        uses: actions/create-github-app-token@v3
        with:
          app-id: ${{ vars.APP_ID }}
          private-key: ${{ secrets.APP_PRIVATE_KEY }}
          owner: ${{ github.repository_owner }}
          repositories: ${{ github.event.repository.name }}

Add minimal permission-* inputs to the with: block for the mint step (needs read access plus issue-comment write for the /dispatch comment).

🤖 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 @.github/workflows/sdd-monitor.yml around lines 126 - 133, The Mint App token
step (name: "Mint App token", id: app, uses: actions/create-github-app-token@v3)
currently mints an installation token with the App's full installation
permissions; add minimal permission-* inputs to the step's with: block to
restrict the token (e.g., permission-issues: write for the /dispatch comment and
permission-contents: read for repo read access), ensuring only required scopes
are granted.
.github/workflows/sdd-triage-promote-ready.yml-60-60 (1)

60-60: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Pin actions/github-script to an immutable commit SHA (avoid @v9)
In .github/workflows/sdd-triage-promote-ready.yml (line 60), uses: actions/github-script@v9 uses a mutable tag; pin to the intended v9 release commit SHA. The same unpinned @v9 appears in several other workflows too—update them for consistency.

🔒 Suggested change
-        uses: actions/github-script@v9
+        uses: actions/github-script@<full-length-commit-sha>
🤖 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 @.github/workflows/sdd-triage-promote-ready.yml at line 60, Replace the
mutable action reference "uses: actions/github-script@v9" with the immutable
commit SHA for the intended v9 release: fetch the exact commit SHA from the
actions/github-script v9 release on GitHub and update the workflow entry to
"uses: actions/github-script@<COMMIT_SHA>"; apply the same replacement across
all workflows that currently use "`@v9`" to ensure consistency and immutability.
🟡 Minor comments (2)
.github/workflows/sdd-validate.yml-109-126 (1)

109-126: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Fail closed when parent-issue lookup errors.

If github.rest.issues.get fails, the current branch falls through and treats the issue as a tracking issue (Line 125), which can trigger false-positive validation runs.

Suggested change
-                let parentUrl = '';
+                let parentUrl = '';
+                let lookupOk = true;
                 try {
                   const { data } = await github.rest.issues.get({
                     owner: context.repo.owner,
                     repo: context.repo.repo,
                     issue_number: payload.issue.number,
                   });
                   parentUrl = data.parent_issue_url || '';
                 } catch (err) {
+                  lookupOk = false;
                   core.info('Could not fetch issue #'
                     + payload.issue.number + ': ' + err.message);
                 }
-                if (parentUrl) {
+                if (!lookupOk) {
+                  core.info('Skipping run because parent lookup failed.');
+                } else if (parentUrl) {
                   core.info('Issue #' + payload.issue.number
                     + ' has a parent (' + parentUrl
                     + '); not a tracking issue, ignoring.');
                 } else {
                   run = true;
🤖 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 @.github/workflows/sdd-validate.yml around lines 109 - 126, The parent-issue
lookup can fail and currently falls through treating the issue as a tracking
issue; update the catch block around github.rest.issues.get to fail closed by
setting run = false (or otherwise aborting further processing) and log the error
with context (use payload.issue.number and parentUrl variables), so that when an
exception occurs you do not proceed to set ctx/run for validation; modify the
catch that currently calls core.info to also prevent the else-branch from
marking the issue as a tracking issue (e.g., set run = false or return) and keep
the existing log via core.info.
.github/workflows/sdd-execute-haiku.yml-481-507 (1)

481-507: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Stop using parent_issue_url as a fast-path/router discriminator.

In .github/workflows/sdd-execute-haiku.yml (fast-path-complete detection), isFastpath is gated on !parent_issue_url, but sdd-dispatch.yml documents that REST GET /issues/{n} leaves native sub-issues with an empty parent_issue_url. That makes parent_issue_url an unreliable way to distinguish tracking issues from task sub-issues. Normal full-path task merges shouldn’t match fastpathSignal because sdd:in-progress/sdd:dispatched are applied to tracking issues (tasks get sdd:ready), but the router remains brittle—use a stable discriminator instead (e.g., tracking vs task via model:* label presence/absence) rather than parent_issue_url.

🤖 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 @.github/workflows/sdd-execute-haiku.yml around lines 481 - 507, The code
currently treats an issue as a "tracking" issue by checking parent_issue_url
(parentUrl) which is unreliable; change the fast-path discriminator to detect
tracking vs task via stable model:* labels instead: after fetching issue labels
in the github.rest.issues.get response, compute a boolean like isTracking =
labels.includes('model:tracking') (or another agreed stable model label) and
replace the existing check that uses parentUrl (the "!parentUrl &&
fastpathSignal" condition) with "isTracking && fastpathSignal"; remove reliance
on parent_issue_url entirely when deciding isFastpath so fast-path detection
uses only labels and the existing sdd:in-progress / sdd:dispatched checks.
🧹 Nitpick comments (1)
.github/workflows/sdd-execute-opus.yml (1)

481-507: ⚡ Quick win

Avoid relying on REST parent_issue_url for fast-path vs full-path merge routing (use GraphQL parent instead).

The fastpath-complete routing in .github/workflows/sdd-execute-opus.yml (lines 481-507) doesn’t treat !parent_issue_url as “tracking” by itself: it also requires either sdd:fastpath/sdd:fastpath-review labels, or (sdd:in-progress present AND sdd:dispatched absent). Since sdd-dispatch applies sdd:in-progress/sdd:dispatched to the tracking issue (not task sub-issues), a normal full-path task merge is unlikely to satisfy fastpathSignal even if REST returns an empty parent_issue_url.

However, REST parent_issue_url is explicitly unreliable in this repo (GraphQL Issue.parent is treated as authoritative in sdd-dispatch due to issue #133), so using REST here is still fragile. Switch this check to GraphQL (or a label-based tracking invariant such as “no model:*”).

🤖 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 @.github/workflows/sdd-execute-opus.yml around lines 481 - 507, The code uses
the REST field parent_issue_url (via github.rest.issues.get for refNumber) to
decide fast-path routing (logic in fastpathSignal using parent_issue_url and
labels), which is unreliable; replace the REST parent check with a GraphQL query
for the Issue.parent (or alternatively a robust label-only invariant such as
detecting absence of any model:* labels) and base the fastpathSignal on that
authoritative parent value plus labels; update the block that reads parentUrl
and computes fastpathSignal (references: headRef, headMatch, refNumber,
github.rest.issues.get, parent_issue_url, fastpathSignal) to fetch Issue.parent
via GitHub GraphQL and use that result in place of parent_issue_url (or use the
label-based check) before evaluating the fast-path conditional.
🤖 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/workflows/sdd-validate.yml:
- Line 68: The workflow uses non-deterministic action refs; replace the floating
refs `actions/github-script@v9` and
`norrietaylor/spectacles/.github/workflows/sdd-validate.lock.yml@main` with
their corresponding pinned commit SHAs (exact full commit hashes) to ensure
reproducible runs—locate the `uses:` lines in .github/workflows/sdd-validate.yml
and update each `uses:` value to the full git sha for the referenced repository
and path.

---

Major comments:
In @.github/workflows/distillery-sync.yml:
- Line 31: Replace the mutable reference "uses:
norrietaylor/spectacles/.github/workflows/distillery-sync.lock.yml@main" with an
immutable commit SHA: identify the correct commit SHA for
norrietaylor/spectacles (e.g., from the repository's commits for the referenced
workflow file) and update that uses line to reference the SHA instead of `@main`
so the reusable workflow is pinned; ensure the updated line still points to
".github/workflows/distillery-sync.lock.yml@<SHA>" to satisfy the pinned-uses
policy.

In @.github/workflows/sdd-dispatch.yml:
- Around line 849-854: The create-github-app-token steps currently mint
installation tokens with full app installation permissions; update each
actions/create-github-app-token@v3 invocation to scope the token to the minimal
permission needed for the workflow by adding the permission-issues input (e.g.,
permission-issues: write) so the token only allows Issues API operations
(createComment, addLabels, removeLabel); locate each usage of
actions/create-github-app-token@v3 in the file and add the permission-issues:
write key under with: for those steps.
- Line 78: The workflow pins ephemeral action references (e.g., uses:
actions/github-script@v9 and uses: actions/create-github-app-token@v3) which
should be replaced with their immutable commit SHAs/digests; locate every
occurrence of those uses strings in .github/workflows/sdd-dispatch.yml
(examples: the lines referencing actions/github-script@v9 and
actions/create-github-app-token@v3) and update each to the corresponding full
SHA or digest from the action repository (e.g., actions/github-script@<sha>),
commit the exact digests so the workflow uses a fixed, immutable version, and
verify the workflow still passes.

In @.github/workflows/sdd-execute-haiku.yml:
- Line 112: The group expression is using startsWith('/execute') and
startsWith('/revise') which also match tokens like "/executefoo" and "/revisex";
update every occurrence of that token test (the startsWith checks in the group
expression and the similar checks around lines referenced) to use a token-strict
match (e.g. a regex that enforces end-of-token or whitespace such as
matches(github.event.comment.body, '^/execute(\\s|$)') and '^/revise(\\s|$)') so
only exact commands or commands followed by whitespace match and avoid false
positives that join the shared concurrency group.
- Line 174: The workflow uses mutable refs; replace the mutable tags with
immutable commit SHAs: locate the uses entries for actions/github-script@v9,
actions/create-github-app-token@v3, and the
norrietaylor/spectacles.../sdd-execute-haiku.lock.yml@main reference, and update
each to the corresponding full commit SHA (e.g., actions/github-script@<sha>,
actions/create-github-app-token@<sha>,
norrietaylor/spectacles/.../sdd-execute-haiku.lock.yml@<sha>) so the workflow
pins to immutable refs and remains reproducible.

In @.github/workflows/sdd-execute-opus.yml:
- Line 112: The group expression uses startsWith(github.event.comment.body,
'/execute') and '/revise' which also match prefixes like /executefoo; replace
those with token-strict regex matches using the matches operator, e.g. use
github.event.comment.body =~ '^/execute\\b' and github.event.comment.body =~
'^/revise\\b' (and update the same occurrences referenced in the later block
around lines 134-145) so only exact commands or commands followed by
whitespace/word-boundaries trigger the concurrency-group logic.
- Line 174: The workflow is pinning external actions and a reusable workflow to
mutable tags which can change; update the three uses entries (the action
identifier strings "actions/github-script@v9",
"actions/create-github-app-token@v3", and the reusable workflow reference
"norrietaylor/spectacles/.github/workflows/sdd-execute-opus.lock.yml@main") to
their corresponding immutable full commit SHAs (replace the `@vX/`@main refs with
@<full-commit-sha>) so the workflow consistently executes the exact intended
commits; locate and replace each occurrence (the one at the shown line and the
other occurrences around lines referenced in the review) ensuring the SHAs come
from the exact upstream repository commit you want pinned.

In @.github/workflows/sdd-execute-sonnet.yml:
- Line 112: The group expression currently uses startsWith('/execute') and
startsWith('/revise') which also match '/executefoo' and '/revisex'; change
those to token-strict checks by replacing startsWith('/execute') and
startsWith('/revise') with contains(concat(' ', github.event.comment.body, ' '),
' /execute ') and contains(concat(' ', github.event.comment.body, ' '), '
/revise ') respectively (apply same fix to the other occurrence in the
expression block referenced). Update the group expression where
github.event.comment.body is checked so the shared concurrency key only triggers
on exact token matches.
- Line 174: The workflow uses mutable tag refs for third-party actions and a
reusable workflow; find the exact commit SHAs for each referenced resource
(actions/github-script@v9,
norrietaylor/spectacles/.github/workflows/sdd-execute-sonnet.lock.yml@main,
actions/create-github-app-token@v3) and replace the tag refs with their
corresponding full commit SHAs (e.g. actions/github-script@<full-sha>) so the
workflow is pinned immutably; update each `uses:` entry to the SHA form and
optionally add a short comment noting the pinned SHA and original tag for future
maintainers.

In @.github/workflows/sdd-monitor.yml:
- Around line 331-389: The ready-count logic only parses "blocked by `#N`" (in the
block that builds blockerNums from body.matchAll) and thus ignores cross-repo
blockers; update the parser and blocker lookup so it recognizes both "blocked by
`#123`" and "blocked by owner/repo#123" by extracting optional owner and repo plus
the issue number (use the existing body variable and matchAll call but change
the regex and mapping to produce blocker descriptors rather than plain ints),
then when calling github.rest.issues.get use the matched owner/repo if present
or fall back to the current owner/repo variables (keep checking
got.data.pull_request and got.data.state as before); ensure error messages (the
core.info calls in the catch) include the fully qualified blocker identifier and
treat failures as still blocked exactly as existing logic does.
- Line 128: Pin the GitHub Actions to immutable commit SHAs by replacing uses:
actions/create-github-app-token@v3 and uses: actions/github-script@v9 with their
exact commit SHAs; in the create-github-app-token step (the App token minting
step) add an explicit permissions block requesting least-privilege (e.g.,
issues: read, contents: read, metadata: read — adjust to the exact minimal
scopes the monitor needs) instead of relying only on
app-id/private-key/owner/repositories; and tighten the “ready” computation by
expanding the blocker regex (currently /blocked by #(\d+)/) to also match
cross-repo forms like owner/repo#123 and full issue URLs (e.g.,
/([\w-]+\/[\w-]+)#(\d+)/ and URL patterns), ensure you resolve and check issues
across repos, and change the logic so an empty blocker set does NOT imply
allClosed (only mark allClosed when at least one blocker was detected and all
detected blockers are closed).
- Around line 126-133: The Mint App token step (name: "Mint App token", id: app,
uses: actions/create-github-app-token@v3) currently mints an installation token
with the App's full installation permissions; add minimal permission-* inputs to
the step's with: block to restrict the token (e.g., permission-issues: write for
the /dispatch comment and permission-contents: read for repo read access),
ensuring only required scopes are granted.

In @.github/workflows/sdd-pr-sanitize.yml:
- Line 66: The workflow is using a mutable tag "uses: actions/github-script@v9";
replace this with the full 40-character commit SHA for the actions/github-script
action (e.g. "uses: actions/github-script@<40-char-sha>  # v9") to pin the
dependency and add the trailing comment to indicate the intended tag; update the
line containing actions/github-script@v9 and mirror the same change for any
other workflows using the same mutable tag.

In @.github/workflows/sdd-review.yml:
- Line 55: The workflow currently references mutable refs (uses:
actions/github-script@v9 and uses:
norrietaylor/spectacles/.github/workflows/sdd-review.lock.yml@main); replace
each mutable tag with the corresponding immutable commit SHA for that
repository/use (pin both actions/github-script and norrietaylor/spectacles
references to their specific commit SHAs) so the workflow and hosted reusable
workflow are fixed to exact commits; update the two uses: entries (the
actions/github-script reference and the norrietaylor/spectacles reusable
workflow reference) to their respective SHAs and commit the change.

In @.github/workflows/sdd-spec.yml:
- Line 88: The workflow uses mutable action refs and mints an overly-broad App
token; replace each mutable `uses:` reference (e.g., actions/github-script@v9,
actions/create-github-app-token@v3,
norrietaylor/spectacles/.github/workflows/sdd-spec.lock.yml@main) with the
corresponding pinned commit SHA for that action, and for the steps that call
`actions/create-github-app-token@v3` narrow the minted installation token by
adding explicit permission scope inputs (and repository list if applicable) so
the token only has the minimum required permissions (e.g., specify permissions
like contents: read or issues: write as needed) and ensure the step uses the
pinned SHA as well.
- Around line 364-369: The GitHub App installation token steps using
actions/create-github-app-token@v3 currently omit explicit permissions and thus
inherit all app permissions; update each "Mint App token" step (the
actions/create-github-app-token@v3 invocation around the existing
app-id/private-key/owner/repositories inputs) to explicitly set
permission-issues: write so the token is least-privilege for the github-script
code that only calls issues.get, issues.listComments, issues.createComment and
issues.addLabels; apply the same change to the duplicate step later in the file.

In @.github/workflows/sdd-triage-dedupe-tasks.yml:
- Around line 102-123: The current dedupe picks `original` solely by normalized
`titleKey` using `isSameTitleEarlierTask`, which can false-match distinct
phase-C tasks; update the matching to compute and compare a stronger fingerprint
(e.g., combine normalized title plus additional distinguishing fields such as
repository identifier, blocker list, and proof-artifact markers parsed from the
issue body/labels) when filtering `siblings` before assigning `original` (adjust
the predicate used in `.filter(...)` instead of `isSameTitleEarlierTask`); apply
the same stronger-fingerprint comparison in the analogous block later (lines
referenced in the review) so we only auto-close a sibling when title plus those
extra fields match.
- Line 40: Replace the mutable tag usage "uses: actions/github-script@v9" with
the full immutable commit SHA for the v9 release (e.g., "uses:
actions/github-script@<full-commit-sha>"); locate the line containing uses:
actions/github-script@v9 in the workflow and update it to reference the
full-length Git commit SHA from the actions/github-script repository to remove
supply-chain risk.

In @.github/workflows/sdd-triage-promote-ready.yml:
- Line 60: Replace the mutable action reference "uses: actions/github-script@v9"
with the immutable commit SHA for the intended v9 release: fetch the exact
commit SHA from the actions/github-script v9 release on GitHub and update the
workflow entry to "uses: actions/github-script@<COMMIT_SHA>"; apply the same
replacement across all workflows that currently use "`@v9`" to ensure consistency
and immutability.

In @.github/workflows/sdd-triage.yml:
- Line 81: Replace the mutable action refs with immutable commit SHAs: locate
the uses entry "uses: actions/github-script@v9" and replace the tag with the
specific commit SHA for the actions/github-script repository, and locate the
reference "norrietaylor/spectacles/.github/workflows/sdd-triage.lock.yml@main"
and replace the branch ref with that repository's exact commit SHA; ensure you
fetch the correct commit SHAs from the respective upstream repos/tags and update
both places so the workflow pins to the immutable SHAs rather than `@v9` or `@main`.
- Around line 89-101: The issue: the 'issues' event handler sets run and ctx for
any label change without ensuring the issue is a top-level tracking issue; to
fix, restrict the labeled/unlabeled checks inside the event === 'issues' branch
to only proceed when the issue is a tracking issue (no parent). Concretely,
inside that branch where action, label, run, and ctx are used, add a guard that
checks the payload.issue is top-level (e.g., !payload.issue.parent or equivalent
field your payload uses to indicate a parent) before evaluating the 'labeled
sdd:triage' or 'unlabeled needs-human' conditions and before setting ctx = {
item_type: 'issue', item_number: payload.issue.number } so sub-issues won't
trigger triage routing.

---

Minor comments:
In @.github/workflows/sdd-execute-haiku.yml:
- Around line 481-507: The code currently treats an issue as a "tracking" issue
by checking parent_issue_url (parentUrl) which is unreliable; change the
fast-path discriminator to detect tracking vs task via stable model:* labels
instead: after fetching issue labels in the github.rest.issues.get response,
compute a boolean like isTracking = labels.includes('model:tracking') (or
another agreed stable model label) and replace the existing check that uses
parentUrl (the "!parentUrl && fastpathSignal" condition) with "isTracking &&
fastpathSignal"; remove reliance on parent_issue_url entirely when deciding
isFastpath so fast-path detection uses only labels and the existing
sdd:in-progress / sdd:dispatched checks.

In @.github/workflows/sdd-validate.yml:
- Around line 109-126: The parent-issue lookup can fail and currently falls
through treating the issue as a tracking issue; update the catch block around
github.rest.issues.get to fail closed by setting run = false (or otherwise
aborting further processing) and log the error with context (use
payload.issue.number and parentUrl variables), so that when an exception occurs
you do not proceed to set ctx/run for validation; modify the catch that
currently calls core.info to also prevent the else-branch from marking the issue
as a tracking issue (e.g., set run = false or return) and keep the existing log
via core.info.

---

Nitpick comments:
In @.github/workflows/sdd-execute-opus.yml:
- Around line 481-507: The code uses the REST field parent_issue_url (via
github.rest.issues.get for refNumber) to decide fast-path routing (logic in
fastpathSignal using parent_issue_url and labels), which is unreliable; replace
the REST parent check with a GraphQL query for the Issue.parent (or
alternatively a robust label-only invariant such as detecting absence of any
model:* labels) and base the fastpathSignal on that authoritative parent value
plus labels; update the block that reads parentUrl and computes fastpathSignal
(references: headRef, headMatch, refNumber, github.rest.issues.get,
parent_issue_url, fastpathSignal) to fetch Issue.parent via GitHub GraphQL and
use that result in place of parent_issue_url (or use the label-based check)
before evaluating the fast-path conditional.
🪄 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: 531e5bcd-15d5-4671-97bb-274f758f9b2e

📥 Commits

Reviewing files that changed from the base of the PR and between 422f245 and dbac3cd.

📒 Files selected for processing (18)
  • .github/ISSUE_TEMPLATE/bug.md
  • .github/ISSUE_TEMPLATE/chore.md
  • .github/ISSUE_TEMPLATE/feature.md
  • .github/ISSUE_TEMPLATE/spec.md
  • .github/workflows/distillery-sync.yml
  • .github/workflows/sdd-dispatch.yml
  • .github/workflows/sdd-execute-haiku.yml
  • .github/workflows/sdd-execute-opus.yml
  • .github/workflows/sdd-execute-sonnet.yml
  • .github/workflows/sdd-monitor.yml
  • .github/workflows/sdd-pr-sanitize.yml
  • .github/workflows/sdd-review.yml
  • .github/workflows/sdd-spec.yml
  • .github/workflows/sdd-triage-dedupe-tasks.yml
  • .github/workflows/sdd-triage-promote-ready.yml
  • .github/workflows/sdd-triage.yml
  • .github/workflows/sdd-validate.yml
  • .gitignore

steps:
- name: Decide whether to run and build aw_context
id: decide
uses: actions/github-script@v9
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Verify this file has no non-SHA uses refs.
rg -nP '^\s*uses:\s*[^@\s]+@(?!(?:[0-9a-fA-F]{40})\b)[^\s]+' .github/workflows/sdd-validate.yml
# Expected after fix: no output

Repository: gominimal/minimal

Length of output: 186


Pin GitHub Actions/workflow uses refs to commit SHAs in .github/workflows/sdd-validate.yml.

Current non-deterministic refs:

  • Line 68: actions/github-script@v9
  • Line 140: norrietaylor/spectacles/.github/workflows/sdd-validate.lock.yml@main
Suggested change
-        uses: actions/github-script@v9
+        uses: actions/github-script@<40-char-commit-sha>

-    uses: norrietaylor/spectacles/.github/workflows/sdd-validate.lock.yml@main
+    uses: norrietaylor/spectacles/.github/workflows/sdd-validate.lock.yml@<40-char-commit-sha>
🧰 Tools
🪛 zizmor (1.25.2)

[error] 68-68: unpinned action reference (unpinned-uses): action is not pinned to a hash (required by blanket policy)

(unpinned-uses)

🤖 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 @.github/workflows/sdd-validate.yml at line 68, The workflow uses
non-deterministic action refs; replace the floating refs
`actions/github-script@v9` and
`norrietaylor/spectacles/.github/workflows/sdd-validate.lock.yml@main` with
their corresponding pinned commit SHAs (exact full commit hashes) to ensure
reproducible runs—locate the `uses:` lines in .github/workflows/sdd-validate.yml
and update each `uses:` value to the full git sha for the referenced repository
and path.

@norrietaylor
Copy link
Copy Markdown
Member Author

Closing this will we tag upstream

@norrietaylor
norrietaylor deleted the spectacles/install branch May 29, 2026 06:17
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

needs-human An agent handed off; a human must act, then clear this label. Set by defer and handoffs.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

0