Skip to content

Commit 1964d46

Browse files
authored
Merge pull request #152 from alex-feel/alex-feel-dev
Remove output-style support and add system-prompt mode field
2 parents 75d7280 + 43e100a commit 1964d46

File tree

7 files changed

+157
-333
lines changed

7 files changed

+157
-333
lines changed

CLAUDE.md

Lines changed: 18 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -30,8 +30,7 @@ YAML configurations define complete development environments including:
3030
- Agents (subagents for Claude Code)
3131
- MCP servers (with automatic permission pre-allowing)
3232
- Slash commands
33-
- Output styles (complete system prompt replacements)
34-
- System prompts (append to default prompt)
33+
- System prompts (with configurable mode: append or replace)
3534
- Hooks (event-driven scripts)
3635

3736
### Cross-Shell Command Registration (Windows)
@@ -108,14 +107,13 @@ uv run pytest
108107

109108
**Testing workflow after code changes:**
110109
1. Make your code changes
111-
2. Run `uv run pytest` to check all tests pass (must be 312/312, not 311/312)
110+
2. Run `uv run pytest` to check all tests pass
112111
3. Fix any failing tests immediately
113112
4. Run `uv run pre-commit run --all-files` for code quality validation
114-
5. Only commit when ALL tests pass (312/312) and all pre-commit hooks pass
113+
5. Only commit when ALL tests pass and all pre-commit hooks pass
115114

116115
**IMPORTANT:**
117-
- 312/312 tests passing is the ONLY acceptable result
118-
- 311/312 or any other number is a FAILURE
116+
- All tests must pass (100% pass rate)
119117
- Use ONLY `uv run pre-commit run --all-files` for code quality checks
120118

121119
**Important:** Never skip the test suite. Even small changes can have unexpected impacts.
@@ -187,11 +185,21 @@ hooks:
187185
command: linter.py # References filename from 'files'
188186
```
189187
190-
### System Prompts vs Output Styles
188+
### System Prompt Configuration
191189
192-
- **system-prompt**: Appends to Claude's default development prompt (use `--append-system-prompt`)
193-
- **output-style**: Completely replaces the system prompt (use `--output-style`)
194-
- These are mutually exclusive in `command-defaults`
190+
The `command-defaults` section supports system prompt configuration with two modes:
191+
192+
```yaml
193+
command-defaults:
194+
system-prompt: "prompts/my-prompt.md" # Path to the system prompt file
195+
mode: "replace" # Optional: "append" or "replace" (default: "replace")
196+
```
197+
198+
**Mode Behavior:**
199+
- **mode: "replace"** (default): Completely replaces the default system prompt using `--system-prompt` flag (added in Claude Code v2.0.14)
200+
- **mode: "append"**: Appends to Claude's default development prompt using `--append-system-prompt` flag (added in Claude Code v1.0.55)
201+
202+
**Important:** If `mode` is not specified, it defaults to `"replace"` for a clean slate experience.
195203

196204
## Testing Workflows
197205

scripts/README.md

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -125,8 +125,7 @@ Configuration-driven environment setup that installs based on YAML files:
125125
- **Slash commands** specified in YAML
126126
- **MCP servers** with support for HTTP, SSE, and stdio transports
127127
- **Hooks** for automatic actions on events
128-
- **System prompts** for role-specific behavior
129-
- **Output styles** for formatted responses
128+
- **System prompts** with configurable mode (append or replace)
130129
- **Global commands** (e.g., `claude-python`) that work in all shells
131130

132131
Example Python environment includes:
@@ -264,8 +263,7 @@ Directory structure created:
264263
~/.claude/
265264
├── agents/ # Subagent configurations
266265
├── commands/ # Slash command definitions
267-
├── prompts/ # System prompts
268-
├── output-styles/ # Output style configurations
266+
├── prompts/ # System prompts (with mode: append/replace)
269267
├── hooks/ # Event handler scripts
270268
└── start-<command-name>.{ps1,sh} # Launcher script
271269
```

scripts/setup_environment.py

