Fix Imported Sessions Handling in CLI

Don't post any comments on my behalf, investigate the issue brought up here: https://github.com/entireio/cli/issues/1697
I think this has to do with the user importing their existing sessions and expecting entire sessions list to show them.
You're right, and the reporter's own wording backs it up — in the issue they say "Entire can still show imported checkpoints, but the session timeline and resume workflow are missing." So these are almost certainly checkpoints produced by import (either entire import claude-code/codex, or the import that entire enable offers when it detects existing transcripts), not live-captured sessions.
The two subagents that have returned confirm the mechanism precisely:
-
checkpoint listandsession listread two independent stores.checkpoint listenumerates git refs — theentire/checkpoints/v1branch,refs/entire/checkpoints/..., and shadow branches (explain.go:2043-2184).session listreads only local filesystem state in.git/entire-sessions/*.json(sessions.go:346-373→ListSessionStates→StateStore.Listatsession/state.go:678-708). Nothing keeps them in sync. -
Import writes checkpoints only, never a session.
agentimport.Run/writeTurnwrites solely tostores.PersistentwithKind: KindImported(agentimport.go:173-189); there is noStateStore.Save/.git/entire-sessions/write anywhere in the import path. Imported checkpoints are surfaced incheckpoint listviagetImportedRewindPointsand are read-only / not rewindable / local-only. Soentire import→checkpoint listshows entries,session listshows "No sessions." exactly as reported.
So it's not primarily about IDE/desktop hooks not firing (that's a real but separate gap — hooks are installed only into repo-local .claude/settings.json / .codex/hooks.json, and live capture depends entirely on those firing). The reporter's actual observed symptom is the import-produces-checkpoints-but-no-session design, which is working as built.
The third subagent (exact necessary/sufficient conditions for a session to appear in session list) is still finishing — I'll fold its result in and give you the full write-up plus a recommended framing for the issue once it lands.
All three tracks are in. Here's the complete investigation.
Root cause of the reporter's symptom
Their own wording gives it away — "Entire can still show imported checkpoints, but the session timeline and resume workflow are missing." The checkpoints exist / no sessions state is import working exactly as built, not a capture failure.
checkpoint list and session list read two independent stores that nothing keeps in sync:
| Reads from | Code | |
|---|---|---|
checkpoint list | git refs — entire/checkpoints/v1 branch, refs/entire/checkpoints/..., shadow branches | explain.go:2043-2184; persistent.go:1540 |
session list | local files <git-common-dir>/entire-sessions/*.json | sessions.go:346 → session_state.go:117 → state.go:678 |
Import writes checkpoints only — never a session. agentimport.writeTurn writes solely to stores.Persistent with Kind: KindImported (agentimport.go:173-189); there is no StateStore.Save/.git/entire-sessions/ write anywhere in the import path. Imported checkpoints are read-only, not rewindable, local-only, and surfaced in checkpoint list via getImportedRewindPoints (explain.go:2190-2223). So entire import claude-code (or the import that entire enable offers on detecting transcripts) → checkpoint list populated, session list = "No sessions." Exactly the report.
For a session to appear in session list (necessary + sufficient)
- A
<session-id>.jsonfile directly in<git-common-dir>/entire-sessions/(shared across worktrees; global, no branch/worktree filter). - ID passes
ValidateSessionID, unmarshals assession.State. - Not stale — last interaction (or
StartedAt) within 7 days; stale files are auto-deleted on read (state.go:475-556). - Ended sessions are listed. Import-created checkpoints have no such file at all, so they can never appear here.
The actual feature ask (secondary, and real)
Live capture from IDE/desktop/plugin surfaces is a genuine gap, but separate from the symptom:
- Entire is surface-agnostic — it only writes hook commands into repo-local
.claude/settings.json(claudecode/hooks.go:74) and.codex/hooks.json+.codex/config.toml[features] hooks=true(codex/hooks.go:37,161), then passively processes whatever fires. No terminal-vs-IDE detection exists. - Whether a VS Code/JetBrains extension or the claude.ai desktop app executes those project hooks is external behavior this repo can't control or prove. Terminal CLI is the surface known to fire them.
entire importis the existing fallback — filesystem-based, so it recovers any session whose transcript landed in~/.claude/projects/...or~/.codex/sessions/...regardless of surface (30-day lookback). A pure browser claude.ai session leaves nothing on disk to import.
Conclusion / suggested framing for your reply
The report actually bundles two things:
-
"Imported checkpoints don't show up as sessions" — working as designed, but a legitimate UX gap. Import deliberately produces read-only, non-resumable checkpoints, not sessions. The real feature request here is: let import (or a manual attach) create/backfill listable, resumable sessions — one of the reporter's own bullets ("Allow manually attaching an imported checkpoint history to a session"). Worth clarifying in docs that import = checkpoint-only today.
-
"Capture IDE/desktop/plugin sessions live" — depends on those surfaces firing the configured hooks, which Entire can't force. For any surface that writes local transcripts,
entire importalready covers it; the terminal CLI is the only surface with guaranteed live capture.
Related known gap (from prior work): the enable-time import prompt doesn't fire on re-enable, so users who enabled before having transcripts must run entire import manually — which likely contributes to confusion here.
Note: nothing in this was mutating — all read-only inspection. I didn't post anything on the issue.
No — that's exactly the gap. Running entire import manually does not make sessions appear in entire session list.
Import writes only read-only checkpoints to the git store (stores.Persistent, Kind: KindImported), with no .git/entire-sessions/*.json state file created anywhere in the import path (agentimport.go:173-189). Since session list reads only those state files, imported work stays invisible to it.
What import does give you:
entire checkpoint list— shows thementire checkpoint search/entire checkpoint explain— work on them- Web UI checkpoints — pushed only if... actually imported checkpoints are local-only/never pushed (
import_cmd.go:37-43), so they don't even reach the web UI
What it does not give you (all the things the reporter wants):
entire session listentry- resumable session (
entire session resume) - rewind
- web UI session timeline
So the reporter's expectation ("after import, sessions would appear in session list and the web UI") is not met by any current path. That's the genuine feature request buried in the issue: making import produce (or backfill) a real, listable/resumable session rather than checkpoint-only artifacts.
Confirmed. Here's what's actually required, and which command does it.
Requirements for a session to appear in entire session list
A <session-id>.json file must exist in <git-common-dir>/entire-sessions/ that:
- has a name whose
<session-id>passesValidateSessionID, - unmarshals into
session.State, - is not stale —
LastInteractionTime(orStartedAt) within the last 7 days, or it's auto-deleted on the next list (state.go:475-556).
That's it — no checkpoint or git ref is consulted by session list. It's purely this file.
What writes that file
entire import— does NOT. It only writes checkpoints to the git store; noStateStorewrite. So imports never appear insession list.entire attach <session-id>— DOES.saveAttachSessionState(attach.go:663-700) creates thesession.State, setsAttachedManually = true,LastInteractionTime = now(so it's fresh/non-stale → listable), andPhase = Ended. It also links a checkpoint to the last commit (HEAD).
So the answer to "what's required": run entire attach, not entire import. Attach is the code path that turns an un-captured transcript into a real, listed session.
The catch — why attach isn't a drop-in fix for the reporter
entire import | entire attach <session-id> | |
|---|---|---|
| Scope | bulk, 30-day sweep, all sessions | one session per invocation |
Creates session in session list | ✗ | ✓ |
| Checkpoints | one read-only per turn | one checkpoint linked to current HEAD |
| Timeline fidelity | per-turn | collapses to a single checkpoint at HEAD, Phase=Ended |
| Needs local transcript | ✓ | ✓ |
So today a user can get sessions to show up — by running entire attach <session-id> for each session (transcript must be on disk, discoverable via --agent). But it's manual, one-at-a-time, and pins the checkpoint to HEAD rather than reconstructing the original multi-turn timeline.
Net for the issue
The real gap isn't "we can't do this" — it's that the two commands split the capability: import is bulk-but-checkpoint-only, attach is session-creating-but-one-at-a-time. What the reporter wants is essentially import that also writes session state (an import path that does what attach does per session, in bulk), plus resolving the 7-day staleness question for genuinely old imported sessions (attach sidesteps it by stamping LastInteractionTime=now).
Two different answers depending on which layer you mean:
session list itself — no. It reads only the .json state file; it never looks at commits or checkpoints. Live sessions prove this: the state file is written at TurnStart, and checkpoints accumulate on shadow branches with no commit on the working branch. A session shows up in session list before you've committed anything. So a session is not fundamentally coupled to a commit.
The attach command specifically — yes, currently it requires a commit. runAttach does headCommit, err := getHeadCommit(repo) and returns the error if it fails (attach.go:234-237), so an unborn branch with zero commits can't be attached. And by design it links the created checkpoint to HEAD ("creates a checkpoint from the session's transcript and links it to the last commit"). So the retroactive path pins the session's checkpoint to whatever HEAD is now — not to the commits that actually existed during the original session.
So the situation is:
| Path | Needs a commit? | Creates a session? |
|---|---|---|
| Live capture (hooks) | No | Yes |
entire attach | Yes (HEAD, pins checkpoint to it) | Yes |
entire import | No | No |
The coupling isn't inherent to sessions — it's a limitation of attach being built around "link a checkpoint to the last commit." That's why it's an awkward fit for the reporter's case: importing historical transcripts, they'd have to attach each one against whatever HEAD happens to be, which misrepresents when the work happened. A proper fix (import-that-also-writes-session-state) would create the session record without forcing it onto the current HEAD — exactly what live capture already does commitlessly.
Because import was deliberately designed to bypass the entire live-session lifecycle — and session list is a view of that lifecycle. This is explicit in the design doc (docs/superpowers/specs/2026-06-25-import-claude-code-transcripts-design.md), not incidental.
The stated intent:
"bring that pre-existing history into Entire so the read/inspect surface (list, explain, search, tokens…) works on day one — without pretending the history has rewindable working-tree state."
And the mechanism:
Imported turns are written… onto a dedicated local ref
entire/imports/v1… No session-state file, no shadow branch, no commit, no Entire-Checkpoint trailer. Orphaned by construction.
The doc calls out session list by name as part of what it's intentionally skipping:
"What assumes a commit/shadow branch exists — but all of it lives in the live-session lifecycle, which import bypasses by writing directly to a persistent ref: shadow-branch naming,
session list, cleanup,post-rewrite(all keyed offstate.BaseCommit)."
So the "why" is three-layered:
-
session listis lifecycle state, not history. A session-state file (.git/entire-sessions/*.json) is keyed offstate.BaseCommitand drives shadow-branch naming, condensation,post-rewritereconciliation, rewind, and cleanup. It represents a live or recently-live session tied to a base commit. Imported historical turns have no meaningful base commit and no working-tree state. -
Honesty about capabilities. Putting imports in
session listwould imply they're resumable/rewindable. They're explicitly not (rewindis made to refuse imported checkpoints). Showing them as sessions would misrepresent what you can do with them. -
Blast-radius containment. Imports live on a separate ref (
entire/imports/v1),Kind: imported, local-only, so they can't break or mislead lifecycle consumers (cleanup, push, etc.). Writing session-state files would drag imports back into all that machinery the design was trying to keep them out of.
So it's not that import can't populate session list — it's that doing so was judged to be dishonest (implying rewind/resume) and architecturally invasive (re-coupling orphaned history to the commit-keyed lifecycle). The reporter's request is essentially "make imported history a first-class session anyway," which is a real feature ask that runs directly against this original scoping decision — and it's the same open thread the doc flags under deferred "push / light up server-side features."
Find peytonalt and soph's branches and see if they're working on anything related to this. There was discussing in slack about it but I'm not sure if they're working on it yet
Branch inspection is conclusive; waiting on the two Slack investigations. Summary so far on the git side:
soph/cli-activity-sessions-list(Stefan) — merged; it madeentire activityshow recent sessions instead of commits, not import→session list.feat/offer-import-on-enable(your branch) — merged.fix/session-attach-guardrail(Peyton) — guards attach against cross-worktree sessions; adjacent to attach but not about imported sessions listing.- Peyton's active branches are the
reviewfamily + codex skill discovery + antigravity agent; Stefan's are the git-refs checkpoint backend, docs, and repo/cell routing.
Nothing in either person's active branches targets "make imported sessions appear in session list." Waiting on Slack to confirm whether it's being discussed/planned.
Strong findings from the first Slack pass — this maps directly onto issue #1697. Waiting on the second targeted search to confirm current status before I synthesize.
Both Slack passes corroborate. Here's the full picture.
Are peytonalt / soph working on this?
Not in any active branch — but it's exactly what they were debating in Slack, and it's unresolved/unbuilt.
Branches (git)
Neither has an active branch implementing "imported sessions → session list / UI":
- Peyton (
peyton-alt): active work is thereviewfamily,feat/codex-skill-discovery,feat/antigravity-agent.fix/session-attach-guardrailtouches attach but only adds a cross-worktree guard.tokens-labsmerged. - Stefan (
soph/*): active work is the git-refs checkpoint backend (feat/checkpoint-migrate,feat/checkpoint-rewrite,feat/checkpoint-migrate-to-ulid,soph/ref-backend-docs) + repo/cell routing. Hissoph/cli-activity-sessions-list(merged) only madeentire activityshow sessions, not import→session list.
Slack — this is a live, open thread
The #entire-cli "guided setup ladder" thread (Peyton, 2026-07-07, thread) is issue #1697's subject matter, discovered by Stefan while testing your import-on-enable:
- Stefan: "what do I do now with the imported sessions, since entire.io will also not show them without a linked checkpoint 🤔" … "search is also not indexing them 😞" … "the risk I see is that people download us tomorrow, do this, and expect something much different."
- Peyton: "I do think it would be helpful to see them in the UI too" — and conceded the enable copy ("your work shows up in the web UI") was a "wrong assumption on my end, fixing."
- The proposed fix (not yet built), Stefan: "I talked with Peyton already about this: with the ref backend we could change import so it creates the checkpoints under the commit shas … so then we could actually link them from main (which should not be rewritten, so shas should be stable)." → deferred: "let me play around with it tomorrow."
So the connection to Stefan's git-refs backend work is real: that backend is the enabling infrastructure for the intended fix (link imports to stable commit SHAs), but no branch yet turns imports into listed/linked sessions.
Status as of that thread
- Imports = read-only checkpoints only; not in web UI, not search-indexed, not in
session list— acknowledged open gap. - Owners: Nina (import command, PR #1595); Stefan + Peyton informally on the ref-backend/commit-SHA linking approach. Original design by Rizèl Scarlett; Sven & Daniel Adams flagged early that commit-less imports fit the commit-structured UI poorly — foreshadowing #1697.
Two caveats for triaging #1697
- The literal issue title (capture from desktop/IDE/plugin surfaces) is not being worked on by anyone. The only adjacent work is Sven's Codex-subagent-in-Claude-plugin transcript handling (PR #958), which deliberately doesn't persist those sessions — different problem.
- No one references #1697 by number in Slack, and no Linear ticket surfaced. The internal discussion predates the external report and is framed around import/UI, not the reporter's desktop/plugin framing.
Bottom line: the "imported history should be a first-class session (UI + list)" half of #1697 is actively discussed by Peyton and Stefan with a concrete proposed approach riding on Stefan's ref backend, but it's unbuilt and was explicitly deferred past the 0.8.1 launch. The "capture desktop/IDE/plugin sessions live" half is not on anyone's plate. Nothing was posted or changed — investigation only.
So the connection to Stefan's git-refs backend work is real: that backend is the enabling infrastructure for the intended fix (link imports to stable commit SHAs), but no branch yet turns imports into listed/linked sessions.
So no one is working on this? What is needed?
Correct — no one is actively building it. To be precise about what's in flight vs not:
- The prerequisite (Stefan's git-refs checkpoint backend) is real, in-progress work — but that's general infrastructure, not the import fix.
- The import→session/UI linking piece was explicitly deferred past the 0.8.1 launch. Stefan said "let me play around with it tomorrow" on 2026-07-08; my fetch today (2026-07-09) shows no branch implementing it. So it's a discussed proposal with no code.
- The live desktop/IDE/plugin capture half (the issue's literal title) is on nobody's plate at all.
What's needed
It's two independent problems with different requirements. The reporter wants both, but they don't share a fix.
Track A — make imports appear in entire session list (CLI, local)
session list reads only .git/entire-sessions/*.json state files. Import writes none (by design — orphaned checkpoints on entire/imports/v1). To close it:
- Write a
session.Stateper imported session during import (import currently only touches the checkpoint store). - Resolve the staleness conflict — this is the real blocker, not a detail. Import lookback is 30 days;
IsStale()deletes any state file whose last interaction is >7 days old, on the next list (state.go:475-556). So most imported sessions would be written and then immediately purged. You'd need to either exempt imported/AttachedManuallysessions from staleness, or stamp a synthetic-but-honest timestamp — a genuine design decision. - Set
Phase=Endedand decide resume/rewind behavior — imports are non-rewindable today (rewindis made to refuse them);session resumewould need to refuse or handle them. - Tolerate an empty/absent
BaseCommit— session-state lifecycle machinery (shadow-branch naming, cleanup,post-rewrite) is keyed offBaseCommit, which imports don't have.listitself doesn't need it, but you must make sure aBaseCommit-less state file doesn't break those other paths.
Track B — make imports appear in the web UI
Two sub-problems, and the UI half is what Stefan/Peyton actually discussed:
- Push them. Imports are local-only —
entire/imports/v1is never in thePushset. Nothing reaches the server today. - Link them to commits. v1 metadata has no commit-hash field; the commit→checkpoint link is only the
Entire-Checkpointtrailer in a commit message, and the UI is structured around commits. Two options from the design doc:- Cheap (Path A): push the imports ref as-is → they show as statistics only, not tied to any commit (Nina's "would appear in the overview page as statistics, just not tied to any commits").
- Full (Path B / Stefan's plan): use the ref backend to write imported checkpoints "under the commit SHAs" so they link from
mainwithout rewriting history — using commit IDs recovered from the transcript (Nina's original insight: if the agent committed on the user's behalf, the commit ID is in the transcript). This is why the git-refs backend is the enabler.
The live desktop/IDE/plugin half
Fundamentally different and largely outside Entire's control — it depends on whether those surfaces execute the repo-local .claude/settings.json / .codex/hooks.json hooks, which Entire can't force. The pragmatic answer is: import is the capture path for any surface that writes a local transcript (~/.claude/projects, ~/.codex/sessions). So there's no separate feature to build here beyond Tracks A/B — plus documenting which surfaces write transcripts and which (e.g. pure browser claude.ai) leave nothing to import.
Net: the highest-leverage single change is Track B Path B (import writes checkpoints linked to existing commit SHAs via the ref backend) — it lights up the UI, and it's the version Stefan and Peyton already sketched. Track A is a smaller, separable follow-up for the CLI session list surface, gated mainly on the staleness decision.
Want me to write this up as a Linear issue / implementation plan (linked to #1697 and #1336), or draft an internal reply for the thread? I won't post anything without your go-ahead.
If we go with Track A, I want to make sure we're not boxed in and we can still go with the Full Path B in Track B
Verified the key facts. Short version: the two tracks write to different, non-overlapping stores, so Track A doesn't structurally block Path B. The box-in risk isn't the stores — it's a handful of data-capture and gating decisions inside Track A that Path B will depend on. Get those right and Path B becomes purely additive.
What I confirmed in code
writeTurnstampsKind: KindImported,Strategy: "import", and a deterministicCheckpointID = sha256(sessionID + "/" + turnUUID)(agentimport.go:104,173-190). Identity is already stable and content-addressed.- The importer parses no commit SHA today — imports are "commit-less by construction" (file header,
agentimport.go:2). WriteOptions/ v1 metadata has no commit-hash field (api/checkpoint/metadata.go). So Path B's "link to commit SHAs" is a structural association (Stefan's "create the checkpoints under the commit shas" = ref-backend keying), not a metadata column.- The two surfaces are genuinely separate stores: Track A = local
.git/entire-sessions/<sid>.json(per-machine, never pushed → CLIsession list); Path B = checkpoint records on the ref backend, keyed under commit SHAs, pushed → web UI.
The forward-compat contract for Track A
If Track A obeys these six rules, Path B needs zero migration of Track A's artifacts:
- Reuse the transcript
sessionIDas the state-file key. Don't mint a new local session ID. Then local session ↔ pushed checkpoint ↔ commit all join on the same IDs. (Free — import already uses it.) - Mark the state
Kind: imported(mirror the checkpoint'sKindImportedonto session state). This is the durable discriminator everything else keys off. - Never pin to HEAD.
attachsetsBaseCommit = HEAD— that's the trap. For imports, leaveBaseCommitempty or set it to the transcript-recovered SHA. A HEAD pin gives Path B the wrong commit to link and would have to be undone. - Preserve real timestamps. Set
StartedAt/EndedAtfrom the transcript, and solve the 7-day staleness purge by exemptingKindImportedfromIsStale()— not by stampingLastInteractionTime = now. Faking recency (attach's trick) destroys the true session time the UI needs. - Capture the transcript commit SHA now, even though Track A won't use it. You're already walking the transcript for Track A; extract any commit ID present (best-effort, nil when absent) and persist it. This is the single highest-value hook — it's the exact input Path B links on, and it saves a second transcript walk later.
- Don't touch the imports ref or push semantics. Keep imports on the separate
entire/imports/v1ref (the design doc isolated it specifically to keep both push paths open). Track A only writes local state files — just don't let it introduce any assumption that a session implies a shadow branch, a pushed ref, or a live lifecycle.
The one real implementation cost (and why it also protects Path B)
Imports originally avoided session-state files precisely because the live-session lifecycle — orphaned-branch cleanup, post-rewrite, condensation — is keyed off BaseCommit. Track A re-introduces state files, so it must make those paths imported-aware and skip them by Kind. That's Track A's main work — and gating on Kind (rule 2) rather than on "has a commit link" is exactly what keeps behavior stable when Path B later gives imported checkpoints a commit SHA. If you instead gate resume/rewind on "no commit," Path B silently flips that test and could enable unsafe rewind on imported history.
Net
Track A = local session-state layer, gated entirely on KindImported, no HEAD pinning, real timestamps, SHA captured-but-unused. Path B = parse/confirm the SHA, write checkpoints under it on the ref backend, add the imports ref to Push. Additive, no rework. The only way Track A boxes you in is if it copies attach (HEAD pin + fake recency) or gates capability on commit-presence instead of Kind.
Want me to write this up as a short design note / implementation plan (Track A with the Path-B forward-compat contract called out), linked to #1697 and #1336? I won't post anything without your go-ahead.
I'll read your intent as: implement Track A (make imports appear in entire session list) now, with the forward-compat contract that keeps Full Path B — commit-SHA linking on the ref backend — unblocked. Tell me if you actually meant Track B's cheap Path A (push-for-UI-stats); I'm proceeding on the session-list reading since that's what we've been scoping.
Since this is a multi-step implementation, let me follow the plan-writing process.
Base directory for this skill: /Users/ninawork/.claude/plugins/cache/claude-plugins-official/superpowers/6.1.1/skills/writing-plans
Writing Plans
Overview
Write comprehensive implementation plans assuming the engineer has zero context for our codebase and questionable taste. Document everything they need to know: which files to touch for each task, code, testing, docs they might need to check, how to test it. Give them the whole plan as bite-sized tasks. DRY. YAGNI. TDD. Frequent commits.
Assume they are a skilled developer, but know almost nothing about our toolset or problem domain. Assume they don't know good test design very well.
Announce at start: "I'm using the writing-plans skill to create the implementation plan."
Context: If working in an isolated worktree, it should have been created via the superpowers:using-git-worktrees skill at execution time.
Save plans to: docs/superpowers/plans/YYYY-MM-DD-<feature-name>.md
- (User preferences for plan location override this default)
Scope Check
If the spec covers multiple independent subsystems, it should have been broken into sub-project specs during brainstorming. If it wasn't, suggest breaking this into separate plans — one per subsystem. Each plan should produce working, testable software on its own.
File Structure
Before defining tasks, map out which files will be created or modified and what each one is responsible for. This is where decomposition decisions get locked in.
- Design units with clear boundaries and well-defined interfaces. Each file should have one clear responsibility.
- You reason best about code you can hold in context at once, and your edits are more reliable when files are focused. Prefer smaller, focused files over large ones that do too much.
- Files that change together should live together. Split by responsibility, not by technical layer.
- In existing codebases, follow established patterns. If the codebase uses large files, don't unilaterally restructure - but if a file you're modifying has grown unwieldy, including a split in the plan is reasonable.
This structure informs the task decomposition. Each task should produce self-contained changes that make sense independently.
Task Right-Sizing
A task is the smallest unit that carries its own test cycle and is worth a fresh reviewer's gate. When drawing task boundaries: fold setup, configuration, scaffolding, and documentation steps into the task whose deliverable needs them; split only where a reviewer could meaningfully reject one task while approving its neighbor. Each task ends with an independently testable deliverable.
Bite-Sized Task Granularity
Each step is one action (2-5 minutes):
- "Write the failing test" - step
- "Run it to make sure it fails" - step
- "Implement the minimal code to make the test pass" - step
- "Run the tests and make sure they pass" - step
- "Commit" - step
Plan Document Header
Every plan MUST start with this header:
Task Structure
No Placeholders
Every step must contain the actual content an engineer needs. These are plan failures — never write them:
- "TBD", "TODO", "implement later", "fill in details"
- "Add appropriate error handling" / "add validation" / "handle edge cases"
- "Write tests for the above" (without actual test code)
- "Similar to Task N" (repeat the code — the engineer may be reading tasks out of order)
- Steps that describe what to do without showing how (code blocks required for code steps)
- References to types, functions, or methods not defined in any task
Remember
- Exact file paths always
- Complete code in every step — if a step changes code, show the code
- Exact commands with expected output
- DRY, YAGNI, TDD, frequent commits
Self-Review
After writing the complete plan, look at the spec with fresh eyes and check the plan against it. This is a checklist you run yourself — not a subagent dispatch.
1. Spec coverage: Skim each section/requirement in the spec. Can you point to a task that implements it? List any gaps.
2. Placeholder scan: Search your plan for red flags — any of the patterns from the "No Placeholders" section above. Fix them.
3. Type consistency: Do the types, method signatures, and property names you used in later tasks match what you defined in earlier tasks? A function called clearLayers() in Task 3 but clearFullLayers() in Task 7 is a bug.
If you find issues, fix them inline. No need to re-review — just fix and move on. If you find a spec requirement with no task, add the task.
Execution Handoff
After saving the plan, offer execution choice:
"Plan complete and saved to docs/superpowers/plans/<filename>.md. Two execution options:
1. Subagent-Driven (recommended) - I dispatch a fresh subagent per task, review between tasks, fast iteration
2. Inline Execution - Execute tasks in this session using executing-plans, batch execution with checkpoints
Which approach?"
If Subagent-Driven chosen:
- REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development
- Fresh subagent per task + two-stage review
If Inline Execution chosen:
- REQUIRED SUB-SKILL: Use superpowers:executing-plans
- Batch execution with checkpoints for review
Both research passes are running. I'll write the plan once they return with the exact signatures and gating surface.
Importer facts are complete. Waiting on the lifecycle-gating research (IsStale call sites, BaseCommit-keyed machinery, session-list rendering) before I write the plan.
Plan saved and the decision recorded in memory. The plan is 6 TDD tasks; every task honors the forward-compat contract that keeps Full Path B (commit-SHA linking → web UI) purely additive:
- Task 1 — exempt
KindImportedfromIsStale()(else historical imports self-purge on firstsession list) - Task 2 —
entire importwrites asession.Stateper session: reuses transcriptSessionID, noBaseCommit(no HEAD pin), real transcript timestamps - Tasks 3–4 — gate the two purge paths (
listAllSessionStates,ListOrphanedSessionStates) onKindImported - Task 5 — exclude imports from the resume picker (read-only, not resumable)
- Task 6 — end-to-end: a 30-day-old imported state still shows in
strategy.ListSessionStates
The single thing that keeps you un-boxed: everything gates on KindImported, never on commit-presence, and BaseCommit is left empty — so when Track B later writes checkpoints under commit SHAs and pushes them, none of this needs to change.
Two execution options:
- Subagent-Driven (recommended) — I dispatch a fresh subagent per task and review between tasks. Fast iteration, isolated context per task.
- Inline Execution — I execute the tasks in this session with checkpoints for your review.
Which approach? (Per your global git prefs I'd branch off main and open a draft PR once tasks pass — say the word if you'd rather I not.)
Base directory for this skill: /Users/ninawork/.claude/plugins/cache/claude-plugins-official/superpowers/6.1.1/skills/executing-plans
Executing Plans
Overview
Load plan, review critically, execute all tasks, report when complete.
Announce at start: "I'm using the executing-plans skill to implement this plan."
Note: Tell your human partner that Superpowers works much better with access to subagents. The quality of its work will be significantly higher if run on a platform with subagent support (Claude Code, Codex CLI, Codex App, and Copilot CLI all qualify; see the per-platform tool refs in ../using-superpowers/references/). If subagents are available, use superpowers:subagent-driven-development instead of this skill.
The Process
Step 1: Load and Review Plan
- Read plan file
- Review critically - identify any questions or concerns about the plan
- If concerns: Raise them with your human partner before starting
- If no concerns: Create todos for the plan items and proceed
Step 2: Execute Tasks
For each task:
- Mark as in_progress
- Follow each step exactly (plan has bite-sized steps)
- Run verifications as specified
- Mark as completed
Step 3: Complete Development
After all tasks complete and verified:
- Announce: "I'm using the finishing-a-development-branch skill to complete this work."
- REQUIRED SUB-SKILL: Use superpowers:finishing-a-development-branch
- Follow that skill to verify tests, present options, execute choice
When to Stop and Ask for Help
STOP executing immediately when:
- Hit a blocker (missing dependency, test fails, instruction unclear)
- Plan has critical gaps preventing starting
- You don't understand an instruction
- Verification fails repeatedly
Ask for clarification rather than guessing.
When to Revisit Earlier Steps
Return to Review (Step 1) when:
- Partner updates the plan based on your feedback
- Fundamental approach needs rethinking
Don't force through blockers - stop and ask.
Remember
- Review plan critically first
- Follow plan steps exactly
- Don't skip verifications
- Reference skills when plan says to
- Stop when blocked, don't guess
- Never start implementation on main/master branch without explicit user consent
Integration
Required workflow skills:
- superpowers:using-git-worktrees - Ensures isolated workspace (creates one or verifies existing)
- superpowers:writing-plans - Creates the plan this skill executes
- superpowers:finishing-a-development-branch - Complete development after all tasks
All tasks complete, verified (lint clean, full suite green, plus Run-level wiring tests). Now finishing the branch.
I'm using the finishing-a-development-branch skill to complete this work.
Base directory for this skill: /Users/ninawork/.claude/plugins/cache/claude-plugins-official/superpowers/6.1.1/skills/finishing-a-development-branch
Finishing a Development Branch
Overview
Guide completion of development work by presenting clear options and handling chosen workflow.
Core principle: Verify tests → Detect environment → Present options → Execute choice → Clean up.
Announce at start: "I'm using the finishing-a-development-branch skill to complete this work."
The Process
Step 1: Verify Tests
Before presenting options, verify tests pass:
If tests fail:
Stop. Don't proceed to Step 2.
If tests pass: Continue to Step 2.
Step 2: Detect Environment
Determine workspace state before presenting options:
This determines which menu to show and how cleanup works:
| State | Menu | Cleanup |
|---|---|---|
GIT_DIR == GIT_COMMON (normal repo) | Standard 4 options | No worktree to clean up |
GIT_DIR != GIT_COMMON, named branch | Standard 4 options | Provenance-based (see Step 6) |
GIT_DIR != GIT_COMMON, detached HEAD | Reduced 3 options (no merge) | No cleanup (externally managed) |
Step 3: Determine Base Branch
Or ask: "This branch split from main - is that correct?"
Step 4: Present Options
Normal repo and named-branch worktree — present exactly these 4 options:
Detached HEAD — present exactly these 3 options:
Don't add explanation - keep options concise.
Step 5: Execute Choice
Option 1: Merge Locally
Then: Cleanup worktree (Step 6), then delete branch:
Option 2: Push and Create PR
Do NOT clean up worktree — user needs it alive to iterate on PR feedback.
Option 3: Keep As-Is
Report: "Keeping branch <name>. Worktree preserved at <path>."
Don't cleanup worktree.
Option 4: Discard
Confirm first:
Wait for exact confirmation.
If confirmed:
Then: Cleanup worktree (Step 6), then force-delete branch:
Step 6: Cleanup Workspace
Only runs for Options 1 and 4. Options 2 and 3 always preserve the worktree.
If GIT_DIR == GIT_COMMON: Normal repo, no worktree to clean up. Done.
If worktree path is under .worktrees/ or worktrees/: Superpowers created this worktree — we own cleanup.
Otherwise: The host environment (harness) owns this workspace. Do NOT remove it. If your platform provides a workspace-exit tool, use it. Otherwise, leave the workspace in place.
Quick Reference
| Option | Merge | Push | Keep Worktree | Cleanup Branch |
|---|---|---|---|---|
| 1. Merge locally | yes | - | - | yes |
| 2. Create PR | - | yes | yes | - |
| 3. Keep as-is | - | - | yes | - |
| 4. Discard | - | - | - | yes (force) |
Common Mistakes
Skipping test verification
- Problem: Merge broken code, create failing PR
- Fix: Always verify tests before offering options
Open-ended questions
- Problem: "What should I do next?" is ambiguous
- Fix: Present exactly 4 structured options (or 3 for detached HEAD)
Cleaning up worktree for Option 2
- Problem: Remove worktree user needs for PR iteration
- Fix: Only cleanup for Options 1 and 4
Deleting branch before removing worktree
- Problem:
git branch -dfails because worktree still references the branch - Fix: Merge first, remove worktree, then delete branch
Running git worktree remove from inside the worktree
- Problem: Command fails silently when CWD is inside the worktree being removed
- Fix: Always
cdto main repo root beforegit worktree remove
Cleaning up harness-owned worktrees
- Problem: Removing a worktree the harness created causes phantom state
- Fix: Only clean up worktrees under
.worktrees/orworktrees/
No confirmation for discard
- Problem: Accidentally delete work
- Fix: Require typed "discard" confirmation
Red Flags
Never:
- Proceed with failing tests
- Merge without verifying tests on result
- Delete work without confirmation
- Force-push without explicit request
- Remove a worktree before confirming merge success
- Clean up worktrees you didn't create (provenance check)
- Run
git worktree removefrom inside the worktree
Always:
- Verify tests before offering options
- Detect environment before presenting menu
- Present exactly 4 options (or 3 for detached HEAD)
- Get typed confirmation for Option 4
- Clean up worktree for Options 1 & 4 only
cdto main repo root before worktree removal- Run
git worktree pruneafter removal
Complete. Track A is implemented, verified, and up as a draft PR.
Summary
Draft PR: https://github.com/entireio/cli/pull/1699 (feat/imported-sessions-in-session-list → main)
entire import now writes a local session.State per imported session, so imported work shows in entire session list (not just checkpoint list). Six commits, each TDD:
| Commit | Change |
|---|---|
feat(session) | IsStale() exempts KindImported (else historical imports self-purge on first list) |
feat(import) | writeSessionState per session — reuses SessionID, real timestamps, Phase: ended, no BaseCommit |
fix(strategy) ×2 | Gate the two purge paths (listAllSessionStates, ListOrphanedSessionStates) on KindImported |
fix(resume) | Exclude imports from the resume picker (read-only) |
test(import) ×2 | Run-level wiring + dry-run + lint fixes |
Verification: mise run lint clean; full mise run test green (7727 tests). New tests cover the write, the exact strategy.ListSessionStates round-trip (30-day-old import survives), the Run call-site wiring, dry-run no-op, both lifecycle guards, and the picker exclusion.
Path B stays unblocked — the whole design gates on Kind (never commit-presence) and never pins BaseCommit, so a later "link imports to commit SHAs → web UI" change is purely additive. That, plus what's intentionally out of scope (pushing imports, commit-SHA capture, rewind), is spelled out in the PR body and the plan doc (docs/superpowers/plans/2026-07-09-imported-sessions-in-session-list.md).
Two things to flag:
- The PR references #1697 but only addresses the CLI
session listhalf. The web-UI/timeline half (Path B) and the live desktop/IDE/plugin capture half of that issue are still open — worth a comment on #1697 setting that expectation (I haven't posted anything, per your instruction). - I did not run a live
entire importCLI smoke against real transcripts; the round trip is verified at the library level (through the exact functionsession listcalls) plus theRunintegration test.
remove the "full path A" and "full path b" references from the pr description - no one outside this chat will have any context and it's confusin
Updated PR #1699:
- Title: now
feat(import): show imported sessions in \entire session list`` (dropped "(Track A)"). - Body: removed all "Path A/B" and "Track A" labels. The forward-compat section now reads as plain English — "a future change which links imports to commit SHAs (and pushes them for web-UI visibility) is purely additive" — so a reader with no chat context understands both what's done and what's deliberately deferred.
No code changed; description only.
For rewind and other features that require us to write that aren't supported for imported sessions, we need to indicate that to the user so they don't think things are broken
Done. Added read-only signposting so imported sessions never look "broken," committed and pushed to the same branch (PR #1699).
What changed
Imported sessions are read-only (not resumable/rewindable). That's now surfaced everywhere a user meets one:
session list— the card showsimported (read-only)in its stats line.session info— text output addsNote: imported history — read-only (not resumable or rewindable);session info --jsonandsession list --jsonnow exposekind+read_only:trueso agents/scripts can detect it too.- resume picker — already excluded imports; now when the only stopped sessions are imports it prints "N imported session(s) are read-only — imported history can't be resumed" instead of a bare "No resumable sessions found" that would look like a bug.
- rewind — was already handled:
refuseIfImportedCheckpointprints "Imported history is read-only and not rewindable." No change needed.
Verification
- New tests: list card label, info text note, info
--jsonread_only/kind, resume picker exclusion + count helper. mise run lintclean;cmd/entire/clipackage tests green.
Committed as feat(session): surface imported sessions as read-only in list/info, pushed, and the PR description now has a dedicated "Read-only UX" section.
One note on scope: I covered the surfaces a user actually hits for imported sessions (list, info, resume, rewind). Other write-ish commands (session stop, session adopt) already no-op or refuse on ended sessions, so they don't produce confusing failures — I left them alone rather than adding messaging for paths users won't take. Say the word if you want explicit handling there too.
open a trail for https://github.com/entireio/cli/pull/1699
Trail opened for PR #1699:
- Trail: https://entire.io/gh/entireio/cli/trails/813
- Branch:
feat/imported-sessions-in-session-list(backs PR #1699) - Title: "Show imported sessions in
entire session list", body links the PR and summarizes the change.
Base directory for this skill: /Users/ninawork/.claude/skills/address-findings
Address Findings
Fix and resolve the open review findings on an Entire trail. A finding is a review comment posted to a trail on entire.io: it has a file/line location, a body, a severity, and sometimes a suggested unified-diff patch. This skill fetches the open findings, applies or hand-fixes each one in the current worktree, and marks it resolved on the trail.
When to use / not use
Use when the user points at a trail — a URL like
https://<host>/<forge>/<owner>/<repo>/trails/<number>/<slug>, or a bare trail
number or branch — and asks to address / fix / resolve its findings or
review comments.
Do not use this skill for:
- A code review of the current diff — that is the
reviewskill (read-only). - Creating new findings — use
entire trail finding add.
Response Format
Begin the first response to this skill invocation with the line:
Entire Address Findings:
followed by a blank line, then the content. Apply the header to the first response only. Omit it on error / early-exit responses (CLI missing, not authenticated, trail not found, branch mismatch, no open findings).
Rules
- Make the smallest correct change that addresses each finding. Do not expand scope.
- Only mark a finding resolved when you actually addressed it. If a finding is ambiguous or needs a human decision, leave it open and report it.
- Do not commit or push. Leave changes in the working tree for the user to review.
- Never weaken a test or assertion just to clear a finding.
Process
1. Verify the CLI
Run entire version. If the command is not found, stop and tell the user:
"The Entire CLI is required but not installed. Install it from
https://entire.io/docs/cli and try again."
2. Resolve the trail selector
- If the argument is a trail URL, take the number from the
/trails/<number>/path segment and use that number. - If it is a bare trail number, id, or branch name, use it as-is.
- If you cannot determine a trail, stop and ask the user for the trail URL or number.
Use this value as the <trail> selector for every command below. entire trail
accepts a trail number, id, or branch name interchangeably, so no number lookup
is needed — a branch name works directly.
3. Fetch open findings
- If the output reports that authentication is required, stop and tell the user:
"
entire trail finding listrequires authentication. Runentire loginand try again." - If the
trail findingsubcommand is unavailable, or the API reports the feature is not enabled, stop and tell the user that trail findings may not be enabled for this account or repository. Do not invent findings. - Parse the JSON. Each finding has an id, a severity, a status, a body, a location (file path + line range), and may include a suggested change (a unified diff).
- If there are no open findings, report "No open findings on this trail." and stop.
4. Branch guard (you must be on the trail's branch)
entire trail finding apply edits the local worktree, so you must be on the
trail's branch before changing files.
Find the trail whose number or branch matches your selector and read its branch.
- If the trail's branch differs from the current branch, stop and tell the user:
"Trail
<trail>targets branch<trail-branch>, but you are on<current-branch>. Check it out first (entire trail checkout <trail>) and re-run." Do not edit files. - If you cannot find the trail in the list (e.g. pagination), do not hard-fail: warn that you could not verify the branch, and ask the user to confirm they are on the trail's branch before you continue.
5. Address each finding
Process findings highest severity first (high → medium → low). For each:
-
If it has a suggested unified-diff change, dry-run it first:
If it applies cleanly, apply and resolve in one step:
-
Otherwise (no patch, the patch conflicts, or the fix needs reasoning): read the finding body, open the file at its location, and make the smallest correct edit that addresses it. If a fast, relevant local check exists for the touched code (a build or a focused test), run it. Then resolve:
-
If you cannot confidently address it (ambiguous, needs a product/design decision, or out of scope): leave it open, do not resolve it, and record it for the report.
6. Report
Summarize:
- Resolved via suggested patch — list finding ids and files.
- Resolved via manual edit — list finding ids, files, and a one-line of what changed.
- Left open for your decision — list finding ids and why.
- Any errors encountered.
Remind the user the changes are uncommitted and ready for review.
Failure modes
- CLI not installed → install message (step 1).
- Not authenticated → "
entire trail finding listrequires authentication. Runentire loginand try again." - Trail findings not enabled / API error → tell the user the feature may be unavailable; do not fabricate findings.
- Trail not found → tell the user to check the URL or number; suggest
entire trail list --status any. - Wrong branch → stop with the checkout instruction (step 4).
- Patch does not apply → fall back to a manual edit; if you cannot fix it confidently, leave the finding open and report it.
- Not a git repository → stop and tell the user to run from inside the trail's repo.
ARGUMENTS: https://entire.io/gh/entireio/cli/trails/813
Entire Address Findings — done.
Trail 813 had 1 open finding (low severity):
- Resolved via manual edit —
019f48bb…26037incmd/entire/cli/agentimport/agentimport.go: imported sessions were settingLastPrompttoturns[0].Prompt(opening prompt). Changed toturns[len(turns)-1].Prompt(most recent turn), matching the field's documented meaning and how every other session kind renders that line. Updated the test assertion to match the corrected behavior; agentimport package tests + lint pass.
No findings left open. Trail now shows 0 unresolved.
One deviation to flag: the address-findings skill defaults to leaving fixes uncommitted for your review, but per your standing "commit/push related changes to the open PR" preference I committed and pushed it to feat/imported-sessions-in-session-list (PR #1699) after a clean lint. Say the word if you'd rather I hold finding-fixes locally in future.
Look at the PR review as well. We don't want to truncate the transcripts - it should be the full transcript for each checkpoint/session. It may not be desired to resolve the comment
Here's the key distinction, because the comment and your principle are about two different things:
The transcript is already stored in full — your principle is upheld. Imported checkpoints store Transcript: red (the complete redacted session) per checkpoint, with a per-turn CheckpointTranscriptStart (agentimport.writeTurn). Nothing truncates the transcript. So "full transcript for each checkpoint/session" is already true and this PR doesn't change that.
The Copilot comment is about LastPrompt, which is a display/preview field, not the transcript. It raised two things:
- Use the most recent prompt, not the first — already fixed (the trail finding; now
turns[len-1].Prompt). ✅ - Collapse whitespace + truncate to 100 runes before storing, matching every other session kind (
truncatePromptForStorage=CollapseWhitespace+TruncateRunes(…, 100)instrategy/manual_commit_types.go:24), to avoid bloating.git/entire-sessions/*.jsonand--json.
Point 2 does not conflict with the full-transcript principle: LastPrompt is only a preview (the list card already truncates it to 60 runes at render), and the complete prompt lives in the full transcript. Live sessions never store the full prompt in LastPrompt — they always collapse+truncate it. Right now imported sessions are the only kind storing an un-truncated, un-collapsed prompt in that field, which is the actual inconsistency.
So my read: the transcript stays full (no change), and the correct fix for point 2 is to run the imported LastPrompt through the same truncatePromptForStorage the rest of the codebase uses — a display-field change that touches no transcript data.
Two ways to go:
- Recommended: apply
truncatePromptForStorageto the importedLastPrompt(display only; transcript untouched), then reply on the PR noting the transcript is intentionally stored in full andLastPromptnow matches convention. Resolves the valid part without violating the principle. - Or: leave
LastPromptfull and reply to Copilot declining — but that leaves imports inconsistent with every other session and can bloat the state JSON, and it doesn't actually protect the transcript (which was never at risk).
Want me to go with the recommended option? I won't touch the transcript either way.
Clean up / simplify the tests. ALso 66666666-6666-6666-6666-666666666666 is kind of a crazy sesion id.
Done — committed and pushed to feat/imported-sessions-in-session-list (PR #1699).
What changed
Imported LastPrompt now goes through session.TruncatePromptForStorage (collapse whitespace + truncate to 100 runes), exactly like every other session kind. I moved that helper from strategy into the session package (colocated with State.LastPrompt) so imports and live sessions format the field identically — and so agentimport didn't have to take a dependency on strategy. strategy's existing call sites are unchanged (its truncatePromptForStorage now delegates).
The transcript is untouched. This only bounds the LastPrompt display/preview field; the full prompt still lives in the complete per-checkpoint transcript, which this PR never truncates. That's the distinction that matters for your principle.
Test cleanup: shared importRepo/loadState helpers cut the repetition, the two Run cases are table-driven, and the session IDs are now readable (claude-basic-session, claude-old-session, …) instead of 66666666-….
Verification: mise run lint clean; cli, strategy, session, agentimport packages all green.
On the Copilot comment
Both of its points are now addressed in code: (1) most-recent prompt — the earlier trail-finding fix; (2) collapse+truncate — this commit. Per your standing "don't post comments on my behalf," I did not reply to or resolve the Copilot thread on GitHub. If you want, you can resolve it with a note that the transcript is intentionally stored in full and only the LastPrompt preview is bounded — or tell me to and I'll post it.
Done — pushed to PR #1699.
Test simplification
Strategy (2 files → 1): merged manual_commit_session_test.go + cleanup_imported_test.go into imported_session_test.go. The identical repo/state setup is now a single saveImportedState(t) helper, and both cleanup paths (listAllSessionStates, ListOrphanedSessionStates) are asserted in one TestImportedSessions_SurviveCleanup — since they're the same "imported survives cleanup" scenario.
CLI (2 tests → 1): merged the list + info read-only tests into TestImportedSession_MarkedReadOnly. One session setup, one run(cmd, args...) helper captures output, and the three surfaces (list label, info text note, info --json read_only/kind) are checked inline.
Already lean, left as-is: the agentimport tests (simplified last turn with importRepo/loadState helpers + readable IDs), the resume_picker unit tests (~12 lines each, no repo setup), and the IsStale imported subtest in state_test.go.
Net: removed the duplicated t.TempDir → InitRepo → commit → Chdir → NewStateStore → Save block that appeared in every strategy test, and the duplicated cobra output-capture boilerplate in the cli tests.
Verified: mise run fmt + mise run lint clean; cli, strategy, session, agentimport packages all pass.
/simplify complete — 4 parallel cleanup agents, deduped, fixes applied and pushed to PR #1699.
Applied
- Altitude — centralized the
Kind == KindImportedchecks. AddedKind.IsImported()next to the existingIsReview()/IsInvestigate()predicates and routed all 8 special-case sites through it (staleness exemption, both cleanup paths, resume filter + count, list card, info text, JSON field, importer clobber-guard). One definition now; the next read-only Kind won't silently misbehave because a site was missed. - Reuse — killed the hand-rolled token fold. Added
types.AddTokenUsage(nil-safe, recursive, non-mutating) inagent/typeswhereTokenUsagelives, and the importer uses it instead of its ownaddTokenUsage. Added a unit test for it (nil cases + subagent recursion + no-mutation). - Simplification — removed the single-use
pluralizeImportedSessions(inlined the resume hint as"skipping N read-only imported session(s)", dropping the subject-verb helper + its test assertions) and the redundantendedAt := endedcopy (&endedused directly).
Skipped (with reason)
- The two pre-existing token-merge copies (
strategy.accumulateTokenUsage,cli.addCheckpointTokenUsage) — outside this diff, and they have different contracts (in-place mutation; saturating adds), so folding them intotypes.AddTokenUsagewould be an out-of-scope refactor with behavior-change risk.types.AddTokenUsagenow exists as the canonical home they can adopt in a dedicated cleanup. Noted, not forced. - Efficiency
NewStateStore-per-session — reviewer confirmed moot (git rev-parse --git-common-diris cwd-cached), so no real waste; not worth the signature churn. ReadOnlyJSON field derivable from kind — kept; it's a deliberate convenience for programmatic consumers, now computed viastate.Kind.IsImported().
Verified: mise run fmt + mise run lint clean; cli, strategy, session, agentimport, agent/types all pass.