test(auto-decide): accept Step 0E's documented AUTO_DECIDE line with its decisions suffix; replay the captured trial

This commit is contained in:
garrytan committed 2026-10-04 05:47:42 +00:00
1 parent 755c14e0b3
commit a8ecc71991
5 files changed
+270 -3

No files matched your search

+1
View File
@@ -57,6 +57,7 @@ The first two rows are the ones to care about: a review that read nothing, or fo
- **Required team mode also blocks Copilot CLI's skill tool.** Re-run `gstack-team-init required` to upgrade the project hook.
- **`gstack-qa-evidence capture` prints a `revalidate` list after your inputs change**, naming the probes to rerun on current inputs before `materialize`; `materialize` says its verdict is final for that report root, so rerun the listed probes first.
- **/gstack-upgrade asks you to paste the absolute path Step 2 prints**; every later step checks it is inside gstack's checkout before running git.
- **Browser setup names the platform when Aside is absent:** the probe prints `NEEDS_ASIDE: <OS>` (was `NEEDS_ASIDE`), and the macOS-only download pitch trusts that line instead of re-checking the OS. `GSTACK_PLATFORM` overrides it for tests and unusual hosts; set it in your shell, never a project `.env`.
### Security
+53
View File
@@ -7,6 +7,7 @@ import { ceoModeHandoffs, findNativeAutoDecision } from './helpers/native-auto-d
import { hasNativePostAnswerCeoPosture } from './helpers/ceo-mode-option';
import { readPlanCountTranscript, type NativePublicToolEvent } from './helpers/plan-count-transcript';
import expansionCapture from './fixtures/ceo-expansion-auq-ac.json';
import stateCapture from './fixtures/auto-decide-state-cab3.json';
const ROOT = path.resolve(import.meta.dir, '..');
const BIN = path.join(ROOT, 'bin', 'gstack-ceo-mode-handoff');
@@ -90,6 +91,58 @@ describe('native handoff evidence', () => {
expect(verdict(withdrawn)).toBeNull();
});
test('with the owned decision record, the handoff is the declaration the census run never sent', () => {
const attempt = () => {
const f = structuredClone(stateCapture) as any;
const use = f.tools.find((e: any) => e.input?.command?.includes('gstack-question-log'));
const record = JSON.parse(/gstack-question-log '(\{[^\n]*\})'/.exec(use.input.command)![1]!);
record.source = 'agent';
record.ts = f.tools.find((e: any) => e.kind === 'result' && e.toolUseId === use.toolUseId).timestamp;
f.options.stateEvidence = { questionId: 'plan-ceo-review-mode', preference: 'never-ask', records: [record] };
f.transcript.assistantMessages.find((m: any) => m.text.startsWith('**Mode:')).text = 'Continuing with the review.';
return { f, loggedAt: Date.parse(record.ts) };
};
const decide = (f: any) => findNativeAutoDecision(f.transcript, f.tools, f.options);
const { f: unsent } = attempt();
expect(decide(unsent)).toBeNull();
const { f: sent, loggedAt } = attempt();
sent.tools.push(...handoff(sent.options.sessionId, loggedAt + 1000, `${CMD} "HOLD SCOPE" --auto`, AUTO_LINE));
expect(decide(sent)).toMatchObject({ option: 'HOLD SCOPE', annotation: AUTO_LINE, stateRecord: sent.options.stateEvidence.records[0] });
const { f: early } = attempt();
early.tools.push(...handoff(early.options.sessionId, loggedAt - 5000, `${CMD} "HOLD SCOPE" --auto`, AUTO_LINE));
expect(decide(early)).toBeNull();
const { f: changed, loggedAt: changedAt } = attempt();
changed.tools.push(...handoff(changed.options.sessionId, changedAt + 1000, `${CMD} "HOLD SCOPE" --auto`, AUTO_LINE));
changed.transcript.assistantMessages.push({ sessionId: changed.options.sessionId,
timestamp: new Date(changedAt + 3000).toISOString(), text: 'Mode: SCOPE EXPANSION (saved preference).' });
expect(decide(changed)).toBeNull();
});
test('captured trial: the documented AUTO_DECIDE line with its decisions suffix is the declaration', () => {
const captured = JSON.parse(fs.readFileSync(path.join(import.meta.dir, 'fixtures/auto-decide-handoff-line-e354.json'), 'utf8'));
const attempt = () => {
const f = structuredClone(captured);
const use = f.tools.find((e: any) => e.kind === 'use' && e.input?.command?.includes('gstack-question-log'));
const record = JSON.parse(/gstack-question-log '(\{[^\n]*\})'/.exec(use.input.command)![1]!);
record.source = 'agent';
record.ts = f.tools.find((e: any) => e.kind === 'result' && e.toolUseId === use.toolUseId).timestamp;
f.options.stateEvidence = { questionId: 'plan-ceo-review-mode', preference: 'never-ask', records: [record] };
return f;
};
const decide = (f: any) => findNativeAutoDecision(f.transcript, f.tools, f.options);
const line = (f: any) => f.transcript.assistantMessages.find((m: any) => m.text.startsWith('Auto-decided review mode'));
expect(decide(attempt())).toMatchObject({ option: 'HOLD SCOPE' });
for (const [name, mutate] of Object.entries({
'no decisions suffix': (f: any) => { line(f).text = line(f).text.replace(/ Approved decisions: /, ' Decisions: '); },
'different mode': (f: any) => { line(f).text = line(f).text.replace('→ HOLD SCOPE', '→ SCOPE EXPANSION'); },
'quoted line': (f: any) => { line(f).text = '> ' + line(f).text; },
'later withdrawal': (f: any) => { line(f).text += '\n\nCorrection: I withdraw this decision.'; },
'missing record': (f: any) => { f.options.stateEvidence.records = []; },
})) {
const f = attempt(); mutate(f); expect(decide(f), name).toBeNull();
}
});
test('an answered mode handoff establishes that mode posture, and only that mode', () => {
const records = structuredClone(expansionCapture.records);
const root = fs.mkdtempSync(path.join(os.tmpdir(), 'ceo-mode-handoff-'));
+211
View File
@@ -0,0 +1,211 @@
{
"source": "Local paid trial 1 of auto-decide-preserved on e3546d9 (2026-10-04): the model ran gstack-ceo-mode-handoff and sent the documented AUTO_DECIDE line with its Approved decisions suffix; the observer timed out because the detector did not accept that suffix.",
"options": {
"skillName": "plan-ceo-review",
"sessionId": "3eea0abd-2db9-46e3-bab3-da7dcc9c7857",
"commandStartedAt": 1791092659000,
"now": 1791092750000
},
"transcript": {
"status": "ready",
"calls": [],
"assistantMessages": [
{
"sessionId": "3eea0abd-2db9-46e3-bab3-da7dcc9c7857",
"text": "Received. I have the draft plan for deterministic skill-list ordering as context, along with your constraints: this invocation is for the review-mode decision only, no mode has been selected yet, and the audit record and handoff line should use the full selected mode name rather than a menu letter. I'll wait for your slash command before starting anything or calling any tools.",
"timestamp": "2026-10-04T05:44:22.090Z"
},
{
"sessionId": "3eea0abd-2db9-46e3-bab3-da7dcc9c7857",
"text": "Running the plan-ceo-review preamble now, then I'll work Step 0 up to the mode decision only, per your scope.",
"timestamp": "2026-10-04T05:44:28.673Z"
},
{
"sessionId": "3eea0abd-2db9-46e3-bab3-da7dcc9c7857",
"text": "Preference check for the mode question returned AUTO_DECIDE, so the recommended mode selects itself. Recording the Step 0 evidence and running the mode handoff now.",
"timestamp": "2026-10-04T05:45:13.714Z"
},
{
"sessionId": "3eea0abd-2db9-46e3-bab3-da7dcc9c7857",
"text": "Auto-decided review mode → HOLD SCOPE (your preference). Change with /plan-tune. Approved decisions: none. The plan is a fix to an existing listing path, not a new capability or system, and it pins membership, aliases, metadata and output formats as unchanged. HOLD SCOPE matches that: the review's value is in rigor on the sort itself (case handling, locale independence, enumeration-order tests, the unchanged-format contract), not in widening scope.\n\nNo new approach decision was needed.\n\n**Step 0 evidence recorded**\n\n- **0A Premise.** Real problem: skill listings change order between runs because they inherit directory enumeration order. Target outcome: byte-identical listings across runs and machines so script and review diffs stay quiet. Do-nothing cost: spurious diffs in every comparison. The plan addresses the pain directly, not a proxy.\n- **0B Existing code leverage.** This fixture repo contains only PLAN.md and CLAUDE.md, so the listing path, registry and test harness could not be inspected. The plan reuses the existing listing path rather than rebuilding it. Marked unknown for the substantive review.\n- **0C Dream state.** Current: enumeration-order output. This plan: one deterministic sort before render. 12-month ideal: all user-visible registries render in stable order. This plan moves toward it.\n- **0E count.** Estimated two changed files (listing source, tests). Estimate only, since the code is not in this checkout. Below every threshold. Category is fix/refactor, no overlap.\n\n**Audit record and provenance**\n\n| Field | Value |\n|---|---|\n| question_id | plan-ceo-review-mode |\n| user_choice | HOLD SCOPE |\n| recommended | HOLD SCOPE |\n| auto_decided | true |\n| session_id | 200041-1791092671-91ee5553 |\n\nBoth the handoff command and the question log exited cleanly.\n\n**Deferred per your instruction**\n\n- The Office Hours offer was not raised. The design doc check found none for this branch.\n- The skill-start output carried a one-time setup prompt to add skill routing rules to CLAUDE.md. Not raised, and plan mode forbids that edit and commit anyway.\n- The remaining HOLD SCOPE route (0G complexity check, 0I temporal interrogation, Sections 1 through 11, the review report) was not run. No plan file was written and ExitPlanMode was not called, since there is no review report to gate on.\n- Skill-end telemetry was not run because the workflow did not complete. Telemetry is off in this session, so nothing is lost.\n\nNext step when you are ready: re-invoke the review with HOLD SCOPE as the settled mode. Since the implementation is not in this checkout, point the substantive review at the repo that holds the listing path so 0B and the test diagram have real code to cite.",
"timestamp": "2026-10-04T05:45:33.721Z"
}
]
},
"tools": [
{
"sessionId": "3eea0abd-2db9-46e3-bab3-da7dcc9c7857",
"timestamp": "2026-10-04T05:44:29.672Z",
"toolUseId": "toolu_01TDuheF7ku6W9HsNfkAERQv",
"kind": "use",
"name": "Bash",
"input": {
"command": "~/.claude/skills/gstack/bin/gstack-skill-start --skill \"plan-ceo-review\" --model \"claude\"",
"description": "Run gstack skill-start preamble"
},
"messageId": "msg_011Cfgfa1Mx6J3DunDBbvdR7",
"requestId": "req_011Cfgfa19J83hFBVegums39"
},
{
"sessionId": "3eea0abd-2db9-46e3-bab3-da7dcc9c7857",
"timestamp": "2026-10-04T05:44:32.032Z",
"toolUseId": "toolu_01TDuheF7ku6W9HsNfkAERQv",
"kind": "result",
"content": "SKILL_START_PROTO: 1\nBRANCH: main\nPROACTIVE: true\nPROACTIVE_PROMPTED: yes\nSKILL_PREFIX: false\nREPO_MODE: unknown\nSESSION_KIND: interactive\nCONDUCTOR_SESSION: true\nACTIVATED: yes\nFIRST_LOOP_SHOWN: yes\nFIRST_TASK: \nLAKE_INTRO: yes\nTELEMETRY: off\nTEL_PROMPTED: yes\nSESSION_ID: 200041-1791092671-91ee5553\nTEL_START: 1791092671\nEXPLAIN_LEVEL: default\nQUESTION_TUNING: true\nUPDATE_CHECK: false\nLEARNINGS: 0\nHAS_ROUTING: no\nROUTING_DECLINED: false\nVENDORED_GSTACK: no\nMODEL_OVERLAY: claude\nGSTACK_PLAN_MODE: active\nARTIFACTS_SYNC: off\nGSTACK_INSTRUCTION_BEGIN: routing-injection 200041-1791092671-91ee5553\ngstack works best when the project CLAUDE.md includes skill routing rules. If no CLAUDE.md exists in the project root, create it. Use AskUserQuestion:\n\n> gstack works best when your project's CLAUDE.md includes skill routing rules.\n\nOptions: A) Add routing rules to CLAUDE.md (recommended) B) No thanks, I'll invoke skills manually\n\nIf A: append this section to the end of CLAUDE.md, then commit it (`git add CLAUDE.md && git commit -m \"chore: add gstack skill routing rules to CLAUDE.md\"`):\n\n## Skill routing\n\nWhen the user's request matches an available skill, invoke it via the Skill tool. When in doubt, invoke the skill.\n\nKey routing rules:\n- Product ideas/brainstorming → invoke /office-hours\n- Strategy/scope → invoke /plan-ceo-review\n- Architecture → invoke /plan-eng-review\n- Design system/plan review → invoke /design-consultation or /plan-design-review\n- Full review pipeline → invoke /autoplan\n- Bugs/errors → invoke /investigate\n- QA/testing site behavior → invoke /qa or /qa-only\n- Code review/diff check → invoke /review\n- Visual polish → invoke /design-review\n- Ship/deploy/PR → invoke /ship or /land-and-deploy\n- Save progress → invoke /context-save\n- Resume context → invoke /context-restore\n- Author a backlog-ready spec/issue → invoke /spec\n\nIf B: run `/tmp/gstack-hermetic-199581-OJuLQB/skill-home-VLvRTR/.claude/skills/gstack/bin/gstack-config set routing_declined true` and say they can re-enable with `/tmp/gstack-hermetic-199581-OJuLQB/skill-home-VLvRTR/.claude/skills/gstack/bin/gstack-config set routing_declined false`. This only happens once per project.\nGSTACK_INSTRUCTION_END",
"isError": false
},
{
"sessionId": "3eea0abd-2db9-46e3-bab3-da7dcc9c7857",
"timestamp": "2026-10-04T05:44:39.611Z",
"toolUseId": "toolu_01Gc2ZLMaYT7Dc82UUQyqdKg",
"kind": "use",
"name": "Bash",
"input": {
"command": "~/.claude/skills/gstack/bin/gstack-context-recovery 2>&1 | head -40",
"description": "Recover recent project context"
},
"messageId": "msg_011Cfgfai1BtXXo2yUzZYVpu",
"requestId": "req_011CfgfahnYQCtEx5koh4NqW"
},
{
"sessionId": "3eea0abd-2db9-46e3-bab3-da7dcc9c7857",
"timestamp": "2026-10-04T05:44:43.304Z",
"toolUseId": "toolu_01Y1esLMcCQDseb5pL48GSgM",
"kind": "use",
"name": "Bash",
"input": {
"command": "echo \"REMOTE: $(git remote get-url origin 2>&1)\"; echo \"HEAD_REF: $(git symbolic-ref refs/remotes/origin/HEAD 2>&1)\"; echo \"--- log\"; git log --oneline -30; echo \"--- stash\"; git stash list; echo \"--- status\"; git status --short; echo \"--- todos grep\"; grep -r \"TODO\\|FIXME\\|HACK\\|XXX\" -l --exclude-dir=node_modules --exclude-dir=vendor --exclude-dir=.git . | head -30; echo \"--- recent files\"; git log --since=30.days --name-only --format=\"\" | sort | uniq -c | sort -rn | head -20; echo \"--- tree\"; find . -path ./.git -prune -o -type f -print | head -50",
"description": "Audit repo history, stashes, TODO markers, and file tree"
},
"messageId": "msg_011Cfgfai1BtXXo2yUzZYVpu",
"requestId": "req_011CfgfahnYQCtEx5koh4NqW"
},
{
"sessionId": "3eea0abd-2db9-46e3-bab3-da7dcc9c7857",
"timestamp": "2026-10-04T05:44:46.808Z",
"toolUseId": "toolu_0163ms4qs32CtCay2kUZExv7",
"kind": "use",
"name": "Bash",
"input": {
"command": "SLUG=$(~/.claude/skills/gstack/browse/bin/remote-slug 2>/dev/null || basename \"$(git rev-parse --show-toplevel 2>/dev/null || pwd)\"); BRANCH=$(git rev-parse --abbrev-ref HEAD 2>/dev/null | tr '/' '-' || echo 'no-branch'); DESIGN=$(~/.claude/skills/gstack/bin/gstack-design-doc-find \"$SLUG\" \"$BRANCH\"); [ -n \"$DESIGN\" ] && echo \"Design doc found: $DESIGN\" || echo \"No design doc found\"; echo \"CROSS_PROJECT: $(~/.claude/skills/gstack/bin/gstack-config get cross_project_learnings 2>/dev/null || echo unset)\"; ~/.claude/skills/gstack/bin/gstack-learnings-search --limit 10 2>&1 | head -20",
"description": "Check for design doc and prior learnings"
},
"messageId": "msg_011Cfgfai1BtXXo2yUzZYVpu",
"requestId": "req_011CfgfahnYQCtEx5koh4NqW"
},
{
"sessionId": "3eea0abd-2db9-46e3-bab3-da7dcc9c7857",
"timestamp": "2026-10-04T05:44:49.434Z",
"toolUseId": "toolu_012SDpZhuZvTUTGNhF7EhGiK",
"kind": "use",
"name": "Bash",
"input": {
"command": "SLUG=$(~/.claude/skills/gstack/bin/gstack-slug --get SLUG 2>/dev/null) || true; echo \"SLUG=$SLUG\"; for d in product goals recent-decisions; do echo \"### $d\"; ~/.claude/skills/gstack/bin/gstack-brain-cache get $d --project \"$SLUG\" 2>/dev/null || echo \"(no $d digest available yet)\"; done; echo \"### user-profile\"; ~/.claude/skills/gstack/bin/gstack-brain-cache get user-profile 2>/dev/null || echo \"(no user-profile digest available yet)\"",
"description": "Load brain context digests"
},
"messageId": "msg_011Cfgfai1BtXXo2yUzZYVpu",
"requestId": "req_011CfgfahnYQCtEx5koh4NqW"
},
{
"sessionId": "3eea0abd-2db9-46e3-bab3-da7dcc9c7857",
"timestamp": "2026-10-04T05:44:51.435Z",
"toolUseId": "toolu_01CnoUijouGBKqemuXC37uQa",
"kind": "use",
"name": "Bash",
"input": {
"command": "printf '%s' \"Select plan-ceo-review mode: SCOPE EXPANSION, SELECTIVE EXPANSION, HOLD SCOPE, or SCOPE REDUCTION for deterministic skill-list ordering plan\" | ~/.claude/skills/gstack/bin/gstack-question-preference --check \"plan-ceo-review-mode\" --summary-stdin; echo \"EXIT: $?\"",
"description": "Check question preference for review mode"
},
"messageId": "msg_011Cfgfai1BtXXo2yUzZYVpu",
"requestId": "req_011CfgfahnYQCtEx5koh4NqW"
},
{
"sessionId": "3eea0abd-2db9-46e3-bab3-da7dcc9c7857",
"timestamp": "2026-10-04T05:44:51.901Z",
"toolUseId": "toolu_01YRX1MZhLWB5RurfJ4NxXHv",
"kind": "use",
"name": "Read",
"input": {
"file_path": "/tmp/gstack-plan-count-TqfCDl/PLAN.md"
},
"messageId": "msg_011Cfgfai1BtXXo2yUzZYVpu",
"requestId": "req_011CfgfahnYQCtEx5koh4NqW"
},
{
"sessionId": "3eea0abd-2db9-46e3-bab3-da7dcc9c7857",
"timestamp": "2026-10-04T05:44:52.607Z",
"toolUseId": "toolu_01Gc2ZLMaYT7Dc82UUQyqdKg",
"kind": "result",
"content": "--- RECENT ARTIFACTS ---\n{\"skill\":\"plan-ceo-review\",\"event\":\"started\",\"branch\":\"main\",\"session\":\"200041-1791092671-91ee5553\",\"ts\":\"2026-10-04T05:44:32.100Z\"}\n--- END ARTIFACTS ---",
"isError": false
},
{
"sessionId": "3eea0abd-2db9-46e3-bab3-da7dcc9c7857",
"timestamp": "2026-10-04T05:44:52.829Z",
"toolUseId": "toolu_01Y1esLMcCQDseb5pL48GSgM",
"kind": "result",
"content": "REMOTE: error: No such remote 'origin'\nHEAD_REF: fatal: ref refs/remotes/origin/HEAD is not a symbolic ref\n--- log\n7575cfb Seed review plan\n--- stash\n--- status\n--- todos grep\n--- recent files\n 1 PLAN.md\n 1 CLAUDE.md\n--- tree\n./PLAN.md\n./CLAUDE.md",
"isError": false
},
{
"sessionId": "3eea0abd-2db9-46e3-bab3-da7dcc9c7857",
"timestamp": "2026-10-04T05:44:53.130Z",
"toolUseId": "toolu_0163ms4qs32CtCay2kUZExv7",
"kind": "result",
"content": "No design doc found\nCROSS_PROJECT: false",
"isError": false
},
{
"sessionId": "3eea0abd-2db9-46e3-bab3-da7dcc9c7857",
"timestamp": "2026-10-04T05:44:53.317Z",
"toolUseId": "toolu_012SDpZhuZvTUTGNhF7EhGiK",
"kind": "result",
"content": "SLUG=gstack-plan-count-TqfCDl\n### product\n(no product digest available yet)\n### goals\n(no goals digest available yet)\n### recent-decisions\n(no recent-decisions digest available yet)\n### user-profile\n(no user-profile digest available yet)",
"isError": false
},
{
"sessionId": "3eea0abd-2db9-46e3-bab3-da7dcc9c7857",
"timestamp": "2026-10-04T05:44:53.525Z",
"toolUseId": "toolu_01CnoUijouGBKqemuXC37uQa",
"kind": "result",
"content": "AUTO_DECIDE\nEXIT: 0",
"isError": false
},
{
"sessionId": "3eea0abd-2db9-46e3-bab3-da7dcc9c7857",
"timestamp": "2026-10-04T05:44:53.551Z",
"toolUseId": "toolu_01YRX1MZhLWB5RurfJ4NxXHv",
"kind": "result",
"content": "1\t# Draft: deterministic skill-list ordering\n2\t\n3\tUsers compare skill listings in scripts and reviews. Make the existing listing\n4\tpath sort registered skill names deterministically before rendering them.\n5\tKeep skill membership, aliases, metadata and text/JSON output formats unchanged.\n6\tCover mixed-case names and differing directory enumeration order with tests.\n7\t\n8\tThis draft is the review target, not the current branch. For this invocation,\n9\tI want only the review-mode decision; I will handle optional Office Hours and\n10\tsetup separately, and run the substantive review later. No review mode has\n11\tbeen selected. For this mode-only handoff, use the full selected mode name in\n12\tthe normal audit record's user_choice and recommended fields and in the skill's\n13\tnormal mode handoff line. A menu letter alone would not identify the chosen mode.",
"file": {
"filePath": "/tmp/gstack-plan-count-TqfCDl/PLAN.md",
"content": "# Draft: deterministic skill-list ordering\n\nUsers compare skill listings in scripts and reviews. Make the existing listing\npath sort registered skill names deterministically before rendering them.\nKeep skill membership, aliases, metadata and text/JSON output formats unchanged.\nCover mixed-case names and differing directory enumeration order with tests.\n\nThis draft is the review target, not the current branch. For this invocation,\nI want only the review-mode decision; I will handle optional Office Hours and\nsetup separately, and run the substantive review later. No review mode has\nbeen selected. For this mode-only handoff, use the full selected mode name in\nthe normal audit record's user_choice and recommended fields and in the skill's\nnormal mode handoff line. A menu letter alone would not identify the chosen mode.",
"numLines": 13,
"startLine": 1,
"totalLines": 13
},
"isError": false
},
{
"sessionId": "3eea0abd-2db9-46e3-bab3-da7dcc9c7857",
"timestamp": "2026-10-04T05:45:16.905Z",
"toolUseId": "toolu_01Jn15qP1z8x4jRCS5zRtWJC",
"kind": "use",
"name": "Bash",
"input": {
"command": "~/.claude/skills/gstack/bin/gstack-ceo-mode-handoff \"HOLD SCOPE\" --decisions \"none\" --auto; echo \"HANDOFF_EXIT: $?\"; ~/.claude/skills/gstack/bin/gstack-question-log '{\"skill\":\"plan-ceo-review\",\"question_id\":\"plan-ceo-review-mode\",\"question_summary\":\"Select plan-ceo-review mode for deterministic skill-list ordering plan\",\"category\":\"routing\",\"door_type\":\"two-way\",\"options_count\":4,\"user_choice\":\"HOLD SCOPE\",\"recommended\":\"HOLD SCOPE\",\"auto_decided\":true,\"session_id\":\"200041-1791092671-91ee5553\"}'; echo \"QLOG_EXIT: $?\"",
"description": "Run mode handoff and log the auto-decided mode question"
},
"messageId": "msg_011CfgfcJ5qNxo7xp7SnVfEP",
"requestId": "req_011CfgfcHrgxFL9dPKXGrcXu"
},
{
"sessionId": "3eea0abd-2db9-46e3-bab3-da7dcc9c7857",
"timestamp": "2026-10-04T05:45:18.007Z",
"toolUseId": "toolu_01Jn15qP1z8x4jRCS5zRtWJC",
"kind": "result",
"content": "Auto-decided review mode → HOLD SCOPE (your preference). Change with /plan-tune. Approved decisions: none.\nHANDOFF_EXIT: 0\nQLOG_EXIT: 0",
"isError": false
}
]
}
+4 -2
View File
@@ -15,7 +15,7 @@ export interface NativeAutoDecision {
}
const plain = (text: string) => text.replace(/\*\*([^*]+)\*\*/g, '$1').trim();
const annotationLine = /^Auto-decided ([^\r\n→]{1,240}) → ([^\r\n→]{1,200}) \(your (?:preference|saved preference on `([a-z][a-z0-9-]*)`)\)\. Change with \/plan-tune\.$/;
const annotationLine = /^Auto-decided ([^\r\n→]{1,240}) → ([^\r\n→]{1,200}) \(your (?:preference|saved preference on `([a-z][a-z0-9-]*)`)\)\. Change with \/plan-tune\.(?: Approved decisions: [^\r\n]+)?$/;
/** Asserted prose only; later quoted examples cannot retract a current decision. */
function publicProse(text: string): string {
@@ -263,7 +263,9 @@ function currentModeStatement(text: string, questionSummary?: string): { option:
for (let i = 0; i < lines.length; i++) {
const statement = lines[i]!.replace(/^\s*[-*+]\s+/, '').trim();
const field = modeField(statement);
const option = field?.completed ? selectedMode(field.value, questionSummary) : null;
// Step 0E's documented AUTO_DECIDE handoff line declares the mode it names.
const handoff = /^Auto-decided review mode → ([A-Z ]+) \(your preference\)\. Change with \/plan-tune\. Approved decisions: \S/.exec(statement);
const option = handoff ? handoff[1]! : field?.completed ? selectedMode(field.value, questionSummary) : null;
if (!option || !modeNames.includes(option)) continue;
// Source/example introductions and conditional selections cannot supply
// a current declaration merely by putting a Mode field on the next line.
+1 -1
View File
@@ -317,7 +317,7 @@ export const E2E_TOUCHFILES: Record<string, string[]> = {
// written a never-ask preference, AUQ should still auto-decide rather than
// surfacing the question. Touches the question-tuning + preference
// infrastructure plus the resolvers that own the AUTO_DECIDE preamble.
'auto-decide-preserved': ['bin/gstack-ceo-mode-handoff', 'bin/gstack-state-root.sh', 'lib/state-root.ts', 'lib/claude-public-transcript.ts',
'auto-decide-preserved': ['bin/gstack-ceo-mode-handoff', 'test/fixtures/auto-decide-handoff-line-e354.json', 'bin/gstack-state-root.sh', 'lib/state-root.ts', 'lib/claude-public-transcript.ts',
'test/fixtures/auto-decide-recommendation-361c.json',