From ff7497faab93f25ea9af546a0f530df4fb8874af Mon Sep 17 00:00:00 2001 From: Manas Raghuwanshi Date: Wed, 5 Aug 2026 15:27:02 +0530 Subject: [PATCH 1/5] docs: stop claiming a platform the agent cannot run on MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Windows was listed as supported in six places while `runCommand` spawns `sh -c`, which Windows has no equivalent of — so `run_terminal` and `run_tests`, the two tools the agent leans on hardest, cannot run there. The approval classifier compounds it: it knows `rm -rf`, `sudo` and `chmod`, not `del /f /s /q`, so every Windows command falls through to the fail-closed default and asks. CI never caught this because the matrix is ubuntu and macOS. Adding windows-latest would produce a permanently red job, so the matrix keeps its two entries and records why the third is missing. The `win32` branch in config/paths.ts stays. It is correct, it costs nothing, and it is what WSL or a later port would want — it just no longer implies a platform that works. --- .github/workflows/ci.yml | 10 ++++++++++ README.md | 1 - config/paths.ts | 11 +++++++++++ docs/getting-started/connect-a-provider.md | 2 +- docs/getting-started/install.md | 2 +- docs/guides/sessions-and-history.md | 1 - docs/reference/configuration.md | 2 -- 7 files changed, 23 insertions(+), 6 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 160b933..d6c07d0 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -38,6 +38,16 @@ jobs: # The approval classifier and the config loader are built on path # semantics, and macOS differs from Linux on case sensitivity and on # where a temporary directory lives. Both are cheap to run. + # + # windows-latest is deliberately absent, and its absence is the honest + # signal rather than an oversight: `runCommand` spawns `sh -c`, which + # Windows does not have, so `run_terminal` and `run_tests` cannot run + # there at all. The classifier compounds it — it knows `rm -rf`, `sudo` + # and `chmod`, not `del /f /s /q` or `reg delete`, so every Windows + # command falls through to the fail-closed default and asks. Adding the + # runner would produce a permanently red job that documents nothing the + # docs do not now say outright. The platform is a port, not a matrix + # entry, and the docs claim macOS and Linux only. os: [ubuntu-latest, macos-latest] steps: diff --git a/README.md b/README.md index 363c72d..14ecefa 100644 --- a/README.md +++ b/README.md @@ -84,7 +84,6 @@ Prompt → repository context → streaming agent → focused tools → review d The conversation, provider configuration, and local state are stored in: - macOS and Linux: `~/.config/woopcode/` -- Windows: `%LOCALAPPDATA%\\woopcode\\` ### Session history diff --git a/config/paths.ts b/config/paths.ts index 3ba43c7..87f038a 100644 --- a/config/paths.ts +++ b/config/paths.ts @@ -9,6 +9,17 @@ import { existsSync, mkdirSync } from "fs"; * Location: * - macOS/Linux: ~/.config/woopcode/ * - Windows: %LOCALAPPDATA%\woopcode\ + * + * The Windows branch is correct and unreachable in practice, and the + * documentation no longer advertises the platform. `runCommand` in + * tools/command.ts spawns `sh -c`, which Windows does not have, so + * `run_terminal` and `run_tests` cannot run there — and the approval classifier + * knows only POSIX command names, so every Windows command falls to its + * fail-closed default and asks. Supporting the platform is a port, not a fix. + * + * Kept rather than deleted because it costs nothing, it is what WSL and any + * later port would want, and removing it would make the gap harder to find + * than this comment does. */ export function getConfigDir(): string { const home = homedir(); diff --git a/docs/getting-started/connect-a-provider.md b/docs/getting-started/connect-a-provider.md index 439a8f7..cd6f2d5 100644 --- a/docs/getting-started/connect-a-provider.md +++ b/docs/getting-started/connect-a-provider.md @@ -62,7 +62,7 @@ mode in one line. ## Where the key goes Into `providers.json` in your config directory — `~/.config/woopcode/` on macOS -and Linux, `%LOCALAPPDATA%\woopcode\` on Windows. +and Linux. :::warning The key is stored in plain text. There is no encryption and no keychain diff --git a/docs/getting-started/install.md b/docs/getting-started/install.md index 5599248..792d3ee 100644 --- a/docs/getting-started/install.md +++ b/docs/getting-started/install.md @@ -51,7 +51,7 @@ woopcode --version | | | | --- | --- | | Runtime | Bun 1.0+ | -| Platforms | macOS, Linux, Windows | +| Platforms | macOS, Linux — on Windows, use WSL | | Network | Outbound HTTPS to the provider | | Account | A Google Gemini API key — [free to create](https://aistudio.google.com/apikey) | diff --git a/docs/guides/sessions-and-history.md b/docs/guides/sessions-and-history.md index a8786e3..c56eb4f 100644 --- a/docs/guides/sessions-and-history.md +++ b/docs/guides/sessions-and-history.md @@ -51,7 +51,6 @@ context. If something matters, restate it. | Platform | Path | | --- | --- | | macOS, Linux | `~/.config/woopcode/conversation.json` | -| Windows | `%LOCALAPPDATA%\woopcode\conversation.json` | :::warning There is one history file, not one per repository. Starting Woopcode in a diff --git a/docs/reference/configuration.md b/docs/reference/configuration.md index 088edb8..8c3f399 100644 --- a/docs/reference/configuration.md +++ b/docs/reference/configuration.md @@ -16,7 +16,6 @@ since: 0.6.0 | Platform | Directory | | --- | --- | | macOS, Linux | `$XDG_CONFIG_HOME/woopcode/`, or `~/.config/woopcode/` | -| Windows | `%LOCALAPPDATA%\woopcode\` | The directory is created on first run. @@ -99,7 +98,6 @@ something you may want to remove yourself. | Variable | Effect | | --- | --- | | `XDG_CONFIG_HOME` | Moves the config directory on macOS and Linux | -| `LOCALAPPDATA` | Moves the config directory on Windows | ### Credentials From 6f9460bc05cd827cda49c22d1c2e99f3b5dda68c Mon Sep 17 00:00:00 2001 From: Manas Raghuwanshi Date: Wed, 5 Aug 2026 15:31:32 +0530 Subject: [PATCH 2/5] fix(onboarding): point the reset script at the config it claims to reset MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit test-reset.ts targeted ./config/providers.json — inside the repository, where user configuration has never lived. Real config is in ~/.config/woopcode/, so every invocation raised ENOENT and reset nothing. It is listed in CLAUDE.md's command table, which is how it stayed broken: the only check on it was somebody running it by hand. It now imports getConfigDir rather than rebuilding the path, so the two cannot drift again, and moves the whole directory aside instead of blanking apiKey fields. Conversation history and the execution log are what separate "no key yet" from "never run before", and a first run has neither. Also names every supported vendor variable in the headless no-provider error. It said GEMINI_API_KEY only, sending a user holding an OpenAI key to buy a credential they already had. Covered by a subprocess integration test: two of its three cases fail against the previous script. --- onboarding/index.ts | 7 +- onboarding/test-reset.ts | 81 +++++++------ .../config/testReset.integration.test.ts | 106 ++++++++++++++++++ 3 files changed, 158 insertions(+), 36 deletions(-) create mode 100644 packages/tests/config/testReset.integration.test.ts diff --git a/onboarding/index.ts b/onboarding/index.ts index 168c136..6a88b4e 100644 --- a/onboarding/index.ts +++ b/onboarding/index.ts @@ -59,9 +59,14 @@ export async function ensureProviderConfigured(): Promise { // never arrive, so an unconfigured non-interactive run must fail with a // message that names the fix rather than hanging until the caller's timeout. if (!canPromptInteractively()) { + // Every supported vendor variable is named, not just Gemini's. This + // message is the only instruction a headless caller gets, and pointing a + // user who holds an OpenAI key at GEMINI_API_KEY sends them to buy a + // credential they already have the equivalent of. throw new Error( "No provider is configured and there is no terminal to run setup in.\n" + - "Set GEMINI_API_KEY (or WOOPCODE_API_KEY with WOOPCODE_PROVIDER) in the environment.", + "Set one of GEMINI_API_KEY, OPENAI_API_KEY or ANTHROPIC_API_KEY in the\n" + + "environment, or WOOPCODE_API_KEY together with WOOPCODE_PROVIDER.", ); } diff --git a/onboarding/test-reset.ts b/onboarding/test-reset.ts index 385785c..d7b8098 100755 --- a/onboarding/test-reset.ts +++ b/onboarding/test-reset.ts @@ -1,55 +1,66 @@ #!/usr/bin/env bun /** - * Utility script to reset provider configuration for testing onboarding. - * - * Usage: - * bun onboarding/test-reset.ts # Clear all API keys - * bun onboarding/test-reset.ts restore # Restore from backup + * Puts the machine back into the state a new user starts from, so the setup + * wizard can be walked without uninstalling anything. + * + * bun onboarding/test-reset.ts # move the config directory aside + * bun onboarding/test-reset.ts restore # put it back + * + * The whole directory moves, rather than the keys being blanked in place. + * `ensureProviderConfigured` treats a keyless provider and a missing config the + * same way, but the rest of a first run does not: conversation.json and + * execution-log.json are what separate "no key yet" from "never run before", + * and blanking a key leaves both behind. + * + * This previously pointed at ./config/providers.json — a path inside the + * repository, where no configuration has ever lived. It raised ENOENT on every + * invocation, which is why it never reset anything. `getConfigDir` is imported + * rather than rebuilt so the two cannot drift apart again. */ -import { existsSync } from "fs"; +import { existsSync, renameSync, rmSync } from "node:fs"; +import { dirname, join } from "node:path"; +import { getConfigDir } from "../config/paths"; -const CONFIG_PATH = "./config/providers.json"; -const BACKUP_PATH = "./config/providers.json.backup"; +// getConfigDir creates the directory as a side effect, which is harmless here: +// an empty directory moved aside is the same cold start as a missing one. +const CONFIG_DIR = getConfigDir(); +const BACKUP_DIR = join(dirname(CONFIG_DIR), "woopcode.pre-onboarding-test"); -const command = process.argv[2]; - -async function clearKeys() { - // Backup current config - if (existsSync(CONFIG_PATH)) { - const current = await Bun.file(CONFIG_PATH).text(); - await Bun.write(BACKUP_PATH, current); - console.log("✓ Backed up current configuration"); +function reset() { + if (existsSync(BACKUP_DIR)) { + console.error(`✖ A backup is already sitting at ${BACKUP_DIR}`); + console.error(" Restore it first, or delete it if you no longer want it."); + process.exit(1); } - // Clear all API keys - const config = JSON.parse(await Bun.file(CONFIG_PATH).text()); - - for (const provider in config.providers) { - config.providers[provider].apiKey = ""; - } + renameSync(CONFIG_DIR, BACKUP_DIR); - await Bun.write(CONFIG_PATH, JSON.stringify(config, null, 2)); - console.log("✓ Cleared all API keys"); - console.log("\nYou can now test the onboarding flow:"); + console.log(`✓ Moved ${CONFIG_DIR}`); + console.log(` to ${BACKUP_DIR}`); + console.log("\nWalk the setup wizard:"); console.log(" bun cli.ts"); - console.log("\nTo restore your keys:"); + console.log("\nThen put your real configuration back:"); console.log(" bun onboarding/test-reset.ts restore"); } -async function restore() { - if (!existsSync(BACKUP_PATH)) { - console.error("✖ No backup found"); +function restore() { + if (!existsSync(BACKUP_DIR)) { + console.error(`✖ No backup at ${BACKUP_DIR}`); process.exit(1); } - const backup = await Bun.file(BACKUP_PATH).text(); - await Bun.write(CONFIG_PATH, backup); - console.log("✓ Restored configuration from backup"); + // Whatever the wizard just wrote is test data, and the backup is the real + // configuration. Removing it first keeps rename from failing on a directory + // that already exists. + rmSync(CONFIG_DIR, { recursive: true, force: true }); + renameSync(BACKUP_DIR, CONFIG_DIR); + + console.log(`✓ Restored ${CONFIG_DIR}`); } -if (command === "restore") { - await restore(); +if (process.argv[2] === "restore") { + restore(); } else { - await clearKeys(); + reset(); } diff --git a/packages/tests/config/testReset.integration.test.ts b/packages/tests/config/testReset.integration.test.ts new file mode 100644 index 0000000..90f30f0 --- /dev/null +++ b/packages/tests/config/testReset.integration.test.ts @@ -0,0 +1,106 @@ +import { afterEach, describe, expect, test } from "bun:test"; +import { mkdirSync, rmSync, existsSync } from "node:fs"; +import { tmpdir } from "node:os"; +import { join } from "node:path"; + +/** + * `onboarding/test-reset.ts` is the documented way to reach a first-run state, + * and it was broken for long enough that nobody noticed: it pointed at + * `./config/providers.json`, a path inside the repository where configuration + * has never lived, and raised ENOENT on every invocation. It is listed in + * CLAUDE.md's command table, so the only thing standing between it and silence + * was somebody running it by hand. + * + * Driven as a subprocess rather than imported, because the script resolves the + * config directory at module load and acts at the top level — importing it + * would run it against whatever `XDG_CONFIG_HOME` said at that moment. Spawning + * also tests the contract a developer actually uses. + * + * `XDG_CONFIG_HOME` is passed per spawn rather than assigned to `process.env`, + * so there is no window in which this file's redirect is visible to another + * test — and nothing to restore afterwards, which is the failure mode + * `approval.integration.test.ts` had. + */ + +const SCRIPT = join(import.meta.dir, "../../../onboarding/test-reset.ts"); + +const fixtures: string[] = []; + +function makeConfigHome(): { configHome: string; woopcode: string } { + // A UUID, not Date.now(): millisecond resolution lets two runs build the same + // path and delete each other's files. + const configHome = join(tmpdir(), `woop-reset-${crypto.randomUUID()}`); + const woopcode = join(configHome, "woopcode"); + mkdirSync(woopcode, { recursive: true }); + fixtures.push(configHome); + return { configHome, woopcode }; +} + +async function run(configHome: string, ...args: string[]) { + const proc = Bun.spawn({ + cmd: ["bun", SCRIPT, ...args], + env: { ...process.env, XDG_CONFIG_HOME: configHome }, + stdout: "pipe", + stderr: "pipe", + }); + const [stdout, stderr] = await Promise.all([ + new Response(proc.stdout).text(), + new Response(proc.stderr).text(), + ]); + return { exitCode: await proc.exited, stdout, stderr }; +} + +afterEach(() => { + for (const directory of fixtures.splice(0)) { + rmSync(directory, { recursive: true, force: true }); + } +}); + +describe("onboarding test-reset", () => { + test("moves the real config directory aside and puts it back", async () => { + const { configHome, woopcode } = makeConfigHome(); + const providers = join(woopcode, "providers.json"); + const conversation = join(woopcode, "conversation.json"); + await Bun.write(providers, '{"defaultProvider":"google"}'); + await Bun.write(conversation, '["real history"]'); + + const reset = await run(configHome); + expect(reset.exitCode).toBe(0); + + // A missing directory is the cold start: `ensureProviderConfigured` finds no + // usable provider and the wizard runs. + expect(existsSync(woopcode)).toBe(false); + + // Whatever the wizard would have written is test data, and must not survive + // the restore. + mkdirSync(woopcode, { recursive: true }); + await Bun.write(providers, '{"defaultProvider":"throwaway"}'); + + const restore = await run(configHome, "restore"); + expect(restore.exitCode).toBe(0); + expect(await Bun.file(providers).text()).toBe('{"defaultProvider":"google"}'); + expect(await Bun.file(conversation).text()).toBe('["real history"]'); + }); + + test("refuses a second reset rather than overwriting the backup", async () => { + const { configHome, woopcode } = makeConfigHome(); + await Bun.write(join(woopcode, "providers.json"), '{"defaultProvider":"google"}'); + + expect((await run(configHome)).exitCode).toBe(0); + + // The second reset would move an empty or wizard-written directory over the + // one holding the developer's real key. + mkdirSync(woopcode, { recursive: true }); + const second = await run(configHome); + expect(second.exitCode).toBe(1); + expect(second.stderr).toContain("backup is already sitting"); + }); + + test("reports a restore with nothing to restore", async () => { + const { configHome } = makeConfigHome(); + + const restore = await run(configHome, "restore"); + expect(restore.exitCode).toBe(1); + expect(restore.stderr).toContain("No backup"); + }); +}); From ab61eee88ab49be979aab0c74cd33c1c6c4f6b36 Mon Sep 17 00:00:00 2001 From: Manas Raghuwanshi Date: Wed, 5 Aug 2026 15:43:38 +0530 Subject: [PATCH 3/5] docs(readme): say what is true here instead of what every agent claims MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The first screen opened with "an early-stage project" and a table of repository-aware, terminal-first and visible execution — claims that carry no information because every competing agent makes them, on a project with three provider integrations, a benchmark harness and a four-gate pre-commit system. Replaced with the things that are specific and checkable: writes stop on a diff with no path around the review, unrecognised shell commands are treated as destructive, plan mode is gated in two places and why both are needed, provider reasoning is replayed per provider because both Anthropic and OpenAI fail silently without it, and tools are tested against a real filesystem. The provider-maturity note stays — that Gemini is best-tested is useful to someone choosing — but as a fact rather than an apology. Platform support is now stated up front, so a Windows user learns it here rather than after installing. npm description likewise: it was generic enough to describe any agent. --- README.md | 16 ++++++++-------- package.json | 2 +- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index 14ecefa..87b3cc5 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ Woopcode runs where you work: in the terminal and inside the current repository. Ask it to investigate, explain, implement, review, or test a change; it streams progress, uses focused tools, and presents edits as a readable diff before it writes to an existing file. -> **Status:** an early-stage project. Google Gemini, OpenAI, and Anthropic are all implemented and usable. Gemini is the most heavily exercised path — it is what the benchmark suite runs against — so treat it as the best-tested option rather than the only one. +Google Gemini, OpenAI, and Anthropic are all implemented. Gemini is the most heavily exercised path — it is what the benchmark suite runs against — so treat it as the best-tested option rather than the only one. Runs on macOS and Linux; on Windows, use WSL. ## Demo @@ -16,13 +16,13 @@ Woopcode runs where you work: in the terminal and inside the current repository. ## Why Woopcode -| | | -| --------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| **Repository-aware** | Starts with your package metadata, README, and top-level project structure, then discovers deeper context only when needed. Repository context is budgeted per request rather than dumped, since the agent can read any file on demand. | -| **Terminal-first** | A focused React Ink interface with a pinned header, scrollable conversation, keyboard navigation, and no browser tab required. | -| **Visible execution** | Streams assistant output and tool activity so you can follow the work instead of waiting behind an opaque progress screen. | -| **Review before overwrite** | Existing-file edits and overwrites pause on a unified diff for approval. | -| **Practical guardrails** | Detects duplicate tool calls, limits tool iterations, supports cancellation, and returns recoverable tool errors to the agent. | +| | | +| ----------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| **Nothing is overwritten unreviewed** | Every edit to an existing file stops on a unified diff and waits. A tool that writes raises an approval request rather than touching the disk itself, so there is no path around the review. | +| **Shell commands fail closed** | Commands are classified by risk before they run, and a command the classifier does not recognise is treated as destructive. A line is judged by its riskiest part, so `git status && rm -rf build` asks — and so does anything hidden inside `$(...)`. | +| **Plan mode is enforced twice** | The provider is not offered the writing tools, *and* the loop refuses a write that arrives anyway. Both are load-bearing: `run_terminal` has to stay available for inspection, so `sed -i` and `cat > file` reach the disk through a tool the first gate must keep. | +| **Provider reasoning is replayed correctly** | Anthropic and OpenAI both require the reasoning that preceded a tool call to be sent back with that call's result, and both fail silently without it — the request succeeds and the model simply reasons from less. Each client handles its own rules; the agent loop stays neutral. | +| **Tested against a real filesystem** | Tools are exercised on real files in temporary directories rather than behind mocks. Only the provider and the approval prompt are faked, because one would make network calls and the other needs a human. | ## Quick start diff --git a/package.json b/package.json index ca794af..7c8a213 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "woopcode", "version": "0.8.2", - "description": "An autonomous AI coding assistant built for the terminal", + "description": "A terminal coding agent that shows every edit as a diff before it writes, and asks before running a shell command it cannot vouch for", "license": "MIT", "author": { "name": "Manas Raghuwanshi", From 51d6dc499cec8a333343800f8e5517312aadad5f Mon Sep 17 00:00:00 2001 From: Manas Raghuwanshi Date: Wed, 5 Aug 2026 15:47:05 +0530 Subject: [PATCH 4/5] chore(release): bump version to 0.9.0 Points homepage at the deployed documentation site rather than the GitHub README, and links it from the top of the README. Both were checked with curl before being committed; the previous value sent anyone arriving from npm to a file rather than the docs. --- README.md | 2 ++ package.json | 4 ++-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 87b3cc5..d1d049b 100644 --- a/README.md +++ b/README.md @@ -4,6 +4,8 @@ [![TypeScript](https://img.shields.io/badge/language-TypeScript-3178c6?logo=typescript&logoColor=white)](https://www.typescriptlang.org) [![License: MIT](https://img.shields.io/badge/license-MIT-22c55e.svg)](LICENSE) +[Documentation](https://woop-code.vercel.app) · [Install](https://woop-code.vercel.app/docs/getting-started/install) · [How a turn works](https://woop-code.vercel.app/docs/introduction/how-a-turn-works) + **A terminal-native coding agent that understands your repository, shows its work, and keeps you in control of code changes.** Woopcode runs where you work: in the terminal and inside the current repository. Ask it to investigate, explain, implement, review, or test a change; it streams progress, uses focused tools, and presents edits as a readable diff before it writes to an existing file. diff --git a/package.json b/package.json index 7c8a213..112b51e 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "woopcode", - "version": "0.8.2", + "version": "0.9.0", "description": "A terminal coding agent that shows every edit as a diff before it writes, and asks before running a shell command it cannot vouch for", "license": "MIT", "author": { @@ -11,7 +11,7 @@ "type": "git", "url": "git+https://github.com/mangit955/woop-code.git" }, - "homepage": "https://github.com/mangit955/woop-code#readme", + "homepage": "https://woop-code.vercel.app", "bugs": { "url": "https://github.com/mangit955/woop-code/issues" }, From 77d0224127fb12dc2e8f2c6ef9fadd043aa99362 Mon Sep 17 00:00:00 2001 From: Manas Raghuwanshi Date: Wed, 5 Aug 2026 15:55:56 +0530 Subject: [PATCH 5/5] chore: drop AI attribution from the ship workflow The ship command told me to end every commit body with a Co-Authored-By trailer naming a model, and every PR body with a "Generated with Claude Code" footer. Neither says anything about why a change exists, and the contribution record on this repository is the maintainer's. The rule now covers PR bodies, issues and review comments too, so it cannot come back through a path the commit rule does not reach. No existing commit carried the trailer. The footer was removed from the body of #51. --- .claude/commands/ship.md | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/.claude/commands/ship.md b/.claude/commands/ship.md index 743aab6..0afbc8f 100644 --- a/.claude/commands/ship.md +++ b/.claude/commands/ship.md @@ -80,11 +80,12 @@ The subject must be conventional (`.githooks/commit-msg` rejects anything else): `(): `, lowercase, no trailing period. Scopes in use: `tools`, `runtime`, `provider`, `tests`, `ci`, `replay`, `docs`. -The body says why, not what — the diff already says what. End it with: +The body says why, not what — the diff already says what. -``` -Co-Authored-By: Claude Opus 5 -``` +**No attribution trailers.** No `Co-Authored-By`, no tool or model name, no +"generated with" line — not in commits, PR bodies, issues or review comments. +The commit history is the maintainer's, and a trailer naming a tool says nothing +about why the change exists. Write the message to a temp file and use `git commit -F `, so backticks and newlines survive. @@ -140,11 +141,9 @@ Either way: - **Verified means verified.** List the checks that actually ran and what they actually printed. If something was skipped or could not be tested, say that plainly rather than writing a sentence that implies a green run. -- End with: - -``` -🤖 Generated with [Claude Code](https://claude.com/claude-code) -``` +- End on the verification. No attribution footer, no tool or model name, no + "generated with" line — see the rule under **Commit**, which applies to PR + bodies, issues and review comments too. ## 7. Report