Compare commits
6
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fccb75298e | ||
|
|
3208abe464 | ||
|
|
08c7ca752a | ||
|
|
63b533e541 | ||
|
|
c4163373fb | ||
|
|
d3777cbbee |
@@ -32,7 +32,7 @@ This needs no root: `cpu memory pids` are already delegated to the user manager
|
|||||||
|
|
||||||
### The launcher
|
### The launcher
|
||||||
|
|
||||||
`dev-claude.fish` builds a launch prefix once and uses it at all four `exec` sites:
|
`dev-claude.fish` builds a launch prefix once and uses it at all four launch sites:
|
||||||
|
|
||||||
```fish
|
```fish
|
||||||
set -l launch /usr/bin/env claude
|
set -l launch /usr/bin/env claude
|
||||||
@@ -43,7 +43,7 @@ end
|
|||||||
|
|
||||||
Because `systemd-run --scope` **execs** rather than forks, the resulting process tree and command line are byte-identical to running `claude` directly. The only difference is which cgroup it lands in.
|
Because `systemd-run --scope` **execs** rather than forks, the resulting process tree and command line are byte-identical to running `claude` directly. The only difference is which cgroup it lands in.
|
||||||
|
|
||||||
## Two non-obvious things
|
## Three non-obvious things
|
||||||
|
|
||||||
These are the parts that will look wrong later and get "cleaned up". Don't.
|
These are the parts that will look wrong later and get "cleaned up". Don't.
|
||||||
|
|
||||||
@@ -74,9 +74,36 @@ ppid: bash # systemd-run exec'd; no extra process in the tree
|
|||||||
|
|
||||||
`env` also does the PATH lookup that `command claude` used to do here, so a fish function or alias named `claude` still cannot shadow the real binary.
|
`env` also does the PATH lookup that `command claude` used to do here, so a fish function or alias named `claude` still cannot shadow the real binary.
|
||||||
|
|
||||||
### 2. `command` cannot be used in the fallback
|
### 2. `dev-claude` must not `exec`
|
||||||
|
|
||||||
The natural fallback is `set -l launch command claude`, but fish rejects it:
|
This one bit once already: for months every Claude pane was saved, matched nothing, and restored nothing — silently.
|
||||||
|
|
||||||
|
tmux-resurrect's `ps` save strategy does not read the pane process. It reads the pane process's **children** (`save_command_strategies/ps.sh`):
|
||||||
|
|
||||||
|
```bash
|
||||||
|
ps -ao "ppid,args" | sed "s/^ *//" | grep "^${PANE_PID}" | cut -d' ' -f2-
|
||||||
|
```
|
||||||
|
|
||||||
|
`exec` makes Claude *become* the pane pid, so that grep returns Claude's own subprocesses instead of Claude. The saved `pane_full_command` came out as `socat UNIX-LISTEN:/tmp/claude-http-….sock …` — or empty, when nothing happened to be running — never `claude --session-id …`. Both fail `^claude `, so `@resurrect-processes` never fires.
|
||||||
|
|
||||||
|
Running Claude as a plain foreground child keeps fish at the pane pid, which is exactly where resurrect looks:
|
||||||
|
|
||||||
|
```
|
||||||
|
$ fish -c 'echo $fish_pid; /usr/bin/env sleep 3' # no exec
|
||||||
|
286 285 fish -c …
|
||||||
|
317 286 sleep 3
|
||||||
|
-> resurrect saves: [sleep 3]
|
||||||
|
|
||||||
|
$ fish -c 'echo $fish_pid; exec /usr/bin/env sleep 3'
|
||||||
|
328 285 sleep 3
|
||||||
|
-> resurrect saves: []
|
||||||
|
```
|
||||||
|
|
||||||
|
The cost is that the pane returns to a fish prompt when Claude exits rather than closing. That is the intended trade — a `dev` layout does not collapse when a session ends.
|
||||||
|
|
||||||
|
### 3. `command` cannot be used in the fallback
|
||||||
|
|
||||||
|
The natural fallback is `set -l launch command claude`. While `dev-claude` still used `exec`, fish rejected it outright:
|
||||||
|
|
||||||
```
|
```
|
||||||
fish: The expanded command is a keyword.
|
fish: The expanded command is a keyword.
|
||||||
@@ -84,7 +111,7 @@ set -l launch command echo; exec $launch ok
|
|||||||
^~~~~~^
|
^~~~~~^
|
||||||
```
|
```
|
||||||
|
|
||||||
`exec` will not accept an expansion that begins with a builtin. `/usr/bin/env claude` is used in both branches instead — same guarantee, and it expands cleanly.
|
`exec` will not accept an expansion that begins with a builtin. That specific constraint is gone with `exec`, but `/usr/bin/env claude` stays in both branches: it gives the same shadowing guarantee, and keeping the two branches identical means the saved command line does not depend on whether `systemd-run` was found.
|
||||||
|
|
||||||
## Limits, and why each one
|
## Limits, and why each one
|
||||||
|
|
||||||
@@ -141,13 +168,20 @@ Confirm a running session is actually inside the slice:
|
|||||||
systemd-cgls --user-unit claude.slice
|
systemd-cgls --user-unit claude.slice
|
||||||
```
|
```
|
||||||
|
|
||||||
Confirm tmux-resurrect is unaffected — this is the string its `ps` save strategy reads, and it must show a **bare** `claude`, not an absolute path:
|
Confirm tmux-resurrect is unaffected — this is the string its `ps` save strategy reads. It must be non-empty, must name `claude` and not one of its subprocesses, and must show a **bare** `claude`, not an absolute path:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
ps -ao ppid,args | grep "^<pane_pid>"
|
ps -ao ppid,args | grep "^$(tmux display -p '#{pane_pid}') "
|
||||||
# claude --session-id <uuid>
|
# claude --session-id <uuid>
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Then check what actually landed on disk, which is the thing restore reads:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
grep -a claude ~/.local/share/tmux/resurrect/last | cut -f11
|
||||||
|
# :claude --session-id <uuid>
|
||||||
|
```
|
||||||
|
|
||||||
### Testing containment safely
|
### Testing containment safely
|
||||||
|
|
||||||
Do **not** force an OOM inside `claude.slice` to test it. The kernel picks the largest consumer in the cgroup, which is very likely to be a real session rather than the test process. Use an unrelated top-level slice instead — note that systemd treats `-` as a hierarchy separator, so a name like `claude-test.slice` would nest *inside* `claude.slice` and hit the same problem:
|
Do **not** force an OOM inside `claude.slice` to test it. The kernel picks the largest consumer in the cgroup, which is very likely to be a real session rather than the test process. Use an unrelated top-level slice instead — note that systemd treats `-` as a hierarchy separator, so a name like `claude-test.slice` would nest *inside* `claude.slice` and hit the same problem:
|
||||||
|
|||||||
+50
-13
@@ -33,7 +33,32 @@
|
|||||||
],
|
],
|
||||||
"defaultMode": "auto"
|
"defaultMode": "auto"
|
||||||
},
|
},
|
||||||
|
"model": "opus[1m]",
|
||||||
|
"disableClaudeAiConnectors": true,
|
||||||
|
"remoteControlAtStartup": false,
|
||||||
"hooks": {
|
"hooks": {
|
||||||
|
"PostToolUse": [
|
||||||
|
{
|
||||||
|
"matcher": "Bash",
|
||||||
|
"hooks": [
|
||||||
|
{
|
||||||
|
"type": "command",
|
||||||
|
"command": "atuin hook claude-code"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"PostToolUseFailure": [
|
||||||
|
{
|
||||||
|
"matcher": "Bash",
|
||||||
|
"hooks": [
|
||||||
|
{
|
||||||
|
"type": "command",
|
||||||
|
"command": "atuin hook claude-code"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
"PreToolUse": [
|
"PreToolUse": [
|
||||||
{
|
{
|
||||||
"matcher": "Bash",
|
"matcher": "Bash",
|
||||||
@@ -47,22 +72,33 @@
|
|||||||
"command": "~/.claude/hooks/block-file-deletion.sh"
|
"command": "~/.claude/hooks/block-file-deletion.sh"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"matcher": "Bash",
|
||||||
|
"hooks": [
|
||||||
|
{
|
||||||
|
"type": "command",
|
||||||
|
"command": "atuin hook claude-code"
|
||||||
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
"disableWorkflows": true,
|
||||||
|
"disableArtifact": true,
|
||||||
"statusLine": {
|
"statusLine": {
|
||||||
"type": "command",
|
"type": "command",
|
||||||
"command": "bash /home/tgrosinger/.claude/statusline-command.sh"
|
"command": "bash /home/tgrosinger/.claude/statusline-command.sh"
|
||||||
},
|
},
|
||||||
"enabledPlugins": {
|
"enabledPlugins": {
|
||||||
"pr-review-toolkit@claude-plugins-official": true,
|
|
||||||
"gopls-lsp@claude-plugins-official": true,
|
|
||||||
"frontend-design@claude-plugins-official": true,
|
|
||||||
"code-simplifier@claude-plugins-official": true,
|
|
||||||
"skill-creator@claude-plugins-official": true,
|
|
||||||
"typescript-lsp@claude-plugins-official": true,
|
|
||||||
"claude-md-management@claude-plugins-official": true,
|
"claude-md-management@claude-plugins-official": true,
|
||||||
"security-guidance@claude-plugins-official": true
|
"code-simplifier@claude-plugins-official": true,
|
||||||
|
"frontend-design@claude-plugins-official": true,
|
||||||
|
"gopls-lsp@claude-plugins-official": true,
|
||||||
|
"pr-review-toolkit@claude-plugins-official": true,
|
||||||
|
"security-guidance@claude-plugins-official": true,
|
||||||
|
"skill-creator@claude-plugins-official": true,
|
||||||
|
"typescript-lsp@claude-plugins-official": true
|
||||||
},
|
},
|
||||||
"sandbox": {
|
"sandbox": {
|
||||||
"enabled": true,
|
"enabled": true,
|
||||||
@@ -86,19 +122,23 @@
|
|||||||
"filesystem": {
|
"filesystem": {
|
||||||
"allowWrite": [
|
"allowWrite": [
|
||||||
"~/.local/share/pnpm",
|
"~/.local/share/pnpm",
|
||||||
"~/.cache/pnpm"
|
"~/.cache/pnpm",
|
||||||
|
"~/Documents/Atrium"
|
||||||
],
|
],
|
||||||
"denyRead": [
|
"denyRead": [
|
||||||
"~/.ssh",
|
"~/.ssh",
|
||||||
"~/.config/Signal",
|
"~/.config/Signal",
|
||||||
"~/Documents"
|
"~/Documents"
|
||||||
|
],
|
||||||
|
"allowRead": [
|
||||||
|
"~/Documents/Atrium"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"excludedCommands": [
|
"excludedCommands": [
|
||||||
"git push *",
|
"git push *",
|
||||||
"brew *",
|
"brew *",
|
||||||
"devbox add *",
|
"tn *",
|
||||||
"nix *"
|
"obsidian *"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"spinnerVerbs": {
|
"spinnerVerbs": {
|
||||||
@@ -119,8 +159,5 @@
|
|||||||
"theme": "light",
|
"theme": "light",
|
||||||
"editorMode": "vim",
|
"editorMode": "vim",
|
||||||
"agentPushNotifEnabled": true,
|
"agentPushNotifEnabled": true,
|
||||||
"disableClaudeAiConnectors": true,
|
|
||||||
"disableWorkflows": true,
|
|
||||||
"disableArtifact": true,
|
|
||||||
"skipAutoPermissionPrompt": true
|
"skipAutoPermissionPrompt": true
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,29 @@
|
|||||||
|
function aiq -d "Ask Codex for a fish shell command"
|
||||||
|
if test (count $argv) -eq 0
|
||||||
|
echo "Usage: aiq <what you want to do>" >&2
|
||||||
|
return 1
|
||||||
|
end
|
||||||
|
|
||||||
|
if not command -q codex
|
||||||
|
echo "aiq: codex is not installed or is not in PATH" >&2
|
||||||
|
return 127
|
||||||
|
end
|
||||||
|
|
||||||
|
set -l need (string join ' ' -- $argv)
|
||||||
|
set -l prompt "You are suggesting a shell command for the fish shell. Do not run the command or modify any files. The user needs to: $need
|
||||||
|
|
||||||
|
Output the suggested command first. Follow it with additional details only when they are very important, or when there is relevant optional configuration the user should know about. Keep the response concise and do not wrap the command in backticks."
|
||||||
|
|
||||||
|
# Codex writes progress and run metadata to stderr; keep this helper focused
|
||||||
|
# on the final answer, but surface stderr when the request fails.
|
||||||
|
set -l errors (mktemp); or return 1
|
||||||
|
command codex exec --ephemeral --sandbox read-only --skip-git-repo-check "$prompt" 2>$errors
|
||||||
|
set -l result $status
|
||||||
|
|
||||||
|
if test $result -ne 0
|
||||||
|
command cat $errors >&2
|
||||||
|
end
|
||||||
|
command rm -f $errors
|
||||||
|
|
||||||
|
return $result
|
||||||
|
end
|
||||||
@@ -14,15 +14,20 @@ function dev-claude -d "Start Claude with a durable session id so tmux-resurrect
|
|||||||
# regex, so without the shim the saved command becomes an absolute path,
|
# regex, so without the shim the saved command becomes an absolute path,
|
||||||
# stops matching, and pane restore silently dies. env re-execs with argv[0]
|
# stops matching, and pane restore silently dies. env re-execs with argv[0]
|
||||||
# as a bare "claude", keeping the saved command byte-identical.
|
# as a bare "claude", keeping the saved command byte-identical.
|
||||||
# 2. env does the PATH lookup that "command" used to do here, so a fish
|
# 2. env does the PATH lookup that "command" would do here, so a fish
|
||||||
# function or alias named claude still can't shadow the real binary.
|
# function or alias named claude still can't shadow the real binary.
|
||||||
# "command" itself cannot be used: fish rejects "exec $launch ..." when the
|
|
||||||
# expansion starts with a builtin ("The expanded command is a keyword").
|
|
||||||
set -l launch /usr/bin/env claude
|
set -l launch /usr/bin/env claude
|
||||||
if command -q systemd-run
|
if command -q systemd-run
|
||||||
set launch systemd-run --user --scope --quiet --collect --slice=claude.slice -- /usr/bin/env claude
|
set launch systemd-run --user --scope --quiet --collect --slice=claude.slice -- /usr/bin/env claude
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Never exec here. tmux-resurrect's ps save strategy reads the *children* of
|
||||||
|
# the pane pid (`ps -ao ppid,args | grep "^$pane_pid"`), so exec'ing would make
|
||||||
|
# claude the pane pid itself and the saved command would be one of claude's own
|
||||||
|
# subprocesses — restore then silently does nothing. Keeping fish as the pane
|
||||||
|
# process is what puts "claude --session-id <uuid>" where resurrect looks.
|
||||||
|
# Claude exiting drops back to a prompt rather than closing the pane.
|
||||||
|
|
||||||
# Restore path: resurrect replays the id we launched with. Claude rejects
|
# Restore path: resurrect replays the id we launched with. Claude rejects
|
||||||
# --session-id for an id that already exists, so switch to --resume once a
|
# --session-id for an id that already exists, so switch to --resume once a
|
||||||
# transcript for it is on disk.
|
# transcript for it is on disk.
|
||||||
@@ -32,17 +37,20 @@ function dev-claude -d "Start Claude with a durable session id so tmux-resurrect
|
|||||||
set -l id $argv[2]
|
set -l id $argv[2]
|
||||||
set -l transcript (find $HOME/.claude/projects -maxdepth 2 -name "$id.jsonl" -print -quit)
|
set -l transcript (find $HOME/.claude/projects -maxdepth 2 -name "$id.jsonl" -print -quit)
|
||||||
if test -n "$transcript"
|
if test -n "$transcript"
|
||||||
exec $launch --resume $id $argv[3..]
|
$launch --resume $id $argv[3..]
|
||||||
|
return
|
||||||
end
|
end
|
||||||
exec $launch --session-id $id $argv[3..]
|
$launch --session-id $id $argv[3..]
|
||||||
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
# An explicit session flag means the caller is choosing the conversation.
|
# An explicit session flag means the caller is choosing the conversation.
|
||||||
for flag in -c --continue -r --resume --session-id
|
for flag in -c --continue -r --resume --session-id
|
||||||
if contains -- $flag $argv
|
if contains -- $flag $argv
|
||||||
exec $launch $argv
|
$launch $argv
|
||||||
|
return
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
exec $launch --session-id (uuidgen) $argv
|
$launch --session-id (uuidgen) $argv
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -0,0 +1,105 @@
|
|||||||
|
/**
|
||||||
|
* Atuin extension for pi.
|
||||||
|
*
|
||||||
|
* Tracks bash commands executed by pi in Atuin history with author `pi`.
|
||||||
|
*
|
||||||
|
* Install with:
|
||||||
|
* atuin hook install pi
|
||||||
|
*
|
||||||
|
* Then restart pi or run /reload.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import type { ExtensionAPI, ExtensionContext } from "@mariozechner/pi-coding-agent";
|
||||||
|
|
||||||
|
const ATUIN_AUTHOR = "pi";
|
||||||
|
const ATUIN_TIMEOUT_MS = 10_000;
|
||||||
|
|
||||||
|
async function startHistory(
|
||||||
|
pi: ExtensionAPI,
|
||||||
|
cwd: string,
|
||||||
|
command: string,
|
||||||
|
): Promise<string | undefined> {
|
||||||
|
try {
|
||||||
|
const result = await pi.exec(
|
||||||
|
"atuin",
|
||||||
|
["history", "start", "--author", ATUIN_AUTHOR, "--", command],
|
||||||
|
{ cwd, timeout: ATUIN_TIMEOUT_MS },
|
||||||
|
);
|
||||||
|
|
||||||
|
if (result.code !== 0) return undefined;
|
||||||
|
|
||||||
|
const id = result.stdout.trim();
|
||||||
|
return id.length > 0 ? id : undefined;
|
||||||
|
} catch {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function endHistory(
|
||||||
|
pi: ExtensionAPI,
|
||||||
|
cwd: string,
|
||||||
|
historyId: string,
|
||||||
|
exitCode: number,
|
||||||
|
): Promise<void> {
|
||||||
|
try {
|
||||||
|
await pi.exec(
|
||||||
|
"atuin",
|
||||||
|
["history", "end", historyId, "--exit", String(exitCode)],
|
||||||
|
{ cwd, timeout: ATUIN_TIMEOUT_MS },
|
||||||
|
);
|
||||||
|
} catch {
|
||||||
|
// Ignore Atuin failures so command execution is never blocked.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// The bash tool reports failures by appending a status line to the result
|
||||||
|
// text rather than exposing a numeric exit code, so recover it from there.
|
||||||
|
function exitCodeFromResult(result: unknown, isError: boolean): number {
|
||||||
|
if (!isError) return 0;
|
||||||
|
|
||||||
|
const content = (result as { content?: unknown } | undefined)?.content;
|
||||||
|
const text = Array.isArray(content)
|
||||||
|
? content
|
||||||
|
.map((part) => {
|
||||||
|
const t = (part as { text?: unknown } | undefined)?.text;
|
||||||
|
return typeof t === "string" ? t : "";
|
||||||
|
})
|
||||||
|
.join("\n")
|
||||||
|
: "";
|
||||||
|
|
||||||
|
const exited = text.match(/Command exited with code (\d+)\s*$/);
|
||||||
|
if (exited) return Number(exited[1]);
|
||||||
|
if (/Command aborted\s*$/.test(text)) return 130;
|
||||||
|
if (/Command timed out after \S+ seconds\s*$/.test(text)) return 124;
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function atuinPiExtension(pi: ExtensionAPI) {
|
||||||
|
// Atuin history IDs for in-flight bash tool calls, keyed by tool call ID.
|
||||||
|
const pending = new Map<string, string>();
|
||||||
|
|
||||||
|
// Observe bash executions through events instead of registering a bash
|
||||||
|
// tool: registering one conflicts with other extensions that provide
|
||||||
|
// their own bash tool (sandboxes, RTK, remote runners), while events
|
||||||
|
// fire no matter which extension's bash tool ends up executing the
|
||||||
|
// command.
|
||||||
|
pi.on("tool_call", async (event, ctx: ExtensionContext) => {
|
||||||
|
if (event.toolName !== "bash") return;
|
||||||
|
|
||||||
|
const command = (event.input as { command?: unknown }).command;
|
||||||
|
if (typeof command !== "string" || command.length === 0) return;
|
||||||
|
|
||||||
|
const historyId = await startHistory(pi, ctx.cwd, command);
|
||||||
|
if (historyId) pending.set(event.toolCallId, historyId);
|
||||||
|
});
|
||||||
|
|
||||||
|
// tool_execution_end also fires when another extension blocks the call,
|
||||||
|
// unlike tool_result, so entries started above are always closed.
|
||||||
|
pi.on("tool_execution_end", async (event, ctx: ExtensionContext) => {
|
||||||
|
const historyId = pending.get(event.toolCallId);
|
||||||
|
if (!historyId) return;
|
||||||
|
pending.delete(event.toolCallId);
|
||||||
|
|
||||||
|
await endHistory(pi, ctx.cwd, historyId, exitCodeFromResult(event.result, event.isError));
|
||||||
|
});
|
||||||
|
}
|
||||||
@@ -0,0 +1,71 @@
|
|||||||
|
---
|
||||||
|
name: rewrite-comments
|
||||||
|
description: Inspect and rewrite unclear source-code comments after tracing the surrounding behavior and contracts. Use when asked to review, improve, clean up, or rewrite comments selected by a Git commit, the current working tree, or a named function or method; function requests may target comments on the declaration, inside its body, or both.
|
||||||
|
---
|
||||||
|
|
||||||
|
# Rewrite Comments
|
||||||
|
|
||||||
|
Rewrite comments only after understanding the code well enough to preserve its meaning. Make edits in the current working tree; never rewrite Git history unless explicitly requested.
|
||||||
|
|
||||||
|
## Resolve the target
|
||||||
|
|
||||||
|
Determine the scope from the request:
|
||||||
|
|
||||||
|
- **Commit:** Inspect the commit diff and the complete current versions of the code regions it changes. Treat comments added or modified by the commit, plus comments attached to or inside changed declarations, as candidates. If the relevant code no longer exists in the current tree, report that instead of editing an older revision or history.
|
||||||
|
- **Working tree:** Inspect staged, unstaged, and untracked changes. Treat comments in or attached to changed declarations as candidates, prioritizing comments changed in the diff.
|
||||||
|
- **Function:** Locate the named function or method. Include comments documenting the declaration and comments within its body by default. Honor `on`, `above`, or `documentation` as declaration-only, and `within`, `inside`, or `body` as body-only.
|
||||||
|
|
||||||
|
Use repository-aware search and language structure where available. Account for overloads, methods with the same name, generated code, and renamed files. Ask for clarification only when multiple plausible targets remain and choosing one would materially change the edits.
|
||||||
|
|
||||||
|
Do not broaden the edit scope merely because nearby comments could also be improved. Read outside the scope freely to establish context.
|
||||||
|
|
||||||
|
## Build context before editing
|
||||||
|
|
||||||
|
Read the full enclosing declaration and enough neighboring code to understand it. Trace relevant definitions and call sites until the important behavior is supported by evidence. Establish, as applicable:
|
||||||
|
|
||||||
|
- why the code exists and which callers depend on it;
|
||||||
|
- accepted inputs, returned values, mutations, side effects, and failure behavior;
|
||||||
|
- ordering, lifecycle, concurrency, caching, security, and performance constraints;
|
||||||
|
- invariants, edge cases, compatibility requirements, and intentionally surprising choices;
|
||||||
|
- types, tests, interfaces, domain documentation, and architectural decisions that define the contract.
|
||||||
|
|
||||||
|
Prefer direct evidence from code, tests, and repository documentation. Do not invent intent. If a comment makes a claim that cannot be verified, either make it narrower and factual or remove it.
|
||||||
|
|
||||||
|
Stop exploring when the comment's purpose and every retained behavioral claim can be explained from evidence. Avoid tracing unrelated parts of the system.
|
||||||
|
|
||||||
|
## Judge each candidate
|
||||||
|
|
||||||
|
Keep an accurate comment when it already adds durable information. Rewrite or remove a comment when it is unclear, redundant, misleading, stale, speculative, or coupled to incidental implementation details.
|
||||||
|
|
||||||
|
Prioritize comments that capture:
|
||||||
|
|
||||||
|
- hidden contracts and invariants;
|
||||||
|
- non-obvious reasons and tradeoffs;
|
||||||
|
- caller-visible edge cases or failure behavior;
|
||||||
|
- constraints imposed by another subsystem, API, format, or compatibility promise;
|
||||||
|
- deliberate deviations from the obvious implementation.
|
||||||
|
|
||||||
|
Avoid comments that:
|
||||||
|
|
||||||
|
- narrate syntax or restate names and types;
|
||||||
|
- describe mechanics that are immediately apparent from the next few lines;
|
||||||
|
- duplicate the type system or stable API shape without adding a contract;
|
||||||
|
- predict future work without an actionable, repository-standard marker;
|
||||||
|
- mention transient details, line positions, counts, or internal steps likely to drift;
|
||||||
|
- preserve a confident explanation unsupported by the code.
|
||||||
|
|
||||||
|
Retain required legal notices, tool directives, generated-file markers, suppression comments, and structured documentation tags unless the task explicitly includes them and changing them is safe.
|
||||||
|
|
||||||
|
## Rewrite
|
||||||
|
|
||||||
|
Edit only comments unless the user explicitly requests code changes. Preserve behavior, public API, formatting conventions, comment style, and documentation syntax.
|
||||||
|
|
||||||
|
Write concise, direct comments at the narrowest useful location. Explain `why`, `must`, `unless`, or `despite` when those ideas matter. State contracts in terms of observable behavior rather than current implementation. Include inputs and outputs only when their semantics, ownership, units, normalization, sentinel values, or failure modes are not already obvious from code and types.
|
||||||
|
|
||||||
|
Delete a comment when removal is clearer than a rewrite. Do not add comments merely to replace every removed one, and do not churn wording without a meaningful clarity or correctness improvement.
|
||||||
|
|
||||||
|
## Verify and report
|
||||||
|
|
||||||
|
Review the final diff and confirm that edits are limited to the resolved scope and do not alter executable code. Re-read each edited comment against its code and callers. Run repository-required checks for changed files and any broader checks explicitly required by repository instructions; treat failures as blocking.
|
||||||
|
|
||||||
|
Summarize which comments changed and the hidden behavior or contract they now clarify. Mention comments intentionally removed, unresolved ambiguity, unverifiable claims, and checks that could not be run.
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
interface:
|
||||||
|
display_name: "Rewrite Comments"
|
||||||
|
short_description: "Rewrite code comments around selected changes"
|
||||||
|
default_prompt: "Use $rewrite-comments to inspect and improve comments in my working tree."
|
||||||
Reference in New Issue
Block a user