From dd053f4a38f091cfba72ebaf7af0bb7489b2ecdb Mon Sep 17 00:00:00 2001 From: Garry Tan Date: Fri, 17 Apr 2026 06:03:00 +0800 Subject: [PATCH] feat: subagent isolation for /ship's 4 context-heaviest sub-workflows MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fights context rot. By late /ship, the parent context is bloated with 500-1,750 lines of intermediate tool output from tests, coverage audits, reviews, adversarial checks, and PR body construction. The model is at its least intelligent when it reaches doc-sync — which is why /document-release was being skipped ~80% of the time. Applies subagent dispatch (proven pattern from Review Army at Step 9.1 and Adversarial at Step 11) to four sub-workflows where the parent only needs the conclusion, not the intermediate output: - Step 7 (Test Coverage Audit) — subagent returns coverage_pct, gaps, diagram, tests_added - Step 8 (Plan Completion Audit) — subagent returns total_items, done, changed, deferred, summary - Step 10 (Greptile Triage) — subagent fetches + classifies, parent handles user interaction and commits fixes (AskUserQuestion + Edit can't run in subagents) - Step 18 (Documentation Sync) — subagent invokes full /document-release skill in fresh context; parent embeds documentation_section in PR body Sequencing fix for Step 18: runs AFTER Step 17 (Push) and BEFORE Step 19 (Create PR). The PR is created once from final HEAD with the ## Documentation section baked into the initial body — no create-then- re-edit dance, no race conditions with document-release's own PR body editor. Adds "You are NOT done" guardrail after Step 17 (Push) to break the natural stopping point that currently causes doc-release skips. Each subagent falls back to inline execution if it fails or returns invalid JSON. /ship never blocks on subagent failure. Co-Authored-By: Claude Opus 4.7 (1M context) --- ship/SKILL.md | 125 +++++++++++++++++++++++++++++++++------------ ship/SKILL.md.tmpl | 125 +++++++++++++++++++++++++++++++++------------ 2 files changed, 182 insertions(+), 68 deletions(-) diff --git a/ship/SKILL.md b/ship/SKILL.md index a8b6a75e..db978bd2 100644 --- a/ship/SKILL.md +++ b/ship/SKILL.md @@ -1099,7 +1099,13 @@ If multiple suites need to run, run them sequentially (each needs a test lane). ## Step 7: Test Coverage Audit -100% coverage is the goal — every untested path is a path where bugs hide and vibe coding becomes yolo coding. Evaluate what was ACTUALLY coded (from the diff), not what was planned. +**Dispatch this step as a subagent** using the Agent tool with `subagent_type: "general-purpose"`. The subagent runs the coverage audit in a fresh context window — the parent only sees the conclusion, not intermediate file reads. This is context-rot defense. + +**Subagent prompt:** Pass the following instructions to the subagent, with `` substituted with the base branch: + +> You are running a ship-workflow test coverage audit. Run `git diff ...HEAD` as needed. Do not commit or push — report only. +> +> 100% coverage is the goal — every untested path is a path where bugs hide and vibe coding becomes yolo coding. Evaluate what was ACTUALLY coded (from the diff), not what was planned. ### Test Framework Detection @@ -1356,12 +1362,30 @@ Repo: {owner/repo} ## Critical Paths - {end-to-end flow that must work} ``` +> +> After your analysis, output a single JSON object on the LAST LINE of your response (no other text after it): +> `{"coverage_pct":N,"gaps":N,"diagram":"","tests_added":["path",...]}` + +**Parent processing:** + +1. Read the subagent's final output. Parse the LAST line as JSON. +2. Store `coverage_pct` (for Step 20 metrics), `gaps` (user summary), `tests_added` (for the commit). +3. Embed `diagram` verbatim in the PR body's `## Test Coverage` section (Step 19). +4. Print a one-line summary: `Coverage: {coverage_pct}%, {gaps} gaps. {tests_added.length} tests added.` + +**If the subagent fails, times out, or returns invalid JSON:** Fall back to running the audit inline in the parent. Do not block /ship on subagent failure — partial results are better than none. --- ## Step 8: Plan Completion Audit -### Plan File Discovery +**Dispatch this step as a subagent** using the Agent tool with `subagent_type: "general-purpose"`. The subagent reads the plan file and every referenced code file in its own fresh context. Parent gets only the conclusion. + +**Subagent prompt:** Pass these instructions to the subagent: + +> You are running a ship-workflow plan completion audit. The base branch is ``. Use `git diff ...HEAD` to see what shipped. Do not commit or push — report only. +> +> ### Plan File Discovery 1. **Conversation context (primary):** Check if there is an active plan file in this conversation. The host agent's system messages include plan file paths when in plan mode. If found, use it directly — this is the most reliable signal. @@ -1477,6 +1501,18 @@ After producing the completion checklist: **No plan file found:** Skip entirely. "No plan file detected — skipping plan completion audit." **Include in PR body (Step 8):** Add a `## Plan Completion` section with the checklist summary. +> +> After your analysis, output a single JSON object on the LAST LINE of your response (no other text after it): +> `{"total_items":N,"done":N,"changed":N,"deferred":N,"summary":""}` + +**Parent processing:** + +1. Parse the LAST line of the subagent's output as JSON. +2. Store `done`, `deferred` for Step 20 metrics; use `summary` in PR body. +3. If `deferred > 0` and no user override, present the deferred items via AskUserQuestion before continuing. +4. Embed `summary` in PR body's `## Plan Completion` section (Step 19). + +**If the subagent fails or returns invalid JSON:** Fall back to running the audit inline. Never block /ship on subagent failure. --- @@ -1981,17 +2017,28 @@ Save the review output — it goes into the PR body in Step 19. ## Step 10: Address Greptile review comments (if PR exists) -Read `.claude/skills/review/greptile-triage.md` and follow the fetch, filter, classify, and **escalation detection** steps. +**Dispatch the fetch + classification as a subagent** using the Agent tool with `subagent_type: "general-purpose"`. The subagent pulls every Greptile comment, runs the escalation detection algorithm, and classifies each comment. Parent receives a structured list and handles user interaction + file edits. -**If no PR exists, `gh` fails, API returns an error, or there are zero Greptile comments:** Skip this step silently. Continue to Step 12. +**Subagent prompt:** -**If Greptile comments are found:** +> You are classifying Greptile review comments for a /ship workflow. Read `.claude/skills/review/greptile-triage.md` and follow the fetch, filter, classify, and **escalation detection** steps. Do NOT fix code, do NOT reply to comments, do NOT commit — report only. +> +> For each comment, assign: `classification` (`valid_actionable`, `already_fixed`, `false_positive`, `suppressed`), `escalation_tier` (1 or 2), the file:line or [top-level] tag, body summary, and permalink URL. +> +> If no PR exists, `gh` fails, the API errors, or there are zero comments, output: `{"total":0,"comments":[]}` and stop. +> +> Otherwise, output a single JSON object on the LAST LINE of your response: +> `{"total":N,"comments":[{"classification":"...","escalation_tier":N,"ref":"file:line","summary":"...","permalink":"url"},...]}` -Include a Greptile summary in your output: `+ N Greptile comments (X valid, Y fixed, Z FP)` +**Parent processing:** -Before replying to any comment, run the **Escalation Detection** algorithm from greptile-triage.md to determine whether to use Tier 1 (friendly) or Tier 2 (firm) reply templates. +Parse the LAST line as JSON. -For each classified comment: +If `total` is 0, skip this step silently. Continue to Step 12. + +Otherwise, print: `+ {total} Greptile comments ({valid_actionable} valid, {already_fixed} already fixed, {false_positive} FP)`. + +For each comment in `comments`: **VALID & ACTIONABLE:** Use AskUserQuestion with: - The comment (file:line or [top-level] + body summary + permalink URL) @@ -2370,12 +2417,41 @@ echo "LOCAL: $LOCAL REMOTE: $REMOTE" [ "$LOCAL" = "$REMOTE" ] && echo "ALREADY_PUSHED" || echo "PUSH_NEEDED" ``` -If `ALREADY_PUSHED`, skip the push but continue to Step 19. Otherwise push with upstream tracking: +If `ALREADY_PUSHED`, skip the push but continue to Step 18. Otherwise push with upstream tracking: ```bash git push -u origin ``` +**You are NOT done.** The code is pushed but documentation sync and PR creation are mandatory final steps. Continue to Step 18. + +--- + +## Step 18: Documentation sync (via subagent, before PR creation) + +**Dispatch /document-release as a subagent** using the Agent tool with `subagent_type: "general-purpose"`. The subagent gets a fresh context window — zero rot from the preceding 17 steps. It also runs the **full** `/document-release` workflow (with CHANGELOG clobber protection, doc exclusions, risky-change gates, named staging, race-safe PR body editing) rather than a weaker reimplementation. + +**Sequencing:** This step runs AFTER Step 17 (Push) and BEFORE Step 19 (Create PR). The PR is created once from final HEAD with the `## Documentation` section baked into the initial body. No create-then-re-edit dance. + +**Subagent prompt:** + +> You are executing the /document-release workflow after a code push. Read the full skill file `${HOME}/.claude/skills/gstack/document-release/SKILL.md` and execute its complete workflow end-to-end, including CHANGELOG clobber protection, doc exclusions, risky-change gates, and named staging. Do NOT attempt to edit the PR body — no PR exists yet. Branch: ``, base: ``. +> +> After completing the workflow, output a single JSON object on the LAST LINE of your response (no other text after it): +> `{"files_updated":["README.md","CLAUDE.md",...],"commit_sha":"abc1234","pushed":true,"documentation_section":""}` +> +> If no documentation files needed updating, output: +> `{"files_updated":[],"commit_sha":null,"pushed":false,"documentation_section":null}` + +**Parent processing:** + +1. Parse the LAST line of the subagent's output as JSON. +2. Store `documentation_section` — Step 19 embeds it in the PR body (or omits the section if null). +3. If `files_updated` is non-empty, print: `Documentation synced: {files_updated.length} files updated, committed as {commit_sha}`. +4. If `files_updated` is empty, print: `Documentation is current — no updates needed.` + +**If the subagent fails or returns invalid JSON:** Print a warning and proceed to Step 19 without a `## Documentation` section. Do not block /ship on subagent failure. The user can run `/document-release` manually after the PR lands. + --- ## Step 19: Create PR/MR @@ -2392,7 +2468,7 @@ gh pr view --json url,number,state -q 'if .state == "OPEN" then "PR #\(.number): glab mr view -F json 2>/dev/null | jq -r 'if .state == "opened" then "MR_EXISTS" else "NO_MR" end' 2>/dev/null || echo "NO_MR" ``` -If an **open** PR/MR already exists: **update** the PR body using `gh pr edit --body "..."` (GitHub) or `glab mr update -d "..."` (GitLab). Always regenerate the PR body from scratch using this run's fresh results (test output, coverage audit, review findings, adversarial review, TODOS summary). Never reuse stale PR body content from a prior run. Print the existing URL and continue to Step 18. +If an **open** PR/MR already exists: **update** the PR body using `gh pr edit --body "..."` (GitHub) or `glab mr update -d "..."` (GitLab). Always regenerate the PR body from scratch using this run's fresh results (test output, coverage audit, review findings, adversarial review, TODOS summary, documentation_section from Step 18). Never reuse stale PR body content from a prior run. Print the existing URL and continue to Step 20. If no PR/MR exists: create a pull request (GitHub) or merge request (GitLab) using the platform detected in Step 0. @@ -2446,6 +2522,10 @@ you missed it.> +## Documentation + + + ## Test plan - [x] All Rails tests pass (N runs, 0 failures) - [x] All Vitest tests pass (N tests) @@ -2474,30 +2554,7 @@ EOF **If neither CLI is available:** Print the branch name, remote URL, and instruct the user to create the PR/MR manually via the web UI. Do not stop — the code is pushed and ready. -**Output the PR/MR URL** — then proceed to Step 18. - ---- - -## Step 18: Auto-invoke /document-release - -After the PR is created, automatically sync project documentation. Read the -`document-release/SKILL.md` skill file (adjacent to this skill's directory) and -execute its full workflow: - -1. Read the `/document-release` skill: `cat ${CLAUDE_SKILL_DIR}/../document-release/SKILL.md` -2. Follow its instructions — it reads all .md files in the project, cross-references - the diff, and updates anything that drifted (README, ARCHITECTURE, CONTRIBUTING, - CLAUDE.md, TODOS, etc.) -3. If any docs were updated, commit the changes and push to the same branch: - ```bash - git add -A && git commit -m "docs: sync documentation with shipped changes" && git push - ``` -4. If no docs needed updating, say "Documentation is current — no updates needed." - -This step is automatic. Do not ask the user for confirmation. The goal is zero-friction -doc updates — the user runs `/ship` and documentation stays current without a separate command. - -If Step 18 created a docs commit, re-edit the PR/MR body to include the latest commit SHA in the summary. This ensures the PR body reflects the truly final state after document-release. +**Output the PR/MR URL** — then proceed to Step 20. --- diff --git a/ship/SKILL.md.tmpl b/ship/SKILL.md.tmpl index 9de170d7..05291c6d 100644 --- a/ship/SKILL.md.tmpl +++ b/ship/SKILL.md.tmpl @@ -228,13 +228,49 @@ If multiple suites need to run, run them sequentially (each needs a test lane). ## Step 7: Test Coverage Audit -{{TEST_COVERAGE_AUDIT_SHIP}} +**Dispatch this step as a subagent** using the Agent tool with `subagent_type: "general-purpose"`. The subagent runs the coverage audit in a fresh context window — the parent only sees the conclusion, not intermediate file reads. This is context-rot defense. + +**Subagent prompt:** Pass the following instructions to the subagent, with `` substituted with the base branch: + +> You are running a ship-workflow test coverage audit. Run `git diff ...HEAD` as needed. Do not commit or push — report only. +> +> {{TEST_COVERAGE_AUDIT_SHIP}} +> +> After your analysis, output a single JSON object on the LAST LINE of your response (no other text after it): +> `{"coverage_pct":N,"gaps":N,"diagram":"","tests_added":["path",...]}` + +**Parent processing:** + +1. Read the subagent's final output. Parse the LAST line as JSON. +2. Store `coverage_pct` (for Step 20 metrics), `gaps` (user summary), `tests_added` (for the commit). +3. Embed `diagram` verbatim in the PR body's `## Test Coverage` section (Step 19). +4. Print a one-line summary: `Coverage: {coverage_pct}%, {gaps} gaps. {tests_added.length} tests added.` + +**If the subagent fails, times out, or returns invalid JSON:** Fall back to running the audit inline in the parent. Do not block /ship on subagent failure — partial results are better than none. --- ## Step 8: Plan Completion Audit -{{PLAN_COMPLETION_AUDIT_SHIP}} +**Dispatch this step as a subagent** using the Agent tool with `subagent_type: "general-purpose"`. The subagent reads the plan file and every referenced code file in its own fresh context. Parent gets only the conclusion. + +**Subagent prompt:** Pass these instructions to the subagent: + +> You are running a ship-workflow plan completion audit. The base branch is ``. Use `git diff ...HEAD` to see what shipped. Do not commit or push — report only. +> +> {{PLAN_COMPLETION_AUDIT_SHIP}} +> +> After your analysis, output a single JSON object on the LAST LINE of your response (no other text after it): +> `{"total_items":N,"done":N,"changed":N,"deferred":N,"summary":""}` + +**Parent processing:** + +1. Parse the LAST line of the subagent's output as JSON. +2. Store `done`, `deferred` for Step 20 metrics; use `summary` in PR body. +3. If `deferred > 0` and no user override, present the deferred items via AskUserQuestion before continuing. +4. Embed `summary` in PR body's `## Plan Completion` section (Step 19). + +**If the subagent fails or returns invalid JSON:** Fall back to running the audit inline. Never block /ship on subagent failure. --- @@ -304,17 +340,28 @@ Save the review output — it goes into the PR body in Step 19. ## Step 10: Address Greptile review comments (if PR exists) -Read `.claude/skills/review/greptile-triage.md` and follow the fetch, filter, classify, and **escalation detection** steps. +**Dispatch the fetch + classification as a subagent** using the Agent tool with `subagent_type: "general-purpose"`. The subagent pulls every Greptile comment, runs the escalation detection algorithm, and classifies each comment. Parent receives a structured list and handles user interaction + file edits. -**If no PR exists, `gh` fails, API returns an error, or there are zero Greptile comments:** Skip this step silently. Continue to Step 12. +**Subagent prompt:** -**If Greptile comments are found:** +> You are classifying Greptile review comments for a /ship workflow. Read `.claude/skills/review/greptile-triage.md` and follow the fetch, filter, classify, and **escalation detection** steps. Do NOT fix code, do NOT reply to comments, do NOT commit — report only. +> +> For each comment, assign: `classification` (`valid_actionable`, `already_fixed`, `false_positive`, `suppressed`), `escalation_tier` (1 or 2), the file:line or [top-level] tag, body summary, and permalink URL. +> +> If no PR exists, `gh` fails, the API errors, or there are zero comments, output: `{"total":0,"comments":[]}` and stop. +> +> Otherwise, output a single JSON object on the LAST LINE of your response: +> `{"total":N,"comments":[{"classification":"...","escalation_tier":N,"ref":"file:line","summary":"...","permalink":"url"},...]}` -Include a Greptile summary in your output: `+ N Greptile comments (X valid, Y fixed, Z FP)` +**Parent processing:** -Before replying to any comment, run the **Escalation Detection** algorithm from greptile-triage.md to determine whether to use Tier 1 (friendly) or Tier 2 (firm) reply templates. +Parse the LAST line as JSON. -For each classified comment: +If `total` is 0, skip this step silently. Continue to Step 12. + +Otherwise, print: `+ {total} Greptile comments ({valid_actionable} valid, {already_fixed} already fixed, {false_positive} FP)`. + +For each comment in `comments`: **VALID & ACTIONABLE:** Use AskUserQuestion with: - The comment (file:line or [top-level] + body summary + permalink URL) @@ -507,12 +554,41 @@ echo "LOCAL: $LOCAL REMOTE: $REMOTE" [ "$LOCAL" = "$REMOTE" ] && echo "ALREADY_PUSHED" || echo "PUSH_NEEDED" ``` -If `ALREADY_PUSHED`, skip the push but continue to Step 19. Otherwise push with upstream tracking: +If `ALREADY_PUSHED`, skip the push but continue to Step 18. Otherwise push with upstream tracking: ```bash git push -u origin ``` +**You are NOT done.** The code is pushed but documentation sync and PR creation are mandatory final steps. Continue to Step 18. + +--- + +## Step 18: Documentation sync (via subagent, before PR creation) + +**Dispatch /document-release as a subagent** using the Agent tool with `subagent_type: "general-purpose"`. The subagent gets a fresh context window — zero rot from the preceding 17 steps. It also runs the **full** `/document-release` workflow (with CHANGELOG clobber protection, doc exclusions, risky-change gates, named staging, race-safe PR body editing) rather than a weaker reimplementation. + +**Sequencing:** This step runs AFTER Step 17 (Push) and BEFORE Step 19 (Create PR). The PR is created once from final HEAD with the `## Documentation` section baked into the initial body. No create-then-re-edit dance. + +**Subagent prompt:** + +> You are executing the /document-release workflow after a code push. Read the full skill file `${HOME}/.claude/skills/gstack/document-release/SKILL.md` and execute its complete workflow end-to-end, including CHANGELOG clobber protection, doc exclusions, risky-change gates, and named staging. Do NOT attempt to edit the PR body — no PR exists yet. Branch: ``, base: ``. +> +> After completing the workflow, output a single JSON object on the LAST LINE of your response (no other text after it): +> `{"files_updated":["README.md","CLAUDE.md",...],"commit_sha":"abc1234","pushed":true,"documentation_section":""}` +> +> If no documentation files needed updating, output: +> `{"files_updated":[],"commit_sha":null,"pushed":false,"documentation_section":null}` + +**Parent processing:** + +1. Parse the LAST line of the subagent's output as JSON. +2. Store `documentation_section` — Step 19 embeds it in the PR body (or omits the section if null). +3. If `files_updated` is non-empty, print: `Documentation synced: {files_updated.length} files updated, committed as {commit_sha}`. +4. If `files_updated` is empty, print: `Documentation is current — no updates needed.` + +**If the subagent fails or returns invalid JSON:** Print a warning and proceed to Step 19 without a `## Documentation` section. Do not block /ship on subagent failure. The user can run `/document-release` manually after the PR lands. + --- ## Step 19: Create PR/MR @@ -529,7 +605,7 @@ gh pr view --json url,number,state -q 'if .state == "OPEN" then "PR #\(.number): glab mr view -F json 2>/dev/null | jq -r 'if .state == "opened" then "MR_EXISTS" else "NO_MR" end' 2>/dev/null || echo "NO_MR" ``` -If an **open** PR/MR already exists: **update** the PR body using `gh pr edit --body "..."` (GitHub) or `glab mr update -d "..."` (GitLab). Always regenerate the PR body from scratch using this run's fresh results (test output, coverage audit, review findings, adversarial review, TODOS summary). Never reuse stale PR body content from a prior run. Print the existing URL and continue to Step 18. +If an **open** PR/MR already exists: **update** the PR body using `gh pr edit --body "..."` (GitHub) or `glab mr update -d "..."` (GitLab). Always regenerate the PR body from scratch using this run's fresh results (test output, coverage audit, review findings, adversarial review, TODOS summary, documentation_section from Step 18). Never reuse stale PR body content from a prior run. Print the existing URL and continue to Step 20. If no PR/MR exists: create a pull request (GitHub) or merge request (GitLab) using the platform detected in Step 0. @@ -583,6 +659,10 @@ you missed it.> +## Documentation + + + ## Test plan - [x] All Rails tests pass (N runs, 0 failures) - [x] All Vitest tests pass (N tests) @@ -611,30 +691,7 @@ EOF **If neither CLI is available:** Print the branch name, remote URL, and instruct the user to create the PR/MR manually via the web UI. Do not stop — the code is pushed and ready. -**Output the PR/MR URL** — then proceed to Step 18. - ---- - -## Step 18: Auto-invoke /document-release - -After the PR is created, automatically sync project documentation. Read the -`document-release/SKILL.md` skill file (adjacent to this skill's directory) and -execute its full workflow: - -1. Read the `/document-release` skill: `cat ${CLAUDE_SKILL_DIR}/../document-release/SKILL.md` -2. Follow its instructions — it reads all .md files in the project, cross-references - the diff, and updates anything that drifted (README, ARCHITECTURE, CONTRIBUTING, - CLAUDE.md, TODOS, etc.) -3. If any docs were updated, commit the changes and push to the same branch: - ```bash - git add -A && git commit -m "docs: sync documentation with shipped changes" && git push - ``` -4. If no docs needed updating, say "Documentation is current — no updates needed." - -This step is automatic. Do not ask the user for confirmation. The goal is zero-friction -doc updates — the user runs `/ship` and documentation stays current without a separate command. - -If Step 18 created a docs commit, re-edit the PR/MR body to include the latest commit SHA in the summary. This ensures the PR body reflects the truly final state after document-release. +**Output the PR/MR URL** — then proceed to Step 20. ---