mirror of
https://github.com/garrytan/gstack.git
synced 2026-11-10 02:30:39 +01:00
Make autoplan's plan-approval gate item p1 so gstack-gate accepts it (#4181)
/autoplan Phase 4 told the agent to write a gate item with id `plan` and to read replies `plana`/`planc`/`pland`/`plane`. gstack-gate only accepts ids of a lowercase label plus a number, so render refused the gate and the replies never parsed. Rename it to `p1` (the id lib/autoplan-gate.ts and docs/unattended.md already use), lower the autoplan size pins by the 10 characters saved, and add a test that builds the gate from the template text and runs it through bin/gstack-gate render and parse. Co-authored-by: Eng. Mahmoud Bek ElNemer <83190847+ElNeMeR70@users.noreply.github.com> Co-authored-by: Yuan S. <3579304+szsunyuan@users.noreply.github.com> Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
This commit is contained in:
1 parent
6473a17de4
commit
7b97904bc8
5 files changed
+48
-18
No files matched your search
+3
-3
@@ -625,7 +625,7 @@ in an unattended run, never a warning; interactive runs follow the repair rule a
|
||||
Write `$_AP/gate.json` (shape: `gstack-gate --help`): one item per User Challenge
|
||||
(`uc<n>`, approval; `why` = you said / both recommend / why / blind spots / cost if
|
||||
wrong), per taste decision (`d<n>`, auto; `why` = principle and the alternative's
|
||||
impact) and `plan` (approval: a) approve as-is (recommended) b) with overrides
|
||||
impact) and `p1` (approval: a) approve as-is (recommended) b) with overrides
|
||||
c) interrogate d) revise e) reject). Run `~/.claude/skills/gstack/bin/gstack-gate render $_AP/gate.json`
|
||||
and append its `decisions` output to `$_AP/decisions.jsonl`. Write the full review
|
||||
to `$_AP/review-record.md` and its Plan Summary alone to `$_AP/summary.md`:
|
||||
@@ -663,8 +663,8 @@ verbatim (URGENT block, plain summary, numbered decisions seven per page with ea
|
||||
auto default, review-record path; `--page N` for later pages), never the full review.
|
||||
|
||||
Parse the reply: `~/.claude/skills/gstack/bin/gstack-gate parse $_AP/gate.json --reply "<text>" --gate-rev <rev>`;
|
||||
unparsed or stale replies are re-presented (`gate_rev`+1), never guessed. `all` or `plana`
|
||||
without overrides = A; `d<n>` overrides = B; `uc<n>` overrides = B2; `planc`/`pland`/`plane`
|
||||
unparsed or stale replies are re-presented (`gate_rev`+1), never guessed. `all` or `p1a`
|
||||
without overrides = A; `d<n>` overrides = B; `uc<n>` overrides = B2; `p1c`/`p1d`/`p1e`
|
||||
= C/D/E. Under `SESSION_KIND: unattended` Read `sections/unattended.md`, end here
|
||||
with its `GSTACK_RESULT` line and never approve.
|
||||
|
||||
|
||||
@@ -360,7 +360,7 @@ there with each incomplete item.
|
||||
Write `$_AP/gate.json` (shape: `gstack-gate --help`): one item per User Challenge
|
||||
(`uc<n>`, approval; `why` = you said / both recommend / why / blind spots / cost if
|
||||
wrong), per taste decision (`d<n>`, auto; `why` = principle and the alternative's
|
||||
impact) and `plan` (approval: a) approve as-is (recommended) b) with overrides
|
||||
impact) and `p1` (approval: a) approve as-is (recommended) b) with overrides
|
||||
c) interrogate d) revise e) reject). Run `~/.claude/skills/gstack/bin/gstack-gate render $_AP/gate.json`
|
||||
and append its `decisions` output to `$_AP/decisions.jsonl`. Write the full review
|
||||
to `$_AP/review-record.md` and its Plan Summary alone to `$_AP/summary.md`:
|
||||
@@ -398,8 +398,8 @@ verbatim (URGENT block, plain summary, numbered decisions seven per page with ea
|
||||
auto default, review-record path; `--page N` for later pages), never the full review.
|
||||
|
||||
Parse the reply: `~/.claude/skills/gstack/bin/gstack-gate parse $_AP/gate.json --reply "<text>" --gate-rev <rev>`;
|
||||
unparsed or stale replies are re-presented (`gate_rev`+1), never guessed. `all` or `plana`
|
||||
without overrides = A; `d<n>` overrides = B; `uc<n>` overrides = B2; `planc`/`pland`/`plane`
|
||||
unparsed or stale replies are re-presented (`gate_rev`+1), never guessed. `all` or `p1a`
|
||||
without overrides = A; `d<n>` overrides = B; `uc<n>` overrides = B2; `p1c`/`p1d`/`p1e`
|
||||
= C/D/E. Under `SESSION_KIND: unattended` Read `sections/unattended.md`, end here
|
||||
with its `GSTACK_RESULT` line and never approve.
|
||||
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
- **/autoplan's final approval gate renders again, and replies to its plan-approval item are accepted.** The skill told the agent to name that gate item `plan` and to read replies such as `plana` and `planc`, but `gstack-gate` only accepts an id made of a lowercase label and a number, so `gstack-gate render` refused the whole gate and those replies never parsed (#4181). The skill now names it `p1`, with replies `p1a`, `p1c`, `p1d` and `p1e`, the id gstack's own gate builder and docs/unattended.md already use. A test now builds the gate exactly as the skill describes it and runs it through `gstack-gate render` and `parse`. Reported by @exGeni in #4181; fixes proposed by @ElNeMeR70 (#4189) and @szsunyuan (#4191).
|
||||
+12
-12
@@ -2,53 +2,53 @@
|
||||
"_comment": "Generated SKILL.md files over the limit, pinned at their exact character count (#3199). test/skill-md-size-guard.test.ts fails when a pinned skill grows past its pin or shrinks below it: lower the pin when a skill shrinks and delete it once the skill is within the limit. Raising a pin is an owner decision. The shared-preamble hoist (GSTA-96) moved every tier 2+ skill's shared preamble into sections/preamble.md; the pins below are what remains.",
|
||||
"limit": 100000,
|
||||
"pins": {
|
||||
"agy/gstack-autoplan": 109585,
|
||||
"agy/gstack-autoplan": 109575,
|
||||
"agy/gstack-plan-design-review": 109096,
|
||||
"agy/gstack-plan-devex-review": 114924,
|
||||
"agy/gstack-plan-eng-review": 143580,
|
||||
"agy/gstack-review": 104735,
|
||||
"codex/gstack-autoplan": 108328,
|
||||
"codex/gstack-autoplan": 108318,
|
||||
"codex/gstack-plan-design-review": 110453,
|
||||
"codex/gstack-plan-devex-review": 116432,
|
||||
"codex/gstack-plan-eng-review": 144745,
|
||||
"codex/gstack-review": 104119,
|
||||
"copilot/gstack-autoplan": 109028,
|
||||
"copilot/gstack-autoplan": 109018,
|
||||
"copilot/gstack-plan-design-review": 109362,
|
||||
"copilot/gstack-plan-devex-review": 114386,
|
||||
"copilot/gstack-plan-eng-review": 143745,
|
||||
"copilot/gstack-review": 104316,
|
||||
"cursor/gstack-autoplan": 108260,
|
||||
"cursor/gstack-autoplan": 108250,
|
||||
"cursor/gstack-plan-design-review": 107904,
|
||||
"cursor/gstack-plan-devex-review": 113656,
|
||||
"cursor/gstack-plan-eng-review": 142335,
|
||||
"cursor/gstack-review": 122788,
|
||||
"factory/gstack-autoplan": 110538,
|
||||
"factory/gstack-autoplan": 110528,
|
||||
"factory/gstack-plan-design-review": 111002,
|
||||
"factory/gstack-plan-devex-review": 117019,
|
||||
"factory/gstack-plan-eng-review": 145443,
|
||||
"factory/gstack-review": 126561,
|
||||
"gbrain/gstack-autoplan": 108667,
|
||||
"gbrain/gstack-autoplan": 108657,
|
||||
"gbrain/gstack-plan-eng-review": 124963,
|
||||
"gemini/gstack-autoplan": 108818,
|
||||
"gemini/gstack-autoplan": 108808,
|
||||
"gemini/gstack-plan-design-review": 109190,
|
||||
"gemini/gstack-plan-devex-review": 114214,
|
||||
"gemini/gstack-plan-eng-review": 143578,
|
||||
"gemini/gstack-review": 104138,
|
||||
"hermes/gstack-autoplan": 108621,
|
||||
"hermes/gstack-autoplan": 108611,
|
||||
"hermes/gstack-plan-eng-review": 124999,
|
||||
"kiro/gstack-autoplan": 108034,
|
||||
"kiro/gstack-autoplan": 108024,
|
||||
"kiro/gstack-plan-design-review": 107750,
|
||||
"kiro/gstack-plan-devex-review": 113508,
|
||||
"kiro/gstack-plan-eng-review": 142197,
|
||||
"kiro/gstack-review": 122612,
|
||||
"openclaw/gstack-autoplan": 108490,
|
||||
"openclaw/gstack-autoplan": 108480,
|
||||
"openclaw/gstack-plan-eng-review": 123478,
|
||||
"opencode/gstack-autoplan": 109009,
|
||||
"opencode/gstack-autoplan": 108999,
|
||||
"opencode/gstack-plan-design-review": 109218,
|
||||
"opencode/gstack-plan-devex-review": 114264,
|
||||
"opencode/gstack-plan-eng-review": 143604,
|
||||
"opencode/gstack-review": 123410,
|
||||
"slate/gstack-autoplan": 108147,
|
||||
"slate/gstack-autoplan": 108137,
|
||||
"slate/gstack-plan-design-review": 107827,
|
||||
"slate/gstack-plan-devex-review": 113582,
|
||||
"slate/gstack-plan-eng-review": 142266,
|
||||
|
||||
@@ -250,3 +250,32 @@ describe('bin/gstack-gate', () => {
|
||||
expect(pending.every(r => r.status === 'pending')).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('the /autoplan Phase 4 gate as documented (#4181)', () => {
|
||||
// Value: protects=the gate /autoplan tells the agent to write renders, and the documented plan-approval replies parse, through bin/gstack-gate;
|
||||
// fails_when=the template names a gate item id or reply token outside the gate grammar; why_new=the grammar cases build their own lists; seam=none
|
||||
test('the template\'s plan-approval item renders and each documented reply for it parses to its option', () => {
|
||||
const tmpl = fs.readFileSync(path.join(ROOT, 'autoplan', 'SKILL.md.tmpl'), 'utf8').replace(/\s+/g, ' ');
|
||||
const write_ = /Write `\$_AP\/gate\.json`.*?gstack-gate render/.exec(tmpl)?.[0] ?? '';
|
||||
const item = /and `([^`]+)` \(approval: (a\) .*? e\) \w+)\)/.exec(write_);
|
||||
expect(item).not.toBeNull();
|
||||
const id = item![1]!;
|
||||
const parts = item![2]!.split(/(?:^| )([a-e])\) /).slice(1);
|
||||
const options = parts.flatMap((key, i) => (i % 2 ? [] : [{ key, text: parts[i + 1]! }]));
|
||||
expect(options.map(o => o.key)).toEqual(['a', 'b', 'c', 'd', 'e']);
|
||||
const gate: GateList = { schema_version: 1, run: 'autoplan-4181', gate_rev: 1, items: [
|
||||
{ id: 'uc1', title: 'User challenge', options: ab, recommended: 'a', kind: 'approval' },
|
||||
{ id: 'd1', title: 'Taste decision', options: ab, recommended: 'a', kind: 'auto' },
|
||||
{ id, title: 'Plan approval', options, recommended: 'a', kind: 'approval' },
|
||||
] };
|
||||
const file = write('gate.json', gate);
|
||||
const parseText = /Parse the reply:.*?SESSION_KIND/.exec(tmpl)?.[0] ?? '';
|
||||
const replies = [...parseText.matchAll(/`([a-z0-9]+)`/g)].map(m => m[1]!).filter(t => t.startsWith(id) && t.length === id.length + 1);
|
||||
expect(replies.map(t => t.slice(-1))).toEqual(['a', 'c', 'd', 'e']);
|
||||
|
||||
const calls = [['render', file], ...replies.map(reply => ['parse', file, '--reply', reply, '--gate-rev', '1', '--json'])];
|
||||
const results = calls.map(args => { const r = run(args); return { args: args.filter(a => a !== file).join(' '), code: r.code, err: r.err, out: r.out }; });
|
||||
expect(results.map(({ args, code, err }) => ({ args, code, err }))).toEqual(results.map(({ args }) => ({ args, code: EXIT.ok, err: '' })));
|
||||
expect(results.slice(1).map(r => chosen(JSON.parse(r.out))[id])).toEqual(['a', 'c', 'd', 'e']);
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user