From e5d2270c8b1946225f26a69a5fcc535db4e35e70 Mon Sep 17 00:00:00 2001 From: NagyVikt Date: Wed, 22 Apr 2026 16:14:21 +0200 Subject: [PATCH] Let gx emit smaller prompt slices for lower-token handoffs The prompt surface only emitted the full checklist or full command block, so agents had to paste extra guidance or duplicate those lines into docs. This adds named prompt parts, list output, and exec-mode validation so callers can fetch only the section they need without changing the default outputs. Constraint: Existing gx prompt, gx prompt --exec, and deprecated prompt aliases had to stay compatible Rejected: Trim AGENTS.md again instead | user asked to route the prompt slices through gx itself Confidence: high Scope-risk: narrow Reversibility: clean Directive: Add future agent guidance as named gx prompt parts before copying it into repo docs or templates Tested: node --check src/context.js Tested: node --check src/cli/main.js Tested: node --test test/prompt.test.js Tested: openspec validate agent-codex-improve-gx-prompt-parts-for-token-usage-2026-04-22-16-05 --type change --strict Tested: openspec validate --specs Not-tested: gx branch finish network/PR path before running the finish pipeline --- README.md | 3 + .../.openspec.yaml | 2 + .../proposal.md | 28 +++ .../specs/gx-prompt-parts/spec.md | 43 ++++ .../tasks.md | 35 +++ src/cli/main.js | 50 ++++- src/context.js | 210 +++++++++++++++--- test/prompt.test.js | 43 ++++ 8 files changed, 377 insertions(+), 37 deletions(-) create mode 100644 openspec/changes/agent-codex-improve-gx-prompt-parts-for-token-usage-2026-04-22-16-05/.openspec.yaml create mode 100644 openspec/changes/agent-codex-improve-gx-prompt-parts-for-token-usage-2026-04-22-16-05/proposal.md create mode 100644 openspec/changes/agent-codex-improve-gx-prompt-parts-for-token-usage-2026-04-22-16-05/specs/gx-prompt-parts/spec.md create mode 100644 openspec/changes/agent-codex-improve-gx-prompt-parts-for-token-usage-2026-04-22-16-05/tasks.md diff --git a/README.md b/README.md index 780bf69..3c45f82 100644 --- a/README.md +++ b/README.md @@ -366,6 +366,9 @@ That GitHub release then triggers `.github/workflows/release.yml`, which perform ```sh gx prompt # full checklist (paste into Codex/Claude) gx prompt --exec # commands only +gx prompt --part task-loop +gx prompt --exec --part finish --part cleanup +gx prompt --list-parts gx prompt --snippet # AGENTS.md managed-block template ``` diff --git a/openspec/changes/agent-codex-improve-gx-prompt-parts-for-token-usage-2026-04-22-16-05/.openspec.yaml b/openspec/changes/agent-codex-improve-gx-prompt-parts-for-token-usage-2026-04-22-16-05/.openspec.yaml new file mode 100644 index 0000000..25345f4 --- /dev/null +++ b/openspec/changes/agent-codex-improve-gx-prompt-parts-for-token-usage-2026-04-22-16-05/.openspec.yaml @@ -0,0 +1,2 @@ +schema: spec-driven +created: 2026-04-22 diff --git a/openspec/changes/agent-codex-improve-gx-prompt-parts-for-token-usage-2026-04-22-16-05/proposal.md b/openspec/changes/agent-codex-improve-gx-prompt-parts-for-token-usage-2026-04-22-16-05/proposal.md new file mode 100644 index 0000000..03591b5 --- /dev/null +++ b/openspec/changes/agent-codex-improve-gx-prompt-parts-for-token-usage-2026-04-22-16-05/proposal.md @@ -0,0 +1,28 @@ +## Why + +- `gx prompt` currently emits either the full setup checklist, the full + command-only block, or the AGENTS managed snippet. +- Agents and humans often need only one slice of that guidance + (`task-loop`, `finish`, `openspec`, etc.), so they either paste the whole + checklist or copy those lines into other docs/prompts by hand. +- That wastes tokens in agent handoffs and keeps prompt-facing docs more + repetitive than they need to be. + +## What Changes + +- Add named prompt parts to `gx prompt` so callers can request only the needed + guidance with `--part `. +- Add `gx prompt --list-parts` so the available slices are discoverable without + reading source or README prose. +- Support `gx prompt --exec --part ...` for command-capable parts only, with a + clear error when a selected part has no shell-safe command form. +- Update README/help text and focused prompt tests around the new surface. + +## Impact + +- Existing `gx prompt`, `gx prompt --exec`, and `gx prompt --snippet` behavior + stays intact for callers that do not request parts. +- Agent/token usage improves because handoffs can fetch just the required + prompt slices instead of the entire checklist. +- The change is limited to CLI prompt rendering, prompt docs, and focused + tests; no branch/lock/doctor workflow behavior changes. diff --git a/openspec/changes/agent-codex-improve-gx-prompt-parts-for-token-usage-2026-04-22-16-05/specs/gx-prompt-parts/spec.md b/openspec/changes/agent-codex-improve-gx-prompt-parts-for-token-usage-2026-04-22-16-05/specs/gx-prompt-parts/spec.md new file mode 100644 index 0000000..b55062f --- /dev/null +++ b/openspec/changes/agent-codex-improve-gx-prompt-parts-for-token-usage-2026-04-22-16-05/specs/gx-prompt-parts/spec.md @@ -0,0 +1,43 @@ +## ADDED Requirements + +### Requirement: `gx prompt` SHALL emit named prompt parts +`gx prompt` SHALL support selecting one or more named checklist slices with +`--part ` so callers can request only the needed guidance. + +#### Scenario: prompt mode returns only the selected parts +- **GIVEN** the user runs `gx prompt --part task-loop --part finish` +- **WHEN** the CLI renders prompt output +- **THEN** it SHALL include the `task-loop` and `finish` guidance in the order + requested +- **AND** it SHALL omit unrelated prompt sections such as `cleanup` and + `review-bot` +- **AND** the default `gx prompt` output without `--part` SHALL remain the full + checklist. + +### Requirement: `gx prompt --exec` SHALL support command-capable parts +Command-only prompt output SHALL allow part selection for sections that have a +shell-safe command form. + +#### Scenario: exec mode renders only the requested command-capable parts +- **GIVEN** the user runs `gx prompt --exec --part install --part task-loop` +- **WHEN** the CLI renders command-only output +- **THEN** it SHALL emit only the `install` and `task-loop` commands in the + order requested +- **AND** it SHALL omit command lines for other sections such as `cleanup` + unless they were requested. + +#### Scenario: exec mode rejects prompt-only parts +- **GIVEN** the user runs `gx prompt --exec --part openspec` +- **WHEN** `openspec` has no shell-safe command-only rendering +- **THEN** the CLI SHALL exit non-zero +- **AND** it SHALL report that the selected part is not available in exec mode. + +### Requirement: `gx prompt` SHALL list available parts +The CLI SHALL expose the available prompt part names without requiring source +inspection. + +#### Scenario: list parts +- **WHEN** the user runs `gx prompt --list-parts` +- **THEN** the CLI SHALL print the supported part names +- **AND** the list SHALL include both command-capable parts and prompt-only + parts. diff --git a/openspec/changes/agent-codex-improve-gx-prompt-parts-for-token-usage-2026-04-22-16-05/tasks.md b/openspec/changes/agent-codex-improve-gx-prompt-parts-for-token-usage-2026-04-22-16-05/tasks.md new file mode 100644 index 0000000..afb1f26 --- /dev/null +++ b/openspec/changes/agent-codex-improve-gx-prompt-parts-for-token-usage-2026-04-22-16-05/tasks.md @@ -0,0 +1,35 @@ +## Definition of Done + +This change is complete only when **all** of the following are true: + +- Every checkbox below is checked. +- The agent branch reaches `MERGED` state on `origin` and the PR URL + state are recorded in the completion handoff. +- If any step blocks (test failure, conflict, ambiguous result), append a `BLOCKED:` line under section 4 explaining the blocker and **STOP**. Do not tick remaining cleanup boxes; do not silently skip the cleanup pipeline. + +## Handoff + +- Handoff: change=`agent-codex-improve-gx-prompt-parts-for-token-usage-2026-04-22-16-05`; branch=`agent/codex/improve-gx-prompt-parts-for-token-usage-2026-04-22-16-05`; scope=`src/context.js, src/cli/main.js, test/prompt.test.js, README.md, OpenSpec change docs`; action=`add gx prompt part selection, document the new surface, and keep the full prompt modes backward compatible`. +- Copy prompt: Continue `agent-codex-improve-gx-prompt-parts-for-token-usage-2026-04-22-16-05` on branch `agent/codex/improve-gx-prompt-parts-for-token-usage-2026-04-22-16-05`. Work inside the existing sandbox, review `openspec/changes/agent-codex-improve-gx-prompt-parts-for-token-usage-2026-04-22-16-05/tasks.md`, continue from the current state instead of creating a new sandbox, and when the work is done run `gx branch finish --branch agent/codex/improve-gx-prompt-parts-for-token-usage-2026-04-22-16-05 --base main --via-pr --wait-for-merge --cleanup`. + +## 1. Specification + +- [x] 1.1 Finalize proposal scope and acceptance criteria for `agent-codex-improve-gx-prompt-parts-for-token-usage-2026-04-22-16-05`. +- [x] 1.2 Define normative requirements in `specs/gx-prompt-parts/spec.md`. + +## 2. Implementation + +- [x] 2.1 Add named `gx prompt --part` / `--list-parts` support while keeping the existing full prompt, `--exec`, and `--snippet` outputs intact. +- [x] 2.2 Teach `gx prompt --exec --part ...` to emit only command-capable slices and fail clearly when a selected part has no command-only form. +- [x] 2.3 Update focused prompt docs/tests in `README.md` and `test/prompt.test.js`. + +## 3. Verification + +- [x] 3.1 Run targeted project verification commands (`node --check src/context.js`, `node --check src/cli/main.js`, `node --test test/prompt.test.js`) — passed on `2026-04-22`. +- [x] 3.2 Run `openspec validate agent-codex-improve-gx-prompt-parts-for-token-usage-2026-04-22-16-05 --type change --strict` — passed on `2026-04-22`. +- [x] 3.3 Run `openspec validate --specs` — passed on `2026-04-22` with `No items found to validate.` + +## 4. Cleanup (mandatory; run before claiming completion) + +- [ ] 4.1 Run the cleanup pipeline: `gx branch finish --branch agent/codex/improve-gx-prompt-parts-for-token-usage-2026-04-22-16-05 --base main --via-pr --wait-for-merge --cleanup`. This handles commit -> push -> PR create -> merge wait -> worktree prune in one invocation. +- [ ] 4.2 Record the PR URL and final merge state (`MERGED`) in the completion handoff. +- [ ] 4.3 Confirm the sandbox worktree is gone (`git worktree list` no longer shows the agent path; `git branch -a` shows no surviving local/remote refs for the branch). diff --git a/src/cli/main.js b/src/cli/main.js index 208c8e9..e62e8cc 100755 --- a/src/cli/main.js +++ b/src/cli/main.js @@ -60,6 +60,9 @@ const { DEPRECATED_COMMAND_ALIASES, envFlagIsTruthy, defaultAgentWorktreeRelativeDir, + listAiSetupPartNames, + parseAiSetupPartNames, + renderAiSetupPrompt, AI_SETUP_PROMPT, AI_SETUP_COMMANDS, SCORECARD_RISK_BY_CHECK, @@ -5226,26 +5229,59 @@ function copyCommands() { function prompt(rawArgs) { const args = Array.isArray(rawArgs) ? rawArgs : []; let variant = 'prompt'; - for (const arg of args) { + let listParts = false; + const selectedParts = []; + for (let index = 0; index < args.length; index += 1) { + const arg = args[index]; if (arg === '--exec' || arg === '--commands') variant = 'exec'; else if (arg === '--snippet' || arg === '--agents') variant = 'snippet'; else if (arg === '--prompt' || arg === '--full') variant = 'prompt'; + else if (arg === '--list-parts') listParts = true; + else if (arg === '--part' || arg === '--parts') { + const rawValue = args[index + 1]; + if (!rawValue || rawValue.startsWith('--')) { + throw new Error(`${arg} requires a value`); + } + selectedParts.push(...parseAiSetupPartNames(rawValue)); + index += 1; + } else if (arg.startsWith('--part=')) { + selectedParts.push(...parseAiSetupPartNames(arg.slice('--part='.length))); + } else if (arg.startsWith('--parts=')) { + selectedParts.push(...parseAiSetupPartNames(arg.slice('--parts='.length))); + } else if (arg === '-h' || arg === '--help') variant = 'help'; else throw new Error(`Unknown option: ${arg}`); } if (variant === 'help') { console.log( `${SHORT_TOOL_NAME} prompt commands:\n` + - ` ${SHORT_TOOL_NAME} prompt Print AI setup checklist\n` + - ` ${SHORT_TOOL_NAME} prompt --exec Print setup commands only (shell-ready)\n` + - ` ${SHORT_TOOL_NAME} prompt --snippet Print the AGENTS.md managed-block template`, + ` ${SHORT_TOOL_NAME} prompt Print AI setup checklist\n` + + ` ${SHORT_TOOL_NAME} prompt --exec Print setup commands only (shell-ready)\n` + + ` ${SHORT_TOOL_NAME} prompt --part Print only the named checklist slice(s)\n` + + ` ${SHORT_TOOL_NAME} prompt --exec --part Print only the named exec-capable slice(s)\n` + + ` ${SHORT_TOOL_NAME} prompt --list-parts List prompt part names\n` + + ` ${SHORT_TOOL_NAME} prompt --exec --list-parts List exec-capable prompt part names\n` + + ` ${SHORT_TOOL_NAME} prompt --snippet Print the AGENTS.md managed-block template`, ); process.exitCode = 0; return; } - if (variant === 'exec') return copyCommands(); - if (variant === 'snippet') return printAgentsSnippet(); - return copyPrompt(); + if (variant === 'snippet') { + if (listParts || selectedParts.length > 0) { + throw new Error('--snippet does not support --list-parts or --part'); + } + return printAgentsSnippet(); + } + if (listParts) { + if (selectedParts.length > 0) { + throw new Error('--list-parts does not support --part'); + } + process.stdout.write(`${listAiSetupPartNames({ execOnly: variant === 'exec' }).join('\n')}\n`); + process.exitCode = 0; + return; + } + process.stdout.write(renderAiSetupPrompt({ exec: variant === 'exec', parts: selectedParts })); + process.exitCode = 0; } function branch(rawArgs) { diff --git a/src/context.js b/src/context.js index 2530708..ebff7f2 100644 --- a/src/context.js +++ b/src/context.js @@ -348,7 +348,7 @@ const CLI_COMMAND_DESCRIPTIONS = [ ['cleanup', 'Prune merged/stale agent branches and worktrees'], ['release', 'Create or update the current GitHub release with README-generated notes'], ['agents', 'Start/stop repo-scoped review + cleanup bots'], - ['prompt', 'Print AI setup checklist (--exec, --snippet)'], + ['prompt', 'Print AI setup checklist or named slices (--exec, --part, --list-parts, --snippet)'], ['report', 'Security/safety reports (e.g. OpenSSF scorecard)'], ['help', 'Show this help output'], ['version', 'Print GitGuardex version'], @@ -369,6 +369,12 @@ const AGENT_BOT_DESCRIPTIONS = [ const DOCTOR_AUTO_FINISH_DETAIL_LIMIT = 6; const DOCTOR_AUTO_FINISH_BRANCH_LABEL_MAX = 72; const DOCTOR_AUTO_FINISH_MESSAGE_MAX = 160; +const AI_SETUP_PART_ALIASES = new Map([ + ['task', 'task-loop'], + ['loop', 'task-loop'], + ['reviewbot', 'review-bot'], + ['forksync', 'fork-sync'], +]); function envFlagIsTruthy(raw) { const lowered = String(raw || '').trim().toLowerCase(); @@ -383,35 +389,176 @@ function defaultAgentWorktreeRelativeDir(env = process.env) { return isClaudeCodeSession(env) ? CLAUDE_WORKTREE_RELATIVE_DIR : CODEX_WORKTREE_RELATIVE_DIR; } -const AI_SETUP_PROMPT = `GitGuardex (gx) setup checklist for Codex/Claude in this repo. - -1) Install: ${GLOBAL_INSTALL_COMMAND} && gh --version -2) Bootstrap: gx setup -3) Repair: gx doctor -4) Task loop: gx branch start "" "" - then gx locks claim --branch "" -> gx branch finish -5) Integrate: gx merge --branch --branch -6) Finish: gx finish --all -7) Cleanup: gx cleanup -8) OpenSpec: /opsx:propose -> /opsx:apply -> /opsx:archive -9) Optional: gx protect add release staging -10) Optional: gx sync --check && gx sync -11) Review bot: install https://github.com/apps/cr-gpt + set OPENAI_API_KEY -12) Fork sync: install https://github.com/apps/pull + cp .github/pull.yml.example .github/pull.yml -`; - -const AI_SETUP_COMMANDS = `${GLOBAL_INSTALL_COMMAND} -gh --version -gx setup -gx doctor -gx branch start "" "" -gx locks claim --branch "" -gx merge --branch "" --branch "" -gx finish --all -gx cleanup -gx protect add release staging -gx sync --check && gx sync -`; +const AI_SETUP_PARTS = [ + { + name: 'install', + label: 'Install', + promptLines: [`${GLOBAL_INSTALL_COMMAND} && gh --version`], + execLines: [GLOBAL_INSTALL_COMMAND, 'gh --version'], + }, + { + name: 'bootstrap', + label: 'Bootstrap', + promptLines: ['gx setup'], + execLines: ['gx setup'], + }, + { + name: 'repair', + label: 'Repair', + promptLines: ['gx doctor'], + execLines: ['gx doctor'], + }, + { + name: 'task-loop', + label: 'Task loop', + promptLines: [ + 'gx branch start "" ""', + 'then gx locks claim --branch "" -> gx branch finish', + ], + execLines: [ + 'gx branch start "" ""', + 'gx locks claim --branch "" ', + ], + }, + { + name: 'integrate', + label: 'Integrate', + promptLines: ['gx merge --branch --branch '], + execLines: ['gx merge --branch --branch '], + }, + { + name: 'finish', + label: 'Finish', + promptLines: ['gx finish --all'], + execLines: ['gx finish --all'], + }, + { + name: 'cleanup', + label: 'Cleanup', + promptLines: ['gx cleanup'], + execLines: ['gx cleanup'], + }, + { + name: 'openspec', + label: 'OpenSpec', + promptLines: ['/opsx:propose -> /opsx:apply -> /opsx:archive'], + }, + { + name: 'protect', + label: 'Protect', + promptLines: ['gx protect add release staging'], + execLines: ['gx protect add release staging'], + }, + { + name: 'sync', + label: 'Sync', + promptLines: ['gx sync --check && gx sync'], + execLines: ['gx sync --check && gx sync'], + }, + { + name: 'review-bot', + label: 'Review bot', + promptLines: ['install https://github.com/apps/cr-gpt + set OPENAI_API_KEY'], + }, + { + name: 'fork-sync', + label: 'Fork sync', + promptLines: ['install https://github.com/apps/pull + cp .github/pull.yml.example .github/pull.yml'], + }, +]; +const AI_SETUP_PARTS_BY_NAME = new Map(AI_SETUP_PARTS.map((part) => [part.name, part])); +const AI_SETUP_EXEC_PART_NAMES = AI_SETUP_PARTS + .filter((part) => Array.isArray(part.execLines)) + .map((part) => part.name); + +function normalizeAiSetupPartName(rawName) { + const normalized = String(rawName || '') + .trim() + .toLowerCase() + .replace(/_/g, '-'); + return AI_SETUP_PART_ALIASES.get(normalized) || normalized; +} + +function listAiSetupPartNames(options = {}) { + if (!options.execOnly) return AI_SETUP_PARTS.map((part) => part.name); + return AI_SETUP_EXEC_PART_NAMES.slice(); +} + +function parseAiSetupPartNames(rawValue) { + return String(rawValue || '') + .split(',') + .map((entry) => normalizeAiSetupPartName(entry)) + .filter(Boolean); +} + +function resolveAiSetupParts(rawPartNames, options = {}) { + const exec = Boolean(options.exec); + const requestedPartNames = Array.isArray(rawPartNames) ? rawPartNames : []; + const availablePartNames = listAiSetupPartNames(); + const execCapablePartNames = listAiSetupPartNames({ execOnly: true }); + const seen = new Set(); + const resolved = []; + + for (const rawName of requestedPartNames) { + const name = normalizeAiSetupPartName(rawName); + const part = AI_SETUP_PARTS_BY_NAME.get(name); + if (!part) { + throw new Error( + `Unknown prompt part: ${rawName}. Available parts: ${availablePartNames.join(', ')}`, + ); + } + if (exec && !Array.isArray(part.execLines)) { + throw new Error( + `Prompt part '${name}' is not available with --exec. ` + + `Exec-capable parts: ${execCapablePartNames.join(', ')}`, + ); + } + if (seen.has(name)) continue; + seen.add(name); + resolved.push(part); + } + + return resolved; +} + +function renderFullAiSetupPrompt() { + const lines = ['GitGuardex (gx) setup checklist for Codex/Claude in this repo.', '']; + const indentWidth = 18; + + AI_SETUP_PARTS.forEach((part, index) => { + const [lead, ...tail] = part.promptLines; + const prefix = `${index + 1}) ${part.label}:`; + lines.push(`${prefix.padEnd(indentWidth)}${lead}`); + tail.forEach((line) => lines.push(`${' '.repeat(indentWidth)}${line}`)); + }); + + return `${lines.join('\n')}\n`; +} + +function renderPartialAiSetupPrompt(parts) { + return `${parts + .map((part) => `${part.label}:\n${part.promptLines.join('\n')}`) + .join('\n\n')}\n`; +} + +function renderAiSetupCommands(parts) { + return `${parts.flatMap((part) => part.execLines).join('\n')}\n`; +} + +function renderAiSetupPrompt(options = {}) { + const exec = Boolean(options.exec); + const requestedPartNames = Array.isArray(options.parts) ? options.parts : []; + if (requestedPartNames.length === 0) { + return exec + ? renderAiSetupCommands(resolveAiSetupParts(AI_SETUP_EXEC_PART_NAMES, { exec: true })) + : renderFullAiSetupPrompt(); + } + const parts = resolveAiSetupParts(requestedPartNames, { exec }); + return exec ? renderAiSetupCommands(parts) : renderPartialAiSetupPrompt(parts); +} + +const AI_SETUP_PROMPT = renderAiSetupPrompt(); +const AI_SETUP_COMMANDS = renderAiSetupPrompt({ exec: true }); const SCORECARD_RISK_BY_CHECK = { 'Dangerous-Workflow': 'Critical', @@ -511,6 +658,9 @@ module.exports = { envFlagIsTruthy, isClaudeCodeSession, defaultAgentWorktreeRelativeDir, + listAiSetupPartNames, + parseAiSetupPartNames, + renderAiSetupPrompt, AI_SETUP_PROMPT, AI_SETUP_COMMANDS, SCORECARD_RISK_BY_CHECK, diff --git a/test/prompt.test.js b/test/prompt.test.js index f54a09f..1ac14ef 100644 --- a/test/prompt.test.js +++ b/test/prompt.test.js @@ -95,6 +95,49 @@ test('prompt --exec outputs command-only checklist', () => { assert.doesNotMatch(result.stdout, /GitGuardex \(gx\) setup checklist/); }); +test('prompt --part outputs only the selected checklist slices', () => { + const repoDir = initRepo(); + const result = runNode(['prompt', '--part', 'task-loop', '--part', 'finish'], repoDir); + assert.equal(result.status, 0, result.stderr || result.stdout); + assert.match(result.stdout, /^Task loop:/m); + assert.match(result.stdout, /gx branch start "" ""/); + assert.match(result.stdout, /^Finish:/m); + assert.match(result.stdout, /gx finish --all/); + assert.doesNotMatch(result.stdout, /GitGuardex \(gx\) setup checklist/); + assert.doesNotMatch(result.stdout, /^Cleanup:/m); + assert.doesNotMatch(result.stdout, /\/opsx:propose/); +}); + +test('prompt --exec --part outputs only selected command-capable slices', () => { + const repoDir = initRepo(); + const result = runNode(['prompt', '--exec', '--part', 'install', '--part', 'task-loop'], repoDir); + assert.equal(result.status, 0, result.stderr || result.stdout); + assert.match(result.stdout, /^npm i -g @imdeadpool\/guardex/m); + assert.match(result.stdout, /^gh --version$/m); + assert.match(result.stdout, /^gx branch start "" ""$/m); + assert.match(result.stdout, /^gx locks claim --branch "" $/m); + assert.doesNotMatch(result.stdout, /^gx cleanup$/m); + assert.doesNotMatch(result.stdout, /\/opsx:propose/); +}); + +test('prompt --list-parts prints the available prompt slices', () => { + const repoDir = initRepo(); + const result = runNode(['prompt', '--list-parts'], repoDir); + assert.equal(result.status, 0, result.stderr || result.stdout); + assert.match(result.stdout, /^install$/m); + assert.match(result.stdout, /^task-loop$/m); + assert.match(result.stdout, /^openspec$/m); + assert.match(result.stdout, /^review-bot$/m); +}); + +test('prompt --exec rejects prompt-only parts', () => { + const repoDir = initRepo(); + const result = runNode(['prompt', '--exec', '--part', 'openspec'], repoDir); + assert.equal(result.status, 1, 'exec mode should reject prompt-only parts'); + assert.match(result.stderr, /Prompt part 'openspec' is not available with --exec/); + assert.match(result.stderr, /Exec-capable parts:/); +}); + test('deprecated copy-prompt alias still works and warns', () => { const repoDir = initRepo();