mirror of
https://github.com/garrytan/gstack.git
synced 2026-10-31 22:14:25 +01:00
test(auto-decide): the printed handoff may precede Step 0E's provenance log; replay census 37182865432; TODOS for two rotating census reds
This commit is contained in:
1 parent
44ef18cd0e
commit
d89dc326c2
5 files changed
+282
-7
No files matched your search
@@ -137,6 +137,24 @@ Deferred from the approved severe fix wave (docs/designs/SEVERE_FIX_WAVE_2026_10
|
||||
is `test/fixtures/auto-decide-handoff-line-e354.json`). Local paid trials after
|
||||
the repair: auto-decide-preserved 1/1, plan-ceo-mode-routing 1/1 (both modes);
|
||||
every session ran the helper.
|
||||
- **plan-ceo-section-loading misses correctly worded stale-fill findings** —
|
||||
censuses 37178734784 (65d94f2) and 37182865432 (44ef18c) failed
|
||||
`hasStaleFillRaceFinding` although the review found the race. In 44ef18c it
|
||||
named schedule S2 ("a read can fetch an old copy, pause, and only then put
|
||||
it in the cache… every later reader gets stale data") and approved a
|
||||
fill-admission guard. The detector is a regex family over phrasings; adding a
|
||||
spelling per failure is the wrong fix. Bind the case to a structured finding
|
||||
record (or an LLM judge with a fixed rubric) instead. 2 of the last 3
|
||||
censuses, 0 of the 16 before. Effort M. **Priority:** P2.
|
||||
- **auq-matrix recommendation-substance judge rotates red** — census
|
||||
37182865432 scored plan-design-review's "A because the plan is thin
|
||||
everywhere, so skipping dimensions mostly skips gaps" 3 < 4. The same case
|
||||
was red in 5 of the 16 earlier censuses (37151477069 on main,
|
||||
36898545245, 36787269090, 36641820398, 36633323521) across skills and checks
|
||||
(a two-`(recommended)` option count; office-hours substance). It is kind
|
||||
`rule` but gates on one judge sample. Decide under EVAL_POLICY whether it is
|
||||
a `judge` case (3 samples, mean) and supply pass-rate evidence before
|
||||
changing the kind. Effort S. **Priority:** P2.
|
||||
- **Floor runner cannot grant an owned Edit when the TTY collapses spaces** —
|
||||
PR run 37176835584 (7249f01): plan-ceo-finding-floor timed out at 600 s with
|
||||
the native "Do you want to make this edit to gstack-test-plan-ceo-floor.md?"
|
||||
|
||||
@@ -109,7 +109,8 @@ describe('native handoff evidence', () => {
|
||||
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));
|
||||
const started = Date.parse(early.tools.find((e: any) => e.kind === 'result' && String(e.content).includes('SKILL_START_PROTO')).timestamp);
|
||||
early.tools.push(...handoff(early.options.sessionId, started - 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));
|
||||
@@ -143,6 +144,39 @@ describe('native handoff evidence', () => {
|
||||
}
|
||||
});
|
||||
|
||||
test('census 37182865432: the printed handoff before the provenance log is the declaration', () => {
|
||||
const captured = JSON.parse(fs.readFileSync(path.join(import.meta.dir, 'fixtures/auto-decide-handoff-before-log-37182865432.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);
|
||||
// As captured, the closing "**Mode decided: HOLD SCOPE.**" chat also declares the mode.
|
||||
expect(decide(attempt())).toMatchObject({ option: 'HOLD SCOPE' });
|
||||
const handoffOnly = () => {
|
||||
const f = attempt();
|
||||
f.transcript.assistantMessages.find((m: any) => m.text.startsWith('**Mode decided:')).text = 'Wrapping up this invocation.';
|
||||
return f;
|
||||
};
|
||||
expect(decide(handoffOnly())).toMatchObject({ option: 'HOLD SCOPE', annotation: AUTO_LINE });
|
||||
for (const [name, mutate] of Object.entries({
|
||||
'handoff output altered': (f: any) => { f.tools.find((e: any) => e.kind === 'result' && e.content === AUTO_LINE).content = AUTO_LINE.replace('HOLD SCOPE', 'SCOPE EXPANSION'); },
|
||||
'record names another mode': (f: any) => { f.options.stateEvidence.records[0].user_choice = 'SCOPE EXPANSION'; f.options.stateEvidence.records[0].recommended = 'SCOPE EXPANSION'; },
|
||||
'later withdrawal': (f: any) => { f.transcript.assistantMessages.push({ sessionId: f.options.sessionId, timestamp: new Date(f.options.now - 1000).toISOString(), text: 'Correction: I withdraw this decision.' }); },
|
||||
'missing record': (f: any) => { f.options.stateEvidence.records = []; },
|
||||
})) {
|
||||
const f = handoffOnly(); mutate(f); expect(decide(f), name).toBeNull();
|
||||
}
|
||||
const pending = attempt();
|
||||
pending.transcript.assistantMessages.find((m: any) => m.text.startsWith('**Mode decided:')).text = '**Mode pending: HOLD SCOPE.**';
|
||||
expect(decide(pending)).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-'));
|
||||
|
||||
@@ -0,0 +1,221 @@
|
||||
{
|
||||
"source": "Periodic census 37182865432 (44ef18c), auto-decide-preserved slice 20: the model ran gstack-ceo-mode-handoff, then logged provenance as Step 0E orders; the detector required the handoff after the log and timed out.",
|
||||
"options": {
|
||||
"skillName": "plan-ceo-review",
|
||||
"sessionId": "1d84eb71-de32-4e99-a62e-9046d0850c10",
|
||||
"commandStartedAt": 1791095476944,
|
||||
"now": 1791095559635
|
||||
},
|
||||
"transcript": {
|
||||
"status": "ready",
|
||||
"calls": [],
|
||||
"assistantMessages": [
|
||||
{
|
||||
"sessionId": "1d84eb71-de32-4e99-a62e-9046d0850c10",
|
||||
"text": "Received. I have the draft plan for deterministic skill-list ordering as context, along with your instructions: this is a mode-only handoff, no review mode has been selected yet, and the audit record's user_choice and recommended fields plus the mode handoff line should carry the full selected mode name rather than a menu letter. I'll wait for your slash command before starting anything.",
|
||||
"timestamp": "2026-10-04T06:31:16.610Z"
|
||||
},
|
||||
{
|
||||
"sessionId": "1d84eb71-de32-4e99-a62e-9046d0850c10",
|
||||
"text": "Starting the plan-ceo-review preamble now. Per your instructions, I'll scope this run to the mode decision only: skip the Office Hours offer and setup prompts, gather just enough plan context to recommend a mode, ask the four-mode question, then run the handoff and audit record.",
|
||||
"timestamp": "2026-10-04T06:31:24.520Z"
|
||||
},
|
||||
{
|
||||
"sessionId": "1d84eb71-de32-4e99-a62e-9046d0850c10",
|
||||
"text": "Preamble checks out, and with Conductor session and question tuning active, I'll check mode preference and send decision briefs as prose. I'm deferring the routing-rules setup since you'll handle it separately and plan mode blocks CLAUDE.md edits anyway\u2014moving on to gather plan context.\n\n",
|
||||
"timestamp": "2026-10-04T06:31:37.194Z"
|
||||
},
|
||||
{
|
||||
"sessionId": "1d84eb71-de32-4e99-a62e-9046d0850c10",
|
||||
"text": "I've gathered context: only the plan fixture exists in the repo, no remote, so the base branch defaults to `main`, and the saved preference (AUTO_DECIDE) means I'll go with my recommendation of HOLD SCOPE. Running the mode handoff now.\n\n",
|
||||
"timestamp": "2026-10-04T06:32:05.754Z"
|
||||
},
|
||||
{
|
||||
"sessionId": "1d84eb71-de32-4e99-a62e-9046d0850c10",
|
||||
"text": "I'm defaulting to HOLD SCOPE review mode since this is a small, tightly-invariant fix (deterministic sort on an existing listing path, ~1-3 files) with no new approach needed\u2014focus will be on sort correctness (case handling, tie-breaking, enumeration-order tests). Recording provenance and wrapping up this invocation.\n\n",
|
||||
"timestamp": "2026-10-04T06:32:19.720Z"
|
||||
},
|
||||
{
|
||||
"sessionId": "1d84eb71-de32-4e99-a62e-9046d0850c10",
|
||||
"text": "**Mode decided: HOLD SCOPE.** The substantive review has not started, per your request.\n\nWhat this run did:\n- **Preamble** ran healthy. Conductor session, interactive, question tuning on.\n- **Mode selection** resolved by your saved preference for the mode question, which returned AUTO_DECIDE, so no question was asked. The handoff command printed the auto-decided line above.\n- **Audit record** logged with question id `plan-ceo-review-mode`, user_choice and recommended both set to the full name HOLD SCOPE, auto_decided true.\n- **Telemetry** recorded with outcome abort, since the review was intentionally stopped after the mode step.\n\nWhat this run skipped, by your instruction:\n- The Office Hours offer. No design doc exists for this branch, so it would normally fire.\n- The routing-rules setup prompt from skill-start. It is marker-based and will fire again on the next healthy run. Plan mode would also have blocked the CLAUDE.md edit and commit.\n- Review Sections 1 to 11, the plan file report, and ExitPlanMode. Nothing was written to the plan file.\n\nWhen you resume, HOLD SCOPE routes through 0G complexity check, 0I temporal interrogation, then the full review sections. Points worth pressing then: byte-order versus case-insensitive sorting for mixed-case names, tie-breaking for names that differ only by case, and whether the tests truly vary directory enumeration order instead of relying on the filesystem.\n\nSTATUS: DONE\nNo durable learnings this session.",
|
||||
"timestamp": "2026-10-04T06:32:34.635Z"
|
||||
}
|
||||
]
|
||||
},
|
||||
"tools": [
|
||||
{
|
||||
"sessionId": "1d84eb71-de32-4e99-a62e-9046d0850c10",
|
||||
"timestamp": "2026-10-04T06:31:25.439Z",
|
||||
"toolUseId": "toolu_01L7uumE3CicSqLq5YF1knYj",
|
||||
"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_011Cfgj9VVtgc1YPY6mmRiFm",
|
||||
"requestId": "req_011Cfgj9VHEbELRBQiu2UADr"
|
||||
},
|
||||
{
|
||||
"sessionId": "1d84eb71-de32-4e99-a62e-9046d0850c10",
|
||||
"timestamp": "2026-10-04T06:31:26.964Z",
|
||||
"toolUseId": "toolu_01L7uumE3CicSqLq5YF1knYj",
|
||||
"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: 1582-1791095486-11f777bc\nTEL_START: 1791095486\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 1582-1791095486-11f777bc\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 \u2192 invoke /office-hours\n- Strategy/scope \u2192 invoke /plan-ceo-review\n- Architecture \u2192 invoke /plan-eng-review\n- Design system/plan review \u2192 invoke /design-consultation or /plan-design-review\n- Full review pipeline \u2192 invoke /autoplan\n- Bugs/errors \u2192 invoke /investigate\n- QA/testing site behavior \u2192 invoke /qa or /qa-only\n- Code review/diff check \u2192 invoke /review\n- Visual polish \u2192 invoke /design-review\n- Ship/deploy/PR \u2192 invoke /ship or /land-and-deploy\n- Save progress \u2192 invoke /context-save\n- Resume context \u2192 invoke /context-restore\n- Author a backlog-ready spec/issue \u2192 invoke /spec\n\nIf B: run `/home/runner/.cache/gstack-paid-shard-JD3Soh/tmp/gstack-hermetic-1394-NpFf9B/skill-home-IidIe9/.claude/skills/gstack/bin/gstack-config set routing_declined true` and say they can re-enable with `/home/runner/.cache/gstack-paid-shard-JD3Soh/tmp/gstack-hermetic-1394-NpFf9B/skill-home-IidIe9/.claude/skills/gstack/bin/gstack-config set routing_declined false`. This only happens once per project.\nGSTACK_INSTRUCTION_END",
|
||||
"isError": false
|
||||
},
|
||||
{
|
||||
"sessionId": "1d84eb71-de32-4e99-a62e-9046d0850c10",
|
||||
"timestamp": "2026-10-04T06:31:37.196Z",
|
||||
"toolUseId": "toolu_01WANu5n8ZmUR9vrNDrNSBwU",
|
||||
"kind": "use",
|
||||
"name": "Read",
|
||||
"input": {
|
||||
"file_path": "/home/runner/.cache/gstack-paid-shard-JD3Soh/tmp/gstack-plan-count-DAXalf/PLAN.md"
|
||||
},
|
||||
"messageId": "msg_011CfgjAEx4HP8MpqmQTz4Qf",
|
||||
"requestId": "req_011CfgjADu3noWMS75Adfr5q"
|
||||
},
|
||||
{
|
||||
"sessionId": "1d84eb71-de32-4e99-a62e-9046d0850c10",
|
||||
"timestamp": "2026-10-04T06:31:37.215Z",
|
||||
"toolUseId": "toolu_01WANu5n8ZmUR9vrNDrNSBwU",
|
||||
"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": "/home/runner/.cache/gstack-paid-shard-JD3Soh/tmp/gstack-plan-count-DAXalf/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": "1d84eb71-de32-4e99-a62e-9046d0850c10",
|
||||
"timestamp": "2026-10-04T06:31:39.661Z",
|
||||
"toolUseId": "toolu_016Dk1tbvmvWJFZPCZbBKtx5",
|
||||
"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 \"--- files\"; ls -la; echo \"--- todos\"; grep -rn \"TODO\\|FIXME\\|HACK\\|XXX\" -l --exclude-dir=node_modules --exclude-dir=vendor --exclude-dir=.git . | head -30; echo \"--- TODOS.md\"; cat TODOS.md 2>/dev/null || echo \"(none)\"",
|
||||
"description": "Audit repo state, remote, history and TODOs"
|
||||
},
|
||||
"messageId": "msg_011CfgjAEx4HP8MpqmQTz4Qf",
|
||||
"requestId": "req_011CfgjADu3noWMS75Adfr5q"
|
||||
},
|
||||
{
|
||||
"sessionId": "1d84eb71-de32-4e99-a62e-9046d0850c10",
|
||||
"timestamp": "2026-10-04T06:31:41.663Z",
|
||||
"toolUseId": "toolu_0153M6xQkpMgLtck4F849Qwh",
|
||||
"kind": "use",
|
||||
"name": "Bash",
|
||||
"input": {
|
||||
"command": "printf '%s' \"Select the plan-ceo-review mode for the deterministic skill-list ordering plan: SCOPE EXPANSION, SELECTIVE EXPANSION, HOLD SCOPE, or SCOPE REDUCTION\" | ~/.claude/skills/gstack/bin/gstack-question-preference --check \"plan-ceo-review-mode\" --summary-stdin; echo \"PREF_EXIT: $?\"",
|
||||
"description": "Check saved preference for the mode question"
|
||||
},
|
||||
"messageId": "msg_011CfgjAEx4HP8MpqmQTz4Qf",
|
||||
"requestId": "req_011CfgjADu3noWMS75Adfr5q"
|
||||
},
|
||||
{
|
||||
"sessionId": "1d84eb71-de32-4e99-a62e-9046d0850c10",
|
||||
"timestamp": "2026-10-04T06:31:44.535Z",
|
||||
"toolUseId": "toolu_01QoUodomJaBeM9zZB5VKzoN",
|
||||
"kind": "use",
|
||||
"name": "Bash",
|
||||
"input": {
|
||||
"command": "~/.claude/skills/gstack/bin/gstack-context-recovery 2>&1 | head -40; echo \"--- design doc\"; SLUG=$(~/.claude/skills/gstack/browse/bin/remote-slug 2>/dev/null || basename \"$(git -C /home/runner/.cache/gstack-paid-shard-JD3Soh/tmp/gstack-plan-count-DAXalf rev-parse --show-toplevel 2>/dev/null || pwd)\"); BRANCH=main; DESIGN=$(~/.claude/skills/gstack/bin/gstack-design-doc-find \"$SLUG\" \"$BRANCH\" 2>/dev/null); [ -n \"$DESIGN\" ] && echo \"Design doc found: $DESIGN\" || echo \"No design doc found\"",
|
||||
"description": "Recover context and check for design doc"
|
||||
},
|
||||
"messageId": "msg_011CfgjAEx4HP8MpqmQTz4Qf",
|
||||
"requestId": "req_011CfgjADu3noWMS75Adfr5q"
|
||||
},
|
||||
{
|
||||
"sessionId": "1d84eb71-de32-4e99-a62e-9046d0850c10",
|
||||
"timestamp": "2026-10-04T06:31:45.415Z",
|
||||
"toolUseId": "toolu_016Dk1tbvmvWJFZPCZbBKtx5",
|
||||
"kind": "result",
|
||||
"content": "REMOTE: error: No such remote 'origin'\nHEAD_REF: fatal: ref refs/remotes/origin/HEAD is not a symbolic ref\n--- log\n6e5f800 Seed review plan\n--- stash\n--- files\ntotal 20\ndrwx------ 3 runner runner 4096 Oct 4 06:31 .\ndrwxr-xr-x 7 runner runner 4096 Oct 4 06:31 ..\ndrwxr-xr-x 8 runner runner 4096 Oct 4 06:31 .git\n-rw-r--r-- 1 runner runner 1132 Oct 4 06:31 CLAUDE.md\n-rw-r--r-- 1 runner runner 826 Oct 4 06:31 PLAN.md\n--- todos\n--- TODOS.md\n(none)",
|
||||
"isError": false
|
||||
},
|
||||
{
|
||||
"sessionId": "1d84eb71-de32-4e99-a62e-9046d0850c10",
|
||||
"timestamp": "2026-10-04T06:31:45.505Z",
|
||||
"toolUseId": "toolu_0153M6xQkpMgLtck4F849Qwh",
|
||||
"kind": "result",
|
||||
"content": "AUTO_DECIDE\nPREF_EXIT: 0",
|
||||
"isError": false
|
||||
},
|
||||
{
|
||||
"sessionId": "1d84eb71-de32-4e99-a62e-9046d0850c10",
|
||||
"timestamp": "2026-10-04T06:31:45.590Z",
|
||||
"toolUseId": "toolu_01QoUodomJaBeM9zZB5VKzoN",
|
||||
"kind": "result",
|
||||
"content": "--- RECENT ARTIFACTS ---\n{\"skill\":\"plan-ceo-review\",\"event\":\"started\",\"branch\":\"main\",\"session\":\"1582-1791095486-11f777bc\",\"ts\":\"2026-10-04T06:31:26.989Z\"}\n--- END ARTIFACTS ---\n--- design doc\nNo design doc found",
|
||||
"isError": false
|
||||
},
|
||||
{
|
||||
"sessionId": "1d84eb71-de32-4e99-a62e-9046d0850c10",
|
||||
"timestamp": "2026-10-04T06:32:05.755Z",
|
||||
"toolUseId": "toolu_01JuPbmLq6ZmKbP3t91p9szw",
|
||||
"kind": "use",
|
||||
"name": "Bash",
|
||||
"input": {
|
||||
"command": "~/.claude/skills/gstack/bin/gstack-ceo-mode-handoff \"HOLD SCOPE\" --decisions \"none\" --auto",
|
||||
"description": "Run gstack CEO mode handoff for HOLD SCOPE"
|
||||
},
|
||||
"messageId": "msg_011CfgjBbpD8J6519jkqvLre",
|
||||
"requestId": "req_011CfgjBbYqDtppjsWAGqNoo"
|
||||
},
|
||||
{
|
||||
"sessionId": "1d84eb71-de32-4e99-a62e-9046d0850c10",
|
||||
"timestamp": "2026-10-04T06:32:11.420Z",
|
||||
"toolUseId": "toolu_01JuPbmLq6ZmKbP3t91p9szw",
|
||||
"kind": "result",
|
||||
"content": "Auto-decided review mode \u2192 HOLD SCOPE (your preference). Change with /plan-tune. Approved decisions: none.",
|
||||
"isError": false
|
||||
},
|
||||
{
|
||||
"sessionId": "1d84eb71-de32-4e99-a62e-9046d0850c10",
|
||||
"timestamp": "2026-10-04T06:32:20.892Z",
|
||||
"toolUseId": "toolu_01PbxZacUu8Ph611URXMobAZ",
|
||||
"kind": "use",
|
||||
"name": "Bash",
|
||||
"input": {
|
||||
"command": "~/.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\":\"1582-1791095486-11f777bc\"}'; echo \"QLOG_EXIT: $?\"",
|
||||
"description": "Log auto-decided mode question to audit record"
|
||||
},
|
||||
"messageId": "msg_011CfgjDWBvJPKDwNcYbjB7h",
|
||||
"requestId": "req_011CfgjDVy2SFveysWKw5SbU"
|
||||
},
|
||||
{
|
||||
"sessionId": "1d84eb71-de32-4e99-a62e-9046d0850c10",
|
||||
"timestamp": "2026-10-04T06:32:22.625Z",
|
||||
"toolUseId": "toolu_01RKKa2pmNcBXzN8VTEWWaM6",
|
||||
"kind": "use",
|
||||
"name": "Bash",
|
||||
"input": {
|
||||
"command": "~/.claude/skills/gstack/bin/gstack-skill-end --skill \"plan-ceo-review\" --outcome abort --session-id \"1582-1791095486-11f777bc\" --tel-start \"1791095486\" --used-browse no --error-message \"\" --failed-step \"\" 2>/dev/null || true; echo \"TEL_EXIT: $?\"",
|
||||
"description": "Run skill-end telemetry for the mode-only run"
|
||||
},
|
||||
"messageId": "msg_011CfgjDWBvJPKDwNcYbjB7h",
|
||||
"requestId": "req_011CfgjDVy2SFveysWKw5SbU"
|
||||
},
|
||||
{
|
||||
"sessionId": "1d84eb71-de32-4e99-a62e-9046d0850c10",
|
||||
"timestamp": "2026-10-04T06:32:23.792Z",
|
||||
"toolUseId": "toolu_01PbxZacUu8Ph611URXMobAZ",
|
||||
"kind": "result",
|
||||
"content": "QLOG_EXIT: 0",
|
||||
"isError": false
|
||||
},
|
||||
{
|
||||
"sessionId": "1d84eb71-de32-4e99-a62e-9046d0850c10",
|
||||
"timestamp": "2026-10-04T06:32:23.918Z",
|
||||
"toolUseId": "toolu_01RKKa2pmNcBXzN8VTEWWaM6",
|
||||
"kind": "result",
|
||||
"content": "SKILL_END: recorded outcome=abort duration_s=57\nTEL_EXIT: 0",
|
||||
"isError": false
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -47,10 +47,11 @@ function modeField(line: string): { value: string; completed: boolean } | null {
|
||||
const match = /^(?:(?:Correction|Actually|Update):\s*)?(?<label>(?:Review )?Mode(?: decision)?|Decision)(?: (?<status>[^:\r\n]+))?:\s*(?<value>.*)$/i.exec(line.replace(/^\s*[-*+]\s+/, '').trim());
|
||||
if (!match) return null;
|
||||
// "Mode" and "Mode decision" are both field labels. If an explicit status
|
||||
// follows, only the completion class is supported. Pending, cancelled,
|
||||
// follows, only the completion class (including decided/selected/chosen) is
|
||||
// supported. Pending, cancelled,
|
||||
// unfinished and unknown statuses also invalidate an earlier declaration.
|
||||
const { label, status, value: rawValue } = match.groups!;
|
||||
const completeStatus = !status || /^(?:done|complete|completed)$/i.test(status.trim());
|
||||
const completeStatus = !status || /^(?:done|complete|completed|decided|selected|chosen)$/i.test(status.trim());
|
||||
const explicitMode = /^(?:the )?(?:review )?mode\b(?:\s+is\b|:|\s*=(?!=))?\s*/i;
|
||||
// An unqualified Decision field owns a review mode only when its value
|
||||
// names that vocabulary. Keep unrelated decisions out of withdrawal checks;
|
||||
@@ -317,7 +318,8 @@ function structuredModeDecision(transcript: PlanCountTranscript, tools: NativePu
|
||||
if (starts.length !== 1) return null;
|
||||
const start = starts[0]!, questionId = `${opts.skillName}-mode`;
|
||||
// The handoff helper's printed AUTO_DECIDE line is the current declaration
|
||||
// when it follows the decision record and nothing later withdraws or changes it.
|
||||
// when it follows the preference check and nothing later withdraws or changes
|
||||
// it. Step 0E records provenance after the handoff, so the log may follow it.
|
||||
const autoHandoff = (after: number, option: string, summary: string) => {
|
||||
const handoff = ceoModeHandoffs(owned, opts.sessionId).find(h => h.auto && h.option === option &&
|
||||
timely(h.timestamp) && time(h.timestamp) >= after);
|
||||
@@ -339,7 +341,7 @@ function structuredModeDecision(transcript: PlanCountTranscript, tools: NativePu
|
||||
row.auto_decided === true && modeValue(row.user_choice) && modeValue(row.user_choice) === modeValue(row.recommended) &&
|
||||
typeof row.question_summary === 'string' && row.question_summary.trim() &&
|
||||
loggedAt >= time(start.result.timestamp) && loggedAt <= opts.now) {
|
||||
const handoff = autoHandoff(loggedAt, modeValue(row.user_choice)!, row.question_summary);
|
||||
const handoff = autoHandoff(time(start.result.timestamp), modeValue(row.user_choice)!, row.question_summary);
|
||||
if (handoff) return { ...handoff, summary: row.question_summary, preambleToolUseId: start.use.toolUseId, stateRecord: row };
|
||||
for (const message of current) {
|
||||
const declared = currentModeStatement(message.text, row.question_summary);
|
||||
@@ -389,7 +391,7 @@ function structuredModeDecision(transcript: PlanCountTranscript, tools: NativePu
|
||||
});
|
||||
if (logs.length !== 1 || !logs[0]!.result) return null;
|
||||
const logged = logs[0]!;
|
||||
const handoff = autoHandoff(time(logged.result!.timestamp), modeValue(logged.log.user_choice)!, logged.log.question_summary);
|
||||
const handoff = autoHandoff(time(check.result!.timestamp), modeValue(logged.log.user_choice)!, logged.log.question_summary);
|
||||
if (handoff) return { ...handoff, summary: logged.log.question_summary, preambleToolUseId: start.use.toolUseId,
|
||||
preferenceToolUseId: check.use.toolUseId, questionLogToolUseId: logged.use.toolUseId };
|
||||
for (const message of current) {
|
||||
|
||||
@@ -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', 'test/fixtures/auto-decide-handoff-line-e354.json', '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', 'test/fixtures/auto-decide-handoff-before-log-37182865432.json', 'bin/gstack-state-root.sh', 'lib/state-root.ts', 'lib/claude-public-transcript.ts',
|
||||
|
||||
'test/fixtures/auto-decide-recommendation-361c.json',
|
||||
|
||||
|
||||
Reference in new issue
Block a user