diff --git a/assets/hooks/pre-commit b/assets/hooks/pre-commit index 8715a75..09d21eb 100755 --- a/assets/hooks/pre-commit +++ b/assets/hooks/pre-commit @@ -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 diff --git a/assets/templates/design.discussion.md b/assets/templates/design.discussion.md new file mode 100644 index 0000000..d0ac66e --- /dev/null +++ b/assets/templates/design.discussion.md @@ -0,0 +1,24 @@ + + +## 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. diff --git a/assets/templates/rules/features.ai-rules.yml b/assets/templates/rules/features.ai-rules.yml index befe2f8..47cb383 100644 --- a/assets/templates/rules/features.ai-rules.yml +++ b/assets/templates/rules/features.ai-rules.yml @@ -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: + created: + 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). diff --git a/automation/ai_config.py b/automation/ai_config.py index d153f06..457eaa4 100644 --- a/automation/ai_config.py +++ b/automation/ai_config.py @@ -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)}, diff --git a/automation/runner.py b/automation/runner.py index 89a831c..17ad7b4 100644 --- a/automation/runner.py +++ b/automation/runner.py @@ -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) diff --git a/config/ai.yml b/config/ai.yml index 920d9f6..0676df5 100644 --- a/config/ai.yml +++ b/config/ai.yml @@ -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: diff --git a/tools/mock_ai.sh b/tools/mock_ai.sh index 59a8770..9d4c96b 100755 --- a/tools/mock_ai.sh +++ b/tools/mock_ai.sh @@ -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}" diff --git a/tools/setup_claude_agents.sh b/tools/setup_claude_agents.sh index 2b6eba2..21e42b8 100755 --- a/tools/setup_claude_agents.sh +++ b/tools/setup_claude_agents.sh @@ -29,8 +29,8 @@ diffs wrapped between <<>> and <<>> 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/