mirror of
https://github.com/garrytan/gstack.git
synced 2026-11-04 15:57:33 +01:00
fix(autoplan): revise naming a non-pending decision refuses with AMENDMENT_UNKNOWN, not PLAN_AMEND_PENDING
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
This commit is contained in:
1 parent
5d5f41fc82
commit
0736955c85
6 files changed
+32
-2
No files matched your search
+1
-1
@@ -8,7 +8,7 @@
|
||||
|
||||
- **`submit --record <file>` binds a review a subagent wrote by hand.** It accepts the canonical findings fence, one findings table (Severity and Title columns), one severity-tagged list (`- [High] title`) or a `No findings.` line, and it never guesses a severity. A record whose `INPUT:` line is present but wrong is refused. A record without one binds only with `--attest "<reason>" --attempt <id>`; it is labeled `receipt: attested`, `validate` counts it (`attested=<n>`), and it is refused if the plan was revised after that attempt was dispatched. `--attempt` is optional when exactly one attempt is open. `submit --voice outside --unavailable|--failed "<reason>"` closes a phase whose outside voice cannot run, and `next` and `resume` never redispatch it.
|
||||
- **`revise --plan <v2>` reviews a revised plan in the same run.** Every phase whose reviewed Implementation-plan hash differs reopens (plus Eng) on the new snapshot; at the gate it starts the next gate revision. The revise is one recoverable step (`resume` finishes an interrupted one), and archives never overwrite each other. `next` prints `PLAN_CHANGED` and `answer` prints `PLAN_SOURCE_CHANGED` when the plan file moved since its snapshot.
|
||||
- **Accepted plan changes must reach the plan.** When the owner accepts a change at the gate, `answer` prints one `APPLY:` line per change and `NEXT: gstack-autoplan revise …`. `next` refuses with `PLAN_AMEND_PENDING` until `revise --incorporates <decision ids>` binds the edit, or the owner withdraws a change with `--withdraw <id>`. A run cannot export as complete with a change pending.
|
||||
- **Accepted plan changes must reach the plan.** When the owner accepts a change at the gate, `answer` prints one `APPLY:` line per change and `NEXT: gstack-autoplan revise …`. `next` refuses with `PLAN_AMEND_PENDING` until `revise --incorporates <decision ids>` binds the edit, or the owner withdraws a change with `--withdraw <id>`. A run cannot export as complete with a change pending. Naming an id that is not pending (never accepted as a plan change, or already settled) refuses with `AMENDMENT_UNKNOWN` instead of asking for the edit you just made.
|
||||
- **User-challenge defaults follow agreeing voices, and say why.** When both voices flag the same finding as a change to scope, cost or direction (the same `changes` value) and both give a remedy, the item now recommends the change. Option b shows both remedies word for word, and the default is labeled `recommended: b (both voices; remedies shown)`. Anything weaker keeps your direction, labeled `(plan direction; voices disagree)`. **`all` now accepts those agreed changes**; reply `all as-is` to keep every direction in one token. p1's recommended text names the changes it would apply.
|
||||
- **Reruns stop churning.** Reviewers set `user_challenge` only for a change to scope, cost or direction (`changes`). On a rerun, a challenge without one becomes the agent's own decision and does not reopen anything. Rerun prompts carry a `DECIDED AT THE GATE` block, and answers to items from phases that did not rerun carry forward instead of being asked again. A partial reply never overwrites an earlier answer, and the gate stays open until every approval is settled.
|
||||
- **A plan without the two sections is scaffolded, not refused with empty output.** The first `next` explains `PLAN_SECTIONS_INVALID`; `next --scaffold` reviews a copy with the missing heading added and never edits your file.
|
||||
|
||||
@@ -176,6 +176,7 @@ gstack_result_anchor() {
|
||||
DRAFT_DIRECTION_UNRESOLVED) printf '%s' 'draft-direction-unresolved' ;;
|
||||
PLAN_SECTIONS_INVALID) printf '%s' 'plan-sections-invalid' ;;
|
||||
PLAN_AMEND_PENDING) printf '%s' 'plan-amend-pending' ;;
|
||||
AMENDMENT_UNKNOWN) printf '%s' 'amendment-unknown' ;;
|
||||
GSTACK_VERSION_CHANGED) printf '%s' 'gstack-version-changed' ;;
|
||||
ARTIFACT_GATE_UNSETTLED) printf '%s' 'artifact-gate-unsettled' ;;
|
||||
UPGRADE_REFUSED) printf '%s' 'upgrade-refused' ;;
|
||||
|
||||
@@ -2570,6 +2570,18 @@ not change; an id you do not name stays pending. To drop an accepted change
|
||||
instead, `gstack-autoplan revise --out <dir> --withdraw <decision id>` records
|
||||
the withdrawal in the journal.
|
||||
|
||||
<a id="amendment-unknown"></a>
|
||||
### `AMENDMENT_UNKNOWN`
|
||||
|
||||
**Meaning.** `gstack-autoplan revise --incorporates` or `--withdraw` named a
|
||||
decision id that is not a pending amendment: the owner never accepted it as a
|
||||
plan change at the gate (for example a gate answered before gstack recorded
|
||||
amendments), or it was already incorporated or withdrawn. Nothing was changed.
|
||||
|
||||
**Fix.** Name only the ids the refusal lists after `pending:`. With none
|
||||
pending, revise without `--incorporates`:
|
||||
`gstack-autoplan revise --out <dir> --plan <file>`.
|
||||
|
||||
<a id="gstack-version-changed"></a>
|
||||
### `GSTACK_VERSION_CHANGED`
|
||||
|
||||
|
||||
@@ -56,7 +56,7 @@ export function reviseRun(o: ReviseOptions): ReviseResult {
|
||||
if (['complete', 'incomplete', 'refused'].includes(state.status)) throw new RunError('PHASE_NOT_AWAITING', `run is ${state.status}; revise works on a running or gated run`, 3);
|
||||
const pending = pendingAmendments(state).map(a => a.decision);
|
||||
for (const id of [...(o.incorporates ?? []), ...(o.withdraw ?? [])]) {
|
||||
if (!pending.includes(id)) throw new RunError('PLAN_AMEND_PENDING', `${id} is not a pending amendment (pending: ${pending.join(', ') || 'none'})`, 3);
|
||||
if (!pending.includes(id)) throw new RunError('AMENDMENT_UNKNOWN', `${id} is not a pending amendment (pending: ${pending.join(', ') || 'none'})`, 3);
|
||||
}
|
||||
const at = now();
|
||||
if (!o.plan) {
|
||||
|
||||
@@ -476,6 +476,11 @@ export const RESULT_CODES = {
|
||||
summary: 'the owner accepted plan changes at the gate that the plan has not absorbed yet',
|
||||
fix: 'edit the plan, then `gstack-autoplan revise --out <dir> --plan <file> --incorporates <decision id,...>`; or drop one with `gstack-autoplan revise --out <dir> --withdraw <decision id>`',
|
||||
},
|
||||
AMENDMENT_UNKNOWN: {
|
||||
anchor: 'amendment-unknown',
|
||||
summary: '`revise --incorporates` or `--withdraw` named a decision that is not a pending amendment (never accepted as a plan change, or already incorporated or withdrawn)',
|
||||
fix: 'name only the ids the refusal lists after `pending:`; with none pending, drop --incorporates and run `gstack-autoplan revise --out <dir> --plan <file>`',
|
||||
},
|
||||
GSTACK_VERSION_CHANGED: {
|
||||
anchor: 'gstack-version-changed',
|
||||
summary: 'the run directory was written by a different gstack version',
|
||||
|
||||
@@ -124,6 +124,18 @@ describe('revise', () => {
|
||||
expect(res.status).toBe(1);
|
||||
expect(res.stderr).toMatch(/revised after .*dispatched.*\(RESULT_RECEIPT_MISSING\)$/m);
|
||||
});
|
||||
test('naming a decision that is not a pending amendment refuses with AMENDMENT_UNKNOWN and never asks for the edit just made', () => {
|
||||
const s = start();
|
||||
const before = fs.readFileSync(path.join(s.out, 'run.json'), 'utf8');
|
||||
for (const args of [['--plan', v2File(s.dir), '--incorporates', 'd1b'], ['--withdraw', 'd1b']]) {
|
||||
const res = r.ap(['revise', '--out', s.out, ...args]);
|
||||
expect(res.status).toBe(3);
|
||||
expect(res.stderr).toMatch(/d1b is not a pending amendment \(pending: none\).*\(AMENDMENT_UNKNOWN\)$/m);
|
||||
expect(res.stderr).not.toContain('PLAN_AMEND_PENDING');
|
||||
expect(res.stderr).not.toContain('edit the plan');
|
||||
}
|
||||
expect(fs.readFileSync(path.join(s.out, 'run.json'), 'utf8')).toBe(before);
|
||||
});
|
||||
});
|
||||
|
||||
describe('drift hints', () => {
|
||||
|
||||
Reference in new issue
Block a user