From 9116540c8f0bdac6dd7bab5041c9f3245981daf8 Mon Sep 17 00:00:00 2001 From: Christoph Maier Date: Mon, 20 Jul 2026 15:31:30 +0200 Subject: [PATCH 01/42] feature(safe-role-model-handoff): Prevent failed role model restoration Feature: safe-role-model-handoff --- extensions/iterator.js | 36 ++++-- memory/backlog/index.md | 14 ++- memory/features/index.md | 3 + memory/features/safe-role-model-handoff.md | 43 +++++++ memory/index.md | 2 + memory/log.md | 17 +++ memory/plan.md | 36 ++++++ memory/state.md | 6 +- memory/usage.md | 27 +++-- test/extension-model-lifecycle.test.mjs | 125 +++++++++++++++++++++ 10 files changed, 282 insertions(+), 27 deletions(-) create mode 100644 memory/features/index.md create mode 100644 memory/features/safe-role-model-handoff.md create mode 100644 memory/plan.md create mode 100644 test/extension-model-lifecycle.test.mjs diff --git a/extensions/iterator.js b/extensions/iterator.js index df9c841..a5acd7a 100644 --- a/extensions/iterator.js +++ b/extensions/iterator.js @@ -535,7 +535,9 @@ export default function iteratorExtension(pi) { let featureWave = null; // fixed ready-feature snapshot; review stays manual let preAutoModel = null; // the user's model before the first role switch let pendingRole = null; // exact role command captured from the current input - let manualRoleActive = false; // this turn switched a manual role model + // A before_agent_start/agent_end pair can overlap a queued follow-up. Keep + // restoration ownership in FIFO order instead of a shared boolean. + const manualRoleTurns = []; const notifyUi = (msg, level = "info") => { if (lastCtx?.hasUI) lastCtx.ui.notify(`iterator: ${msg}`, level); @@ -556,9 +558,15 @@ export default function iteratorExtension(pi) { streamingBehavior: "followUp", // older runtimes take the raw option name }); - /** Apply a role's model/thinking overrides; remember the user's model once. */ + /** + * Apply a role's model/thinking overrides and report whether the model + * actually changed. Only a successful switch may arm restoration: a failed + * provider lookup must leave the active session model (and its credentials) + * completely untouched. + */ const applyRole = async (role, settings) => { const spec = roleModelSpec(settings, role); + let switchedModel = false; try { if (spec.model) { const slash = spec.model.indexOf("/"); @@ -566,13 +574,17 @@ export default function iteratorExtension(pi) { const id = spec.model.slice(slash + 1); const m = lastCtx?.modelRegistry?.find?.(provider, id); if (m) { - if (!preAutoModel) preAutoModel = lastCtx?.model || null; + const previousModel = preAutoModel || lastCtx?.model || null; const ok = await pi.setModel(m); - if (!ok) + if (ok) { + if (!preAutoModel) preAutoModel = previousModel; + switchedModel = true; + } else { notifyUi( `no API key for ${spec.model} — staying on the active model`, "warning", ); + } } else { notifyUi( `unknown model ${spec.model} for ${role} — staying on the active model`, @@ -587,6 +599,7 @@ export default function iteratorExtension(pi) { "warning", ); } + return switchedModel; }; /** Restore the user's model after an automatic or manual role turn. */ @@ -1484,11 +1497,14 @@ export default function iteratorExtension(pi) { rememberCtx(ctx); const workOwner = session?.ensureWorking?.("AI is working…") ?? null; agentWorkOwners.push(workOwner); + const manualRoleTurn = { switched: false }; + manualRoleTurns.push(manualRoleTurn); try { const { hub, implement, settings, state } = await gatherSession(ctx.cwd); - if (pendingRole && state?.mode !== "auto" && !featureWave) { - await applyRole(pendingRole, settings); - manualRoleActive = true; + const role = pendingRole; + pendingRole = null; // role input belongs to exactly one agent turn + if (role && state?.mode !== "auto" && !featureWave) { + manualRoleTurn.switched = await applyRole(role, settings); } // Model selection also applies to /iterator-plan before a bundle exists; // only the ambient bundle context depends on durable plan state. @@ -1609,13 +1625,11 @@ export default function iteratorExtension(pi) { pi.on("agent_end", async (_event, ctx) => { rememberCtx(ctx); const endedWorkOwner = agentWorkOwners.shift() ?? null; + const manualRoleTurn = manualRoleTurns.shift(); try { invalidateSession(); // the turn may have changed files/commits await flushUsage(ctx.cwd); - if (manualRoleActive) { - manualRoleActive = false; - await restoreModel(); - } + if (manualRoleTurn?.switched) await restoreModel(); await refreshHub(ctx.cwd); await refreshStatus(ctx); // Keep abortPending set until this stale agent_end reaches its final diff --git a/memory/backlog/index.md b/memory/backlog/index.md index 12e44c7..357e9d8 100644 --- a/memory/backlog/index.md +++ b/memory/backlog/index.md @@ -2,10 +2,18 @@ type: Backlog title: Iterator backlog description: Saved ideas and bugs kept separate from active plan features. -items: "[]" -timestamp: 2026-07-18T06:52:21.220Z +items: "[{\"id\":\"it-looks-as-if-the-agent-isnt-using-new-before-a-new-feature-is-implemented-in-either-auto-or-manual-mode\",\"title\":\"It looks as if the agent isnt using /new before a new feature is implemented in either auto or manual mode.\",\"details\":\"The goal is that as little context as possible but all the necvessary context is send to the agent so he can work directly when implmeenting.\",\"kind\":\"bug\",\"selected\":false,\"created\":\"2026-07-20T09:54:13.424Z\",\"updated\":\"2026-07-20T09:54:13.424Z\"},{\"id\":\"when-adding-a-plan-just-create-the-features\",\"title\":\"When adding a plan just create the features\",\"details\":\"When the user adds a full plan the planning phase is skipped and the features are created based on the plan instead of creatying a plan based on the plan.\",\"kind\":\"idea\",\"selected\":false,\"created\":\"2026-07-20T09:55:29.549Z\",\"updated\":\"2026-07-20T09:55:29.549Z\"},{\"id\":\"like-in-plan-allow-in-the-idea-baclkog-inputs-the-same-file-seleciton-logic-with\",\"title\":\"like in plan allow in the idea baclkog inputs the same file seleciton logic with @\",\"details\":\"Like in plan input allow in the idea backlog input the selection of files via @\",\"kind\":\"idea\",\"selected\":false,\"created\":\"2026-07-20T09:59:31.047Z\",\"updated\":\"2026-07-20T09:59:31.047Z\"},{\"id\":\"after-feature-show-work-tab\",\"title\":\"After feature show work tab\",\"details\":\"After a plan was created successful it should show tyhe work tab as there are all the infos.\",\"kind\":\"bug\",\"selected\":false,\"created\":\"2026-07-20T11:31:41.565Z\",\"updated\":\"2026-07-20T11:31:41.565Z\"},{\"id\":\"move-all-active-plan-related-infos-to-work\",\"title\":\"Move all active plan related infos to work\",\"details\":\"THere is still the replan and progress and other active plan information in the plan tab. MOve all of it and merge it into plan. the plan tab just holds infos for future plans and the work tab holds all active information.\",\"kind\":\"bug\",\"selected\":false,\"created\":\"2026-07-20T11:32:58.508Z\",\"updated\":\"2026-07-20T11:32:58.508Z\"},{\"id\":\"openeing-setting-should-be-an-overaly\",\"title\":\"openeing setting should be an overaly\",\"details\":\"Openeing setting in a differnt tba s work doesnt show it. make it a overlay so it always works.\",\"kind\":\"bug\",\"selected\":false,\"created\":\"2026-07-20T11:33:34.075Z\",\"updated\":\"2026-07-20T11:33:34.075Z\"},{\"id\":\"after-adding-tests-its-not-clear-that-they-are-there\",\"title\":\"After adding tests its not clear that they are there\",\"details\":\"Make sure after adding tests before implementing that the feature is in red mode and implement knows this and implements according.\",\"kind\":\"bug\",\"selected\":false,\"created\":\"2026-07-20T12:06:45.237Z\",\"updated\":\"2026-07-20T12:06:45.237Z\"},{\"id\":\"red-mode-tests-should-show-the-real-test-code-for-verification\",\"title\":\"red mode tests should show the real test code for verification\",\"details\":\"as tests are important and the real implementation is most important the user needs to verify the code for each test.\",\"kind\":\"idea\",\"selected\":false,\"created\":\"2026-07-20T13:29:32.558Z\",\"updated\":\"2026-07-20T13:29:32.558Z\"},{\"id\":\"red-mode-tests-should-show-the-real-test-code-for-verification-2\",\"title\":\"red mode tests should show the real test code for verification\",\"details\":\"as tests are important and the real implementation is most important the user needs to verify the code for each test.\",\"kind\":\"idea\",\"selected\":false,\"created\":\"2026-07-20T13:29:35.077Z\",\"updated\":\"2026-07-20T13:29:35.077Z\"}]" +timestamp: 2026-07-20T13:29:35.078Z --- # Backlog -(empty) +* [bug] It looks as if the agent isnt using /new before a new feature is implemented in either auto or manual mode. +* [idea] When adding a plan just create the features +* [idea] like in plan allow in the idea baclkog inputs the same file seleciton logic with @ +* [bug] After feature show work tab +* [bug] Move all active plan related infos to work +* [bug] openeing setting should be an overaly +* [bug] After adding tests its not clear that they are there +* [idea] red mode tests should show the real test code for verification +* [idea] red mode tests should show the real test code for verification diff --git a/memory/features/index.md b/memory/features/index.md new file mode 100644 index 0000000..b3ad94c --- /dev/null +++ b/memory/features/index.md @@ -0,0 +1,3 @@ +# Features + +* [Safe role-model handoff](safe-role-model-handoff.md) - ⬜ pending · medium · Keep the active managed model usable when a tester or implementer role override is unavailable, so red-test completion can hand off to implementation without an OpenAI credential error. diff --git a/memory/features/safe-role-model-handoff.md b/memory/features/safe-role-model-handoff.md new file mode 100644 index 0000000..a306749 --- /dev/null +++ b/memory/features/safe-role-model-handoff.md @@ -0,0 +1,43 @@ +--- +type: Feature +title: Safe role-model handoff +description: Keep the active managed model usable when a tester or implementer role override is unavailable, so red-test completion can hand off to implementation without an OpenAI credential error. +status: implemented +size: medium +depends_on: [] +files: ["extensions/iterator.js", "test/extension-model-lifecycle.test.mjs"] +memories: [architecture/package-and-skill-layout, decisions/backlog-planning-and-feature-waves, decisions/iterator-dashboard-feature-workflow, decisions/manual-role-models-and-runtime-reset, decisions/memory-relevance-usage-and-dashboard-recovery, decisions/parallel-feature-waves-and-consolidated-review, decisions/polish-dashboard-and-multi-agent-workflows, decisions/powerline-shows-sandbox-ui-port] +timestamp: "2026-07-20T13:31:30.159Z" +tags: [] +--- + +# Implementation notes + +Reproduce the exact manual lifecycle through mocked Pi hooks: `/iterator-test` input, `before_agent_start`, failed or unavailable `pi.setModel`, `agent_end`, then `/iterator-implement`. In `extensions/iterator.js`, consume the pending role per turn and distinguish an attempted switch from a successful switch. Do not arm `preAutoModel` or invoke restoration unless `pi.setModel` returned success; keep thinking-level handling independent. Cover `active` settings as a strict no-`setModel` path, a failed explicit tester override followed by implementation, a successful override restoring once, and stale-role isolation. Preserve the same behavior for automatic/wave roles and do not alter environment variables or red-test artifacts. + +# Snippets + +```js +const applyRole = async (role, settings) => { + const spec = roleModelSpec(settings, role); + // Current bug seam: preAutoModel is armed before setModel reports success. + const ok = await pi.setModel(model); +}; +``` + +```js +pi.on("before_agent_start", async (_event, ctx) => { + if (pendingRole && state?.mode !== "auto" && !featureWave) { + await applyRole(pendingRole, settings); + manualRoleActive = true; + } +}); + +pi.on("agent_end", async (_event, ctx) => { + if (manualRoleActive) await restoreModel(); +}); +``` + +# Blast radius + +Manual Iterator role commands plus auto-mode and ready-wave model transitions; incorrect restoration can affect the provider credentials used by every subsequent agent turn. diff --git a/memory/index.md b/memory/index.md index 16d67f2..1b238ba 100644 --- a/memory/index.md +++ b/memory/index.md @@ -10,6 +10,8 @@ last_memorized_commit: c5b75e3e4b034bc80a1600e57249e162639d32a8 * [Design](design.md) - A compact dark developer control plane with clear workflow state. * [Backlog](backlog/index.md) - Saved ideas and bugs outside active plan features. * [Settings](settings.md) - Project settings (auto mode, models, git flow). +* [Features](features/) - One document per implementation feature. +* [Plan](plan.md) - Prevent red-test completion and role-model restoration from leaving the next Iterator turn on an unusable OpenAI credential path. * [Architecture](/architecture/) - How the system is structured. * [Decisions](/decisions/) - Durable product and implementation choices agents should preserve. * [Patterns & Conventions](/patterns/) - How code and workflows are written in this repo. diff --git a/memory/log.md b/memory/log.md index 3b27155..6a4f9b5 100644 --- a/memory/log.md +++ b/memory/log.md @@ -1,5 +1,22 @@ # iterator update log +## 2026-07-20 +* **Implementation**: Committed feature(safe-role-model-handoff) on branch iterator/safe-role-model-handoff; awaiting review. +* **Backlog**: create red-mode-tests-should-show-the-real-test-code-for-verification-2. +* **Backlog**: create red-mode-tests-should-show-the-real-test-code-for-verification. +* **Update**: Applied 1 feature adjustment(s). +* **Creation**: 1 feature(s) written. +* **Creation**: Plan "Fix post-test role model credential corruption" approved on branch main. Consumed 1 selected backlog candidate(s). +* **Backlog**: select after-red-mode-test-creation-iterator-doesnt-work (selected). +* **Backlog**: create after-red-mode-test-creation-iterator-doesnt-work. +* **Backlog**: create after-adding-tests-its-not-clear-that-they-are-there. +* **Backlog**: create openeing-setting-should-be-an-overaly. +* **Backlog**: create move-all-active-plan-related-infos-to-work. +* **Backlog**: create after-feature-show-work-tab. +* **Backlog**: create like-in-plan-allow-in-the-idea-baclkog-inputs-the-same-file-seleciton-logic-with. +* **Backlog**: create when-adding-a-plan-just-create-the-features. +* **Backlog**: create it-looks-as-if-the-agent-isnt-using-new-before-a-new-feature-is-implemented-in-either-auto-or-manual-mode. + ## 2026-07-19 * **Retirement**: Plan "Improve memory relevance, usage costs, and dashboard recovery" condensed into [Memory relevance, usage costs, and dashboard recovery](/decisions/memory-relevance-usage-and-dashboard-recovery.md). diff --git a/memory/plan.md b/memory/plan.md new file mode 100644 index 0000000..889cbfc --- /dev/null +++ b/memory/plan.md @@ -0,0 +1,36 @@ +--- +type: Plan +title: Fix post-test role model credential corruption +description: Prevent red-test completion and role-model restoration from leaving the next Iterator turn on an unusable OpenAI credential path. +status: approved +branch: iterator/fix-post-test-role-model-credential-corruption +worktree: /Volumes/Extern/Projects/iterator-iterator-fix-post-test-role-model-credential-corruption +created: 2026-07-20 +timestamp: 2026-07-20T13:10:00.129Z +--- + +# Goal + +Make the red-test → implementation handoff reliable: after `/iterator-test` writes and commits intentionally failing tests, the following `/iterator-implement` turn must use the intended active or explicitly configured role model without producing a `proxy-managed` OpenAI 401 or leaking model state between turns. + +# Architecture + +- Build on `architecture/package-and-skill-layout`: isolate the extension's manual-role lifecycle (`input` → `before_agent_start` → `agent_end`) and reproduce the tester-to-implementer sequence with direct extension-hook tests, including managed/proxied active models and failed switches. +- Keep role resolution in `lib/pi-tools.mjs` and session orchestration in `extensions/iterator.js`; make role state turn-scoped, consume stale pending roles, and only restore a prior model when an explicit switch actually succeeded. +- Treat `active` as a strict no-model-switch path. Preserve thinking-level overrides independently, and never synthesize, export, or rewrite provider API-key environment variables. +- Verify both manual red-test handoff and automatic/wave role transitions, then run `npm run sync` for any canonical `lib/` changes so shipped skill copies remain identical. + +# Dependencies + +(none) + +# Key decisions + +- Follow `decisions/manual-role-models-and-runtime-reset`: retain configured per-role model selection and restoration, but harden its lifecycle rather than disabling role models. +- A default or explicit `active` role model must not call `pi.setModel`; an unknown, unavailable, or failed explicit override must stay on the usable current model and must not arm a later restoration. +- Add hook-level regression coverage for tester → implementer and restoration failure paths, closing the direct extension-lifecycle test gap recorded by `decisions/manual-role-models-and-runtime-reset`. +- Preserve the red/green feature contract: red tests remain committed and visible to implementation; deferred formatter output is treated as context, not as a trigger for provider selection. + +# Features + +* [Safe role-model handoff](/features/safe-role-model-handoff.md) - Keep the active managed model usable when a tester or implementer role override is unavailable, so red-test completion can hand off to implementation without an OpenAI credential error. diff --git a/memory/state.md b/memory/state.md index d61f6f9..23d7dc1 100644 --- a/memory/state.md +++ b/memory/state.md @@ -4,11 +4,11 @@ title: Runtime state description: Machine-owned iterator flow state — never hand-edited. mode: manual paused: false -phase: done +phase: idle active_feature: null -strikes: "{\"onboard-planless-projects\":1,\"reliable-work-blocker\":1}" +strikes: "{}" escalation: null -timestamp: 2026-07-18T07:58:05.493Z +timestamp: 2026-07-20T13:10:00.133Z --- Runtime flow state; read via gather, written only by the state op. diff --git a/memory/usage.md b/memory/usage.md index 0145f41..6cdb848 100644 --- a/memory/usage.md +++ b/memory/usage.md @@ -1,23 +1,30 @@ --- type: Usage title: Token usage -description: Per-step model/token ledger for the active plan — written only by the usage op. -totals: "{\"steps\":{\"hub\":{\"openai-codex/gpt-5.6-terra\":{\"input\":261034,\"output\":1116,\"cacheRead\":1795072,\"cacheWrite\":0,\"turns\":8}}},\"features\":{\"retire-plan\":{\"input\":261034,\"output\":1116,\"cacheRead\":1795072,\"cacheWrite\":0,\"turns\":8}}}" -timestamp: 2026-07-19T07:33:07.539Z +description: Per-step model/token ledger and optional project-owned pricing for the active plan — written only by the usage op. +totals: "{\"steps\":{\"hub\":{\"openai-codex/gpt-5.6-terra\":{\"input\":261034,\"output\":1116,\"cacheRead\":1795072,\"cacheWrite\":0,\"turns\":8}},\"plan\":{\"openai-codex/gpt-5.6-sol\":{\"input\":63084,\"output\":4999,\"cacheRead\":612864,\"cacheWrite\":0,\"turns\":20}}},\"features\":{\"retire-plan\":{\"input\":261034,\"output\":1116,\"cacheRead\":1795072,\"cacheWrite\":0,\"turns\":8}},\"featureModels\":{}}" +prices: "{}" +timestamp: 2026-07-20T13:18:22.498Z --- # Usage ## hub -| model | input | output | cache read | cache write | turns | -| --- | ---: | ---: | ---: | ---: | ---: | -| openai-codex/gpt-5.6-terra | 261034 | 1116 | 1795072 | 0 | 8 | +| model | input | output | cache read | cache write | turns | cost | +| --- | ---: | ---: | ---: | ---: | ---: | ---: | +| openai-codex/gpt-5.6-terra | 261034 | 1116 | 1795072 | 0 | 8 | — | + +## plan + +| model | input | output | cache read | cache write | turns | cost | +| --- | ---: | ---: | ---: | ---: | ---: | ---: | +| openai-codex/gpt-5.6-sol | 63084 | 4999 | 612864 | 0 | 20 | — | ## Per feature -| feature | input | output | cache read | cache write | turns | -| --- | ---: | ---: | ---: | ---: | ---: | -| retire-plan | 261034 | 1116 | 1795072 | 0 | 8 | +| feature | input | output | cache read | cache write | turns | cost | +| --- | ---: | ---: | ---: | ---: | ---: | ---: | +| retire-plan | 261034 | 1116 | 1795072 | 0 | 8 | — | -Total: 261034 in / 1116 out / 1795072 cache-read / 0 cache-write over 8 turns. +Total: 324118 in / 6115 out / 2407936 cache-read / 0 cache-write over 28 turns. Cost unavailable: add every used model rate. diff --git a/test/extension-model-lifecycle.test.mjs b/test/extension-model-lifecycle.test.mjs new file mode 100644 index 0000000..dade47e --- /dev/null +++ b/test/extension-model-lifecycle.test.mjs @@ -0,0 +1,125 @@ +import { test } from "node:test"; +import assert from "node:assert/strict"; +import { execFileSync } from "node:child_process"; +import { createRequire } from "node:module"; +import { mkdtempSync, mkdirSync, rmSync, writeFileSync } from "node:fs"; +import { tmpdir } from "node:os"; +import { join } from "node:path"; + +const require = createRequire(import.meta.url); +const extensionDependenciesAvailable = (() => { + try { + require.resolve("typebox"); + return true; + } catch { + return false; + } +})(); + +async function iteratorExtension(pi) { + const { default: register } = await import("../extensions/iterator.js"); + return register(pi); +} + +function fixture(settings = {}) { + const root = mkdtempSync(join(tmpdir(), "iterator-role-model-")); + execFileSync("git", ["init", "--quiet"], { cwd: root }); + mkdirSync(join(root, "memory"), { recursive: true }); + writeFileSync( + join(root, "memory", "plan.md"), + "---\ntype: Plan\ntitle: Role model test\nstatus: approved\n---\n\n# Goal\nTest role models.\n", + ); + writeFileSync( + join(root, "memory", "settings.md"), + `---\ntype: Settings\n${Object.entries(settings) + .map(([key, value]) => `${key}: ${value}`) + .join("\n")}\n---\n`, + ); + writeFileSync(join(root, "memory", "state.md"), "---\ntype: State\nmode: manual\n---\n"); + return root; +} + +function mockPi() { + const handlers = new Map(); + const setModels = []; + const currentModel = { provider: "proxy", id: "managed" }; + const overrideModel = { provider: "openai", id: "override" }; + const pi = { + on(name, handler) { + handlers.set(name, handler); + }, + registerCommand() {}, + registerTool() {}, + setThinkingLevel() {}, + async setModel(model) { + setModels.push(model); + return model !== overrideModel || pi.overrideSucceeds; + }, + }; + return { pi, handlers, setModels, currentModel, overrideModel }; +} + +async function startTurn(handlers, root, modelRegistry, model) { + await handlers.get("before_agent_start")({}, { + cwd: root, + hasUI: false, + modelRegistry, + model, + }); +} + +test("failed tester override does not restore or alter the following active implementer", { skip: !extensionDependenciesAvailable }, async () => { + const root = fixture({ tester_model: "openai/override" }); + try { + const { pi, handlers, setModels, currentModel, overrideModel } = mockPi(); + await iteratorExtension(pi); + const registry = { find: () => overrideModel }; + + await handlers.get("input")({ text: "/iterator-test safe-role-model-handoff" }); + await startTurn(handlers, root, registry, currentModel); + await handlers.get("agent_end")({}, { cwd: root, hasUI: false }); + + await handlers.get("input")({ text: "/iterator-implement safe-role-model-handoff" }); + await startTurn(handlers, root, registry, currentModel); + await handlers.get("agent_end")({}, { cwd: root, hasUI: false }); + + assert.deepEqual(setModels, [overrideModel]); + } finally { + rmSync(root, { recursive: true, force: true }); + } +}); + +test("successful manual override restores once and a role input is consumed by one turn", { skip: !extensionDependenciesAvailable }, async () => { + const root = fixture({ tester_model: "openai/override" }); + try { + const { pi, handlers, setModels, currentModel, overrideModel } = mockPi(); + pi.overrideSucceeds = true; + await iteratorExtension(pi); + const registry = { find: () => overrideModel }; + + await handlers.get("input")({ text: "/iterator-test safe-role-model-handoff" }); + await startTurn(handlers, root, registry, currentModel); + await startTurn(handlers, root, registry, currentModel); + await handlers.get("agent_end")({}, { cwd: root, hasUI: false }); + + assert.deepEqual(setModels, [overrideModel, currentModel]); + } finally { + rmSync(root, { recursive: true, force: true }); + } +}); + +test("active role settings never call setModel", { skip: !extensionDependenciesAvailable }, async () => { + const root = fixture({ implementer_model: "active" }); + try { + const { pi, handlers, setModels, currentModel } = mockPi(); + await iteratorExtension(pi); + + await handlers.get("input")({ text: "/iterator-implement safe-role-model-handoff" }); + await startTurn(handlers, root, { find: () => null }, currentModel); + await handlers.get("agent_end")({}, { cwd: root, hasUI: false }); + + assert.deepEqual(setModels, []); + } finally { + rmSync(root, { recursive: true, force: true }); + } +}); From dbdf5f5aee234865d1432ba9b3f59477aa76aa61 Mon Sep 17 00:00:00 2001 From: Christoph Maier Date: Mon, 20 Jul 2026 15:31:30 +0200 Subject: [PATCH 02/42] chore(iterator): record feature commit --- memory/features/safe-role-model-handoff.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/memory/features/safe-role-model-handoff.md b/memory/features/safe-role-model-handoff.md index a306749..71e3fe3 100644 --- a/memory/features/safe-role-model-handoff.md +++ b/memory/features/safe-role-model-handoff.md @@ -7,8 +7,12 @@ size: medium depends_on: [] files: ["extensions/iterator.js", "test/extension-model-lifecycle.test.mjs"] memories: [architecture/package-and-skill-layout, decisions/backlog-planning-and-feature-waves, decisions/iterator-dashboard-feature-workflow, decisions/manual-role-models-and-runtime-reset, decisions/memory-relevance-usage-and-dashboard-recovery, decisions/parallel-feature-waves-and-consolidated-review, decisions/polish-dashboard-and-multi-agent-workflows, decisions/powerline-shows-sandbox-ui-port] -timestamp: "2026-07-20T13:31:30.159Z" +timestamp: "2026-07-20T13:31:30.243Z" tags: [] +commits: + - sha: 9116540c8f0bdac6dd7bab5041c9f3245981daf8 + kind: implement + date: 2026-07-20 --- # Implementation notes From 4befb05133395b472ed7697c92ea22da546b76f4 Mon Sep 17 00:00:00 2001 From: Christoph Maier Date: Mon, 20 Jul 2026 15:33:09 +0200 Subject: [PATCH 03/42] chore(iterator): record feature commits and memory updates --- .../manual-role-models-and-runtime-reset.md | 12 ++++-------- memory/features/index.md | 2 +- memory/features/safe-role-model-handoff.md | 5 +++-- memory/log.md | 2 ++ 4 files changed, 10 insertions(+), 11 deletions(-) diff --git a/memory/decisions/manual-role-models-and-runtime-reset.md b/memory/decisions/manual-role-models-and-runtime-reset.md index c5e0fa9..5e80ffb 100644 --- a/memory/decisions/manual-role-models-and-runtime-reset.md +++ b/memory/decisions/manual-role-models-and-runtime-reset.md @@ -5,8 +5,8 @@ description: Manual Iterator role commands temporarily select configured models, status: accepted date: 2026-07-17 tags: [models, workflow, runtime-state, pi] -files: ["lib/write.mjs", "extensions/iterator.js", "lib/pi-tools.mjs", "test/write.test.mjs", "test/pi-tools.test.mjs"] -timestamp: 2026-07-17T17:51:38.757Z +files: ["lib/write.mjs", "extensions/iterator.js", "lib/pi-tools.mjs", "test/write.test.mjs", "test/pi-tools.test.mjs", "test/extension-model-lifecycle.test.mjs"] +timestamp: "2026-07-20T13:33:09.768Z" --- ## Decision @@ -17,10 +17,6 @@ Approved plan creation resets runtime state to manual and idle, clearing active ## Consequences -Usage rows now reflect the model that executed a manual role command, and stale auto bookkeeping cannot dispatch work into a newly approved plan. Parser coverage verifies exact command handling and plan-review precedence; writer coverage verifies approved resets and draft preservation. The whole-plan review noted that extension lifecycle paths still lack direct hook-level tests. +A role switch only arms restoration after `pi.setModel` succeeds. Failed or unavailable configured models leave the current model and its credentials untouched; `active` never calls `pi.setModel`. Manual role ownership is FIFO across overlapping starts and ends, so each role input is consumed by one agent turn and only that successful turn can restore the prior model. -# Retired plan - -Condensed from plan "Apply role models on manual turns and reset stale auto state" (2 features, archived under /features/archive/2026-07-17-manual-role-models-and-runtime-reset/). - -Token usage: 2207627 in / 20868 out / 18306048 cache-read / 0 cache-write over 103 turns (per-step breakdown in the archived usage.md). +Extension lifecycle tests cover failed tester-to-implementer handoffs, one-time restoration after a successful override, and `active` no-switch behavior. Parser coverage verifies exact command handling and plan-review precedence; writer coverage verifies approved resets and draft preservation. diff --git a/memory/features/index.md b/memory/features/index.md index b3ad94c..bc527db 100644 --- a/memory/features/index.md +++ b/memory/features/index.md @@ -1,3 +1,3 @@ # Features -* [Safe role-model handoff](safe-role-model-handoff.md) - ⬜ pending · medium · Keep the active managed model usable when a tester or implementer role override is unavailable, so red-test completion can hand off to implementation without an OpenAI credential error. +* [Safe role-model handoff](safe-role-model-handoff.md) - ✅ done · medium · Keep the active managed model usable when a tester or implementer role override is unavailable, so red-test completion can hand off to implementation without an OpenAI credential error. diff --git a/memory/features/safe-role-model-handoff.md b/memory/features/safe-role-model-handoff.md index 71e3fe3..9aca296 100644 --- a/memory/features/safe-role-model-handoff.md +++ b/memory/features/safe-role-model-handoff.md @@ -2,17 +2,18 @@ type: Feature title: Safe role-model handoff description: Keep the active managed model usable when a tester or implementer role override is unavailable, so red-test completion can hand off to implementation without an OpenAI credential error. -status: implemented +status: done size: medium depends_on: [] files: ["extensions/iterator.js", "test/extension-model-lifecycle.test.mjs"] memories: [architecture/package-and-skill-layout, decisions/backlog-planning-and-feature-waves, decisions/iterator-dashboard-feature-workflow, decisions/manual-role-models-and-runtime-reset, decisions/memory-relevance-usage-and-dashboard-recovery, decisions/parallel-feature-waves-and-consolidated-review, decisions/polish-dashboard-and-multi-agent-workflows, decisions/powerline-shows-sandbox-ui-port] -timestamp: "2026-07-20T13:31:30.243Z" +timestamp: "2026-07-20T13:33:09.759Z" tags: [] commits: - sha: 9116540c8f0bdac6dd7bab5041c9f3245981daf8 kind: implement date: 2026-07-20 +done: 2026-07-20 --- # Implementation notes diff --git a/memory/log.md b/memory/log.md index 6a4f9b5..283105c 100644 --- a/memory/log.md +++ b/memory/log.md @@ -1,6 +1,8 @@ # iterator update log ## 2026-07-20 +* **Update**: Memorized [Apply role models to manual turns and reset stale runtime state](/decisions/manual-role-models-and-runtime-reset.md). +* **Review**: Accepted [Safe role-model handoff](/features/safe-role-model-handoff.md) (committed as feature(safe-role-model-handoff)). * **Implementation**: Committed feature(safe-role-model-handoff) on branch iterator/safe-role-model-handoff; awaiting review. * **Backlog**: create red-mode-tests-should-show-the-real-test-code-for-verification-2. * **Backlog**: create red-mode-tests-should-show-the-real-test-code-for-verification. From e7a69a4ee3dd962817c766d39109012284a0e530 Mon Sep 17 00:00:00 2001 From: Christoph Maier Date: Mon, 20 Jul 2026 15:59:02 +0200 Subject: [PATCH 04/42] feature(fresh-implementation-session): Start feature implementation in fresh sessions Feature: fresh-implementation-session --- extensions/iterator.js | 118 ++++++++++++------ lib/pi-tools.mjs | 20 ++- memory/backlog/index.md | 11 +- memory/decisions/index.md | 1 + .../decisions/safe-role-model-restoration.md | 28 +++++ memory/features/active-plan-workspace.md | 43 +++++++ .../index.md | 3 + .../plan.md | 36 ++++++ .../safe-role-model-handoff.md | 0 .../usage.md | 43 +++++++ .../features/fresh-implementation-session.md | 37 ++++++ memory/features/index.md | 5 +- memory/features/settings-dashboard-modal.md | 44 +++++++ memory/features/visible-red-test-handoff.md | 39 ++++++ memory/index.md | 2 +- memory/log.md | 12 ++ memory/plan.md | 36 +++--- memory/state.md | 8 +- memory/usage.md | 12 +- skills/iterator-implement/SKILL.md | 8 +- test/extension-fresh-session.test.mjs | 73 +++++++++++ test/pi-tools.test.mjs | 11 +- 22 files changed, 514 insertions(+), 76 deletions(-) create mode 100644 memory/decisions/safe-role-model-restoration.md create mode 100644 memory/features/active-plan-workspace.md create mode 100644 memory/features/archive/2026-07-20-safe-role-model-restoration/index.md create mode 100644 memory/features/archive/2026-07-20-safe-role-model-restoration/plan.md rename memory/features/{ => archive/2026-07-20-safe-role-model-restoration}/safe-role-model-handoff.md (100%) create mode 100644 memory/features/archive/2026-07-20-safe-role-model-restoration/usage.md create mode 100644 memory/features/fresh-implementation-session.md create mode 100644 memory/features/settings-dashboard-modal.md create mode 100644 memory/features/visible-red-test-handoff.md create mode 100644 test/extension-fresh-session.test.mjs diff --git a/extensions/iterator.js b/extensions/iterator.js index a5acd7a..38c583c 100644 --- a/extensions/iterator.js +++ b/extensions/iterator.js @@ -60,6 +60,7 @@ import { completeFeatureWaveAbort, extractPathsFromBash, footerText, + implementationCommand, mergePayload, nextAutoAction, nextFeatureWaveAction, @@ -663,7 +664,9 @@ export default function iteratorExtension(pi) { phase: "implementing", active_feature: action.feature, }); - await applyRole(action.role, settings); + // The replacement session applies the implementer role when its final + // skill command starts; switching here would leak a role model into a + // session that is about to be torn down. attribution = { step: action.step, feature: action.feature }; session?.showWorking({ text: `Wave: implementing ${action.feature} (${featureWave.results.length}/${featureWave.results.length + featureWave.queue.length + 1} finished)…`, @@ -671,7 +674,7 @@ export default function iteratorExtension(pi) { feature: action.feature, }); await pushStatus(cwd); - await dispatch(action.cmd); + await dispatch(implementationCommand(action.feature, { auto: true })); } catch (error) { featureWave = null; await writeState({ @@ -781,29 +784,11 @@ export default function iteratorExtension(pi) { }); invalidateSession(); } - // Fresh context per feature: when the next implement round targets a - // DIFFERENT feature than the previous one, clear the conversation - // context if the runtime supports it. Correctness never depends on - // this — the implement gather payload (contract, relevantMemories, - // finishedFeatures) is self-contained by design. - if ( - action.step === "implement" && - action.feature && - sess.state?.active_feature && - sess.state.active_feature !== action.feature - ) { - try { - if (typeof pi.clearContext === "function") await pi.clearContext(); - else if (typeof pi.compact === "function") await pi.compact(); - } catch { - /* best-effort only */ - } - } await writeState({ phase: AUTO_PHASE_FOR_STEP[action.step] || "implementing", active_feature: action.feature || null, }); - await applyRole(action.role, sess.settings); + if (action.step !== "implement") await applyRole(action.role, sess.settings); attribution = { step: action.step, feature: action.feature || null }; const p = sess.hub?.progress || {}; // Structured working state: the shell renders step/feature and a @@ -816,7 +801,11 @@ export default function iteratorExtension(pi) { }); await pushStatus(cwd); try { - await dispatch(action.cmd); + await dispatch( + action.step === "implement" + ? implementationCommand(action.feature, { auto: true }) + : action.cmd, + ); } catch (err) { // Recoverable, never a silent stall: keep mode:auto but pause, so // the control strip's Continue re-enters kickAuto and re-dispatches. @@ -945,7 +934,10 @@ export default function iteratorExtension(pi) { session?.showWorking?.("Resuming with your guidance…"); dispatch( feature - ? `/skill:iterator-implement ${feature} --auto — user guidance: ${guidance}` + ? implementationCommand(feature, { + auto: true, + guidance: `user guidance: ${guidance}`, + }) : guidance, ); } catch (e) { @@ -1064,24 +1056,58 @@ export default function iteratorExtension(pi) { }; // --------------------------------------------------------------------- - // Friendly commands (skills stay the source of the flow logic). - // /iterator-implement with no argument turns into a TUI feature picker. + // Friendly commands (skills stay the source of the flow logic). An + // implementation is the one exception: it replaces the Pi session before + // sending the skill command, so the fresh agent sees the feature contract + // rather than the accumulated conversation. + + const startImplementationSession = async (args, ctx) => { + const rawArgs = String(args || "").trim(); + const [commandArgs, guidance] = rawArgs.split(/\s+—\s+/, 2); + const tokens = commandArgs.split(/\s+/).filter(Boolean); + let feature = tokens.find((token) => !token.startsWith("--")) || null; + const auto = tokens.includes("--auto"); + if (!feature) { + const picked = await pickReadyFeature(ctx); + if (picked === undefined) return; + feature = picked; + } + const command = `/skill:iterator-implement ${feature}${auto ? " --auto" : ""}${guidance ? ` — ${guidance}` : ""}`; + const handoff = { + feature, + auto, + // A ready-wave lives in extension memory; preserve only its plain, + // immutable snapshot so the replacement runtime can finish the wave. + featureWave: featureWave + ? { + ...featureWave, + queue: [...featureWave.queue], + results: [...featureWave.results], + } + : null, + }; + const result = await ctx.newSession({ + parentSession: ctx.sessionManager?.getSessionFile?.(), + setup: async (manager) => { + manager.appendCustomEntry("iterator-implementation-handoff", handoff); + }, + withSession: async (replacementCtx) => { + await replacementCtx.sendUserMessage(command); + }, + }); + if (result?.cancelled && ctx.hasUI) + ctx.ui.notify("iterator: fresh implementation session cancelled", "info"); + }; for (const command of COMMANDS) { pi.registerCommand(command.name, { description: command.description, handler: async (args = "", ctx) => { - const trimmedArgs = String(args).trim(); - if ( - command.name === "iterator-implement" && - !trimmedArgs && - ctx?.hasUI - ) { - const picked = await pickReadyFeature(ctx); - if (picked === undefined) return; // dismissed / nothing ready - dispatch(`/skill:iterator-implement ${picked}`.trim()); + if (command.name === "iterator-implement") { + await startImplementationSession(args, ctx); return; } + const trimmedArgs = String(args).trim(); dispatch( `/skill:${command.name}${trimmedArgs ? ` ${trimmedArgs}` : ""}`, ); @@ -1105,7 +1131,7 @@ export default function iteratorExtension(pi) { pi.registerCommand("iterator-next", { description: - "Implement the next dependency-ready feature, no questions asked.", + "Implement the next dependency-ready feature in a fresh session.", handler: async (_args, ctx) => { try { const imp = await gatherPayload(ctx.cwd, "implement"); @@ -1119,7 +1145,7 @@ export default function iteratorExtension(pi) { ctx.ui.notify(`iterator: nothing to implement — ${why}`, "warning"); return; } - dispatch(`/skill:iterator-implement ${imp.next.name}`); + await startImplementationSession(imp.next.name, ctx); } catch (e) { if (ctx.hasUI) ctx.ui.notify(`iterator: ${e.message}`, "error"); } @@ -1587,8 +1613,25 @@ export default function iteratorExtension(pi) { // plan-less project lands on Planning's goal/init hero rather than an empty // Work tab. - pi.on("session_start", async (_event, ctx) => { + pi.on("session_start", async (event, ctx) => { rememberCtx(ctx); + const sessionEntries = ctx.sessionManager?.getEntries?.() || []; + let handoff = null; + // A persisted marker is only a handoff during the session replacement + // that created it. On reload/restart, retain the normal auto safety pause. + if (event?.reason === "new") { + for (let index = sessionEntries.length - 1; index >= 0; index -= 1) { + const entry = sessionEntries[index]; + if ( + entry.type === "custom" && + entry.customType === "iterator-implementation-handoff" + ) { + handoff = entry.data; + break; + } + } + } + if (handoff?.featureWave) featureWave = handoff.featureWave; await refreshStatus(ctx); try { await ensureServer(ctx); @@ -1596,6 +1639,7 @@ export default function iteratorExtension(pi) { // pause it and let the human press Continue in the dashboard. const { state } = await gatherSession(ctx.cwd); if ( + !handoff && state?.mode === "auto" && !state.paused && ["testing", "implementing", "reviewing"].includes(state.phase) diff --git a/lib/pi-tools.mjs b/lib/pi-tools.mjs index 7aefe12..0a12f56 100644 --- a/lib/pi-tools.mjs +++ b/lib/pi-tools.mjs @@ -23,12 +23,26 @@ export function mergePayload(gathered, extra) { * Map a hub- or knowledge-view result to the command that runs the chosen * flow, or null for cancel/timeout/close/anything non-actionable. */ +export function implementationCommand(feature, { auto = false, guidance = null } = {}) { + const parts = ["/iterator-implement"]; + if (feature) parts.push(feature); + if (auto) parts.push("--auto"); + if (guidance) parts.push(`— ${guidance}`); + return parts.join(" "); +} + export function actionToCommand(result) { if (!result || result.type !== "action") return null; - // Hub (Work tab): step flows, optionally scoped to a feature. A typed plan - // goal from the hero rides along so /iterator-plan can skip its questions. - const STEPS = ["plan", "feature", "test", "implement", "review", "design"]; + // Implementation uses the extension command rather than a direct skill + // expansion: only a command context can create the focused replacement + // session before the skill gets its deterministic feature contract. + if (result.action === "implement") + return implementationCommand(result.feature, { guidance: result.prompt }); + + // Hub (Work tab): other step flows, optionally scoped to a feature. A typed + // plan goal from the hero rides along so /iterator-plan can skip questions. + const STEPS = ["plan", "feature", "test", "review", "design"]; if (STEPS.includes(result.action)) { const parts = [`/skill:iterator-${result.action}`]; if (result.feature) parts.push(result.feature); diff --git a/memory/backlog/index.md b/memory/backlog/index.md index 357e9d8..9fe106c 100644 --- a/memory/backlog/index.md +++ b/memory/backlog/index.md @@ -2,18 +2,15 @@ type: Backlog title: Iterator backlog description: Saved ideas and bugs kept separate from active plan features. -items: "[{\"id\":\"it-looks-as-if-the-agent-isnt-using-new-before-a-new-feature-is-implemented-in-either-auto-or-manual-mode\",\"title\":\"It looks as if the agent isnt using /new before a new feature is implemented in either auto or manual mode.\",\"details\":\"The goal is that as little context as possible but all the necvessary context is send to the agent so he can work directly when implmeenting.\",\"kind\":\"bug\",\"selected\":false,\"created\":\"2026-07-20T09:54:13.424Z\",\"updated\":\"2026-07-20T09:54:13.424Z\"},{\"id\":\"when-adding-a-plan-just-create-the-features\",\"title\":\"When adding a plan just create the features\",\"details\":\"When the user adds a full plan the planning phase is skipped and the features are created based on the plan instead of creatying a plan based on the plan.\",\"kind\":\"idea\",\"selected\":false,\"created\":\"2026-07-20T09:55:29.549Z\",\"updated\":\"2026-07-20T09:55:29.549Z\"},{\"id\":\"like-in-plan-allow-in-the-idea-baclkog-inputs-the-same-file-seleciton-logic-with\",\"title\":\"like in plan allow in the idea baclkog inputs the same file seleciton logic with @\",\"details\":\"Like in plan input allow in the idea backlog input the selection of files via @\",\"kind\":\"idea\",\"selected\":false,\"created\":\"2026-07-20T09:59:31.047Z\",\"updated\":\"2026-07-20T09:59:31.047Z\"},{\"id\":\"after-feature-show-work-tab\",\"title\":\"After feature show work tab\",\"details\":\"After a plan was created successful it should show tyhe work tab as there are all the infos.\",\"kind\":\"bug\",\"selected\":false,\"created\":\"2026-07-20T11:31:41.565Z\",\"updated\":\"2026-07-20T11:31:41.565Z\"},{\"id\":\"move-all-active-plan-related-infos-to-work\",\"title\":\"Move all active plan related infos to work\",\"details\":\"THere is still the replan and progress and other active plan information in the plan tab. MOve all of it and merge it into plan. the plan tab just holds infos for future plans and the work tab holds all active information.\",\"kind\":\"bug\",\"selected\":false,\"created\":\"2026-07-20T11:32:58.508Z\",\"updated\":\"2026-07-20T11:32:58.508Z\"},{\"id\":\"openeing-setting-should-be-an-overaly\",\"title\":\"openeing setting should be an overaly\",\"details\":\"Openeing setting in a differnt tba s work doesnt show it. make it a overlay so it always works.\",\"kind\":\"bug\",\"selected\":false,\"created\":\"2026-07-20T11:33:34.075Z\",\"updated\":\"2026-07-20T11:33:34.075Z\"},{\"id\":\"after-adding-tests-its-not-clear-that-they-are-there\",\"title\":\"After adding tests its not clear that they are there\",\"details\":\"Make sure after adding tests before implementing that the feature is in red mode and implement knows this and implements according.\",\"kind\":\"bug\",\"selected\":false,\"created\":\"2026-07-20T12:06:45.237Z\",\"updated\":\"2026-07-20T12:06:45.237Z\"},{\"id\":\"red-mode-tests-should-show-the-real-test-code-for-verification\",\"title\":\"red mode tests should show the real test code for verification\",\"details\":\"as tests are important and the real implementation is most important the user needs to verify the code for each test.\",\"kind\":\"idea\",\"selected\":false,\"created\":\"2026-07-20T13:29:32.558Z\",\"updated\":\"2026-07-20T13:29:32.558Z\"},{\"id\":\"red-mode-tests-should-show-the-real-test-code-for-verification-2\",\"title\":\"red mode tests should show the real test code for verification\",\"details\":\"as tests are important and the real implementation is most important the user needs to verify the code for each test.\",\"kind\":\"idea\",\"selected\":false,\"created\":\"2026-07-20T13:29:35.077Z\",\"updated\":\"2026-07-20T13:29:35.077Z\"}]" -timestamp: 2026-07-20T13:29:35.078Z +items: "[{\"id\":\"when-adding-a-plan-just-create-the-features\",\"title\":\"When adding a plan just create the features\",\"details\":\"When the user adds a full plan the planning phase is skipped and the features are created based on the plan instead of creatying a plan based on the plan.\",\"kind\":\"idea\",\"selected\":false,\"created\":\"2026-07-20T09:55:29.549Z\",\"updated\":\"2026-07-20T09:55:29.549Z\"},{\"id\":\"like-in-plan-allow-in-the-idea-baclkog-inputs-the-same-file-seleciton-logic-with\",\"title\":\"like in plan allow in the idea baclkog inputs the same file seleciton logic with @\",\"details\":\"Like in plan input allow in the idea backlog input the selection of files via @\",\"kind\":\"idea\",\"selected\":false,\"created\":\"2026-07-20T09:59:31.047Z\",\"updated\":\"2026-07-20T09:59:31.047Z\"},{\"id\":\"red-mode-tests-should-show-the-real-test-code-for-verification\",\"title\":\"red mode tests should show the real test code for verification\",\"details\":\"as tests are important and the real implementation is most important the user needs to verify the code for each test.\",\"kind\":\"idea\",\"selected\":false,\"created\":\"2026-07-20T13:29:32.558Z\",\"updated\":\"2026-07-20T13:29:32.558Z\"},{\"id\":\"red-mode-tests-should-show-the-real-test-code-for-verification-2\",\"title\":\"red mode tests should show the real test code for verification\",\"details\":\"as tests are important and the real implementation is most important the user needs to verify the code for each test.\",\"kind\":\"idea\",\"selected\":false,\"created\":\"2026-07-20T13:29:35.077Z\",\"updated\":\"2026-07-20T13:29:35.077Z\"},{\"id\":\"memroy-update-needs-foxus\",\"title\":\"memroy update needs foxus\",\"details\":\"When a memory update is proposed highlight the changed text sections o its easier to see what changed.\",\"kind\":\"idea\",\"selected\":false,\"created\":\"2026-07-20T13:34:04.421Z\",\"updated\":\"2026-07-20T13:34:04.421Z\"},{\"id\":\"save-candidate-should-alsways-work-as-its-a-save-to-file\",\"title\":\"save candidate should alsways work as its a save to file\",\"details\":\"As save candidate doesnt require a agent pass it should always work.\",\"kind\":\"idea\",\"selected\":false,\"created\":\"2026-07-20T13:35:46.415Z\",\"updated\":\"2026-07-20T13:35:46.415Z\"}]" +timestamp: 2026-07-20T13:48:00.106Z --- # Backlog -* [bug] It looks as if the agent isnt using /new before a new feature is implemented in either auto or manual mode. * [idea] When adding a plan just create the features * [idea] like in plan allow in the idea baclkog inputs the same file seleciton logic with @ -* [bug] After feature show work tab -* [bug] Move all active plan related infos to work -* [bug] openeing setting should be an overaly -* [bug] After adding tests its not clear that they are there * [idea] red mode tests should show the real test code for verification * [idea] red mode tests should show the real test code for verification +* [idea] memroy update needs foxus +* [idea] save candidate should alsways work as its a save to file diff --git a/memory/decisions/index.md b/memory/decisions/index.md index 62bbb77..7da6b2f 100644 --- a/memory/decisions/index.md +++ b/memory/decisions/index.md @@ -11,6 +11,7 @@ Durable product and implementation choices agents should preserve. * [Powerline shows the sandbox-published UI port](/decisions/powerline-shows-sandbox-ui-port.md) - The footer trails a ui:PORT segment resolved from ITERATOR_DISPLAY_PORT, falling back to ~/.pisbx-env because sbx run never sources it into pi's environment. * [Return to Work when Settings closes](/decisions/settings-close-returns-to-work.md) - Idle Settings close events restore the refreshed Work hub without changing the settings persistence path. * [Review navigation and Work context](/decisions/review-navigation-and-work-context.md) - Keep interactive reviews stable across dashboard navigation, make Work the active feature surface, and simplify review controls without weakening the review gate. +* [Safely restore configured Iterator role models](/decisions/safe-role-model-restoration.md) - Only successfully switched role models are restored, so failed provider changes cannot corrupt the active session credentials. * [Sync shared libs into droppable skills](/decisions/synced-droppable-skill-libs.md) - Shared code is developed in root lib/ and copied into skill folders so skills work when installed together or copied manually. * [Unify Iterator dashboard and feature workflow](/decisions/iterator-dashboard-feature-workflow.md) - Dashboard workflows keep backlog candidates separate from active work until selected candidates are consumed by approved plan creation. * [Use an OKF markdown bundle in target repos](/decisions/okf-markdown-bundle.md) - Project memory is stored as markdown plus YAML frontmatter in each target repo instead of in an external database. diff --git a/memory/decisions/safe-role-model-restoration.md b/memory/decisions/safe-role-model-restoration.md new file mode 100644 index 0000000..a4c0816 --- /dev/null +++ b/memory/decisions/safe-role-model-restoration.md @@ -0,0 +1,28 @@ +--- +type: Decision +title: Safely restore configured Iterator role models +description: Only successfully switched role models are restored, so failed provider changes cannot corrupt the active session credentials. +status: accepted +date: 2026-07-20 +tags: [models, credentials, workflow, regression] +files: ["extensions/iterator.js", "test/extension-model-lifecycle.test.mjs"] +timestamp: 2026-07-20T13:37:19.997Z +--- + +## Outcome + +Iterator now treats manual role-model selection as a turn-scoped operation. A configured tester or implementer override that cannot be used leaves the active session model untouched, preventing a red-test-to-implementation handoff from falling onto an invalid provider credential path. + +## Decision + +Arm restoration only after `pi.setModel` succeeds. The `active` setting never invokes model switching, and each input role is consumed by exactly one agent turn. Track successful manual role changes in FIFO lifecycle order so an overlapping follow-up cannot restore another turn's model. + +## Verification + +The extension lifecycle suite covers failed override handoff, one-time restoration after a successful override, and active-model no-switch behavior. The full test suite remains green; the lifecycle cases skip only in environments without the optional `typebox` peer dependency. + +# Retired plan + +Condensed from plan "Fix post-test role model credential corruption" (1 features, archived under /features/archive/2026-07-20-safe-role-model-restoration/). + +Token usage: 603657 in / 16114 out / 5499904 cache-read / 0 cache-write over 67 turns (per-step breakdown in the archived usage.md). diff --git a/memory/features/active-plan-workspace.md b/memory/features/active-plan-workspace.md new file mode 100644 index 0000000..dd2f20a --- /dev/null +++ b/memory/features/active-plan-workspace.md @@ -0,0 +1,43 @@ +--- +type: Feature +title: Active plan workspace +description: Make Work the complete home for an active plan and land there after plan or feature approval, while Planning stays focused on future work and archives. +status: pending +size: medium +depends_on: [fresh-implementation-session] +files: ["lib/views/hub.mjs", "lib/views/planning.mjs", "extensions/iterator.js", "test/ui.test.mjs", "test/client-js-parse.test.mjs", "test/session-server.test.mjs"] +memories: [pitfalls/cancel-now-after-grace-timer, pitfalls/client-js-template-literal-escaping, architecture/package-and-skill-layout, architecture/workflow-state-ownership, patterns/safe-browser-rendering, decisions/backlog-planning-and-feature-waves, decisions/consume-accepted-backlog-ideas, decisions/iterator-dashboard-feature-workflow] +conflicts: "[{\"decision\":\"decisions/review-navigation-and-work-context\",\"note\":\"The accepted plan intentionally moves plan lifecycle controls from Planning to Work, revising the recorded split where Planning owned those actions.\"}]" +timestamp: "2026-07-20T13:50:52.261Z" +tags: [] +--- + +# Implementation notes + +Move active-plan lifecycle controls—revise, feature/re-feature, whole-plan review, retirement, plan cancellation—and their stage-driven affordances from `planning.mjs` into the Work plan bar beside progress and execution. Planning should continue to show plan creation when none exists, backlog CRUD/selection, and retired plans; with an active plan it should explain that management is on Work rather than duplicate state. Add intentional Work activation after approved plan creation and accepted feature breakdown without changing ordinary refreshes that preserve the user's current tab. Continue rendering server-derived `stage`, readiness, progress, and dirty state; do not rederive lifecycle rules in either view. Follow `memory/design.md`, keep mobile controls usable, and retain inline-client parse tests. + +# Snippets + +```js +// Current Planning surface owns active lifecycle buttons. +revise.addEventListener('click', () => action('plan', null)); +refeature.addEventListener('click', () => action('feature', null)); +``` + +```js +const landOnPlanning = preferPlanning && !hub.plan; +session.showView({ step:'planning', activate: landOnPlanning, ... }); +session.showView({ step:'hub', render: () => VIEWS.hub(hub) }); +``` + +# Depends on + +* [Fresh implementation session](/features/fresh-implementation-session.md) + +# Blast radius + +Planning/Work navigation, all active plan lifecycle actions, and post-approval landing behavior. + +# Decision conflicts + +* [decisions/review-navigation-and-work-context](/decisions/review-navigation-and-work-context.md) — The accepted plan intentionally moves plan lifecycle controls from Planning to Work, revising the recorded split where Planning owned those actions. diff --git a/memory/features/archive/2026-07-20-safe-role-model-restoration/index.md b/memory/features/archive/2026-07-20-safe-role-model-restoration/index.md new file mode 100644 index 0000000..bc527db --- /dev/null +++ b/memory/features/archive/2026-07-20-safe-role-model-restoration/index.md @@ -0,0 +1,3 @@ +# Features + +* [Safe role-model handoff](safe-role-model-handoff.md) - ✅ done · medium · Keep the active managed model usable when a tester or implementer role override is unavailable, so red-test completion can hand off to implementation without an OpenAI credential error. diff --git a/memory/features/archive/2026-07-20-safe-role-model-restoration/plan.md b/memory/features/archive/2026-07-20-safe-role-model-restoration/plan.md new file mode 100644 index 0000000..889cbfc --- /dev/null +++ b/memory/features/archive/2026-07-20-safe-role-model-restoration/plan.md @@ -0,0 +1,36 @@ +--- +type: Plan +title: Fix post-test role model credential corruption +description: Prevent red-test completion and role-model restoration from leaving the next Iterator turn on an unusable OpenAI credential path. +status: approved +branch: iterator/fix-post-test-role-model-credential-corruption +worktree: /Volumes/Extern/Projects/iterator-iterator-fix-post-test-role-model-credential-corruption +created: 2026-07-20 +timestamp: 2026-07-20T13:10:00.129Z +--- + +# Goal + +Make the red-test → implementation handoff reliable: after `/iterator-test` writes and commits intentionally failing tests, the following `/iterator-implement` turn must use the intended active or explicitly configured role model without producing a `proxy-managed` OpenAI 401 or leaking model state between turns. + +# Architecture + +- Build on `architecture/package-and-skill-layout`: isolate the extension's manual-role lifecycle (`input` → `before_agent_start` → `agent_end`) and reproduce the tester-to-implementer sequence with direct extension-hook tests, including managed/proxied active models and failed switches. +- Keep role resolution in `lib/pi-tools.mjs` and session orchestration in `extensions/iterator.js`; make role state turn-scoped, consume stale pending roles, and only restore a prior model when an explicit switch actually succeeded. +- Treat `active` as a strict no-model-switch path. Preserve thinking-level overrides independently, and never synthesize, export, or rewrite provider API-key environment variables. +- Verify both manual red-test handoff and automatic/wave role transitions, then run `npm run sync` for any canonical `lib/` changes so shipped skill copies remain identical. + +# Dependencies + +(none) + +# Key decisions + +- Follow `decisions/manual-role-models-and-runtime-reset`: retain configured per-role model selection and restoration, but harden its lifecycle rather than disabling role models. +- A default or explicit `active` role model must not call `pi.setModel`; an unknown, unavailable, or failed explicit override must stay on the usable current model and must not arm a later restoration. +- Add hook-level regression coverage for tester → implementer and restoration failure paths, closing the direct extension-lifecycle test gap recorded by `decisions/manual-role-models-and-runtime-reset`. +- Preserve the red/green feature contract: red tests remain committed and visible to implementation; deferred formatter output is treated as context, not as a trigger for provider selection. + +# Features + +* [Safe role-model handoff](/features/safe-role-model-handoff.md) - Keep the active managed model usable when a tester or implementer role override is unavailable, so red-test completion can hand off to implementation without an OpenAI credential error. diff --git a/memory/features/safe-role-model-handoff.md b/memory/features/archive/2026-07-20-safe-role-model-restoration/safe-role-model-handoff.md similarity index 100% rename from memory/features/safe-role-model-handoff.md rename to memory/features/archive/2026-07-20-safe-role-model-restoration/safe-role-model-handoff.md diff --git a/memory/features/archive/2026-07-20-safe-role-model-restoration/usage.md b/memory/features/archive/2026-07-20-safe-role-model-restoration/usage.md new file mode 100644 index 0000000..78582d3 --- /dev/null +++ b/memory/features/archive/2026-07-20-safe-role-model-restoration/usage.md @@ -0,0 +1,43 @@ +--- +type: Usage +title: Token usage +description: Per-step model/token ledger and optional project-owned pricing for the active plan — written only by the usage op. +totals: "{\"steps\":{\"hub\":{\"openai-codex/gpt-5.6-terra\":{\"input\":261034,\"output\":1116,\"cacheRead\":1795072,\"cacheWrite\":0,\"turns\":8}},\"plan\":{\"openai-codex/gpt-5.6-sol\":{\"input\":63084,\"output\":4999,\"cacheRead\":612864,\"cacheWrite\":0,\"turns\":20}},\"implement\":{\"openai-codex/gpt-5.6-terra\":{\"input\":117926,\"output\":9423,\"cacheRead\":2369024,\"cacheWrite\":0,\"turns\":31}},\"review\":{\"openai-codex/gpt-5.6-sol\":{\"input\":161613,\"output\":576,\"cacheRead\":722944,\"cacheWrite\":0,\"turns\":8}}},\"features\":{\"retire-plan\":{\"input\":261034,\"output\":1116,\"cacheRead\":1795072,\"cacheWrite\":0,\"turns\":8},\"safe-role-model-handoff\":{\"input\":279539,\"output\":9999,\"cacheRead\":3091968,\"cacheWrite\":0,\"turns\":39}},\"featureModels\":{\"safe-role-model-handoff\":{\"openai-codex/gpt-5.6-terra\":{\"input\":117926,\"output\":9423,\"cacheRead\":2369024,\"cacheWrite\":0,\"turns\":31},\"openai-codex/gpt-5.6-sol\":{\"input\":161613,\"output\":576,\"cacheRead\":722944,\"cacheWrite\":0,\"turns\":8}}}}" +prices: "{}" +timestamp: 2026-07-20T13:35:07.863Z +--- + +# Usage + +## hub + +| model | input | output | cache read | cache write | turns | cost | +| --- | ---: | ---: | ---: | ---: | ---: | ---: | +| openai-codex/gpt-5.6-terra | 261034 | 1116 | 1795072 | 0 | 8 | — | + +## plan + +| model | input | output | cache read | cache write | turns | cost | +| --- | ---: | ---: | ---: | ---: | ---: | ---: | +| openai-codex/gpt-5.6-sol | 63084 | 4999 | 612864 | 0 | 20 | — | + +## implement + +| model | input | output | cache read | cache write | turns | cost | +| --- | ---: | ---: | ---: | ---: | ---: | ---: | +| openai-codex/gpt-5.6-terra | 117926 | 9423 | 2369024 | 0 | 31 | — | + +## review + +| model | input | output | cache read | cache write | turns | cost | +| --- | ---: | ---: | ---: | ---: | ---: | ---: | +| openai-codex/gpt-5.6-sol | 161613 | 576 | 722944 | 0 | 8 | — | + +## Per feature + +| feature | input | output | cache read | cache write | turns | cost | +| --- | ---: | ---: | ---: | ---: | ---: | ---: | +| retire-plan | 261034 | 1116 | 1795072 | 0 | 8 | — | +| safe-role-model-handoff | 279539 | 9999 | 3091968 | 0 | 39 | — | + +Total: 603657 in / 16114 out / 5499904 cache-read / 0 cache-write over 67 turns. Cost unavailable: add every used model rate. diff --git a/memory/features/fresh-implementation-session.md b/memory/features/fresh-implementation-session.md new file mode 100644 index 0000000..db50132 --- /dev/null +++ b/memory/features/fresh-implementation-session.md @@ -0,0 +1,37 @@ +--- +type: Feature +title: Fresh implementation session +description: Start every manual, ready-wave, and automatic feature implementation in a new Pi session seeded only with the named feature command and deterministic bundle context. +status: implemented +size: large +depends_on: [] +files: ["extensions/iterator.js", "lib/pi-tools.mjs", "skills/iterator-implement/SKILL.md", "test/pi-tools.test.mjs", "test/extension-fresh-session.test.mjs"] +memories: [architecture/package-and-skill-layout, decisions/backlog-planning-and-feature-waves, decisions/iterator-dashboard-feature-workflow, decisions/manual-role-models-and-runtime-reset, decisions/memory-relevance-usage-and-dashboard-recovery, decisions/parallel-feature-waves-and-consolidated-review, decisions/polish-dashboard-and-multi-agent-workflows, decisions/powerline-shows-sandbox-ui-port] +timestamp: "2026-07-20T13:59:01.971Z" +tags: [] +--- + +# Implementation notes + +Add one extension-command handoff seam that owns `ExtensionCommandContext.newSession()`. Route explicit `/iterator-implement`, dashboard implementation actions, ready-wave items, and auto-mode implementation decisions through it. Capture only plain strings/state before replacement; inside `withSession`, use only the replacement context to send `/skill:iterator-implement ` with `--auto` or user guidance as needed. Do not append prior conversation. Preserve parent-session lineage if available. Distinguish intentional `session_start.reason === 'new'` implementation handoffs from interrupted auto runs so the extension does not pause them, and rebuild any in-memory wave ownership from durable/safely serialized state or a replacement-safe command. Keep model-role switching and explicit review acceptance unchanged. Update the implement runbook to describe the Pi-only fresh-session boundary while leaving Claude Code deterministic gather/write behavior intact. + +# Snippets + +```ts +const result = await ctx.newSession({ + parentSession: ctx.sessionManager.getSessionFile(), + withSession: async (replacementCtx) => { + await replacementCtx.sendUserMessage('/skill:iterator-implement auth --auto'); + }, +}); +``` + +```js +// Existing auto/wave paths dispatch implementation as another turn. +dispatch(action.cmd); +dispatch(`/skill:iterator-implement ${feature} --auto`); +``` + +# Blast radius + +Every Pi implementation entrypoint and session lifecycle; mistakes can lose auto/wave progress, duplicate a feature turn, or carry stale session-bound objects into the replacement runtime. diff --git a/memory/features/index.md b/memory/features/index.md index bc527db..9e5c70f 100644 --- a/memory/features/index.md +++ b/memory/features/index.md @@ -1,3 +1,6 @@ # Features -* [Safe role-model handoff](safe-role-model-handoff.md) - ✅ done · medium · Keep the active managed model usable when a tester or implementer role override is unavailable, so red-test completion can hand off to implementation without an OpenAI credential error. +* [Fresh implementation session](fresh-implementation-session.md) - ⬜ pending · large · Start every manual, ready-wave, and automatic feature implementation in a new Pi session seeded only with the named feature command and deterministic bundle context. +* [Active plan workspace](active-plan-workspace.md) - ⬜ pending · medium · depends: fresh-implementation-session · Make Work the complete home for an active plan and land there after plan or feature approval, while Planning stays focused on future work and archives. +* [Settings dashboard modal](settings-dashboard-modal.md) - ⬜ pending · medium · depends: active-plan-workspace · Open project Settings as a modal above any dashboard tab and return to the exact originating context on save or close. +* [Visible red-test handoff](visible-red-test-handoff.md) - ⬜ pending · medium · depends: active-plan-workspace · Show committed red tests as the implementation target in Work and carry their exact status and paths into the fresh implementer context. diff --git a/memory/features/settings-dashboard-modal.md b/memory/features/settings-dashboard-modal.md new file mode 100644 index 0000000..72d6684 --- /dev/null +++ b/memory/features/settings-dashboard-modal.md @@ -0,0 +1,44 @@ +--- +type: Feature +title: Settings dashboard modal +description: Open project Settings as a modal above any dashboard tab and return to the exact originating context on save or close. +status: pending +size: medium +depends_on: [active-plan-workspace] +files: ["lib/session-server.mjs", "lib/views/settings.mjs", "extensions/iterator.js", "test/session-server.test.mjs", "test/ui.test.mjs"] +memories: [pitfalls/cancel-now-after-grace-timer, architecture/browser-server-contract, architecture/package-and-skill-layout, decisions/backlog-planning-and-feature-waves, decisions/iterator-dashboard-feature-workflow, decisions/manual-role-models-and-runtime-reset, decisions/memory-relevance-usage-and-dashboard-recovery, decisions/parallel-feature-waves-and-consolidated-review] +conflicts: "[{\"decision\":\"decisions/settings-close-returns-to-work\",\"note\":\"The accepted plan replaces forced Work restoration with modal dismissal back to whichever tab and round opened Settings.\"}]" +timestamp: "2026-07-20T13:50:52.261Z" +tags: [] +--- + +# Implementation notes + +Promote Settings from a Work-tab iframe document to shell-owned modal state. The gear and `/iterator-settings` must display it immediately regardless of the active tab, without replacing stored tab HTML, cancelling a pending plan/review round, or clearing the Work ownership overlay. Serve/render the existing settings form inside a modal frame or dedicated modal endpoint under `session-server`; post saves through the existing deterministic settings callback. Close and successful save should dismiss the modal and reveal the same originating tab and round. Cover navigation from Planning, Work, Knowledge, and Usage, including while Work is blocked, responsive sizing, focus/close behavior, and reconnect replay. Keep settings persistence unchanged and sync root libraries. + +# Snippets + +```js +// Current behavior maps settings into Work and can leave other tabs unchanged. +if (["planning", "plan", "feature", "archive"].includes(step)) return "planning"; +return "work"; +``` + +```js +session.showView({ + step: 'settings', + render: () => VIEWS.settings(payload), +}); +``` + +# Depends on + +* [Active plan workspace](/features/active-plan-workspace.md) + +# Blast radius + +Persistent dashboard shell navigation, pending interactive rounds, Work overlay ownership, and every Settings open/save/close path. + +# Decision conflicts + +* [decisions/settings-close-returns-to-work](/decisions/settings-close-returns-to-work.md) — The accepted plan replaces forced Work restoration with modal dismissal back to whichever tab and round opened Settings. diff --git a/memory/features/visible-red-test-handoff.md b/memory/features/visible-red-test-handoff.md new file mode 100644 index 0000000..71b3d07 --- /dev/null +++ b/memory/features/visible-red-test-handoff.md @@ -0,0 +1,39 @@ +--- +type: Feature +title: Visible red-test handoff +description: Show committed red tests as the implementation target in Work and carry their exact status and paths into the fresh implementer context. +status: pending +size: medium +depends_on: [active-plan-workspace] +files: ["lib/gather.mjs", "lib/views/hub.mjs", "lib/pi-tools.mjs", "test/gather.test.mjs", "test/ui.test.mjs", "test/pi-tools.test.mjs"] +memories: [pitfalls/client-js-template-literal-escaping, architecture/package-and-skill-layout, architecture/workflow-state-ownership, patterns/safe-browser-rendering, decisions/backlog-planning-and-feature-waves, decisions/iterator-dashboard-feature-workflow, decisions/manual-role-models-and-runtime-reset, decisions/memory-relevance-usage-and-dashboard-recovery] +timestamp: "2026-07-20T13:50:52.261Z" +tags: [] +--- + +# Implementation notes + +Extend the server-gathered hub feature shape with recorded test paths/count where appropriate, preserving `tests_status` as the source of truth. In Work, make red an explicit expected checkpoint: badge/text should say tests are committed and intentionally failing, and the primary action should read as driving those tests green rather than generic implementation. After test creation, refresh/activate the relevant Work context so the new state is visible. Enrich the concise ambient/implementation kickoff state with the active feature's red status and test paths without duplicating the full contract or prior conversation. Ensure no UI or helper treats red as a failed implementation or silently changes tests; implementation remains responsible for the normal green gate. + +# Snippets + +```js +return { + name: c.slug, + testsStatus: c.fm.tests_status || 'none', + // add the recorded test paths needed by Work and the handoff summary +}; +``` + +```js +test.textContent = c.status==='pending' ? 'Test (red)' : 'Test'; +impl.textContent = 'Implement'; +``` + +# Depends on + +* [Active plan workspace](/features/active-plan-workspace.md) + +# Blast radius + +Feature cards, footer/ambient context size, gather payload compatibility, and the red/green contract used by manual and automatic implementation. diff --git a/memory/index.md b/memory/index.md index 1b238ba..7f480f6 100644 --- a/memory/index.md +++ b/memory/index.md @@ -11,7 +11,7 @@ last_memorized_commit: c5b75e3e4b034bc80a1600e57249e162639d32a8 * [Backlog](backlog/index.md) - Saved ideas and bugs outside active plan features. * [Settings](settings.md) - Project settings (auto mode, models, git flow). * [Features](features/) - One document per implementation feature. -* [Plan](plan.md) - Prevent red-test completion and role-model restoration from leaving the next Iterator turn on an unusable OpenAI credential path. +* [Plan](plan.md) - Start each implementation in a minimal fresh session and make Work the single active-plan surface with clear red-test and modal settings state. * [Architecture](/architecture/) - How the system is structured. * [Decisions](/decisions/) - Durable product and implementation choices agents should preserve. * [Patterns & Conventions](/patterns/) - How code and workflows are written in this repo. diff --git a/memory/log.md b/memory/log.md index 283105c..7f43963 100644 --- a/memory/log.md +++ b/memory/log.md @@ -1,6 +1,18 @@ # iterator update log ## 2026-07-20 +* **Implementation**: Committed feature(fresh-implementation-session) on branch iterator/safe-role-model-handoff; awaiting review. +* **Update**: Applied 4 feature adjustment(s). +* **Creation**: 4 feature(s) written. +* **Creation**: Plan "Focus feature execution and dashboard ownership" approved on branch iterator/safe-role-model-handoff. Consumed 5 selected backlog candidate(s). +* **Backlog**: select openeing-setting-should-be-an-overaly (selected). +* **Backlog**: select it-looks-as-if-the-agent-isnt-using-new-before-a-new-feature-is-implemented-in-either-auto-or-manual-mode (selected). +* **Backlog**: select move-all-active-plan-related-infos-to-work (selected). +* **Backlog**: select after-feature-show-work-tab (selected). +* **Backlog**: select after-adding-tests-its-not-clear-that-they-are-there (selected). +* **Retirement**: Plan "Fix post-test role model credential corruption" condensed into [Safely restore configured Iterator role models](/decisions/safe-role-model-restoration.md). +* **Backlog**: create save-candidate-should-alsways-work-as-its-a-save-to-file. +* **Backlog**: create memroy-update-needs-foxus. * **Update**: Memorized [Apply role models to manual turns and reset stale runtime state](/decisions/manual-role-models-and-runtime-reset.md). * **Review**: Accepted [Safe role-model handoff](/features/safe-role-model-handoff.md) (committed as feature(safe-role-model-handoff)). * **Implementation**: Committed feature(safe-role-model-handoff) on branch iterator/safe-role-model-handoff; awaiting review. diff --git a/memory/plan.md b/memory/plan.md index 889cbfc..68f4c1f 100644 --- a/memory/plan.md +++ b/memory/plan.md @@ -1,24 +1,25 @@ --- type: Plan -title: Fix post-test role model credential corruption -description: Prevent red-test completion and role-model restoration from leaving the next Iterator turn on an unusable OpenAI credential path. +title: Focus feature execution and dashboard ownership +description: Start each implementation in a minimal fresh session and make Work the single active-plan surface with clear red-test and modal settings state. status: approved -branch: iterator/fix-post-test-role-model-credential-corruption -worktree: /Volumes/Extern/Projects/iterator-iterator-fix-post-test-role-model-credential-corruption +branch: iterator/safe-role-model-handoff created: 2026-07-20 -timestamp: 2026-07-20T13:10:00.129Z +timestamp: 2026-07-20T13:48:00.101Z --- # Goal -Make the red-test → implementation handoff reliable: after `/iterator-test` writes and commits intentionally failing tests, the following `/iterator-implement` turn must use the intended active or explicitly configured role model without producing a `proxy-managed` OpenAI 401 or leaking model state between turns. +Make every manual or automatic feature implementation start with a clean Pi session containing only the deterministic feature contract and relevant project knowledge, while simplifying the dashboard so Work owns all active-plan information, Settings opens reliably as a modal from any tab, and committed red tests are unmistakable before implementation. # Architecture -- Build on `architecture/package-and-skill-layout`: isolate the extension's manual-role lifecycle (`input` → `before_agent_start` → `agent_end`) and reproduce the tester-to-implementer sequence with direct extension-hook tests, including managed/proxied active models and failed switches. -- Keep role resolution in `lib/pi-tools.mjs` and session orchestration in `extensions/iterator.js`; make role state turn-scoped, consume stale pending roles, and only restore a prior model when an explicit switch actually succeeded. -- Treat `active` as a strict no-model-switch path. Preserve thinking-level overrides independently, and never synthesize, export, or rewrite provider API-key environment variables. -- Verify both manual red-test handoff and automatic/wave role transitions, then run `npm run sync` for any canonical `lib/` changes so shipped skill copies remain identical. +- Build on `architecture/package-and-skill-layout` and Pi's documented session-replacement lifecycle: route manual, ready-wave, and auto implementation dispatch through an extension command that uses `ExtensionCommandContext.newSession()`, then send only `/skill:iterator-implement ` plus required mode/guidance in the replacement-session context. The implement gather contract, relevant memories, and recorded test paths remain the source of necessary context; prior conversation history is not copied. +- Preserve `architecture/workflow-state-ownership`: durable plan/feature/auto state remains in the bundle and `lib/status.mjs`/gather payloads. Intentional implementation-session replacement must resume the named feature without being mistaken for an interrupted auto run, losing a fixed-wave item, or bypassing dependency and review gates. +- Consolidate active-plan presentation in `lib/views/hub.mjs`: progress, dependency graph, feature actions, revise/re-feature/review-plan/retire/cancel controls, and escalation all live on Work. `lib/views/planning.mjs` becomes the future-work surface for plan creation, selected backlog candidates, and retired-plan browsing when no active work needs management. +- Let `lib/session-server.mjs` own navigation and modal presentation: approved plan/feature transitions intentionally activate Work, while Settings renders in a shell-level modal above whichever tab is open, preserves the originating tab and any pending round, and closes back to that exact context. +- Make red-test state explicit across gather, Work cards, and implementation kickoff: after `/iterator-test`, show that failing tests are committed and expected, expose the recorded test paths/status to the implementer, and label the implementation action as the green step rather than as a generic pending feature. +- Keep all UI changes within `memory/design.md` (compact dark control plane, semantic status colors, responsive controls), retain inline-script parse coverage for `pitfalls/client-js-template-literal-escaping`, and run `npm run sync` after canonical `lib/` changes. # Dependencies @@ -26,11 +27,16 @@ Make the red-test → implementation handoff reliable: after `/iterator-test` wr # Key decisions -- Follow `decisions/manual-role-models-and-runtime-reset`: retain configured per-role model selection and restoration, but harden its lifecycle rather than disabling role models. -- A default or explicit `active` role model must not call `pi.setModel`; an unknown, unavailable, or failed explicit override must stay on the usable current model and must not arm a later restoration. -- Add hook-level regression coverage for tester → implementer and restoration failure paths, closing the direct extension-lifecycle test gap recorded by `decisions/manual-role-models-and-runtime-reset`. -- Preserve the red/green feature contract: red tests remain committed and visible to implementation; deferred formatter output is treated as context, not as a trigger for provider selection. +- Use a real fresh Pi session for each feature implementation, not compaction or a conversation summary. Record the previous session as lineage if useful, but inject no old chat; deterministic gather data is the complete handoff. +- Fresh-session routing applies to explicit manual implementation, fixed ready waves, and auto-mode implementation steps. Testing and review stay in their existing turns unless they independently dispatch a new feature implementation. +- Explicitly revise `decisions/review-navigation-and-work-context`: active plan lifecycle controls move from Planning to Work so Planning contains only future/backlog/archive concerns. This accepted deviation must be captured by `/iterator-memorize` after implementation. +- Explicitly revise `decisions/settings-close-returns-to-work`: Settings becomes a tab-independent modal and closes to its originating tab/context instead of always forcing Work. This accepted deviation must be captured by `/iterator-memorize` after implementation. +- Keep `decisions/backlog-planning-and-feature-waves` and `decisions/parallel-feature-waves-and-consolidated-review`: fresh sessions must not change the immutable ready-wave snapshot, single-model-flow guard, commit attribution, or explicit review acceptance. +- Red remains an expected pre-implementation state: never weaken or auto-fix those tests during test creation; the fresh implementer receives their paths and is responsible for driving them green. # Features -* [Safe role-model handoff](/features/safe-role-model-handoff.md) - Keep the active managed model usable when a tester or implementer role override is unavailable, so red-test completion can hand off to implementation without an OpenAI credential error. +* [Fresh implementation session](/features/fresh-implementation-session.md) - Start every manual, ready-wave, and automatic feature implementation in a new Pi session seeded only with the named feature command and deterministic bundle context. +* [Active plan workspace](/features/active-plan-workspace.md) - Make Work the complete home for an active plan and land there after plan or feature approval, while Planning stays focused on future work and archives. +* [Settings dashboard modal](/features/settings-dashboard-modal.md) - Open project Settings as a modal above any dashboard tab and return to the exact originating context on save or close. +* [Visible red-test handoff](/features/visible-red-test-handoff.md) - Show committed red tests as the implementation target in Work and carry their exact status and paths into the fresh implementer context. diff --git a/memory/state.md b/memory/state.md index 23d7dc1..72a3c63 100644 --- a/memory/state.md +++ b/memory/state.md @@ -2,13 +2,13 @@ type: State title: Runtime state description: Machine-owned iterator flow state — never hand-edited. -mode: manual +mode: auto paused: false -phase: idle -active_feature: null +phase: implementing +active_feature: fresh-implementation-session strikes: "{}" escalation: null -timestamp: 2026-07-20T13:10:00.133Z +timestamp: 2026-07-20T13:52:11.253Z --- Runtime flow state; read via gather, written only by the state op. diff --git a/memory/usage.md b/memory/usage.md index 6cdb848..e3e965a 100644 --- a/memory/usage.md +++ b/memory/usage.md @@ -2,9 +2,9 @@ type: Usage title: Token usage description: Per-step model/token ledger and optional project-owned pricing for the active plan — written only by the usage op. -totals: "{\"steps\":{\"hub\":{\"openai-codex/gpt-5.6-terra\":{\"input\":261034,\"output\":1116,\"cacheRead\":1795072,\"cacheWrite\":0,\"turns\":8}},\"plan\":{\"openai-codex/gpt-5.6-sol\":{\"input\":63084,\"output\":4999,\"cacheRead\":612864,\"cacheWrite\":0,\"turns\":20}}},\"features\":{\"retire-plan\":{\"input\":261034,\"output\":1116,\"cacheRead\":1795072,\"cacheWrite\":0,\"turns\":8}},\"featureModels\":{}}" +totals: "{\"steps\":{\"hub\":{\"openai-codex/gpt-5.6-terra\":{\"input\":243028,\"output\":842,\"cacheRead\":811520,\"cacheWrite\":0,\"turns\":9}},\"plan\":{\"openai-codex/gpt-5.6-sol\":{\"input\":118306,\"output\":8575,\"cacheRead\":3610112,\"cacheWrite\":0,\"turns\":21}}},\"features\":{\"retire-plan\":{\"input\":243028,\"output\":842,\"cacheRead\":811520,\"cacheWrite\":0,\"turns\":9}},\"featureModels\":{\"retire-plan\":{\"openai-codex/gpt-5.6-terra\":{\"input\":243028,\"output\":842,\"cacheRead\":811520,\"cacheWrite\":0,\"turns\":9}}}}" prices: "{}" -timestamp: 2026-07-20T13:18:22.498Z +timestamp: 2026-07-20T13:51:20.415Z --- # Usage @@ -13,18 +13,18 @@ timestamp: 2026-07-20T13:18:22.498Z | model | input | output | cache read | cache write | turns | cost | | --- | ---: | ---: | ---: | ---: | ---: | ---: | -| openai-codex/gpt-5.6-terra | 261034 | 1116 | 1795072 | 0 | 8 | — | +| openai-codex/gpt-5.6-terra | 243028 | 842 | 811520 | 0 | 9 | — | ## plan | model | input | output | cache read | cache write | turns | cost | | --- | ---: | ---: | ---: | ---: | ---: | ---: | -| openai-codex/gpt-5.6-sol | 63084 | 4999 | 612864 | 0 | 20 | — | +| openai-codex/gpt-5.6-sol | 118306 | 8575 | 3610112 | 0 | 21 | — | ## Per feature | feature | input | output | cache read | cache write | turns | cost | | --- | ---: | ---: | ---: | ---: | ---: | ---: | -| retire-plan | 261034 | 1116 | 1795072 | 0 | 8 | — | +| retire-plan | 243028 | 842 | 811520 | 0 | 9 | — | -Total: 324118 in / 6115 out / 2407936 cache-read / 0 cache-write over 28 turns. Cost unavailable: add every used model rate. +Total: 361334 in / 9417 out / 4421632 cache-read / 0 cache-write over 30 turns. Cost unavailable: add every used model rate. diff --git a/skills/iterator-implement/SKILL.md b/skills/iterator-implement/SKILL.md index 91b8648..cdd177c 100644 --- a/skills/iterator-implement/SKILL.md +++ b/skills/iterator-implement/SKILL.md @@ -14,7 +14,13 @@ commits its own files, unrelated working-tree churn never pollutes a review, and several independent features can be implemented back to back and reviewed afterwards. -**pi mode:** see `/../iterator/PI.md`. +**pi mode:** see `/../iterator/PI.md`. The friendly +`/iterator-implement` and `/iterator-next` commands create a replacement Pi +session before running the feature skill. That session receives only the named +implementation command and rebuilds its context from the deterministic +implement gather contract. Dashboard and auto-mode implementation dispatches +use the same command. A direct `/skill:iterator-implement` invocation remains +available for harnesses that already started the dedicated session. **Claude Code mode:** Use gather/write directly instead of the Pi dashboard. Implement only `next`, commit it through `commit-feature`, and report it as diff --git a/test/extension-fresh-session.test.mjs b/test/extension-fresh-session.test.mjs new file mode 100644 index 0000000..bb93026 --- /dev/null +++ b/test/extension-fresh-session.test.mjs @@ -0,0 +1,73 @@ +import { test } from "node:test"; +import assert from "node:assert/strict"; +import { createRequire } from "node:module"; + +const require = createRequire(import.meta.url); +const extensionDependenciesAvailable = (() => { + try { + require.resolve("typebox"); + return true; + } catch { + return false; + } +})(); + +async function loadExtension() { + const { default: register } = await import("../extensions/iterator.js"); + return register; +} + +function mockPi() { + const commands = new Map(); + return { + pi: { + on() {}, + registerTool() {}, + registerCommand(name, definition) { + commands.set(name, definition); + }, + sendUserMessage() {}, + setThinkingLevel() {}, + async setModel() { + return true; + }, + }, + commands, + }; +} + +test("iterator-implement starts a clean replacement session with only its feature command", { skip: !extensionDependenciesAvailable }, async () => { + const { pi, commands } = mockPi(); + const register = await loadExtension(); + register(pi); + const marker = []; + const sent = []; + const ctx = { + hasUI: true, + sessionManager: { getSessionFile: () => "/tmp/parent.jsonl" }, + async newSession(options) { + assert.equal(options.parentSession, "/tmp/parent.jsonl"); + await options.setup({ + appendCustomEntry(type, data) { + marker.push({ type, data }); + }, + }); + await options.withSession({ + async sendUserMessage(command) { + sent.push(command); + }, + }); + return { cancelled: false }; + }, + }; + + await commands.get("iterator-implement").handler("auth --auto", ctx); + + assert.deepEqual(sent, ["/skill:iterator-implement auth --auto"]); + assert.deepEqual(marker, [ + { + type: "iterator-implementation-handoff", + data: { feature: "auth", auto: true, featureWave: null }, + }, + ]); +}); diff --git a/test/pi-tools.test.mjs b/test/pi-tools.test.mjs index fa931c4..10f7303 100644 --- a/test/pi-tools.test.mjs +++ b/test/pi-tools.test.mjs @@ -6,6 +6,7 @@ import { join } from "node:path"; import { actionToCommand, activityTextFromMessage, + implementationCommand, bundleExists, featuresDirEntries, composeAmbientContext, @@ -43,7 +44,7 @@ test("actionToCommand maps hub actions to skill commands", () => { ); assert.equal( actionToCommand({ type: "action", action: "implement", feature: "auth" }), - "/skill:iterator-implement auth", + "/iterator-implement auth", ); assert.equal( actionToCommand({ type: "action", action: "review", feature: "auth" }), @@ -55,6 +56,14 @@ test("actionToCommand maps hub actions to skill commands", () => { ); }); +test("implementationCommand creates fresh-session command invocations", () => { + assert.equal(implementationCommand("auth"), "/iterator-implement auth"); + assert.equal( + implementationCommand("auth", { auto: true, guidance: "keep tests red" }), + "/iterator-implement auth --auto — keep tests red", + ); +}); + test("actionToCommand returns null for cancel/timeout/garbage", () => { assert.equal(actionToCommand({ type: "cancel" }), null); assert.equal(actionToCommand({ type: "timeout" }), null); From 6cb321ddba8da406446ce810373f0649d63d0057 Mon Sep 17 00:00:00 2001 From: Christoph Maier Date: Mon, 20 Jul 2026 15:59:02 +0200 Subject: [PATCH 05/42] chore(iterator): record feature commit --- memory/features/fresh-implementation-session.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/memory/features/fresh-implementation-session.md b/memory/features/fresh-implementation-session.md index db50132..c6cee83 100644 --- a/memory/features/fresh-implementation-session.md +++ b/memory/features/fresh-implementation-session.md @@ -7,8 +7,12 @@ size: large depends_on: [] files: ["extensions/iterator.js", "lib/pi-tools.mjs", "skills/iterator-implement/SKILL.md", "test/pi-tools.test.mjs", "test/extension-fresh-session.test.mjs"] memories: [architecture/package-and-skill-layout, decisions/backlog-planning-and-feature-waves, decisions/iterator-dashboard-feature-workflow, decisions/manual-role-models-and-runtime-reset, decisions/memory-relevance-usage-and-dashboard-recovery, decisions/parallel-feature-waves-and-consolidated-review, decisions/polish-dashboard-and-multi-agent-workflows, decisions/powerline-shows-sandbox-ui-port] -timestamp: "2026-07-20T13:59:01.971Z" +timestamp: "2026-07-20T13:59:02.057Z" tags: [] +commits: + - sha: e7a69a4ee3dd962817c766d39109012284a0e530 + kind: implement + date: 2026-07-20 --- # Implementation notes From 382ac72398fb7443fe5b9a594105e0704a76a8bb Mon Sep 17 00:00:00 2001 From: Christoph Maier Date: Mon, 20 Jul 2026 16:05:37 +0200 Subject: [PATCH 06/42] feature(fresh-implementation-session): Preserve role and auto state across fresh sessions Feature: fresh-implementation-session --- extensions/iterator.js | 31 ++++++------ lib/pi-tools.mjs | 37 +++++++++++++- .../features/fresh-implementation-session.md | 8 +++- memory/log.md | 2 + memory/state.md | 4 +- memory/usage.md | 19 ++++++-- test/extension-fresh-session.test.mjs | 11 ++++- test/pi-tools.test.mjs | 48 +++++++++++++++++++ 8 files changed, 136 insertions(+), 24 deletions(-) diff --git a/extensions/iterator.js b/extensions/iterator.js index 38c583c..098ca36 100644 --- a/extensions/iterator.js +++ b/extensions/iterator.js @@ -61,6 +61,7 @@ import { extractPathsFromBash, footerText, implementationCommand, + implementationHandoffState, mergePayload, nextAutoAction, nextFeatureWaveAction, @@ -69,6 +70,7 @@ import { roleFromInput, roleModelSpec, runJson, + shouldApplyRole, scriptPath, shouldNudge, uiPort, @@ -788,7 +790,8 @@ export default function iteratorExtension(pi) { phase: AUTO_PHASE_FOR_STEP[action.step] || "implementing", active_feature: action.feature || null, }); - if (action.step !== "implement") await applyRole(action.role, sess.settings); + if (action.step !== "implement") + await applyRole(action.role, sess.settings); attribution = { step: action.step, feature: action.feature || null }; const p = sess.hub?.progress || {}; // Structured working state: the shell renders step/feature and a @@ -1076,6 +1079,7 @@ export default function iteratorExtension(pi) { const handoff = { feature, auto, + autoSteps, // A ready-wave lives in extension memory; preserve only its plain, // immutable snapshot so the replacement runtime can finish the wave. featureWave: featureWave @@ -1086,6 +1090,9 @@ export default function iteratorExtension(pi) { } : null, }; + // A tester/reviewer role belongs to the old session. Return to the user's + // model before replacement so an `active` implementer never inherits it. + await restoreModel(); const result = await ctx.newSession({ parentSession: ctx.sessionManager?.getSessionFile?.(), setup: async (manager) => { @@ -1529,7 +1536,12 @@ export default function iteratorExtension(pi) { const { hub, implement, settings, state } = await gatherSession(ctx.cwd); const role = pendingRole; pendingRole = null; // role input belongs to exactly one agent turn - if (role && state?.mode !== "auto" && !featureWave) { + if ( + shouldApplyRole(role, { + mode: state?.mode, + featureWave, + }) + ) { manualRoleTurn.switched = await applyRole(role, settings); } // Model selection also applies to /iterator-plan before a bundle exists; @@ -1616,22 +1628,11 @@ export default function iteratorExtension(pi) { pi.on("session_start", async (event, ctx) => { rememberCtx(ctx); const sessionEntries = ctx.sessionManager?.getEntries?.() || []; - let handoff = null; // A persisted marker is only a handoff during the session replacement // that created it. On reload/restart, retain the normal auto safety pause. - if (event?.reason === "new") { - for (let index = sessionEntries.length - 1; index >= 0; index -= 1) { - const entry = sessionEntries[index]; - if ( - entry.type === "custom" && - entry.customType === "iterator-implementation-handoff" - ) { - handoff = entry.data; - break; - } - } - } + const handoff = implementationHandoffState(sessionEntries, event?.reason); if (handoff?.featureWave) featureWave = handoff.featureWave; + if (handoff) autoSteps = handoff.autoSteps; await refreshStatus(ctx); try { await ensureServer(ctx); diff --git a/lib/pi-tools.mjs b/lib/pi-tools.mjs index 0a12f56..3c33e31 100644 --- a/lib/pi-tools.mjs +++ b/lib/pi-tools.mjs @@ -23,7 +23,10 @@ export function mergePayload(gathered, extra) { * Map a hub- or knowledge-view result to the command that runs the chosen * flow, or null for cancel/timeout/close/anything non-actionable. */ -export function implementationCommand(feature, { auto = false, guidance = null } = {}) { +export function implementationCommand( + feature, + { auto = false, guidance = null } = {}, +) { const parts = ["/iterator-implement"]; if (feature) parts.push(feature); if (auto) parts.push("--auto"); @@ -31,6 +34,38 @@ export function implementationCommand(feature, { auto = false, guidance = null } return parts.join(" "); } +/** Read and normalize the marker created for an intentional fresh session. */ +export function implementationHandoffState(entries, reason) { + if (reason !== "new" || !Array.isArray(entries)) return null; + for (let index = entries.length - 1; index >= 0; index -= 1) { + const entry = entries[index]; + if ( + entry?.type !== "custom" || + entry.customType !== "iterator-implementation-handoff" || + !entry.data || + typeof entry.data !== "object" + ) + continue; + return { + ...entry.data, + autoSteps: + Number.isInteger(entry.data.autoSteps) && entry.data.autoSteps >= 0 + ? entry.data.autoSteps + : 0, + }; + } + return null; +} + +export function shouldApplyRole(role, { mode, featureWave } = {}) { + if (!role) return false; + // Auto/wave drivers no longer pre-apply the implementer role because the + // implementation runs after a session replacement. The resulting skill + // turn — including a harness-started direct skill invocation — owns it. + if (role === "implementer") return true; + return mode !== "auto" && !featureWave; +} + export function actionToCommand(result) { if (!result || result.type !== "action") return null; diff --git a/memory/features/fresh-implementation-session.md b/memory/features/fresh-implementation-session.md index c6cee83..ebb4f0b 100644 --- a/memory/features/fresh-implementation-session.md +++ b/memory/features/fresh-implementation-session.md @@ -7,12 +7,13 @@ size: large depends_on: [] files: ["extensions/iterator.js", "lib/pi-tools.mjs", "skills/iterator-implement/SKILL.md", "test/pi-tools.test.mjs", "test/extension-fresh-session.test.mjs"] memories: [architecture/package-and-skill-layout, decisions/backlog-planning-and-feature-waves, decisions/iterator-dashboard-feature-workflow, decisions/manual-role-models-and-runtime-reset, decisions/memory-relevance-usage-and-dashboard-recovery, decisions/parallel-feature-waves-and-consolidated-review, decisions/polish-dashboard-and-multi-agent-workflows, decisions/powerline-shows-sandbox-ui-port] -timestamp: "2026-07-20T13:59:02.057Z" +timestamp: "2026-07-20T14:05:37.935Z" tags: [] commits: - sha: e7a69a4ee3dd962817c766d39109012284a0e530 kind: implement date: 2026-07-20 +reviewed: 2026-07-20 --- # Implementation notes @@ -39,3 +40,8 @@ dispatch(`/skill:iterator-implement ${feature} --auto`); # Blast radius Every Pi implementation entrypoint and session lifecycle; mistakes can lose auto/wave progress, duplicate a feature turn, or carry stale session-bound objects into the replacement runtime. + +# Review + +## 2026-07-20 +* **Needs changes** _(agent review: openai-codex/gpt-5.6-sol)_ — Fresh-session auto/wave implementations no longer receive the configured implementer role: the old driver now skips applyRole, while the replacement turn's before_agent_start still refuses pendingRole whenever state.mode is auto or featureWave is set. Restore any prior step role before ctx.newSession, mark the intentional handoff in the new extension instance, and apply/restore the implementer role exactly once there. Also carry autoSteps through the handoff so replacing the session for every feature cannot reset and bypass AUTO_MAX_STEPS. Add lifecycle assertions for auto and ready-wave handoffs (the current extension test is skipped when typebox is absent). diff --git a/memory/log.md b/memory/log.md index 7f43963..0d2341b 100644 --- a/memory/log.md +++ b/memory/log.md @@ -2,6 +2,8 @@ ## 2026-07-20 * **Implementation**: Committed feature(fresh-implementation-session) on branch iterator/safe-role-model-handoff; awaiting review. +* **Review**: Reviewed [Fresh implementation session](/features/fresh-implementation-session.md); changes (agent). +* **Implementation**: Committed feature(fresh-implementation-session) on branch iterator/safe-role-model-handoff; awaiting review. * **Update**: Applied 4 feature adjustment(s). * **Creation**: 4 feature(s) written. * **Creation**: Plan "Focus feature execution and dashboard ownership" approved on branch iterator/safe-role-model-handoff. Consumed 5 selected backlog candidate(s). diff --git a/memory/state.md b/memory/state.md index 72a3c63..1aaf841 100644 --- a/memory/state.md +++ b/memory/state.md @@ -6,9 +6,9 @@ mode: auto paused: false phase: implementing active_feature: fresh-implementation-session -strikes: "{}" +strikes: "{\"fresh-implementation-session\":1}" escalation: null -timestamp: 2026-07-20T13:52:11.253Z +timestamp: 2026-07-20T14:00:52.115Z --- Runtime flow state; read via gather, written only by the state op. diff --git a/memory/usage.md b/memory/usage.md index e3e965a..d69e306 100644 --- a/memory/usage.md +++ b/memory/usage.md @@ -2,9 +2,9 @@ type: Usage title: Token usage description: Per-step model/token ledger and optional project-owned pricing for the active plan — written only by the usage op. -totals: "{\"steps\":{\"hub\":{\"openai-codex/gpt-5.6-terra\":{\"input\":243028,\"output\":842,\"cacheRead\":811520,\"cacheWrite\":0,\"turns\":9}},\"plan\":{\"openai-codex/gpt-5.6-sol\":{\"input\":118306,\"output\":8575,\"cacheRead\":3610112,\"cacheWrite\":0,\"turns\":21}}},\"features\":{\"retire-plan\":{\"input\":243028,\"output\":842,\"cacheRead\":811520,\"cacheWrite\":0,\"turns\":9}},\"featureModels\":{\"retire-plan\":{\"openai-codex/gpt-5.6-terra\":{\"input\":243028,\"output\":842,\"cacheRead\":811520,\"cacheWrite\":0,\"turns\":9}}}}" +totals: "{\"steps\":{\"hub\":{\"openai-codex/gpt-5.6-terra\":{\"input\":243028,\"output\":842,\"cacheRead\":811520,\"cacheWrite\":0,\"turns\":9}},\"plan\":{\"openai-codex/gpt-5.6-sol\":{\"input\":118306,\"output\":8575,\"cacheRead\":3610112,\"cacheWrite\":0,\"turns\":21}},\"implement\":{\"openai-codex/gpt-5.6-terra\":{\"input\":1086985,\"output\":12201,\"cacheRead\":6675968,\"cacheWrite\":0,\"turns\":32}},\"review\":{\"openai-codex/gpt-5.6-sol\":{\"input\":588476,\"output\":2395,\"cacheRead\":1334784,\"cacheWrite\":0,\"turns\":7}}},\"features\":{\"retire-plan\":{\"input\":243028,\"output\":842,\"cacheRead\":811520,\"cacheWrite\":0,\"turns\":9},\"fresh-implementation-session\":{\"input\":1675461,\"output\":14596,\"cacheRead\":8010752,\"cacheWrite\":0,\"turns\":39}},\"featureModels\":{\"retire-plan\":{\"openai-codex/gpt-5.6-terra\":{\"input\":243028,\"output\":842,\"cacheRead\":811520,\"cacheWrite\":0,\"turns\":9}},\"fresh-implementation-session\":{\"openai-codex/gpt-5.6-terra\":{\"input\":1086985,\"output\":12201,\"cacheRead\":6675968,\"cacheWrite\":0,\"turns\":32},\"openai-codex/gpt-5.6-sol\":{\"input\":588476,\"output\":2395,\"cacheRead\":1334784,\"cacheWrite\":0,\"turns\":7}}}}" prices: "{}" -timestamp: 2026-07-20T13:51:20.415Z +timestamp: 2026-07-20T14:00:51.847Z --- # Usage @@ -21,10 +21,23 @@ timestamp: 2026-07-20T13:51:20.415Z | --- | ---: | ---: | ---: | ---: | ---: | ---: | | openai-codex/gpt-5.6-sol | 118306 | 8575 | 3610112 | 0 | 21 | — | +## implement + +| model | input | output | cache read | cache write | turns | cost | +| --- | ---: | ---: | ---: | ---: | ---: | ---: | +| openai-codex/gpt-5.6-terra | 1086985 | 12201 | 6675968 | 0 | 32 | — | + +## review + +| model | input | output | cache read | cache write | turns | cost | +| --- | ---: | ---: | ---: | ---: | ---: | ---: | +| openai-codex/gpt-5.6-sol | 588476 | 2395 | 1334784 | 0 | 7 | — | + ## Per feature | feature | input | output | cache read | cache write | turns | cost | | --- | ---: | ---: | ---: | ---: | ---: | ---: | | retire-plan | 243028 | 842 | 811520 | 0 | 9 | — | +| fresh-implementation-session | 1675461 | 14596 | 8010752 | 0 | 39 | — | -Total: 361334 in / 9417 out / 4421632 cache-read / 0 cache-write over 30 turns. Cost unavailable: add every used model rate. +Total: 2036795 in / 24013 out / 12432384 cache-read / 0 cache-write over 69 turns. Cost unavailable: add every used model rate. diff --git a/test/extension-fresh-session.test.mjs b/test/extension-fresh-session.test.mjs index bb93026..ea79c4a 100644 --- a/test/extension-fresh-session.test.mjs +++ b/test/extension-fresh-session.test.mjs @@ -36,7 +36,9 @@ function mockPi() { }; } -test("iterator-implement starts a clean replacement session with only its feature command", { skip: !extensionDependenciesAvailable }, async () => { +test("iterator-implement starts a clean replacement session with only its feature command", { + skip: !extensionDependenciesAvailable, +}, async () => { const { pi, commands } = mockPi(); const register = await loadExtension(); register(pi); @@ -67,7 +69,12 @@ test("iterator-implement starts a clean replacement session with only its featur assert.deepEqual(marker, [ { type: "iterator-implementation-handoff", - data: { feature: "auth", auto: true, featureWave: null }, + data: { + feature: "auth", + auto: true, + autoSteps: 0, + featureWave: null, + }, }, ]); }); diff --git a/test/pi-tools.test.mjs b/test/pi-tools.test.mjs index 10f7303..2a46eae 100644 --- a/test/pi-tools.test.mjs +++ b/test/pi-tools.test.mjs @@ -7,6 +7,8 @@ import { actionToCommand, activityTextFromMessage, implementationCommand, + implementationHandoffState, + shouldApplyRole, bundleExists, featuresDirEntries, composeAmbientContext, @@ -64,6 +66,52 @@ test("implementationCommand creates fresh-session command invocations", () => { ); }); +test("implementationHandoffState preserves auto and wave lifecycle state only for new sessions", () => { + const featureWave = { queue: ["b"], active: "a", results: [] }; + const entries = [ + { type: "custom", customType: "other", data: {} }, + { + type: "custom", + customType: "iterator-implementation-handoff", + data: { feature: "a", auto: true, autoSteps: 17, featureWave }, + }, + ]; + assert.deepEqual(implementationHandoffState(entries, "new"), { + feature: "a", + auto: true, + autoSteps: 17, + featureWave, + }); + assert.equal(implementationHandoffState(entries, "reload"), null); + assert.equal(implementationHandoffState([], "new"), null); +}); + +test("implementationHandoffState defaults malformed circuit-breaker state", () => { + const entries = [ + { + type: "custom", + customType: "iterator-implementation-handoff", + data: { feature: "a", autoSteps: -1 }, + }, + ]; + assert.equal(implementationHandoffState(entries, "new").autoSteps, 0); +}); + +test("shouldApplyRole lets fresh auto and wave implementers own their role", () => { + assert.equal(shouldApplyRole("implementer", { mode: "auto" }), true); + assert.equal( + shouldApplyRole("implementer", { + mode: "manual", + featureWave: { active: "auth" }, + }), + true, + ); + assert.equal( + shouldApplyRole("tester", { mode: "manual", featureWave: null }), + true, + ); +}); + test("actionToCommand returns null for cancel/timeout/garbage", () => { assert.equal(actionToCommand({ type: "cancel" }), null); assert.equal(actionToCommand({ type: "timeout" }), null); From ff02b5fcfde41199a59f3488d2b212dcbd6b59cc Mon Sep 17 00:00:00 2001 From: Christoph Maier Date: Mon, 20 Jul 2026 16:05:38 +0200 Subject: [PATCH 07/42] chore(iterator): record feature commit --- memory/features/fresh-implementation-session.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/memory/features/fresh-implementation-session.md b/memory/features/fresh-implementation-session.md index ebb4f0b..144cb84 100644 --- a/memory/features/fresh-implementation-session.md +++ b/memory/features/fresh-implementation-session.md @@ -7,12 +7,15 @@ size: large depends_on: [] files: ["extensions/iterator.js", "lib/pi-tools.mjs", "skills/iterator-implement/SKILL.md", "test/pi-tools.test.mjs", "test/extension-fresh-session.test.mjs"] memories: [architecture/package-and-skill-layout, decisions/backlog-planning-and-feature-waves, decisions/iterator-dashboard-feature-workflow, decisions/manual-role-models-and-runtime-reset, decisions/memory-relevance-usage-and-dashboard-recovery, decisions/parallel-feature-waves-and-consolidated-review, decisions/polish-dashboard-and-multi-agent-workflows, decisions/powerline-shows-sandbox-ui-port] -timestamp: "2026-07-20T14:05:37.935Z" +timestamp: "2026-07-20T14:05:38.005Z" tags: [] commits: - sha: e7a69a4ee3dd962817c766d39109012284a0e530 kind: implement date: 2026-07-20 + - sha: 382ac72398fb7443fe5b9a594105e0704a76a8bb + kind: implement + date: 2026-07-20 reviewed: 2026-07-20 --- From 170f7ae9fb11918da354ff10421ea843b83d4d57 Mon Sep 17 00:00:00 2001 From: Christoph Maier Date: Mon, 20 Jul 2026 16:06:33 +0200 Subject: [PATCH 08/42] chore(iterator): record feature commits and memory updates --- memory/features/fresh-implementation-session.md | 5 +++-- memory/features/index.md | 2 +- memory/log.md | 1 + memory/state.md | 4 ++-- memory/usage.md | 9 +++++---- 5 files changed, 12 insertions(+), 9 deletions(-) diff --git a/memory/features/fresh-implementation-session.md b/memory/features/fresh-implementation-session.md index 144cb84..931a239 100644 --- a/memory/features/fresh-implementation-session.md +++ b/memory/features/fresh-implementation-session.md @@ -2,12 +2,12 @@ type: Feature title: Fresh implementation session description: Start every manual, ready-wave, and automatic feature implementation in a new Pi session seeded only with the named feature command and deterministic bundle context. -status: implemented +status: done size: large depends_on: [] files: ["extensions/iterator.js", "lib/pi-tools.mjs", "skills/iterator-implement/SKILL.md", "test/pi-tools.test.mjs", "test/extension-fresh-session.test.mjs"] memories: [architecture/package-and-skill-layout, decisions/backlog-planning-and-feature-waves, decisions/iterator-dashboard-feature-workflow, decisions/manual-role-models-and-runtime-reset, decisions/memory-relevance-usage-and-dashboard-recovery, decisions/parallel-feature-waves-and-consolidated-review, decisions/polish-dashboard-and-multi-agent-workflows, decisions/powerline-shows-sandbox-ui-port] -timestamp: "2026-07-20T14:05:38.005Z" +timestamp: "2026-07-20T14:06:33.938Z" tags: [] commits: - sha: e7a69a4ee3dd962817c766d39109012284a0e530 @@ -17,6 +17,7 @@ commits: kind: implement date: 2026-07-20 reviewed: 2026-07-20 +done: 2026-07-20 --- # Implementation notes diff --git a/memory/features/index.md b/memory/features/index.md index 9e5c70f..f436388 100644 --- a/memory/features/index.md +++ b/memory/features/index.md @@ -1,6 +1,6 @@ # Features -* [Fresh implementation session](fresh-implementation-session.md) - ⬜ pending · large · Start every manual, ready-wave, and automatic feature implementation in a new Pi session seeded only with the named feature command and deterministic bundle context. +* [Fresh implementation session](fresh-implementation-session.md) - ✅ done · large · Start every manual, ready-wave, and automatic feature implementation in a new Pi session seeded only with the named feature command and deterministic bundle context. * [Active plan workspace](active-plan-workspace.md) - ⬜ pending · medium · depends: fresh-implementation-session · Make Work the complete home for an active plan and land there after plan or feature approval, while Planning stays focused on future work and archives. * [Settings dashboard modal](settings-dashboard-modal.md) - ⬜ pending · medium · depends: active-plan-workspace · Open project Settings as a modal above any dashboard tab and return to the exact originating context on save or close. * [Visible red-test handoff](visible-red-test-handoff.md) - ⬜ pending · medium · depends: active-plan-workspace · Show committed red tests as the implementation target in Work and carry their exact status and paths into the fresh implementer context. diff --git a/memory/log.md b/memory/log.md index 0d2341b..ec8f33a 100644 --- a/memory/log.md +++ b/memory/log.md @@ -1,6 +1,7 @@ # iterator update log ## 2026-07-20 +* **Review**: Accepted [Fresh implementation session](/features/fresh-implementation-session.md) (committed as feature(fresh-implementation-session)). * **Implementation**: Committed feature(fresh-implementation-session) on branch iterator/safe-role-model-handoff; awaiting review. * **Review**: Reviewed [Fresh implementation session](/features/fresh-implementation-session.md); changes (agent). * **Implementation**: Committed feature(fresh-implementation-session) on branch iterator/safe-role-model-handoff; awaiting review. diff --git a/memory/state.md b/memory/state.md index 1aaf841..380637d 100644 --- a/memory/state.md +++ b/memory/state.md @@ -4,11 +4,11 @@ title: Runtime state description: Machine-owned iterator flow state — never hand-edited. mode: auto paused: false -phase: implementing +phase: reviewing active_feature: fresh-implementation-session strikes: "{\"fresh-implementation-session\":1}" escalation: null -timestamp: 2026-07-20T14:00:52.115Z +timestamp: 2026-07-20T14:05:54.015Z --- Runtime flow state; read via gather, written only by the state op. diff --git a/memory/usage.md b/memory/usage.md index d69e306..16fb336 100644 --- a/memory/usage.md +++ b/memory/usage.md @@ -2,9 +2,9 @@ type: Usage title: Token usage description: Per-step model/token ledger and optional project-owned pricing for the active plan — written only by the usage op. -totals: "{\"steps\":{\"hub\":{\"openai-codex/gpt-5.6-terra\":{\"input\":243028,\"output\":842,\"cacheRead\":811520,\"cacheWrite\":0,\"turns\":9}},\"plan\":{\"openai-codex/gpt-5.6-sol\":{\"input\":118306,\"output\":8575,\"cacheRead\":3610112,\"cacheWrite\":0,\"turns\":21}},\"implement\":{\"openai-codex/gpt-5.6-terra\":{\"input\":1086985,\"output\":12201,\"cacheRead\":6675968,\"cacheWrite\":0,\"turns\":32}},\"review\":{\"openai-codex/gpt-5.6-sol\":{\"input\":588476,\"output\":2395,\"cacheRead\":1334784,\"cacheWrite\":0,\"turns\":7}}},\"features\":{\"retire-plan\":{\"input\":243028,\"output\":842,\"cacheRead\":811520,\"cacheWrite\":0,\"turns\":9},\"fresh-implementation-session\":{\"input\":1675461,\"output\":14596,\"cacheRead\":8010752,\"cacheWrite\":0,\"turns\":39}},\"featureModels\":{\"retire-plan\":{\"openai-codex/gpt-5.6-terra\":{\"input\":243028,\"output\":842,\"cacheRead\":811520,\"cacheWrite\":0,\"turns\":9}},\"fresh-implementation-session\":{\"openai-codex/gpt-5.6-terra\":{\"input\":1086985,\"output\":12201,\"cacheRead\":6675968,\"cacheWrite\":0,\"turns\":32},\"openai-codex/gpt-5.6-sol\":{\"input\":588476,\"output\":2395,\"cacheRead\":1334784,\"cacheWrite\":0,\"turns\":7}}}}" +totals: "{\"steps\":{\"hub\":{\"openai-codex/gpt-5.6-terra\":{\"input\":243028,\"output\":842,\"cacheRead\":811520,\"cacheWrite\":0,\"turns\":9}},\"plan\":{\"openai-codex/gpt-5.6-sol\":{\"input\":118306,\"output\":8575,\"cacheRead\":3610112,\"cacheWrite\":0,\"turns\":21}},\"implement\":{\"openai-codex/gpt-5.6-terra\":{\"input\":1086985,\"output\":12201,\"cacheRead\":6675968,\"cacheWrite\":0,\"turns\":32},\"openai-codex/gpt-5.6-sol\":{\"input\":1191326,\"output\":7856,\"cacheRead\":6515712,\"cacheWrite\":0,\"turns\":26}},\"review\":{\"openai-codex/gpt-5.6-sol\":{\"input\":588476,\"output\":2395,\"cacheRead\":1334784,\"cacheWrite\":0,\"turns\":7}}},\"features\":{\"retire-plan\":{\"input\":243028,\"output\":842,\"cacheRead\":811520,\"cacheWrite\":0,\"turns\":9},\"fresh-implementation-session\":{\"input\":2866787,\"output\":22452,\"cacheRead\":14526464,\"cacheWrite\":0,\"turns\":65}},\"featureModels\":{\"retire-plan\":{\"openai-codex/gpt-5.6-terra\":{\"input\":243028,\"output\":842,\"cacheRead\":811520,\"cacheWrite\":0,\"turns\":9}},\"fresh-implementation-session\":{\"openai-codex/gpt-5.6-terra\":{\"input\":1086985,\"output\":12201,\"cacheRead\":6675968,\"cacheWrite\":0,\"turns\":32},\"openai-codex/gpt-5.6-sol\":{\"input\":1779802,\"output\":10251,\"cacheRead\":7850496,\"cacheWrite\":0,\"turns\":33}}}}" prices: "{}" -timestamp: 2026-07-20T14:00:51.847Z +timestamp: 2026-07-20T14:05:53.791Z --- # Usage @@ -26,6 +26,7 @@ timestamp: 2026-07-20T14:00:51.847Z | model | input | output | cache read | cache write | turns | cost | | --- | ---: | ---: | ---: | ---: | ---: | ---: | | openai-codex/gpt-5.6-terra | 1086985 | 12201 | 6675968 | 0 | 32 | — | +| openai-codex/gpt-5.6-sol | 1191326 | 7856 | 6515712 | 0 | 26 | — | ## review @@ -38,6 +39,6 @@ timestamp: 2026-07-20T14:00:51.847Z | feature | input | output | cache read | cache write | turns | cost | | --- | ---: | ---: | ---: | ---: | ---: | ---: | | retire-plan | 243028 | 842 | 811520 | 0 | 9 | — | -| fresh-implementation-session | 1675461 | 14596 | 8010752 | 0 | 39 | — | +| fresh-implementation-session | 2866787 | 22452 | 14526464 | 0 | 65 | — | -Total: 2036795 in / 24013 out / 12432384 cache-read / 0 cache-write over 69 turns. Cost unavailable: add every used model rate. +Total: 3228121 in / 31869 out / 18948096 cache-read / 0 cache-write over 95 turns. Cost unavailable: add every used model rate. From c5f6915bf82dc6914b8a02a707422a0c67b1a26a Mon Sep 17 00:00:00 2001 From: Christoph Maier Date: Mon, 20 Jul 2026 16:18:42 +0200 Subject: [PATCH 09/42] feature(active-plan-workspace): Make Work the active plan workspace Feature: active-plan-workspace --- extensions/iterator.js | 19 ++++- lib/views/hub.mjs | 46 ++++++++++++ lib/views/planning.mjs | 70 +++---------------- memory/features/active-plan-workspace.md | 4 +- .../features/fresh-implementation-session.md | 3 +- memory/log.md | 2 + memory/state.md | 4 +- memory/usage.md | 10 +-- skills/iterator/lib/views/hub.mjs | 46 ++++++++++++ skills/iterator/lib/views/planning.mjs | 70 +++---------------- test/session-server.test.mjs | 17 +++++ test/ui.test.mjs | 24 +++---- 12 files changed, 172 insertions(+), 143 deletions(-) diff --git a/extensions/iterator.js b/extensions/iterator.js index 098ca36..28a2aab 100644 --- a/extensions/iterator.js +++ b/extensions/iterator.js @@ -321,7 +321,10 @@ export default function iteratorExtension(pi) { }; /** Refresh the idle dashboard tabs (no pending round). */ - const refreshHub = async (cwd, { preferPlanning = false } = {}) => { + const refreshHub = async ( + cwd, + { preferPlanning = false, activateWork = false } = {}, + ) => { if (!session || !session.isRunning() || session.hasPending()) return; try { // Inactive tabs first: their refreshes are stored silently. On first @@ -343,7 +346,11 @@ export default function iteratorExtension(pi) { render: () => VIEWS.planning({ ...hub, step: "planning" }), activate: landOnPlanning, }); - session.showView({ step: "hub", render: () => VIEWS.hub(hub) }); + session.showView({ + step: "hub", + render: () => VIEWS.hub(hub), + activate: activateWork, + }); await pushStatus(cwd); } catch { /* a broken bundle read must never take pi down */ @@ -998,9 +1005,13 @@ export default function iteratorExtension(pi) { // (decisions/settings-close-returns-to-work) — never a model turn. // "planning" is pure navigation too: refresh both dashboard tabs // (the shell's Planning tab already holds the view). + if (result?.type === "action" && result.action === "hub") { + void refreshHub(ctxCwd(), { activateWork: true }); + return; + } if ( result?.type === "action" && - ["hub", "close", "planning"].includes(result.action) + ["close", "planning"].includes(result.action) ) { void refreshHub(ctxCwd()); return; @@ -1294,6 +1305,7 @@ export default function iteratorExtension(pi) { (params.op === "adjustments" || params.type === "plan-approved") && (params.accept === true || params.type === "plan-approved"); if (approved) { + void refreshHub(ctx.cwd, { activateWork: true }); try { const { settings, state } = await gatherSession(ctx.cwd); if (settings?.auto_mode === "on" && state?.mode !== "auto") { @@ -1511,6 +1523,7 @@ export default function iteratorExtension(pi) { applied = { ok: false, error: e.message }; } invalidateSession(); + if (applied?.ok) await refreshHub(ctx.cwd, { activateWork: true }); return asText({ ...result, applied }); } return asText(result); diff --git a/lib/views/hub.mjs b/lib/views/hub.mjs index 90dad21..f6f6155 100644 --- a/lib/views/hub.mjs +++ b/lib/views/hub.mjs @@ -110,6 +110,52 @@ function render(){ ? ''+done+' / '+total+' features done'+ '
' : 'not broken into features yet'); + // Active-plan lifecycle belongs with its progress and feature work. Every + // condition is the server-derived stage; this view never infers it. + const revise = document.createElement('button'); + revise.className = 'act'; + revise.textContent = 'Revise plan'; + revise.addEventListener('click', () => action('plan', null, 'Starting /iterator-plan')); + bar.appendChild(revise); + const refeature = document.createElement('button'); + refeature.className = D.stage==='needs-features' ? 'act primary-act' : 'act'; + refeature.textContent = D.stage==='needs-features' ? 'Feature the plan' : 'Re-feature'; + refeature.title = D.stage==='needs-features' + ? 'Next step: break the approved plan into small, dependency-ordered features (/iterator-feature)' + : 'Redraw the feature set from the plan (/iterator-feature)'; + refeature.addEventListener('click', () => action('feature', null, 'Starting /iterator-feature')); + bar.appendChild(refeature); + if(D.stage==='awaiting-plan-review' || D.stage==='retirable'){ + const reviewPlan = document.createElement('button'); + reviewPlan.className = 'act primary-act'; + reviewPlan.textContent = D.plan.planReviewed ? 'Re-review plan' : 'Review plan'; + reviewPlan.title = D.plan.planReviewed + ? 'Plan reviewed '+D.plan.planReviewed+' \\u2014 run the whole-plan review again' + : 'Review all changes and commits against the plan\\u2019s goals and decisions'; + reviewPlan.addEventListener('click', () => action('review-plan', null, 'Starting /iterator-review-plan')); + bar.appendChild(reviewPlan); + } + if(D.stage==='retirable'){ + const retire = document.createElement('button'); + retire.className = 'act primary-act'; + retire.textContent = 'Retire plan'; + retire.title = 'Condense the finished plan into a decisions/ memory and archive its features'; + confirmButton(retire, 'Retires the plan \\u2014 click again', () => + action('retire', null, 'Starting plan retirement')); + bar.appendChild(retire); + } + const cancelPlan = document.createElement('button'); + cancelPlan.className = 'act danger'; + cancelPlan.textContent = 'Cancel plan'; + const dirtyWarn = (D.dirty && D.dirty.count) + ? D.dirty.count + ' uncommitted file' + (D.dirty.count!==1?'s':'') + ' + ' + : ''; + cancelPlan.title = 'Abandon this plan: archive it and DELETE its branch/worktree' + + (dirtyWarn ? ' \\u2014 \\u26a0 ' + dirtyWarn + 'unmerged commits will be lost' : ''); + confirmButton(cancelPlan, '\\u26a0 Deletes ' + dirtyWarn + 'branch \\u2014 click again', () => + action('cancel-plan', null, 'Cancelling plan')); + bar.appendChild(cancelPlan); + // Auto mode: once the feature set is approved (pending features exist), the // whole test → implement → review loop can run agent-driven. const readyWave = Array.isArray(D.readyWave) ? D.readyWave : []; diff --git a/lib/views/planning.mjs b/lib/views/planning.mjs index 7b9c314..be494d4 100644 --- a/lib/views/planning.mjs +++ b/lib/views/planning.mjs @@ -183,65 +183,17 @@ function render(){ return; } - // Plan bar: title, status, progress count, and the stage-driven lifecycle - // buttons. Execution controls (auto mode, Test/Implement/Review) live on - // the Work surface. - const done = (D.progress&&D.progress.done)||0, total = (D.progress&&D.progress.total)||CH.length; - const bar = document.createElement('div'); - bar.className = 'planbar'; - bar.innerHTML = ''+esc(D.plan.title||'Plan')+''+ - ''+esc(D.plan.status||'draft')+''+ - (total - ? ''+done+' / '+total+' features done' - : 'not broken into features yet'); - const revise = document.createElement('button'); - revise.className='act'; revise.textContent='Revise plan'; - revise.addEventListener('click', () => action('plan', null, 'Starting /iterator-plan')); - bar.appendChild(revise); - // Before featuring, this button IS the continue action — make it read and - // look like one instead of a "Re-feature" that has nothing to redo. - const refeature = document.createElement('button'); - refeature.className = D.stage==='needs-features' ? 'act primary-act' : 'act'; - refeature.textContent = D.stage==='needs-features' ? 'Feature the plan' : 'Re-feature'; - refeature.title = D.stage==='needs-features' - ? 'Next step: break the approved plan into small, dependency-ordered features (/iterator-feature)' - : 'Redraw the feature set from the plan (/iterator-feature)'; - refeature.addEventListener('click', () => action('feature', null, 'Starting /iterator-feature')); - bar.appendChild(refeature); - // Everything landed → the whole-plan review; every feature done → retire. - // Both derive from the server-computed stage, never recomputed here. - if(D.stage==='awaiting-plan-review' || D.stage==='retirable'){ - const rp = document.createElement('button'); - rp.className = 'act primary-act'; - rp.textContent = D.plan.planReviewed ? 'Re-review plan' : 'Review plan'; - rp.title = D.plan.planReviewed - ? 'Plan reviewed '+D.plan.planReviewed+' \\u2014 run the whole-plan review again' - : 'Review all changes and commits against the plan\\u2019s goals and decisions'; - rp.addEventListener('click', () => action('review-plan', null, 'Starting /iterator-review-plan')); - bar.appendChild(rp); - } - if(D.stage==='retirable'){ - const retire = document.createElement('button'); - retire.className='act primary-act'; retire.textContent='Retire plan'; - retire.title='Condense the finished plan into a decisions/ memory and archive its features'; - confirmButton(retire, 'Retires the plan \\u2014 click again', () => - action('retire', null, 'Starting plan retirement')); - bar.appendChild(retire); - } - // Cancel: abandon the plan entirely — archives the bundle side and deletes - // the plan branch/worktree, so the armed label spells out what is at stake. - const cancelPlan = document.createElement('button'); - cancelPlan.className = 'act danger'; - cancelPlan.textContent = 'Cancel plan'; - const dirtyWarn = (D.dirty && D.dirty.count) - ? D.dirty.count + ' uncommitted file' + (D.dirty.count!==1?'s':'') + ' + ' - : ''; - cancelPlan.title = 'Abandon this plan: archive it and DELETE its branch/worktree' - + (dirtyWarn ? ' \\u2014 \\u26a0 ' + dirtyWarn + 'unmerged commits will be lost' : ''); - confirmButton(cancelPlan, '\\u26a0 Deletes ' + dirtyWarn + 'branch \\u2014 click again', () => - action('cancel-plan', null, 'Cancelling plan')); - bar.appendChild(cancelPlan); - w.appendChild(bar); + // Planning is a staging surface once a plan exists. Active lifecycle, + // progress, and feature controls stay together on Work. + const hero = document.createElement('div'); + hero.className = 'hero'; + hero.innerHTML = '

Planning is staged

'+esc(D.plan.title||'This plan')+' is active on Work.
Use Work for progress, feature actions, and plan lifecycle controls.

'; + const open = document.createElement('button'); + open.className = 'act primary-act'; + open.textContent = 'Open Work'; + open.addEventListener('click', () => action('hub', null, 'Opening Work')); + hero.appendChild(open); + w.appendChild(hero); renderBacklog(w); renderRetired(w); } diff --git a/memory/features/active-plan-workspace.md b/memory/features/active-plan-workspace.md index dd2f20a..c5f34bb 100644 --- a/memory/features/active-plan-workspace.md +++ b/memory/features/active-plan-workspace.md @@ -2,13 +2,13 @@ type: Feature title: Active plan workspace description: Make Work the complete home for an active plan and land there after plan or feature approval, while Planning stays focused on future work and archives. -status: pending +status: implemented size: medium depends_on: [fresh-implementation-session] files: ["lib/views/hub.mjs", "lib/views/planning.mjs", "extensions/iterator.js", "test/ui.test.mjs", "test/client-js-parse.test.mjs", "test/session-server.test.mjs"] memories: [pitfalls/cancel-now-after-grace-timer, pitfalls/client-js-template-literal-escaping, architecture/package-and-skill-layout, architecture/workflow-state-ownership, patterns/safe-browser-rendering, decisions/backlog-planning-and-feature-waves, decisions/consume-accepted-backlog-ideas, decisions/iterator-dashboard-feature-workflow] conflicts: "[{\"decision\":\"decisions/review-navigation-and-work-context\",\"note\":\"The accepted plan intentionally moves plan lifecycle controls from Planning to Work, revising the recorded split where Planning owned those actions.\"}]" -timestamp: "2026-07-20T13:50:52.261Z" +timestamp: "2026-07-20T14:18:42.409Z" tags: [] --- diff --git a/memory/features/fresh-implementation-session.md b/memory/features/fresh-implementation-session.md index 931a239..0539f46 100644 --- a/memory/features/fresh-implementation-session.md +++ b/memory/features/fresh-implementation-session.md @@ -7,7 +7,7 @@ size: large depends_on: [] files: ["extensions/iterator.js", "lib/pi-tools.mjs", "skills/iterator-implement/SKILL.md", "test/pi-tools.test.mjs", "test/extension-fresh-session.test.mjs"] memories: [architecture/package-and-skill-layout, decisions/backlog-planning-and-feature-waves, decisions/iterator-dashboard-feature-workflow, decisions/manual-role-models-and-runtime-reset, decisions/memory-relevance-usage-and-dashboard-recovery, decisions/parallel-feature-waves-and-consolidated-review, decisions/polish-dashboard-and-multi-agent-workflows, decisions/powerline-shows-sandbox-ui-port] -timestamp: "2026-07-20T14:06:33.938Z" +timestamp: "2026-07-20T14:06:39.909Z" tags: [] commits: - sha: e7a69a4ee3dd962817c766d39109012284a0e530 @@ -48,4 +48,5 @@ Every Pi implementation entrypoint and session lifecycle; mistakes can lose auto # Review ## 2026-07-20 +* **Approved** _(agent review: openai-codex/gpt-5.6-sol)_ — Approved: replacement sessions now restore the prior step model before switching, apply the implementer role in the fresh turn, preserve ready-wave and autoSteps state, and retain interruption safety on reload; targeted lifecycle helpers and the full suite cover the corrected paths. * **Needs changes** _(agent review: openai-codex/gpt-5.6-sol)_ — Fresh-session auto/wave implementations no longer receive the configured implementer role: the old driver now skips applyRole, while the replacement turn's before_agent_start still refuses pendingRole whenever state.mode is auto or featureWave is set. Restore any prior step role before ctx.newSession, mark the intentional handoff in the new extension instance, and apply/restore the implementer role exactly once there. Also carry autoSteps through the handoff so replacing the session for every feature cannot reset and bypass AUTO_MAX_STEPS. Add lifecycle assertions for auto and ready-wave handoffs (the current extension test is skipped when typebox is absent). diff --git a/memory/log.md b/memory/log.md index ec8f33a..6dc65fd 100644 --- a/memory/log.md +++ b/memory/log.md @@ -1,6 +1,8 @@ # iterator update log ## 2026-07-20 +* **Implementation**: Committed feature(active-plan-workspace) on branch iterator/safe-role-model-handoff; awaiting review. +* **Review**: Reviewed [Fresh implementation session](/features/fresh-implementation-session.md); approved (agent). * **Review**: Accepted [Fresh implementation session](/features/fresh-implementation-session.md) (committed as feature(fresh-implementation-session)). * **Implementation**: Committed feature(fresh-implementation-session) on branch iterator/safe-role-model-handoff; awaiting review. * **Review**: Reviewed [Fresh implementation session](/features/fresh-implementation-session.md); changes (agent). diff --git a/memory/state.md b/memory/state.md index 380637d..5cb8e3b 100644 --- a/memory/state.md +++ b/memory/state.md @@ -4,11 +4,11 @@ title: Runtime state description: Machine-owned iterator flow state — never hand-edited. mode: auto paused: false -phase: reviewing +phase: implementing active_feature: fresh-implementation-session strikes: "{\"fresh-implementation-session\":1}" escalation: null -timestamp: 2026-07-20T14:05:54.015Z +timestamp: 2026-07-20T14:15:13.083Z --- Runtime flow state; read via gather, written only by the state op. diff --git a/memory/usage.md b/memory/usage.md index 16fb336..dc0cfe3 100644 --- a/memory/usage.md +++ b/memory/usage.md @@ -2,9 +2,9 @@ type: Usage title: Token usage description: Per-step model/token ledger and optional project-owned pricing for the active plan — written only by the usage op. -totals: "{\"steps\":{\"hub\":{\"openai-codex/gpt-5.6-terra\":{\"input\":243028,\"output\":842,\"cacheRead\":811520,\"cacheWrite\":0,\"turns\":9}},\"plan\":{\"openai-codex/gpt-5.6-sol\":{\"input\":118306,\"output\":8575,\"cacheRead\":3610112,\"cacheWrite\":0,\"turns\":21}},\"implement\":{\"openai-codex/gpt-5.6-terra\":{\"input\":1086985,\"output\":12201,\"cacheRead\":6675968,\"cacheWrite\":0,\"turns\":32},\"openai-codex/gpt-5.6-sol\":{\"input\":1191326,\"output\":7856,\"cacheRead\":6515712,\"cacheWrite\":0,\"turns\":26}},\"review\":{\"openai-codex/gpt-5.6-sol\":{\"input\":588476,\"output\":2395,\"cacheRead\":1334784,\"cacheWrite\":0,\"turns\":7}}},\"features\":{\"retire-plan\":{\"input\":243028,\"output\":842,\"cacheRead\":811520,\"cacheWrite\":0,\"turns\":9},\"fresh-implementation-session\":{\"input\":2866787,\"output\":22452,\"cacheRead\":14526464,\"cacheWrite\":0,\"turns\":65}},\"featureModels\":{\"retire-plan\":{\"openai-codex/gpt-5.6-terra\":{\"input\":243028,\"output\":842,\"cacheRead\":811520,\"cacheWrite\":0,\"turns\":9}},\"fresh-implementation-session\":{\"openai-codex/gpt-5.6-terra\":{\"input\":1086985,\"output\":12201,\"cacheRead\":6675968,\"cacheWrite\":0,\"turns\":32},\"openai-codex/gpt-5.6-sol\":{\"input\":1779802,\"output\":10251,\"cacheRead\":7850496,\"cacheWrite\":0,\"turns\":33}}}}" +totals: "{\"steps\":{\"hub\":{\"openai-codex/gpt-5.6-terra\":{\"input\":243028,\"output\":842,\"cacheRead\":811520,\"cacheWrite\":0,\"turns\":9}},\"plan\":{\"openai-codex/gpt-5.6-sol\":{\"input\":118306,\"output\":8575,\"cacheRead\":3610112,\"cacheWrite\":0,\"turns\":21}},\"implement\":{\"openai-codex/gpt-5.6-terra\":{\"input\":1086985,\"output\":12201,\"cacheRead\":6675968,\"cacheWrite\":0,\"turns\":32},\"openai-codex/gpt-5.6-sol\":{\"input\":1191326,\"output\":7856,\"cacheRead\":6515712,\"cacheWrite\":0,\"turns\":26}},\"review\":{\"openai-codex/gpt-5.6-sol\":{\"input\":638064,\"output\":3474,\"cacheRead\":3515392,\"cacheWrite\":0,\"turns\":14}}},\"features\":{\"retire-plan\":{\"input\":243028,\"output\":842,\"cacheRead\":811520,\"cacheWrite\":0,\"turns\":9},\"fresh-implementation-session\":{\"input\":2916375,\"output\":23531,\"cacheRead\":16707072,\"cacheWrite\":0,\"turns\":72}},\"featureModels\":{\"retire-plan\":{\"openai-codex/gpt-5.6-terra\":{\"input\":243028,\"output\":842,\"cacheRead\":811520,\"cacheWrite\":0,\"turns\":9}},\"fresh-implementation-session\":{\"openai-codex/gpt-5.6-terra\":{\"input\":1086985,\"output\":12201,\"cacheRead\":6675968,\"cacheWrite\":0,\"turns\":32},\"openai-codex/gpt-5.6-sol\":{\"input\":1829390,\"output\":11330,\"cacheRead\":10031104,\"cacheWrite\":0,\"turns\":40}}}}" prices: "{}" -timestamp: 2026-07-20T14:05:53.791Z +timestamp: 2026-07-20T14:07:08.091Z --- # Usage @@ -32,13 +32,13 @@ timestamp: 2026-07-20T14:05:53.791Z | model | input | output | cache read | cache write | turns | cost | | --- | ---: | ---: | ---: | ---: | ---: | ---: | -| openai-codex/gpt-5.6-sol | 588476 | 2395 | 1334784 | 0 | 7 | — | +| openai-codex/gpt-5.6-sol | 638064 | 3474 | 3515392 | 0 | 14 | — | ## Per feature | feature | input | output | cache read | cache write | turns | cost | | --- | ---: | ---: | ---: | ---: | ---: | ---: | | retire-plan | 243028 | 842 | 811520 | 0 | 9 | — | -| fresh-implementation-session | 2866787 | 22452 | 14526464 | 0 | 65 | — | +| fresh-implementation-session | 2916375 | 23531 | 16707072 | 0 | 72 | — | -Total: 3228121 in / 31869 out / 18948096 cache-read / 0 cache-write over 95 turns. Cost unavailable: add every used model rate. +Total: 3277709 in / 32948 out / 21128704 cache-read / 0 cache-write over 102 turns. Cost unavailable: add every used model rate. diff --git a/skills/iterator/lib/views/hub.mjs b/skills/iterator/lib/views/hub.mjs index 90dad21..f6f6155 100644 --- a/skills/iterator/lib/views/hub.mjs +++ b/skills/iterator/lib/views/hub.mjs @@ -110,6 +110,52 @@ function render(){ ? ''+done+' / '+total+' features done'+ '
' : 'not broken into features yet'); + // Active-plan lifecycle belongs with its progress and feature work. Every + // condition is the server-derived stage; this view never infers it. + const revise = document.createElement('button'); + revise.className = 'act'; + revise.textContent = 'Revise plan'; + revise.addEventListener('click', () => action('plan', null, 'Starting /iterator-plan')); + bar.appendChild(revise); + const refeature = document.createElement('button'); + refeature.className = D.stage==='needs-features' ? 'act primary-act' : 'act'; + refeature.textContent = D.stage==='needs-features' ? 'Feature the plan' : 'Re-feature'; + refeature.title = D.stage==='needs-features' + ? 'Next step: break the approved plan into small, dependency-ordered features (/iterator-feature)' + : 'Redraw the feature set from the plan (/iterator-feature)'; + refeature.addEventListener('click', () => action('feature', null, 'Starting /iterator-feature')); + bar.appendChild(refeature); + if(D.stage==='awaiting-plan-review' || D.stage==='retirable'){ + const reviewPlan = document.createElement('button'); + reviewPlan.className = 'act primary-act'; + reviewPlan.textContent = D.plan.planReviewed ? 'Re-review plan' : 'Review plan'; + reviewPlan.title = D.plan.planReviewed + ? 'Plan reviewed '+D.plan.planReviewed+' \\u2014 run the whole-plan review again' + : 'Review all changes and commits against the plan\\u2019s goals and decisions'; + reviewPlan.addEventListener('click', () => action('review-plan', null, 'Starting /iterator-review-plan')); + bar.appendChild(reviewPlan); + } + if(D.stage==='retirable'){ + const retire = document.createElement('button'); + retire.className = 'act primary-act'; + retire.textContent = 'Retire plan'; + retire.title = 'Condense the finished plan into a decisions/ memory and archive its features'; + confirmButton(retire, 'Retires the plan \\u2014 click again', () => + action('retire', null, 'Starting plan retirement')); + bar.appendChild(retire); + } + const cancelPlan = document.createElement('button'); + cancelPlan.className = 'act danger'; + cancelPlan.textContent = 'Cancel plan'; + const dirtyWarn = (D.dirty && D.dirty.count) + ? D.dirty.count + ' uncommitted file' + (D.dirty.count!==1?'s':'') + ' + ' + : ''; + cancelPlan.title = 'Abandon this plan: archive it and DELETE its branch/worktree' + + (dirtyWarn ? ' \\u2014 \\u26a0 ' + dirtyWarn + 'unmerged commits will be lost' : ''); + confirmButton(cancelPlan, '\\u26a0 Deletes ' + dirtyWarn + 'branch \\u2014 click again', () => + action('cancel-plan', null, 'Cancelling plan')); + bar.appendChild(cancelPlan); + // Auto mode: once the feature set is approved (pending features exist), the // whole test → implement → review loop can run agent-driven. const readyWave = Array.isArray(D.readyWave) ? D.readyWave : []; diff --git a/skills/iterator/lib/views/planning.mjs b/skills/iterator/lib/views/planning.mjs index 7b9c314..be494d4 100644 --- a/skills/iterator/lib/views/planning.mjs +++ b/skills/iterator/lib/views/planning.mjs @@ -183,65 +183,17 @@ function render(){ return; } - // Plan bar: title, status, progress count, and the stage-driven lifecycle - // buttons. Execution controls (auto mode, Test/Implement/Review) live on - // the Work surface. - const done = (D.progress&&D.progress.done)||0, total = (D.progress&&D.progress.total)||CH.length; - const bar = document.createElement('div'); - bar.className = 'planbar'; - bar.innerHTML = ''+esc(D.plan.title||'Plan')+''+ - ''+esc(D.plan.status||'draft')+''+ - (total - ? ''+done+' / '+total+' features done' - : 'not broken into features yet'); - const revise = document.createElement('button'); - revise.className='act'; revise.textContent='Revise plan'; - revise.addEventListener('click', () => action('plan', null, 'Starting /iterator-plan')); - bar.appendChild(revise); - // Before featuring, this button IS the continue action — make it read and - // look like one instead of a "Re-feature" that has nothing to redo. - const refeature = document.createElement('button'); - refeature.className = D.stage==='needs-features' ? 'act primary-act' : 'act'; - refeature.textContent = D.stage==='needs-features' ? 'Feature the plan' : 'Re-feature'; - refeature.title = D.stage==='needs-features' - ? 'Next step: break the approved plan into small, dependency-ordered features (/iterator-feature)' - : 'Redraw the feature set from the plan (/iterator-feature)'; - refeature.addEventListener('click', () => action('feature', null, 'Starting /iterator-feature')); - bar.appendChild(refeature); - // Everything landed → the whole-plan review; every feature done → retire. - // Both derive from the server-computed stage, never recomputed here. - if(D.stage==='awaiting-plan-review' || D.stage==='retirable'){ - const rp = document.createElement('button'); - rp.className = 'act primary-act'; - rp.textContent = D.plan.planReviewed ? 'Re-review plan' : 'Review plan'; - rp.title = D.plan.planReviewed - ? 'Plan reviewed '+D.plan.planReviewed+' \\u2014 run the whole-plan review again' - : 'Review all changes and commits against the plan\\u2019s goals and decisions'; - rp.addEventListener('click', () => action('review-plan', null, 'Starting /iterator-review-plan')); - bar.appendChild(rp); - } - if(D.stage==='retirable'){ - const retire = document.createElement('button'); - retire.className='act primary-act'; retire.textContent='Retire plan'; - retire.title='Condense the finished plan into a decisions/ memory and archive its features'; - confirmButton(retire, 'Retires the plan \\u2014 click again', () => - action('retire', null, 'Starting plan retirement')); - bar.appendChild(retire); - } - // Cancel: abandon the plan entirely — archives the bundle side and deletes - // the plan branch/worktree, so the armed label spells out what is at stake. - const cancelPlan = document.createElement('button'); - cancelPlan.className = 'act danger'; - cancelPlan.textContent = 'Cancel plan'; - const dirtyWarn = (D.dirty && D.dirty.count) - ? D.dirty.count + ' uncommitted file' + (D.dirty.count!==1?'s':'') + ' + ' - : ''; - cancelPlan.title = 'Abandon this plan: archive it and DELETE its branch/worktree' - + (dirtyWarn ? ' \\u2014 \\u26a0 ' + dirtyWarn + 'unmerged commits will be lost' : ''); - confirmButton(cancelPlan, '\\u26a0 Deletes ' + dirtyWarn + 'branch \\u2014 click again', () => - action('cancel-plan', null, 'Cancelling plan')); - bar.appendChild(cancelPlan); - w.appendChild(bar); + // Planning is a staging surface once a plan exists. Active lifecycle, + // progress, and feature controls stay together on Work. + const hero = document.createElement('div'); + hero.className = 'hero'; + hero.innerHTML = '

Planning is staged

'+esc(D.plan.title||'This plan')+' is active on Work.
Use Work for progress, feature actions, and plan lifecycle controls.

'; + const open = document.createElement('button'); + open.className = 'act primary-act'; + open.textContent = 'Open Work'; + open.addEventListener('click', () => action('hub', null, 'Opening Work')); + hero.appendChild(open); + w.appendChild(hero); renderBacklog(w); renderRetired(w); } diff --git a/test/session-server.test.mjs b/test/session-server.test.mjs index 4d00ebe..b15d51b 100644 --- a/test/session-server.test.mjs +++ b/test/session-server.test.mjs @@ -577,6 +577,23 @@ test("showView can intentionally activate Planning for the startup landing page" } }); +test("showView can intentionally activate Work after an approved transition", async () => { + const { session, origin } = await startSession(); + try { + session.showView({ + step: "hub", + render: () => viewHtml("ACTIVE-WORK"), + activate: true, + }); + const sse = await firstSseEvent(origin); + assert.equal(sse.event, "view"); + assert.equal(sse.data.tab, "work"); + assert.match(await (await fetch(origin + "/")).text(), /let tab = "work"/); + } finally { + await session.stop(); + } +}); + test("tabs: steps render into their tab; inactive-tab refreshes are stored silently", async () => { const { session, origin } = await startSession(); try { diff --git a/test/ui.test.mjs b/test/ui.test.mjs index 65eeb48..5567a59 100644 --- a/test/ui.test.mjs +++ b/test/ui.test.mjs @@ -446,15 +446,15 @@ test("hub gates Implement/Review on status and renders escalation + review-plan assert.match(html, /Review all/); assert.match(html, /action\('review-all'/); assert.match(html, /Array\.isArray\(D\.reviewWave\)/); - // Active feature context and management live on Work. + // Active feature and plan lifecycle management live on Work. assert.match(html, /renderGraphInto/); assert.match(html, /action\('cancel-feature'/); - // Plan-lifecycle controls live on the Planning surface, not Work. - assert.doesNotMatch(html, /action\('review-plan'/); - assert.doesNotMatch(html, /Retires the plan/); + assert.match(html, /action\('review-plan'/); + assert.match(html, /Retires the plan/); + assert.match(html, /action\('cancel-plan'/); }); -test("planning drives plan-lifecycle controls from the server-derived stage", async () => { +test("planning keeps active work staged while Work drives lifecycle controls", async () => { const { render: planning } = await import("../lib/views/planning.mjs"); const html = planning({ step: "planning", @@ -496,15 +496,15 @@ test("planning drives plan-lifecycle controls from the server-derived stage", as ], backlog: [], }); - // Lifecycle buttons key off the server-derived stage. - assert.match(html, /D\.stage==='retirable'/); - assert.match(html, /action\('review-plan'/); - assert.match(html, /Retires the plan/); - assert.match(html, /action\('cancel-plan'/); - // Active features and their dependency graph live on Work, not Planning. + assert.match(html, /Planning is staged/); + assert.match(html, /Open Work/); + assert.match(html, /action\('hub'/); + // Active lifecycle, features, and execution controls are absent from Planning. + assert.doesNotMatch(html, /action\('review-plan'/); + assert.doesNotMatch(html, /Retires the plan/); + assert.doesNotMatch(html, /action\('cancel-plan'/); assert.doesNotMatch(html, /action\('cancel-feature'/); assert.doesNotMatch(html, /renderGraphInto/); - // The execution controls live on Work, not here. assert.doesNotMatch(html, /action\('implement'/); assert.doesNotMatch(html, /auto-implement/); // Retired-plan browsing remains a planning concern. From 614e983bcc5cde9363b168d46d3424a9d672b75d Mon Sep 17 00:00:00 2001 From: Christoph Maier Date: Mon, 20 Jul 2026 16:18:42 +0200 Subject: [PATCH 10/42] chore(iterator): record feature commit --- memory/features/active-plan-workspace.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/memory/features/active-plan-workspace.md b/memory/features/active-plan-workspace.md index c5f34bb..2eee8e9 100644 --- a/memory/features/active-plan-workspace.md +++ b/memory/features/active-plan-workspace.md @@ -8,8 +8,12 @@ depends_on: [fresh-implementation-session] files: ["lib/views/hub.mjs", "lib/views/planning.mjs", "extensions/iterator.js", "test/ui.test.mjs", "test/client-js-parse.test.mjs", "test/session-server.test.mjs"] memories: [pitfalls/cancel-now-after-grace-timer, pitfalls/client-js-template-literal-escaping, architecture/package-and-skill-layout, architecture/workflow-state-ownership, patterns/safe-browser-rendering, decisions/backlog-planning-and-feature-waves, decisions/consume-accepted-backlog-ideas, decisions/iterator-dashboard-feature-workflow] conflicts: "[{\"decision\":\"decisions/review-navigation-and-work-context\",\"note\":\"The accepted plan intentionally moves plan lifecycle controls from Planning to Work, revising the recorded split where Planning owned those actions.\"}]" -timestamp: "2026-07-20T14:18:42.409Z" +timestamp: "2026-07-20T14:18:42.491Z" tags: [] +commits: + - sha: c5f6915bf82dc6914b8a02a707422a0c67b1a26a + kind: implement + date: 2026-07-20 --- # Implementation notes From 6bcce8b8e1097d9135c7644c2cb44edc719841f7 Mon Sep 17 00:00:00 2001 From: Christoph Maier Date: Mon, 20 Jul 2026 16:23:31 +0200 Subject: [PATCH 11/42] feature(active-plan-workspace): Clarify Work ownership and verify approval landing Feature: active-plan-workspace --- lib/views/hub.mjs | 16 ++++---- memory/decisions/index.md | 2 +- .../review-navigation-and-work-context.md | 20 ++++------ memory/features/active-plan-workspace.md | 8 +++- memory/log.md | 3 ++ memory/state.md | 6 +-- memory/usage.md | 11 ++--- skills/iterator/lib/views/hub.mjs | 16 ++++---- test/extension-work-activation.test.mjs | 40 +++++++++++++++++++ 9 files changed, 86 insertions(+), 36 deletions(-) create mode 100644 test/extension-work-activation.test.mjs diff --git a/lib/views/hub.mjs b/lib/views/hub.mjs index f6f6155..0992d66 100644 --- a/lib/views/hub.mjs +++ b/lib/views/hub.mjs @@ -2,9 +2,10 @@ * iterator: Work dashboard UI on the shared shell (../ui.mjs, * ../server.mjs). The execution surface of the flow: the active plan's * progress, dependency graph, and feature cards with their Test / Implement / - * Review actions. Planning owns backlog and plan-lifecycle controls; active - * plan context and feature management live here. Both surfaces render from - * the same gather payload so they can never disagree about state. + * Review actions. Work owns active-plan progress, lifecycle controls, and + * feature management; Planning owns backlog, future planning, and archives. + * Both surfaces render from the same gather payload so they cannot disagree + * about state. * * input: { step:"hub", branch, * plan: { title, status } | null, // null = no bundle yet @@ -19,8 +20,9 @@ * conflicts } ] } // # of flagged decision conflicts * retired: [ { name, title, created } ] // archived plans, newest first * output: one JSON line to stdout — - * { type:"action", action:"planning"|"test"|"implement"|"review"|"review-all"|"implement-wave"|"auto-implement" - * |"cancel-feature"|"escalation-restart"|"escalation-guide", + * { type:"action", action:"planning"|"plan"|"feature"|"test"|"implement"|"review"|"review-all" + * |"review-plan"|"retire"|"implement-wave"|"auto-implement"|"cancel-plan"|"cancel-feature" + * |"escalation-restart"|"escalation-guide", * feature:""|null, * prompt:""|null } // escalation-guide only * plus the shared { type:"cancel" } / { type:"timeout" }. @@ -99,8 +101,8 @@ function render(){ w.appendChild(box); } - // Plan bar: progress + the execution controls. Lifecycle buttons (revise, - // feature, review-plan, retire, cancel) live on the Planning surface. + // Work keeps active-plan progress, lifecycle, and execution controls in one + // plan bar; Planning remains a staging and archive surface. const done = (D.progress&&D.progress.done)||0, total = (D.progress&&D.progress.total)||CH.length; const bar = document.createElement('div'); bar.className = 'planbar'; diff --git a/memory/decisions/index.md b/memory/decisions/index.md index 7da6b2f..7b25804 100644 --- a/memory/decisions/index.md +++ b/memory/decisions/index.md @@ -10,8 +10,8 @@ Durable product and implementation choices agents should preserve. * [Polish dashboard and multi-agent workflows](/decisions/polish-dashboard-and-multi-agent-workflows.md) - Dashboard polish and workflow refinements that clarify project context, constrain settings to usable models, and support deterministic Claude Code feature execution. * [Powerline shows the sandbox-published UI port](/decisions/powerline-shows-sandbox-ui-port.md) - The footer trails a ui:PORT segment resolved from ITERATOR_DISPLAY_PORT, falling back to ~/.pisbx-env because sbx run never sources it into pi's environment. * [Return to Work when Settings closes](/decisions/settings-close-returns-to-work.md) - Idle Settings close events restore the refreshed Work hub without changing the settings persistence path. -* [Review navigation and Work context](/decisions/review-navigation-and-work-context.md) - Keep interactive reviews stable across dashboard navigation, make Work the active feature surface, and simplify review controls without weakening the review gate. * [Safely restore configured Iterator role models](/decisions/safe-role-model-restoration.md) - Only successfully switched role models are restored, so failed provider changes cannot corrupt the active session credentials. * [Sync shared libs into droppable skills](/decisions/synced-droppable-skill-libs.md) - Shared code is developed in root lib/ and copied into skill folders so skills work when installed together or copied manually. * [Unify Iterator dashboard and feature workflow](/decisions/iterator-dashboard-feature-workflow.md) - Dashboard workflows keep backlog candidates separate from active work until selected candidates are consumed by approved plan creation. * [Use an OKF markdown bundle in target repos](/decisions/okf-markdown-bundle.md) - Project memory is stored as markdown plus YAML frontmatter in each target repo instead of in an external database. +* [Work owns active plan context and lifecycle](/decisions/review-navigation-and-work-context.md) - Keep active-plan progress, execution, and lifecycle controls on Work while Planning is reserved for staged future work and archives. diff --git a/memory/decisions/review-navigation-and-work-context.md b/memory/decisions/review-navigation-and-work-context.md index 98b5659..7c334f0 100644 --- a/memory/decisions/review-navigation-and-work-context.md +++ b/memory/decisions/review-navigation-and-work-context.md @@ -1,28 +1,24 @@ --- type: Decision -title: Review navigation and Work context -description: Keep interactive reviews stable across dashboard navigation, make Work the active feature surface, and simplify review controls without weakening the review gate. +title: Work owns active plan context and lifecycle +description: Keep active-plan progress, execution, and lifecycle controls on Work while Planning is reserved for staged future work and archives. status: accepted -date: 2026-07-17 +date: 2026-07-20 tags: [workflow, review, dashboard, navigation] -files: ["lib/session-server.mjs", "lib/ui.mjs", "lib/views/hub.mjs", "lib/views/planning.mjs", "lib/views/graph.mjs", "lib/views/review.mjs", "test/session-server.test.mjs", "test/ui.test.mjs", "test/client-js-parse.test.mjs"] -timestamp: 2026-07-17T16:38:22.809Z +files: ["lib/session-server.mjs", "lib/ui.mjs", "lib/views/hub.mjs", "lib/views/planning.mjs", "lib/views/graph.mjs", "lib/views/review.mjs", "test/session-server.test.mjs", "test/ui.test.mjs", "test/client-js-parse.test.mjs", "extensions/iterator.js"] +timestamp: "2026-07-20T14:19:10.566Z" --- ## Decision The persistent session shell, not its replaceable iframe views, owns unload cancellation. Switching between Planning and Work must preserve a pending review round and its single eventual result; an explicit cancel still pre-empts any pending reload-grace timer. -Work is the home for an active plan's progress, dependency graph, feature cards, execution controls, and feature cancellation. Planning remains focused on backlog collection and plan lifecycle actions. Both surfaces render the same server-derived gather data and never reconstruct readiness or lifecycle state locally. +Work is the home for every active-plan concern: progress, dependency graph, feature cards, execution controls, revise/re-feature, whole-plan review, retirement, plan cancellation, and feature cancellation. Planning is the staging surface for future plans, backlog collection, and retired-plan browsing; when a plan is active it directs users to Work instead of duplicating lifecycle controls. Both surfaces render the same server-derived gather data and never reconstruct readiness or lifecycle state locally. + +Approved plans and accepted feature sets intentionally activate Work. Ordinary refreshes preserve the user's selected tab, so dashboard navigation and pending rounds remain stable. The review sidebar and detail headings wrap long feature names rather than clipping them. The obsolete lower-right Feedback panel and JSON-preview bookkeeping are removed; feature verdicts, notes, and line comments continue to drive the existing header submission flow. ## Constraints Dashboard navigation must not weaken the single-model-flow guard or backlog-write allowance. Changes to root `lib/` are synchronized into shipped skill copies, and inline view scripts retain parse coverage. - -# Retired plan - -Condensed from plan "Keep active review stable and clarify Planning versus Work" (3 features, archived under /features/archive/2026-07-17-review-navigation-and-work-context/). - -Token usage: 817664 in / 25689 out / 13951488 cache-read / 0 cache-write over 113 turns (per-step breakdown in the archived usage.md). diff --git a/memory/features/active-plan-workspace.md b/memory/features/active-plan-workspace.md index 2eee8e9..77a18e5 100644 --- a/memory/features/active-plan-workspace.md +++ b/memory/features/active-plan-workspace.md @@ -8,12 +8,13 @@ depends_on: [fresh-implementation-session] files: ["lib/views/hub.mjs", "lib/views/planning.mjs", "extensions/iterator.js", "test/ui.test.mjs", "test/client-js-parse.test.mjs", "test/session-server.test.mjs"] memories: [pitfalls/cancel-now-after-grace-timer, pitfalls/client-js-template-literal-escaping, architecture/package-and-skill-layout, architecture/workflow-state-ownership, patterns/safe-browser-rendering, decisions/backlog-planning-and-feature-waves, decisions/consume-accepted-backlog-ideas, decisions/iterator-dashboard-feature-workflow] conflicts: "[{\"decision\":\"decisions/review-navigation-and-work-context\",\"note\":\"The accepted plan intentionally moves plan lifecycle controls from Planning to Work, revising the recorded split where Planning owned those actions.\"}]" -timestamp: "2026-07-20T14:18:42.491Z" +timestamp: "2026-07-20T14:23:31.442Z" tags: [] commits: - sha: c5f6915bf82dc6914b8a02a707422a0c67b1a26a kind: implement date: 2026-07-20 +reviewed: 2026-07-20 --- # Implementation notes @@ -45,3 +46,8 @@ Planning/Work navigation, all active plan lifecycle actions, and post-approval l # Decision conflicts * [decisions/review-navigation-and-work-context](/decisions/review-navigation-and-work-context.md) — The accepted plan intentionally moves plan lifecycle controls from Planning to Work, revising the recorded split where Planning owned those actions. + +# Review + +## 2026-07-20 +* **Needs changes** _(agent review: openai/gpt-5.2)_ — Update lib/views/hub.mjs’s module contract and plan-bar comments (and the synced skill copy) to reflect that Work now owns plan lifecycle actions; they currently still state that Planning owns those controls and omit plan/feature/review-plan/retire/cancel-plan from the output contract. Add extension-level coverage proving plan approval and accepted feature breakdown call refreshHub with activateWork:true, rather than only testing session.showView’s generic activate option. diff --git a/memory/log.md b/memory/log.md index 6dc65fd..500e5c2 100644 --- a/memory/log.md +++ b/memory/log.md @@ -2,6 +2,9 @@ ## 2026-07-20 * **Implementation**: Committed feature(active-plan-workspace) on branch iterator/safe-role-model-handoff; awaiting review. +* **Review**: Reviewed [Active plan workspace](/features/active-plan-workspace.md); changes (agent). +* **Update**: Memorized [Work owns active plan context and lifecycle](/decisions/review-navigation-and-work-context.md). +* **Implementation**: Committed feature(active-plan-workspace) on branch iterator/safe-role-model-handoff; awaiting review. * **Review**: Reviewed [Fresh implementation session](/features/fresh-implementation-session.md); approved (agent). * **Review**: Accepted [Fresh implementation session](/features/fresh-implementation-session.md) (committed as feature(fresh-implementation-session)). * **Implementation**: Committed feature(fresh-implementation-session) on branch iterator/safe-role-model-handoff; awaiting review. diff --git a/memory/state.md b/memory/state.md index 5cb8e3b..aadd3ee 100644 --- a/memory/state.md +++ b/memory/state.md @@ -5,10 +5,10 @@ description: Machine-owned iterator flow state — never hand-edited. mode: auto paused: false phase: implementing -active_feature: fresh-implementation-session -strikes: "{\"fresh-implementation-session\":1}" +active_feature: active-plan-workspace +strikes: "{\"fresh-implementation-session\":1,\"active-plan-workspace\":1}" escalation: null -timestamp: 2026-07-20T14:15:13.083Z +timestamp: 2026-07-20T14:21:45.715Z --- Runtime flow state; read via gather, written only by the state op. diff --git a/memory/usage.md b/memory/usage.md index dc0cfe3..3bc75a1 100644 --- a/memory/usage.md +++ b/memory/usage.md @@ -2,9 +2,9 @@ type: Usage title: Token usage description: Per-step model/token ledger and optional project-owned pricing for the active plan — written only by the usage op. -totals: "{\"steps\":{\"hub\":{\"openai-codex/gpt-5.6-terra\":{\"input\":243028,\"output\":842,\"cacheRead\":811520,\"cacheWrite\":0,\"turns\":9}},\"plan\":{\"openai-codex/gpt-5.6-sol\":{\"input\":118306,\"output\":8575,\"cacheRead\":3610112,\"cacheWrite\":0,\"turns\":21}},\"implement\":{\"openai-codex/gpt-5.6-terra\":{\"input\":1086985,\"output\":12201,\"cacheRead\":6675968,\"cacheWrite\":0,\"turns\":32},\"openai-codex/gpt-5.6-sol\":{\"input\":1191326,\"output\":7856,\"cacheRead\":6515712,\"cacheWrite\":0,\"turns\":26}},\"review\":{\"openai-codex/gpt-5.6-sol\":{\"input\":638064,\"output\":3474,\"cacheRead\":3515392,\"cacheWrite\":0,\"turns\":14}}},\"features\":{\"retire-plan\":{\"input\":243028,\"output\":842,\"cacheRead\":811520,\"cacheWrite\":0,\"turns\":9},\"fresh-implementation-session\":{\"input\":2916375,\"output\":23531,\"cacheRead\":16707072,\"cacheWrite\":0,\"turns\":72}},\"featureModels\":{\"retire-plan\":{\"openai-codex/gpt-5.6-terra\":{\"input\":243028,\"output\":842,\"cacheRead\":811520,\"cacheWrite\":0,\"turns\":9}},\"fresh-implementation-session\":{\"openai-codex/gpt-5.6-terra\":{\"input\":1086985,\"output\":12201,\"cacheRead\":6675968,\"cacheWrite\":0,\"turns\":32},\"openai-codex/gpt-5.6-sol\":{\"input\":1829390,\"output\":11330,\"cacheRead\":10031104,\"cacheWrite\":0,\"turns\":40}}}}" +totals: "{\"steps\":{\"hub\":{\"openai-codex/gpt-5.6-terra\":{\"input\":243028,\"output\":842,\"cacheRead\":811520,\"cacheWrite\":0,\"turns\":9}},\"plan\":{\"openai-codex/gpt-5.6-sol\":{\"input\":118306,\"output\":8575,\"cacheRead\":3610112,\"cacheWrite\":0,\"turns\":21}},\"implement\":{\"openai-codex/gpt-5.6-terra\":{\"input\":1454180,\"output\":20444,\"cacheRead\":13496832,\"cacheWrite\":0,\"turns\":53},\"openai-codex/gpt-5.6-sol\":{\"input\":1191326,\"output\":7856,\"cacheRead\":6515712,\"cacheWrite\":0,\"turns\":26}},\"review\":{\"openai-codex/gpt-5.6-sol\":{\"input\":683106,\"output\":5003,\"cacheRead\":3732480,\"cacheWrite\":0,\"turns\":20}}},\"features\":{\"retire-plan\":{\"input\":243028,\"output\":842,\"cacheRead\":811520,\"cacheWrite\":0,\"turns\":9},\"fresh-implementation-session\":{\"input\":2916375,\"output\":23531,\"cacheRead\":16707072,\"cacheWrite\":0,\"turns\":72},\"active-plan-workspace\":{\"input\":412237,\"output\":9772,\"cacheRead\":7037952,\"cacheWrite\":0,\"turns\":27}},\"featureModels\":{\"retire-plan\":{\"openai-codex/gpt-5.6-terra\":{\"input\":243028,\"output\":842,\"cacheRead\":811520,\"cacheWrite\":0,\"turns\":9}},\"fresh-implementation-session\":{\"openai-codex/gpt-5.6-terra\":{\"input\":1086985,\"output\":12201,\"cacheRead\":6675968,\"cacheWrite\":0,\"turns\":32},\"openai-codex/gpt-5.6-sol\":{\"input\":1829390,\"output\":11330,\"cacheRead\":10031104,\"cacheWrite\":0,\"turns\":40}},\"active-plan-workspace\":{\"openai-codex/gpt-5.6-terra\":{\"input\":367195,\"output\":8243,\"cacheRead\":6820864,\"cacheWrite\":0,\"turns\":21},\"openai-codex/gpt-5.6-sol\":{\"input\":45042,\"output\":1529,\"cacheRead\":217088,\"cacheWrite\":0,\"turns\":6}}}}" prices: "{}" -timestamp: 2026-07-20T14:07:08.091Z +timestamp: 2026-07-20T14:21:45.420Z --- # Usage @@ -25,14 +25,14 @@ timestamp: 2026-07-20T14:07:08.091Z | model | input | output | cache read | cache write | turns | cost | | --- | ---: | ---: | ---: | ---: | ---: | ---: | -| openai-codex/gpt-5.6-terra | 1086985 | 12201 | 6675968 | 0 | 32 | — | +| openai-codex/gpt-5.6-terra | 1454180 | 20444 | 13496832 | 0 | 53 | — | | openai-codex/gpt-5.6-sol | 1191326 | 7856 | 6515712 | 0 | 26 | — | ## review | model | input | output | cache read | cache write | turns | cost | | --- | ---: | ---: | ---: | ---: | ---: | ---: | -| openai-codex/gpt-5.6-sol | 638064 | 3474 | 3515392 | 0 | 14 | — | +| openai-codex/gpt-5.6-sol | 683106 | 5003 | 3732480 | 0 | 20 | — | ## Per feature @@ -40,5 +40,6 @@ timestamp: 2026-07-20T14:07:08.091Z | --- | ---: | ---: | ---: | ---: | ---: | ---: | | retire-plan | 243028 | 842 | 811520 | 0 | 9 | — | | fresh-implementation-session | 2916375 | 23531 | 16707072 | 0 | 72 | — | +| active-plan-workspace | 412237 | 9772 | 7037952 | 0 | 27 | — | -Total: 3277709 in / 32948 out / 21128704 cache-read / 0 cache-write over 102 turns. Cost unavailable: add every used model rate. +Total: 3689946 in / 42720 out / 28166656 cache-read / 0 cache-write over 129 turns. Cost unavailable: add every used model rate. diff --git a/skills/iterator/lib/views/hub.mjs b/skills/iterator/lib/views/hub.mjs index f6f6155..0992d66 100644 --- a/skills/iterator/lib/views/hub.mjs +++ b/skills/iterator/lib/views/hub.mjs @@ -2,9 +2,10 @@ * iterator: Work dashboard UI on the shared shell (../ui.mjs, * ../server.mjs). The execution surface of the flow: the active plan's * progress, dependency graph, and feature cards with their Test / Implement / - * Review actions. Planning owns backlog and plan-lifecycle controls; active - * plan context and feature management live here. Both surfaces render from - * the same gather payload so they can never disagree about state. + * Review actions. Work owns active-plan progress, lifecycle controls, and + * feature management; Planning owns backlog, future planning, and archives. + * Both surfaces render from the same gather payload so they cannot disagree + * about state. * * input: { step:"hub", branch, * plan: { title, status } | null, // null = no bundle yet @@ -19,8 +20,9 @@ * conflicts } ] } // # of flagged decision conflicts * retired: [ { name, title, created } ] // archived plans, newest first * output: one JSON line to stdout — - * { type:"action", action:"planning"|"test"|"implement"|"review"|"review-all"|"implement-wave"|"auto-implement" - * |"cancel-feature"|"escalation-restart"|"escalation-guide", + * { type:"action", action:"planning"|"plan"|"feature"|"test"|"implement"|"review"|"review-all" + * |"review-plan"|"retire"|"implement-wave"|"auto-implement"|"cancel-plan"|"cancel-feature" + * |"escalation-restart"|"escalation-guide", * feature:""|null, * prompt:""|null } // escalation-guide only * plus the shared { type:"cancel" } / { type:"timeout" }. @@ -99,8 +101,8 @@ function render(){ w.appendChild(box); } - // Plan bar: progress + the execution controls. Lifecycle buttons (revise, - // feature, review-plan, retire, cancel) live on the Planning surface. + // Work keeps active-plan progress, lifecycle, and execution controls in one + // plan bar; Planning remains a staging and archive surface. const done = (D.progress&&D.progress.done)||0, total = (D.progress&&D.progress.total)||CH.length; const bar = document.createElement('div'); bar.className = 'planbar'; diff --git a/test/extension-work-activation.test.mjs b/test/extension-work-activation.test.mjs new file mode 100644 index 0000000..d92b859 --- /dev/null +++ b/test/extension-work-activation.test.mjs @@ -0,0 +1,40 @@ +import { readFileSync } from "node:fs"; +import { test } from "node:test"; +import assert from "node:assert/strict"; + +const source = readFileSync( + new URL("../extensions/iterator.js", import.meta.url), + "utf8", +); + +function sourceSection(start, end) { + const from = source.indexOf(start); + const to = source.indexOf(end, from + start.length); + assert.notEqual(from, -1, `missing section start: ${start}`); + assert.notEqual(to, -1, `missing section end: ${end}`); + return source.slice(from, to); +} + +test("accepted feature breakdown intentionally activates Work", () => { + const section = sourceSection( + "// Issue 5: auto mode starts right after the feature set is approved", + "pi.registerTool({\n\t\tname: \"okf_write\"", + ); + assert.match(section, /const approved\s*=/); + assert.match( + section, + /if \(approved\) \{\s*void refreshHub\(ctx\.cwd, \{ activateWork: true \}\);/, + ); +}); + +test("approved plan application intentionally activates Work", () => { + const section = sourceSection( + "// Apply-on-approve (mirrors lib/app.mjs)", + "return asText(result);", + ); + assert.match(section, /result\?\.type === "plan-approved"/); + assert.match( + section, + /if \(applied\?\.ok\) await refreshHub\(ctx\.cwd, \{ activateWork: true \}\);/, + ); +}); From 97c0b980be1a56f986fb403483daec8b47eccea2 Mon Sep 17 00:00:00 2001 From: Christoph Maier Date: Mon, 20 Jul 2026 16:23:31 +0200 Subject: [PATCH 12/42] chore(iterator): record feature commit --- memory/features/active-plan-workspace.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/memory/features/active-plan-workspace.md b/memory/features/active-plan-workspace.md index 77a18e5..2c9e40b 100644 --- a/memory/features/active-plan-workspace.md +++ b/memory/features/active-plan-workspace.md @@ -8,12 +8,15 @@ depends_on: [fresh-implementation-session] files: ["lib/views/hub.mjs", "lib/views/planning.mjs", "extensions/iterator.js", "test/ui.test.mjs", "test/client-js-parse.test.mjs", "test/session-server.test.mjs"] memories: [pitfalls/cancel-now-after-grace-timer, pitfalls/client-js-template-literal-escaping, architecture/package-and-skill-layout, architecture/workflow-state-ownership, patterns/safe-browser-rendering, decisions/backlog-planning-and-feature-waves, decisions/consume-accepted-backlog-ideas, decisions/iterator-dashboard-feature-workflow] conflicts: "[{\"decision\":\"decisions/review-navigation-and-work-context\",\"note\":\"The accepted plan intentionally moves plan lifecycle controls from Planning to Work, revising the recorded split where Planning owned those actions.\"}]" -timestamp: "2026-07-20T14:23:31.442Z" +timestamp: "2026-07-20T14:23:31.543Z" tags: [] commits: - sha: c5f6915bf82dc6914b8a02a707422a0c67b1a26a kind: implement date: 2026-07-20 + - sha: 6bcce8b8e1097d9135c7644c2cb44edc719841f7 + kind: implement + date: 2026-07-20 reviewed: 2026-07-20 --- From bf537904d738ac6fb4897a98f6a9615dcd283d4b Mon Sep 17 00:00:00 2001 From: Christoph Maier Date: Mon, 20 Jul 2026 16:23:59 +0200 Subject: [PATCH 13/42] chore(iterator): record feature commits and memory updates --- memory/features/active-plan-workspace.md | 5 +++-- memory/features/index.md | 2 +- memory/log.md | 1 + memory/state.md | 4 ++-- memory/usage.md | 10 +++++----- 5 files changed, 12 insertions(+), 10 deletions(-) diff --git a/memory/features/active-plan-workspace.md b/memory/features/active-plan-workspace.md index 2c9e40b..d8d8da0 100644 --- a/memory/features/active-plan-workspace.md +++ b/memory/features/active-plan-workspace.md @@ -2,13 +2,13 @@ type: Feature title: Active plan workspace description: Make Work the complete home for an active plan and land there after plan or feature approval, while Planning stays focused on future work and archives. -status: implemented +status: done size: medium depends_on: [fresh-implementation-session] files: ["lib/views/hub.mjs", "lib/views/planning.mjs", "extensions/iterator.js", "test/ui.test.mjs", "test/client-js-parse.test.mjs", "test/session-server.test.mjs"] memories: [pitfalls/cancel-now-after-grace-timer, pitfalls/client-js-template-literal-escaping, architecture/package-and-skill-layout, architecture/workflow-state-ownership, patterns/safe-browser-rendering, decisions/backlog-planning-and-feature-waves, decisions/consume-accepted-backlog-ideas, decisions/iterator-dashboard-feature-workflow] conflicts: "[{\"decision\":\"decisions/review-navigation-and-work-context\",\"note\":\"The accepted plan intentionally moves plan lifecycle controls from Planning to Work, revising the recorded split where Planning owned those actions.\"}]" -timestamp: "2026-07-20T14:23:31.543Z" +timestamp: "2026-07-20T14:23:59.725Z" tags: [] commits: - sha: c5f6915bf82dc6914b8a02a707422a0c67b1a26a @@ -18,6 +18,7 @@ commits: kind: implement date: 2026-07-20 reviewed: 2026-07-20 +done: 2026-07-20 --- # Implementation notes diff --git a/memory/features/index.md b/memory/features/index.md index f436388..954358c 100644 --- a/memory/features/index.md +++ b/memory/features/index.md @@ -1,6 +1,6 @@ # Features * [Fresh implementation session](fresh-implementation-session.md) - ✅ done · large · Start every manual, ready-wave, and automatic feature implementation in a new Pi session seeded only with the named feature command and deterministic bundle context. -* [Active plan workspace](active-plan-workspace.md) - ⬜ pending · medium · depends: fresh-implementation-session · Make Work the complete home for an active plan and land there after plan or feature approval, while Planning stays focused on future work and archives. +* [Active plan workspace](active-plan-workspace.md) - ✅ done · medium · depends: fresh-implementation-session · Make Work the complete home for an active plan and land there after plan or feature approval, while Planning stays focused on future work and archives. * [Settings dashboard modal](settings-dashboard-modal.md) - ⬜ pending · medium · depends: active-plan-workspace · Open project Settings as a modal above any dashboard tab and return to the exact originating context on save or close. * [Visible red-test handoff](visible-red-test-handoff.md) - ⬜ pending · medium · depends: active-plan-workspace · Show committed red tests as the implementation target in Work and carry their exact status and paths into the fresh implementer context. diff --git a/memory/log.md b/memory/log.md index 500e5c2..ac343ef 100644 --- a/memory/log.md +++ b/memory/log.md @@ -1,6 +1,7 @@ # iterator update log ## 2026-07-20 +* **Review**: Accepted [Active plan workspace](/features/active-plan-workspace.md) (committed as feature(active-plan-workspace)). * **Implementation**: Committed feature(active-plan-workspace) on branch iterator/safe-role-model-handoff; awaiting review. * **Review**: Reviewed [Active plan workspace](/features/active-plan-workspace.md); changes (agent). * **Update**: Memorized [Work owns active plan context and lifecycle](/decisions/review-navigation-and-work-context.md). diff --git a/memory/state.md b/memory/state.md index aadd3ee..b26d379 100644 --- a/memory/state.md +++ b/memory/state.md @@ -4,11 +4,11 @@ title: Runtime state description: Machine-owned iterator flow state — never hand-edited. mode: auto paused: false -phase: implementing +phase: reviewing active_feature: active-plan-workspace strikes: "{\"fresh-implementation-session\":1,\"active-plan-workspace\":1}" escalation: null -timestamp: 2026-07-20T14:21:45.715Z +timestamp: 2026-07-20T14:23:41.523Z --- Runtime flow state; read via gather, written only by the state op. diff --git a/memory/usage.md b/memory/usage.md index 3bc75a1..4814ea0 100644 --- a/memory/usage.md +++ b/memory/usage.md @@ -2,9 +2,9 @@ type: Usage title: Token usage description: Per-step model/token ledger and optional project-owned pricing for the active plan — written only by the usage op. -totals: "{\"steps\":{\"hub\":{\"openai-codex/gpt-5.6-terra\":{\"input\":243028,\"output\":842,\"cacheRead\":811520,\"cacheWrite\":0,\"turns\":9}},\"plan\":{\"openai-codex/gpt-5.6-sol\":{\"input\":118306,\"output\":8575,\"cacheRead\":3610112,\"cacheWrite\":0,\"turns\":21}},\"implement\":{\"openai-codex/gpt-5.6-terra\":{\"input\":1454180,\"output\":20444,\"cacheRead\":13496832,\"cacheWrite\":0,\"turns\":53},\"openai-codex/gpt-5.6-sol\":{\"input\":1191326,\"output\":7856,\"cacheRead\":6515712,\"cacheWrite\":0,\"turns\":26}},\"review\":{\"openai-codex/gpt-5.6-sol\":{\"input\":683106,\"output\":5003,\"cacheRead\":3732480,\"cacheWrite\":0,\"turns\":20}}},\"features\":{\"retire-plan\":{\"input\":243028,\"output\":842,\"cacheRead\":811520,\"cacheWrite\":0,\"turns\":9},\"fresh-implementation-session\":{\"input\":2916375,\"output\":23531,\"cacheRead\":16707072,\"cacheWrite\":0,\"turns\":72},\"active-plan-workspace\":{\"input\":412237,\"output\":9772,\"cacheRead\":7037952,\"cacheWrite\":0,\"turns\":27}},\"featureModels\":{\"retire-plan\":{\"openai-codex/gpt-5.6-terra\":{\"input\":243028,\"output\":842,\"cacheRead\":811520,\"cacheWrite\":0,\"turns\":9}},\"fresh-implementation-session\":{\"openai-codex/gpt-5.6-terra\":{\"input\":1086985,\"output\":12201,\"cacheRead\":6675968,\"cacheWrite\":0,\"turns\":32},\"openai-codex/gpt-5.6-sol\":{\"input\":1829390,\"output\":11330,\"cacheRead\":10031104,\"cacheWrite\":0,\"turns\":40}},\"active-plan-workspace\":{\"openai-codex/gpt-5.6-terra\":{\"input\":367195,\"output\":8243,\"cacheRead\":6820864,\"cacheWrite\":0,\"turns\":21},\"openai-codex/gpt-5.6-sol\":{\"input\":45042,\"output\":1529,\"cacheRead\":217088,\"cacheWrite\":0,\"turns\":6}}}}" +totals: "{\"steps\":{\"hub\":{\"openai-codex/gpt-5.6-terra\":{\"input\":243028,\"output\":842,\"cacheRead\":811520,\"cacheWrite\":0,\"turns\":9}},\"plan\":{\"openai-codex/gpt-5.6-sol\":{\"input\":118306,\"output\":8575,\"cacheRead\":3610112,\"cacheWrite\":0,\"turns\":21}},\"implement\":{\"openai-codex/gpt-5.6-terra\":{\"input\":1454180,\"output\":20444,\"cacheRead\":13496832,\"cacheWrite\":0,\"turns\":53},\"openai-codex/gpt-5.6-sol\":{\"input\":1332608,\"output\":10554,\"cacheRead\":7464960,\"cacheWrite\":0,\"turns\":42}},\"review\":{\"openai-codex/gpt-5.6-sol\":{\"input\":683106,\"output\":5003,\"cacheRead\":3732480,\"cacheWrite\":0,\"turns\":20}}},\"features\":{\"retire-plan\":{\"input\":243028,\"output\":842,\"cacheRead\":811520,\"cacheWrite\":0,\"turns\":9},\"fresh-implementation-session\":{\"input\":2916375,\"output\":23531,\"cacheRead\":16707072,\"cacheWrite\":0,\"turns\":72},\"active-plan-workspace\":{\"input\":553519,\"output\":12470,\"cacheRead\":7987200,\"cacheWrite\":0,\"turns\":43}},\"featureModels\":{\"retire-plan\":{\"openai-codex/gpt-5.6-terra\":{\"input\":243028,\"output\":842,\"cacheRead\":811520,\"cacheWrite\":0,\"turns\":9}},\"fresh-implementation-session\":{\"openai-codex/gpt-5.6-terra\":{\"input\":1086985,\"output\":12201,\"cacheRead\":6675968,\"cacheWrite\":0,\"turns\":32},\"openai-codex/gpt-5.6-sol\":{\"input\":1829390,\"output\":11330,\"cacheRead\":10031104,\"cacheWrite\":0,\"turns\":40}},\"active-plan-workspace\":{\"openai-codex/gpt-5.6-terra\":{\"input\":367195,\"output\":8243,\"cacheRead\":6820864,\"cacheWrite\":0,\"turns\":21},\"openai-codex/gpt-5.6-sol\":{\"input\":186324,\"output\":4227,\"cacheRead\":1166336,\"cacheWrite\":0,\"turns\":22}}}}" prices: "{}" -timestamp: 2026-07-20T14:21:45.420Z +timestamp: 2026-07-20T14:23:41.264Z --- # Usage @@ -26,7 +26,7 @@ timestamp: 2026-07-20T14:21:45.420Z | model | input | output | cache read | cache write | turns | cost | | --- | ---: | ---: | ---: | ---: | ---: | ---: | | openai-codex/gpt-5.6-terra | 1454180 | 20444 | 13496832 | 0 | 53 | — | -| openai-codex/gpt-5.6-sol | 1191326 | 7856 | 6515712 | 0 | 26 | — | +| openai-codex/gpt-5.6-sol | 1332608 | 10554 | 7464960 | 0 | 42 | — | ## review @@ -40,6 +40,6 @@ timestamp: 2026-07-20T14:21:45.420Z | --- | ---: | ---: | ---: | ---: | ---: | ---: | | retire-plan | 243028 | 842 | 811520 | 0 | 9 | — | | fresh-implementation-session | 2916375 | 23531 | 16707072 | 0 | 72 | — | -| active-plan-workspace | 412237 | 9772 | 7037952 | 0 | 27 | — | +| active-plan-workspace | 553519 | 12470 | 7987200 | 0 | 43 | — | -Total: 3689946 in / 42720 out / 28166656 cache-read / 0 cache-write over 129 turns. Cost unavailable: add every used model rate. +Total: 3831228 in / 45418 out / 29115904 cache-read / 0 cache-write over 145 turns. Cost unavailable: add every used model rate. From 791f0a75c5cae0a78ca6839fa63f131d293597f5 Mon Sep 17 00:00:00 2001 From: Christoph Maier Date: Mon, 20 Jul 2026 16:35:16 +0200 Subject: [PATCH 14/42] feature(settings-dashboard-modal): Open Settings as a shell-owned dashboard modal Feature: settings-dashboard-modal --- extensions/iterator.js | 19 ++-- lib/session-server.mjs | 78 ++++++++++++++- lib/views/settings.mjs | 9 +- memory/features/active-plan-workspace.md | 3 +- memory/features/settings-dashboard-modal.md | 4 +- memory/log.md | 2 + memory/state.md | 8 +- memory/usage.md | 10 +- skills/iterator/lib/views/settings.mjs | 9 +- test/session-server.test.mjs | 101 ++++++++++++++++++++ test/ui.test.mjs | 14 +++ 11 files changed, 230 insertions(+), 27 deletions(-) diff --git a/extensions/iterator.js b/extensions/iterator.js index 28a2aab..297cbd1 100644 --- a/extensions/iterator.js +++ b/extensions/iterator.js @@ -386,7 +386,8 @@ export default function iteratorExtension(pi) { "info", ); } - await refreshHub(cwd); + session?.closeModal?.(); + await refreshStatus(cwd); } catch (e) { if (lastCtx?.hasUI) lastCtx.ui.notify( @@ -446,15 +447,15 @@ export default function iteratorExtension(pi) { } }; - /** Show the settings view as an idle dashboard page (unsolicited round). */ + /** Open Settings above the shell without replacing its tab or pending round. */ const openSettings = async () => { const cwd = ctxCwd(); try { const payload = await gatherPayload(cwd, "settings"); const models = await modelOptions(); - session.showView({ - step: "settings", - render: () => VIEWS.settings(models ? { ...payload, models } : payload), + session.showModal({ + render: () => + VIEWS.settings({ ...(models ? { ...payload, models } : payload), modal: true }), }); } catch (e) { if (lastCtx?.hasUI) lastCtx.ui.notify(`iterator: ${e.message}`, "error"); @@ -987,8 +988,12 @@ export default function iteratorExtension(pi) { void saveUsagePrices(result.prices); return; } - // Settings is an idle page: its Close button emits cancel, which - // must restore the dashboard rather than leave its view in place. + // Settings is shell-owned: dismissal leaves the originating tab, + // pending round, and Work overlay untouched. + if (result?.type === "settings-close") { + session.closeModal(); + return; + } if (result?.type === "cancel") { void refreshHub(ctxCwd()); return; diff --git a/lib/session-server.mjs b/lib/session-server.mjs index 3c5d634..83ba8ca 100644 --- a/lib/session-server.mjs +++ b/lib/session-server.mjs @@ -144,6 +144,18 @@ iframe{display:block;width:100%;flex:1;border:0} #overlay-btns button#ov-abort:hover{border-color:#f85149} #conn{position:fixed;right:10px;bottom:10px;display:none;font:12px -apple-system,sans-serif; color:#d29922;background:#161b22;border:1px solid #30363d;border-radius:12px;padding:3px 10px;z-index:60} +#settings-modal{position:fixed;inset:0;display:none;align-items:center;justify-content:center; + padding:var(--modal-gap,24px);background:rgba(1,4,9,.72);z-index:70} +#settings-modal.open{display:flex} +#settings-dialog{width:min(860px,100%);height:min(780px,100%);display:flex;flex-direction:column; + overflow:hidden;background:#0d1117;border:1px solid #30363d;border-radius:10px;box-shadow:0 12px 40px rgba(0,0,0,.5)} +#settings-dialog header{display:flex;align-items:center;justify-content:space-between;gap:12px;padding:8px 12px; + color:#c9d1d9;background:#161b22;border-bottom:1px solid #30363d;font:600 13px -apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif} +#settings-dialog button{font:12px -apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif;color:#c9d1d9; + background:#21262d;border:1px solid #30363d;border-radius:6px;padding:5px 10px;cursor:pointer} +#settings-dialog button:hover{border-color:#8b949e} +#settings-frame{display:block;width:100%;flex:1;border:0} +@media(max-width:640px){#settings-modal{--modal-gap:0}#settings-dialog{width:100%;height:100%;border-radius:0;border-width:0}} @@ -176,12 +188,22 @@ iframe{display:block;width:100%;flex:1;border:0} +
reconnecting…