feat: add multi-provider AI system with model hint optimization
Major automation enhancements for flexible AI provider configuration: 1. Add config/ai.yml - Centralized AI configuration - Three command chains: default, fast, quality - Multi-provider fallback (Claude → Codex → Gemini) - Configurable per optimization level - Sentinel token configuration 2. Extend automation/ai_config.py - Add RunnerSettings with three chain support - Add get_chain_for_hint() method - Load and validate all three command chains - Proper fallback to defaults 3. Update automation/runner.py - Read model_hint from .ai-rules.yml - Pass model_hint to generate_output() - Support output_type hint overrides 4. Update automation/patcher.py - Add model_hint parameter throughout pipeline - Inject TASK COMPLEXITY hint into prompts - ModelConfig.get_commands_for_hint() selects chain - Fallback mechanism tries all commands in chain 5. Add design discussion stage to features.ai-rules.yml - New design_gate_writer rule (model_hint: fast) - New design_discussion_writer rule (model_hint: quality) - Update feature_request to create design gate - Update feature_discussion to create design gate - Add design.discussion.md file associations - Proper status transitions: READY_FOR_DESIGN → READY_FOR_IMPLEMENTATION 6. Add assets/templates/design.discussion.md - Template for Stage 3 design discussions - META header with tokens support - Design goals and participation instructions 7. Update tools/setup_claude_agents.sh - Agent descriptions reference TASK COMPLEXITY hint - cdev-patch: "MUST BE USED when TASK COMPLEXITY is FAST" - cdev-patch-quality: "MUST BE USED when TASK COMPLEXITY is QUALITY" 8. Fix assets/hooks/pre-commit - Correct template path comment (process/templates not assets/templates) 9. Update tools/mock_ai.sh - Log prompts to /tmp/mock_ai_prompts.log for debugging Impact: - Users can configure AI providers via config/ai.yml - Automatic fallback between Claude, Codex, Gemini - Fast models for simple tasks (vote counting, gate checks) - Quality models for complex tasks (design, implementation planning) - Reduced costs through intelligent model selection - Design stage now properly integrated into workflow 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
parent
0e83b6cf88
commit
372d24b30d
|
|
@ -88,13 +88,13 @@ check_append_only_discussion() {
|
|||
# - {discussion}.sum.md (e.g., "feature.discussion.sum.md")
|
||||
# - Auto-stages the new file for inclusion in the commit
|
||||
#
|
||||
# Template location: assets/templates/feature.discussion.sum.md
|
||||
# Template location: process/templates/feature.discussion.sum.md
|
||||
# ============================================================================
|
||||
ensure_summary() {
|
||||
local disc="$1"
|
||||
local dir; dir="$(dirname "$disc")"
|
||||
local sum="$dir/$(basename "$disc" .md).sum.md"
|
||||
local template_path="assets/templates/feature.discussion.sum.md"
|
||||
local template_path="process/templates/feature.discussion.sum.md"
|
||||
|
||||
# Only create if it doesn't exist
|
||||
if [ ! -f "$sum" ]; then
|
||||
|
|
|
|||
|
|
@ -0,0 +1,24 @@
|
|||
<!--META
|
||||
{
|
||||
"kind": "discussion",
|
||||
"tokens": ["FeatureId", "CreatedDate"]
|
||||
}
|
||||
-->
|
||||
|
||||
## Summary
|
||||
Design discussion for {FeatureId}. This stage focuses on architecture, technology choices, and technical implementation approach.
|
||||
|
||||
## Design Goals
|
||||
- Define system architecture and component interactions
|
||||
- Choose appropriate technologies and frameworks
|
||||
- Identify data models and API contracts
|
||||
- Document trade-offs and decision rationale
|
||||
- Establish measurable acceptance criteria
|
||||
|
||||
## Participation
|
||||
- Append your input at the end as: "YourName: your comment…"
|
||||
- Every comment must end with a vote line: "VOTE: READY|CHANGES|REJECT"
|
||||
- Focus on: architecture, scalability, maintainability, technical risks
|
||||
|
||||
## Design Artifacts
|
||||
The AI will maintain `design/design.md` based on this discussion.
|
||||
|
|
@ -4,6 +4,8 @@ file_associations:
|
|||
"request.md": "feature_request"
|
||||
"feature.discussion.md": "feature_discussion_update"
|
||||
"feature.discussion.sum.md": "discussion_summary"
|
||||
"design.discussion.md": "design_discussion_update"
|
||||
"design.discussion.sum.md": "discussion_summary"
|
||||
"implementation.discussion.md": "implementation_discussion_update"
|
||||
"implementation.discussion.sum.md": "discussion_summary"
|
||||
|
||||
|
|
@ -13,15 +15,27 @@ rules:
|
|||
feature_discussion:
|
||||
path: "Docs/features/{feature_id}/discussions/feature.discussion.md"
|
||||
output_type: "feature_discussion_writer"
|
||||
implementation_gate:
|
||||
path: "Docs/features/{feature_id}/discussions/implementation.discussion.md"
|
||||
output_type: "implementation_gate_writer"
|
||||
design_gate:
|
||||
path: "Docs/features/{feature_id}/discussions/design.discussion.md"
|
||||
output_type: "design_gate_writer"
|
||||
|
||||
feature_discussion_update:
|
||||
outputs:
|
||||
self_append:
|
||||
path: "{path}"
|
||||
output_type: "feature_discussion_writer"
|
||||
design_gate:
|
||||
path: "Docs/features/{feature_id}/discussions/design.discussion.md"
|
||||
output_type: "design_gate_writer"
|
||||
|
||||
design_discussion_update:
|
||||
outputs:
|
||||
self_append:
|
||||
path: "{path}"
|
||||
output_type: "design_discussion_writer"
|
||||
implementation_gate:
|
||||
path: "Docs/features/{feature_id}/discussions/implementation.discussion.md"
|
||||
output_type: "implementation_gate_writer"
|
||||
|
||||
implementation_discussion_update:
|
||||
outputs:
|
||||
|
|
@ -38,6 +52,7 @@ rules:
|
|||
If missing, create summary with standard markers. Only modify the content between the SUMMARY markers.
|
||||
|
||||
feature_discussion_writer:
|
||||
model_hint: fast # Simple vote counting and comment appending - use fast models
|
||||
instruction: |
|
||||
You maintain the feature discussion derived from the feature request.
|
||||
|
||||
|
|
@ -81,7 +96,7 @@ rules:
|
|||
* Integer `N` → require at least `N` votes.
|
||||
* `"all"` → require a vote from every eligible voter (and none opposing for READY).
|
||||
* If there are no eligible voters the `"all"` condition never passes.
|
||||
- Promotion (`status: READY_FOR_IMPLEMENTATION`):
|
||||
- Promotion (`status: READY_FOR_DESIGN`):
|
||||
* READY threshold satisfied AND REJECT threshold NOT satisfied.
|
||||
- Rejection (`status: FEATURE_REJECTED`):
|
||||
* REJECT threshold satisfied AND READY threshold NOT satisfied.
|
||||
|
|
@ -92,14 +107,99 @@ rules:
|
|||
- Emit a single unified diff touching only this discussion file.
|
||||
- Keep diffs minimal (append-only plus header adjustments).
|
||||
|
||||
design_gate_writer:
|
||||
model_hint: fast # Simple gate checking and file creation - use fast models
|
||||
instruction: |
|
||||
Create or update the design discussion located at the path provided.
|
||||
|
||||
Creation criteria:
|
||||
- Locate the sibling feature discussion (`feature.discussion.md`).
|
||||
- Read its YAML header. Only create/update this design file when that header shows `status: READY_FOR_DESIGN`.
|
||||
- If the status is `OPEN` or `FEATURE_REJECTED`, produce **no diff**.
|
||||
|
||||
When creating the design discussion:
|
||||
---
|
||||
type: design-discussion
|
||||
stage: design
|
||||
status: OPEN
|
||||
feature_id: <same feature id as the source request>
|
||||
created: <today in YYYY-MM-DD>
|
||||
promotion_rule:
|
||||
allow_agent_votes: false
|
||||
ready_min_eligible_votes: 2
|
||||
reject_min_eligible_votes: 1
|
||||
participation:
|
||||
instructions: |
|
||||
- Append your input at the end as: "YourName: your comment…"
|
||||
- Every comment must end with a vote line: "VOTE: READY|CHANGES|REJECT"
|
||||
- Agents/bots must prefix names with "AI_". Example: "AI_Claude: … VOTE: CHANGES"
|
||||
voting:
|
||||
values: [READY, CHANGES, REJECT]
|
||||
---
|
||||
Sections to include:
|
||||
## Summary – brief overview of design goals
|
||||
## Architecture – component interactions, system structure
|
||||
## Technology Choices – frameworks, libraries, rationale
|
||||
## Data Models – schemas, API contracts
|
||||
## Risks / Trade-offs – known concerns or alternatives
|
||||
|
||||
Subsequent updates:
|
||||
- Keep diffs minimal, amending sections in place.
|
||||
- Do not change status automatically; human votes or policies will manage it.
|
||||
|
||||
Output a unified diff for this file only. If no changes are required, emit nothing.
|
||||
|
||||
design_discussion_writer:
|
||||
model_hint: quality # Complex architecture and design work - use quality models
|
||||
instruction: |
|
||||
You maintain the design discussion derived from the feature discussion.
|
||||
|
||||
If the discussion file is missing:
|
||||
- Create it with the same YAML header structure as feature discussions (see design_gate_writer).
|
||||
- Add sections:
|
||||
## Summary – brief overview linking to feature request
|
||||
## Architecture – system design and component interactions
|
||||
## Technology Stack – chosen technologies and rationale
|
||||
## Data Models & APIs – schemas, contracts, endpoints
|
||||
## Acceptance Criteria – measurable design quality gates
|
||||
## Risks / Trade-offs – known concerns or alternatives
|
||||
- Append an initial comment signed `AI_Claude:` proposing architecture and ending with a vote line.
|
||||
|
||||
If the discussion exists:
|
||||
- Append a concise AI_Claude comment at the end proposing design refinements/questions.
|
||||
- Always end your comment with exactly one vote line: `VOTE: READY`, `VOTE: CHANGES`, or `VOTE: REJECT`.
|
||||
|
||||
Voting & promotion rules:
|
||||
- Read `promotion_rule` from the header (same logic as feature_discussion_writer).
|
||||
- Eligible voters:
|
||||
* allow_agent_votes=false → ignore names starting with "AI_" (case-insensitive)
|
||||
* allow_agent_votes=true → everyone counts
|
||||
- For each participant the most recent vote wins. A vote is a line matching `VOTE:\s*(READY|CHANGES|REJECT)`.
|
||||
- Count READY and REJECT votes among eligible voters. CHANGES is neutral.
|
||||
- Threshold interpretation:
|
||||
* Integer `N` → require at least `N` votes.
|
||||
* `"all"` → require a vote from every eligible voter (and none opposing for READY).
|
||||
* If there are no eligible voters the `"all"` condition never passes.
|
||||
- Promotion (`status: READY_FOR_IMPLEMENTATION`):
|
||||
* READY threshold satisfied AND REJECT threshold NOT satisfied.
|
||||
- Rejection (`status: DESIGN_REJECTED`):
|
||||
* REJECT threshold satisfied AND READY threshold NOT satisfied.
|
||||
- Otherwise keep `status: OPEN`.
|
||||
- When the status changes, update the header and state the outcome explicitly in your comment.
|
||||
|
||||
Output requirements:
|
||||
- Emit a single unified diff touching only this discussion file.
|
||||
- Keep diffs minimal (append-only plus header adjustments).
|
||||
|
||||
implementation_gate_writer:
|
||||
model_hint: fast # Simple gate checking and file creation - use fast models
|
||||
instruction: |
|
||||
Create or update the implementation discussion located at the path provided.
|
||||
|
||||
Creation criteria:
|
||||
- Locate the sibling feature discussion (`feature.discussion.md`).
|
||||
- Locate the sibling design discussion (`design.discussion.md`).
|
||||
- Read its YAML header. Only create/update this implementation file when that header shows `status: READY_FOR_IMPLEMENTATION`.
|
||||
- If the status is `OPEN` or `FEATURE_REJECTED`, produce **no diff**.
|
||||
- If the status is `OPEN` or `DESIGN_REJECTED`, produce **no diff**.
|
||||
|
||||
When creating the implementation discussion:
|
||||
---
|
||||
|
|
@ -122,6 +222,7 @@ rules:
|
|||
Output a unified diff for this file only. If no changes are required, emit nothing.
|
||||
|
||||
impl_discussion_writer:
|
||||
model_hint: quality # Implementation planning requires careful task breakdown - use quality models
|
||||
instruction: |
|
||||
Append planning updates to the implementation discussion in an incremental, checklist-driven style.
|
||||
- Work within the existing sections (Scope, Tasks, Acceptance Criteria, Risks / Notes).
|
||||
|
|
|
|||
|
|
@ -38,8 +38,27 @@ def parse_command_chain(raw: str | None) -> List[str]:
|
|||
@dataclass
|
||||
class RunnerSettings:
|
||||
command_chain: List[str]
|
||||
command_chain_fast: List[str]
|
||||
command_chain_quality: List[str]
|
||||
sentinel: str
|
||||
|
||||
def get_chain_for_hint(self, hint: str) -> List[str]:
|
||||
"""
|
||||
Return the appropriate command chain based on the model hint.
|
||||
|
||||
Args:
|
||||
hint: 'fast', 'quality', or empty string
|
||||
|
||||
Returns:
|
||||
The appropriate command chain list
|
||||
"""
|
||||
if hint == "fast" and self.command_chain_fast:
|
||||
return self.command_chain_fast
|
||||
elif hint == "quality" and self.command_chain_quality:
|
||||
return self.command_chain_quality
|
||||
else:
|
||||
return self.command_chain
|
||||
|
||||
|
||||
@dataclass
|
||||
class RambleSettings:
|
||||
|
|
@ -80,6 +99,7 @@ def load_ai_settings(repo_root: Path) -> AISettings:
|
|||
if not isinstance(runner_data, dict):
|
||||
runner_data = {}
|
||||
|
||||
# Load default command chain
|
||||
chain = runner_data.get("command_chain", [])
|
||||
if not isinstance(chain, list):
|
||||
chain = []
|
||||
|
|
@ -87,6 +107,18 @@ def load_ai_settings(repo_root: Path) -> AISettings:
|
|||
if not command_chain:
|
||||
command_chain = DEFAULT_COMMAND_CHAIN.copy()
|
||||
|
||||
# Load fast-optimized command chain (optional)
|
||||
chain_fast = runner_data.get("command_chain_fast", [])
|
||||
if not isinstance(chain_fast, list):
|
||||
chain_fast = []
|
||||
command_chain_fast = [str(entry).strip() for entry in chain_fast if str(entry).strip()]
|
||||
|
||||
# Load quality-optimized command chain (optional)
|
||||
chain_quality = runner_data.get("command_chain_quality", [])
|
||||
if not isinstance(chain_quality, list):
|
||||
chain_quality = []
|
||||
command_chain_quality = [str(entry).strip() for entry in chain_quality if str(entry).strip()]
|
||||
|
||||
sentinel = runner_data.get("sentinel")
|
||||
if not isinstance(sentinel, str) or not sentinel.strip():
|
||||
sentinel = DEFAULT_SENTINEL
|
||||
|
|
@ -104,7 +136,12 @@ def load_ai_settings(repo_root: Path) -> AISettings:
|
|||
providers = {}
|
||||
|
||||
return AISettings(
|
||||
runner=RunnerSettings(command_chain=command_chain, sentinel=sentinel),
|
||||
runner=RunnerSettings(
|
||||
command_chain=command_chain,
|
||||
command_chain_fast=command_chain_fast,
|
||||
command_chain_quality=command_chain_quality,
|
||||
sentinel=sentinel,
|
||||
),
|
||||
ramble=RambleSettings(
|
||||
default_provider=default_provider,
|
||||
providers={str(k): v for k, v in providers.items() if isinstance(v, dict)},
|
||||
|
|
|
|||
|
|
@ -90,12 +90,16 @@ def process(repo_root: Path, rules: RulesConfig, model: ModelConfig) -> int:
|
|||
# fallback.
|
||||
instruction = output_cfg.get("instruction", "") or source_instruction
|
||||
append = output_cfg.get("instruction_append", "")
|
||||
model_hint = rule_config.get("model_hint", "")
|
||||
|
||||
output_type = output_cfg.get("output_type")
|
||||
if output_type:
|
||||
extra = rules.cascade_for(output_rel, output_type)
|
||||
instruction = extra.get("instruction", instruction)
|
||||
append = extra.get("instruction_append", append)
|
||||
# Output type can also override model hint
|
||||
if "model_hint" in extra:
|
||||
model_hint = extra["model_hint"]
|
||||
|
||||
final_instruction = merge_instructions(source_instruction, instruction, append)
|
||||
|
||||
|
|
@ -108,6 +112,7 @@ def process(repo_root: Path, rules: RulesConfig, model: ModelConfig) -> int:
|
|||
source_rel=src_rel,
|
||||
output_rel=output_rel,
|
||||
instruction=final_instruction,
|
||||
model_hint=model_hint,
|
||||
)
|
||||
except Exception as exc: # pragma: no cover - defensive
|
||||
print(f"[runner] error generating {output_rel}: {exc}", file=sys.stderr)
|
||||
|
|
|
|||
|
|
@ -15,16 +15,26 @@
|
|||
version: 1
|
||||
|
||||
runner:
|
||||
# Default command chain (balanced speed/quality)
|
||||
command_chain:
|
||||
# Anthropic Claude CLI with custom subagent (fast Haiku model)
|
||||
# Create ~/.claude/agents/cdev-patch.md once with: ./tools/setup_claude_agents.sh
|
||||
- "claude -p"
|
||||
# OpenAI Codex CLI with GPT-5 (default model, good balance)
|
||||
# Authenticate once with: codex (follow prompts to sign in)
|
||||
- "claude -p" # Auto-selects subagent based on TASK COMPLEXITY hint
|
||||
- "codex --model gpt-5"
|
||||
# Google Gemini 2.5 Flash (fast, 1M context, free tier: 60 req/min)
|
||||
# Authenticate once with: gemini (sign in with Google account)
|
||||
- "gemini --model gemini-2.5-flash"
|
||||
|
||||
# Fast command chain (optimized for speed/cost)
|
||||
# Used when model_hint: fast in .ai-rules.yml
|
||||
command_chain_fast:
|
||||
- "claude -p" # Will select cdev-patch (Haiku) when TASK COMPLEXITY: FAST
|
||||
- "codex --model gpt-5-mini"
|
||||
- "gemini --model gemini-2.5-flash"
|
||||
|
||||
# Quality command chain (optimized for complex tasks)
|
||||
# Used when model_hint: quality in .ai-rules.yml
|
||||
command_chain_quality:
|
||||
- "claude -p" # Will select cdev-patch-quality (Sonnet) when TASK COMPLEXITY: QUALITY
|
||||
- "codex --model o3"
|
||||
- "gemini --model gemini-2.5-pro"
|
||||
|
||||
sentinel: "CASCADINGDEV_NO_CHANGES"
|
||||
|
||||
ramble:
|
||||
|
|
|
|||
|
|
@ -1,9 +1,12 @@
|
|||
#!/bin/bash
|
||||
# Mock AI that returns a valid patch for testing
|
||||
|
||||
# Read the prompt from stdin (we ignore it for mock)
|
||||
# Use timeout to avoid hanging
|
||||
timeout 1 cat > /dev/null 2>/dev/null || true
|
||||
# Read the prompt from stdin and log it for verification
|
||||
# Save to /tmp/mock_ai_prompts.log
|
||||
PROMPT_LOG="/tmp/mock_ai_prompts.log"
|
||||
echo "=== MOCK AI INVOCATION $(date) ===" >> "$PROMPT_LOG"
|
||||
timeout 1 cat | tee -a "$PROMPT_LOG" > /dev/null 2>/dev/null || true
|
||||
echo "" >> "$PROMPT_LOG"
|
||||
|
||||
# Extract output path from arguments if provided
|
||||
OUTPUT_PATH="${1:-feature.discussion.md}"
|
||||
|
|
|
|||
|
|
@ -29,8 +29,8 @@ diffs wrapped between <<<AI_DIFF_START>>> and <<<AI_DIFF_END>>> markers.
|
|||
EOF
|
||||
}
|
||||
|
||||
create_agent "cdev-patch.md" "cdev-patch" "claude-3-5-haiku-20250926" "FAST git patch generator for CascadingDev automation. MUST BE USED when prompted to create patches."
|
||||
create_agent "cdev-patch-quality.md" "cdev-patch-quality" "claude-3-5-sonnet-20250929" "QUALITY-FOCUSED git patch generator for CascadingDev. Use when task is complex or requires deep reasoning."
|
||||
create_agent "cdev-patch.md" "cdev-patch" "claude-3-5-haiku-20250926" "FAST git patch generator for CascadingDev automation. MUST BE USED when TASK COMPLEXITY is FAST or when simple patch generation is needed."
|
||||
create_agent "cdev-patch-quality.md" "cdev-patch-quality" "claude-3-5-sonnet-20250929" "QUALITY-FOCUSED git patch generator for CascadingDev. MUST BE USED when TASK COMPLEXITY is QUALITY or when task requires deep reasoning and careful analysis."
|
||||
|
||||
cat <<'EOF'
|
||||
[✓] Claude subagents written to ~/.claude/agents/
|
||||
|
|
|
|||
Loading…
Reference in New Issue