diff --git a/plugins/plan-lifecycle/agents/plan-reviewer.md b/plugins/plan-lifecycle/agents/plan-reviewer.md
index 7a3ad319..7b9ea575 100644
--- a/plugins/plan-lifecycle/agents/plan-reviewer.md
+++ b/plugins/plan-lifecycle/agents/plan-reviewer.md
@@ -21,9 +21,11 @@ lifecycle change.
A plan-review finding is exactly one of `goal_fit`, `research_gap`, or `security_risk`; nothing else is a finding. A sufficient plan passes.
Report a `goal_fit` finding when the plan uses a temporary workaround instead of the root-cause fix, leaves a caller on a replaced path, or keeps an obsolete path, unless Open questions record user approval.
+Report a `goal_fit` finding when Acceptance lacks a check that the repository instructions require for the paths in the Steps Files cells.
Perform one review invocation and return one verdict. Never demand style,
naming, formatting, line counts, more citations, additional probes, mutation
-tests, extra acceptance rows, cosmetic work, or restructuring for its own sake.
+tests, acceptance rows beyond the checks that repository instructions require,
+cosmetic work, or restructuring for its own sake.
## Workflow
diff --git a/plugins/plan-lifecycle/skills/productivity/plan-manager/SKILL.md b/plugins/plan-lifecycle/skills/productivity/plan-manager/SKILL.md
index 6a1f05ae..64857070 100644
--- a/plugins/plan-lifecycle/skills/productivity/plan-manager/SKILL.md
+++ b/plugins/plan-lifecycle/skills/productivity/plan-manager/SKILL.md
@@ -5,7 +5,7 @@ user-invocable: true
metadata:
pattern: tool-wrapper
updated: "2026-10-19"
- content_hash: "243b9e9f8f3a8755b28f902d400f4e0de2bf40f46d42ea6a4530e44f2ec5d6d8"
+ content_hash: "bb1ee37a5ff78641994eb70b3c785307086998dd40a97623a9700e47d24837ca"
---
# Plan Manager
diff --git a/plugins/plan-lifecycle/skills/productivity/plan-manager/references/plan-contract.md b/plugins/plan-lifecycle/skills/productivity/plan-manager/references/plan-contract.md
index d48307a2..12b3bd3c 100644
--- a/plugins/plan-lifecycle/skills/productivity/plan-manager/references/plan-contract.md
+++ b/plugins/plan-lifecycle/skills/productivity/plan-manager/references/plan-contract.md
@@ -67,11 +67,15 @@ Use these table headers:
```
Each Steps row describes one bounded task. `#` is its display number. `Id` is
-its durable command identifier. `Files` defines scope. `Depends` is `-` or
+its durable command identifier. `Files` defines scope as comma-separated
+repository-relative paths; a path can be in backticks. `Depends` is `-` or
comma-separated display numbers or ids. The helper resolves ids to display
-numbers on write. `Done when` names observable completion, not activity.
-Acceptance rows identify a command and its expected result. They cover the goal,
-not just individual steps. Use repository-relative paths and portable commands.
+numbers on write. Two steps that list the same path need a Depends chain
+between them, or one of them must own the shared edit. `Done when` names
+observable completion, not activity. Acceptance rows identify a command and its
+expected result. They cover the goal, not just individual steps. Acceptance also
+holds one row for each check that the repository instructions require for the
+paths in Files. Use repository-relative paths and portable commands.
| Token | Meaning |
|---|---|
@@ -115,6 +119,9 @@ may move to any open status. After it starts, only ongoing and blocked are legal
targets. Status writes repair phase labels. Blocked requires a single-line
reason at the start of Open questions. Leaving blocked removes that line.
+`planned` and `ongoing` require a filled Research section. The trimmed section
+must not be empty and must not equal `_Not researched yet._`.
+
An open plan must be ongoing for `step`. Its done and skipped steps are immutable.
Other open step transitions are free. A finished plan permits only done or skipped
targets for terminal repair. To move a step of a finished plan to another status, reopen the issue first. Other closed plans do not permit step transitions.
@@ -150,6 +157,12 @@ Run this review loop for plan review and for code review:
without a relevant change. If no answer is available, record the reason and
block. A yolo run uses the round ceiling in Yolo runs instead.
+A plan pass covers the body that the reviewer read. After `plan=pass`, an edit
+that changes Goal, Research, Steps, Acceptance, or Do not touch needs a new
+plan-review round before you write code that depends on the change. Edits to
+Verification Results or Open questions, and `step` state changes, need no new
+round.
+
Record user-only decisions in Open questions. Do not invent approval.
The helper finds review records in comments with these rules. The reviewer
@@ -202,7 +215,9 @@ Scratch lives at `git rev-parse --git-path docks-review`, with directory mode
0700. Export writes the body and a `.origin` sidecar containing its sha256 hex
plus newline, with sidecar mode 0600. The digest detects staleness; it grants no
permission. Edit validates provenance, normalizes the file, applies the freeze,
-and refreshes the sidecar before the remote write. Re-export after remote changes.
+writes the remote body, and then refreshes the sidecar. A failed remote write
+leaves the sidecar unchanged, so the same file stays valid for a retry.
+Re-export after remote changes.
Keep scratch and machine paths out of durable records. No queue file is tool input.
## Normalization, advice, and refusals
@@ -217,9 +232,12 @@ rewrite never drops the row. Once work has started, `edit` also refuses while
the current record holds such a row. A duplicate or unknown `##` heading blocks every body write; a retired `## Review` heading is dropped on the next write. An explicit `--mode` on `new` outranks any Mode line inside `--goal`; otherwise the last unfenced, unindented Mode line in Goal is the decision.
Advice prints `advice: ` on stdout and exits 0. It covers absolute machine
-paths, `_Not researched yet._` after drafting, defaulted Mode, unknown Effect or
-Status, an unfinished dependency when a step moves to in-flight or done, and
-every non-local Effect as an in-session permission reminder. A machine path
+paths, a Research section that holds only `_Not researched yet._` after drafting,
+defaulted Mode, unknown Effect or Status, an unfinished dependency when a step
+moves to in-flight or done, two steps that share a Files path with no Depends
+chain, an `edit` that changes Goal, Research, Steps, Acceptance, or Do not touch
+after a trusted plan pass, and every non-local Effect as an in-session
+permission reminder. A machine path
points into a user or root home, a mount, a named temporary or runtime
directory, a Windows drive, or a network share. Its advice is
`body contains an absolute machine path: `, once per distinct path.
@@ -240,6 +258,7 @@ stale export: was exported from a superseded body; run `plan.mjs export <
step state is frozen once work starts:
plan # is closed; status applies to open plans
illegal plan status transition: ->
+status requires a filled Research section
illegal step status transition: ->
plan # is finished; reopen it before moving step to
plan status is ; expected ongoing
@@ -258,6 +277,24 @@ blocked status requires --reason as single-line text
reserved label namespace:
```
+GitHub call failures exit 1 and print one of these messages on stderr. `edit`,
+`status`, and `step` print their stdout result only after the write succeeds.
+
+```text
+gh failed: >
+gh api graphql returned errors:
+```
+
+A failure can be short-lived, such as
+`GraphQL: Something went wrong while executing your query`. After such a
+failure:
+
+1. Run `show ` and check whether the change is on the issue.
+2. If it is not, run the same command once more. For `edit`, use the same file.
+3. If it is, do not run the command again. Run `export ` before the next body
+ edit.
+4. If the second run fails, stop the phase and report the message.
+
## Landing and archive proof
### Linked branch
@@ -337,6 +374,7 @@ question. All other contract rules stay in force.
| Contract question | Yolo rule |
|---|---|
| Path and Mode | Plan with `Mode: plan-and-implement` |
+| Request that names an existing plan, or an incomplete request | The yolo skill's Existing plans and incomplete requests rule |
| Non-local Effect step before the landing merge | The effect table below |
| Linked-branch timing | Right after `new`, while the plan is drafting |
| Dirty checkout at linked-branch setup | A separate clean worktree, without a question |
diff --git a/plugins/plan-lifecycle/skills/productivity/plan-manager/scripts/plan.mjs b/plugins/plan-lifecycle/skills/productivity/plan-manager/scripts/plan.mjs
index 841b615f..dd2c7e12 100755
--- a/plugins/plan-lifecycle/skills/productivity/plan-manager/scripts/plan.mjs
+++ b/plugins/plan-lifecycle/skills/productivity/plan-manager/scripts/plan.mjs
@@ -520,6 +520,29 @@ function replaceTable(sections, name, header, separator, rows) {
);
sections.set(name, parsed.lines.join('\n'));
}
+// Steps that edit the same path need a Depends order, or parallel work collides (#78).
+function sharedFilesAdvice(rows) {
+ const paths = rows.map(
+ (row) => new Set(row[3].split(',').map((part) => unquoteCode(part.trim())).filter((part) => part && part !== '-')),
+ );
+ const dependencies = new Map(
+ rows.map((row) => [row[0], row[4] === '-' ? [] : row[4].split(',').map((part) => part.trim())]),
+ );
+ const reaches = (from, to, seen = new Set()) => {
+ if (seen.has(from)) return false;
+ seen.add(from);
+ return (dependencies.get(from) ?? []).some((next) => next === to || reaches(next, to, seen));
+ };
+ const advice = [];
+ rows.forEach((row, index) => {
+ for (let other = index + 1; other < rows.length; other += 1) {
+ const shared = [...paths[index]].filter((entry) => paths[other].has(entry));
+ if (shared.length && !reaches(row[0], rows[other][0]) && !reaches(rows[other][0], row[0]))
+ advice.push(`steps ${row[1]} and ${rows[other][1]} share ${shared.join(', ')} with no Depends link`);
+ }
+ });
+ return advice;
+}
export function normalizePlan(text, { mode: explicitMode } = {}) {
const advice = [];
const cleaned = text
@@ -559,6 +582,7 @@ export function normalizePlan(text, { mode: explicitMode } = {}) {
};
for (const row of rows)
if (row.length === 8) row[4] = row[4] === '-' ? '-' : row[4].split(',').map(dependency).join(', ');
+ advice.push(...sharedFilesAdvice(rows.filter((row) => row.length === 8)));
replaceTable(sections, 'Steps', STEPS_HEADER, STEPS_SEPARATOR, rows);
replaceTable(
sections,
@@ -595,12 +619,23 @@ export function parsePlan(text) {
acceptance: table(sections.get('Acceptance'), ACCEPTANCE_HEADER).rows,
};
}
+// A drafted plan holds this placeholder until research is published (#78).
+const RESEARCH_PLACEHOLDER = '_Not researched yet._';
+// Compare the whole section: research prose may quote the placeholder.
+function researchFilled(plan) {
+ const research = plan.sections.get('Research').trim();
+ return research !== '' && research !== RESEARCH_PLACEHOLDER;
+}
function printAdvice(text, status, extra = [], options = {}) {
const result = normalizePlan(text, options);
- if (status !== 'drafting' && parsePlan(text).sections.get('Research').includes('_Not researched yet._'))
- result.advice.push('Research is not filled after drafting.');
+ if (status !== 'drafting' && !researchFilled(parsePlan(text))) result.advice.push('Research is not filled after drafting.');
for (const message of [...result.advice, ...extra]) console.log(`advice: ${message}`);
}
+// A plan-review pass covers the body that the reviewer read (#78).
+const PLANNED_WORK_SECTIONS = ['Goal', 'Research', 'Steps', 'Acceptance', 'Do not touch'];
+function plannedWorkChanges(before, after) {
+ return PLANNED_WORK_SECTIONS.filter((name) => before.sections.get(name) !== after.sections.get(name));
+}
// Reviewers write Markdown: read review keywords through emphasis, code marks, quotes, and list markers (#49).
function reviewText(line) {
return line
@@ -679,7 +714,7 @@ function createPlan(args) {
return /^ {0,3}##[ \t]/.test(masked) ? line.replace(/^( {0,3})##/, '$1###') : line;
})
.join('\n');
- const source = `## Goal\n${goalText}\n## Research\n_Not researched yet._\n## Steps\n${STEPS_HEADER}\n${STEPS_SEPARATOR}\n## Acceptance\n${ACCEPTANCE_HEADER}\n${ACCEPTANCE_SEPARATOR}`;
+ const source = `## Goal\n${goalText}\n## Research\n${RESEARCH_PLACEHOLDER}\n## Steps\n${STEPS_HEADER}\n${STEPS_SEPARATOR}\n## Acceptance\n${ACCEPTANCE_HEADER}\n${ACCEPTANCE_SEPARATOR}`;
const { body } = normalizePlan(source, { mode: options['--mode'] });
const url = withBodyFile(body, (file) =>
runGh([
@@ -742,7 +777,7 @@ function editPlan(args) {
const [value, ...flags] = args,
file = parseOptions(flags, new Set(['--file']))['--file'];
if (!file) fail('edit requires an issue and --file');
- const { issue, parsed, status } = readPlanIssue(value, true, { history: true }),
+ const { issue, parsed, status } = readPlanIssue(value, true, { history: true, comments: true }),
origin = readOrigin(file);
if (!origin)
fail(
@@ -782,14 +817,21 @@ function editPlan(args) {
});
}
fs.writeFileSync(file, body);
+ editIssueBodyIfUnchanged(issue, body);
+ // Refresh provenance only after the remote write, so a failed write can be retried with the same file (#78).
fs.writeFileSync(originFile(file), `${bodyDigest(body)}\n`, { mode: 0o600 });
fs.chmodSync(originFile(file), 0o600);
- editIssueBodyIfUnchanged(issue, body);
const changes = changedLines(issue.body, body);
console.log(headerStrip(issue, status));
console.log(`changed: ${changes.length} line(s)`);
for (const line of changes) console.log(line);
- printAdvice(source, status);
+ const changedWork = reviewVerdicts(issue).plan === 'pass' ? plannedWorkChanges(parsed, incomingPlan) : [];
+ const rereview = changedWork.length
+ ? [
+ `plan review passed before this edit; changed sections: ${changedWork.join(', ')}; run a new plan-review round before code that depends on them`,
+ ]
+ : [];
+ printAdvice(source, status, rereview);
}
function setPlanStatus(args) {
const [value, raw, ...flags] = args,
@@ -803,6 +845,8 @@ function setPlanStatus(args) {
fail(`plan #${issue.number} is closed; status applies to open plans`);
if (workStarted(issue, status) && !['ongoing', 'blocked'].includes(target))
fail(`illegal plan status transition: ${status} -> ${target}`);
+ if (['planned', 'ongoing'].includes(target) && !researchFilled(parsed))
+ fail(`status ${target} requires a filled Research section`);
const content = parsed.sections.get('Open questions').replace(/^Blocked:[^\n]*\n?\s*/i, '');
parsed.sections.set(
'Open questions',
diff --git a/plugins/plan-lifecycle/skills/productivity/plan-reviewer/SKILL.md b/plugins/plan-lifecycle/skills/productivity/plan-reviewer/SKILL.md
index 61a252e9..8a4f11a5 100644
--- a/plugins/plan-lifecycle/skills/productivity/plan-reviewer/SKILL.md
+++ b/plugins/plan-lifecycle/skills/productivity/plan-reviewer/SKILL.md
@@ -5,7 +5,7 @@ user-invocable: false
metadata:
pattern: tool-wrapper
updated: "2026-10-19"
- content_hash: "10bb17553f82450f69825d1f6b0e18b15ac9d76c135e0a38ce525f0fd5a9c7ee"
+ content_hash: "52a28e27f3849805a5044e9c6f16c95320abca5f6ab4aa2f5a19c4645461b94f"
---
# Plan Reviewer
@@ -35,6 +35,7 @@ citations, probes, tests, or acceptance rows without a load-bearing defect.
| `security_risk` | The plan introduces or ignores a security risk or destructive action without confirmation |
Report a `goal_fit` finding when the plan uses a temporary workaround instead of the root-cause fix, leaves a caller on a replaced path, or keeps an obsolete path, unless Open questions record user approval.
+Report a `goal_fit` finding when Acceptance lacks a check that the repository instructions require for the paths in the Steps Files cells.
Read enough surrounding code, callers, tests, and local instructions to verify
claims. Check library and API claims against current official documentation.
Do not demand additional sources when existing evidence establishes the claim.
diff --git a/plugins/plan-lifecycle/skills/productivity/yolo/SKILL.md b/plugins/plan-lifecycle/skills/productivity/yolo/SKILL.md
index 7c08f802..7027cf6d 100644
--- a/plugins/plan-lifecycle/skills/productivity/yolo/SKILL.md
+++ b/plugins/plan-lifecycle/skills/productivity/yolo/SKILL.md
@@ -6,7 +6,7 @@ disable-model-invocation: true
metadata:
pattern: pipeline
updated: "2026-10-19"
- content_hash: "60d809dee20db8430222e943110dbff3e144540bd5655de8bb90fe62bf878be0"
+ content_hash: "c88bf4293b208fa56f085e1be92738f8e87a55e4e23c64f2c401b11cef0e5942"
---
# Yolo
@@ -59,14 +59,42 @@ practice.
| Phase | Yolo action | Done when |
|---|---|---|
-| Decide | The path is a plan. The Mode is `plan-and-implement`. Ask nothing. | The goal fits in one sentence |
-| Draft | Run the preflight, create the issue, and set up the linked branch | `show ` reads drafting and the branch is listed |
-| Research | Follow Research and Engineering standards below | `status planned` succeeds |
+| Decide | Apply Existing plans and incomplete requests below. Otherwise the path is a new plan with Mode `plan-and-implement`. Ask nothing. | The run adopted a plan, or the goal fits in one sentence |
+| Draft | Skip when the run adopted a plan. Run the preflight, create the issue, and set up the linked branch | The run adopted a plan, or `show ` reads drafting and the branch is listed |
+| Research | Follow Research and Engineering standards below | `status planned` succeeds, or `show ` of an adopted plan already reads planned or ongoing. No advice needs a decision |
| Plan review | Run Review loops below | `show ` reads `plan=pass` |
-| Implement | Follow plan-manager phase 5 with the docks skills below | Verification Results hold real results |
+| Implement | Follow plan-manager phase 5, Engineering standards (always with `code-clarity`), Red baseline, and Commits below | Verification Results hold real results, and the candidate is committed |
| Code review | Run Review loops below | `show ` reads `code=pass` |
| Land | Follow Landing below | The merge question has an answer, or no answer is possible |
+## Existing plans and incomplete requests
+
+Before Draft, run `list` and `gh pr list --repo --state open`.
+
+- When the request names an open plan issue, or an open pull request that
+ closes one, adopt that plan. Do not run `new`.
+- When the request text is incomplete, act on the one reading that the
+ repository state supports, such as the only open plan. Record that reading as
+ a request correction. When no reading or more than one reading has support,
+ stop before any write and report the readings.
+- Otherwise go to Draft.
+
+To adopt a plan:
+
+1. Run `show `. Adopt only an open plan. When the helper reports that
+ another login owns the plan, stop and report.
+2. Export the plan. Add the delegation line to Goal when it is missing, and set
+ `Mode: plan-and-implement`.
+3. Replace each Goal or Do not touch line that conflicts with this run, such as
+ "leave the pull request open". Record a request correction whose `Asked:`
+ part quotes the old line.
+4. When steps 2 or 3 changed the export, publish it with `edit`. A Goal change
+ after `plan=pass` needs a new plan-review round.
+5. Reuse the branch that `gh issue develop --list` shows, or the head branch
+ of the open pull request. When neither exists, use the contract's Linked
+ branch steps.
+6. Continue at the first Run order phase whose Done when is not met.
+
## Draft
1. Run the publication preflight. If it fails, stop and create nothing.
@@ -105,6 +133,10 @@ Record each request correction under `### Request corrections` in Research:
Asked: . Did: . Reason: .
```
+Read the instruction files that apply to each path in the Files cells:
+`AGENTS.md`, `CLAUDE.md`, and `CONTRIBUTING.md` in that folder and its parent
+folders. Add one Acceptance row for each check that they require for the change.
+
## Engineering standards
Load each matching docks skill when the runtime has it. A runtime can show it as
@@ -114,18 +146,22 @@ skill each step applies. When a standard defines completion, put it in that
step's `Done when`. Apply each step's skills again while you implement that
step, and run the `code-review` self-review before code review round 1.
+Apply `code-clarity` to every step that changes code, tests, comments, or error
+messages, together with the other matching skills. Record it for each such
+step, and check its rules again in the self-review.
+
| Work in a step | docks skill |
|---|---|
| New behavior specified by inputs and outputs | `tdd-workflow` |
| Tests for code that already exists | `test-coverage` |
| A bug, regression, or review finding | `fix-workflow` |
| Module, class, or service boundaries | `solid` |
-| Names, comments, errors, and function size | `code-clarity` |
+| Every change to code, tests, comments, or error messages | `code-clarity` |
| Identifier types, input parsing, tagged unions | `type-safety-discipline` |
| A linter or type-checker error | `lint-no-suppressions` |
| Lint rules or repeated literal values | `code-guardrails` |
| Dependency upgrades or advisories | `dep-vuln-workflow` |
-| Commits and the pull request description | `commit-discipline` |
+| Commits | `commit-discipline` |
| React components | `react-component-patterns` |
| Colors, tokens, and themes | `design-tokenization` |
| Keyboard, focus, and ARIA | `accessibility` |
@@ -144,6 +180,53 @@ When docks is not installed, apply these rules:
- Fix each lint or type error. Do not add a suppression comment.
- Reuse the pattern that the repository has. Do not add a second convention.
- Leave no dead code, compatibility alias, or shim.
+- Name things with domain words. Write comments that give the reason, not the
+ mechanics. Make each error name the failed operation, its subject, and the
+ recovery.
+
+## Red baseline
+
+The main context owns the red baseline of each `tdd-workflow` step. A subagent
+can write the spec tests.
+
+1. Write the spec tests before the production code of the step.
+2. Run them. Each new test must fail because the behavior is missing: on an
+ assertion, or on an export or symbol that this step adds. A failure from any
+ other import, setup, or syntax error is not a red baseline. Do not add an
+ empty stub to change a missing export into an assertion failure.
+3. Stage the spec tests with `git add`. Add this line to Verification Results:
+ `Red baseline, step : : failed, passed`. When a missing
+ export stops a test file, write ` failed: missing ` instead of
+ the counts for that file.
+4. Keep the staged spec tests unchanged while you write production code:
+ `git diff -- ` prints nothing. When a spec test is wrong, record
+ the reason in Verification Results, fix the test, and run the baseline again.
+
+## Commits
+
+1. Commit each step on the linked branch when its proof passes, as
+ `commit-discipline` says. Each commit must pass the focused tests, the type
+ check, and the lint for its files.
+2. When you split finished work into commits, check each commit in a temporary
+ worktree: `git worktree add --detach `, run the checks, then
+ `git worktree remove -- `. `` is
+ `${XDG_DATA_HOME:-$HOME/.local/share}/agent-worktrees//check-`.
+ Do not put it under `/tmp` or inside `.git`.
+3. Before code review round 1, leave no staged, unstaged, or untracked change of
+ the candidate.
+4. Add each review fix as a new commit. Do not amend, squash, or rebase.
+5. The reviewed head is `HEAD` after the last passing code-review round.
+
+## Checks that fail outside the goal
+
+When a required check fails on files that the plan does not change:
+
+1. Run `git fetch origin `. Then run the same check on
+ `origin/` in a temporary worktree, as Commits item 2 says.
+2. When it fails the same way there, it is a baseline failure. Record the
+ command, the failing files, and the default-branch result in Verification
+ Results. Do not fix it unless the Goal covers it.
+3. When it passes there, this change causes the failure. Fix it in this plan.
## Review loops
@@ -152,6 +235,16 @@ Use the contract's review loop with its Yolo runs ceiling. Dispatch the
a wrapper is missing, dispatch a fresh read-only agent with the matching
instructions. Post each returned block unchanged as one issue comment.
+Before code review round 1, run the `code-review` self-review with
+`code-clarity` on the full candidate diff. Post its findings as one issue
+comment whose first line is `Self-review`; write `No findings.` when it finds
+nothing. The helper does not read that comment as a review. Fix each finding
+with `fix-workflow`, commit the fixes, and then start round 1.
+
+After `plan=pass`, an `edit` that changes Goal, Research, Steps, Acceptance, or
+Do not touch needs a new plan-review round before code that depends on it. The
+helper prints advice for that case.
+
| Verdict | Yolo action |
|---|---|
| Plan `repair` | Reproduce each finding, repair a fresh export, and review again |
@@ -166,53 +259,50 @@ same finding again, research it again and try a different approach.
## Landing
-1. Commit the reviewed bytes on the linked branch.
+1. Confirm that `git status --porcelain` prints nothing. The reviewed head is
+ `HEAD`.
2. Fetch the default branch. If it is not an ancestor of `HEAD`, merge
`origin/` into the linked branch. Do not rebase. Rerun acceptance,
and run a code-review round on the result. Commit each repair.
3. Push the linked branch with a normal push.
-4. Write the run summary below to a file in the review scratch directory. End
- it with `Closes #` on its own line.
-5. Open the pull request with `gh pr create --repo --base
- --head --title --body-file `.
+4. Write the run summary from `references/run-summary.md` to a file in the
+ review scratch directory. The run summary is the whole pull request
+ description; do not add the `commit-discipline` description sections. Write
+ `pending` for the pull request URL and the checks. End the file with
+ `Closes #` on its own line.
+5. When `gh pr list --repo --head --state open` shows a
+ pull request, update its body with `gh pr edit --repo
+ --body-file `. Otherwise open the pull request with `gh pr create
+ --repo --base --head --title
+ --body-file `.
6. Wait for checks as the contract says. If a check fails, fix it, rerun
acceptance, review again, push, and wait again.
7. Confirm that `gh pr view --repo --json headRefOid` shows
- the reviewed head.
+ the reviewed head. Write the pull request URL and the check result into the
+ summary file. Then update the body with `gh pr edit --repo
+ --body-file `.
8. Print the run summary. In the same response, ask one question:
`Merge pull request # into ?`, with the options `Merge now` and
`Leave pull request open`.
-9. On `Merge now`, merge and archive as the contract says. Ask separately
- before each step that runs after the merge. If this run made a worktree,
- run `git worktree remove -- `, then `git worktree prune`.
+9. On `Merge now`, merge and archive as the contract says. Then clean up:
+ - If this run made a worktree, run `git worktree remove -- `, then
+ `git worktree prune`.
+ - If this run used the user's checkout and `git status --porcelain` prints
+ nothing, run `git switch ` and
+ `git pull --ff-only origin `.
+ - Keep the merged local branch. Name it in the final report as a branch that
+ the user can delete.
+
+ Ask separately before each plan step that runs after the merge.
10. On any other answer, or when no answer is possible, leave the pull request
and the issue open. Report both links.
+11. When the message that answers the merge question also starts a new yolo
+ run, finish this run first: merge or leave open as the answer says, then
+ archive and clean up. Then start the new run.
## Run summary
-Keep the summary short. Use this shape:
-
-```markdown
-## Yolo run: #
-
-Pull request: . Checks: .
-
-### What changed
-- (``)
-
-### Decisions and corrections
--
-
-### Reviews
-Plan review passed in round . Code review passed in round .
-Open findings: .
-
-### Verification
-- ``:
-
-### After merge
--
-```
+Use the template in [`references/run-summary.md`](references/run-summary.md).
## Recover after compaction
@@ -222,6 +312,7 @@ request exists, GitHub lists no branch there. Then read the pull request number
from `gh issue view --repo --json closedByPullRequestsReferences`,
and read the branch from `gh pr view --repo --json headRefName`.
Run `git worktree list` for a worktree. Count review rounds from issue comments.
+A `Self-review` comment is not a review round.
## BAD / GOOD
diff --git a/plugins/plan-lifecycle/skills/productivity/yolo/references/run-summary.md b/plugins/plan-lifecycle/skills/productivity/yolo/references/run-summary.md
new file mode 100644
index 00000000..343ae3d7
--- /dev/null
+++ b/plugins/plan-lifecycle/skills/productivity/yolo/references/run-summary.md
@@ -0,0 +1,32 @@
+# Run summary
+
+Keep the summary short. Use this shape:
+
+```markdown
+## Yolo run: #
+
+Pull request: . Checks: . Reviewed head: ``.
+
+### What changed
+- (``)
+
+### Decisions and corrections
+-
+
+### Reviews
+Plan review passed in round . Code review passed in round .
+Open findings: .
+
+### Verification
+- ``:
+
+### Limits and failures
+-
+
+### After merge
+-
+```
+
+Copy each failure and limitation from Verification Results into Limits and
+failures. Report a check that partly failed as a failure with its cause, not as
+a pass.
diff --git a/scripts/tests/plan-cli.mjs b/scripts/tests/plan-cli.mjs
index ce999aca..9bccb6a5 100755
--- a/scripts/tests/plan-cli.mjs
+++ b/scripts/tests/plan-cli.mjs
@@ -944,6 +944,87 @@ try {
expectSuccess(edit(numericId), 'second normalization');
assert.equal(issue(numericId).body, once, 'a second normalization changes nothing');
+ // A drafted plan enters planned or ongoing only after research replaces the placeholder (#78).
+ const unresearched = createPlan(
+ 'unresearched plan',
+ body().replace('The parser loses mixed-case records.', '_Not researched yet._'),
+ );
+ refuse(run('status', String(unresearched), 'planned'), 'status planned requires a filled Research section');
+ refuse(run('status', String(unresearched), 'ongoing'), 'status ongoing requires a filled Research section');
+ assert.deepEqual(issue(unresearched).labels, ['plan', 'plan:drafting'], 'a refused status keeps the phase label');
+ expectSuccess(
+ run('status', String(unresearched), 'blocked', '--reason', 'Waiting for research'),
+ 'an unresearched plan can still block',
+ );
+ const emptyResearch = createPlan('empty research', body().replace('The parser loses mixed-case records.', ''));
+ refuse(run('status', String(emptyResearch), 'planned'), 'status planned requires a filled Research section');
+ const quotedPlaceholder = createPlan(
+ 'research quotes the placeholder',
+ body().replace(
+ 'The parser loses mixed-case records.',
+ 'A new plan holds `_Not researched yet._` until research is published.',
+ ),
+ );
+ const quotedStatus = run('status', String(quotedPlaceholder), 'planned');
+ expectSuccess(quotedStatus, 'research prose may quote the placeholder');
+ assert.doesNotMatch(quotedStatus.stdout, /Research is not filled/, 'quoted placeholder is not unfilled research');
+
+ // A failed remote edit keeps the export valid, so the same file can be sent again (#78).
+ const retried = createPlan('retried edit');
+ const retryFile = exported(retried);
+ fs.writeFileSync(retryFile, fs.readFileSync(retryFile, 'utf8').replace('Not run.', 'Smoke passed.'));
+ const transientError = 'GraphQL: Something went wrong while executing your query';
+ updateState((state) => {
+ state.issueEditError = transientError;
+ });
+ const failedEdit = run('edit', String(retried), '--file', retryFile);
+ refuse(failedEdit, `gh issue edit failed: ${transientError}`);
+ assert.equal(failedEdit.stdout, '', 'a failed edit prints no result');
+ updateState((state) => {
+ delete state.issueEditError;
+ });
+ expectSuccess(run('edit', String(retried), '--file', retryFile), 'retry with the same export');
+ assert.match(issue(retried).body, /Smoke passed\./, 'the retry writes the edited body');
+
+ // Steps that edit the same path need a Depends chain, or parallel work collides (#78).
+ const sharedFiles = createPlan('shared files');
+ const sharedRows = [
+ '| 1 | fix_parser | Fix parser | src/parser.mjs | - | local | planned | Records round-trip |',
+ '| 2 | parse_dates | Parse dates | `src/parser.mjs`, src/dates.mjs | - | local | planned | Dates parse |',
+ '| 3 | format_dates | Format dates | src/dates.mjs | parse_dates | local | planned | Dates format |',
+ '| 4 | print_dates | Print dates | src/dates.mjs | format_dates | local | planned | Dates print |',
+ ].join('\n');
+ const sharedEdit = edit(sharedFiles, (text) =>
+ text.replace(
+ '| 1 | fix_parser | Fix parser | src/parser.mjs | - | local | planned | Records round-trip |',
+ sharedRows,
+ ),
+ );
+ expectSuccess(sharedEdit, 'edit with shared files');
+ assert.deepEqual(
+ sharedEdit.stdout.split('\n').filter((line) => line.includes(' share ')),
+ ['advice: steps fix_parser and parse_dates share src/parser.mjs with no Depends link'],
+ 'only steps without a Depends chain get shared-path advice',
+ );
+
+ // A plan-review pass covers the body that the reviewer read (#78).
+ const reviewed = createPlan('reviewed plan');
+ addIssueComment(reviewed, '### Plan review round 1\n\nPlan-review: pass\n\nNone.');
+ const resultsEdit = edit(reviewed, (text) => text.replace('Not run.', 'Smoke passed.'));
+ expectSuccess(resultsEdit, 'edit verification results after a plan pass');
+ assert.doesNotMatch(resultsEdit.stdout, /plan review passed before this edit/, 'evidence edits need no new round');
+ const researchEdit = edit(reviewed, (text) =>
+ text.replace(
+ 'The parser loses mixed-case records.',
+ 'The parser loses mixed-case records.\n\nDecision: keep the old date format.',
+ ),
+ );
+ expectSuccess(researchEdit, 'edit research after a plan pass');
+ assert.match(
+ researchEdit.stdout,
+ /^advice: plan review passed before this edit; changed sections: Research; run a new plan-review round before code that depends on them$/m,
+ );
+
updateState((state) => {
for (let i = 0; i < 500; i += 1) {
const number = state.nextIssue++;
@@ -968,7 +1049,7 @@ try {
assert.match(limitedList.stdout, /^advice: list stopped at 500 plans; more plans may exist$/m);
console.log(
- 'plan-cli smoke PASSED: labels, snapshot paging, normalization, ownership, race, provenance, step freeze, transitions, review trust, archive proof, list, retire, large output, Markdown review records, early closure, list limit, machine path advice',
+ 'plan-cli smoke PASSED: labels, snapshot paging, normalization, ownership, race, provenance, step freeze, transitions, review trust, archive proof, list, retire, large output, Markdown review records, early closure, list limit, machine path advice, research gate, edit retry, shared files advice, re-review advice',
);
} finally {
fs.rmSync(scratch, { recursive: true, force: true });