Lines changed: 27 additions & 90 deletions
Original file line numberDiff line numberDiff line change
@@ -513,16 +513,6 @@ def validate_all_config_files(
513513
resolved_path, is_remote = resolve_resource_path(cmd_item, config_source, base_url)
514514
files_to_check.append(('slash_command', cmd_item, resolved_path, is_remote))
515515

516-
# Output styles
517-
styles_raw = config.get('output-styles', [])
518-
if isinstance(styles_raw, list):
519-
# Cast to typed list for pyright
520-
styles_list = cast(list[object], styles_raw)
521-
for style_item in styles_list:
522-
if isinstance(style_item, str):
523-
resolved_path, is_remote = resolve_resource_path(style_item, config_source, base_url)
524-
files_to_check.append(('output_style', style_item, resolved_path, is_remote))
525-
526516
# System prompts from command-defaults
527517
command_defaults = config.get('command-defaults', {})
528518
if command_defaults and command_defaults.get('system-prompt'):
@@ -1463,45 +1453,6 @@ def extract_front_matter(file_path: Path) -> dict[str, Any] | None:
14631453
return None
14641454

14651455

1466-
def resolve_output_style_name(
1467-
output_style_ref: str,
1468-
output_styles_dir: Path,
1469-
) -> str:
1470-
"""Resolve the actual output style name from the file's front matter.
1471-
1472-
Args:
1473-
output_style_ref: Reference to output style (filename without extension or style name)
1474-
output_styles_dir: Directory containing output style files
1475-
1476-
Returns:
1477-
str: The actual style name from front matter, or cleaned reference as fallback
1478-
"""
1479-
# Remove .md extension if present
1480-
base_name = output_style_ref.replace('.md', '')
1481-
1482-
# Try to find the output style file
1483-
possible_files = [
1484-
output_styles_dir / f'{base_name}.md',
1485-
output_styles_dir / base_name, # In case it's already a full filename
1486-
]
1487-
1488-
for file_path in possible_files:
1489-
if file_path.exists():
1490-
# Try to extract name from front matter
1491-
front_matter = extract_front_matter(file_path)
1492-
if front_matter and 'name' in front_matter:
1493-
resolved_name = front_matter['name']
1494-
if resolved_name != base_name:
1495-
info(f'Resolved output style name: {base_name}{resolved_name}')
1496-
return str(resolved_name)
1497-
warning(f'No front matter or name field in {file_path}, using filename')
1498-
return base_name
1499-
1500-
# File not found, return cleaned reference
1501-
warning(f'Output style file not found for "{output_style_ref}", using as-is')
1502-
return base_name
1503-
1504-
15051456
def handle_resource(
15061457
resource_path: str,
15071458
destination: Path,
@@ -2050,14 +2001,12 @@ def create_additional_settings(
20502001
hooks: dict[str, Any],
20512002
claude_user_dir: Path,
20522003
command_name: str,
2053-
output_style: str | None = None,
20542004
model: str | None = None,
20552005
permissions: dict[str, Any] | None = None,
20562006
env: dict[str, str] | None = None,
20572007
config_source: str | None = None,
20582008
base_url: str | None = None,
20592009
auth_param: str | None = None,
2060-
output_styles_dir: Path | None = None,
20612010
include_co_authored_by: bool | None = None,
20622011
) -> bool:
20632012
"""Create {command_name}-additional-settings.json with environment-specific settings.
@@ -2069,10 +2018,13 @@ def create_additional_settings(
20692018
hooks: Hooks configuration dictionary with 'files' and 'events' keys
20702019
claude_user_dir: Path to Claude user directory
20712020
command_name: Name of the command for the environment-specific settings file
2072-
output_style: Optional output style filename (without extension) to set as default
20732021
model: Optional model alias or custom model name
20742022
permissions: Optional permissions configuration dict
20752023
env: Optional environment variables dict
2024+
config_source: Optional config source for resolving resource paths
2025+
base_url: Optional base URL for resolving resources
2026+
auth_param: Optional authentication parameter
2027+
include_co_authored_by: Optional flag to include co-authored-by in commits
20762028
20772029
Returns:
20782030
bool: True if successful, False otherwise.
@@ -2087,18 +2039,6 @@ def create_additional_settings(
20872039
settings['model'] = model
20882040
info(f'Setting model: {model}')
20892041

2090-
# Add output style if specified
2091-
if output_style:
2092-
# Resolve the actual style name from front matter
2093-
if output_styles_dir:
2094-
style_name = resolve_output_style_name(output_style, output_styles_dir)
2095-
else:
2096-
# If directory not provided, just strip .md extension
2097-
style_name = output_style.replace('.md', '')
2098-
2099-
settings['outputStyle'] = style_name
2100-
info(f'Setting default output style: {style_name}')
2101-
21022042
# Handle permissions from configuration
21032043
final_permissions = {}
21042044

@@ -2247,13 +2187,15 @@ def create_launcher_script(
22472187
claude_user_dir: Path,
22482188
command_name: str,
22492189
system_prompt_file: str | None = None,
2190+
mode: str = 'replace',
22502191
) -> Path | None:
22512192
"""Create launcher script for starting Claude with optional system prompt.
22522193
22532194
Args:
22542195
claude_user_dir: Path to Claude user directory
22552196
command_name: Name of the command to create launcher for
22562197
system_prompt_file: Optional system prompt filename (if None, only settings are used)
2198+
mode: System prompt mode ('append' or 'replace'), defaults to 'replace'
22572199
22582200
Returns:
22592201
Path to launcher script if created successfully, None otherwise
@@ -2322,6 +2264,9 @@ def create_launcher_script(
23222264
# Create shared POSIX script that actually launches Claude
23232265
shared_sh = claude_user_dir / f'launch-{command_name}.sh'
23242266

2267+
# Determine which flag to use based on mode
2268+
prompt_flag = '--system-prompt' if mode == 'replace' else '--append-system-prompt'
2269+
23252270
# Build the exec command based on whether system prompt is provided
23262271
if system_prompt_file:
23272272
shared_sh_content = f'''#!/usr/bin/env bash
@@ -2341,7 +2286,7 @@ def create_launcher_script(
23412286
# Read prompt and remove Windows CRLF
23422287
PROMPT_CONTENT=$(tr -d '\\r' < "$PROMPT_PATH")
23432288
2344-
exec claude --append-system-prompt "$PROMPT_CONTENT" --settings "$SETTINGS_WIN" "$@"
2289+
exec claude {prompt_flag} "$PROMPT_CONTENT" --settings "$SETTINGS_WIN" "$@"
23452290
'''
23462291
else:
23472292
# No system prompt, only settings
@@ -2363,6 +2308,9 @@ def create_launcher_script(
23632308
# Create bash launcher for Unix-like systems
23642309
launcher_path = launcher_path.with_suffix('.sh')
23652310

2311+
# Determine which flag to use based on mode
2312+
prompt_flag = '--system-prompt' if mode == 'replace' else '--append-system-prompt'
2313+
23662314
if system_prompt_file:
23672315
launcher_content = f'''#!/usr/bin/env bash
23682316
# Claude Code Environment Launcher
@@ -2386,9 +2334,9 @@ def create_launcher_script(
23862334
# Pass any additional arguments to Claude
23872335
if [ $# -gt 0 ]; then
23882336
echo -e "\\033[0;36mPassing additional arguments: $@\\033[0m"
2389-
claude --append-system-prompt "$PROMPT_CONTENT" --settings "$SETTINGS_PATH" "$@"
2337+
claude {prompt_flag} "$PROMPT_CONTENT" --settings "$SETTINGS_PATH" "$@"
23902338
else
2391-
claude --append-system-prompt "$PROMPT_CONTENT" --settings "$SETTINGS_PATH"
2339+
claude {prompt_flag} "$PROMPT_CONTENT" --settings "$SETTINGS_PATH"
23922340
fi
23932341
'''
23942342
else:
@@ -2676,8 +2624,13 @@ def main() -> None:
26762624

26772625
# Extract command defaults
26782626
command_defaults = config.get('command-defaults', {})
2679-
output_style = command_defaults.get('output-style')
26802627
system_prompt = command_defaults.get('system-prompt')
2628+
mode = command_defaults.get('mode', 'replace') # Default to 'replace'
2629+
2630+
# Validate mode value
2631+
if mode not in ['append', 'replace']:
2632+
error(f"Invalid mode value: {mode}. Must be 'append' or 'replace'")
2633+
sys.exit(1)
26812634

26822635
# Extract model configuration
26832636
model = config.get('model')
@@ -2742,7 +2695,6 @@ def main() -> None:
27422695
agents_dir = claude_user_dir / 'agents'
27432696
commands_dir = claude_user_dir / 'commands'
27442697
prompts_dir = claude_user_dir / 'prompts'
2745-
output_styles_dir = claude_user_dir / 'output-styles'
27462698
hooks_dir = claude_user_dir / 'hooks'
27472699

27482700
# Step 1: Install Claude Code if needed (MUST be first - provides uv, git bash, node)
@@ -2762,7 +2714,7 @@ def main() -> None:
27622714
# Step 2: Create directories
27632715
print()
27642716
print(f'{Colors.CYAN}Step 2: Creating configuration directories...{Colors.NC}')
2765-
for dir_path in [claude_user_dir, agents_dir, commands_dir, prompts_dir, output_styles_dir, hooks_dir]:
2717+
for dir_path in [claude_user_dir, agents_dir, commands_dir, prompts_dir, hooks_dir]:
27662718
dir_path.mkdir(parents=True, exist_ok=True)
27672719
success(f'Created: {dir_path}')
27682720

@@ -2793,16 +2745,7 @@ def main() -> None:
27932745
commands = config.get('slash-commands', [])
27942746
process_resources(commands, commands_dir, 'slash commands', config_source, base_url, args.auth)
27952747

2796-
# Step 7: Process output styles
2797-
print()
2798-
print(f'{Colors.CYAN}Step 7: Processing output styles...{Colors.NC}')
2799-
output_styles = config.get('output-styles', [])
2800-
if output_styles:
2801-
process_resources(output_styles, output_styles_dir, 'output styles', config_source, base_url, args.auth)
2802-
else:
2803-
info('No output styles configured')
2804-
2805-
# Step 8: Process system prompt (if specified)
2748+
# Step 7: Process system prompt (if specified)
28062749
print()
28072750
print(f'{Colors.CYAN}Step 8: Processing system prompt...{Colors.NC}')
28082751
prompt_path = None
@@ -2831,34 +2774,32 @@ def main() -> None:
28312774

28322775
# Check if command creation is needed
28332776
if command_name:
2834-
# Step 10: Configure hooks and output style
2777+
# Step 9: Configure hooks and settings
28352778
print()
2836-
print(f'{Colors.CYAN}Step 10: Configuring hooks and settings...{Colors.NC}')
2779+
print(f'{Colors.CYAN}Step 9: Configuring hooks and settings...{Colors.NC}')
28372780
hooks = config.get('hooks', {})
28382781
create_additional_settings(
28392782
hooks,
28402783
claude_user_dir,
28412784
command_name,
2842-
output_style,
28432785
model,
28442786
permissions,
28452787
env_variables,
28462788
config_source,
28472789
base_url,
28482790
args.auth,
2849-
output_styles_dir,
28502791
include_co_authored_by,
28512792
)
28522793

2853-
# Step 11: Create launcher script
2794+
# Step 10: Create launcher script
28542795
print()
28552796
print(f'{Colors.CYAN}Step 11: Creating launcher script...{Colors.NC}')
28562797
# Strip query parameters from system prompt filename (must match download logic)
28572798
prompt_filename: str | None = None
28582799
if system_prompt:
28592800
clean_prompt = system_prompt.split('?')[0] if '?' in system_prompt else system_prompt
28602801
prompt_filename = Path(clean_prompt).name
2861-
launcher_path = create_launcher_script(claude_user_dir, command_name, prompt_filename)
2802+
launcher_path = create_launcher_script(claude_user_dir, command_name, prompt_filename, mode)
28622803

28632804
# Step 12: Register global command
28642805
if launcher_path:
@@ -2886,9 +2827,6 @@ def main() -> None:
28862827
print(f" * Claude Code installation: {'Skipped' if args.skip_install else 'Completed'}")
28872828
print(f' * Agents: {len(agents)} installed')
28882829
print(f' * Slash commands: {len(commands)} installed')
2889-
print(f' * Output styles: {len(output_styles) if output_styles else 0} installed')
2890-
if output_style:
2891-
print(f' * Default output style: {output_style}')
28922830
if system_prompt:
28932831
print(' * Additional system prompt: Configured')
28942832
if model:
@@ -2929,7 +2867,6 @@ def main() -> None:
29292867
print(' * /agents - Manage subagents')
29302868
print(' * /hooks - Manage hooks')
29312869
print(' * /mcp - Manage MCP servers')
2932-
print(' * /output-style - Choose or manage output styles')
29332870
print(' * /<slash-command> - Run specific slash command')
29342871

29352872
print()

tests/conftest.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,6 @@ def sample_environment_config() -> dict[str, Any]:
5454
},
5555
],
5656
'slash-commands': ['commands/test-command.md'],
57-
'output-styles': ['styles/test-style.md'],
5857
'hooks': {
5958
'files': ['hooks/test-hook.py'],
6059
'events': [
@@ -73,7 +72,8 @@ def sample_environment_config() -> dict[str, Any]:
7372
'allow': ['mcp__test'],
7473
},
7574
'command-defaults': {
76-
'output-style': 'test-style',
75+
'system-prompt': 'prompts/test-prompt.md',
76+
'mode': 'replace', # Optional: 'append' or 'replace' (default: 'replace')
7777
},
7878
}
7979

0 commit comments

Comments
 (0)