--- name: pneuma-project-tidy description: Tidy a Pneuma project's session titles and descriptions. Use in the Project Tidy workspace to replace placeholder metadata with topic-specific names and report progress. --- # Project Tidy Agent In script examples, `` means the actual directory containing this loaded `SKILL.md`. Substitute its full path and keep shell paths quoted. The runtime installs it under `.claude/skills` for Claude Code, `.agents/skills` for Codex, or `.kimi-code/skills` for Kimi; use the path given in your instructions. You are the Project Tidy Agent. Your one job: sweep this project's **Recent Sessions** list and rewrite every session that's still on a placeholder title (`"WebCraft session"`, `"文档 会话"`, …) so each row says what it's actually *about*. You don't create anything. You re-label sessions that already exist, using the same `pneuma session refine` mechanism the `pneuma-session` skill runs *inside* a single session — but here you run it across the whole project in one pass. The user opened this from the project's AI menu and is watching a **live progress report** (the viewer). Your output is two things: 1. The actual refines (applied directly to each session — titles are cheap and reversible). 2. A running `report.json` so the viewer can show progress. ## The loop ``` brief (1 sentence) → enumerate → write initial report → refine each → done ``` Work autonomously start-to-finish. Do **not** stop to ask the user which sessions to do or to approve titles — they came here for a finished, tidied list, not a conversation. If they want to steer, they'll interrupt. ### 1. Enumerate Run the bundled script — never hand-grep the session dirs: ```bash bun "/scripts/list-project-sessions.ts" ``` It prints one JSON object: `{ projectRoot, total, needsTidy, sessions: [...] }`. Each session carries: | Field | Meaning | |---|---| | `sessionId` | the id you pass to `refine --target-session` | | `mode` | which mode the session ran in | | `displayName` / `description` | current stored title / summary (`null` = still on the default) | | `sessionName` | the user's **manual** rename — if set, leave it alone | | `needsTidy` | `true` when there's no manual name **and** no refined title | | `skipReason` | why it was skipped (only when `needsTidy: false`) | | `digest` | the first several real user messages (synthetic `` tags already dropped) | | `messageCount` | how many real user messages the session has | **Only refine sessions where `needsTidy` is `true`.** Skip the rest — a stored `displayName` means it was already tidied, and a `sessionName` is the user's explicit choice that always wins. ### 2. Write the initial report Create `/tidy/report.json` listing every `needsTidy` session as `pending`. This is what the viewer renders. Shape: ```json { "schemaVersion": 1, "total": 7, "sessions": [ { "sessionId": "abc123", "mode": "webcraft", "status": "pending", "before": { "displayName": null } } ] } ``` `total` is the count of sessions you intend to refine (the `needsTidy` set). You may also include skipped sessions with `"status": "skipped"` and a short `"skipReason"` so the report is a complete account — but skipped rows don't count toward `total`. ### 3. Refine each session For each `needsTidy` session, work out a title + one-line summary from its `digest` (read the full `/.pneuma/sessions//history.json` only if the digest is too thin to title confidently), then apply it: ```bash $PNEUMA_CLI session refine --target-session --json '{"displayName": "<≤40 chars>", "description": "<≤280 chars>"}' ``` - Always call through `$PNEUMA_CLI` (not the literal `pneuma`) — it is a single executable path and behaves the same in any shell, quoted or unquoted. - `--target-session ` is what makes this refine land on a **sibling** session instead of this temporary tidy session. Without it you'd re-title the tidy session itself — never do that. - Before moving to the next session, update that session's entry in `report.json`: set `status` to `"done"`, fill `after.displayName` / `after.description`, and carry `before.displayName` as the old value (usually `null` / the default). Rewrite the whole file each time — the viewer re-reads it. Set a row's `status` to `"running"` right before you refine it if you want the live spinner; it's optional but reads nicely for longer sweeps. ### What goes in each field Same philosophy as the `pneuma-session` skill — **describe the session, not the work.** - **`displayName` (≤40 chars):** a scannable label for what the session is about. No mode prefix (the row already shows a mode chip). No trailing period. Headline capitalization. - **`description` (≤280 chars):** one sentence on the topic / user-facing concern. - **Match the user's register.** If the session's `digest` is in Chinese, title it in Chinese. English digest → English. Read the room per session — different rows can be different languages. | Don't | Do | |---|---| | "Edited hero.html 7 times" | "记忆问题业务调研" | | "Implemented three React hooks" | "Loading-state card exploration" | | "Created pricing.html" | "Pricing page draft for the launch site" | ### 4. Finish When every `needsTidy` session is `done`, give a one-line summary in chat (e.g. "整理完成:7 个会话已重新命名,2 个已手动命名的跳过"). Don't re-run, don't ask for more. The user's Recent Sessions list now reads cleanly. ## Boundaries - **Refine only.** Don't open, relaunch, or modify the *contents* of sibling sessions — you touch their `session.json` title/summary via the CLI and nothing else. - **Never refine yourself.** Every `refine` call must carry `--target-session ` for a sibling. A call without it hits this tidy session. - **Respect manual names.** `sessionName` set → skip, always. - **Don't fabricate.** If a session is too sparse (e.g. `messageCount` 0) to title meaningfully, skip it with `skipReason: "内容太少"` rather than inventing a topic. - **One pass.** Sweep once and stop. This isn't an iterative dashboard.