diff --git a/dev/plans/26174-planctl-context-tokens/tasks.md b/dev/plans/26174-planctl-context-tokens/tasks.md new file mode 100644 index 0000000..306fc77 --- /dev/null +++ b/dev/plans/26174-planctl-context-tokens/tasks.md @@ -0,0 +1,76 @@ +# Tasks — planctl Context-Window Token Awareness + +Source PRD: `prd.md` +Source design: `design.md` + +## Relevant Files + +- `cmd/planctl/context.go` — new: `TokenCtx`, `ReadTokenCtx`, `resolveSessionUUID`, `transcriptPath`, `parseLastUsage`, `readLimit`, `Classify`, `formatTokenCount`, `parsePIDFile`. +- `cmd/planctl/context_test.go` — new: unit tests for all pure functions + integration-style `ReadTokenCtx` fixture test. +- `cmd/planctl/proc_darwin.go` — new: `ppidOf`, `processAlive`, `processStartTimeSec` on macOS via `syscall.SysctlKinfoProc` (build tag `//go:build darwin`). +- `cmd/planctl/proc_linux.go` — new: same surface on Linux via safe `/proc//stat` parsing (build tag `//go:build linux`). +- `cmd/planctl/proc_test.go` — new: platform-agnostic tests exercising helpers against the calling process PID. +- `cmd/planctl/main.go` — modified: call `ReadTokenCtx()` once in subcommand dispatch and thread `*TokenCtx` into emitters. +- `cmd/planctl/main_test.go` — modified: extend golden-fixture harness for per-fixture env vars and staged transcript JSONL. +- `cmd/planctl/emit.go` — modified: `emitText` / `emitJSON` take `*TokenCtx`; append context line / `context_window` key; extend `jsonSummary` with `*jsonCtxWin`. +- `cmd/planctl/emit_test.go` — modified: update call sites for new signature; add context-emission tests. +- `cmd/planctl/testdata/v2/context-info/` — new: 71% fill fixture (text). +- `cmd/planctl/testdata/v2/context-warn/` — new: 87% fill fixture (text). +- `cmd/planctl/testdata/v2/context-error/` — new: 96% fill fixture (text). +- `cmd/planctl/testdata/v2/context-normal/` — new: 50% fill — no context line emitted. +- `cmd/planctl/testdata/v2/context-no-session/` — new: no env vars — baseline unchanged. +- `cmd/planctl/testdata/v2/context-no-limit/` — new: session set, limit unset — raw count + `(limit unknown)`. +- `cmd/planctl/testdata/v2/context-json-warn/` — new: JSON mode at warn band. +- `cmd/planctl/testdata/v2/context-multi-plan/` — new: single context line after aggregate summary. +- `scripts/jj-hook.sh` — modified: session tracking with UUID validation + `planctl_start_ticks` helper. +- `scripts/test-jj-hooks.sh` — modified: PID-file lifecycle assertion. +- `INTEGRATION.md` — modified: context-window section with hook snippets and env var docs. + +### Notes + +- Run tests with `go test ./cmd/planctl/...`. Hook integration test: `bash scripts/test-jj-hooks.sh`. +- VCS convention per `AGENTS.md`: jj feature branch off `main`, parent-task commits at each checkpoint. +- Source-code traceability tag format: `// spec:26174-planctl-context-tokens/`. Example: `// spec:26174-planctl-context-tokens/R2.1`. + +## Instructions for Completing Tasks + +As you complete each task, flip `[ ]` to `[x]` in this file. Update after each sub-task, not just per parent. + +## Tasks + +- [ ] 0.0 Create feature branch _Requirements: infra_ + - [ ] 0.1 Start jj change on `main`: `jj new main -m "feat: 26174-planctl-context-tokens"` _Requirements: infra_ + - [ ] 0.2 Create bookmark: `jj bookmark create feature/26174-planctl-context-tokens` _Requirements: infra_ + +- [ ] 1.0 Implement `context.go`, platform helpers, and unit tests _Requirements: R1.1-R1.6, R2.1-R2.6, R3.1-R3.3, R3.5, R4.4, R6.1-R6.3_ _Design: D§1, D§3.1-3.9, D§6.1, D§7.1_ + - [ ] 1.1 Create `cmd/planctl/context.go` with `TokenCtx` struct and `formatTokenCount(n int64) string` per design §3.1/§3.8. Add `cmd/planctl/context_test.go` with `TestFormatTokenCount` covering n=0→`< 1 k`, n=500→`< 1 k`, n=999→`< 1 k`, n=1000→`1 k`, n=1499→`1 k`, n=1500→`2 k`. _Requirements: R2.2, R2.4, R4.4_ _Design: D§3.1, D§3.8, D§7.1_ + - [ ] 1.2 Add `(t *TokenCtx) Classify() (severity, msg string)` per design §3.7. Add `TestClassify_bands` (pct 0/69/70/84/85/94/95/100/nil) and `TestClassify_unknownLimit` (`Limit == 0` → `("", "")`). _Requirements: R3.1, R3.2, R3.3, R3.5_ _Design: D§3.7, D§7.1_ + - [ ] 1.3 Add `readLimit() int64` per design §3.6 — reads `CLAUDE_CODE_MAX_CONTEXT_TOKENS`, returns 0 on unset/invalid. Add `TestReadLimit_set` / `_unset` / `_invalid`. _Requirements: R2.4_ _Design: D§3.6, D§7.1_ + - [ ] 1.4 Add `transcriptPath(uuid, home, projectDir string) string` per design §3.4 — replace `/` with `-`, fall back to `os.Getwd()`. Add `TestTranscriptPath_slash`, `_nested`, `_fallbackCwd`. _Requirements: R1.5, R1.6_ _Design: D§3.4, D§7.1_ + - [ ] 1.5 Add `parsePIDFile(path string) (uuid string, startStamp int64, err error)` per design §3.9 — two lines, second parses as int64. Add `TestParsePIDFile_valid`, `_missingLine2`, `_malformedInt`. _Requirements: R6.2, R6.3_ _Design: D§3.9, D§4.1, D§7.1_ + - [ ] 1.6 Add `parseLastUsage(path string) (int64, error)` per design §3.5 — `bufio.Scanner` 10 MB buffer, forward scan, last `type=assistant` with present `message.usage` wins (presence per PRD R2.1, not non-zero). Skip decode errors. Return `errNoUsageLine` when none. Add `TestParseLastUsage_valid` / `_malformedMid` / `_noAssistant` / `_emptyFile` / `_zeroUsageStillQualifies`. _Requirements: R2.1, R2.2, R2.3, R2.6_ _Design: D§3.5, D§4.2, D§7.1_ + - [ ] 1.7 Add `proc_darwin.go` (`//go:build darwin`) using `syscall.SysctlKinfoProc` for `ppidOf`/`processStartTimeSec`. Add `proc_linux.go` (`//go:build linux`): `ppidOf` reads PPid from `/proc//status`; `processStartTimeSec` reads starttime from `/proc//stat` via safe parsing — `strings.LastIndex` to find last `)`, then field 20 of remainder (raw clock ticks since boot; hook writes ticks too per §5.5/§4.1, so comparison units match). Add `proc_test.go` exercising all three against `os.Getpid()`/`os.Getppid()`. _Requirements: R1.2, R6.3_ _Design: D§3.3, D§7.3_ + - [ ] 1.8 Add `resolveSessionUUID() string` per design §3.3 — env var first; then up-to-8-hop PID walk. Use unexported `var pidSessionDir = "/tmp/planctl-sessions"` for testability. Add `TestResolveSessionUUID_envWins` / `_pidWalkFinds` / `_staleSkipped` / `_startTimeMismatch` / `_exhausted`. _Requirements: R1.1, R1.2, R1.3, R1.4, R6.1, R6.3_ _Design: D§3.3, D§6.2, D§6.3, D§7.1_ + - [ ] 1.9 Wire `ReadTokenCtx() *TokenCtx` per design §3.2 — compose resolveSessionUUID → transcriptPath → parseLastUsage → readLimit; nil at every failure. Add `TestReadTokenCtx_happyPath` and `_noSession`. No subprocesses invoked (D§6.4). _Requirements: R1.1-R1.6, R2.1-R2.6_ _Design: D§3.2, D§6.4, D§7.1_ + +- [ ] 2.0 Wire token context into planctl output (`main.go`, `emit.go`, E2E golden tests) _Requirements: R3.4, R4.1-R4.5, R5.1-R5.4_ _Design: D§4.3, D§4.4, D§5.3, D§5.4, D§7.2_ + - [ ] 2.1 Extend `emit.go` types: add `jsonCtxWin` struct with `TokensUsed int64`, `TokensLimit *int64`, `Pct *int`, `Severity string`, `Recommendation string` (pointer fields `omitempty`); extend `jsonSummary` with `ContextWin *jsonCtxWin`. _Requirements: R5.1, R5.2, R5.3_ _Design: D§5.4_ + - [ ] 2.2 Change `emitText` and `emitJSON` signatures to accept `*TokenCtx`. Text: canonical context line for Info/Warn/Error, unknown-limit form, multi-plan once after aggregate, Normal/nil silent. JSON: `context_window` for Info/Warn/Error, omit for Normal/nil; context never changes error/warning counts. NOTE: 2.1, 2.2, 2.3 must all land in the same parent-task commit — signature change breaks callers immediately. _Requirements: R3.4, R3.5, R4.1-R4.3, R4.5, R5.1-R5.4_ _Design: D§4.3, D§4.4, D§5.3_ + - [ ] 2.3 Update `main.go`: call `ReadTokenCtx()` once at entry; thread `ctx` to `emitText`/`emitJSON` for all subcommands. Must land with 2.1/2.2 (see note on 2.2). _Requirements: R3.4_ _Design: D§1, D§2_ + - [ ] 2.4 Update `emit_test.go` and `main_test.go` call sites to pass `nil` ctx where no context asserted. Add `emit_test.go` cases: text info/warn/error band, unknown-limit, Normal omission, JSON known+unknown shapes, multi-plan placement. _Requirements: R4.1-R4.5, R5.1-R5.4_ _Design: D§4.3, D§4.4, D§7.2_ + - [ ] 2.5 Extend golden-fixture harness: if `env.txt` exists load KEY=VALUE via `t.Setenv`; if `transcript.jsonl` exists stage in temp HOME at `~/.claude/projects//.jsonl`. _Requirements: R3.4, R4.1_ _Design: D§7.2_ + - [ ] 2.6 Create `testdata/v2/context-info/`, `context-warn/`, `context-error/`, `context-normal/`, `context-no-session/`, `context-no-limit/` — each with `prd.md`, `tasks.md`, `env.txt`, `transcript.jsonl` (where applicable), `expected.golden`, `expected.exit`. _Requirements: R3.2, R3.3, R3.5, R4.1, R4.2, R4.5_ _Design: D§4.3, D§7.2_ + - [ ] 2.7 Create `testdata/v2/context-json-warn/` (warn band, JSON format) and `testdata/v2/context-multi-plan/` (two plan dirs, context once after aggregate). _Requirements: R4.3, R5.1-R5.4_ _Design: D§4.3, D§4.4, D§7.2_ + - [ ] 2.8 Run `go test ./cmd/planctl/...`; all existing fixtures byte-identical, eight new context fixtures pass. _Requirements: R4.5_ _Design: D§7.2_ + +- [ ] 3.0 Extend `scripts/jj-hook.sh` with session tracking _Requirements: R7.1, R7.2, R7.3_ _Design: D§4.1, D§5.1, D§5.5, D§7.4_ + - [ ] 3.1 Add `planctl_start_ticks()` per design §5.5: Linux — safe `/proc//stat` parse via `sed 's/.*)//' | awk '{print $20}'`; macOS — `ps -o lstart=` → `date` epoch seconds. Returns 0 on failure. _Requirements: R6.2, R7.3_ _Design: D§5.5_ + - [ ] 3.2 Extend `session-start`: parse `.session_id` via `jq`; validate UUID regex; append `CLAUDE_SESSION_ID` to `$CLAUDE_ENV_FILE`; `mkdir -p /tmp/planctl-sessions` (creates temp dir per §5.1); write two-line temp file via `mktemp` and `mv` atomically. _Requirements: R6.2, R7.1, R7.3_ _Design: D§4.1, D§5.1, D§5.5_ + - [ ] 3.3 Extend `session-end`: `rm -f /tmp/planctl-sessions/${PPID}`. _Requirements: R7.2, R7.3_ _Design: D§5.5_ + - [ ] 3.4 Extend `scripts/test-jj-hooks.sh`: assert PID file created on start with two lines (UUID + numeric stamp); assert removed on end. _Requirements: R7.1, R7.2_ _Design: D§7.4_ + - [ ] 3.5 Manual smoke in a live Claude Code session: PID file appears on start, `planctl status` emits context line at ≥ 70% fill, file removed on exit. _Requirements: R7.1, R7.2_ _Design: D§7.4_ + +- [ ] 4.0 Document hook integration in `INTEGRATION.md` _Requirements: R7.1, R7.2, R7.4_ _Design: D§5.5_ + - [ ] 4.1 Add context-window section: threshold bands, text line format, JSON `context_window` shape. _Requirements: R4.1, R4.2, R5.1_ _Design: D§4.3, D§4.4_ + - [ ] 4.2 Copy-paste `session-start`/`session-end` additions and `planctl_start_ticks` helper. _Requirements: R7.1, R7.2, R7.3_ _Design: D§5.5_ + - [ ] 4.3 Document `CLAUDE_CODE_MAX_CONTEXT_TOKENS`: raw count when unset; hooks optional; graceful degradation. _Requirements: R2.4, R3.3, R7.4_ _Design: D§5.2_