From 8c98f443dc9aaf7579913c81ae2fa2864b8e19a0 Mon Sep 17 00:00:00 2001 From: iray-tno Date: Fri, 12 Jun 2026 16:06:00 +0900 Subject: [PATCH 1/2] feat: #12 scaffold cross-AI second opinions --- src/ai_assistant_config.py | 140 +++++++++++++++++++++++++++++- tests/test_ai_assistant_config.py | 49 +++++++++++ 2 files changed, 188 insertions(+), 1 deletion(-) create mode 100644 tests/test_ai_assistant_config.py diff --git a/src/ai_assistant_config.py b/src/ai_assistant_config.py index 35fab01..3414456 100644 --- a/src/ai_assistant_config.py +++ b/src/ai_assistant_config.py @@ -5,6 +5,30 @@ from pathlib import Path from typing import Dict, Any, Optional, List + +SECOND_OPINION_ASSISTANTS = { + "claude-code": { + "commands": ["claude"], + "label": "Claude Code", + "invoke": '{command} -p "$PROMPT" < /dev/null', + }, + "codex": { + "commands": ["codex"], + "label": "Codex", + "invoke": '{command} exec "$PROMPT" < /dev/null', + }, + "gemini-cli": { + "commands": ["gemini", "gemini-cli"], + "label": "Gemini CLI", + "invoke": '{command} -p "$PROMPT" < /dev/null', + }, + "antigravity": { + "commands": ["antigravity"], + "label": "Antigravity", + "invoke": '{command} "$PROMPT" < /dev/null', + }, +} + @click.group() def ai_config(): """Configure AI coding assistants for optimal PrepKit workflows.""" @@ -50,6 +74,101 @@ def setup_assistant_config(assistant: str, config_source: Path, workspace_path: elif assistant == 'gemini-cli': setup_gemini_specific(workspace_path) + +def detect_installed_second_opinion_assistants() -> Dict[str, str]: + """Return supported assistant names mapped to their detected CLI command.""" + installed = {} + for assistant, config in SECOND_OPINION_ASSISTANTS.items(): + for command in config["commands"]: + resolved = shutil.which(command) + if resolved: + installed[assistant] = command + break + return installed + + +def _render_second_opinion_script(target_name: str, target_command: str) -> str: + target_config = SECOND_OPINION_ASSISTANTS[target_name] + invocation = target_config["invoke"].format(command=target_command) + return f'''#!/bin/bash +set -euo pipefail + +if [ "$#" -eq 0 ]; then + echo "Usage: $0 " >&2 + exit 1 +fi + +ROOT_DIR="$(cd "$(dirname "$0")/../../.." && pwd)" +CONTEXT_FILE="$ROOT_DIR/.prepkit/second-opinion/context.md" +REQUEST="$*" + +if [ -f "$CONTEXT_FILE" ]; then + CONTEXT="$(cat "$CONTEXT_FILE")" +else + CONTEXT="No shared project context file found." +fi + +PROMPT="$(cat < None: + """Create symmetric second-opinion scripts for the installed assistants.""" + base_dir = workspace_path / '.prepkit' / 'second-opinion' + base_dir.mkdir(parents=True, exist_ok=True) + + context_file = base_dir / 'context.md' + if not context_file.exists(): + context_file.write_text('''# PrepKit Second-Opinion Context + +Summarize the current task, constraints, rejected ideas, and local verification commands here before asking another assistant for review. + +Ground rules: +- Treat second opinions as proposals, not truth. +- Verify suggestions with local tests, benchmarks, or targeted code inspection. +- Prefer concrete file paths, commands, and measurable claims. +''') + click.echo(f"🧭 Created shared context primer: {context_file}") + + for source_name in installed: + source_dir = base_dir / source_name + source_dir.mkdir(parents=True, exist_ok=True) + + target_names = [name for name in installed if name != source_name] + readme_lines = [ + f"# Second opinions for {SECOND_OPINION_ASSISTANTS[source_name]['label']}", + "", + "Use these scripts to ask a different installed assistant for an independent review.", + "Update `../context.md` first so the consulted assistant has project-specific grounding.", + "", + ] + + for target_name in target_names: + script_name = f"ask-{target_name}.sh" + script_file = source_dir / script_name + script_file.write_text(_render_second_opinion_script(target_name, installed[target_name])) + script_file.chmod(0o755) + readme_lines.append(f"- `{script_name}` asks {SECOND_OPINION_ASSISTANTS[target_name]['label']}.") + + (source_dir / "README.md").write_text("\n".join(readme_lines) + "\n") + click.echo(f"🔁 Created {len(target_names)} second-opinion script(s) for {source_name}") + def setup_copilot_specific(workspace_path: Path) -> None: """Set up GitHub Copilot specific configuration.""" vscode_dir = workspace_path / '.vscode' @@ -313,6 +432,25 @@ def status(assistant: Optional[str]) -> None: else: click.echo(f"❌ {ai_assistant}: Not configured") + +@ai_config.command(name="second-opinion") +@click.option('--workspace-dir', type=click.Path(), default='.', help="Target workspace directory (default: current directory)") +def second_opinion(workspace_dir: str) -> None: + """Scaffold cross-AI second-opinion scripts for installed assistant CLIs.""" + workspace_path = Path(workspace_dir).resolve() + installed = detect_installed_second_opinion_assistants() + + if len(installed) < 2: + click.echo("❌ Need at least two supported assistant CLIs installed for second-opinion scaffolding.") + supported = ", ".join(SECOND_OPINION_ASSISTANTS.keys()) + click.echo(f" Supported assistants: {supported}") + return + + detected = ", ".join(installed.keys()) + click.echo(f"Detected assistant CLIs: {detected}") + scaffold_second_opinion(workspace_path, installed) + click.echo("✅ Cross-AI second-opinion scaffolding complete!") + @ai_config.command() @click.argument('assistant', type=click.Choice(['claude-code', 'github-copilot', 'gemini-cli'])) def docs(assistant: str) -> None: @@ -335,4 +473,4 @@ def docs(assistant: str) -> None: click.echo(f"❌ No configuration found for {assistant}. Run setup first.") if __name__ == '__main__': - ai_config() \ No newline at end of file + ai_config() diff --git a/tests/test_ai_assistant_config.py b/tests/test_ai_assistant_config.py new file mode 100644 index 0000000..d3e5fee --- /dev/null +++ b/tests/test_ai_assistant_config.py @@ -0,0 +1,49 @@ +from click.testing import CliRunner + +import ai_assistant_config +from main import cli + + +def test_second_opinion_requires_at_least_two_assistants(tmp_path, monkeypatch): + """Second-opinion scaffolding needs at least two detected CLIs.""" + monkeypatch.setattr(ai_assistant_config.shutil, "which", lambda command: None) + + result = CliRunner().invoke( + cli, + ["ai-config", "second-opinion", "--workspace-dir", str(tmp_path)], + ) + + assert result.exit_code == 0 + assert "Need at least two supported assistant CLIs" in result.output + assert not (tmp_path / ".prepkit" / "second-opinion").exists() + + +def test_second_opinion_scaffolds_symmetric_scripts(tmp_path, monkeypatch): + """Each detected assistant gets scripts for the other detected assistants.""" + installed_commands = {"claude": "/usr/bin/claude", "codex": "/usr/bin/codex"} + monkeypatch.setattr( + ai_assistant_config.shutil, + "which", + lambda command: installed_commands.get(command), + ) + + result = CliRunner().invoke( + cli, + ["ai-config", "second-opinion", "--workspace-dir", str(tmp_path)], + ) + + assert result.exit_code == 0 + assert "Detected assistant CLIs: claude-code, codex" in result.output + + base_dir = tmp_path / ".prepkit" / "second-opinion" + assert (base_dir / "context.md").exists() + + claude_to_codex = base_dir / "claude-code" / "ask-codex.sh" + codex_to_claude = base_dir / "codex" / "ask-claude-code.sh" + assert claude_to_codex.exists() + assert codex_to_claude.exists() + assert not (base_dir / "claude-code" / "ask-claude-code.sh").exists() + + assert 'codex exec "$PROMPT" < /dev/null' in claude_to_codex.read_text() + assert 'claude -p "$PROMPT" < /dev/null' in codex_to_claude.read_text() + assert "ask-codex.sh" in (base_dir / "claude-code" / "README.md").read_text() From 2d4e393bf4e8a48800016df1964aca5ff3552db2 Mon Sep 17 00:00:00 2001 From: iray-tno Date: Fri, 12 Jun 2026 17:46:50 +0900 Subject: [PATCH 2/2] chore: #12 replace Gemini CLI with Antigravity CLI --- configs/ai-assistants/antigravity-cli.md | 26 +++ configs/ai-assistants/gemini-cli.md | 235 ----------------------- src/ai_assistant_config.py | 127 +++--------- tests/test_ai_assistant_config.py | 16 ++ 4 files changed, 67 insertions(+), 337 deletions(-) create mode 100644 configs/ai-assistants/antigravity-cli.md delete mode 100644 configs/ai-assistants/gemini-cli.md diff --git a/configs/ai-assistants/antigravity-cli.md b/configs/ai-assistants/antigravity-cli.md new file mode 100644 index 0000000..c237f9e --- /dev/null +++ b/configs/ai-assistants/antigravity-cli.md @@ -0,0 +1,26 @@ +# Antigravity CLI Configuration for PrepKit + +PrepKit uses Antigravity CLI as an optional coding assistant for independent review and second-opinion workflows. + +## Recommended Usage + +Use PrepKit's generated second-opinion scripts rather than calling assistants ad hoc: + +```bash +uv run prepkit ai-config setup antigravity-cli +uv run prepkit ai-config second-opinion +``` + +Before asking for a second opinion, update: + +```text +.prepkit/second-opinion/context.md +``` + +Include: +- the current task and constraints +- files already inspected +- commands/tests already run +- ideas already rejected + +Treat assistant output as a proposal. Verify recommendations with local tests, benchmarks, or direct code inspection. diff --git a/configs/ai-assistants/gemini-cli.md b/configs/ai-assistants/gemini-cli.md deleted file mode 100644 index b8e1bda..0000000 --- a/configs/ai-assistants/gemini-cli.md +++ /dev/null @@ -1,235 +0,0 @@ -# Gemini CLI Configuration for PrepKit - -## Setup - -Install and configure Gemini CLI: -```bash -pip install google-generativeai -export GOOGLE_API_KEY="your-api-key" -``` - -## Custom Commands for Competitive Programming - -Create shell aliases for common tasks: - -```bash -# ~/.bashrc or ~/.zshrc -alias gem-analyze='gemini-cli "Analyze this competitive programming code for correctness and efficiency:"' -alias gem-optimize='gemini-cli "Optimize this C++ code for competitive programming, focusing on performance:"' -alias gem-debug='gemini-cli "Debug this competitive programming solution. Check for common issues:"' -alias gem-explain='gemini-cli "Explain this algorithm step by step for competitive programming:"' -``` - -## Prompt Templates - -### Code Analysis Template -```bash -#!/bin/bash -# gem-analyze-cp.sh - Analyze competitive programming code - -if [ $# -ne 1 ]; then - echo "Usage: $0 " - exit 1 -fi - -gemini-cli " -As an expert in competitive programming, analyze this C++ code: - -$(cat $1) - -Please check: -1. Algorithm correctness and complexity -2. Edge cases and boundary conditions -3. Input/output format correctness -4. Potential optimization opportunities -5. PrepKit preprocessing compatibility (constexpr usage, include patterns) - -Provide specific improvements and explain your reasoning. -" -``` - -### Test Case Generation -```bash -#!/bin/bash -# gem-testcases.sh - Generate test cases - -gemini-cli " -Generate comprehensive test cases for this competitive programming problem: - -$(cat problem_statement.txt) - -Include: -- Sample input/output from problem statement -- Edge cases (minimum/maximum constraints) -- Corner cases (empty input, single elements) -- Stress test cases (large inputs) -- Expected algorithmic behavior verification - -Format as: -INPUT: -[test case] -EXPECTED OUTPUT: -[expected result] -" -``` - -### Algorithm Template Generation -```bash -#!/bin/bash -# gem-template.sh - Generate algorithm template - -ALGORITHM=$1 -shift -CONSTRAINTS="$@" - -gemini-cli " -Generate a C++ competitive programming template for: $ALGORITHM - -Constraints: $CONSTRAINTS - -Requirements: -- Optimized for competitive programming -- Include necessary headers and using namespace std -- Use constexpr for constants that won't change -- Add comments explaining key parts -- Include main() function with fast I/O setup -- Make it compatible with PrepKit preprocessing (single-file output) - -Template should be production-ready for contests. -" -``` - -## Integration Scripts - -### Preprocessing Analysis -```bash -#!/bin/bash -# gem-preprocess.sh - Analyze preprocessing results - -ORIGINAL=$1 -PREPROCESSED=$2 - -gemini-cli " -Compare these two C++ files - original vs preprocessed by PrepKit: - -ORIGINAL: -$(cat $ORIGINAL) - -PREPROCESSED: -$(cat $PREPROCESSED) - -Analyze: -1. What transformations were applied? -2. Are there any issues with the preprocessing? -3. Is the functionality preserved? -4. Can the code be further optimized? -5. Any potential runtime issues? -" -``` - -### Experiment Analysis -```bash -#!/bin/bash -# gem-experiment.sh - Analyze ML experiment results - -gemini-cli " -Analyze this machine learning experiment configuration and results: - -CONFIG: -$(cat conf/config.yaml) - -WANDB LOGS: -$(cat experiment_logs.txt) - -Please provide: -1. Hyperparameter optimization suggestions -2. Potential overfitting/underfitting issues -3. Recommendations for next experiments -4. Kaggle submission strategy based on validation scores -" -``` - -## VS Code Integration - -Create `.vscode/tasks.json`: -```json -{ - "version": "2.0.0", - "tasks": [ - { - "label": "Gemini Analyze Code", - "type": "shell", - "command": "gem-analyze", - "args": ["${file}"], - "group": "build", - "presentation": { - "echo": true, - "reveal": "always" - } - }, - { - "label": "Gemini Generate Tests", - "type": "shell", - "command": "gem-testcases.sh", - "group": "test" - }, - { - "label": "Gemini Optimize Code", - "type": "shell", - "command": "gem-optimize", - "args": ["${file}"] - } - ] -} -``` - -## Batch Processing Scripts - -### Contest Preparation -```bash -#!/bin/bash -# contest-prep.sh - Prepare for contest with Gemini analysis - -echo "Analyzing contest problems with Gemini..." - -for problem in problems/*.cpp; do - echo "=== Analyzing $(basename $problem) ===" - gem-analyze $problem > analyses/$(basename $problem .cpp)_analysis.txt - - echo "Generating test cases..." - gem-testcases.sh < problems/$(basename $problem .cpp)_statement.txt > tests/$(basename $problem .cpp)_tests.txt -done - -echo "Contest preparation complete! Check analyses/ and tests/ directories." -``` - -### Experiment Review -```bash -#!/bin/bash -# review-experiments.sh - Review recent experiments - -echo "Analyzing recent experiments..." - -for run_dir in wandb/latest-run/files/; do - if [ -f "$run_dir/config.yaml" ]; then - echo "=== Reviewing $(basename $run_dir) ===" - gem-experiment.sh $run_dir > reviews/$(basename $run_dir)_review.txt - fi -done -``` - -## Best Practices - -1. **Context Setting**: Always provide problem constraints and requirements -2. **Iterative Refinement**: Use follow-up prompts to drill down on specific issues -3. **Code Validation**: Cross-check Gemini suggestions with actual testing -4. **Performance Focus**: Emphasize competitive programming performance requirements -5. **Integration**: Combine with PrepKit tools for complete workflow automation - -## Performance Tips - -- Use specific, detailed prompts for better results -- Include problem constraints and time limits -- Reference competitive programming best practices -- Validate suggestions through testing -- Save effective prompts as templates for reuse \ No newline at end of file diff --git a/src/ai_assistant_config.py b/src/ai_assistant_config.py index 3414456..75577f6 100644 --- a/src/ai_assistant_config.py +++ b/src/ai_assistant_config.py @@ -17,12 +17,7 @@ "label": "Codex", "invoke": '{command} exec "$PROMPT" < /dev/null', }, - "gemini-cli": { - "commands": ["gemini", "gemini-cli"], - "label": "Gemini CLI", - "invoke": '{command} -p "$PROMPT" < /dev/null', - }, - "antigravity": { + "antigravity-cli": { "commands": ["antigravity"], "label": "Antigravity", "invoke": '{command} "$PROMPT" < /dev/null', @@ -35,7 +30,7 @@ def ai_config(): pass @ai_config.command() -@click.argument('assistant', type=click.Choice(['claude-code', 'github-copilot', 'gemini-cli', 'all'])) +@click.argument('assistant', type=click.Choice(['claude-code', 'github-copilot', 'antigravity-cli', 'all'])) @click.option('--workspace-dir', type=click.Path(), default='.', help="Target workspace directory (default: current directory)") def setup(assistant: str, workspace_dir: str) -> None: """Set up AI assistant configuration files in the workspace.""" @@ -45,7 +40,7 @@ def setup(assistant: str, workspace_dir: str) -> None: click.echo(f"Setting up {assistant} configuration in {workspace_path}") if assistant == 'all': - assistants_to_setup = ['claude-code', 'github-copilot', 'gemini-cli'] + assistants_to_setup = ['claude-code', 'github-copilot', 'antigravity-cli'] else: assistants_to_setup = [assistant] @@ -71,8 +66,8 @@ def setup_assistant_config(assistant: str, config_source: Path, workspace_path: setup_copilot_specific(workspace_path) elif assistant == 'claude-code': setup_claude_code_specific(workspace_path) - elif assistant == 'gemini-cli': - setup_gemini_specific(workspace_path) + elif assistant == 'antigravity-cli': + setup_antigravity_specific(workspace_path) def detect_installed_second_opinion_assistants() -> Dict[str, str]: @@ -315,96 +310,25 @@ def setup_claude_code_specific(workspace_path: Path) -> None: f.write(content) click.echo(f"📄 Created Claude Code prompt: {filename}") -def setup_gemini_specific(workspace_path: Path) -> None: - """Set up Gemini CLI specific configuration.""" - gemini_dir = workspace_path / '.prepkit' / 'gemini-cli' - gemini_dir.mkdir(parents=True, exist_ok=True) - - # Create shell scripts for common tasks - scripts = { - 'analyze-cp.sh': '''#!/bin/bash -# Analyze competitive programming code with Gemini - -if [ $# -ne 1 ]; then - echo "Usage: $0 " - exit 1 -fi - -gemini-cli " -As an expert in competitive programming, analyze this C++ code: - -$(cat $1) - -Please check: -1. Algorithm correctness and complexity -2. Edge cases and boundary conditions -3. Input/output format correctness -4. Potential optimization opportunities -5. PrepKit preprocessing compatibility - -Provide specific improvements and explain reasoning. -" -''', - 'optimize-code.sh': '''#!/bin/bash -# Optimize code for competitive programming - -if [ $# -ne 1 ]; then - echo "Usage: $0 " - exit 1 -fi +def setup_antigravity_specific(workspace_path: Path) -> None: + """Set up Antigravity CLI specific guidance.""" + antigravity_dir = workspace_path / '.prepkit' / 'antigravity-cli' + antigravity_dir.mkdir(parents=True, exist_ok=True) -gemini-cli " -Optimize this C++ competitive programming code for performance: + guide_file = antigravity_dir / 'second-opinion.md' + guide_file.write_text('''# Antigravity CLI Second-Opinion Guide -$(cat $1) +Use `prepkit ai-config second-opinion` to create scripts that ask other installed assistants for independent review. -Focus on: -- Time complexity improvements -- Memory usage optimization -- Constant factor optimizations -- PrepKit constexpr opportunities -- Code size reduction for minification - -Provide optimized version with explanations. -" -''', - 'generate-tests.sh': '''#!/bin/bash -# Generate test cases with Gemini - -if [ $# -ne 1 ]; then - echo "Usage: $0 " - exit 1 -fi - -gemini-cli " -Generate comprehensive test cases for this competitive programming problem: - -$(cat $1) - -Include: -- Sample input/output from problem -- Edge cases (min/max constraints) -- Corner cases (empty, single elements) -- Stress test cases (large inputs) - -Format each as: -INPUT: -[test case] -EXPECTED OUTPUT: -[expected result] -" -''' - } - - for script_name, script_content in scripts.items(): - script_file = gemini_dir / script_name - with open(script_file, 'w') as f: - f.write(script_content) - script_file.chmod(0o755) # Make executable - click.echo(f"🔧 Created Gemini script: {script_name}") +Before asking for review: +- update `.prepkit/second-opinion/context.md` +- include the concrete files, commands, and measurements already checked +- verify any recommendation with local tests or benchmarks +''') + click.echo(f"📄 Created Antigravity CLI guide: {guide_file}") @ai_config.command() -@click.option('--assistant', type=click.Choice(['claude-code', 'github-copilot', 'gemini-cli'])) +@click.option('--assistant', type=click.Choice(['claude-code', 'github-copilot', 'antigravity-cli'])) def status(assistant: Optional[str]) -> None: """Show status of AI assistant configurations.""" workspace_path = Path('.').resolve() @@ -414,7 +338,7 @@ def status(assistant: Optional[str]) -> None: click.echo("❌ No AI assistant configurations found. Run 'setup' first.") return - assistants_to_check = [assistant] if assistant else ['claude-code', 'github-copilot', 'gemini-cli'] + assistants_to_check = [assistant] if assistant else ['claude-code', 'github-copilot', 'antigravity-cli'] for ai_assistant in assistants_to_check: config_file = ai_config_dir / f"{ai_assistant}.md" @@ -424,11 +348,10 @@ def status(assistant: Optional[str]) -> None: vscode_settings = workspace_path / '.vscode' / 'settings.json' if vscode_settings.exists(): click.echo(f" 📁 VS Code settings: {vscode_settings}") - elif ai_assistant == 'gemini-cli': - gemini_dir = workspace_path / '.prepkit' / 'gemini-cli' - if gemini_dir.exists(): - scripts = list(gemini_dir.glob('*.sh')) - click.echo(f" 🔧 Scripts available: {len(scripts)}") + elif ai_assistant == 'antigravity-cli': + guide_file = workspace_path / '.prepkit' / 'antigravity-cli' / 'second-opinion.md' + if guide_file.exists(): + click.echo(f" 📄 Antigravity guide: {guide_file}") else: click.echo(f"❌ {ai_assistant}: Not configured") @@ -452,7 +375,7 @@ def second_opinion(workspace_dir: str) -> None: click.echo("✅ Cross-AI second-opinion scaffolding complete!") @ai_config.command() -@click.argument('assistant', type=click.Choice(['claude-code', 'github-copilot', 'gemini-cli'])) +@click.argument('assistant', type=click.Choice(['claude-code', 'github-copilot', 'antigravity-cli'])) def docs(assistant: str) -> None: """Open documentation for an AI assistant.""" workspace_path = Path('.').resolve() diff --git a/tests/test_ai_assistant_config.py b/tests/test_ai_assistant_config.py index d3e5fee..f68d0b5 100644 --- a/tests/test_ai_assistant_config.py +++ b/tests/test_ai_assistant_config.py @@ -18,6 +18,22 @@ def test_second_opinion_requires_at_least_two_assistants(tmp_path, monkeypatch): assert not (tmp_path / ".prepkit" / "second-opinion").exists() +def test_setup_all_includes_antigravity_not_gemini(tmp_path): + """The supported assistant set includes Antigravity CLI and excludes Gemini CLI.""" + result = CliRunner().invoke( + cli, + ["ai-config", "setup", "all", "--workspace-dir", str(tmp_path)], + ) + + assert result.exit_code == 0 + ai_config_dir = tmp_path / ".prepkit" / "ai-assistants" + assert (ai_config_dir / "claude-code.md").exists() + assert (ai_config_dir / "github-copilot.md").exists() + assert (ai_config_dir / "antigravity-cli.md").exists() + assert not (ai_config_dir / "gemini-cli.md").exists() + assert (tmp_path / ".prepkit" / "antigravity-cli" / "second-opinion.md").exists() + + def test_second_opinion_scaffolds_symmetric_scripts(tmp_path, monkeypatch): """Each detected assistant gets scripts for the other detected assistants.""" installed_commands = {"claude": "/usr/bin/claude", "codex": "/usr/bin/codex"}