diff --git a/skills/gitguardex/SKILL.md b/skills/gitguardex/SKILL.md index ada7e706..5647cbc0 100644 --- a/skills/gitguardex/SKILL.md +++ b/skills/gitguardex/SKILL.md @@ -10,4 +10,6 @@ Use when repo safety may be broken. Bootstrap: `gx setup` Ops: `gx branch start "" ""`, `gx locks claim --branch "" `, `gx branch finish --branch "" --base --via-pr --wait-for-merge --cleanup`, `gx finish --all`, `gx cleanup` -When inspecting or verifying, prefer `rtk` compact wrappers if available (`rtk git status`, `rtk grep`, `rtk test `). Do not wrap commands whose stdout is parsed by scripts. +When inspecting or verifying, prefer `rtk` compact wrappers if available (`rtk git status`, `rtk grep`, `rtk test `, and noisy gx reads like `rtk gx status` / `rtk gx doctor`). Do not wrap commands whose stdout is parsed by scripts (`--json`, `--porcelain`, exact stdout contracts) or shell-ready output (`gx prompt --exec`). + +To shrink gx's own large narrative output (e.g. `gx prompt`, `gx prompt --snippet`) before it lands in your context, set `GUARDEX_COMPRESS_CMD="stdout filter>"`; gx routes that output through the filter (terse/non-TTY mode, fail-open, JSON skipped). Unset = byte-for-byte unchanged. diff --git a/src/cli/commands/prompt.js b/src/cli/commands/prompt.js index 7a947eb0..85ebb80f 100644 --- a/src/cli/commands/prompt.js +++ b/src/cli/commands/prompt.js @@ -20,11 +20,16 @@ function printAgentsSnippet() { } function copyPrompt() { - process.stdout.write(AI_SETUP_PROMPT); + // AI_SETUP_PROMPT is the large narrative checklist an agent reads each + // session, so route it through the optional compressor (GUARDEX_COMPRESS_CMD) + // exactly like the main `gx prompt` path and `printAgentsSnippet`. + process.stdout.write(compressBlock(AI_SETUP_PROMPT)); process.exitCode = 0; } function copyCommands() { + // AI_SETUP_COMMANDS is shell-ready output (runnable commands), so it is never + // compressed — a compressor would corrupt the commands an agent pastes. process.stdout.write(AI_SETUP_COMMANDS); process.exitCode = 0; } @@ -83,7 +88,11 @@ function prompt(rawArgs) { process.exitCode = 0; return; } - process.stdout.write(renderAiSetupPrompt({ exec: variant === 'exec', parts: selectedParts })); + const rendered = renderAiSetupPrompt({ exec: variant === 'exec', parts: selectedParts }); + // The default/prompt variant is the large narrative checklist; route it + // through the optional compressor. The --exec variant is shell-ready output + // (runnable commands) and must never be compressed. + process.stdout.write(variant === 'exec' ? rendered : compressBlock(rendered)); process.exitCode = 0; } diff --git a/templates/AGENTS.multiagent-safety.md b/templates/AGENTS.multiagent-safety.md index b4e0ce36..18cb8b26 100644 --- a/templates/AGENTS.multiagent-safety.md +++ b/templates/AGENTS.multiagent-safety.md @@ -137,8 +137,8 @@ Persist unresolved questions or blockers into `openspec/plan//open-qu ### Optional companion tooling (use if installed) - **fff MCP** (file search): prefer for all file search; fall back to `rtk grep`/`rtk find` or `rg`. -- **rtk** (shell compression): wrap noisy discovery (`rtk ls`/`grep`/`find`/`read`), git/gh (`rtk git status`/`gh pr list`), and verification (`rtk tsc`/`lint`/`test`). Do **not** wrap machine-readable commands (`--porcelain`, `--json`, exact stdout contracts). -- **headroom** (context compression): when available, run large `gx` output, long logs, and big file/diff dumps through `headroom_compress` before reasoning over them (reversible — `headroom_retrieve` restores). Or set `GUARDEX_COMPRESS_CMD=""` so gx routes its own large narrative output through your compressor. Keep PR URLs, branch names, and file paths visible; never compress `--json`/`--porcelain` or values you act on verbatim. +- **rtk** (shell compression): wrap noisy discovery (`rtk ls`/`grep`/`find`/`read`), git/gh (`rtk git status`/`gh pr list`), verification (`rtk tsc`/`lint`/`test`), and noisy gx reads (`rtk gx status`/`rtk gx doctor`). Do **not** wrap machine-readable commands (`--porcelain`, `--json`, exact stdout contracts) or shell-ready output (`gx prompt --exec`). +- **headroom** (context compression): when available, run large `gx` output, long logs, and big file/diff dumps through `headroom_compress` before reasoning over them (reversible — `headroom_retrieve` restores). Or set `GUARDEX_COMPRESS_CMD=""` so gx routes its own large narrative output — `gx prompt`, `gx prompt --snippet` — through your compressor (terse/non-TTY only, fail-open, JSON skipped; `--exec` stays raw). Keep PR URLs, branch names, and file paths visible; never compress `--json`/`--porcelain` or values you act on verbatim. - **OpenSpec**: keep `openspec/changes//tasks.md` current during work, not batched. Validate with `openspec validate --specs` before archive. ### Token / context budget diff --git a/templates/codex/skills/gitguardex/SKILL.md b/templates/codex/skills/gitguardex/SKILL.md index ada7e706..5647cbc0 100644 --- a/templates/codex/skills/gitguardex/SKILL.md +++ b/templates/codex/skills/gitguardex/SKILL.md @@ -10,4 +10,6 @@ Use when repo safety may be broken. Bootstrap: `gx setup` Ops: `gx branch start "" ""`, `gx locks claim --branch "" `, `gx branch finish --branch "" --base --via-pr --wait-for-merge --cleanup`, `gx finish --all`, `gx cleanup` -When inspecting or verifying, prefer `rtk` compact wrappers if available (`rtk git status`, `rtk grep`, `rtk test `). Do not wrap commands whose stdout is parsed by scripts. +When inspecting or verifying, prefer `rtk` compact wrappers if available (`rtk git status`, `rtk grep`, `rtk test `, and noisy gx reads like `rtk gx status` / `rtk gx doctor`). Do not wrap commands whose stdout is parsed by scripts (`--json`, `--porcelain`, exact stdout contracts) or shell-ready output (`gx prompt --exec`). + +To shrink gx's own large narrative output (e.g. `gx prompt`, `gx prompt --snippet`) before it lands in your context, set `GUARDEX_COMPRESS_CMD="stdout filter>"`; gx routes that output through the filter (terse/non-TTY mode, fail-open, JSON skipped). Unset = byte-for-byte unchanged. diff --git a/test/prompt.test.js b/test/prompt.test.js index 9b1f7131..1fc42d45 100644 --- a/test/prompt.test.js +++ b/test/prompt.test.js @@ -219,6 +219,33 @@ test('prompt --snippet is unchanged when GUARDEX_COMPRESS_CMD points at a missin assert.match(result.stdout, //); }); +test('prompt (default checklist) routes through GUARDEX_COMPRESS_CMD when set', () => { + const repoDir = initRepo(); + const plain = runNode(['prompt'], repoDir); + assert.equal(plain.status, 0, plain.stderr || plain.stdout); + assert.match(plain.stdout, /GitGuardex \(gx\) setup checklist/); + + const compressed = runNodeWithEnv(['prompt'], repoDir, { + GUARDEX_COMPRESS_CMD: 'tr a-z A-Z', + }); + assert.equal(compressed.status, 0, compressed.stderr || compressed.stdout); + // `tr a-z A-Z` uppercases the whole block, proving it was piped through. + assert.match(compressed.stdout, /GITGUARDEX \(GX\) SETUP CHECKLIST/); + assert.doesNotMatch(compressed.stdout, /GitGuardex \(gx\) setup checklist/); +}); + +test('prompt --exec is never compressed (shell-ready output stays runnable)', () => { + const repoDir = initRepo(); + // Even with a compressor configured, --exec output must pass through raw so + // the commands an agent pastes are not mangled. + const result = runNodeWithEnv(['prompt', '--exec'], repoDir, { + GUARDEX_COMPRESS_CMD: 'tr a-z A-Z', + }); + assert.equal(result.status, 0, result.stderr || result.stdout); + assert.match(result.stdout, /^npm i -g @imdeadpool\/guardex/m); + assert.doesNotMatch(result.stdout, /NPM I -G @IMDEADPOOL/); +}); + test('deprecated copy-prompt alias still works and warns', () => { const repoDir = initRepo();