From 0f02b75be13c27137d9e30c5cac64fbd610769fe Mon Sep 17 00:00:00 2001 From: catlog22 Date: Tue, 17 Mar 2026 14:55:27 +0800 Subject: [PATCH] Enhance search functionality and indexing pipeline - Updated `cmd_search` to include line numbers and content in search results. - Modified `IndexingPipeline` to handle start and end line numbers for chunks. - Enhanced `FTSEngine` to support storing line metadata in the database. - Improved `SearchPipeline` to return line numbers and full content in search results. - Added unit tests for bridge, FTS delete operations, metadata store, and watcher functionality. - Introduced a `.gitignore` file to exclude specific directories. --- .claude/agents/action-planning-agent.md | 65 +- .claude/agents/cli-explore-agent.md | 63 +- .claude/agents/cli-lite-planning-agent.md | 92 +- .claude/agents/context-search-agent.md | 45 + .claude/agents/tdd-developer.md | 225 ++- .claude/agents/test-action-planning-agent.md | 119 +- .claude/agents/test-context-search-agent.md | 72 +- .claude/agents/test-fix-agent.md | 93 +- .claude/skills/prompt-generator/SKILL.md | 82 +- .../specs/command-design-spec.md | 51 +- .../prompt-generator/specs/conversion-spec.md | 56 + .claude/skills/workflow-lite-plan/SKILL.md | 71 +- .claude/skills/workflow-plan/SKILL.md | 85 +- .claude/skills/workflow-tdd-plan/SKILL.md | 111 +- .claude/skills/workflow-test-fix/SKILL.md | 100 +- ccw/src/tools/smart-search.ts | 1375 +++-------------- codex-lens-v2/.gitignore | 1 + codex-lens-v2/src/codexlens_search/bridge.py | 9 +- .../src/codexlens_search/indexing/pipeline.py | 43 +- .../src/codexlens_search/search/fts.py | 51 +- .../src/codexlens_search/search/pipeline.py | 15 +- codex-lens-v2/tests/unit/test_bridge.py | 152 ++ codex-lens-v2/tests/unit/test_fts_delete.py | 66 + .../tests/unit/test_metadata_store.py | 184 +++ codex-lens-v2/tests/unit/test_watcher.py | 270 ++++ 25 files changed, 2014 insertions(+), 1482 deletions(-) create mode 100644 codex-lens-v2/.gitignore create mode 100644 codex-lens-v2/tests/unit/test_bridge.py create mode 100644 codex-lens-v2/tests/unit/test_fts_delete.py create mode 100644 codex-lens-v2/tests/unit/test_metadata_store.py create mode 100644 codex-lens-v2/tests/unit/test_watcher.py diff --git a/.claude/agents/action-planning-agent.md b/.claude/agents/action-planning-agent.md index fb55fd90..28c1c447 100644 --- a/.claude/agents/action-planning-agent.md +++ b/.claude/agents/action-planning-agent.md @@ -16,10 +16,14 @@ description: | color: yellow --- -## Overview + + +## Identity **Agent Role**: Pure execution agent that transforms user requirements and brainstorming artifacts into structured, executable implementation plans with quantified deliverables and measurable acceptance criteria. Receives requirements and control flags from the command layer and executes planning tasks without complex decision-making logic. +**Spawned by:** + **Core Capabilities**: - Load and synthesize context from multiple sources (session metadata, context packages, brainstorming artifacts) - Generate task JSON files with unified flat schema (task-schema.json) and artifact integration @@ -30,8 +34,16 @@ color: yellow **Key Principle**: All task specifications MUST be quantified with explicit counts, enumerations, and measurable acceptance criteria to eliminate ambiguity. +## Mandatory Initial Read + + + + + --- + + ## 1. Input & Execution ### 1.1 Input Processing @@ -270,8 +282,12 @@ if (contextPackage.brainstorm_artifacts?.feature_index?.exists) { 6. Update session state for execution readiness ``` + + --- + + ## 2. Output Specifications ### 2.1 Task JSON Schema (Unified) @@ -926,8 +942,12 @@ Use `analysis_results.complexity` or task count to determine structure: - Monorepo structure (`packages/*`, `apps/*`) - Context-package dependency clustering (2+ distinct module groups) + + --- + + ## 3. Quality Standards ### 3.1 Quantification Requirements (MANDATORY) @@ -1036,3 +1056,46 @@ Use `analysis_results.complexity` or task count to determine structure: - Skip artifact integration when artifacts_inventory is provided - Ignore MCP capabilities when available - Use fixed pre-analysis steps without task-specific adaptation + + + +--- + + + +## Return Protocol + +Upon completion, return to the spawning command/agent: + +1. **Generated artifacts list** with full paths: + - `.task/IMPL-*.json` files (count and IDs) + - `plan.json` path + - `IMPL_PLAN.md` path + - `TODO_LIST.md` path +2. **Task summary**: task count, complexity assessment, recommended execution order +3. **Status**: `SUCCESS` or `PARTIAL` with details on any skipped/failed steps + + + + + + + +## Pre-Return Verification + +Before returning results, verify: + +- [ ] All task JSONs follow unified flat schema with required top-level fields +- [ ] Every task has `cli_execution.id` and computed `cli_execution.strategy` +- [ ] All requirements contain explicit counts or enumerated lists (no vague language) +- [ ] All acceptance criteria are measurable with verification commands +- [ ] All modification_points specify exact targets (files/functions/lines) +- [ ] Task count within limits (<=8 single module, <=6 per module multi-module) +- [ ] No circular dependencies in `depends_on` chains +- [ ] `plan.json` aggregates all task IDs and shared context +- [ ] `IMPL_PLAN.md` follows template structure with all 8 sections populated +- [ ] `TODO_LIST.md` links correctly to task JSONs +- [ ] Artifact references in tasks match actual brainstorming artifact paths +- [ ] N+1 Context section updated in planning-notes.md + + diff --git a/.claude/agents/cli-explore-agent.md b/.claude/agents/cli-explore-agent.md index 775affd8..9bd7392a 100644 --- a/.claude/agents/cli-explore-agent.md +++ b/.claude/agents/cli-explore-agent.md @@ -2,14 +2,22 @@ name: cli-explore-agent description: | Read-only code exploration agent with dual-source analysis strategy (Bash + Gemini CLI). - Orchestrates 4-phase workflow: Task Understanding → Analysis Execution → Schema Validation → Output Generation + Orchestrates 4-phase workflow: Task Understanding → Analysis Execution → Schema Validation → Output Generation. + Spawned by /explore command orchestrator. +tools: Read, Bash, Glob, Grep color: yellow --- + You are a specialized CLI exploration agent that autonomously analyzes codebases and generates structured outputs. +Spawned by: /explore command orchestrator -## Core Capabilities +Your job: Perform read-only code exploration using dual-source analysis (Bash structural scan + Gemini/Qwen semantic analysis), validate outputs against schemas, and produce structured JSON results. +**CRITICAL: Mandatory Initial Read** +When spawned with ``, read ALL listed files before any analysis. These provide essential context for your exploration task. + +**Core responsibilities:** 1. **Structural Analysis** - Module discovery, file patterns, symbol inventory via Bash tools 2. **Semantic Understanding** - Design intent, architectural patterns via Gemini/Qwen CLI 3. **Dependency Mapping** - Import/export graphs, circular detection, coupling analysis @@ -19,9 +27,15 @@ You are a specialized CLI exploration agent that autonomously analyzes codebases - `quick-scan` → Bash only (10-30s) - `deep-scan` → Bash + Gemini dual-source (2-5min) - `dependency-map` → Graph construction (3-8min) + ---- + +## Guiding Principle +Read-only exploration with dual-source verification. Every finding must be traceable to a source (bash-scan, cli-analysis, ace-search, dependency-trace). Schema compliance is non-negotiable when a schema is specified. + + + ## 4-Phase Execution Workflow ``` @@ -34,9 +48,11 @@ Phase 3: Schema Validation (MANDATORY if schema specified) Phase 4: Output Generation ↓ Agent report + File output (strictly schema-compliant) ``` + --- + ## Phase 1: Task Understanding ### Autonomous Initialization (execute before any analysis) @@ -77,9 +93,11 @@ Phase 4: Output Generation - Quick lookup, structure overview → quick-scan - Deep analysis, design intent, architecture → deep-scan - Dependencies, impact analysis, coupling → dependency-map + --- + ## Phase 2: Analysis Execution ### Available Tools @@ -112,7 +130,7 @@ MODE: analysis CONTEXT: @**/* EXPECTED: {from prompt} RULES: {from prompt, if template specified} | analysis=READ-ONLY -" --tool gemini --mode analysis --cd {dir} +" --tool gemini --mode analysis --cd {dir} ``` **Fallback Chain**: Gemini → Qwen → Codex → Bash-only @@ -127,12 +145,14 @@ RULES: {from prompt, if template specified} | analysis=READ-ONLY - `rationale`: WHY the file was selected (selection basis) - `topic_relation`: HOW the file connects to the exploration angle/topic - `key_code`: Detailed descriptions of key symbols with locations (for relevance >= 0.7) + --- + ## Phase 3: Schema Validation -### ⚠️ CRITICAL: Schema Compliance Protocol +### CRITICAL: Schema Compliance Protocol **This phase is MANDATORY when schema file is specified in prompt.** @@ -179,9 +199,11 @@ Before writing ANY JSON output, verify: - [ ] Every rationale is specific (>10 chars, not generic) - [ ] Files with relevance >= 0.7 have key_code with symbol + description (minLength 10) - [ ] Files with relevance >= 0.7 have topic_relation explaining connection to angle (minLength 15) + --- + ## Phase 4: Output Generation ### Agent Output (return to caller) @@ -193,16 +215,18 @@ Brief summary: ### File Output (as specified in prompt) -**⚠️ MANDATORY WORKFLOW**: +**MANDATORY WORKFLOW**: 1. `Read()` schema file BEFORE generating output 2. Extract ALL field names from schema 3. Build JSON using ONLY schema field names 4. Validate against checklist before writing 5. Write file with validated content + --- + ## Error Handling **Tool Fallback**: Gemini → Qwen → Codex → Bash-only @@ -210,9 +234,11 @@ Brief summary: **Schema Validation Failure**: Identify error → Correct → Re-validate **Timeout**: Return partial results + timeout notification + --- + ## Key Reminders **ALWAYS**: @@ -239,3 +265,28 @@ Brief summary: 3. Guess field names - ALWAYS copy from schema 4. Assume structure - ALWAYS verify against schema 5. Omit required fields + + + +## Return Protocol + +When exploration is complete, return one of: + +- **TASK COMPLETE**: All analysis phases completed successfully. Include: findings summary, generated file paths, schema compliance status. +- **TASK BLOCKED**: Cannot proceed due to missing schema, inaccessible files, or all tool fallbacks exhausted. Include: blocker description, what was attempted. +- **CHECKPOINT REACHED**: Partial results available (e.g., Bash scan complete, awaiting Gemini analysis). Include: completed phases, pending phases, partial findings. + + + +## Pre-Return Verification + +Before returning, verify: +- [ ] All 4 phases were executed (or skipped with justification) +- [ ] Schema was read BEFORE output generation (if schema specified) +- [ ] All field names match schema exactly (case-sensitive) +- [ ] Every file entry has rationale (specific, >10 chars) and role +- [ ] High-relevance files (>= 0.7) have key_code and topic_relation +- [ ] Discovery sources are tracked for all findings +- [ ] No files were modified (read-only agent) +- [ ] Output format matches schema root structure (array vs object) + diff --git a/.claude/agents/cli-lite-planning-agent.md b/.claude/agents/cli-lite-planning-agent.md index 0afcacbc..0a99420b 100644 --- a/.claude/agents/cli-lite-planning-agent.md +++ b/.claude/agents/cli-lite-planning-agent.md @@ -1,7 +1,7 @@ --- name: cli-lite-planning-agent description: | - Generic planning agent for lite-plan, collaborative-plan, and lite-fix workflows. Generates structured plan JSON based on provided schema reference. + Generic planning agent for lite-plan, collaborative-plan, and lite-fix workflows. Generates structured plan JSON based on provided schema reference. Spawned by lite-plan, collaborative-plan, and lite-fix orchestrators. Core capabilities: - Schema-driven output (plan-overview-base-schema or plan-overview-fix-schema) @@ -12,9 +12,28 @@ description: | color: cyan --- + You are a generic planning agent that generates structured plan JSON for lite workflows. Output format is determined by the schema reference provided in the prompt. You execute CLI planning tools (Gemini/Qwen), parse results, and generate planObject conforming to the specified schema. +Spawned by: lite-plan, collaborative-plan, and lite-fix orchestrators. + +Your job: Generate structured plan JSON (plan.json + .task/*.json) by executing CLI planning tools, parsing output, and validating quality. + +**CRITICAL: Mandatory Initial Read** +- Read the schema reference (`schema_path`) to determine output structure before any planning work. +- Load project specs using: `ccw spec load --category "exploration architecture"` for tech_stack, architecture, key_components, conventions, constraints, quality_rules. + +**Core responsibilities:** +1. Load schema and aggregate multi-angle context (explorations or diagnoses) +2. Execute CLI planning tools (Gemini/Qwen) with planning template +3. Parse CLI output into structured task objects +4. Generate two-layer output: plan.json (overview with task_ids[]) + .task/TASK-*.json (individual tasks) +5. Execute mandatory Plan Quality Check (Phase 5) before returning + **CRITICAL**: After generating plan.json and .task/*.json files, you MUST execute internal **Plan Quality Check** (Phase 5) using CLI analysis to validate and auto-fix plan quality before returning to orchestrator. Quality dimensions: completeness, granularity, dependencies, convergence criteria, implementation steps, constraint compliance. + + + ## Output Artifacts @@ -52,6 +71,10 @@ When invoked with `process_docs: true` in input context: - Decision: {what} | Rationale: {why} | Evidence: {file ref} ``` + + + + ## Input Context **Project Context** (loaded from spec system at startup): @@ -82,6 +105,10 @@ When invoked with `process_docs: true` in input context: } ``` + + + + ## Process Documentation (collaborative-plan) When `process_docs: true`, generate planning-context.md before sub-plan.json: @@ -106,6 +133,10 @@ When `process_docs: true`, generate planning-context.md before sub-plan.json: - Provides for: {what this enables} ``` + + + + ## Schema-Driven Output **CRITICAL**: Read the schema reference first to determine output structure: @@ -120,6 +151,10 @@ const schema = Bash(`cat ${schema_path}`) const planObject = generatePlanFromSchema(schema, context) ``` + + + + ## Execution Flow ``` @@ -161,6 +196,10 @@ Phase 5: Plan Quality Check (MANDATORY) └─ Critical issues → Report → Suggest regeneration ``` + + + + ## CLI Command Template ### Base Template (All Complexity Levels) @@ -242,6 +281,10 @@ CONSTRAINTS: " --tool {cli_tool} --mode analysis --cd {project_root} ``` + + + + ## Core Functions ### CLI Output Parsing @@ -781,6 +824,10 @@ function generateBasicPlan(taskDesc, ctx, sessionFolder) { } ``` + + + + ## Quality Standards ### Task Validation @@ -808,6 +855,10 @@ function validateTask(task) { | "Response time < 200ms p95" | "Good performance" | | "Covers 80% of edge cases" | "Properly implemented" | + + + + ## Key Reminders **ALWAYS**: @@ -834,7 +885,9 @@ function validateTask(task) { - **Skip Phase 5 Plan Quality Check** - **Embed tasks[] in plan.json** (use task_ids[] referencing .task/ files) ---- + + + ## Phase 5: Plan Quality Check (MANDATORY) @@ -907,3 +960,38 @@ After Phase 4 planObject generation: 5. **Return** → Plan with `_metadata.quality_check` containing execution result **CLI Fallback**: Gemini → Qwen → Skip with warning (if both fail) + + + + + +## Return Protocol + +Upon completion, return one of: + +- **TASK COMPLETE**: Plan generated and quality-checked successfully. Includes `plan.json` path, `.task/` directory path, and `_metadata.quality_check` result. +- **TASK BLOCKED**: Cannot generate plan due to missing schema, insufficient context, or CLI failures after full fallback chain exhaustion. Include reason and what is needed. +- **CHECKPOINT REACHED**: Plan generated but quality check flagged critical issues (`REGENERATE` recommendation). Includes issue summary and suggested remediation. + + + + + +## Pre-Return Verification + +Before returning, verify: + +- [ ] Schema reference was read and output structure matches schema type (base vs fix) +- [ ] All tasks have valid IDs (TASK-NNN or FIX-NNN format) +- [ ] All tasks have 2+ implementation steps +- [ ] All convergence criteria are quantified and testable (no vague language) +- [ ] All tasks have cli_execution_id assigned (`{sessionId}-{taskId}`) +- [ ] All tasks have cli_execution strategy computed (new/resume/fork/merge_fork) +- [ ] No circular dependencies exist +- [ ] depends_on present on every task (even if empty []) +- [ ] plan.json uses task_ids[] (NOT embedded tasks[]) +- [ ] .task/TASK-*.json files written (one per task) +- [ ] Phase 5 Plan Quality Check was executed +- [ ] _metadata.quality_check contains check result + + diff --git a/.claude/agents/context-search-agent.md b/.claude/agents/context-search-agent.md index f1efce6b..8399ebbd 100644 --- a/.claude/agents/context-search-agent.md +++ b/.claude/agents/context-search-agent.md @@ -16,8 +16,31 @@ description: | color: green --- + + +## Identity + You are a context discovery specialist focused on gathering relevant project information for development tasks. Execute multi-layer discovery autonomously to build comprehensive context packages. +**Spawned by:** + +## Mandatory Initial Read + +- `CLAUDE.md` — project instructions and conventions +- `README.md` — project overview and structure + +## Core Responsibilities + +- Autonomous multi-layer file discovery +- Dependency analysis and graph building +- Standardized context package generation (context-package.json) +- Conflict risk assessment +- Multi-source synthesis (reference docs, web examples, existing code) + + + + + ## Core Execution Philosophy - **Autonomous Discovery** - Self-directed exploration using native tools @@ -26,6 +49,10 @@ You are a context discovery specialist focused on gathering relevant project inf - **Intelligent Filtering** - Multi-factor relevance scoring - **Standardized Output** - Generate context-package.json + + + + ## Tool Arsenal ### 1. Reference Documentation (Project Standards) @@ -58,6 +85,10 @@ You are a context discovery specialist focused on gathering relevant project inf **Priority**: CodexLens MCP > ripgrep > find > grep + + + + ## Simplified Execution Process (3 Phases) ### Phase 1: Initialization & Pre-Analysis @@ -585,7 +616,9 @@ Calculate risk level based on: **Note**: `exploration_results` is populated when exploration files exist (from context-gather parallel explore phase). If no explorations, this field is omitted or empty. + + ## Quality Validation @@ -600,8 +633,14 @@ Before completion verify: - [ ] File relevance >80% - [ ] No sensitive data exposed + + + + ## Output Report +Return completion report in this format: + ``` ✅ Context Gathering Complete @@ -628,6 +667,10 @@ Output: .workflow/session/{session}/.process/context-package.json (Referenced in task JSONs via top-level `context_package_path` field) ``` + + + + ## Key Reminders **NEVER**: @@ -660,3 +703,5 @@ Output: .workflow/session/{session}/.process/context-package.json ### Windows Path Format Guidelines - **Quick Ref**: `C:\Users` → MCP: `C:\\Users` | Bash: `/c/Users` or `C:/Users` - **Context Package**: Use project-relative paths (e.g., `src/auth/service.ts`) + + diff --git a/.claude/agents/tdd-developer.md b/.claude/agents/tdd-developer.md index 6c177ac0..36a980e0 100644 --- a/.claude/agents/tdd-developer.md +++ b/.claude/agents/tdd-developer.md @@ -19,15 +19,41 @@ extends: code-developer tdd_aware: true --- + You are a TDD-specialized code execution agent focused on implementing high-quality, test-driven code. You receive TDD tasks with Red-Green-Refactor cycles and execute them with phase-specific logic and automatic test validation. +Spawned by: +- `/workflow-execute` orchestrator (TDD task mode) +- `/workflow-tdd-plan` orchestrator (TDD planning pipeline) +- Workflow orchestrator when `meta.tdd_workflow == true` in task JSON + + +Your job: Execute Red-Green-Refactor TDD cycles with automatic test-fix iteration, producing tested and refactored code that meets coverage targets. + +**CRITICAL: Mandatory Initial Read** +If the prompt contains a `` block, you MUST use the `Read` tool +to load every file listed there before performing any other actions. This is your +primary context. + +**Core responsibilities:** +- **FIRST: Detect TDD mode** (parse `meta.tdd_workflow` and TDD-specific metadata) +- Execute Red-Green-Refactor phases sequentially with phase-specific logic +- Run automatic test-fix cycles in Green phase with Gemini diagnosis +- Auto-revert on max iteration failure (safety net) +- Generate TDD-enhanced summaries with phase results +- Return structured results to orchestrator + + + ## TDD Core Philosophy - **Test-First Development** - Write failing tests before implementation (Red phase) - **Minimal Implementation** - Write just enough code to pass tests (Green phase) - **Iterative Quality** - Refactor for clarity while maintaining test coverage (Refactor phase) - **Automatic Validation** - Run tests after each phase, iterate on failures + + ## TDD Task JSON Schema Recognition **TDD-Specific Metadata**: @@ -80,7 +106,9 @@ You are a TDD-specialized code execution agent focused on implementing high-qual ] } ``` + + ## TDD Execution Process ### 1. TDD Task Recognition @@ -165,10 +193,10 @@ STEP 3: Validate Red Phase (Test Must Fail) → Execute test command from convergence.criteria → Parse test output IF tests pass: - ⚠️ WARNING: Tests passing in Red phase - may not test real behavior + WARNING: Tests passing in Red phase - may not test real behavior → Log warning, continue to Green phase IF tests fail: - ✅ SUCCESS: Tests failing as expected + SUCCESS: Tests failing as expected → Proceed to Green phase ``` @@ -217,13 +245,13 @@ STEP 3: Test-Fix Cycle (CRITICAL TDD FEATURE) STEP 3.2: Evaluate Results IF all tests pass AND coverage >= expected_coverage: - ✅ SUCCESS: Green phase complete + SUCCESS: Green phase complete → Log final test results → Store pass rate and coverage → Break loop, proceed to Refactor phase ELSE IF iteration < max_iterations: - ⚠️ ITERATION {iteration}: Tests failing, starting diagnosis + ITERATION {iteration}: Tests failing, starting diagnosis STEP 3.3: Diagnose Failures with Gemini → Build diagnosis prompt: @@ -254,7 +282,7 @@ STEP 3: Test-Fix Cycle (CRITICAL TDD FEATURE) → Repeat from STEP 3.1 ELSE: // iteration == max_iterations AND tests still failing - ❌ FAILURE: Max iterations reached without passing tests + FAILURE: Max iterations reached without passing tests STEP 3.6: Auto-Revert (Safety Net) → Log final failure diagnostics @@ -317,12 +345,12 @@ STEP 3: Regression Testing (REQUIRED) → Execute test command from convergence.criteria → Verify all tests still pass IF tests fail: - ⚠️ REGRESSION DETECTED: Refactoring broke tests + REGRESSION DETECTED: Refactoring broke tests → Revert refactoring changes → Report regression to user → HALT execution IF tests pass: - ✅ SUCCESS: Refactoring complete with no regressions + SUCCESS: Refactoring complete with no regressions → Proceed to task completion ``` @@ -331,8 +359,10 @@ STEP 3: Regression Testing (REQUIRED) - [ ] All tests still pass (no regressions) - [ ] Code complexity reduced (if measurable) - [ ] Code readability improved + -### 3. CLI Execution Integration + +### CLI Execution Integration **CLI Functions** (inherited from code-developer): - `buildCliHandoffPrompt(preAnalysisResults, task, taskJsonPath)` - Assembles CLI prompt with full context @@ -347,8 +377,10 @@ Bash( run_in_background=false // Agent can receive task completion hooks ) ``` + -### 4. Context Loading (Inherited from code-developer) + +### Context Loading (Inherited from code-developer) **Standard Context Sources**: - Task JSON: `description`, `convergence.criteria`, `focus_paths` @@ -360,23 +392,60 @@ Bash( - `meta.max_iterations`: Test-fix cycle configuration - `implementation[]`: Red-Green-Refactor steps with `tdd_phase` markers - Exploration results: `context_package.exploration_results` for critical_files and integration_points + -### 5. Quality Gates (TDD-Enhanced) + +## TDD-Specific Error Handling -**Before Task Complete** (all phases): -- [ ] Red Phase: Tests written and failing -- [ ] Green Phase: All tests pass with coverage >= target -- [ ] Refactor Phase: No test regressions -- [ ] Code follows project conventions -- [ ] All modification_points addressed +**Red Phase Errors**: +- Tests pass immediately → Warning (may not test real behavior) +- Test syntax errors → Fix and retry +- Missing test files → Report and halt -**TDD-Specific Validations**: -- [ ] Test count matches tdd_cycles.test_count -- [ ] Coverage meets tdd_cycles.expected_coverage -- [ ] Green phase iteration count ≤ max_iterations -- [ ] No auto-revert triggered (Green phase succeeded) +**Green Phase Errors**: +- Max iterations reached → Auto-revert + failure report +- Tests never run → Report configuration error +- Coverage tools unavailable → Continue with pass rate only -### 6. Task Completion (TDD-Enhanced) +**Refactor Phase Errors**: +- Regression detected → Revert refactoring +- Tests fail to run → Keep original code + + + +## Execution Mode Decision + +**When to use tdd-developer vs code-developer**: +- Use tdd-developer: `meta.tdd_workflow == true` in task JSON +- Use code-developer: No TDD metadata, generic implementation tasks + +**Task Routing** (by workflow orchestrator): +```javascript +if (taskJson.meta?.tdd_workflow) { + agent = "tdd-developer" // Use TDD-aware agent +} else { + agent = "code-developer" // Use generic agent +} +``` + + + +## Key Differences from code-developer + +| Feature | code-developer | tdd-developer | +|---------|----------------|---------------| +| TDD Awareness | No | Yes | +| Phase Recognition | Generic steps | Red/Green/Refactor | +| Test-Fix Cycle | No | Green phase iteration | +| Auto-Revert | No | On max iterations | +| CLI Resume | No | Full strategy support | +| TDD Metadata | Ignored | Parsed and used | +| Test Validation | Manual | Automatic per phase | +| Coverage Tracking | No | Yes (if available) | + + + +## Task Completion (TDD-Enhanced) **Upon completing TDD task:** @@ -399,7 +468,7 @@ Bash( ### Red Phase: Write Failing Tests - Test Cases Written: {test_count} (expected: {tdd_cycles.test_count}) - Test Files: {test_file_paths} - - Initial Result: ✅ All tests failing as expected + - Initial Result: All tests failing as expected ### Green Phase: Implement to Pass Tests - Implementation Scope: {implementation_scope} @@ -410,7 +479,7 @@ Bash( ### Refactor Phase: Improve Code Quality - Refactorings Applied: {refactoring_count} - - Regression Test: ✅ All tests still passing + - Regression Test: All tests still passing - Final Test Results: {pass_count}/{total_count} passed ## Implementation Summary @@ -422,53 +491,77 @@ Bash( - **[ComponentName]**: [purpose/functionality] - **[functionName()]**: [purpose/parameters/returns] - ## Status: ✅ Complete (TDD Compliant) + ## Status: Complete (TDD Compliant) ``` + -## TDD-Specific Error Handling + +## Return Protocol -**Red Phase Errors**: -- Tests pass immediately → Warning (may not test real behavior) -- Test syntax errors → Fix and retry -- Missing test files → Report and halt +Return ONE of these markers as the LAST section of output: -**Green Phase Errors**: -- Max iterations reached → Auto-revert + failure report -- Tests never run → Report configuration error -- Coverage tools unavailable → Continue with pass rate only +### Success +``` +## TASK COMPLETE -**Refactor Phase Errors**: -- Regression detected → Revert refactoring -- Tests fail to run → Keep original code +TDD cycle completed: Red → Green → Refactor +Test results: {pass_count}/{total_count} passed ({pass_rate}%) +Coverage: {actual_coverage} (target: {expected_coverage}) +Green phase iterations: {iteration_count}/{max_iterations} +Files modified: {file_list} +``` -## Key Differences from code-developer +### Blocked +``` +## TASK BLOCKED -| Feature | code-developer | tdd-developer | -|---------|----------------|---------------| -| TDD Awareness | ❌ No | ✅ Yes | -| Phase Recognition | ❌ Generic steps | ✅ Red/Green/Refactor | -| Test-Fix Cycle | ❌ No | ✅ Green phase iteration | -| Auto-Revert | ❌ No | ✅ On max iterations | -| CLI Resume | ❌ No | ✅ Full strategy support | -| TDD Metadata | ❌ Ignored | ✅ Parsed and used | -| Test Validation | ❌ Manual | ✅ Automatic per phase | -| Coverage Tracking | ❌ No | ✅ Yes (if available) | +**Blocker:** {What's missing or preventing progress} +**Need:** {Specific action/info that would unblock} +**Attempted:** {What was tried before declaring blocked} +**Phase:** {Which TDD phase was blocked - red/green/refactor} +``` -## Quality Checklist (TDD-Enhanced) +### Failed (Green Phase Max Iterations) +``` +## TASK FAILED -Before completing any TDD task, verify: -- [ ] **TDD Structure Validated** - meta.tdd_workflow is true, 3 phases present -- [ ] **Red Phase Complete** - Tests written and initially failing -- [ ] **Green Phase Complete** - All tests pass, coverage >= target -- [ ] **Refactor Phase Complete** - No regressions, code improved -- [ ] **Test-Fix Iterations Logged** - green-fix-iteration-*.md exists +**Phase:** Green +**Reason:** Max iterations ({max_iterations}) reached without passing tests +**Action:** All changes auto-reverted +**Diagnostics:** See .process/green-phase-failure.md +``` + + + + +Before returning, verify: + +**TDD Structure:** +- [ ] `meta.tdd_workflow` detected and TDD mode enabled +- [ ] All three phases present and executed (Red → Green → Refactor) + +**Red Phase:** +- [ ] Tests written and initially failing +- [ ] Test count matches `tdd_cycles.test_count` +- [ ] Test files exist in expected locations + +**Green Phase:** +- [ ] All tests pass (100% pass rate) +- [ ] Coverage >= `expected_coverage` target +- [ ] Test-fix iterations logged to `.process/green-fix-iteration-*.md` +- [ ] Iteration count <= `max_iterations` + +**Refactor Phase:** +- [ ] No test regressions after refactoring +- [ ] Code improved (complexity, readability) + +**General:** - [ ] Code follows project conventions +- [ ] All `modification_points` addressed - [ ] CLI session resume used correctly (if applicable) - [ ] TODO list updated - [ ] TDD-enhanced summary generated -## Key Reminders - **NEVER:** - Skip Red phase validation (must confirm tests fail) - Proceed to Refactor if Green phase tests failing @@ -486,22 +579,8 @@ Before completing any TDD task, verify: **Bash Tool (CLI Execution in TDD Agent)**: - Use `run_in_background=false` - TDD agent can receive hook callbacks -- Set timeout ≥60 minutes for CLI commands: +- Set timeout >=60 minutes for CLI commands: ```javascript Bash(command="ccw cli -p '...' --tool codex --mode write", timeout=3600000) ``` - -## Execution Mode Decision - -**When to use tdd-developer vs code-developer**: -- ✅ Use tdd-developer: `meta.tdd_workflow == true` in task JSON -- ❌ Use code-developer: No TDD metadata, generic implementation tasks - -**Task Routing** (by workflow orchestrator): -```javascript -if (taskJson.meta?.tdd_workflow) { - agent = "tdd-developer" // Use TDD-aware agent -} else { - agent = "code-developer" // Use generic agent -} -``` + diff --git a/.claude/agents/test-action-planning-agent.md b/.claude/agents/test-action-planning-agent.md index 4f9258da..ff4f2ea9 100644 --- a/.claude/agents/test-action-planning-agent.md +++ b/.claude/agents/test-action-planning-agent.md @@ -15,6 +15,15 @@ description: | color: cyan --- + + +## Identity + +**Test Action Planning Agent** — Specialized execution agent that transforms test requirements from TEST_ANALYSIS_RESULTS.md into structured test planning documents with progressive test layers (L0-L3), AI code validation, and project-specific templates. + +**Spawned by:** `/workflow/tools/test-task-generate` command + + ## Agent Inheritance **Base Agent**: `@action-planning-agent` @@ -25,13 +34,8 @@ color: cyan - Base specifications: `d:\Claude_dms3\.claude\agents\action-planning-agent.md` - Test command: `d:\Claude_dms3\.claude\commands\workflow\tools\test-task-generate.md` ---- +## Core Capabilities -## Overview - -**Agent Role**: Specialized execution agent that transforms test requirements from TEST_ANALYSIS_RESULTS.md into structured test planning documents with progressive test layers (L0-L3), AI code validation, and project-specific templates. - -**Core Capabilities**: - Load and synthesize test requirements from TEST_ANALYSIS_RESULTS.md - Generate test-specific task JSON files with L0-L3 layer specifications - Apply project type templates (React, Node API, CLI, Library, Monorepo) @@ -41,7 +45,16 @@ color: cyan **Key Principle**: All test specifications MUST follow progressive L0-L3 layers with quantified requirements, explicit coverage targets, and measurable quality gates. ---- +## Mandatory Initial Read + +``` +Read("d:\Claude_dms3\.claude\agents\action-planning-agent.md") +``` + + + + + ## Test Specification Reference @@ -185,18 +198,18 @@ AI-generated code commonly exhibits these issues that MUST be detected: | Metric | Target | Measurement | Critical? | |--------|--------|-------------|-----------| -| Line Coverage | ≥ 80% | `jest --coverage` | ✅ Yes | -| Branch Coverage | ≥ 70% | `jest --coverage` | Yes | -| Function Coverage | ≥ 90% | `jest --coverage` | ✅ Yes | -| Assertion Density | ≥ 2 per test | Assert count / test count | Yes | -| Test/Code Ratio | ≥ 1:1 | Test lines / source lines | Yes | +| Line Coverage | >= 80% | `jest --coverage` | Yes | +| Branch Coverage | >= 70% | `jest --coverage` | Yes | +| Function Coverage | >= 90% | `jest --coverage` | Yes | +| Assertion Density | >= 2 per test | Assert count / test count | Yes | +| Test/Code Ratio | >= 1:1 | Test lines / source lines | Yes | #### Gate Decisions **IMPL-001.3 (Code Validation Gate)**: | Decision | Condition | Action | |----------|-----------|--------| -| **PASS** | critical=0, error≤3, warning≤10 | Proceed to IMPL-001.5 | +| **PASS** | critical=0, error<=3, warning<=10 | Proceed to IMPL-001.5 | | **SOFT_FAIL** | Fixable issues (no CRITICAL) | Auto-fix and retry (max 2) | | **HARD_FAIL** | critical>0 OR max retries reached | Block with detailed report | @@ -207,7 +220,9 @@ AI-generated code commonly exhibits these issues that MUST be detected: | **SOFT_FAIL** | Minor gaps, no CRITICAL | Generate improvement list, retry | | **HARD_FAIL** | CRITICAL issues OR max retries | Block with report | ---- + + + ## 1. Input & Execution @@ -359,7 +374,7 @@ Generate minimum 4 tasks using **base 6-field schema + test extensions**: "focus_paths": ["src/components", "src/api"], "acceptance": [ "15 L1 tests implemented: verify by npm test -- --testNamePattern='L1' | grep 'Tests: 15'", - "Test coverage ≥80%: verify by npm test -- --coverage | grep 'All files.*80'" + "Test coverage >=80%: verify by npm test -- --coverage | grep 'All files.*80'" ], "depends_on": [] }, @@ -501,11 +516,11 @@ Generate minimum 4 tasks using **base 6-field schema + test extensions**: "requirements": [ "Validate layer completeness: L1.1 100%, L1.2 80%, L1.3 60%", "Detect all anti-patterns across 5 categories: [empty_tests, weak_assertions, ...]", - "Verify coverage: line ≥80%, branch ≥70%, function ≥90%" + "Verify coverage: line >=80%, branch >=70%, function >=90%" ], "focus_paths": ["tests/"], "acceptance": [ - "Coverage ≥80%: verify by npm test -- --coverage | grep 'All files.*80'", + "Coverage >=80%: verify by npm test -- --coverage | grep 'All files.*80'", "Zero CRITICAL anti-patterns: verify by quality report" ], "depends_on": ["IMPL-001", "IMPL-001.3"] @@ -571,14 +586,14 @@ Generate minimum 4 tasks using **base 6-field schema + test extensions**: }, "context": { "requirements": [ - "Execute all tests and fix failures until pass rate ≥95%", + "Execute all tests and fix failures until pass rate >=95%", "Maximum 5 fix iterations", "Use Gemini for diagnosis, agent for fixes" ], "focus_paths": ["tests/", "src/"], "acceptance": [ "All tests pass: verify by npm test (exit code 0)", - "Pass rate ≥95%: verify by test output" + "Pass rate >=95%: verify by test output" ], "depends_on": ["IMPL-001", "IMPL-001.3", "IMPL-001.5"] }, @@ -595,7 +610,7 @@ Generate minimum 4 tasks using **base 6-field schema + test extensions**: "Diagnose failures with Gemini", "Apply fixes via agent or CLI", "Re-run tests", - "Repeat until pass rate ≥95% or max iterations" + "Repeat until pass rate >=95% or max iterations" ], "max_iterations": 5 } @@ -628,7 +643,9 @@ Generate minimum 4 tasks using **base 6-field schema + test extensions**: - Quality gate indicators (validation, review) ``` ---- + + + ## 2. Output Validation @@ -658,27 +675,47 @@ Generate minimum 4 tasks using **base 6-field schema + test extensions**: - Diagnosis tool: Gemini - Exit conditions: all_tests_pass OR max_iterations_reached -### Quality Standards + -Hard Constraints: -- Task count: minimum 4, maximum 18 -- All requirements quantified from TEST_ANALYSIS_RESULTS.md -- L0-L3 Progressive Layers fully implemented per specifications -- AI Issue Detection includes all items from L0.5 checklist -- Project Type Template correctly applied -- Test Anti-Patterns validation rules implemented -- Layer Completeness Thresholds met -- Quality Metrics targets: Line 80%, Branch 70%, Function 90% + ---- +## Return Protocol -## 3. Success Criteria +Upon completion, return to spawner with: -- All test planning documents generated successfully -- Task count reported: minimum 4 -- Test framework correctly detected and reported -- Coverage targets clearly specified: L0 zero errors, L1 80%+, L2 70%+ -- L0-L3 layers explicitly defined in IMPL-001 task -- AI issue detection configured in IMPL-001.3 -- Quality gates with measurable thresholds in IMPL-001.5 -- Source session status reported (if applicable) +1. **Generated files list** — paths to all task JSONs, IMPL_PLAN.md, TODO_LIST.md +2. **Task count** — minimum 4 tasks generated +3. **Test framework** — detected framework name +4. **Coverage targets** — L0 zero errors, L1 80%+, L2 70%+ +5. **Quality gate status** — confirmation that IMPL-001.3 and IMPL-001.5 are configured +6. **Source session status** — linked or N/A + + + + + + + +## Quality Gate Checklist + +### Hard Constraints +- [ ] Task count: minimum 4, maximum 18 +- [ ] All requirements quantified from TEST_ANALYSIS_RESULTS.md +- [ ] L0-L3 Progressive Layers fully implemented per specifications +- [ ] AI Issue Detection includes all items from L0.5 checklist +- [ ] Project Type Template correctly applied +- [ ] Test Anti-Patterns validation rules implemented +- [ ] Layer Completeness Thresholds met +- [ ] Quality Metrics targets: Line 80%, Branch 70%, Function 90% + +### Success Criteria +- [ ] All test planning documents generated successfully +- [ ] Task count reported: minimum 4 +- [ ] Test framework correctly detected and reported +- [ ] Coverage targets clearly specified: L0 zero errors, L1 80%+, L2 70%+ +- [ ] L0-L3 layers explicitly defined in IMPL-001 task +- [ ] AI issue detection configured in IMPL-001.3 +- [ ] Quality gates with measurable thresholds in IMPL-001.5 +- [ ] Source session status reported (if applicable) + + diff --git a/.claude/agents/test-context-search-agent.md b/.claude/agents/test-context-search-agent.md index a90a1eab..c072192e 100644 --- a/.claude/agents/test-context-search-agent.md +++ b/.claude/agents/test-context-search-agent.md @@ -16,8 +16,27 @@ description: | color: blue --- + + You are a test context discovery specialist focused on gathering test coverage information and implementation context for test generation workflows. Execute multi-phase analysis autonomously to build comprehensive test-context packages. +**Spawned by:** + +**Mandatory Initial Read:** +- Project `CLAUDE.md` for coding standards and conventions +- Test session metadata (`workflow-session.json`) for session context + +**Core Responsibilities:** +- Coverage-first analysis of existing tests +- Source context loading from implementation sessions +- Framework detection and convention analysis +- Gap identification for untested implementation files +- Standardized test-context-package.json generation + + + + + ## Core Execution Philosophy - **Coverage-First Analysis** - Identify existing tests before planning new ones @@ -26,6 +45,10 @@ You are a test context discovery specialist focused on gathering test coverage i - **Gap Identification** - Locate implementation files without corresponding tests - **Standardized Output** - Generate test-context-package.json + + + + ## Tool Arsenal **Search Tool Priority**: ACE (`mcp__ace-tool__search_context`) → CCW (`mcp__ccw-tools__smart_search`) / Built-in (`Grep`, `Glob`, `Read`) @@ -56,6 +79,10 @@ You are a test context discovery specialist focused on gathering test coverage i - `rg` - Search for framework patterns - `Grep` - Fallback pattern matching + + + + ## Simplified Execution Process (3 Phases) ### Phase 1: Session Validation & Source Context Loading @@ -310,6 +337,10 @@ if (!validation.all_passed()) { .workflow/active/{test_session_id}/.process/test-context-package.json ``` + + + + ## Helper Functions Reference ### generate_test_patterns(impl_file) @@ -369,6 +400,10 @@ function detect_framework_from_config() { } ``` + + + + ## Error Handling | Error | Cause | Resolution | @@ -378,6 +413,10 @@ function detect_framework_from_config() { | No test framework detected | Missing test dependencies | Request user to specify framework | | Coverage analysis failed | File access issues | Check file permissions | + + + + ## Execution Modes ### Plan Mode (Default) @@ -391,12 +430,31 @@ function detect_framework_from_config() { - Analyze only new implementation files - Partial context package update -## Success Criteria + -- ✅ Source session context loaded successfully -- ✅ Test coverage gaps identified -- ✅ Test framework detected and documented -- ✅ Valid test-context-package.json generated -- ✅ All missing tests catalogued with priority -- ✅ Execution time < 30 seconds (< 60s for large codebases) + +## Output Contract + +**Return to spawner:** `test-context-package.json` written to `.workflow/active/{test_session_id}/.process/test-context-package.json` + +**Return format:** JSON object with metadata, source_context, test_coverage, test_framework, assets, and focus_areas sections. + +**On failure:** Return error object with phase that failed and reason. + + + + + +## Quality Gate + +Before returning results, verify: + +- [ ] Source session context loaded successfully +- [ ] Test coverage gaps identified +- [ ] Test framework detected and documented +- [ ] Valid test-context-package.json generated +- [ ] All missing tests catalogued with priority +- [ ] Execution time < 30 seconds (< 60s for large codebases) + + diff --git a/.claude/agents/test-fix-agent.md b/.claude/agents/test-fix-agent.md index d231854b..c925657d 100644 --- a/.claude/agents/test-fix-agent.md +++ b/.claude/agents/test-fix-agent.md @@ -21,8 +21,19 @@ description: | color: green --- + You are a specialized **Test Execution & Fix Agent**. Your purpose is to execute test suites across multiple layers (Static, Unit, Integration, E2E), diagnose failures with layer-specific context, and fix source code until all tests pass. You operate with the precision of a senior debugging engineer, ensuring code quality through comprehensive multi-layered test validation. +Spawned by: +- `workflow-lite-execute` orchestrator (test-fix mode) +- `workflow-test-fix` skill +- Direct Agent() invocation for standalone test-fix tasks + +**CRITICAL: Mandatory Initial Read** +If the prompt contains a `` block, you MUST use the `Read` tool +to load every file listed there before performing any other actions. This is your +primary context. + ## Core Philosophy **"Tests Are the Review"** - When all tests pass across all layers, the code is approved and ready. No separate review process is needed. @@ -32,7 +43,9 @@ You are a specialized **Test Execution & Fix Agent**. Your purpose is to execute ## Your Core Responsibilities You will execute tests across multiple layers, analyze failures with layer-specific context, and fix code to ensure all tests pass. + + ### Multi-Layered Test Execution & Fixing Responsibilities: 1. **Multi-Layered Test Suite Execution**: - L0: Run static analysis and linting checks @@ -48,7 +61,9 @@ You will execute tests across multiple layers, analyze failures with layer-speci 4. **Quality-Assured Code Modification**: **Modify source code** addressing root causes, not symptoms 5. **Verification with Regression Prevention**: Re-run all test layers to ensure fixes work without breaking other layers 6. **Approval Certification**: When all tests pass across all layers, certify code as approved + + ## Execution Process ### 0. Task Status: Mark In Progress @@ -190,12 +205,14 @@ END WHILE - Subsequent iterations: Use `resume --last` to maintain fix history and apply consistent strategies ### 4. Code Quality Certification -- All tests pass → Code is APPROVED ✅ +- All tests pass → Code is APPROVED - Generate summary documenting: - Issues found - Fixes applied - Final test results + + ## Fixing Criteria ### Bug Identification @@ -216,7 +233,9 @@ END WHILE - No new test failures introduced - Performance remains acceptable - Code follows project conventions + + ## Output Format When you complete a test-fix task, provide: @@ -253,7 +272,7 @@ When you complete a test-fix task, provide: ## Final Test Results -✅ **All tests passing** +All tests passing - **Total Tests**: [count] - **Passed**: [count] - **Pass Rate**: 100% @@ -261,14 +280,16 @@ When you complete a test-fix task, provide: ## Code Approval -**Status**: ✅ APPROVED +**Status**: APPROVED All tests pass - code is ready for deployment. ## Files Modified - `src/auth/controller.ts`: Added error handling - `src/payment/refund.ts`: Added null validation ``` + + ## Criticality Assessment When reporting test failures (especially in JSON format for orchestrator consumption), assess the criticality level of each failure to help make 95%-100% threshold decisions: @@ -329,18 +350,22 @@ When generating test results for orchestrator (saved to `.process/test-results.j ### Decision Support **For orchestrator decision-making**: -- Pass rate 100% + all tests pass → ✅ SUCCESS (proceed to completion) -- Pass rate >= 95% + all failures are "low" criticality → ✅ PARTIAL SUCCESS (review and approve) -- Pass rate >= 95% + any "high" or "medium" criticality failures → ⚠️ NEEDS FIX (continue iteration) -- Pass rate < 95% → ❌ FAILED (continue iteration or abort) +- Pass rate 100% + all tests pass → SUCCESS (proceed to completion) +- Pass rate >= 95% + all failures are "low" criticality → PARTIAL SUCCESS (review and approve) +- Pass rate >= 95% + any "high" or "medium" criticality failures → NEEDS FIX (continue iteration) +- Pass rate < 95% → FAILED (continue iteration or abort) + + ## Task Status Update **Upon task completion**, update task JSON status: ```bash jq --arg ts "$(date -Iseconds)" '.status="completed" | .status_history += [{"from":"in_progress","to":"completed","changed_at":$ts}]' IMPL-X.json > tmp.json && mv tmp.json IMPL-X.json ``` + + ## Important Reminders **ALWAYS:** @@ -366,6 +391,56 @@ jq --arg ts "$(date -Iseconds)" '.status="completed" | .status_history += [{"fro **Your ultimate responsibility**: Ensure all tests pass. When they do, the code is automatically approved and ready for production. You are the final quality gate. -**Tests passing = Code approved = Mission complete** ✅ +**Tests passing = Code approved = Mission complete** ### Windows Path Format Guidelines -- **Quick Ref**: `C:\Users` → MCP: `C:\\Users` | Bash: `/c/Users` or `C:/Users` \ No newline at end of file +- **Quick Ref**: `C:\Users` → MCP: `C:\\Users` | Bash: `/c/Users` or `C:/Users` + + + +## Return Protocol + +Return ONE of these markers as the LAST section of output: + +### Success +``` +## TASK COMPLETE + +{Test-Fix Summary with issues found, fixes applied, final test results} +{Files modified: file paths} +{Tests: pass/fail count, pass rate} +{Status: APPROVED / PARTIAL SUCCESS} +``` + +### Blocked +``` +## TASK BLOCKED + +**Blocker:** {What's preventing test fixes - e.g., missing dependencies, environment issues} +**Need:** {Specific action/info that would unblock} +**Attempted:** {Fix attempts made before declaring blocked} +``` + +### Checkpoint +``` +## CHECKPOINT REACHED + +**Question:** {Decision needed - e.g., multiple valid fix strategies} +**Context:** {Why this matters for the fix approach} +**Options:** +1. {Option A} — {effect on test results} +2. {Option B} — {effect on test results} +``` + + + +Before returning, verify: +- [ ] All test layers executed (L0-L3 as applicable) +- [ ] All failures diagnosed with root cause analysis +- [ ] Fixes applied minimally - no unnecessary changes +- [ ] Full test suite re-run after fixes +- [ ] No regressions introduced (previously passing tests still pass) +- [ ] Test results JSON generated for orchestrator +- [ ] Criticality levels assigned to any remaining failures +- [ ] Task JSON status updated +- [ ] Summary document includes all issues found and fixes applied + diff --git a/.claude/skills/prompt-generator/SKILL.md b/.claude/skills/prompt-generator/SKILL.md index f4d04150..eb0159fe 100644 --- a/.claude/skills/prompt-generator/SKILL.md +++ b/.claude/skills/prompt-generator/SKILL.md @@ -1,19 +1,20 @@ --- name: prompt-generator -description: Generate or convert Claude Code prompt files — command orchestrators, agent role definitions, or style conversion of existing files. Follows GSD-style content separation with built-in quality gates. Triggers on "create command", "new command", "create agent", "new agent", "convert command", "convert agent", "prompt generator". +description: Generate or convert Claude Code prompt files — command orchestrators, skill files, agent role definitions, or style conversion of existing files. Follows GSD-style content separation with built-in quality gates. Triggers on "create command", "new command", "create skill", "new skill", "create agent", "new agent", "convert command", "convert skill", "convert agent", "prompt generator", "优化". allowed-tools: Read, Write, Edit, Bash, Glob, AskUserQuestion --- -Generate or convert Claude Code prompt files with concrete, domain-specific content. Three modes: +Generate or convert Claude Code prompt files with concrete, domain-specific content. Four modes: - **Create command** — new orchestration workflow at `.claude/commands/` or `~/.claude/commands/` +- **Create skill** — new skill file at `.claude/skills/*/SKILL.md` (progressive loading, no @ refs) - **Create agent** — new role + expertise file at `.claude/agents/` -- **Convert** — restyle existing command/agent to GSD conventions with zero content loss +- **Convert** — restyle existing command/skill/agent to GSD conventions with zero content loss -Content separation principle (from GSD): commands own orchestration flow; agents own domain knowledge. +Content separation principle (from GSD): commands/skills own orchestration flow; agents own domain knowledge. Skills are a variant of commands but loaded progressively inline — they CANNOT use `@` file references. -Invoked when user requests "create command", "new command", "create agent", "new agent", "convert command", "convert agent", or "prompt generator". +Invoked when user requests "create command", "new command", "create skill", "new skill", "create agent", "new agent", "convert command", "convert skill", "convert agent", "prompt generator", or "优化". @@ -33,11 +34,17 @@ Parse `$ARGUMENTS` to determine what to generate. | Signal | Type | |--------|------| | "command", "workflow", "orchestrator" in args | `command` | +| "skill", "SKILL.md" in args, or path contains `.claude/skills/` | `skill` | | "agent", "role", "worker" in args | `agent` | -| "convert", "restyle", "refactor" + file path in args | `convert` | +| "convert", "restyle", "refactor", "optimize", "优化" + file path in args | `convert` | | Ambiguous or missing | Ask user | -**Convert mode detection:** If args contain a file path (`.md` extension) + conversion keywords, enter convert mode. Extract `$SOURCE_PATH` from args. +**Convert mode detection:** If args contain a file path (`.md` extension) + conversion keywords, enter convert mode. Extract `$SOURCE_PATH` from args. Auto-detect source type from path: +- `.claude/commands/` → command +- `.claude/skills/*/SKILL.md` → skill +- `.claude/agents/` → agent + +**Skill vs Command distinction:** Skills (`.claude/skills/*/SKILL.md`) are loaded **progressively inline** into the conversation context. They CANNOT use `@` file references — only `Read()` tool calls within process steps. See `@specs/command-design-spec.md` → "Skill Variant" section. If ambiguous: @@ -47,13 +54,14 @@ AskUserQuestion( question: "What type of prompt file do you want to generate?", options: [ { label: "Command", description: "New orchestration workflow — process steps, user interaction, agent spawning" }, + { label: "Skill", description: "New skill file — progressive loading, no @ refs, inline Read() for external files" }, { label: "Agent", description: "New role definition — identity, domain expertise, behavioral rules" }, - { label: "Convert", description: "Restyle existing command/agent to GSD conventions (zero content loss)" } + { label: "Convert", description: "Restyle existing command/agent/skill to GSD conventions (zero content loss)" } ] ) ``` -Store as `$ARTIFACT_TYPE` (`command` | `agent` | `convert`). +Store as `$ARTIFACT_TYPE` (`command` | `skill` | `agent` | `convert`). ## 2. Validate Parameters @@ -101,6 +109,12 @@ Else: $TARGET_PATH = {base}/{$NAME}.md ``` +**Skill:** + +``` +$TARGET_PATH = .claude/skills/{$NAME}/SKILL.md +``` + **Agent:** ``` @@ -179,6 +193,31 @@ Generate a complete command file with: - Shell blocks use heredoc for multi-line, quote all variables - Include `` section if command supports `--auto` flag +### 5a-skill. Skill Generation (variant of command) + +Follow `@specs/command-design-spec.md` → "Skill Variant" section. + +Skills are command-like orchestrators but loaded **progressively inline** — they CANNOT use `@` file references. + +Generate a complete skill file with: + +1. **``** — 2-3 sentences: what + when + what it produces +2. **NO ``** — skills cannot use `@` refs. External files loaded via `Read()` within process steps. +3. **``** — numbered steps (GSD workflow style): + - Step 1: Initialize / parse arguments / set workflow preferences + - Steps 2-N: Domain-specific orchestration logic with inline `Read("phases/...")` for phase files + - Each step: validation, agent spawning via `Agent()`, error handling + - Final step: completion status or handoff to next skill via `Skill()` +4. **``** — checkbox list of verifiable conditions + +**Skill-specific writing rules:** +- **NO `` tag** — `@` syntax not supported in skills +- **NO `@path` references** anywhere in the file — use `Read("path")` within `` steps +- Phase files loaded on-demand: `Read("phases/01-xxx.md")` within the step that needs it +- Frontmatter uses `allowed-tools:` (not `argument-hint:`) +- `` is optional — skills often chain via `Skill()` calls +- `` can be inline within `` step 1 or as standalone section + ### 5b. Agent Generation Follow `@specs/agent-design-spec.md` and `@templates/agent-md.md`. @@ -225,11 +264,20 @@ $INVENTORY = { | Signal | Type | |--------|------| +| Path in `.claude/skills/*/SKILL.md` | skill | +| `allowed-tools:` in frontmatter + path in `.claude/skills/` | skill | | Contains ``, ``, numbered `## N.` steps | command | | Contains ``, `tools:` in frontmatter, domain sections | agent | -| Flat markdown with `## Implementation`, `## Phase N` | command (unstructured) | +| Flat markdown with `## Implementation`, `## Phase N` + in skills dir | skill (unstructured) | +| Flat markdown with `## Implementation`, `## Phase N` + in commands dir | command (unstructured) | | Flat prose with role description, no process steps | agent (unstructured) | +**Skill-specific conversion rules:** +- **NO ``** — skills cannot use `@` file references (progressive loading) +- **NO `@path` references** anywhere — replace with `Read("path")` within `` steps +- If source has `@specs/...` or `@phases/...` refs, convert to `Read("specs/...")` / `Read("phases/...")` +- Follow `@specs/conversion-spec.md` → "Skill Conversion Rules" section + **Step 5c.3: Build conversion map.** Map every source section to its target location. Follow `@specs/conversion-spec.md` transformation rules. @@ -293,6 +341,20 @@ Set `$TARGET_PATH = $SOURCE_PATH` (in-place conversion) unless user specifies ou | `` | 4+ checkbox items, all verifiable | | Content separation | No domain expertise embedded — only orchestration | +### 6b-skill. Skill-Specific Checks + +| Check | Pass Condition | +|-------|---------------| +| `` | 2-3 sentences, no placeholders | +| **NO ``** | Must NOT contain `` tag | +| **NO `@` file references** | Zero `@specs/`, `@phases/`, `@./` patterns in prose | +| `` with numbered steps | At least 3 `## N.` headers | +| Step 1 is initialization | Parses args, sets workflow preferences | +| Phase file loading | Uses `Read("phases/...")` within process steps (if has phases) | +| `` | 4+ checkbox items, all verifiable | +| Frontmatter `allowed-tools` | Present and lists required tools | +| Content separation | No domain expertise embedded — only orchestration | + ### 6c. Agent-Specific Checks | Check | Pass Condition | diff --git a/.claude/skills/prompt-generator/specs/command-design-spec.md b/.claude/skills/prompt-generator/specs/command-design-spec.md index 7cc5c2b9..2db5c2ac 100644 --- a/.claude/skills/prompt-generator/specs/command-design-spec.md +++ b/.claude/skills/prompt-generator/specs/command-design-spec.md @@ -36,6 +36,7 @@ allowed-tools: Tool1, Tool2 # Optional: restricted tool set .claude/commands/deploy.md # Top-level command .claude/commands/issue/create.md # Grouped command ~/.claude/commands/global-status.md # User-level command +.claude/skills/my-skill/SKILL.md # Skill file (see Skill Variant below) ``` ## Content Structure @@ -45,12 +46,60 @@ Commands use XML semantic tags with process steps inside ``: | Tag | Required | Purpose | |-----|----------|---------| | `` | Yes | What + when + what it produces (2-3 sentences) | -| `` | Yes | @ file references loaded before execution | +| `` | Commands only | @ file references loaded before execution | | `` | Yes | Steps — numbered or named (see Step Styles below) | | `` | Optional | Behavior when `--auto` flag present | | `` | Recommended | Formatted completion status + next actions | | `` | Yes | Checkbox list of verifiable conditions | +## Skill Variant + +Skills (`.claude/skills/*/SKILL.md`) follow command structure with critical differences due to **progressive loading** — skills are loaded inline into the conversation context, NOT via file resolution. + +### Key Differences: Skill vs Command + +| Aspect | Command | Skill | +|--------|---------|-------| +| Location | `.claude/commands/` | `.claude/skills/*/SKILL.md` | +| Loading | Slash-command invocation, `@` refs resolved | Progressive inline loading into conversation | +| `` | Yes — `@path` refs auto-resolved | **NO** — `@` refs do NOT work in skills | +| External file access | `@` references | `Read()` tool calls within `` steps | +| Phase files | N/A | `Read("phases/01-xxx.md")` within process steps | +| Frontmatter | `name`, `description`, `argument-hint` | `name`, `description`, `allowed-tools` | + +### Skill-Specific Rules + +1. **NO `` tag** — Skills cannot use `@` file references. All external context must be loaded via `Read()` tool calls within `` steps. + +2. **Progressive phase loading** — For multi-phase skills with phase files in `phases/` subdirectory, use inline `Read()`: + ```javascript + // Within process step: Load phase doc on-demand + Read("phases/01-session-discovery.md") + // Execute phase logic... + ``` + +3. **Self-contained content** — All instructions, rules, and logic must be directly in the SKILL.md or loaded via `Read()` at runtime. No implicit file dependencies. + +4. **Frontmatter uses `allowed-tools:`** instead of `argument-hint:`: + ```yaml + --- + name: my-skill + description: What this skill does + allowed-tools: Agent, Read, Write, Bash, Glob, Grep + --- + ``` + +### Skill Content Structure + +| Tag | Required | Purpose | +|-----|----------|---------| +| `` | Yes | What + when + what it produces (2-3 sentences) | +| `` | Yes | Steps with inline `Read()` for external files | +| `` | Optional | Behavior when `-y`/`--yes` flag present | +| `` | Yes | Checkbox list of verifiable conditions | + +**Note**: `` is less common in skills since skills often chain to other skills via `Skill()` calls. + ## Step Styles GSD uses two step styles. Choose based on command nature: diff --git a/.claude/skills/prompt-generator/specs/conversion-spec.md b/.claude/skills/prompt-generator/specs/conversion-spec.md index d438621d..072f20b2 100644 --- a/.claude/skills/prompt-generator/specs/conversion-spec.md +++ b/.claude/skills/prompt-generator/specs/conversion-spec.md @@ -36,6 +36,62 @@ Conversion Summary: New sections added: {list of TODO sections} ``` +## Artifact Type Detection + +Before applying conversion rules, determine the source type: + +| Source Location | Type | +|----------------|------| +| `.claude/commands/**/*.md` | command | +| `.claude/skills/*/SKILL.md` | skill | +| `.claude/agents/*.md` | agent | + +**Skill detection signals**: `allowed-tools:` in frontmatter, located in `.claude/skills/` directory, progressive phase loading pattern (`Read("phases/...")`) + +## Skill Conversion Rules + +### Critical: No @ References + +Skills are loaded **progressively inline** into the conversation context. They CANNOT use `@` file references — these only work in commands. + +### Source Pattern → Target Pattern (Skill) + +| Source Style | Target Style | +|-------------|-------------| +| `# Title` + flat markdown overview | `` (2-3 sentences) | +| `## Implementation` / `## Execution Flow` / `## Phase Summary` | `` with numbered `## N.` steps | +| Phase file references as prose | `Read("phases/...")` calls within process steps | +| `## Success Criteria` / `## Coordinator Checklist` | `` with checkbox list | +| `## Auto Mode` / `## Auto Mode Defaults` | `` section | +| `## Error Handling` | Preserve as-is within `` or as standalone section | +| Code blocks, tables, ASCII diagrams | **Preserve exactly** | + +### What NOT to Add (Skill-Specific) + +| Element | Why NOT | +|---------|---------| +| `` | Skills cannot use `@` refs — progressive loading | +| `@specs/...` or `@phases/...` | `@` syntax not supported in skills | +| `` | Skills chain via `Skill()` calls, not offer menus | + +### What to ADD (Skill-Specific) + +| Missing Element | Add | +|----------------|-----| +| `` | Extract from overview/description | +| `` wrapper | Wrap implementation steps | +| `` | Generate from coordinator checklist or existing content | +| `` | If auto mode behavior exists, wrap in tag | + +### Frontmatter Conversion (Skill) + +| Source Field | Target Field | Transformation | +|-------------|-------------|----------------| +| `name` | `name` | Keep as-is | +| `description` | `description` | Keep as-is | +| `allowed-tools` | `allowed-tools` | Keep as-is | +| Missing `allowed-tools` | `allowed-tools` | Infer from content | + ## Command Conversion Rules ### Source Pattern → Target Pattern diff --git a/.claude/skills/workflow-lite-plan/SKILL.md b/.claude/skills/workflow-lite-plan/SKILL.md index c80c89d9..22e3ab17 100644 --- a/.claude/skills/workflow-lite-plan/SKILL.md +++ b/.claude/skills/workflow-lite-plan/SKILL.md @@ -4,17 +4,18 @@ description: Lightweight planning skill - task analysis, multi-angle exploration allowed-tools: Skill, Agent, AskUserQuestion, TodoWrite, Read, Write, Edit, Bash, Glob, Grep --- -# Workflow-Lite-Plan - + Planning pipeline: explore → clarify → plan → confirm → handoff to lite-execute. +Produces exploration results, a structured plan (plan.json), independent task files (.task/TASK-*.json), and hands off to lite-execute for implementation. + ---- + -## Context Isolation +## 1. Context Isolation > **CRITICAL**: If invoked from analyze-with-file (via "执行任务"), the analyze-with-file session is **COMPLETE** and all its phase instructions are FINISHED and MUST NOT be referenced. Only follow LP-Phase 1-5 defined in THIS document. Phase numbers are INDEPENDENT of any prior workflow. -## Input +## 2. Input ``` Task description or path to .md file (required) @@ -27,7 +28,7 @@ Planning pipeline: explore → clarify → plan → confirm → handoff to lite- **Note**: Workflow preferences (`autoYes`, `forceExplore`) must be initialized at skill start. If not provided by caller, skill will prompt user for workflow mode selection. -## Output Artifacts +## 3. Output Artifacts | Artifact | Description | |----------|-------------| @@ -43,14 +44,7 @@ Planning pipeline: explore → clarify → plan → confirm → handoff to lite- **Schema Reference**: `~/.ccw/workflows/cli-templates/schemas/plan-overview-base-schema.json` -## Auto Mode Defaults - -When `workflowPreferences.autoYes === true` (entire plan+execute workflow): -- **Clarification**: Skipped | **Plan Confirmation**: Allow & Execute | **Execution**: Auto | **Review**: Skip - -Auto mode authorizes the complete plan-and-execute workflow with a single confirmation. No further prompts. - -## Phase Summary +## 4. Phase Summary | Phase | Core Action | Output | |-------|-------------|--------| @@ -61,9 +55,7 @@ Auto mode authorizes the complete plan-and-execute workflow with a single confir | LP-4 | Display plan → AskUserQuestion (Confirm + Execution + Review) | userSelection | | LP-5 | Build executionContext → Skill("lite-execute") | handoff (Mode 1) | -## Implementation - -### LP-Phase 0: Workflow Preferences Initialization +## 5. LP-Phase 0: Workflow Preferences Initialization ```javascript if (typeof workflowPreferences === 'undefined' || workflowPreferences === null) { @@ -74,7 +66,7 @@ if (typeof workflowPreferences === 'undefined' || workflowPreferences === null) } ``` -### LP-Phase 1: Intelligent Multi-Angle Exploration +## 6. LP-Phase 1: Intelligent Multi-Angle Exploration **Session Setup** (MANDATORY): ```javascript @@ -248,9 +240,7 @@ console.log(`Exploration complete: ${explorationManifest.explorations.map(e => e **Output**: `exploration-{angle}.json` (1-4 files) + `explorations-manifest.json` ---- - -### LP-Phase 2: Clarification (Optional, Multi-Round) +## 7. LP-Phase 2: Clarification (Optional, Multi-Round) **Skip if**: No exploration or `clarification_needs` is empty across all explorations @@ -307,9 +297,7 @@ if (workflowPreferences.autoYes) { **Output**: `clarificationContext` (in-memory) ---- - -### LP-Phase 3: Planning +## 8. LP-Phase 3: Planning **IMPORTANT**: LP-Phase 3 is **planning only** — NO code execution. All execution happens in LP-Phase 5 via lite-execute. @@ -431,9 +419,7 @@ ${complexity} // TodoWrite: Phase 3 → completed, Phase 4 → in_progress ---- - -### LP-Phase 4: Task Confirmation & Execution Selection +## 9. LP-Phase 4: Task Confirmation & Execution Selection **Display Plan**: ```javascript @@ -499,9 +485,7 @@ if (workflowPreferences.autoYes) { // TodoWrite: Phase 4 → completed `[${userSelection.execution_method} + ${userSelection.code_review_tool}]`, Phase 5 → in_progress ---- - -### LP-Phase 5: Handoff to Execution +## 10. LP-Phase 5: Handoff to Execution **CRITICAL**: lite-plan NEVER executes code directly. ALL execution goes through lite-execute. @@ -562,7 +546,7 @@ Skill("lite-execute") // executionContext passed as global variable (Mode 1: In-Memory Plan) ``` -## Session Folder Structure +## 11. Session Folder Structure ``` .workflow/.lite-plan/{task-slug}-{YYYY-MM-DD}/ @@ -576,7 +560,7 @@ Skill("lite-execute") └── ... ``` -## Error Handling +## 12. Error Handling | Error | Resolution | |-------|------------| @@ -585,3 +569,26 @@ Skill("lite-execute") | Clarification timeout | Use exploration findings as-is | | Confirmation timeout | Save context, display resume instructions | | Modify loop > 3 times | Suggest breaking task or using /workflow-plan | + + + + +When `workflowPreferences.autoYes === true` (entire plan+execute workflow): +- **Clarification**: Skipped | **Plan Confirmation**: Allow & Execute | **Execution**: Auto | **Review**: Skip + +Auto mode authorizes the complete plan-and-execute workflow with a single confirmation. No further prompts. + + + +- [ ] Workflow preferences (autoYes, forceExplore) initialized at LP-Phase 0 +- [ ] Complexity assessed and exploration angles selected appropriately +- [ ] Parallel exploration agents launched with run_in_background=false +- [ ] Explorations manifest built from auto-discovered files +- [ ] Clarification needs aggregated, deduped, and presented in batches of 4 +- [ ] Plan generated via direct Claude (Low) or cli-lite-planning-agent (Medium/High) +- [ ] Plan output as two-layer: plan.json (task_ids[]) + .task/TASK-*.json +- [ ] User confirmation collected (or auto-approved in auto mode) +- [ ] executionContext fully built with all artifacts and session references +- [ ] Handoff to lite-execute via Skill("lite-execute") with executionContext +- [ ] No code execution in planning phases -- all execution deferred to lite-execute + diff --git a/.claude/skills/workflow-plan/SKILL.md b/.claude/skills/workflow-plan/SKILL.md index 2d488669..7ad4c572 100644 --- a/.claude/skills/workflow-plan/SKILL.md +++ b/.claude/skills/workflow-plan/SKILL.md @@ -4,18 +4,20 @@ description: Unified planning skill - 4-phase planning workflow, plan verificati allowed-tools: Skill, Agent, AskUserQuestion, TodoWrite, Read, Write, Edit, Bash, Glob, Grep --- -# Workflow Plan + +Unified planning skill combining 4-phase planning workflow, plan quality verification, and interactive replanning. Produces IMPL_PLAN.md, task JSONs, verification reports, and manages plan lifecycle through session-level artifact updates. Routes by mode (plan | verify | replan) and acts as a pure orchestrator: executes phases, parses outputs, and passes context. + -Unified planning skill combining 4-phase planning workflow, plan quality verification, and interactive replanning. Produces IMPL_PLAN.md, task JSONs, verification reports, and manages plan lifecycle through session-level artifact updates. + -## Architecture Overview +## 1. Architecture Overview ``` ┌──────────────────────────────────────────────────────────────────┐ │ Workflow Plan Orchestrator (SKILL.md) │ │ → Route by mode: plan | verify | replan │ │ → Pure coordinator: Execute phases, parse outputs, pass context │ -└──────────────────────────────┬───────────────────────────────────┘ +└──────────────────────────────────┬───────────────────────────────┘ │ ┌───────────────────────┼───────────────────────┐ ↓ ↓ ↓ @@ -38,7 +40,7 @@ Unified planning skill combining 4-phase planning workflow, plan quality verific └───────────┘─── Review ──→ Display session status inline ``` -## Key Design Principles +## 2. Key Design Principles 1. **Pure Orchestrator**: SKILL.md routes and coordinates only; execution detail lives in phase files 2. **Progressive Phase Loading**: Read phase docs ONLY when that phase is about to execute @@ -47,7 +49,7 @@ Unified planning skill combining 4-phase planning workflow, plan quality verific 5. **Auto-Continue**: After each phase completes, automatically execute next pending phase 6. **Accumulated State**: planning-notes.md carries context across phases for N+1 decisions -## Interactive Preference Collection +## 3. Interactive Preference Collection Before dispatching to phase execution, collect workflow preferences via AskUserQuestion: @@ -99,7 +101,7 @@ if (autoYes) { **workflowPreferences** is passed to phase execution as context variable, referenced as `workflowPreferences.autoYes`, `workflowPreferences.interactive` within phases. -## Mode Detection +## 4. Mode Detection ```javascript const args = $ARGUMENTS @@ -113,7 +115,7 @@ function detectMode(args) { } ``` -## Compact Recovery (Phase Persistence) +## 5. Compact Recovery (Phase Persistence) Multi-phase planning (Phase 1-4/5/6) spans long conversations. Uses **双重保险**: TodoWrite 跟踪 active phase 保护其不被压缩,sentinel 作为兜底。 @@ -121,7 +123,7 @@ Multi-phase planning (Phase 1-4/5/6) spans long conversations. Uses **双重保 > The phase currently marked `in_progress` is the active execution phase — preserve its FULL content. > Only compress phases marked `completed` or `pending`. -## Execution Flow +## 6. Execution Flow ### Plan Mode (default) @@ -130,23 +132,23 @@ Input Parsing: └─ Convert user input to structured format (GOAL/SCOPE/CONTEXT) Phase 1: Session Discovery - └─ Ref: phases/01-session-discovery.md + └─ Ref: Read("phases/01-session-discovery.md") └─ Output: sessionId (WFS-xxx), planning-notes.md Phase 2: Context Gathering - └─ Ref: phases/02-context-gathering.md + └─ Ref: Read("phases/02-context-gathering.md") ├─ Tasks attached: Analyze structure → Identify integration → Generate package └─ Output: contextPath + conflictRisk Phase 3: Conflict Resolution (conditional: conflictRisk ≥ medium) └─ Decision (conflictRisk check): - ├─ conflictRisk ≥ medium → Ref: phases/03-conflict-resolution.md + ├─ conflictRisk ≥ medium → Ref: Read("phases/03-conflict-resolution.md") │ ├─ Tasks attached: Detect conflicts → Present to user → Apply strategies │ └─ Output: Modified brainstorm artifacts └─ conflictRisk < medium → Skip to Phase 4 Phase 4: Task Generation - └─ Ref: phases/04-task-generation.md + └─ Ref: Read("phases/04-task-generation.md") └─ Output: IMPL_PLAN.md, task JSONs, TODO_LIST.md Plan Confirmation (User Decision Gate): @@ -160,7 +162,7 @@ Plan Confirmation (User Decision Gate): ``` Phase 5: Plan Verification - └─ Ref: phases/05-plan-verify.md + └─ Ref: Read("phases/05-plan-verify.md") └─ Output: PLAN_VERIFICATION.md with quality gate recommendation ``` @@ -168,7 +170,7 @@ Phase 5: Plan Verification ``` Phase 6: Interactive Replan - └─ Ref: phases/06-replan.md + └─ Ref: Read("phases/06-replan.md") └─ Output: Updated IMPL_PLAN.md, task JSONs, TODO_LIST.md ``` @@ -176,19 +178,19 @@ Phase 6: Interactive Replan | Phase | Document | Purpose | Mode | Compact | |-------|----------|---------|------|---------| -| 1 | [phases/01-session-discovery.md](phases/01-session-discovery.md) | Create or discover workflow session | plan | TodoWrite 驱动 | -| 2 | [phases/02-context-gathering.md](phases/02-context-gathering.md) | Gather project context and analyze codebase | plan | TodoWrite 驱动 | -| 3 | [phases/03-conflict-resolution.md](phases/03-conflict-resolution.md) | Detect and resolve conflicts (conditional) | plan | TodoWrite 驱动 | -| 4 | [phases/04-task-generation.md](phases/04-task-generation.md) | Generate implementation plan and task JSONs | plan | TodoWrite 驱动 + 🔄 sentinel | -| 5 | [phases/05-plan-verify.md](phases/05-plan-verify.md) | Read-only verification with quality gate | verify | TodoWrite 驱动 | -| 6 | [phases/06-replan.md](phases/06-replan.md) | Interactive replanning with boundary clarification | replan | TodoWrite 驱动 | +| 1 | phases/01-session-discovery.md | Create or discover workflow session | plan | TodoWrite 驱动 | +| 2 | phases/02-context-gathering.md | Gather project context and analyze codebase | plan | TodoWrite 驱动 | +| 3 | phases/03-conflict-resolution.md | Detect and resolve conflicts (conditional) | plan | TodoWrite 驱动 | +| 4 | phases/04-task-generation.md | Generate implementation plan and task JSONs | plan | TodoWrite 驱动 + 🔄 sentinel | +| 5 | phases/05-plan-verify.md | Read-only verification with quality gate | verify | TodoWrite 驱动 | +| 6 | phases/06-replan.md | Interactive replanning with boundary clarification | replan | TodoWrite 驱动 | **Compact Rules**: 1. **TodoWrite `in_progress`** → 保留完整内容,禁止压缩 2. **TodoWrite `completed`** → 可压缩为摘要 3. **🔄 sentinel fallback** → Phase 4 包含 compact sentinel;若 compact 后仅存 sentinel 而无完整 Step 协议,必须立即 `Read("phases/04-task-generation.md")` 恢复 -## Core Rules +## 7. Core Rules 1. **Start Immediately**: First action is mode detection + TodoWrite initialization, second action is phase execution 2. **No Preliminary Analysis**: Do not read files, analyze structure, or gather context before Phase 1 @@ -199,7 +201,7 @@ Phase 6: Interactive Replan 7. **Progressive Phase Loading**: Read phase docs ONLY when that phase is about to execute 8. **DO NOT STOP**: Continuous multi-phase workflow. After executing all attached tasks, immediately collapse them and execute next phase -## Input Processing +## 8. Input Processing **Convert User Input to Structured Format**: @@ -228,7 +230,7 @@ Phase 6: Interactive Replan - Extract goal, scope, requirements - Format into structured description -## Data Flow +## 9. Data Flow ### Plan Mode @@ -290,7 +292,7 @@ Phase 6: Mode detection → Clarification → Impact analysis → Backup → App ↓ Output: Updated artifacts + change summary ``` -## TodoWrite Pattern +## 10. TodoWrite Pattern **Core Concept**: Dynamic task attachment and collapse for real-time visibility into workflow execution. @@ -349,7 +351,7 @@ Phase 6: Mode detection → Clarification → Impact analysis → Backup → App **Note**: See individual Phase descriptions for detailed TodoWrite Update examples. -## Post-Phase Updates +## 11. Post-Phase Updates After each phase completes, update planning-notes.md: @@ -360,7 +362,7 @@ After each phase completes, update planning-notes.md: See phase files for detailed update code. -## Error Handling +## 12. Error Handling - **Parsing Failure**: If output parsing fails, retry command once, then report error - **Validation Failure**: If validation fails, report which file/data is missing @@ -368,7 +370,7 @@ See phase files for detailed update code. - **Session Not Found** (verify/replan): Report error with available sessions list - **Task Not Found** (replan): Report error with available tasks list -## Coordinator Checklist +## 13. Coordinator Checklist ### Plan Mode - **Pre-Phase**: Convert user input to structured format (GOAL/SCOPE/CONTEXT) @@ -403,7 +405,7 @@ See phase files for detailed update code. - Initialize TodoWrite with replan-specific tasks - Execute Phase 6 through all sub-phases (clarification → impact → backup → apply → verify) -## Structure Template Reference +## 14. Structure Template Reference **Minimal Structure**: ``` @@ -421,7 +423,7 @@ REQUIREMENTS: [Specific technical requirements] CONSTRAINTS: [Limitations or boundaries] ``` -## Related Skills +## 15. Related Skills **Prerequisite Skills**: - `brainstorm` skill - Optional: Generate role-based analyses before planning @@ -439,3 +441,26 @@ CONSTRAINTS: [Limitations or boundaries] - Display session status inline - Review task breakdown and current progress - `Skill(skill="workflow-execute")` - Begin implementation of generated tasks (skill: workflow-execute) - `workflow-plan` skill (replan phase) - Modify plan (can also invoke via replan mode) + + + + +When `workflowPreferences.autoYes` is true (triggered by `-y` or `--yes` flag, or user selecting "Auto" mode): +- Skip all confirmation prompts, use default values +- Auto-select "Verify Plan Quality" at Plan Confirmation Gate +- Auto-continue to execution if verification returns PROCEED +- Skip interactive clarification in replan mode (use safe defaults) + + + +- [ ] Mode correctly detected from skill trigger (plan / verify / replan) +- [ ] TodoWrite initialized and updated after each phase with attachment/collapse pattern +- [ ] All phases executed in sequence with proper data passing between phases +- [ ] Phase documents loaded progressively via Read() only when phase is about to execute +- [ ] planning-notes.md updated after each phase with accumulated context +- [ ] Phase 3 conditionally executed based on conflictRisk assessment +- [ ] Plan Confirmation Gate presented with correct options after Phase 4 +- [ ] All output artifacts generated: IMPL_PLAN.md, task JSONs, TODO_LIST.md +- [ ] Compact recovery works: in_progress phases preserved, completed phases compressible +- [ ] Error handling covers parsing failures, validation failures, and missing sessions/tasks + diff --git a/.claude/skills/workflow-tdd-plan/SKILL.md b/.claude/skills/workflow-tdd-plan/SKILL.md index 5e8e9f08..1951d801 100644 --- a/.claude/skills/workflow-tdd-plan/SKILL.md +++ b/.claude/skills/workflow-tdd-plan/SKILL.md @@ -4,18 +4,20 @@ description: Unified TDD workflow skill combining 6-phase TDD planning with Red- allowed-tools: Skill, Agent, AskUserQuestion, TaskCreate, TaskUpdate, TaskList, Read, Write, Edit, Bash, Glob, Grep --- -# Workflow TDD + +Unified TDD workflow skill combining TDD planning (Red-Green-Refactor task chain generation with test-first development structure) and TDD verification (compliance validation with quality gate reporting). Produces IMPL_PLAN.md, task JSONs with internal TDD cycles, and TDD_COMPLIANCE_REPORT.md. Triggers on "workflow-tdd-plan" (plan mode) or "workflow-tdd-verify" (verify mode). + -Unified TDD workflow skill combining TDD planning (Red-Green-Refactor task chain generation with test-first development structure) and TDD verification (compliance validation with quality gate reporting). Produces IMPL_PLAN.md, task JSONs with internal TDD cycles, and TDD_COMPLIANCE_REPORT.md. + -## Architecture Overview +## 1. Architecture Overview ``` ┌──────────────────────────────────────────────────────────────────┐ │ Workflow TDD Orchestrator (SKILL.md) │ │ → Route by mode: plan | verify │ │ → Pure coordinator: Execute phases, parse outputs, pass context │ -└──────────────────────────────┬───────────────────────────────────┘ +└──────────────────────────────────┬───────────────────────────────┘ │ ┌───────────────────────┴───────────────────────┐ ↓ ↓ @@ -38,7 +40,7 @@ Unified TDD workflow skill combining TDD planning (Red-Green-Refactor task chain └───────────┘─── Review ──→ Display session status inline ``` -## Key Design Principles +## 2. Key Design Principles 1. **Pure Orchestrator**: SKILL.md routes and coordinates only; execution detail lives in phase files 2. **Progressive Phase Loading**: Read phase docs ONLY when that phase is about to execute @@ -47,7 +49,7 @@ Unified TDD workflow skill combining TDD planning (Red-Green-Refactor task chain 5. **Auto-Continue**: After each phase completes, automatically execute next pending phase 6. **TDD Iron Law**: NO PRODUCTION CODE WITHOUT A FAILING TEST FIRST - enforced in task structure -## Interactive Preference Collection +## 3. Interactive Preference Collection Before dispatching to phase execution, collect workflow preferences via AskUserQuestion: @@ -81,7 +83,7 @@ if (autoYes) { **workflowPreferences** is passed to phase execution as context variable, referenced as `workflowPreferences.autoYes` within phases. -## Mode Detection +## 4. Mode Detection ```javascript const args = $ARGUMENTS @@ -94,7 +96,7 @@ function detectMode(args) { } ``` -## Compact Recovery (Phase Persistence) +## 5. Compact Recovery (Phase Persistence) Multi-phase TDD planning (Phase 1-6/7) spans long conversations. Uses **双重保险**: TodoWrite 跟踪 active phase 保护其不被压缩,sentinel 作为兜底。 @@ -102,42 +104,40 @@ Multi-phase TDD planning (Phase 1-6/7) spans long conversations. Uses **双重 > The phase currently marked `in_progress` is the active execution phase — preserve its FULL content. > Only compress phases marked `completed` or `pending`. -## Execution Flow - -### Plan Mode (default) +## 6. Execution Flow — Plan Mode (default) ``` Input Parsing: └─ Convert user input to TDD structured format (GOAL/SCOPE/CONTEXT/TEST_FOCUS) Phase 1: Session Discovery - └─ Ref: phases/01-session-discovery.md + └─ Read("phases/01-session-discovery.md") └─ Output: sessionId (WFS-xxx) Phase 2: Context Gathering - └─ Ref: phases/02-context-gathering.md + └─ Read("phases/02-context-gathering.md") ├─ Tasks attached: Analyze structure → Identify integration → Generate package └─ Output: contextPath + conflictRisk Phase 3: Test Coverage Analysis - └─ Ref: phases/03-test-coverage-analysis.md + └─ Read("phases/03-test-coverage-analysis.md") ├─ Tasks attached: Detect framework → Analyze coverage → Identify gaps └─ Output: testContextPath Phase 4: Conflict Resolution (conditional: conflictRisk ≥ medium) └─ Decision (conflictRisk check): - ├─ conflictRisk ≥ medium → Ref: phases/04-conflict-resolution.md + ├─ conflictRisk ≥ medium → Read("phases/04-conflict-resolution.md") │ ├─ Tasks attached: Detect conflicts → Log analysis → Apply strategies │ └─ Output: conflict-resolution.json └─ conflictRisk < medium → Skip to Phase 5 Phase 5: TDD Task Generation - └─ Ref: phases/05-tdd-task-generation.md + └─ Read("phases/05-tdd-task-generation.md") ├─ Tasks attached: Discovery → Planning → Output └─ Output: IMPL_PLAN.md, IMPL-*.json, TODO_LIST.md Phase 6: TDD Structure Validation - └─ Ref: phases/06-tdd-structure-validation.md + └─ Read("phases/06-tdd-structure-validation.md") └─ Output: Validation report + Plan Confirmation Gate Plan Confirmation (User Decision Gate): @@ -147,32 +147,34 @@ Plan Confirmation (User Decision Gate): └─ "Review Status Only" → Display session status inline ``` -### Verify Mode +## 7. Execution Flow — Verify Mode ``` Phase 7: TDD Verification - └─ Ref: phases/07-tdd-verify.md + └─ Read("phases/07-tdd-verify.md") └─ Output: TDD_COMPLIANCE_REPORT.md with quality gate recommendation ``` -**Phase Reference Documents** (read on-demand when phase executes): +## 8. Phase Reference Documents + +Read on-demand when phase executes using `Read("phases/...")`: | Phase | Document | Purpose | Mode | Compact | |-------|----------|---------|------|---------| -| 1 | [phases/01-session-discovery.md](phases/01-session-discovery.md) | Create or discover TDD workflow session | plan | TodoWrite 驱动 | -| 2 | [phases/02-context-gathering.md](phases/02-context-gathering.md) | Gather project context and analyze codebase | plan | TodoWrite 驱动 | -| 3 | [phases/03-test-coverage-analysis.md](phases/03-test-coverage-analysis.md) | Analyze test coverage and framework detection | plan | TodoWrite 驱动 | -| 4 | [phases/04-conflict-resolution.md](phases/04-conflict-resolution.md) | Detect and resolve conflicts (conditional) | plan | TodoWrite 驱动 | -| 5 | [phases/05-tdd-task-generation.md](phases/05-tdd-task-generation.md) | Generate TDD tasks with Red-Green-Refactor cycles | plan | TodoWrite 驱动 + 🔄 sentinel | -| 6 | [phases/06-tdd-structure-validation.md](phases/06-tdd-structure-validation.md) | Validate TDD structure and present confirmation gate | plan | TodoWrite 驱动 + 🔄 sentinel | -| 7 | [phases/07-tdd-verify.md](phases/07-tdd-verify.md) | Full TDD compliance verification with quality gate | verify | TodoWrite 驱动 | +| 1 | phases/01-session-discovery.md | Create or discover TDD workflow session | plan | TodoWrite 驱动 | +| 2 | phases/02-context-gathering.md | Gather project context and analyze codebase | plan | TodoWrite 驱动 | +| 3 | phases/03-test-coverage-analysis.md | Analyze test coverage and framework detection | plan | TodoWrite 驱动 | +| 4 | phases/04-conflict-resolution.md | Detect and resolve conflicts (conditional) | plan | TodoWrite 驱动 | +| 5 | phases/05-tdd-task-generation.md | Generate TDD tasks with Red-Green-Refactor cycles | plan | TodoWrite 驱动 + sentinel | +| 6 | phases/06-tdd-structure-validation.md | Validate TDD structure and present confirmation gate | plan | TodoWrite 驱动 + sentinel | +| 7 | phases/07-tdd-verify.md | Full TDD compliance verification with quality gate | verify | TodoWrite 驱动 | **Compact Rules**: 1. **TodoWrite `in_progress`** → 保留完整内容,禁止压缩 2. **TodoWrite `completed`** → 可压缩为摘要 -3. **🔄 sentinel fallback** → Phase 5/6 包含 compact sentinel;若 compact 后仅存 sentinel 而无完整 Step 协议,必须立即 `Read()` 恢复对应 phase 文件 +3. **sentinel fallback** → Phase 5/6 包含 compact sentinel;若 compact 后仅存 sentinel 而无完整 Step 协议,必须立即 `Read()` 恢复对应 phase 文件 -## Core Rules +## 9. Core Rules 1. **Start Immediately**: First action is mode detection + TaskCreate initialization, second action is phase execution 2. **No Preliminary Analysis**: Do not read files, analyze structure, or gather context before Phase 1 @@ -184,7 +186,7 @@ Phase 7: TDD Verification 8. **DO NOT STOP**: Continuous multi-phase workflow. After executing all attached tasks, immediately collapse them and execute next phase 9. **TDD Context**: All descriptions include "TDD:" prefix -## TDD Compliance Requirements +## 10. TDD Compliance Requirements ### The Iron Law @@ -222,7 +224,7 @@ NO PRODUCTION CODE WITHOUT A FAILING TEST FIRST - Test-first forces edge case discovery before implementation - Tests-after verify what was built, not what's required -## Input Processing +## 11. Input Processing **Convert User Input to TDD Structured Format**: @@ -252,9 +254,7 @@ NO PRODUCTION CODE WITHOUT A FAILING TEST FIRST 3. **File/Issue** → Read and structure with TDD -## Data Flow - -### Plan Mode +## 12. Data Flow — Plan Mode ``` User Input (task description) @@ -297,7 +297,7 @@ Plan Confirmation (User Decision Gate): └─ "Review Status Only" → Display session status inline ``` -### Verify Mode +## 13. Data Flow — Verify Mode ``` Input: --session sessionId (or auto-detect) @@ -311,7 +311,7 @@ Phase 7: Session discovery → Chain validation → Coverage analysis → Report - Existing context and analysis - Session-specific configuration -## TodoWrite Pattern +## 14. TodoWrite Pattern **Core Concept**: Dynamic task attachment and collapse for real-time visibility into TDD workflow execution. @@ -394,7 +394,7 @@ Phase 7: Session discovery → Chain validation → Coverage analysis → Report **Note**: See individual Phase descriptions for detailed TodoWrite Update examples. -## Post-Phase Updates +## 15. Post-Phase Updates ### Memory State Check @@ -409,7 +409,7 @@ After heavy phases (Phase 2-3), evaluate context window usage: Similar to workflow-plan, a `planning-notes.md` can accumulate context across phases if needed. See Phase 1 for initialization. -## Error Handling +## 16. Error Handling - **Parsing Failure**: If output parsing fails, retry command once, then report error - **Validation Failure**: Report which file/data is missing or invalid @@ -447,9 +447,8 @@ Similar to workflow-plan, a `planning-notes.md` can accumulate context across ph 2. Summary displayed in Phase 6 output 3. User decides whether to address before `workflow-execute` skill -## Coordinator Checklist +## 17. Coordinator Checklist — Plan Mode -### Plan Mode - **Pre-Phase**: Convert user input to TDD structured format (TDD/GOAL/SCOPE/CONTEXT/TEST_FOCUS) - Initialize TaskCreate before any command (Phase 4 added dynamically after Phase 2) - Execute Phase 1 immediately with structured description @@ -466,20 +465,21 @@ Similar to workflow-plan, a `planning-notes.md` can accumulate context across ph - Verify all Phase 5 outputs (IMPL_PLAN.md, IMPL-*.json, TODO_LIST.md) - Execute Phase 6 (internal TDD structure validation) - **Plan Confirmation Gate**: Present user with choice (Verify → Phase 7 / Execute / Review Status) -- **If user selects Verify**: Read phases/07-tdd-verify.md, execute Phase 7 in-process +- **If user selects Verify**: Read("phases/07-tdd-verify.md"), execute Phase 7 in-process - **If user selects Execute**: Skill(skill="workflow-execute") - **If user selects Review**: Display session status inline - **Auto mode (workflowPreferences.autoYes)**: Auto-select "Verify TDD Compliance", then auto-continue to execute if APPROVED - Update TaskCreate/TaskUpdate after each phase - After each phase, automatically continue to next phase based on TaskList status -### Verify Mode +## 18. Coordinator Checklist — Verify Mode + - Detect/validate session (from --session flag or auto-detect) - Initialize TaskCreate with verification tasks - Execute Phase 7 through all sub-phases (session validation → chain validation → coverage analysis → report generation) - Present quality gate result and next step options -## Related Skills +## 19. Related Skills **Prerequisite Skills**: - None - TDD planning is self-contained (can optionally run brainstorm commands before) @@ -500,3 +500,28 @@ Similar to workflow-plan, a `planning-notes.md` can accumulate context across ph - `workflow-plan` skill (plan-verify phase) - Verify plan quality and dependencies - Display session status inline - Review TDD task breakdown - `Skill(skill="workflow-execute")` - Begin TDD implementation + + + + +When `workflowPreferences.autoYes` is true (triggered by `-y`/`--yes` flag): +- Skip all interactive confirmation prompts +- Use default values for all preference questions +- At Plan Confirmation Gate: Auto-select "Verify TDD Compliance" +- After verification: Auto-continue to execute if quality gate returns APPROVED +- All phases execute continuously without user intervention + + + +- [ ] Mode correctly detected from skill trigger name (plan vs verify) +- [ ] All 6 plan phases execute sequentially with proper data flow between them +- [ ] Phase files loaded progressively via Read() only when phase is about to execute +- [ ] TaskCreate/TaskUpdate tracks all phases with attachment/collapse pattern +- [ ] TDD Iron Law enforced: every task has Red-Green-Refactor structure +- [ ] Phase 4 (Conflict Resolution) conditionally executes based on conflictRisk level +- [ ] Plan Confirmation Gate presents three choices after Phase 6 +- [ ] Verify mode (Phase 7) produces TDD_COMPLIANCE_REPORT.md with quality gate +- [ ] All outputs generated: IMPL_PLAN.md, IMPL-*.json, TODO_LIST.md +- [ ] Compact recovery preserves active phase content via TodoWrite status +- [ ] Error handling retries once on parsing failure, reports on persistent errors + diff --git a/.claude/skills/workflow-test-fix/SKILL.md b/.claude/skills/workflow-test-fix/SKILL.md index e78ac972..7dd51710 100644 --- a/.claude/skills/workflow-test-fix/SKILL.md +++ b/.claude/skills/workflow-test-fix/SKILL.md @@ -4,11 +4,13 @@ description: Unified test-fix pipeline combining test generation (session, conte allowed-tools: Skill, Agent, AskUserQuestion, TaskCreate, TaskUpdate, TaskList, Read, Write, Edit, Bash, Glob, Grep --- -# Workflow Test Fix + +Unified test-fix orchestrator that combines **test planning generation** (Phase 1-4) with **iterative test-cycle execution** (Phase 5) into a single end-to-end pipeline. Creates test sessions with progressive L0-L3 test layers, generates test tasks, then executes them with adaptive fix cycles until pass rate >= 95% or max iterations reached. Triggered via skill name routing for full pipeline or execute-only modes. + -Unified test-fix orchestrator that combines **test planning generation** (Phase 1-4) with **iterative test-cycle execution** (Phase 5) into a single end-to-end pipeline. Creates test sessions with progressive L0-L3 test layers, generates test tasks, then executes them with adaptive fix cycles until pass rate >= 95% or max iterations reached. + -## Architecture Overview +## 1. Architecture Overview ``` ┌───────────────────────────────────────────────────────────────────────────┐ @@ -45,7 +47,7 @@ Task Pipeline (generated in Phase 4, executed in Phase 5): └──────────────┘ └─────────────────┘ └─────────────────┘ └──────────────┘ ``` -## Key Design Principles +## 2. Key Design Principles 1. **Unified Pipeline**: Generation and execution are one continuous workflow - no manual handoff 2. **Pure Orchestrator**: SKILL.md coordinates only - delegates all execution detail to phase files @@ -56,14 +58,14 @@ Task Pipeline (generated in Phase 4, executed in Phase 5): 7. **Quality Gate**: Pass rate >= 95% (criticality-aware) terminates the fix loop 8. **Phase File Hygiene**: Phase files reference `workflowPreferences.*` for preferences, no CLI flag parsing -## Usage +## 3. Usage Full pipeline and execute-only modes are triggered by skill name routing (see Mode Detection). Workflow preferences (auto mode) are collected interactively via AskUserQuestion before dispatching to phases. **Full pipeline** (workflow-test-fix): Task description or session ID as arguments → interactive preference collection → generate + execute pipeline **Execute only** (workflow-test-fix): Auto-discovers active session → interactive preference collection → execution loop -## Interactive Preference Collection +## 4. Interactive Preference Collection Before dispatching to phase execution, collect workflow preferences via AskUserQuestion: @@ -97,7 +99,7 @@ if (autoYes) { **workflowPreferences** is passed to phase execution as context variable, referenced as `workflowPreferences.autoYes` within phases. -## Compact Recovery (Phase Persistence) +## 5. Compact Recovery (Phase Persistence) Multi-phase test-fix pipeline (Phase 1-5) spans long conversations, especially Phase 5 fix loops. Uses **双重保险**: TodoWrite 跟踪 active phase 保护其不被压缩,sentinel 作为兜底。 @@ -105,7 +107,7 @@ Multi-phase test-fix pipeline (Phase 1-5) spans long conversations, especially P > The phase currently marked `in_progress` is the active execution phase — preserve its FULL content. > Only compress phases marked `completed` or `pending`. -## Execution Flow +## 6. Execution Flow ``` Entry Point Detection: @@ -113,23 +115,23 @@ Entry Point Detection: └─ /workflow-test-fix → Execution Only (Phase 5) Phase 1: Session Start (session-start) - └─ Ref: phases/01-session-start.md + └─ Read("phases/01-session-start.md") ├─ Step 1.0: Detect input mode (session | prompt) ├─ Step 1.1: Create test session → testSessionId └─ Output: testSessionId, MODE Phase 2: Test Context Gather (test-context-gather) - └─ Ref: phases/02-test-context-gather.md + └─ Read("phases/02-test-context-gather.md") ├─ Step 1.2: Gather test context → contextPath └─ Output: contextPath Phase 3: Test Concept Enhanced (test-concept-enhanced) - └─ Ref: phases/03-test-concept-enhanced.md + └─ Read("phases/03-test-concept-enhanced.md") ├─ Step 1.3: Test analysis (Gemini) → TEST_ANALYSIS_RESULTS.md └─ Output: TEST_ANALYSIS_RESULTS.md Phase 4: Test Task Generate (test-task-generate) - └─ Ref: phases/04-test-task-generate.md + └─ Read("phases/04-test-task-generate.md") ├─ Step 1.4: Generate test tasks → IMPL_PLAN.md, IMPL-*.json, TODO_LIST.md └─ Output: testSessionId, 4+ task JSONs @@ -137,7 +139,7 @@ Summary Output (inline after Phase 4): └─ Display summary, auto-continue to Phase 5 Phase 5: Test Cycle Execution (test-cycle-execute) - └─ Ref: phases/05-test-cycle-execute.md + └─ Read("phases/05-test-cycle-execute.md") ├─ Step 2.1: Discovery (load session, tasks, iteration state) ├─ Step 2.2: Execute initial tasks (IMPL-001 → 001.3 → 001.5 → 002) ├─ Step 2.3: Fix loop (if pass_rate < 95%) @@ -153,18 +155,18 @@ Phase 5: Test Cycle Execution (test-cycle-execute) | Phase | Document | Purpose | Compact | |-------|----------|---------|---------| -| 1 | [phases/01-session-start.md](phases/01-session-start.md) | Detect input mode, create test session | TodoWrite 驱动 | -| 2 | [phases/02-test-context-gather.md](phases/02-test-context-gather.md) | Gather test context (coverage/codebase) | TodoWrite 驱动 | -| 3 | [phases/03-test-concept-enhanced.md](phases/03-test-concept-enhanced.md) | Gemini analysis, L0-L3 test requirements | TodoWrite 驱动 | -| 4 | [phases/04-test-task-generate.md](phases/04-test-task-generate.md) | Generate task JSONs and IMPL_PLAN.md | TodoWrite 驱动 | -| 5 | [phases/05-test-cycle-execute.md](phases/05-test-cycle-execute.md) | Execute tasks, iterative fix cycles, completion | TodoWrite 驱动 + 🔄 sentinel | +| 1 | phases/01-session-start.md | Detect input mode, create test session | TodoWrite 驱动 | +| 2 | phases/02-test-context-gather.md | Gather test context (coverage/codebase) | TodoWrite 驱动 | +| 3 | phases/03-test-concept-enhanced.md | Gemini analysis, L0-L3 test requirements | TodoWrite 驱动 | +| 4 | phases/04-test-task-generate.md | Generate task JSONs and IMPL_PLAN.md | TodoWrite 驱动 | +| 5 | phases/05-test-cycle-execute.md | Execute tasks, iterative fix cycles, completion | TodoWrite 驱动 + 🔄 sentinel | **Compact Rules**: 1. **TodoWrite `in_progress`** → 保留完整内容,禁止压缩 2. **TodoWrite `completed`** → 可压缩为摘要 3. **🔄 sentinel fallback** → Phase 5 包含 compact sentinel;若 compact 后仅存 sentinel 而无完整 Step 协议,必须立即 `Read("phases/05-test-cycle-execute.md")` 恢复 -## Core Rules +## 7. Core Rules 1. **Start Immediately**: First action is TaskCreate initialization, second action is Phase 1 (or Phase 5 for execute-only entry) 2. **No Preliminary Analysis**: Do not read files or gather context before starting the phase @@ -176,7 +178,7 @@ Phase 5: Test Cycle Execution (test-cycle-execute) 8. **Progressive Loading**: Read phase doc ONLY when that phase is about to execute 9. **Entry Point Routing**: `workflow-test-fix` skill → Phase 1-5; `workflow-test-fix` skill → Phase 5 only -## Input Processing +## 8. Input Processing ### test-fix-gen Entry (Full Pipeline) ``` @@ -194,7 +196,7 @@ Arguments → Parse flags: └─ (no args) → auto-discover active test session ``` -## Data Flow +## 9. Data Flow ``` User Input (session ID | description | file path) @@ -223,7 +225,7 @@ Phase 5: Test Cycle Execution ──────────────── ↓ 2.4: Completion → summary → session archive ``` -## Summary Output (after Phase 4) +## 10. Summary Output (after Phase 4) After Phase 4 completes, display the following summary before auto-continuing to Phase 5: @@ -255,7 +257,7 @@ Review artifacts: **CRITICAL - Next Step**: Auto-continue to Phase 5: Test Cycle Execution. Pass `testSessionId` to Phase 5 for test execution pipeline. Do NOT wait for user confirmation — the unified pipeline continues automatically. -## Test Strategy Overview +## 11. Test Strategy Overview Progressive Test Layers (L0-L3): @@ -273,7 +275,7 @@ Progressive Test Layers (L0-L3): - Pass Rate Gate: >= 95% (criticality-aware) or 100% - Max Fix Iterations: 10 (default, adjustable) -## Strategy Engine (Phase 5) +## 12. Strategy Engine (Phase 5) | Strategy | Trigger | Behavior | |----------|---------|----------| @@ -283,7 +285,7 @@ Progressive Test Layers (L0-L3): Selection logic and CLI fallback chain (Gemini → Qwen → Codex) are detailed in Phase 5. -## Agent Roles +## 13. Agent Roles | Agent | Used In | Responsibility | |-------|---------|---------------| @@ -292,7 +294,7 @@ Selection logic and CLI fallback chain (Gemini → Qwen → Codex) are detailed | **@test-fix-agent** | Phase 5 | Test execution, code fixes, criticality assignment | | **@cli-planning-agent** | Phase 5 (fix loop) | CLI analysis, root cause extraction, fix task generation | -## TodoWrite Pattern +## 14. TodoWrite Pattern **Core Concept**: Dynamic task tracking with attachment/collapse for real-time visibility. @@ -344,7 +346,7 @@ Selection logic and CLI fallback chain (Gemini → Qwen → Codex) are detailed ] ``` -## Session File Structure +## 15. Session File Structure ``` .workflow/active/WFS-test-{session}/ @@ -370,7 +372,7 @@ Selection logic and CLI fallback chain (Gemini → Qwen → Codex) are detailed └── iteration-summaries/ ``` -## Error Handling +## 16. Error Handling ### Phase 1-4 (Generation) @@ -393,13 +395,13 @@ Selection logic and CLI fallback chain (Gemini → Qwen → Codex) are detailed | Regression detected | Rollback last fix, switch to surgical strategy | | Stuck tests detected | Continue with alternative strategy, document | -## Commit Strategy (Phase 5) +## 17. Commit Strategy (Phase 5) Automatic commits at key checkpoints: 1. **After successful iteration** (pass rate increased): `test-cycle: iteration N - strategy (pass: old% → new%)` 2. **Before rollback** (regression detected): `test-cycle: rollback iteration N - regression detected` -## Completion Conditions +## 18. Completion Conditions | Condition | Pass Rate | Action | |-----------|-----------|--------| @@ -407,36 +409,36 @@ Automatic commits at key checkpoints: | **Partial Success** | >= 95%, all failures low criticality | Auto-approve with review note | | **Failure** | < 95% after max iterations | Failure report, mark blocked | -## Post-Completion Expansion +## 19. Post-Completion Expansion **Auto-sync**: Execute `/workflow:session:sync -y "{summary}"` to update specs/*.md + project-tech. After completion, ask user if they want to expand into issues (test/enhance/refactor/doc). Selected items call `/issue:new "{summary} - {dimension}"`. -## Coordinator Checklist +## 20. Coordinator Checklist ### Phase 1 (session-start) - [ ] Detect input type (session ID / description / file path) - [ ] Initialize TaskCreate before any execution -- [ ] Read Phase 1 doc, execute Steps 1.0 + 1.1 +- [ ] Read("phases/01-session-start.md"), execute Steps 1.0 + 1.1 - [ ] Parse testSessionId from step output, store in memory ### Phase 2 (test-context-gather) -- [ ] Read Phase 2 doc, execute Step 1.2 +- [ ] Read("phases/02-test-context-gather.md"), execute Step 1.2 - [ ] Parse contextPath from step output, store in memory ### Phase 3 (test-concept-enhanced) -- [ ] Read Phase 3 doc, execute Step 1.3 +- [ ] Read("phases/03-test-concept-enhanced.md"), execute Step 1.3 - [ ] Verify TEST_ANALYSIS_RESULTS.md created ### Phase 4 (test-task-generate) -- [ ] Read Phase 4 doc, execute Step 1.4 +- [ ] Read("phases/04-test-task-generate.md"), execute Step 1.4 - [ ] Verify all Phase 1-4 outputs (4 task JSONs, IMPL_PLAN.md, TODO_LIST.md) - [ ] Display Summary output (inline) - [ ] Collapse Phase 1-4 tasks, auto-continue to Phase 5 ### Phase 5 (test-cycle-execute) -- [ ] Read Phase 5 doc +- [ ] Read("phases/05-test-cycle-execute.md") - [ ] Load session, tasks, iteration state - [ ] Execute initial tasks sequentially - [ ] Calculate pass rate from test-results.json @@ -446,7 +448,7 @@ After completion, ask user if they want to expand into issues (test/enhance/refa - [ ] Generate completion summary - [ ] Offer post-completion expansion -## Related Skills +## 21. Related Skills **Prerequisite Skills**: - `workflow-plan` skill or `workflow-execute` skill - Complete implementation (Session Mode source) @@ -456,3 +458,25 @@ After completion, ask user if they want to expand into issues (test/enhance/refa - Display session status inline - Review workflow state - `review-cycle` skill - Post-implementation review - `/issue:new` - Create follow-up issues + + + + +When `-y` or `--yes` is detected in $ARGUMENTS or propagated via ccw: +- Skip all AskUserQuestion confirmations +- Use default values for all workflow preferences (`workflowPreferences = { autoYes: true }`) +- Auto-continue through all phases without user interaction +- Phase 1→2→3→4→Summary→5 executes as a fully automatic pipeline + + + +- [ ] Input type correctly detected (session ID / description / file path) +- [ ] All 5 phases execute in sequence (full pipeline) or Phase 5 only (execute-only) +- [ ] Phase documents loaded progressively via Read() only when phase executes +- [ ] TaskCreate/TaskUpdate tracking maintained throughout with attachment/collapse pattern +- [ ] All phase outputs parsed and passed to subsequent phases (testSessionId, contextPath, etc.) +- [ ] Summary displayed after Phase 4 with all task and threshold details +- [ ] Phase 5 fix loop iterates with adaptive strategy until pass rate >= 95% or max iterations +- [ ] Completion summary generated with final pass rate and session archived +- [ ] Post-completion expansion offered to user + diff --git a/ccw/src/tools/smart-search.ts b/ccw/src/tools/smart-search.ts index 24d83fb9..1f7dcc69 100644 --- a/ccw/src/tools/smart-search.ts +++ b/ccw/src/tools/smart-search.ts @@ -1,21 +1,20 @@ /** - * Smart Search Tool - Unified intelligent search with CodexLens integration + * Smart Search Tool - Unified intelligent search powered by codexlens-search v2 * * Features: - * - Fuzzy mode: FTS + ripgrep fusion with RRF ranking (default) - * - Semantic mode: Dense coarse retrieval + cross-encoder reranking - * - CodexLens integration (init, dense_rerank, fts) - * - Ripgrep fallback for exact mode - * - Index status checking and warnings - * - Multi-backend search routing with RRF ranking + * - Semantic search: 2-stage vector (binary coarse + ANN fine) + FTS5 + RRF fusion + reranking + * - Ripgrep fallback for fast exact/regex matching + * - File discovery via glob patterns + * - Incremental indexing with Mark-and-Filter strategy + * - File watcher for automatic index updates * * Actions: - * - init: Initialize CodexLens static index - * - embed: Generate semantic/vector embeddings for the index - * - search: Intelligent search with fuzzy (default) or semantic mode - * - status: Check index status - * - update: Incremental index update for changed files + * - search: Semantic search via v2 bridge with ripgrep fallback + * - init: Initialize v2 index and sync files + * - status: Check v2 index statistics + * - update: Incremental sync for changed files * - watch: Start file watcher for automatic updates + * - find_files: Glob-based file path matching */ import { z } from 'zod'; @@ -29,7 +28,6 @@ import { ensureLiteLLMEmbedderReady, executeCodexLens, getVenvPythonPath, - useCodexLensV2, } from './codex-lens.js'; import { execFile } from 'child_process'; import type { ProgressInfo } from './codex-lens.js'; @@ -76,10 +74,9 @@ function createTimer(): { mark: (name: string) => void; getTimings: () => Timing // Define Zod schema for validation const ParamsSchema = z.object({ - // Action: search (content), find_files (path/name pattern), init, init_force, embed, status, update (incremental), watch + // Action: search (content), find_files (path/name pattern), init, status, update (incremental sync), watch // Note: search_files is deprecated, use search with output_mode='files_only' - // init: static FTS index by default, embed: generate semantic/vector embeddings, init_force: force full rebuild (delete and recreate) - action: z.enum(['init', 'init_force', 'embed', 'search', 'search_files', 'find_files', 'status', 'update', 'watch']).default('search'), + action: z.enum(['init', 'search', 'search_files', 'find_files', 'status', 'update', 'watch']).default('search'), query: z.string().optional().describe('Content search query (for action="search")'), pattern: z.string().optional().describe('Glob pattern for path matching (for action="find_files")'), mode: z.enum(['fuzzy', 'semantic']).default('fuzzy'), @@ -89,16 +86,11 @@ const ParamsSchema = z.object({ contextLines: z.number().default(0), maxResults: z.number().default(5), // Default 5 with full content includeHidden: z.boolean().default(false), - languages: z.array(z.string()).optional(), - embeddingBackend: z.string().optional().describe('Embedding backend for action="embed": fastembed/local or litellm/api. Default bulk preset: local-fast.'), - embeddingModel: z.string().optional().describe('Embedding model/profile for action="embed". Examples: "code", "fast", "qwen3-embedding-sf". Default bulk preset uses "fast".'), - apiMaxWorkers: z.number().int().min(1).optional().describe('Max concurrent API embedding workers for action="embed". Recommended: 8-16 for litellm/api when multiple endpoints are configured.'), - force: z.boolean().default(false).describe('Force regeneration for action="embed".'), + force: z.boolean().default(false).describe('Force full rebuild for action="init".'), limit: z.number().default(5), // Default 5 with full content extraFilesCount: z.number().default(10), // Additional file-only results maxContentLength: z.number().default(200), // Max content length for truncation (50-2000) offset: z.number().default(0), // NEW: Pagination offset (start_index) - enrich: z.boolean().default(false), // Search modifiers for ripgrep mode regex: z.boolean().default(true), // Use regex pattern matching (default: enabled) caseSensitive: z.boolean().default(true), // Case sensitivity (default: case-sensitive) @@ -1966,616 +1958,17 @@ async function maybeStartBackgroundAutoEmbed( }; } -async function executeEmbeddingsViaPython(params: { - projectPath: string; - backend?: string; - model?: string; - force: boolean; - maxWorkers?: number; - endpoints?: RotationEndpointConfig[]; -}): Promise<{ success: boolean; error?: string; progressMessages?: string[] }> { - const { projectPath } = params; - const pythonCode = buildEmbeddingPythonCode(params); +// v1 executeEmbeddingsViaPython removed — v2 uses built-in fastembed models - return await new Promise((resolve) => { - const child = getSpawnRuntime()( - getVenvPythonPathRuntime()(), - ['-c', pythonCode], - buildSmartSearchSpawnOptions(projectPath, { - timeout: 1800000, - }), - ); +// v1 executeInitAction removed — replaced by executeInitActionV2 - let stdout = ''; - let stderr = ''; - const progressMessages: string[] = []; +// v1 executeEmbedAction removed — v2 auto-embeds during sync - child.stdout?.on('data', (data: Buffer) => { - const chunk = data.toString(); - stdout += chunk; - for (const line of chunk.split(/\r?\n/)) { - if (line.startsWith(EMBED_PROGRESS_PREFIX)) { - progressMessages.push(line.slice(EMBED_PROGRESS_PREFIX.length).trim()); - } - } - }); +// v1 executeStatusAction removed — replaced by executeStatusActionV2 - child.stderr?.on('data', (data: Buffer) => { - stderr += data.toString(); - }); +// v1 executeUpdateAction and executeWatchAction removed — replaced by V2 versions - child.on('error', (err) => { - resolve({ success: false, error: `Failed to start embeddings process: ${err.message}`, progressMessages }); - }); - - child.on('close', (code) => { - const jsonLine = extractEmbedJsonLine(stdout); - if (jsonLine) { - try { - const parsed = JSON.parse(jsonLine) as { success?: boolean; error?: string }; - if (parsed.success) { - resolve({ success: true, progressMessages }); - return; - } - resolve({ - success: false, - error: parsed.error || stderr.trim() || stdout.trim() || `Embeddings process exited with code ${code}`, - progressMessages, - }); - return; - } catch { - // Fall through to generic error handling below. - } - } - - resolve({ - success: code === 0, - error: code === 0 ? undefined : (stderr.trim() || stdout.trim() || `Embeddings process exited with code ${code}`), - progressMessages, - }); - }); - }); -} - -/** - * Action: init - Initialize CodexLens index (FTS only, no embeddings) - * For semantic/vector search, follow with action="embed" to generate vectors. - * @param params - Search parameters - * @param force - If true, force full rebuild (delete existing index first) - */ -async function executeInitAction(params: Params, force: boolean = false): Promise { - const { path = '.', languages } = params; - const scope = resolveSearchScope(path); - - // Check CodexLens availability - const readyStatus = await ensureCodexLensReady(); - if (!readyStatus.ready) { - return { - success: false, - error: `CodexLens not available: ${readyStatus.error}. CodexLens will be auto-installed on first use.`, - }; - } - - // Build args with --no-embeddings for FTS-only index (faster) - // Use 'index init' subcommand (new CLI structure) - const args = buildIndexInitArgs(scope.workingDirectory, { force, languages }); - - // Track progress updates - const progressUpdates: ProgressInfo[] = []; - let lastProgress: ProgressInfo | null = null; - - const result = await executeCodexLens(args, { - cwd: scope.workingDirectory, - timeout: 1800000, // 30 minutes for large codebases - onProgress: (progress: ProgressInfo) => { - progressUpdates.push(progress); - lastProgress = progress; - }, - }); - - // Build metadata with progress info - const metadata: SearchMetadata = { - action: force ? 'init_force' : 'init', - path: scope.workingDirectory, - }; - - if (lastProgress !== null) { - const p = lastProgress as ProgressInfo; - metadata.progress = { - stage: p.stage, - message: p.message, - percent: p.percent, - filesProcessed: p.filesProcessed, - totalFiles: p.totalFiles, - }; - } - - if (progressUpdates.length > 0) { - metadata.progressHistory = progressUpdates.slice(-5); // Keep last 5 progress updates - } - - const actionLabel = force ? 'rebuilt (force)' : 'created'; - const successMessage = result.success - ? `FTS index ${actionLabel} for ${path}. Note: For semantic/vector search, create vector index via "ccw view" dashboard or run "codexlens init ${path}" (without --no-embeddings).` - : undefined; - - return { - success: result.success, - error: result.error, - message: successMessage, - metadata, - }; -} - -/** - * Action: embed - Generate semantic/vector embeddings for an indexed project - */ -async function executeEmbedAction(params: Params): Promise { - const { path = '.', embeddingBackend, embeddingModel, apiMaxWorkers, force = false } = params; - const scope = resolveSearchScope(path); - - const readyStatus = await ensureCodexLensReady(); - if (!readyStatus.ready) { - return { - success: false, - error: `CodexLens not available: ${readyStatus.error}. CodexLens will be auto-installed on first use.`, - }; - } - - const currentStatus = await checkIndexStatus(scope.workingDirectory); - const embeddingSelection = resolveEmbeddingSelection(embeddingBackend, embeddingModel, currentStatus.config); - const normalizedBackend = embeddingSelection.backend; - const trimmedModel = embeddingSelection.model; - const endpoints = resolveEmbeddingEndpoints(normalizedBackend); - const configuredApiMaxWorkers = currentStatus.config?.api_max_workers; - const effectiveApiMaxWorkers = typeof apiMaxWorkers === 'number' - ? Math.max(1, Math.floor(apiMaxWorkers)) - : (typeof configuredApiMaxWorkers === 'number' - ? Math.max(1, Math.floor(configuredApiMaxWorkers)) - : resolveApiWorkerCount(undefined, normalizedBackend, endpoints)); - - if (normalizedBackend === 'litellm') { - const embedderReady = await ensureLiteLLMEmbedderReady(); - if (!embedderReady.success) { - return { - success: false, - error: embedderReady.error || 'LiteLLM embedder is not ready.', - }; - } - } - - const result = await executeEmbeddingsViaPython({ - projectPath: scope.workingDirectory, - backend: normalizedBackend, - model: trimmedModel, - force, - maxWorkers: effectiveApiMaxWorkers, - endpoints, - }); - - const indexStatus = result.success ? await checkIndexStatus(scope.workingDirectory) : currentStatus; - const coverage = indexStatus?.embeddings_coverage_percent; - const coverageText = coverage !== undefined ? ` (${coverage.toFixed(1)}% coverage)` : ''; - const progressMessage = result.progressMessages && result.progressMessages.length > 0 - ? result.progressMessages[result.progressMessages.length - 1] - : undefined; - - return { - success: result.success, - error: result.error, - message: result.success - ? `Embeddings generated for ${path}${coverageText}` - : undefined, - metadata: { - action: 'embed', - path: scope.workingDirectory, - backend: normalizedBackend || indexStatus?.config?.embedding_backend, - embeddings_coverage_percent: coverage, - api_max_workers: normalizedBackend === 'litellm' ? effectiveApiMaxWorkers : undefined, - endpoint_count: endpoints.length, - use_gpu: true, - reranker_enabled: currentStatus.config?.reranker_enabled, - reranker_backend: currentStatus.config?.reranker_backend, - reranker_model: currentStatus.config?.reranker_model, - cascade_strategy: currentStatus.config?.cascade_strategy, - staged_stage2_mode: currentStatus.config?.staged_stage2_mode, - static_graph_enabled: currentStatus.config?.static_graph_enabled, - note: [embeddingSelection.note, progressMessage].filter(Boolean).join(' | ') || undefined, - preset: embeddingSelection.preset, - }, - status: indexStatus, - }; -} - -/** - * Action: status - Check CodexLens index status - */ -async function executeStatusAction(params: Params): Promise { - const { path = '.' } = params; - const scope = resolveSearchScope(path); - - const indexStatus = await checkIndexStatus(scope.workingDirectory); - - // Build detailed status message - const statusParts: string[] = []; - - // Index status - statusParts.push(`Index: ${indexStatus.indexed ? 'indexed' : 'not indexed'}`); - if (indexStatus.file_count) { - statusParts.push(`Files: ${indexStatus.file_count}`); - } - - // Embeddings status - if (indexStatus.embeddings_coverage_percent !== undefined) { - statusParts.push(`Embeddings: ${indexStatus.embeddings_coverage_percent.toFixed(1)}%`); - } - if (indexStatus.total_chunks) { - statusParts.push(`Chunks: ${indexStatus.total_chunks}`); - } - - // Config summary - if (indexStatus.config) { - const cfg = indexStatus.config; - // Embedding backend info - const embeddingType = cfg.embedding_backend === 'litellm' ? 'API' : 'Local'; - statusParts.push(`Embedding: ${embeddingType} (${cfg.embedding_model || 'default'})`); - statusParts.push(`Auto Embed Missing: ${isAutoEmbedMissingEnabled(cfg) ? 'on' : 'off'}`); - if (typeof cfg.api_max_workers === 'number') { - statusParts.push(`API Workers: ${cfg.api_max_workers}`); - } - if (cfg.cascade_strategy) { - statusParts.push(`Cascade: ${cfg.cascade_strategy}`); - } - if (cfg.staged_stage2_mode) { - statusParts.push(`Stage2: ${cfg.staged_stage2_mode}`); - } - if (typeof cfg.static_graph_enabled === 'boolean') { - statusParts.push(`Static Graph: ${cfg.static_graph_enabled ? 'on' : 'off'}`); - } - - // Reranker info - if (cfg.reranker_enabled) { - const rerankerType = cfg.reranker_backend === 'onnx' ? 'Local' : 'API'; - statusParts.push(`Reranker: ${rerankerType} (${cfg.reranker_model || 'default'})`); - } else { - statusParts.push('Reranker: disabled'); - } - } - - return { - success: true, - status: indexStatus, - message: indexStatus.warning || statusParts.join(' | '), - metadata: { - action: 'status', - path: scope.workingDirectory, - warning: indexStatus.warning, - reranker_enabled: indexStatus.config?.reranker_enabled, - reranker_backend: indexStatus.config?.reranker_backend, - reranker_model: indexStatus.config?.reranker_model, - cascade_strategy: indexStatus.config?.cascade_strategy, - staged_stage2_mode: indexStatus.config?.staged_stage2_mode, - static_graph_enabled: indexStatus.config?.static_graph_enabled, - suggestions: buildIndexSuggestions(indexStatus, scope), - }, - }; -} - -/** - * Action: update - Incremental index update - * Updates index for changed files without full rebuild - */ -async function executeUpdateAction(params: Params): Promise { - const { path = '.', languages } = params; - const scope = resolveSearchScope(path); - - // Check CodexLens availability - const readyStatus = await ensureCodexLensReady(); - if (!readyStatus.ready) { - return { - success: false, - error: `CodexLens not available: ${readyStatus.error}`, - }; - } - - // Check if index exists first - const indexStatus = await checkIndexStatus(scope.workingDirectory); - if (!indexStatus.indexed) { - return { - success: false, - error: `Directory not indexed. Run smart_search(action="init") first.`, - }; - } - - // Build args for incremental init (without --force) - // Use 'index init' subcommand (new CLI structure) - const args = ['index', 'init', scope.workingDirectory]; - if (languages && languages.length > 0) { - args.push(...languages.flatMap((language) => ['--language', language])); - } - - // Track progress updates - const progressUpdates: ProgressInfo[] = []; - let lastProgress: ProgressInfo | null = null; - - const result = await executeCodexLens(args, { - cwd: scope.workingDirectory, - timeout: 600000, // 10 minutes for incremental updates - onProgress: (progress: ProgressInfo) => { - progressUpdates.push(progress); - lastProgress = progress; - }, - }); - - // Build metadata with progress info - const metadata: SearchMetadata = { - action: 'update', - path, - }; - - if (lastProgress !== null) { - const p = lastProgress as ProgressInfo; - metadata.progress = { - stage: p.stage, - message: p.message, - percent: p.percent, - filesProcessed: p.filesProcessed, - totalFiles: p.totalFiles, - }; - } - - if (progressUpdates.length > 0) { - metadata.progressHistory = progressUpdates.slice(-5); - } - - return { - success: result.success, - error: result.error, - message: result.success - ? `Incremental update completed for ${path}` - : undefined, - metadata, - }; -} - -/** - * Action: watch - Start file watcher for automatic incremental updates - * Note: This starts a background process, returns immediately with status - */ -async function executeWatchAction(params: Params): Promise { - const { path = '.', languages, debounce = 1000 } = params; - const scope = resolveSearchScope(path); - - // Check CodexLens availability - const readyStatus = await ensureCodexLensReady(); - if (!readyStatus.ready) { - return { - success: false, - error: `CodexLens not available: ${readyStatus.error}`, - }; - } - - // Check if index exists first - const indexStatus = await checkIndexStatus(scope.workingDirectory); - if (!indexStatus.indexed) { - return { - success: false, - error: `Directory not indexed. Run smart_search(action="init") first.`, - }; - } - - // Build args for watch command - const args = ['watch', scope.workingDirectory, '--debounce', debounce.toString()]; - if (languages && languages.length > 0) { - args.push(...languages.flatMap((language) => ['--language', language])); - } - - // Start watcher in background (non-blocking) - // Note: The watcher runs until manually stopped - const result = await executeCodexLens(args, { - cwd: scope.workingDirectory, - timeout: 5000, // Short timeout for initial startup check - }); - - return { - success: true, - message: `File watcher started for ${path}. Use Ctrl+C or kill the process to stop.`, - metadata: { - action: 'watch', - path, - note: 'Watcher runs in background. Changes are indexed automatically with debounce.', - }, - }; -} - -/** - * Mode: fuzzy - FTS + ripgrep fusion with RRF ranking - * Runs both exact (FTS) and ripgrep searches in parallel, merges and ranks results - */ -async function executeFuzzyMode(params: Params): Promise { - const { query, path = '.', maxResults = 5, extraFilesCount = 10, codeOnly = true, withDoc = false, excludeExtensions, regex = true, tokenize = true } = params; - // withDoc overrides codeOnly - const effectiveCodeOnly = withDoc ? false : codeOnly; - - if (!query) { - return { - success: false, - error: 'Query is required for search', - }; - } - - const timer = createTimer(); - const ftsWasBroken = codexLensFtsBackendBroken; - const ripgrepQueryMode = resolveRipgrepQueryMode(query, regex, tokenize); - const fuzzyWarnings: string[] = []; - const skipExactDueToCompatibility = ftsWasBroken && !ripgrepQueryMode.literalFallback; - - let skipExactReason: string | undefined; - if (ripgrepQueryMode.literalFallback) { - skipExactReason = 'Skipped CodexLens FTS backend for a literal code-pattern query; using ripgrep literal matching.'; - } else if (codexLensFtsBackendBroken) { - skipExactReason = 'CodexLens FTS backend disabled for this process due to CLI compatibility errors.'; - } - - // Run both searches in parallel - const [ftsResult, ripgrepResult] = await Promise.allSettled([ - skipExactReason - ? Promise.resolve({ success: false, error: skipExactReason }) - : executeCodexLensExactMode(params), - executeRipgrepMode(params), - ]); - timer.mark('parallel_search'); - - if (skipExactReason && !skipExactDueToCompatibility) { - fuzzyWarnings.push(skipExactReason); - } - if (ripgrepResult.status === 'fulfilled' && ripgrepResult.value.metadata?.warning) { - fuzzyWarnings.push(String(ripgrepResult.value.metadata.warning)); - } - const mergedSuggestions = mergeSuggestions( - ftsResult.status === 'fulfilled' ? ftsResult.value.metadata?.suggestions : undefined, - ripgrepResult.status === 'fulfilled' ? ripgrepResult.value.metadata?.suggestions : undefined, - ); - - // Collect results from both sources - const resultsMap = new Map(); - - // Add FTS results if successful - if (ftsResult.status === 'fulfilled' && ftsResult.value.success && ftsResult.value.results) { - resultsMap.set('exact', ftsResult.value.results as any[]); - } - - // Add ripgrep results if successful - if (ripgrepResult.status === 'fulfilled' && ripgrepResult.value.success && ripgrepResult.value.results) { - resultsMap.set('ripgrep', ripgrepResult.value.results as any[]); - } - - const ripgrepResultCount = (resultsMap.get('ripgrep') ?? []).length; - const compatibilityTriggeredThisQuery = !skipExactReason && !ftsWasBroken && codexLensFtsBackendBroken; - if (shouldSurfaceCodexLensFtsCompatibilityWarning({ - compatibilityTriggeredThisQuery, - skipExactDueToCompatibility, - ripgrepResultCount, - })) { - fuzzyWarnings.push('CodexLens FTS backend is incompatible with the current CLI runtime. Falling back to ripgrep results.'); - } - - // If both failed, return error - if (resultsMap.size === 0) { - const errors: string[] = []; - collectBackendError(errors, 'FTS', ftsResult); - collectBackendError(errors, 'Ripgrep', ripgrepResult); - return { - success: false, - error: `Both search backends failed: ${errors.join('; ') || 'unknown error'}`, - }; - } - - // Apply RRF fusion with fuzzy-optimized weights - // Fuzzy mode: balanced between exact and ripgrep - const fusionWeights = { exact: 0.5, ripgrep: 0.5 }; - const totalToFetch = maxResults + extraFilesCount; - const fusedResults = applyRRFFusion(resultsMap, fusionWeights, totalToFetch); - timer.mark('rrf_fusion'); - - // Apply code-only and extension filtering after fusion - const filteredFusedResults = filterNoisyFiles(fusedResults as any[], { codeOnly: effectiveCodeOnly, excludeExtensions }); - - // Normalize results format - const normalizedResults = filteredFusedResults.map((item: any) => ({ - file: item.file || item.path, - line: item.line || 0, - endLine: item.endLine || item.line || 0, - column: item.column || 0, - content: item.content || '', - chunkLines: Array.isArray(item.chunkLines) ? item.chunkLines : undefined, - score: item.fusion_score || 0, - matchCount: item.matchCount, - matchScore: item.matchScore, - })); - - // Split results: first N with full content, rest as file paths only - const { results, extra_files } = splitResultsWithExtraFiles(normalizedResults, maxResults, extraFilesCount); - - // Log timing - timer.log(); - const timings = timer.getTimings(); - const usingExactResults = resultsMap.has('exact'); - - return { - success: true, - results, - extra_files: extra_files.length > 0 ? extra_files : undefined, - metadata: { - mode: 'fuzzy', - backend: usingExactResults ? 'fts+ripgrep' : 'ripgrep', - count: results.length, - query, - note: usingExactResults - ? `Fuzzy search using RRF fusion of FTS and ripgrep (weights: exact=${fusionWeights.exact}, ripgrep=${fusionWeights.ripgrep})` - : 'Fuzzy search resolved using ripgrep only.', - warning: mergeWarnings(...fuzzyWarnings), - suggestions: mergedSuggestions, - timing: TIMING_ENABLED ? timings : undefined, - }, - }; -} - -/** - * Mode: auto - Intent classification and mode selection - * Routes to: hybrid (NL + index) | exact (index) | ripgrep (no index) - */ -async function executeAutoMode(params: Params): Promise { - const { query, path = '.' } = params; - const scope = resolveSearchScope(path); - - if (!query) { - return { - success: false, - error: 'Query is required for search action', - }; - } - - // Check index status - const indexStatus = await checkIndexStatus(scope.workingDirectory); - - // Classify intent with index and embeddings awareness - const classification = classifyIntent( - query, - indexStatus.indexed, - indexStatus.has_embeddings // This now considers 50% threshold - ); - - // Route to appropriate mode based on classification - let result: SearchResult; - - switch (classification.mode) { - case 'hybrid': - result = await executeHybridMode(params); - break; - - case 'exact': - result = await executeCodexLensExactMode(params); - break; - - case 'ripgrep': - result = await executeRipgrepMode(params); - break; - - default: - // Fallback to ripgrep - result = await executeRipgrepMode(params); - break; - } - - // Add classification metadata - result.metadata = enrichMetadataWithIndexStatus(result.metadata, indexStatus, scope); - result.metadata.classified_as = classification.mode; - result.metadata.confidence = classification.confidence; - result.metadata.reasoning = classification.reasoning; - - return result; -} +// v1 executeFuzzyMode and executeAutoMode removed — v2 bridge handles all search /** * Mode: ripgrep - Fast literal string matching using ripgrep @@ -2829,21 +2222,54 @@ async function executeCodexLensV2Bridge( return; } - // Bridge outputs array of {path, score, snippet} - const results: SemanticMatch[] = (Array.isArray(parsed) ? parsed : []).map((r: { path?: string; score?: number; snippet?: string }) => ({ - file: r.path || '', + // Bridge outputs array of {path, score, line, end_line, snippet, content} + const raw: Array<{ + path?: string; score?: number; line?: number; + end_line?: number; snippet?: string; content?: string; + }> = Array.isArray(parsed) ? parsed : []; + + // Build AceLike sections and group by file + const sections: AceLikeSection[] = raw.map(r => ({ + path: r.path || '', + line: r.line || undefined, + endLine: r.end_line || undefined, score: r.score || 0, - content: r.snippet || '', symbol: null, + snippet: r.content || r.snippet || '', })); + const groupMap = new Map(); + for (const s of sections) { + const arr = groupMap.get(s.path) || []; + arr.push(s); + groupMap.set(s.path, arr); + } + const groups: AceLikeGroup[] = Array.from(groupMap.entries()).map( + ([path, secs]) => ({ path, sections: secs, total_matches: secs.length }) + ); + + // Render text view with line numbers + const textParts: string[] = []; + for (const s of sections) { + const lineInfo = s.line ? `:${s.line}${s.endLine ? `-${s.endLine}` : ''}` : ''; + textParts.push(`Path: ${s.path}${lineInfo}\n${s.snippet}\n`); + } + + const aceLikeOutput: AceLikeOutput = { + format: 'ace', + text: textParts.join('\n'), + groups, + sections, + total: sections.length, + }; + resolve({ success: true, - results, + results: aceLikeOutput, metadata: { mode: 'semantic' as any, backend: 'codexlens-v2', - count: results.length, + count: sections.length, query, note: 'Using codexlens-search v2 bridge (2-stage vector + reranking)', }, @@ -2861,304 +2287,125 @@ async function executeCodexLensV2Bridge( } /** - * Mode: exact - CodexLens exact/FTS search - * Requires index + * Execute a generic codexlens-search v2 bridge subcommand (init, status, sync, watch, etc.). + * Returns parsed JSON output from the bridge CLI. */ -async function executeCodexLensExactMode(params: Params): Promise { - const { query, path = '.', maxResults = 5, extraFilesCount = 10, maxContentLength = 200, enrich = false, excludeExtensions, codeOnly = true, withDoc = false, offset = 0 } = params; - const scope = resolveSearchScope(path); - // withDoc overrides codeOnly - const effectiveCodeOnly = withDoc ? false : codeOnly; - - if (!query) { - return { - success: false, - error: 'Query is required for search', - }; - } - - // Check CodexLens availability - const readyStatus = await ensureCodexLensReady(); - if (!readyStatus.ready) { - return { - success: false, - error: `CodexLens not available: ${readyStatus.error}`, - }; - } - - // Check index status - const indexStatus = await checkIndexStatus(scope.workingDirectory); - - // Request more results to support split (full content + extra files) - const totalToFetch = maxResults + extraFilesCount; - const args = ['search', query, '--limit', totalToFetch.toString(), '--offset', offset.toString(), '--method', 'fts', '--json']; - if (enrich) { - args.push('--enrich'); - } - // Add code_only filter if requested (default: true) - if (effectiveCodeOnly) { - args.push('--code-only'); - } - // Add exclude_extensions filter if provided - if (excludeExtensions && excludeExtensions.length > 0) { - args.push('--exclude-extensions', excludeExtensions.join(',')); - } - const result = await executeCodexLens(args, { cwd: scope.workingDirectory }); - - if (!result.success) { - noteCodexLensFtsCompatibility(result.error); - return { - success: false, - error: summarizeBackendError(result.error), - metadata: { - mode: 'exact', - backend: 'codexlens', - count: 0, - query, - warning: mergeWarnings(indexStatus.warning, result.warning), - suggestions: buildIndexSuggestions(indexStatus, scope), - }, - }; - } - - // Parse results - let allResults: SemanticMatch[] = []; - try { - const parsed = JSON.parse(stripAnsi(result.output || '{}')); - const data = parsed.result?.results || parsed.results || parsed; - allResults = (Array.isArray(data) ? data : []).map((item: any) => ({ - file: item.path || item.file, - score: item.score || 0, - content: truncateContent(item.content || item.excerpt, maxContentLength), - symbol: item.symbol || null, - })); - } catch { - // Keep empty results - } - - allResults = filterResultsToTargetFile(allResults, scope); - - // Fallback to fuzzy mode if exact returns no results - if (allResults.length === 0) { - const fuzzyArgs = ['search', query, '--limit', totalToFetch.toString(), '--offset', offset.toString(), '--method', 'fts', '--use-fuzzy', '--json']; - if (enrich) { - fuzzyArgs.push('--enrich'); - } - // Add code_only filter if requested (default: true) - if (effectiveCodeOnly) { - fuzzyArgs.push('--code-only'); - } - // Add exclude_extensions filter if provided - if (excludeExtensions && excludeExtensions.length > 0) { - fuzzyArgs.push('--exclude-extensions', excludeExtensions.join(',')); - } - const fuzzyResult = await executeCodexLens(fuzzyArgs, { cwd: scope.workingDirectory }); - - if (fuzzyResult.success) { +async function executeV2BridgeCommand( + subcommand: string, + args: string[], + options?: { timeout?: number }, +): Promise { + return new Promise((resolve) => { + const fullArgs = [subcommand, ...args]; + execFile('codexlens-search', fullArgs, { + encoding: 'utf-8', + timeout: options?.timeout ?? EXEC_TIMEOUTS.PROCESS_SPAWN, + windowsHide: true, + env: { ...process.env, PYTHONIOENCODING: 'utf-8' }, + }, (error, stdout, stderr) => { + if (error) { + resolve({ + success: false, + error: `codexlens-search ${subcommand} failed: ${error.message}`, + }); + return; + } try { - const parsed = JSON.parse(stripAnsi(fuzzyResult.output || '{}')); - const data = parsed.result?.results || parsed.results || parsed; - allResults = filterResultsToTargetFile((Array.isArray(data) ? data : []).map((item: any) => ({ - file: item.path || item.file, - score: item.score || 0, - content: truncateContent(item.content || item.excerpt, maxContentLength), - symbol: item.symbol || null, - })), scope); + const parsed = JSON.parse(stdout.trim()); + if (parsed && typeof parsed === 'object' && 'error' in parsed) { + resolve({ success: false, error: `codexlens-search: ${parsed.error}` }); + return; + } + resolve({ success: true, status: parsed, message: parsed.status || `${subcommand} completed`, metadata: { action: subcommand } }); } catch { - // Keep empty results + resolve({ success: false, error: `Failed to parse codexlens-search ${subcommand} output`, output: stdout }); } + }); + }); +} - if (allResults.length > 0) { - // Split results: first N with full content, rest as file paths only - const { results, extra_files } = splitResultsWithExtraFiles(allResults, maxResults, extraFilesCount); - return { - success: true, - results, - extra_files: extra_files.length > 0 ? extra_files : undefined, - metadata: { - mode: 'exact', - backend: 'codexlens', - count: results.length, - query, - warning: mergeWarnings(indexStatus.warning, fuzzyResult.warning), - note: 'No exact matches found, showing fuzzy results', - fallback: 'fuzzy', - suggestions: buildIndexSuggestions(indexStatus, scope), - }, - }; - } - } - } +/** + * Action: init (v2) - Initialize index and sync files. + */ +async function executeInitActionV2(params: Params): Promise { + const { path = '.' } = params; + const scope = resolveSearchScope(path); + const dbPath = join(scope.workingDirectory, '.codexlens'); - // Split results: first N with full content, rest as file paths only - const { results, extra_files } = splitResultsWithExtraFiles(allResults, maxResults, extraFilesCount); + // Step 1: init empty index + const initResult = await executeV2BridgeCommand('init', ['--db-path', dbPath]); + if (!initResult.success) return initResult; + + // Step 2: sync all files + const syncResult = await executeV2BridgeCommand('sync', [ + '--root', scope.workingDirectory, + '--db-path', dbPath, + ], { timeout: 1800000 }); // 30 min for large codebases return { - success: true, - results, - extra_files: extra_files.length > 0 ? extra_files : undefined, - metadata: { - mode: 'exact', - backend: 'codexlens', - count: results.length, - query, - warning: mergeWarnings(indexStatus.warning, result.warning), - suggestions: buildIndexSuggestions(indexStatus, scope), - }, + success: syncResult.success, + error: syncResult.error, + message: syncResult.success + ? `Index initialized and synced for ${scope.workingDirectory}` + : undefined, + metadata: { action: 'init', path: scope.workingDirectory }, + status: syncResult.status, }; } /** - * Mode: hybrid - Best quality semantic search - * Uses CodexLens dense_rerank method (dense coarse + cross-encoder rerank) - * Requires index with embeddings + * Action: status (v2) - Report index statistics. */ -async function executeHybridMode(params: Params): Promise { - const timer = createTimer(); - const { query, path = '.', maxResults = 5, extraFilesCount = 10, maxContentLength = 200, enrich = false, excludeExtensions, codeOnly = true, withDoc = false, offset = 0 } = params; +async function executeStatusActionV2(params: Params): Promise { + const { path = '.' } = params; const scope = resolveSearchScope(path); - // withDoc overrides codeOnly - const effectiveCodeOnly = withDoc ? false : codeOnly; + const dbPath = join(scope.workingDirectory, '.codexlens'); - if (!query) { - return { - success: false, - error: 'Query is required for search', - }; - } + return executeV2BridgeCommand('status', ['--db-path', dbPath]); +} - // Check CodexLens availability - const readyStatus = await ensureCodexLensReady(); - timer.mark('codexlens_ready_check'); - if (!readyStatus.ready) { - return { - success: false, - error: `CodexLens not available: ${readyStatus.error}`, - }; - } +/** + * Action: update (v2) - Incremental sync (re-sync changed files). + */ +async function executeUpdateActionV2(params: Params): Promise { + const { path = '.' } = params; + const scope = resolveSearchScope(path); + const dbPath = join(scope.workingDirectory, '.codexlens'); - // Check index status - const indexStatus = await checkIndexStatus(scope.workingDirectory); - timer.mark('index_status_check'); + return executeV2BridgeCommand('sync', [ + '--root', scope.workingDirectory, + '--db-path', dbPath, + ], { timeout: 600000 }); // 10 min +} - // Request more results to support split (full content + extra files) - // NOTE: Current CodexLens search CLI in this environment rejects value-taking options - // like --limit/--offset/--method for search. Keep the invocation minimal and apply - // pagination/selection in CCW after parsing results. - const totalToFetch = maxResults + extraFilesCount; - const args = ['search', query, '--json']; - if (enrich) { - args.push('--enrich'); - } - // Add code_only filter if requested (default: true) - if (effectiveCodeOnly) { - args.push('--code-only'); - } - // Add exclude_extensions filter if provided - if (excludeExtensions && excludeExtensions.length > 0) { - args.push('--exclude-extensions', excludeExtensions.join(',')); - } - const result = await executeCodexLens(args, { cwd: scope.workingDirectory }); - timer.mark('codexlens_search'); +/** + * Action: watch (v2) - Start file watcher for auto-updates. + */ +async function executeWatchActionV2(params: Params): Promise { + const { path = '.', debounce = 1000 } = params; + const scope = resolveSearchScope(path); + const dbPath = join(scope.workingDirectory, '.codexlens'); - if (!result.success) { - timer.log(); - return { - success: false, - error: result.error, - metadata: { - mode: 'hybrid', - backend: 'codexlens', - count: 0, - query, - warning: mergeWarnings(indexStatus.warning, result.warning), - suggestions: buildIndexSuggestions(indexStatus, scope), - }, - }; - } - - // Parse results - let allResults: SemanticMatch[] = []; - let baselineInfo: { score: number; count: number } | null = null; - let initialCount = 0; - - const parsedOutput = parseCodexLensJsonOutput(result.output); - const parsedData = parsedOutput?.result?.results || parsedOutput?.results || parsedOutput; - if (Array.isArray(parsedData)) { - allResults = mapCodexLensSemanticMatches(parsedData, scope, maxContentLength); - timer.mark('parse_results'); - - initialCount = allResults.length; - - // Post-processing pipeline to improve semantic search quality - // 0. Filter dominant baseline scores (hot spot detection) - const baselineResult = filterDominantBaselineScores(allResults); - allResults = baselineResult.filteredResults; - baselineInfo = baselineResult.baselineInfo; - - // 1. Filter noisy directories (node_modules, etc.) - // NOTE: Extension filtering is now done engine-side via --code-only and --exclude-extensions - allResults = filterNoisyFiles(allResults, {}); - // 2. Boost results containing query keywords - allResults = applyKeywordBoosting(allResults, query); - // 3. Enforce score diversity (penalize identical scores) - allResults = enforceScoreDiversity(allResults); - // 4. Re-sort by adjusted scores - allResults.sort((a, b) => b.score - a.score); - timer.mark('post_processing'); - } else { - allResults = parsePlainTextFileMatches(result.output, scope); - if (allResults.length === 0) { - return { - success: true, - results: [], - output: result.output, - metadata: { - mode: 'hybrid', - backend: 'codexlens', - count: 0, - query, - warning: mergeWarnings(indexStatus.warning, result.warning, 'Failed to parse JSON output'), - suggestions: buildIndexSuggestions(indexStatus, scope), - }, - }; - } - timer.mark('parse_results'); - initialCount = allResults.length; - } - - // Split results: first N with full content, rest as file paths only - const { results, extra_files } = splitResultsWithExtraFiles(allResults, maxResults, extraFilesCount); - timer.mark('split_results'); - - // Build metadata with baseline info if detected - let note = 'Using dense_rerank (dense coarse + cross-encoder rerank) for semantic search'; - if (baselineInfo) { - note += ` | Filtered ${initialCount - allResults.length} hot-spot results with baseline score ~${baselineInfo.score.toFixed(4)}`; - } - - // Log timing data - timer.log(); - const timings = timer.getTimings(); + // Watch runs indefinitely — start it with a short initial timeout to confirm startup + const result = await executeV2BridgeCommand('watch', [ + '--root', scope.workingDirectory, + '--db-path', dbPath, + '--debounce-ms', debounce.toString(), + ], { timeout: 5000 }); return { success: true, - results, - extra_files: extra_files.length > 0 ? extra_files : undefined, - metadata: { - mode: 'hybrid', - backend: 'codexlens', - count: results.length, - query, - note, - warning: mergeWarnings(indexStatus.warning, result.warning), - suggestions: buildIndexSuggestions(indexStatus, scope), - suggested_weights: getRRFWeights(query), - timing: TIMING_ENABLED ? timings : undefined, - }, + message: `File watcher started for ${scope.workingDirectory}. Changes are indexed automatically.`, + metadata: { action: 'watch', path: scope.workingDirectory }, + status: result.status, }; } +// v1 executeCodexLensExactMode removed — v2 bridge handles search + +// v1 executeHybridMode removed — v2 bridge handles semantic search +// v1 executeHybridMode removed — v2 bridge handles semantic search + /** * Query intent used to adapt RRF weights (Python parity). * @@ -3579,116 +2826,23 @@ function withTimeout(promise: Promise, ms: number, modeName: string): Prom }); } -/** - * Mode: priority - Fallback search strategy: hybrid -> exact -> ripgrep - * Returns results from the first backend that succeeds and provides results. - * More efficient than parallel mode - stops as soon as valid results are found. - */ -async function executePriorityFallbackMode(params: Params): Promise { - const { query, path = '.' } = params; - const scope = resolveSearchScope(path); - const fallbackHistory: string[] = []; - - if (!query) { - return { success: false, error: 'Query is required for search' }; - } - - // Check index status first - const indexStatus = await checkIndexStatus(scope.workingDirectory); - - // 1. Try Hybrid search (highest priority) - 90s timeout for large indexes - if (indexStatus.indexed && indexStatus.has_embeddings) { - try { - const hybridResult = await withTimeout(executeHybridMode(params), 90000, 'hybrid'); - if (hybridResult.success && hybridResult.results && (hybridResult.results as any[]).length > 0) { - fallbackHistory.push('hybrid: success'); - return { - ...hybridResult, - metadata: { - ...hybridResult.metadata, - mode: 'priority', - note: 'Result from hybrid search (semantic + vector).', - fallback_history: fallbackHistory, - }, - }; - } - fallbackHistory.push('hybrid: no results'); - } catch (error) { - fallbackHistory.push(`hybrid: ${(error as Error).message}`); - } - } else { - fallbackHistory.push(`hybrid: skipped (${!indexStatus.indexed ? 'no index' : 'no embeddings'})`); - } - - // 2. Fallback to Exact search - 10s timeout - if (indexStatus.indexed) { - try { - const exactResult = await withTimeout(executeCodexLensExactMode(params), 10000, 'exact'); - if (exactResult.success && exactResult.results && (exactResult.results as any[]).length > 0) { - fallbackHistory.push('exact: success'); - return { - ...exactResult, - metadata: { - ...exactResult.metadata, - mode: 'priority', - note: 'Result from exact/FTS search (fallback from hybrid).', - fallback_history: fallbackHistory, - }, - }; - } - fallbackHistory.push('exact: no results'); - } catch (error) { - fallbackHistory.push(`exact: ${(error as Error).message}`); - } - } else { - fallbackHistory.push('exact: skipped (no index)'); - } - - // 3. Final fallback to Ripgrep - 5s timeout - try { - const ripgrepResult = await withTimeout(executeRipgrepMode(params), 5000, 'ripgrep'); - fallbackHistory.push(ripgrepResult.success ? 'ripgrep: success' : 'ripgrep: failed'); - return { - ...ripgrepResult, - metadata: { - ...ripgrepResult.metadata, - mode: 'priority', - note: 'Result from ripgrep search (final fallback).', - fallback_history: fallbackHistory, - }, - }; - } catch (error) { - fallbackHistory.push(`ripgrep: ${(error as Error).message}`); - } - - // All modes failed - return { - success: false, - error: 'All search backends in priority mode failed or returned no results.', - metadata: { - mode: 'priority', - query, - fallback_history: fallbackHistory, - } as any, - }; -} +// v1 executePriorityFallbackMode removed — v2 bridge + ripgrep fallback handles all search // Tool schema for MCP export const schema: ToolSchema = { name: 'smart_search', - description: `Unified code search tool. Choose an action and provide its required parameters. + description: `Unified code search tool powered by codexlens-search v2 (2-stage vector + FTS5 + reranking). -Recommended MCP flow: use **action=\"search\"** for lookups, **action=\"init\"** to create a static FTS index, and **action=\"update\"** when files change. Use **watch** only for explicit long-running auto-update sessions. +Recommended flow: use **action=\"search\"** for lookups, **action=\"init\"** to build the semantic index, and **action=\"update\"** when files change. **Actions & Required Parameters:** -* **search** (default): Search file content. +* **search** (default): Semantic code search with ripgrep fallback. * **query** (string, **REQUIRED**): Content to search for. - * *mode* (string): 'fuzzy' (default, FTS+ripgrep for stage-1 lexical search) or 'semantic' (dense+reranker, best when embeddings exist). - * *limit* (number): Max results with full content (default: 5). - * *path* (string): Directory or single file to search (default: current directory; file paths are auto-scoped back to that file). + * *limit* (number): Max results (default: 5). + * *path* (string): Directory or single file to search (default: current directory). * *contextLines* (number): Context lines around matches (default: 0). - * *regex* (boolean): Use regex matching (default: true). + * *regex* (boolean): Use regex matching in ripgrep fallback (default: true). * *caseSensitive* (boolean): Case-sensitive search (default: true). * **find_files**: Find files by path/name pattern. @@ -3697,47 +2851,34 @@ Recommended MCP flow: use **action=\"search\"** for lookups, **action=\"init\"** * *offset* (number): Pagination offset (default: 0). * *includeHidden* (boolean): Include hidden files (default: false). -* **init**: Create a static FTS index (incremental, skips existing, no embeddings). - * *path* (string): Directory to index (default: current). - * *languages* (array): Languages to index (e.g., ["javascript", "typescript"]). - -* **init_force**: Force full rebuild (delete and recreate static index). +* **init**: Initialize v2 semantic index and sync all files. * *path* (string): Directory to index (default: current). -* **embed**: Generate semantic/vector embeddings for an indexed project. - * *path* (string): Directory to embed (default: current). - * *embeddingBackend* (string): 'litellm'/'api' for remote API embeddings, 'fastembed'/'local' for local embeddings. Default bulk preset: local-fast. - * *embeddingModel* (string): Embedding model/profile to use. Default bulk preset uses 'fast'. - * *apiMaxWorkers* (number): Max concurrent API embedding workers. Defaults to auto-sizing from the configured endpoint pool. - * *force* (boolean): Regenerate embeddings even if they already exist. +* **status**: Check v2 index statistics. (No required params) -* **status**: Check index status. (No required params) - -* **update**: Incremental index update. +* **update**: Incremental sync for changed files. * *path* (string): Directory to update (default: current). * **watch**: Start file watcher for auto-updates. * *path* (string): Directory to watch (default: current). **Examples:** - smart_search(query="authentication logic") # Content search (default action) - smart_search(query="MyClass", mode="semantic") # Semantic search - smart_search(action=\"embed\", path=\"/project\", embeddingBackend=\"api\", apiMaxWorkers=8) # Build API vector index - smart_search(action="init", path="/project") # Build static FTS index - smart_search(action="embed", path="/project", embeddingBackend="api") # Build API vector index + smart_search(query="authentication logic") # Semantic search (default) + smart_search(action="init", path="/project") # Build v2 index + smart_search(action="update", path="/project") # Sync changed files smart_search(query="auth", limit=10, offset=0) # Paginated search`, inputSchema: { type: 'object', properties: { action: { type: 'string', - enum: ['init', 'init_force', 'embed', 'search', 'find_files', 'status', 'update', 'watch', 'search_files'], - description: 'Action: search (content search; default and recommended), find_files (path pattern matching), init (create static FTS index, incremental), init_force (force full rebuild), embed (generate semantic/vector embeddings), status (check index), update (incremental refresh), watch (auto-update watcher; opt-in). Note: search_files is deprecated.', + enum: ['init', 'search', 'find_files', 'status', 'update', 'watch', 'search_files'], + description: 'Action: search (semantic search, default), find_files (path pattern matching), init (build v2 index), status (check index), update (incremental sync), watch (auto-update watcher). Note: search_files is deprecated.', default: 'search', }, query: { type: 'string', - description: 'Content search query (for action="search"). Recommended default workflow: action=search with fuzzy mode, plus init/update for static indexing.', + description: 'Content search query (for action="search").', }, pattern: { type: 'string', @@ -3746,7 +2887,7 @@ Recommended MCP flow: use **action=\"search\"** for lookups, **action=\"init\"** mode: { type: 'string', enum: SEARCH_MODES, - description: 'Search mode: fuzzy (FTS + ripgrep fusion, default) or semantic (dense + reranker for natural language queries when embeddings exist).', + description: 'Search mode: fuzzy (v2 semantic + ripgrep fallback, default) or semantic (v2 semantic search only).', default: 'fuzzy', }, output_mode: { @@ -3802,31 +2943,9 @@ Recommended MCP flow: use **action=\"search\"** for lookups, **action=\"init\"** description: 'Include hidden files/directories', default: false, }, - languages: { - type: 'array', - items: { type: 'string' }, - description: 'Languages to index (for init action). Example: ["javascript", "typescript"]', - }, - embeddingBackend: { - type: 'string', - description: 'Embedding backend for action="embed": litellm/api (remote API) or fastembed/local (local GPU/CPU). Default bulk preset: local-fast.', - }, - embeddingModel: { - type: 'string', - description: 'Embedding model/profile for action="embed". Examples: "code", "fast", "qwen3-embedding-sf". Default bulk preset uses "fast".', - }, - apiMaxWorkers: { - type: 'number', - description: 'Max concurrent API embedding workers for action="embed". Defaults to auto-sizing from the configured endpoint pool.', - }, force: { type: 'boolean', - description: 'Force regeneration for action="embed".', - default: false, - }, - enrich: { - type: 'boolean', - description: 'Enrich search results with code graph relationships (calls, imports, called_by, imported_by).', + description: 'Force full rebuild for action="init".', default: false, }, regex: { @@ -4321,37 +3440,26 @@ export async function handler(params: Record): Promise): Promise void ): Promise { const path = (params.path as string) || '.'; - const languages = params.languages as string[] | undefined; - const force = params.force as boolean || false; + const scope = resolveSearchScope(path); + const dbPath = join(scope.workingDirectory, '.codexlens'); - // Check CodexLens availability - const readyStatus = await ensureCodexLensReady(); - if (!readyStatus.ready) { - return { - success: false, - error: `CodexLens not available: ${readyStatus.error}. CodexLens will be auto-installed on first use.`, - }; + // Notify progress start + if (onProgress) { + onProgress({ stage: 'init', message: 'Initializing v2 index...', percent: 0 } as ProgressInfo); } - // Use 'index init' subcommand (new CLI structure) - const args = ['index', 'init', path]; - if (force) { - args.push('--force'); // Force full rebuild - } - if (languages && languages.length > 0) { - args.push(...languages.flatMap((language) => ['--language', language])); + // Step 1: init empty index + const initResult = await executeV2BridgeCommand('init', ['--db-path', dbPath]); + if (!initResult.success) return initResult; + + if (onProgress) { + onProgress({ stage: 'sync', message: 'Syncing files...', percent: 10 } as ProgressInfo); } - // Track progress updates - const progressUpdates: ProgressInfo[] = []; - let lastProgress: ProgressInfo | null = null; + // Step 2: sync all files + const syncResult = await executeV2BridgeCommand('sync', [ + '--root', scope.workingDirectory, + '--db-path', dbPath, + ], { timeout: 1800000 }); - const result = await executeCodexLens(args, { - cwd: path, - timeout: 1800000, // 30 minutes for large codebases - onProgress: (progress: ProgressInfo) => { - progressUpdates.push(progress); - lastProgress = progress; - // Call external progress callback if provided - if (onProgress) { - onProgress(progress); - } - }, - }); - - // Build metadata with progress info - const metadata: SearchMetadata = { - action: force ? 'init_force' : 'init', - path, - }; - - if (lastProgress !== null) { - const p = lastProgress as ProgressInfo; - metadata.progress = { - stage: p.stage, - message: p.message, - percent: p.percent, - filesProcessed: p.filesProcessed, - totalFiles: p.totalFiles, - }; + if (onProgress) { + onProgress({ stage: 'complete', message: 'Index build complete', percent: 100 } as ProgressInfo); } - if (progressUpdates.length > 0) { - metadata.progressHistory = progressUpdates.slice(-5); - } - - const actionLabel = force ? 'rebuilt (force)' : 'created'; return { - success: result.success, - error: result.error, - message: result.success - ? `CodexLens index ${actionLabel} successfully for ${path}` + success: syncResult.success, + error: syncResult.error, + message: syncResult.success + ? `v2 index created and synced for ${scope.workingDirectory}` : undefined, - metadata, + metadata: { action: 'init', path: scope.workingDirectory }, + status: syncResult.status, }; } diff --git a/codex-lens-v2/.gitignore b/codex-lens-v2/.gitignore new file mode 100644 index 00000000..b4a7d405 --- /dev/null +++ b/codex-lens-v2/.gitignore @@ -0,0 +1 @@ +.ace-tool/ diff --git a/codex-lens-v2/src/codexlens_search/bridge.py b/codex-lens-v2/src/codexlens_search/bridge.py index 2caa5805..9b98d24b 100644 --- a/codex-lens-v2/src/codexlens_search/bridge.py +++ b/codex-lens-v2/src/codexlens_search/bridge.py @@ -129,7 +129,14 @@ def cmd_search(args: argparse.Namespace) -> None: results = search.search(args.query, top_k=args.top_k) _json_output([ - {"path": r.path, "score": r.score, "snippet": r.snippet} + { + "path": r.path, + "score": r.score, + "line": r.line, + "end_line": r.end_line, + "snippet": r.snippet, + "content": r.content, + } for r in results ]) diff --git a/codex-lens-v2/src/codexlens_search/indexing/pipeline.py b/codex-lens-v2/src/codexlens_search/indexing/pipeline.py index c75f0226..22cac992 100644 --- a/codex-lens-v2/src/codexlens_search/indexing/pipeline.py +++ b/codex-lens-v2/src/codexlens_search/indexing/pipeline.py @@ -146,14 +146,16 @@ class IndexingPipeline: batch_ids = [] batch_texts = [] batch_paths = [] - for chunk_text, path in file_chunks: + batch_lines: list[tuple[int, int]] = [] + for chunk_text, path, sl, el in file_chunks: batch_ids.append(chunk_id) batch_texts.append(chunk_text) batch_paths.append(path) + batch_lines.append((sl, el)) chunk_id += 1 chunks_created += len(batch_ids) - embed_queue.put((batch_ids, batch_texts, batch_paths)) + embed_queue.put((batch_ids, batch_texts, batch_paths, batch_lines)) # Signal embed worker: no more data embed_queue.put(_SENTINEL) @@ -203,12 +205,12 @@ class IndexingPipeline: if item is _SENTINEL: break - batch_ids, batch_texts, batch_paths = item + batch_ids, batch_texts, batch_paths, batch_lines = item try: vecs = self._embedder.embed_batch(batch_texts) vec_array = np.array(vecs, dtype=np.float32) id_array = np.array(batch_ids, dtype=np.int64) - out_q.put((id_array, vec_array, batch_texts, batch_paths)) + out_q.put((id_array, vec_array, batch_texts, batch_paths, batch_lines)) except Exception as exc: logger.error("Embed worker error: %s", exc) on_error(exc) @@ -221,19 +223,20 @@ class IndexingPipeline: in_q: queue.Queue, on_error: callable, ) -> None: - """Stage 3: Pull (ids, vecs, texts, paths), write to stores.""" + """Stage 3: Pull (ids, vecs, texts, paths, lines), write to stores.""" while True: item = in_q.get() if item is _SENTINEL: break - id_array, vec_array, texts, paths = item + id_array, vec_array, texts, paths, line_ranges = item try: self._binary_store.add(id_array, vec_array) self._ann_index.add(id_array, vec_array) fts_docs = [ - (int(id_array[i]), paths[i], texts[i]) + (int(id_array[i]), paths[i], texts[i], + line_ranges[i][0], line_ranges[i][1]) for i in range(len(id_array)) ] self._fts.add_documents(fts_docs) @@ -251,32 +254,39 @@ class IndexingPipeline: path: str, max_chars: int, overlap: int, - ) -> list[tuple[str, str]]: + ) -> list[tuple[str, str, int, int]]: """Split file text into overlapping chunks. - Returns list of (chunk_text, path) tuples. + Returns list of (chunk_text, path, start_line, end_line) tuples. + Line numbers are 1-based. """ if not text.strip(): return [] - chunks: list[tuple[str, str]] = [] + chunks: list[tuple[str, str, int, int]] = [] lines = text.splitlines(keepends=True) current: list[str] = [] current_len = 0 + chunk_start_line = 1 # 1-based + lines_consumed = 0 for line in lines: + lines_consumed += 1 if current_len + len(line) > max_chars and current: chunk = "".join(current) - chunks.append((chunk, path)) + end_line = lines_consumed - 1 + chunks.append((chunk, path, chunk_start_line, end_line)) # overlap: keep last N characters - tail = "".join(current)[-overlap:] + tail = chunk[-overlap:] if overlap else "" + tail_newlines = tail.count("\n") + chunk_start_line = max(1, end_line - tail_newlines + 1) current = [tail] if tail else [] current_len = len(tail) current.append(line) current_len += len(line) if current: - chunks.append(("".join(current), path)) + chunks.append(("".join(current), path, chunk_start_line, lines_consumed)) return chunks @@ -370,10 +380,12 @@ class IndexingPipeline: batch_ids = [] batch_texts = [] batch_paths = [] - for i, (chunk_text, path) in enumerate(file_chunks): + batch_lines: list[tuple[int, int]] = [] + for i, (chunk_text, path, sl, el) in enumerate(file_chunks): batch_ids.append(start_id + i) batch_texts.append(chunk_text) batch_paths.append(path) + batch_lines.append((sl, el)) # Embed synchronously vecs = self._embedder.embed_batch(batch_texts) @@ -384,7 +396,8 @@ class IndexingPipeline: self._binary_store.add(id_array, vec_array) self._ann_index.add(id_array, vec_array) fts_docs = [ - (batch_ids[i], batch_paths[i], batch_texts[i]) + (batch_ids[i], batch_paths[i], batch_texts[i], + batch_lines[i][0], batch_lines[i][1]) for i in range(len(batch_ids)) ] self._fts.add_documents(fts_docs) diff --git a/codex-lens-v2/src/codexlens_search/search/fts.py b/codex-lens-v2/src/codexlens_search/search/fts.py index 3e85f438..eb4b27fc 100644 --- a/codex-lens-v2/src/codexlens_search/search/fts.py +++ b/codex-lens-v2/src/codexlens_search/search/fts.py @@ -13,21 +13,50 @@ class FTSEngine: ) self._conn.execute( "CREATE TABLE IF NOT EXISTS docs_meta " - "(id INTEGER PRIMARY KEY, path TEXT)" + "(id INTEGER PRIMARY KEY, path TEXT, " + "start_line INTEGER DEFAULT 0, end_line INTEGER DEFAULT 0)" ) self._conn.commit() + self._migrate_line_columns() - def add_documents(self, docs: list[tuple[int, str, str]]) -> None: - """Add documents in batch. docs: list of (id, path, content).""" + def _migrate_line_columns(self) -> None: + """Add start_line/end_line columns if missing (for pre-existing DBs).""" + cols = { + row[1] + for row in self._conn.execute("PRAGMA table_info(docs_meta)").fetchall() + } + for col in ("start_line", "end_line"): + if col not in cols: + self._conn.execute( + f"ALTER TABLE docs_meta ADD COLUMN {col} INTEGER DEFAULT 0" + ) + self._conn.commit() + + def add_documents(self, docs: list[tuple]) -> None: + """Add documents in batch. + + docs: list of (id, path, content) or (id, path, content, start_line, end_line). + """ if not docs: return + meta_rows = [] + fts_rows = [] + for doc in docs: + if len(doc) >= 5: + doc_id, path, content, sl, el = doc[0], doc[1], doc[2], doc[3], doc[4] + else: + doc_id, path, content = doc[0], doc[1], doc[2] + sl, el = 0, 0 + meta_rows.append((doc_id, path, sl, el)) + fts_rows.append((doc_id, content)) self._conn.executemany( - "INSERT OR REPLACE INTO docs_meta (id, path) VALUES (?, ?)", - [(doc_id, path) for doc_id, path, content in docs], + "INSERT OR REPLACE INTO docs_meta (id, path, start_line, end_line) " + "VALUES (?, ?, ?, ?)", + meta_rows, ) self._conn.executemany( "INSERT OR REPLACE INTO docs (rowid, content) VALUES (?, ?)", - [(doc_id, content) for doc_id, path, content in docs], + fts_rows, ) self._conn.commit() @@ -92,3 +121,13 @@ class FTSEngine: ) self._conn.commit() return len(ids) + + def get_doc_meta(self, doc_id: int) -> tuple[str, int, int]: + """Return (path, start_line, end_line) for a doc_id.""" + row = self._conn.execute( + "SELECT path, start_line, end_line FROM docs_meta WHERE id = ?", + (doc_id,), + ).fetchone() + if row: + return row[0], row[1] or 0, row[2] or 0 + return "", 0, 0 diff --git a/codex-lens-v2/src/codexlens_search/search/pipeline.py b/codex-lens-v2/src/codexlens_search/search/pipeline.py index 70993ac8..b766779c 100644 --- a/codex-lens-v2/src/codexlens_search/search/pipeline.py +++ b/codex-lens-v2/src/codexlens_search/search/pipeline.py @@ -28,6 +28,9 @@ class SearchResult: path: str score: float snippet: str = "" + line: int = 0 + end_line: int = 0 + content: str = "" class SearchPipeline: @@ -162,15 +165,17 @@ class SearchPipeline: results: list[SearchResult] = [] for doc_id, score in ranked[:final_top_k]: - path = self._fts._conn.execute( - "SELECT path FROM docs_meta WHERE id = ?", (doc_id,) - ).fetchone() + path, start_line, end_line = self._fts.get_doc_meta(doc_id) + full_content = self._fts.get_content(doc_id) results.append( SearchResult( id=doc_id, - path=path[0] if path else "", + path=path, score=float(score), - snippet=self._fts.get_content(doc_id)[:200], + snippet=full_content[:200], + line=start_line, + end_line=end_line, + content=full_content, ) ) return results diff --git a/codex-lens-v2/tests/unit/test_bridge.py b/codex-lens-v2/tests/unit/test_bridge.py new file mode 100644 index 00000000..8f8eb479 --- /dev/null +++ b/codex-lens-v2/tests/unit/test_bridge.py @@ -0,0 +1,152 @@ +"""Unit tests for bridge.py CLI — argparse parsing, JSON protocol, error handling.""" +from __future__ import annotations + +import json +import subprocess +import sys +from pathlib import Path +from unittest.mock import patch + +import pytest + +from codexlens_search.bridge import _build_parser, _json_output, _error_exit + + +# --------------------------------------------------------------------------- +# Parser construction +# --------------------------------------------------------------------------- + +class TestParser: + @pytest.fixture(autouse=True) + def _parser(self): + self.parser = _build_parser() + + def test_all_subcommands_exist(self): + expected = { + "init", "search", "index-file", "remove-file", + "sync", "watch", "download-models", "status", + } + # parse each subcommand with minimal required args to verify it exists + for cmd in expected: + if cmd == "search": + args = self.parser.parse_args(["search", "--query", "test"]) + elif cmd == "index-file": + args = self.parser.parse_args(["index-file", "--file", "x.py"]) + elif cmd == "remove-file": + args = self.parser.parse_args(["remove-file", "--file", "x.py"]) + elif cmd == "sync": + args = self.parser.parse_args(["sync", "--root", "/tmp"]) + elif cmd == "watch": + args = self.parser.parse_args(["watch", "--root", "/tmp"]) + else: + args = self.parser.parse_args([cmd]) + assert args.command == cmd + + def test_global_db_path_default(self): + args = self.parser.parse_args(["status"]) + assert args.db_path # has a default + + def test_global_db_path_override(self): + args = self.parser.parse_args(["--db-path", "/custom/path", "status"]) + assert args.db_path == "/custom/path" + + def test_search_args(self): + args = self.parser.parse_args(["search", "-q", "hello", "-k", "5"]) + assert args.query == "hello" + assert args.top_k == 5 + + def test_search_default_top_k(self): + args = self.parser.parse_args(["search", "--query", "test"]) + assert args.top_k == 10 + + def test_sync_glob_default(self): + args = self.parser.parse_args(["sync", "--root", "/tmp"]) + assert args.glob == "**/*" + + def test_watch_debounce_default(self): + args = self.parser.parse_args(["watch", "--root", "/tmp"]) + assert args.debounce_ms == 500 + + def test_no_command_returns_none(self): + args = self.parser.parse_args([]) + assert args.command is None + + +# --------------------------------------------------------------------------- +# JSON output helpers +# --------------------------------------------------------------------------- + +class TestJsonHelpers: + def test_json_output(self, capsys): + _json_output({"key": "value"}) + out = capsys.readouterr().out.strip() + parsed = json.loads(out) + assert parsed == {"key": "value"} + + def test_json_output_list(self, capsys): + _json_output([1, 2, 3]) + out = capsys.readouterr().out.strip() + assert json.loads(out) == [1, 2, 3] + + def test_json_output_unicode(self, capsys): + _json_output({"msg": "中文测试"}) + out = capsys.readouterr().out.strip() + assert "中文测试" in out + + def test_error_exit(self): + with pytest.raises(SystemExit) as exc_info: + _error_exit("something broke") + assert exc_info.value.code == 1 + + +# --------------------------------------------------------------------------- +# cmd_init (lightweight, no model loading) +# --------------------------------------------------------------------------- + +class TestCmdInit: + def test_init_creates_databases(self, tmp_path): + """Init should create metadata.db and fts.db.""" + from codexlens_search.bridge import cmd_init + import argparse + + db_path = str(tmp_path / "test_idx") + args = argparse.Namespace(db_path=db_path, verbose=False) + cmd_init(args) + + assert (Path(db_path) / "metadata.db").exists() + assert (Path(db_path) / "fts.db").exists() + + +# --------------------------------------------------------------------------- +# cmd_status (lightweight, no model loading) +# --------------------------------------------------------------------------- + +class TestCmdStatus: + def test_status_not_initialized(self, tmp_path, capsys): + from codexlens_search.bridge import cmd_status + import argparse + + db_path = str(tmp_path / "empty_idx") + Path(db_path).mkdir() + args = argparse.Namespace(db_path=db_path, verbose=False) + cmd_status(args) + + out = json.loads(capsys.readouterr().out.strip()) + assert out["status"] == "not_initialized" + + def test_status_after_init(self, tmp_path, capsys): + from codexlens_search.bridge import cmd_init, cmd_status + import argparse + + db_path = str(tmp_path / "idx") + args = argparse.Namespace(db_path=db_path, verbose=False) + cmd_init(args) + + # Re-capture after init output + capsys.readouterr() + + cmd_status(args) + out = json.loads(capsys.readouterr().out.strip()) + assert out["status"] == "ok" + assert out["files_tracked"] == 0 + assert out["deleted_chunks"] == 0 diff --git a/codex-lens-v2/tests/unit/test_fts_delete.py b/codex-lens-v2/tests/unit/test_fts_delete.py new file mode 100644 index 00000000..97ce0471 --- /dev/null +++ b/codex-lens-v2/tests/unit/test_fts_delete.py @@ -0,0 +1,66 @@ +"""Unit tests for FTSEngine delete_by_path and get_chunk_ids_by_path.""" +from __future__ import annotations + +import pytest + +from codexlens_search.search.fts import FTSEngine + + +@pytest.fixture +def fts(tmp_path): + return FTSEngine(str(tmp_path / "fts.db")) + + +class TestGetChunkIdsByPath: + def test_empty(self, fts): + assert fts.get_chunk_ids_by_path("a.py") == [] + + def test_returns_matching_ids(self, fts): + fts.add_documents([ + (0, "a.py", "hello world"), + (1, "a.py", "foo bar"), + (2, "b.py", "other content"), + ]) + ids = fts.get_chunk_ids_by_path("a.py") + assert sorted(ids) == [0, 1] + + def test_no_match(self, fts): + fts.add_documents([(0, "a.py", "content")]) + assert fts.get_chunk_ids_by_path("b.py") == [] + + +class TestDeleteByPath: + def test_deletes_docs_and_meta(self, fts): + fts.add_documents([ + (0, "target.py", "to be deleted"), + (1, "target.py", "also deleted"), + (2, "keep.py", "keep this"), + ]) + count = fts.delete_by_path("target.py") + assert count == 2 + + # target.py gone from both tables + assert fts.get_chunk_ids_by_path("target.py") == [] + assert fts.get_content(0) == "" + assert fts.get_content(1) == "" + + # keep.py still there + assert fts.get_chunk_ids_by_path("keep.py") == [2] + assert fts.get_content(2) == "keep this" + + def test_delete_nonexistent_path(self, fts): + count = fts.delete_by_path("nonexistent.py") + assert count == 0 + + def test_delete_then_search(self, fts): + fts.add_documents([ + (0, "a.py", "unique searchable content"), + (1, "b.py", "different content here"), + ]) + fts.delete_by_path("a.py") + results = fts.exact_search("unique searchable") + assert len(results) == 0 + + results = fts.exact_search("different") + assert len(results) == 1 + assert results[0][0] == 1 diff --git a/codex-lens-v2/tests/unit/test_metadata_store.py b/codex-lens-v2/tests/unit/test_metadata_store.py new file mode 100644 index 00000000..bd48f79d --- /dev/null +++ b/codex-lens-v2/tests/unit/test_metadata_store.py @@ -0,0 +1,184 @@ +"""Unit tests for MetadataStore — SQLite file-to-chunk mapping + tombstone tracking.""" +from __future__ import annotations + +import pytest + +from codexlens_search.indexing.metadata import MetadataStore + + +@pytest.fixture +def store(tmp_path): + """Create a fresh MetadataStore backed by a temp db.""" + return MetadataStore(str(tmp_path / "meta.db")) + + +# --------------------------------------------------------------------------- +# Table creation +# --------------------------------------------------------------------------- + +class TestTableCreation: + def test_creates_three_tables(self, store): + """MetadataStore should create files, chunks, deleted_chunks tables.""" + tables = store._conn.execute( + "SELECT name FROM sqlite_master WHERE type='table' ORDER BY name" + ).fetchall() + names = {r[0] for r in tables} + assert "files" in names + assert "chunks" in names + assert "deleted_chunks" in names + + def test_foreign_keys_enabled(self, store): + """PRAGMA foreign_keys must be ON.""" + row = store._conn.execute("PRAGMA foreign_keys").fetchone() + assert row[0] == 1 + + def test_wal_mode(self, store): + """journal_mode should be WAL for concurrency.""" + row = store._conn.execute("PRAGMA journal_mode").fetchone() + assert row[0].lower() == "wal" + + +# --------------------------------------------------------------------------- +# register_file +# --------------------------------------------------------------------------- + +class TestRegisterFile: + def test_register_and_retrieve(self, store): + store.register_file("src/main.py", "abc123", 1000.0) + assert store.get_file_hash("src/main.py") == "abc123" + + def test_register_updates_existing(self, store): + store.register_file("a.py", "hash1", 1000.0) + store.register_file("a.py", "hash2", 2000.0) + assert store.get_file_hash("a.py") == "hash2" + + def test_get_file_hash_returns_none_for_unknown(self, store): + assert store.get_file_hash("nonexistent.py") is None + + +# --------------------------------------------------------------------------- +# register_chunks +# --------------------------------------------------------------------------- + +class TestRegisterChunks: + def test_register_and_retrieve_chunks(self, store): + store.register_file("a.py", "h", 1.0) + store.register_chunks("a.py", [(0, "c0"), (1, "c1"), (2, "c2")]) + ids = store.get_chunk_ids_for_file("a.py") + assert sorted(ids) == [0, 1, 2] + + def test_empty_chunks_list(self, store): + store.register_file("a.py", "h", 1.0) + store.register_chunks("a.py", []) + assert store.get_chunk_ids_for_file("a.py") == [] + + def test_chunks_for_unknown_file(self, store): + assert store.get_chunk_ids_for_file("unknown.py") == [] + + +# --------------------------------------------------------------------------- +# mark_file_deleted +# --------------------------------------------------------------------------- + +class TestMarkFileDeleted: + def test_tombstones_chunks(self, store): + store.register_file("a.py", "h", 1.0) + store.register_chunks("a.py", [(10, "c10"), (11, "c11")]) + count = store.mark_file_deleted("a.py") + assert count == 2 + assert store.get_deleted_ids() == {10, 11} + + def test_file_removed_after_delete(self, store): + store.register_file("a.py", "h", 1.0) + store.register_chunks("a.py", [(0, "c0")]) + store.mark_file_deleted("a.py") + assert store.get_file_hash("a.py") is None + + def test_chunks_cascaded_after_delete(self, store): + store.register_file("a.py", "h", 1.0) + store.register_chunks("a.py", [(0, "c0")]) + store.mark_file_deleted("a.py") + assert store.get_chunk_ids_for_file("a.py") == [] + + def test_delete_nonexistent_file(self, store): + count = store.mark_file_deleted("nonexistent.py") + assert count == 0 + + def test_delete_file_without_chunks(self, store): + store.register_file("empty.py", "h", 1.0) + count = store.mark_file_deleted("empty.py") + assert count == 0 + assert store.get_file_hash("empty.py") is None + + +# --------------------------------------------------------------------------- +# file_needs_update +# --------------------------------------------------------------------------- + +class TestFileNeedsUpdate: + def test_new_file_needs_update(self, store): + assert store.file_needs_update("new.py", "any_hash") is True + + def test_unchanged_file(self, store): + store.register_file("a.py", "same_hash", 1.0) + assert store.file_needs_update("a.py", "same_hash") is False + + def test_changed_file(self, store): + store.register_file("a.py", "old_hash", 1.0) + assert store.file_needs_update("a.py", "new_hash") is True + + +# --------------------------------------------------------------------------- +# get_deleted_ids / compact_deleted +# --------------------------------------------------------------------------- + +class TestDeletedIdsAndCompact: + def test_empty_deleted_ids(self, store): + assert store.get_deleted_ids() == set() + + def test_compact_returns_and_clears(self, store): + store.register_file("a.py", "h", 1.0) + store.register_chunks("a.py", [(5, "c5"), (6, "c6")]) + store.mark_file_deleted("a.py") + + deleted = store.compact_deleted() + assert deleted == {5, 6} + assert store.get_deleted_ids() == set() + + def test_compact_noop_when_empty(self, store): + deleted = store.compact_deleted() + assert deleted == set() + + +# --------------------------------------------------------------------------- +# get_all_files / max_chunk_id +# --------------------------------------------------------------------------- + +class TestHelpers: + def test_get_all_files(self, store): + store.register_file("a.py", "h1", 1.0) + store.register_file("b.py", "h2", 2.0) + assert store.get_all_files() == {"a.py": "h1", "b.py": "h2"} + + def test_max_chunk_id_empty(self, store): + assert store.max_chunk_id() == -1 + + def test_max_chunk_id_active(self, store): + store.register_file("a.py", "h", 1.0) + store.register_chunks("a.py", [(0, "c"), (5, "c"), (3, "c")]) + assert store.max_chunk_id() == 5 + + def test_max_chunk_id_includes_deleted(self, store): + store.register_file("a.py", "h", 1.0) + store.register_chunks("a.py", [(10, "c")]) + store.mark_file_deleted("a.py") + assert store.max_chunk_id() == 10 + + def test_max_chunk_id_mixed(self, store): + store.register_file("a.py", "h", 1.0) + store.register_chunks("a.py", [(3, "c")]) + store.register_file("b.py", "h2", 1.0) + store.register_chunks("b.py", [(7, "c")]) + store.mark_file_deleted("a.py") + # deleted has 3, active has 7 + assert store.max_chunk_id() == 7 diff --git a/codex-lens-v2/tests/unit/test_watcher.py b/codex-lens-v2/tests/unit/test_watcher.py new file mode 100644 index 00000000..1ae9acf2 --- /dev/null +++ b/codex-lens-v2/tests/unit/test_watcher.py @@ -0,0 +1,270 @@ +"""Unit tests for watcher module — events, FileWatcher debounce/dedup, IncrementalIndexer.""" +from __future__ import annotations + +import time +from pathlib import Path +from unittest.mock import MagicMock, patch + +import pytest + +from codexlens_search.watcher.events import ChangeType, FileEvent, WatcherConfig +from codexlens_search.watcher.incremental_indexer import BatchResult, IncrementalIndexer + + +# --------------------------------------------------------------------------- +# ChangeType enum +# --------------------------------------------------------------------------- + +class TestChangeType: + def test_values(self): + assert ChangeType.CREATED.value == "created" + assert ChangeType.MODIFIED.value == "modified" + assert ChangeType.DELETED.value == "deleted" + + def test_all_members(self): + assert len(ChangeType) == 3 + + +# --------------------------------------------------------------------------- +# FileEvent +# --------------------------------------------------------------------------- + +class TestFileEvent: + def test_creation(self): + e = FileEvent(path=Path("a.py"), change_type=ChangeType.CREATED) + assert e.path == Path("a.py") + assert e.change_type == ChangeType.CREATED + assert isinstance(e.timestamp, float) + + def test_custom_timestamp(self): + e = FileEvent(path=Path("b.py"), change_type=ChangeType.DELETED, timestamp=42.0) + assert e.timestamp == 42.0 + + +# --------------------------------------------------------------------------- +# WatcherConfig +# --------------------------------------------------------------------------- + +class TestWatcherConfig: + def test_defaults(self): + cfg = WatcherConfig() + assert cfg.debounce_ms == 500 + assert ".git" in cfg.ignored_patterns + assert "__pycache__" in cfg.ignored_patterns + assert "node_modules" in cfg.ignored_patterns + + def test_custom(self): + cfg = WatcherConfig(debounce_ms=1000, ignored_patterns={".custom"}) + assert cfg.debounce_ms == 1000 + assert cfg.ignored_patterns == {".custom"} + + +# --------------------------------------------------------------------------- +# BatchResult +# --------------------------------------------------------------------------- + +class TestBatchResult: + def test_defaults(self): + r = BatchResult() + assert r.files_indexed == 0 + assert r.files_removed == 0 + assert r.chunks_created == 0 + assert r.errors == [] + + def test_total_processed(self): + r = BatchResult(files_indexed=3, files_removed=2) + assert r.total_processed == 5 + + def test_has_errors(self): + r = BatchResult() + assert r.has_errors is False + r.errors.append("oops") + assert r.has_errors is True + + +# --------------------------------------------------------------------------- +# IncrementalIndexer — event routing +# --------------------------------------------------------------------------- + +class TestIncrementalIndexer: + @pytest.fixture + def mock_pipeline(self): + pipeline = MagicMock() + pipeline.index_file.return_value = MagicMock( + files_processed=1, chunks_created=3 + ) + return pipeline + + def test_routes_created_to_index_file(self, mock_pipeline): + indexer = IncrementalIndexer(mock_pipeline, root=Path("/project")) + events = [ + FileEvent(Path("/project/src/new.py"), ChangeType.CREATED), + ] + result = indexer.process_events(events) + assert result.files_indexed == 1 + mock_pipeline.index_file.assert_called_once() + # CREATED should NOT use force=True + call_kwargs = mock_pipeline.index_file.call_args + assert call_kwargs.kwargs.get("force", call_kwargs[1].get("force")) is False + + def test_routes_modified_to_index_file_with_force(self, mock_pipeline): + indexer = IncrementalIndexer(mock_pipeline, root=Path("/project")) + events = [ + FileEvent(Path("/project/src/changed.py"), ChangeType.MODIFIED), + ] + result = indexer.process_events(events) + assert result.files_indexed == 1 + call_kwargs = mock_pipeline.index_file.call_args + assert call_kwargs.kwargs.get("force", call_kwargs[1].get("force")) is True + + def test_routes_deleted_to_remove_file(self, mock_pipeline, tmp_path): + root = tmp_path / "project" + root.mkdir() + indexer = IncrementalIndexer(mock_pipeline, root=root) + events = [ + FileEvent(root / "src" / "old.py", ChangeType.DELETED), + ] + result = indexer.process_events(events) + assert result.files_removed == 1 + # On Windows relative_to produces backslashes, normalize + actual_arg = mock_pipeline.remove_file.call_args[0][0] + assert actual_arg.replace("\\", "/") == "src/old.py" + + def test_batch_with_mixed_events(self, mock_pipeline): + indexer = IncrementalIndexer(mock_pipeline, root=Path("/project")) + events = [ + FileEvent(Path("/project/a.py"), ChangeType.CREATED), + FileEvent(Path("/project/b.py"), ChangeType.MODIFIED), + FileEvent(Path("/project/c.py"), ChangeType.DELETED), + ] + result = indexer.process_events(events) + assert result.files_indexed == 2 + assert result.files_removed == 1 + assert result.total_processed == 3 + + def test_error_isolation(self, mock_pipeline): + """One file failure should not stop processing of others.""" + call_count = [0] + + def side_effect(*args, **kwargs): + call_count[0] += 1 + if call_count[0] == 1: + raise RuntimeError("disk error") + return MagicMock(files_processed=1, chunks_created=1) + + mock_pipeline.index_file.side_effect = side_effect + + indexer = IncrementalIndexer(mock_pipeline, root=Path("/project")) + events = [ + FileEvent(Path("/project/fail.py"), ChangeType.CREATED), + FileEvent(Path("/project/ok.py"), ChangeType.CREATED), + ] + result = indexer.process_events(events) + + assert result.files_indexed == 1 # second succeeded + assert len(result.errors) == 1 # first failed + assert "disk error" in result.errors[0] + + def test_empty_events(self, mock_pipeline): + indexer = IncrementalIndexer(mock_pipeline) + result = indexer.process_events([]) + assert result.total_processed == 0 + mock_pipeline.index_file.assert_not_called() + mock_pipeline.remove_file.assert_not_called() + + +# --------------------------------------------------------------------------- +# FileWatcher — debounce and dedup logic (unit-level, no actual FS) +# --------------------------------------------------------------------------- + +class TestFileWatcherLogic: + """Test FileWatcher internals without starting a real watchdog Observer.""" + + @pytest.fixture + def watcher_parts(self): + """Create a FileWatcher with mocked observer, capture callbacks.""" + # Import here since watchdog is optional + from codexlens_search.watcher.file_watcher import FileWatcher, _EVENT_PRIORITY + + collected = [] + + def on_changes(events): + collected.extend(events) + + cfg = WatcherConfig(debounce_ms=100) + watcher = FileWatcher(Path("."), cfg, on_changes) + return watcher, collected, _EVENT_PRIORITY + + def test_event_priority_ordering(self, watcher_parts): + _, _, priority = watcher_parts + assert priority[ChangeType.DELETED] > priority[ChangeType.MODIFIED] + assert priority[ChangeType.MODIFIED] > priority[ChangeType.CREATED] + + def test_dedup_keeps_higher_priority(self, watcher_parts, tmp_path): + watcher, collected, _ = watcher_parts + f = str(tmp_path / "a.py") + watcher._on_raw_event(f, ChangeType.CREATED) + watcher._on_raw_event(f, ChangeType.DELETED) + + watcher.flush_now() + + assert len(collected) == 1 + assert collected[0].change_type == ChangeType.DELETED + + def test_dedup_does_not_downgrade(self, watcher_parts, tmp_path): + watcher, collected, _ = watcher_parts + f = str(tmp_path / "b.py") + watcher._on_raw_event(f, ChangeType.DELETED) + watcher._on_raw_event(f, ChangeType.CREATED) + + watcher.flush_now() + assert len(collected) == 1 + # CREATED (priority 1) < DELETED (priority 3), so DELETED stays + assert collected[0].change_type == ChangeType.DELETED + + def test_multiple_files_kept(self, watcher_parts, tmp_path): + watcher, collected, _ = watcher_parts + watcher._on_raw_event(str(tmp_path / "a.py"), ChangeType.CREATED) + watcher._on_raw_event(str(tmp_path / "b.py"), ChangeType.MODIFIED) + watcher._on_raw_event(str(tmp_path / "c.py"), ChangeType.DELETED) + + watcher.flush_now() + assert len(collected) == 3 + paths = {str(e.path) for e in collected} + assert len(paths) == 3 + + def test_flush_clears_pending(self, watcher_parts, tmp_path): + watcher, collected, _ = watcher_parts + watcher._on_raw_event(str(tmp_path / "a.py"), ChangeType.CREATED) + watcher.flush_now() + assert len(collected) == 1 + + collected.clear() + watcher.flush_now() + assert len(collected) == 0 + + def test_should_watch_filters_ignored(self, watcher_parts): + watcher, _, _ = watcher_parts + assert watcher._should_watch(Path("/project/src/main.py")) is True + assert watcher._should_watch(Path("/project/.git/config")) is False + assert watcher._should_watch(Path("/project/node_modules/foo.js")) is False + assert watcher._should_watch(Path("/project/__pycache__/mod.pyc")) is False + + def test_jsonl_serialization(self): + from codexlens_search.watcher.file_watcher import FileWatcher + import json + + events = [ + FileEvent(Path("/tmp/a.py"), ChangeType.CREATED, 1000.0), + FileEvent(Path("/tmp/b.py"), ChangeType.DELETED, 2000.0), + ] + output = FileWatcher.events_to_jsonl(events) + lines = output.strip().split("\n") + assert len(lines) == 2 + + obj1 = json.loads(lines[0]) + assert obj1["change_type"] == "created" + assert obj1["timestamp"] == 1000.0 + + obj2 = json.loads(lines[1]) + assert obj2["change_type"] == "deleted"