feat(triage): dry_run input + pre-dispatch fixes (#460)

Adds a dry_run dispatch input so the pipeline can be validated against
real issues without writing to the repo. Also folds in three items
from the #459 code review that are easier to ship before the first
round of dispatches than after.

## dry_run

- New boolean input on `workflow_dispatch` (default false)
- Guards `Apply labels` and `Post comment` steps
- Step summary shows a ⚠ banner + a "Dry run" row when enabled
- Artifacts still upload, so the rendered `comment.md` is inspectable

## Review fixups (from PR #459 review)

1. **Decision gate priority.** Spec §7 puts version drift ahead of
   fetch failure; implementation had them reversed. When both fire,
   `version-drift` is the more specific signal and is the only path
   that hands the maintainer drift-bridge candidates. Swapped.
2. **Issue titles wrapped as untrusted.** `<issue_title>` now carries
   `source="reporter, untrusted"` in all three prompt assemblies
   (classify / doublecheck / investigate). Instruction-as-data
   directive in each prompt updated to name both `<issue_title>` and
   `<issue_body>`. Reporter-controlled title injection surface closed.
5. **`drift-bridge.sh` version search is literal.** `--fixed-strings`
   added to `git log --grep` so `1.3.23` doesn't match `1x3y23`.

Items 3, 4, 6-9 from the review are deferred to Phase 3 (adversarial
reviewer) per the review's own scoping.

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Aaddrick
2026-04-20 18:48:01 -04:00
committed by GitHub
parent 755bef4c28
commit 1de897f56e
5 changed files with 40 additions and 16 deletions

View File

@@ -17,6 +17,11 @@ on:
description: "Issue number to triage"
required: true
type: number
dry_run:
description: "Run the pipeline but skip posting the comment and applying labels (artifacts still uploaded)"
required: false
type: boolean
default: false
permissions:
issues: write
@@ -132,7 +137,7 @@ jobs:
{
cat .claude/scripts/prompts/classify.txt
echo ""
echo "<issue_title>${title}</issue_title>"
echo "<issue_title source=\"reporter, untrusted\">${title}</issue_title>"
echo ""
echo "<issue_body source=\"reporter, untrusted\">"
printf '%s\n' "${body}"
@@ -179,7 +184,7 @@ jobs:
{
cat .claude/scripts/prompts/classify-doublecheck-bugfeature.txt
echo ""
echo "<issue_title>${title}</issue_title>"
echo "<issue_title source=\"reporter, untrusted\">${title}</issue_title>"
echo ""
echo "<issue_body source=\"reporter, untrusted\">"
printf '%s\n' "${body}"
@@ -335,7 +340,7 @@ jobs:
printf '%s\n' "${classification}"
echo '```'
echo ""
echo "<issue_title>${title}</issue_title>"
echo "<issue_title source=\"reporter, untrusted\">${title}</issue_title>"
echo ""
echo "<issue_body source=\"reporter, untrusted\">"
printf '%s\n' "${body}"
@@ -459,12 +464,17 @@ jobs:
passed="${{ steps.validate.outputs.findings_passed }}"
avg="${{ steps.validate.outputs.avg_confidence }}"
if [[ "${fetch_ok}" != "true" ]]; then
variant=8b
reason_id=reference-source-unavailable
elif [[ "${drift}" == "true" ]]; then
# Priority per spec §7 — drift first. When drift and fetch
# both fire, version-drift gives the maintainer a more
# specific signal than reference-source-unavailable (and is
# the only path that hands them drift-bridge candidates, when
# investigation produced files to sweep against).
if [[ "${drift}" == "true" ]]; then
variant=8b
reason_id=version-drift
elif [[ "${fetch_ok}" != "true" ]]; then
variant=8b
reason_id=reference-source-unavailable
elif [[ "${invest_ok}" != "true" ]]; then
variant=8b
reason_id=no-findings
@@ -715,7 +725,10 @@ jobs:
# Stage 9 — labels. 8a → triage: investigated. 8b → triage: needs-
# human / needs-info / not-actionable per classification. Phase 2
# doesn't promote `triage: duplicate` yet (needs Stage 6 confirm).
# Skipped under dry_run — the step-summary table still shows what
# would have been applied.
- name: Apply labels
if: inputs.dry_run != true
env:
GH_TOKEN: ${{ github.token }}
run: |
@@ -798,6 +811,7 @@ jobs:
done
- name: Post comment
if: inputs.dry_run != true
env:
GH_TOKEN: ${{ github.token }}
run: |
@@ -815,13 +829,20 @@ jobs:
FINDINGS_TOTAL: ${{ steps.validate.outputs.findings_total }}
FINDINGS_PASSED: ${{ steps.validate.outputs.findings_passed }}
DRIFT_DETECTED: ${{ steps.drift.outputs.drift_detected }}
DRY_RUN: ${{ inputs.dry_run }}
run: |
{
echo "## Triage v2 — Phase 2"
echo ""
if [[ "${DRY_RUN}" == "true" ]]; then
echo "> ⚠ **Dry run** — no comment posted, no labels applied."
echo "> Inspect the rendered comment under the uploaded \`comment.md\` artifact."
echo ""
fi
echo "| Metric | Value |"
echo "|---|---|"
echo "| Issue | #${ISSUE_NUMBER} |"
echo "| Dry run | ${DRY_RUN:-false} |"
echo "| Classification | ${CLASSIFICATION} |"
echo "| Confidence | ${CONFIDENCE} |"
echo "| Doublecheck disagreed | ${DISAGREED:-n/a} |"
@@ -829,7 +850,7 @@ jobs:
echo "| Findings proposed | ${FINDINGS_TOTAL:-0} |"
echo "| Findings passed mechanical | ${FINDINGS_PASSED:-0} |"
echo "| Findings passed review | n/a (Phase 3) |"
echo "| Comment variant posted | ${VARIANT} |"
echo "| Comment variant rendered | ${VARIANT} |"
echo "| Deferral reason (if applicable) | ${REASON_TEXT:-n/a} |"
} >> "$GITHUB_STEP_SUMMARY"