From e485f1d69b8aa31120634b0ca60c44c2be419914 Mon Sep 17 00:00:00 2001 From: catlog22 Date: Fri, 13 Feb 2026 10:42:37 +0800 Subject: [PATCH] feat: enforce mandatory rationale and role in explore/diagnosis schema output - Remove oneOf string option from relevant_files/affected_files, require structured objects - Add required fields: rationale (minLength 10), role/change_type enum - Add optional fields: discovery_source, key_symbols - Update all caller commands with new format instructions and success criteria - Fix consumer code: Map-based dedup, getPath() helper, path extraction - Fix frontend: f.rationale || f.reason backward-compatible fallback --- .../schemas/diagnosis-json-schema.json | 53 +++- .../schemas/explore-json-schema.json | 59 +++- .claude/agents/cli-explore-agent.md | 37 ++- .claude/agents/cli-lite-planning-agent.md | 296 +++++++++--------- .claude/agents/context-search-agent.md | 12 +- .claude/commands/team/plan.md | 72 ++++- .claude/commands/workflow/lite-fix.md | 287 ++++++++++------- .claude/commands/workflow/lite-plan.md | 132 ++++++-- .../commands/workflow/tools/context-gather.md | 10 +- .codex/agents/cli-explore-agent.md | 37 ++- .codex/agents/cli-lite-planning-agent.md | 24 +- .codex/agents/context-search-agent.md | 12 +- .../phases/02-context-gathering.md | 14 +- .../lite-tasks/LiteContextContent.tsx | 2 +- .../context/ExplorationsSection.tsx | 3 +- .../dashboard-js/views/lite-tasks.js | 2 +- 16 files changed, 679 insertions(+), 373 deletions(-) diff --git a/.ccw/workflows/cli-templates/schemas/diagnosis-json-schema.json b/.ccw/workflows/cli-templates/schemas/diagnosis-json-schema.json index 155082eb..c8bb54a3 100644 --- a/.ccw/workflows/cli-templates/schemas/diagnosis-json-schema.json +++ b/.ccw/workflows/cli-templates/schemas/diagnosis-json-schema.json @@ -83,26 +83,45 @@ }, "affected_files": { "type": "array", + "minItems": 1, "items": { - "oneOf": [ - {"type": "string"}, - { - "type": "object", - "required": ["path", "relevance"], - "properties": { - "path": {"type": "string", "description": "File path relative to project root"}, - "relevance": {"type": "number", "minimum": 0, "maximum": 1, "description": "Relevance score 0.0-1.0 (0.7+ high, 0.5-0.7 medium, <0.5 low)"}, - "rationale": {"type": "string", "description": "Brief explanation of why this file is affected from this diagnosis angle"}, - "change_type": { - "type": "string", - "enum": ["fix_target", "needs_update", "test_coverage", "reference_only"], - "description": "Type of change needed for this file" - } - } + "type": "object", + "required": ["path", "relevance", "rationale", "change_type"], + "properties": { + "path": { + "type": "string", + "description": "File path relative to project root" + }, + "relevance": { + "type": "number", + "minimum": 0, + "maximum": 1, + "description": "Relevance score 0.0-1.0 (0.7+ high, 0.5-0.7 medium, <0.5 low)" + }, + "rationale": { + "type": "string", + "minLength": 10, + "description": "REQUIRED: Selection rationale explaining why this file is affected. Must be specific. Example: 'Contains handleLogin() at line 45 where null check is missing for token response' rather than 'Related to bug'" + }, + "change_type": { + "type": "string", + "enum": ["fix_target", "needs_update", "test_coverage", "reference_only"], + "description": "Type of change needed: fix_target=contains the bug, needs_update=requires changes due to fix, test_coverage=tests to add/update, reference_only=understanding context" + }, + "discovery_source": { + "type": "string", + "enum": ["bash-scan", "cli-analysis", "ace-search", "dependency-trace", "stack-trace", "manual"], + "description": "How the file was identified as affected" + }, + "key_symbols": { + "type": "array", + "items": {"type": "string"}, + "description": "Key symbols (functions, classes) in this file related to the bug" } - ] + }, + "additionalProperties": false }, - "description": "Files affected by the bug. Prefer object format with relevance scores for synthesis prioritization." + "description": "Files affected by the bug. Every file MUST have structured rationale explaining why it is affected and what change is needed." }, "reproduction_steps": { "type": "array", diff --git a/.ccw/workflows/cli-templates/schemas/explore-json-schema.json b/.ccw/workflows/cli-templates/schemas/explore-json-schema.json index 1b2ede00..ecbbd739 100644 --- a/.ccw/workflows/cli-templates/schemas/explore-json-schema.json +++ b/.ccw/workflows/cli-templates/schemas/explore-json-schema.json @@ -1,7 +1,7 @@ { "$schema": "http://json-schema.org/draft-07/schema#", "title": "Exploration Context Schema", - "description": "Code exploration results from cli-explore-agent for task context gathering", + "description": "Code exploration results from cli-explore-agent for task context gathering. Every file MUST include selection rationale.", "type": "object", "required": [ "project_structure", @@ -20,21 +20,54 @@ }, "relevant_files": { "type": "array", + "minItems": 1, "items": { - "oneOf": [ - {"type": "string"}, - { - "type": "object", - "required": ["path", "relevance"], - "properties": { - "path": {"type": "string", "description": "File path relative to project root"}, - "relevance": {"type": "number", "minimum": 0, "maximum": 1, "description": "Relevance score 0.0-1.0 (0.7+ high, 0.5-0.7 medium, <0.5 low)"}, - "rationale": {"type": "string", "description": "Brief explanation of why this file is relevant from this exploration angle"} - } + "type": "object", + "required": ["path", "relevance", "rationale", "role"], + "properties": { + "path": { + "type": "string", + "description": "File path relative to project root" + }, + "relevance": { + "type": "number", + "minimum": 0, + "maximum": 1, + "description": "Relevance score 0.0-1.0 (0.7+ high, 0.5-0.7 medium, <0.5 low)" + }, + "rationale": { + "type": "string", + "minLength": 10, + "description": "REQUIRED: Selection rationale explaining why this file is relevant to the exploration topic. Must be specific (not generic). Example: 'Contains AuthService.login() which is the entry point for JWT token generation' rather than 'Related to auth'" + }, + "role": { + "type": "string", + "enum": [ + "modify_target", + "dependency", + "pattern_reference", + "test_target", + "type_definition", + "integration_point", + "config", + "context_only" + ], + "description": "File's role relative to the task: modify_target=needs changes, dependency=imported by targets, pattern_reference=demonstrates patterns to follow, test_target=tests needing update, type_definition=types/interfaces used, integration_point=where new code connects, config=configuration, context_only=understanding only" + }, + "discovery_source": { + "type": "string", + "enum": ["bash-scan", "cli-analysis", "ace-search", "dependency-trace", "manual"], + "description": "How the file was discovered: bash-scan=structural scan (rg/find/tree), cli-analysis=Gemini/Qwen semantic analysis, ace-search=ACE context engine, dependency-trace=import/export graph traversal, manual=directly specified in task" + }, + "key_symbols": { + "type": "array", + "items": {"type": "string"}, + "description": "Key symbols (functions, classes, types) in this file relevant to the task. Example: ['AuthService', 'login', 'TokenPayload']" } - ] + }, + "additionalProperties": false }, - "description": "File paths to be modified or referenced for the task. Prefer object format with relevance scores for synthesis prioritization." + "description": "Files relevant to the task. Every file MUST have structured rationale explaining its selection basis and role classification." }, "patterns": { "type": "string", diff --git a/.claude/agents/cli-explore-agent.md b/.claude/agents/cli-explore-agent.md index b4c8fd89..016066bf 100644 --- a/.claude/agents/cli-explore-agent.md +++ b/.claude/agents/cli-explore-agent.md @@ -92,9 +92,11 @@ RULES: {from prompt, if template specified} | analysis=READ-ONLY ### Dual-Source Synthesis -1. Bash results: Precise file:line locations -2. Gemini results: Semantic understanding, design intent -3. Merge with source attribution (bash-discovered | gemini-discovered) +1. Bash results: Precise file:line locations → `discovery_source: "bash-scan"` +2. Gemini results: Semantic understanding, design intent → `discovery_source: "cli-analysis"` +3. ACE search: Semantic code search → `discovery_source: "ace-search"` +4. Dependency tracing: Import/export graph → `discovery_source: "dependency-trace"` +5. Merge with source attribution and generate rationale for each file --- @@ -118,7 +120,16 @@ Parse and memorize: 4. **Enum values** - Copy exact strings (e.g., `"critical"` not `"Critical"`) 5. **Nested structures** - Note flat vs nested requirements -**Step 3: Pre-Output Validation Checklist** +**Step 3: File Rationale Validation** (MANDATORY for relevant_files / affected_files) + +Every file entry MUST have: +- `rationale` (required, minLength 10): Specific reason tied to the exploration topic, NOT generic + - GOOD: "Contains AuthService.login() which is the entry point for JWT token generation" + - BAD: "Related to auth" or "Relevant file" +- `role` (required, enum): Structural classification of why it was selected +- `discovery_source` (optional but recommended): How the file was found + +**Step 4: Pre-Output Validation Checklist** Before writing ANY JSON output, verify: @@ -128,6 +139,8 @@ Before writing ANY JSON output, verify: - [ ] Enum values EXACTLY match schema (case-sensitive) - [ ] Nested structures follow schema pattern (flat vs nested) - [ ] Data types correct (string, integer, array, object) +- [ ] Every file in relevant_files has: path + relevance + rationale + role +- [ ] Every rationale is specific (>10 chars, not generic) --- @@ -167,13 +180,15 @@ Brief summary: **ALWAYS**: 1. **Search Tool Priority**: ACE (`mcp__ace-tool__search_context`) → CCW (`mcp__ccw-tools__smart_search`) / Built-in (`Grep`, `Glob`, `Read`) 2. Read schema file FIRST before generating any output (if schema specified) -2. Copy field names EXACTLY from schema (case-sensitive) -3. Verify root structure matches schema (array vs object) -4. Match nested/flat structures as schema requires -5. Use exact enum values from schema (case-sensitive) -6. Include ALL required fields at every level -7. Include file:line references in findings -8. Attribute discovery source (bash/gemini) +3. Copy field names EXACTLY from schema (case-sensitive) +4. Verify root structure matches schema (array vs object) +5. Match nested/flat structures as schema requires +6. Use exact enum values from schema (case-sensitive) +7. Include ALL required fields at every level +8. Include file:line references in findings +9. **Every file MUST have rationale**: Specific selection basis tied to the topic (not generic) +10. **Every file MUST have role**: Classify as modify_target/dependency/pattern_reference/test_target/type_definition/integration_point/config/context_only +11. **Track discovery source**: Record how each file was found (bash-scan/cli-analysis/ace-search/dependency-trace/manual) **Bash Tool**: - Use `run_in_background=false` for all Bash/CLI calls to ensure foreground execution diff --git a/.claude/agents/cli-lite-planning-agent.md b/.claude/agents/cli-lite-planning-agent.md index b843cc19..ff435559 100644 --- a/.claude/agents/cli-lite-planning-agent.md +++ b/.claude/agents/cli-lite-planning-agent.md @@ -4,7 +4,7 @@ description: | Generic planning agent for lite-plan, collaborative-plan, and lite-fix workflows. Generates structured plan JSON based on provided schema reference. Core capabilities: - - Schema-driven output (plan-json-schema or fix-plan-json-schema) + - Schema-driven output (plan-overview-base-schema or plan-overview-fix-schema) - Task decomposition with dependency analysis - CLI execution ID assignment for fork/merge strategies - Multi-angle context integration (explorations or diagnoses) @@ -14,7 +14,7 @@ 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. -**CRITICAL**: After generating plan.json, 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, acceptance criteria, implementation steps, constraint compliance. +**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 @@ -24,7 +24,8 @@ The agent produces different artifacts based on workflow context: | Artifact | Description | |----------|-------------| -| `plan.json` | Structured plan following plan-json-schema.json | +| `plan.json` | Plan overview following plan-overview-base-schema.json (with `task_ids[]` + `task_count`, NO `tasks[]`) | +| `.task/TASK-*.json` | Independent task files following task-schema.json (one per task) | ### Extended Output (collaborative-plan sub-agents) @@ -33,7 +34,7 @@ When invoked with `process_docs: true` in input context: | Artifact | Description | |----------|-------------| | `planning-context.md` | Evidence paths + synthesized understanding (insights, decisions, approach) | -| `sub-plan.json` | Sub-plan following plan-json-schema.json with source_agent metadata | +| `sub-plan.json` | Sub-plan following plan-overview-base-schema.json with source_agent metadata | **planning-context.md format**: ```markdown @@ -57,7 +58,7 @@ When invoked with `process_docs: true` in input context: { // Required task_description: string, // Task or bug description - schema_path: string, // Schema reference path (plan-json-schema or fix-plan-json-schema) + schema_path: string, // Schema reference path (plan-overview-base-schema or plan-overview-fix-schema) session: { id, folder, artifacts }, // Context (one of these based on workflow) @@ -105,8 +106,8 @@ When `process_docs: true`, generate planning-context.md before sub-plan.json: ## Schema-Driven Output **CRITICAL**: Read the schema reference first to determine output structure: -- `plan-json-schema.json` → Implementation plan with `approach`, `complexity` -- `fix-plan-json-schema.json` → Fix plan with `root_cause`, `severity`, `risk_level` +- `plan-overview-base-schema.json` → Implementation plan with `approach`, `complexity` +- `plan-overview-fix-schema.json` → Fix plan with `root_cause`, `severity`, `risk_level` ```javascript // Step 1: Always read schema first @@ -120,7 +121,7 @@ const planObject = generatePlanFromSchema(schema, context) ``` Phase 1: Schema & Context Loading -├─ Read schema reference (plan-json-schema or fix-plan-json-schema) +├─ Read schema reference (plan-overview-base-schema or plan-overview-fix-schema) ├─ Aggregate multi-angle context (explorations or diagnoses) └─ Determine output structure from schema @@ -134,11 +135,11 @@ Phase 3: Parsing & Enhancement ├─ Validate and enhance task objects └─ Infer missing fields from context -Phase 4: planObject Generation -├─ Build planObject conforming to schema +Phase 4: Two-Layer Output Generation +├─ Build task objects conforming to task-schema.json ├─ Assign CLI execution IDs and strategies -├─ Generate flow_control from depends_on -└─ Write initial plan.json +├─ Write .task/TASK-*.json files (one per task) +└─ Write plan.json overview (with task_ids[], NO tasks[]) Phase 5: Plan Quality Check (MANDATORY) ├─ Execute CLI quality check using Gemini (Qwen fallback) @@ -180,14 +181,14 @@ EXPECTED: ## Complexity: {Low|Medium|High} ## Task Breakdown -### T1: [Title] (or FIX1 for fix-plan) +### TASK-001: [Title] (or FIX-001 for fix-plan) **Scope**: [module/feature path] **Action**: [type] **Description**: [what] -**Modification Points**: - [file]: [target] - [change] +**Files**: - **[path]**: [action] / [target] → [change description] **Implementation**: 1. [step] **Reference**: - Pattern: [pattern] - Files: [files] - Examples: [guidance] -**Acceptance**: - [quantified criterion] +**Convergence Criteria**: - [quantified criterion] **Depends On**: [] [MEDIUM/HIGH COMPLEXITY ONLY] @@ -221,21 +222,18 @@ EXPECTED: ## Design Decisions (MEDIUM/HIGH) - Decision: [what] | Rationale: [why] | Tradeoff: [what was traded] -## Flow Control -**Execution Order**: - Phase parallel-1: [T1, T2] (independent) -**Exit Conditions**: - Success: [condition] - Failure: [condition] - ## Time Estimate **Total**: [time] CONSTRAINTS: - Follow schema structure from {schema_path} +- Task IDs use format TASK-001, TASK-002, etc. (FIX-001 for fix-plan) - Complexity determines required fields: * Low: base fields only * Medium: + rationale + verification + design_decisions * High: + risks + code_skeleton + data_flow -- Acceptance/verification must be quantified -- Dependencies use task IDs +- Convergence criteria must be quantified and testable +- Dependencies use task IDs (TASK-001 format) - analysis=READ-ONLY " --tool {cli_tool} --mode analysis --cd {project_root} ``` @@ -255,11 +253,13 @@ function extractSection(cliOutput, header) { // Parse structured tasks from CLI output function extractStructuredTasks(cliOutput, complexity) { const tasks = [] - // Split by task headers - const taskBlocks = cliOutput.split(/### (T\d+):/).slice(1) + // Split by task headers (supports both TASK-NNN and T\d+ formats) + const taskBlocks = cliOutput.split(/### (TASK-\d+|T\d+):/).slice(1) for (let i = 0; i < taskBlocks.length; i += 2) { - const taskId = taskBlocks[i].trim() + const rawId = taskBlocks[i].trim() + // Normalize task ID to TASK-NNN format + const taskId = /^T(\d+)$/.test(rawId) ? `TASK-${rawId.slice(1).padStart(3, '0')}` : rawId const taskText = taskBlocks[i + 1] // Extract base fields @@ -269,14 +269,20 @@ function extractStructuredTasks(cliOutput, complexity) { const descMatch = /\*\*Description\*\*: (.+?)(?=\n)/.exec(taskText) const depsMatch = /\*\*Depends On\*\*: (.+?)(?=\n|$)/.exec(taskText) - // Parse modification points - const modPointsSection = /\*\*Modification Points\*\*:\n((?:- .+?\n)*)/.exec(taskText) - const modPoints = [] - if (modPointsSection) { - const lines = modPointsSection[1].split('\n').filter(s => s.trim().startsWith('-')) + // Parse files (replaces modification_points) + const filesSection = /\*\*Files\*\*:\n((?:- .+?\n)*)/.exec(taskText) + const files = [] + if (filesSection) { + const lines = filesSection[1].split('\n').filter(s => s.trim().startsWith('-')) lines.forEach(line => { - const m = /- \[(.+?)\]: \[(.+?)\] - (.+)/.exec(line) - if (m) modPoints.push({ file: m[1].trim(), target: m[2].trim(), change: m[3].trim() }) + // Format: - **path**: action / target -> change description + const m = /- \*\*(.+?)\*\*: (.+?) \/ (.+?) (?:→|->|-->) (.+)/.exec(line) + if (m) files.push({ path: m[1].trim(), action: m[2].trim(), target: m[3].trim(), change: m[4].trim() }) + else { + // Fallback: - [file]: [target] - [change] (legacy format) + const legacy = /- \[(.+?)\]: \[(.+?)\] - (.+)/.exec(line) + if (legacy) files.push({ path: legacy[1].trim(), action: "modify", target: legacy[2].trim(), change: legacy[3].trim() }) + } }) } @@ -294,10 +300,10 @@ function extractStructuredTasks(cliOutput, complexity) { examples: (/- Examples: (.+)/m.exec(refSection[1]) || [])[1]?.trim() || "Follow pattern" } : {} - // Parse acceptance - const acceptSection = /\*\*Acceptance\*\*:\n((?:- .+?\n)+)/.exec(taskText) - const acceptance = acceptSection - ? acceptSection[1].split('\n').map(s => s.replace(/^- /, '').trim()).filter(Boolean) + // Parse convergence criteria (replaces acceptance) + const convergenceSection = /\*\*Convergence Criteria\*\*:\n((?:- .+?\n)+)/.exec(taskText) + const convergenceCriteria = convergenceSection + ? convergenceSection[1].split('\n').map(s => s.replace(/^- /, '').trim()).filter(Boolean) : [] const task = { @@ -306,17 +312,26 @@ function extractStructuredTasks(cliOutput, complexity) { scope: scopeMatch?.[1].trim() || "", action: actionMatch?.[1].trim() || "Implement", description: descMatch?.[1].trim() || "", - modification_points: modPoints, + files, implementation, reference, - acceptance, - depends_on: depsMatch?.[1] === '[]' ? [] : (depsMatch?.[1] || "").replace(/[\[\]]/g, '').split(',').map(s => s.trim()).filter(Boolean) + convergence: { criteria: convergenceCriteria }, + depends_on: depsMatch?.[1] === '[]' ? [] : (depsMatch?.[1] || "").replace(/[\[\]]/g, '').split(',').map(s => s.trim()).filter(Boolean).map(id => /^T(\d+)$/.test(id) ? `TASK-${id.slice(1).padStart(3, '0')}` : id) } // Add complexity-specific fields if (complexity === "Medium" || complexity === "High") { task.rationale = extractRationale(taskText) - task.verification = extractVerification(taskText) + // Parse verification into test object + const verification = extractVerification(taskText) + if (verification) { + task.test = { + manual_checks: verification.manual_checks || [], + success_metrics: verification.success_metrics || [], + unit: verification.unit_tests || [], + integration: verification.integration_tests || [] + } + } } if (complexity === "High") { @@ -330,25 +345,6 @@ function extractStructuredTasks(cliOutput, complexity) { return tasks } -// Parse flow control section -function extractFlowControl(cliOutput) { - const flowMatch = /## Flow Control\n\*\*Execution Order\*\*:\n((?:- .+?\n)+)/m.exec(cliOutput) - const exitMatch = /\*\*Exit Conditions\*\*:\n- Success: (.+?)\n- Failure: (.+)/m.exec(cliOutput) - - const execution_order = [] - if (flowMatch) { - flowMatch[1].trim().split('\n').forEach(line => { - const m = /- Phase (.+?): \[(.+?)\] \((.+?)\)/.exec(line) - if (m) execution_order.push({ phase: m[1], tasks: m[2].split(',').map(s => s.trim()), type: m[3].includes('independent') ? 'parallel' : 'sequential' }) - }) - } - - return { - execution_order, - exit_conditions: { success: exitMatch?.[1] || "All acceptance criteria met", failure: exitMatch?.[2] || "Critical task fails" } - } -} - // Parse rationale section for a task function extractRationale(taskText) { const rationaleMatch = /\*\*Rationale\*\*:\n- Chosen Approach: (.+?)\n- Alternatives Considered: (.+?)\n- Decision Factors: (.+?)\n- Tradeoffs: (.+)/s.exec(taskText) @@ -492,7 +488,6 @@ function parseCLIOutput(cliOutput) { approach: extractSection(cliOutput, "Approach") || extractSection(cliOutput, "High-Level Approach"), complexity, raw_tasks: extractStructuredTasks(cliOutput, complexity), - flow_control: extractFlowControl(cliOutput), time_estimate: extractSection(cliOutput, "Time Estimate"), // High complexity only data_flow: complexity === "High" ? extractDataFlow(cliOutput) : null, @@ -505,6 +500,8 @@ function parseCLIOutput(cliOutput) { ### Context Enrichment ```javascript +// NOTE: relevant_files items are structured objects: +// {path, relevance, rationale, role, discovery_source?, key_symbols?} function buildEnrichedContext(explorationsContext, explorationAngles) { const enriched = { relevant_files: [], patterns: [], dependencies: [], integration_points: [], constraints: [] } @@ -519,7 +516,16 @@ function buildEnrichedContext(explorationsContext, explorationAngles) { } }) - enriched.relevant_files = [...new Set(enriched.relevant_files)] + // Deduplicate by path, keep highest relevance entry for each path + const fileMap = new Map() + enriched.relevant_files.forEach(f => { + const path = typeof f === 'string' ? f : f.path + const existing = fileMap.get(path) + if (!existing || (f.relevance || 0) > (existing.relevance || 0)) { + fileMap.set(path, typeof f === 'string' ? { path: f, relevance: 0.5, rationale: 'discovered', role: 'context_only' } : f) + } + }) + enriched.relevant_files = [...fileMap.values()] return enriched } ``` @@ -529,21 +535,23 @@ function buildEnrichedContext(explorationsContext, explorationAngles) { ```javascript function validateAndEnhanceTasks(rawTasks, enrichedContext) { return rawTasks.map((task, idx) => ({ - id: task.id || `T${idx + 1}`, + id: task.id || `TASK-${String(idx + 1).padStart(3, '0')}`, title: task.title || "Unnamed task", - file: task.file || inferFile(task, enrichedContext), + scope: task.scope || task.file || inferFile(task, enrichedContext), action: task.action || inferAction(task.title), description: task.description || task.title, - modification_points: task.modification_points?.length > 0 - ? task.modification_points - : [{ file: task.file, target: "main", change: task.description }], + files: task.files?.length > 0 + ? task.files + : [{ path: task.scope || task.file || inferFile(task, enrichedContext), action: "modify", target: "main", change: task.description }], implementation: task.implementation?.length >= 2 ? task.implementation - : [`Analyze ${task.file}`, `Implement ${task.title}`, `Add error handling`], - reference: task.reference || { pattern: "existing patterns", files: enrichedContext.relevant_files.slice(0, 2), examples: "Follow existing structure" }, - acceptance: task.acceptance?.length >= 1 - ? task.acceptance - : [`${task.title} completed`, `Follows conventions`], + : [`Analyze ${task.scope || task.file}`, `Implement ${task.title}`, `Add error handling`], + reference: task.reference || { pattern: "existing patterns", files: enrichedContext.relevant_files.slice(0, 2).map(f => typeof f === 'string' ? f : f.path), examples: "Follow existing structure" }, + convergence: { + criteria: task.convergence?.criteria?.length >= 1 + ? task.convergence.criteria + : [`${task.title} completed`, `Follows conventions`] + }, depends_on: task.depends_on || [] })) } @@ -554,9 +562,11 @@ function inferAction(title) { return match ? match[1] : "Implement" } +// NOTE: relevant_files items are structured objects with .path property function inferFile(task, ctx) { const files = ctx?.relevant_files || [] - return files.find(f => task.title.toLowerCase().includes(f.split('/').pop().split('.')[0].toLowerCase())) || "file-to-be-determined.ts" + const getPath = f => typeof f === 'string' ? f : f.path + return getPath(files.find(f => task.title.toLowerCase().includes(getPath(f).split('/').pop().split('.')[0].toLowerCase())) || {}) || "file-to-be-determined.ts" } ``` @@ -601,54 +611,49 @@ function assignCliExecutionIds(tasks, sessionId) { | depends_on | Parent Children | Strategy | CLI Command | |------------|-----------------|----------|-------------| | [] | - | `new` | `--id {cli_execution_id}` | -| [T1] | 1 | `resume` | `--resume {resume_from}` | -| [T1] | >1 | `fork` | `--resume {resume_from} --id {cli_execution_id}` | -| [T1,T2] | - | `merge_fork` | `--resume {ids.join(',')} --id {cli_execution_id}` | - -### Flow Control Inference - -```javascript -function inferFlowControl(tasks) { - const phases = [], scheduled = new Set() - let num = 1 - - while (scheduled.size < tasks.length) { - const ready = tasks.filter(t => !scheduled.has(t.id) && t.depends_on.every(d => scheduled.has(d))) - if (!ready.length) break - - const isParallel = ready.length > 1 && ready.every(t => !t.depends_on.length) - phases.push({ phase: `${isParallel ? 'parallel' : 'sequential'}-${num}`, tasks: ready.map(t => t.id), type: isParallel ? 'parallel' : 'sequential' }) - ready.forEach(t => scheduled.add(t.id)) - num++ - } - - return { execution_order: phases, exit_conditions: { success: "All acceptance criteria met", failure: "Critical task fails" } } -} -``` +| [TASK-001] | 1 | `resume` | `--resume {resume_from}` | +| [TASK-001] | >1 | `fork` | `--resume {resume_from} --id {cli_execution_id}` | +| [TASK-001,TASK-002] | - | `merge_fork` | `--resume {ids.join(',')} --id {cli_execution_id}` | ### planObject Generation ```javascript +// Write individual task files to .task/ directory +function writeTaskFiles(tasks, sessionFolder) { + const taskDir = `${sessionFolder}/.task` + Bash(`mkdir -p "${taskDir}"`) + tasks.forEach(task => { + Write(`${taskDir}/${task.id}.json`, JSON.stringify(task, null, 2)) + }) + return tasks.map(t => t.id) +} + function generatePlanObject(parsed, enrichedContext, input, schemaType) { const complexity = parsed.complexity || input.complexity || "Medium" const tasks = validateAndEnhanceTasks(parsed.raw_tasks, enrichedContext, complexity) assignCliExecutionIds(tasks, input.session.id) // MANDATORY: Assign CLI execution IDs - const flow_control = parsed.flow_control?.execution_order?.length > 0 ? parsed.flow_control : inferFlowControl(tasks) - const focus_paths = [...new Set(tasks.flatMap(t => [t.file || t.scope, ...t.modification_points.map(m => m.file)]).filter(Boolean))] - // Base fields (common to both schemas) + // Write individual task files and collect IDs + const task_ids = writeTaskFiles(tasks, input.session.folder) + + // Determine plan_type from schema + const plan_type = schemaType === 'fix-plan' ? 'fix' : 'feature' + + // Base fields (plan overview - NO tasks[], NO flow_control, NO focus_paths) const base = { summary: parsed.summary || `Plan for: ${input.task_description.slice(0, 100)}`, - tasks, - flow_control, - focus_paths, + approach: parsed.approach || "Step-by-step implementation", + task_ids, + task_count: task_ids.length, estimated_time: parsed.time_estimate || `${tasks.length * 30} minutes`, recommended_execution: (complexity === "Low" || input.severity === "Low") ? "Agent" : "Codex", _metadata: { timestamp: new Date().toISOString(), source: "cli-lite-planning-agent", + plan_type, + schema_version: "2.0", planning_mode: "agent-based", - context_angles: input.contextAngles || [], + exploration_angles: input.contextAngles || [], duration_seconds: Math.round((Date.now() - startTime) / 1000) } } @@ -669,12 +674,12 @@ function generatePlanObject(parsed, enrichedContext, input, schemaType) { root_cause: parsed.root_cause || "Root cause from diagnosis", strategy: parsed.strategy || "comprehensive_fix", severity: input.severity || "Medium", - risk_level: parsed.risk_level || "medium" + risk_level: parsed.risk_level || "medium", + complexity } } else { return { ...base, - approach: parsed.approach || "Step-by-step implementation", complexity } } @@ -684,21 +689,23 @@ function generatePlanObject(parsed, enrichedContext, input, schemaType) { function validateAndEnhanceTasks(rawTasks, enrichedContext, complexity) { return rawTasks.map((task, idx) => { const enhanced = { - id: task.id || `T${idx + 1}`, + id: task.id || `TASK-${String(idx + 1).padStart(3, '0')}`, title: task.title || "Unnamed task", scope: task.scope || task.file || inferFile(task, enrichedContext), action: task.action || inferAction(task.title), description: task.description || task.title, - modification_points: task.modification_points?.length > 0 - ? task.modification_points - : [{ file: task.scope || task.file, target: "main", change: task.description }], + files: task.files?.length > 0 + ? task.files + : [{ path: task.scope || task.file || inferFile(task, enrichedContext), action: "modify", target: "main", change: task.description }], implementation: task.implementation?.length >= 2 ? task.implementation : [`Analyze ${task.scope || task.file}`, `Implement ${task.title}`, `Add error handling`], - reference: task.reference || { pattern: "existing patterns", files: enrichedContext.relevant_files.slice(0, 2), examples: "Follow existing structure" }, - acceptance: task.acceptance?.length >= 1 - ? task.acceptance - : [`${task.title} completed`, `Follows conventions`], + reference: task.reference || { pattern: "existing patterns", files: enrichedContext.relevant_files.slice(0, 2).map(f => typeof f === 'string' ? f : f.path), examples: "Follow existing structure" }, + convergence: { + criteria: task.convergence?.criteria?.length >= 1 + ? task.convergence.criteria + : [`${task.title} completed`, `Follows conventions`] + }, depends_on: task.depends_on || [] } @@ -710,11 +717,11 @@ function validateAndEnhanceTasks(rawTasks, enrichedContext, complexity) { decision_factors: ["Maintainability", "Performance"], tradeoffs: "None significant" } - enhanced.verification = task.verification || { - unit_tests: [`test_${task.id.toLowerCase()}_basic`], - integration_tests: [], + enhanced.test = task.test || { manual_checks: ["Verify expected behavior"], - success_metrics: ["All tests pass"] + success_metrics: ["All tests pass"], + unit: [`test_${task.id.toLowerCase().replace(/-/g, '_')}_basic`], + integration: [] } } @@ -747,20 +754,24 @@ try { } else throw error } -function generateBasicPlan(taskDesc, ctx) { - const files = ctx?.relevant_files || [] +// NOTE: relevant_files items are structured objects with .path property +function generateBasicPlan(taskDesc, ctx, sessionFolder) { + const relevantFiles = (ctx?.relevant_files || []).map(f => typeof f === 'string' ? f : f.path) const tasks = [taskDesc].map((t, i) => ({ - id: `T${i + 1}`, title: t, file: files[i] || "tbd", action: "Implement", description: t, - modification_points: [{ file: files[i] || "tbd", target: "main", change: t }], + id: `TASK-${String(i + 1).padStart(3, '0')}`, title: t, scope: relevantFiles[i] || "tbd", action: "Implement", description: t, + files: [{ path: relevantFiles[i] || "tbd", action: "modify", target: "main", change: t }], implementation: ["Analyze structure", "Implement feature", "Add validation"], - acceptance: ["Task completed", "Follows conventions"], depends_on: [] + convergence: { criteria: ["Task completed", "Follows conventions"] }, depends_on: [] })) + // Write task files + const task_ids = writeTaskFiles(tasks, sessionFolder) + return { - summary: `Direct implementation: ${taskDesc}`, approach: "Step-by-step", tasks, - flow_control: { execution_order: [{ phase: "sequential-1", tasks: tasks.map(t => t.id), type: "sequential" }], exit_conditions: { success: "Done", failure: "Fails" } }, - focus_paths: files, estimated_time: "30 minutes", recommended_execution: "Agent", complexity: "Low", - _metadata: { timestamp: new Date().toISOString(), source: "cli-lite-planning-agent", planning_mode: "direct", exploration_angles: [], duration_seconds: 0 } + summary: `Direct implementation: ${taskDesc}`, approach: "Step-by-step", + task_ids, task_count: task_ids.length, + estimated_time: "30 minutes", recommended_execution: "Agent", complexity: "Low", + _metadata: { timestamp: new Date().toISOString(), source: "cli-lite-planning-agent", plan_type: "feature", schema_version: "2.0", planning_mode: "direct", exploration_angles: [], duration_seconds: 0 } } } ``` @@ -772,21 +783,21 @@ function generateBasicPlan(taskDesc, ctx) { ```javascript function validateTask(task) { const errors = [] - if (!/^T\d+$/.test(task.id)) errors.push("Invalid task ID") + if (!/^TASK-\d{3}$/.test(task.id) && !/^FIX-\d{3}$/.test(task.id)) errors.push("Invalid task ID (expected TASK-NNN or FIX-NNN)") if (!task.title?.trim()) errors.push("Missing title") - if (!task.file?.trim()) errors.push("Missing file") + if (!task.description?.trim()) errors.push("Missing description") if (!['Create', 'Update', 'Implement', 'Refactor', 'Add', 'Delete', 'Configure', 'Test', 'Fix'].includes(task.action)) errors.push("Invalid action") if (!task.implementation?.length >= 2) errors.push("Need 2+ implementation steps") - if (!task.acceptance?.length >= 1) errors.push("Need 1+ acceptance criteria") - if (task.depends_on?.some(d => !/^T\d+$/.test(d))) errors.push("Invalid dependency format") - if (task.acceptance?.some(a => /works correctly|good performance/i.test(a))) errors.push("Vague acceptance criteria") + if (!task.convergence?.criteria?.length >= 1) errors.push("Need 1+ convergence criteria") + if (task.depends_on?.some(d => !/^(TASK|FIX)-\d{3}$/.test(d))) errors.push("Invalid dependency format") + if (task.convergence?.criteria?.some(c => /works correctly|good performance/i.test(c))) errors.push("Vague convergence criteria") return { valid: !errors.length, errors } } ``` -### Acceptance Criteria +### Convergence Criteria Quality -| ✓ Good | ✗ Bad | +| Good | Bad | |--------|-------| | "3 methods: login(), logout(), validate()" | "Service works correctly" | | "Response time < 200ms p95" | "Good performance" | @@ -797,12 +808,12 @@ function validateTask(task) { **ALWAYS**: - **Search Tool Priority**: ACE (`mcp__ace-tool__search_context`) → CCW (`mcp__ccw-tools__smart_search`) / Built-in (`Grep`, `Glob`, `Read`) - **Read schema first** to determine output structure -- Generate task IDs (T1/T2 for plan, FIX1/FIX2 for fix-plan) +- Generate task IDs (TASK-001/TASK-002 for plan, FIX-001/FIX-002 for fix-plan) - Include depends_on (even if empty []) - **Assign cli_execution_id** (`{sessionId}-{taskId}`) - **Compute cli_execution strategy** based on depends_on -- Quantify acceptance/verification criteria -- Generate flow_control from dependencies +- Quantify convergence criteria and test metrics +- **Write BOTH plan.json AND .task/*.json files** (two-layer output) - Handle CLI errors with fallback chain **Bash Tool**: @@ -810,12 +821,13 @@ function validateTask(task) { **NEVER**: - Execute implementation (return plan only) -- Use vague acceptance criteria +- Use vague convergence criteria - Create circular dependencies - Skip task validation - **Skip CLI execution ID assignment** - **Ignore schema structure** - **Skip Phase 5 Plan Quality Check** +- **Embed tasks[] in plan.json** (use task_ids[] referencing .task/ files) --- @@ -832,7 +844,7 @@ After generating plan.json, **MUST** execute CLI quality check before returning | **Completeness** | All user requirements reflected in tasks | Yes | | **Task Granularity** | Each task 15-60 min scope | No | | **Dependencies** | No circular deps, correct ordering | Yes | -| **Acceptance Criteria** | Quantified and testable (not vague) | No | +| **Convergence Criteria** | Quantified and testable (not vague) | No | | **Implementation Steps** | 2+ actionable steps per task | No | | **Constraint Compliance** | Follows project-guidelines.json | Yes | @@ -841,9 +853,9 @@ After generating plan.json, **MUST** execute CLI quality check before returning Use `ccw cli` with analysis mode to validate plan against quality dimensions: ```bash -ccw cli -p "Validate plan quality: completeness, granularity, dependencies, acceptance criteria, implementation steps, constraint compliance" \ +ccw cli -p "Validate plan quality: completeness, granularity, dependencies, convergence criteria, implementation steps, constraint compliance" \ --tool gemini --mode analysis \ - --context "@{plan_json_path} @.workflow/project-guidelines.json" + --context "@{plan_json_path} @{task_dir}/*.json @.workflow/project-guidelines.json" ``` **Expected Output Structure**: @@ -855,7 +867,7 @@ ccw cli -p "Validate plan quality: completeness, granularity, dependencies, acce ### Result Parsing Parse CLI output sections using regex to extract: -- **6 Dimension Results**: Each with `passed` boolean and issue lists (missing requirements, oversized/undersized tasks, vague criteria, etc.) +- **6 Dimension Results**: Each with `passed` boolean and issue lists (missing requirements, oversized/undersized tasks, vague convergence criteria, etc.) - **Summary Counts**: Critical issues, minor issues - **Recommendation**: `PASS` | `AUTO_FIX` | `REGENERATE` - **Fixes**: Optional JSON patches for auto-fixable issues @@ -866,7 +878,7 @@ Apply automatic fixes for minor issues: | Issue Type | Auto-Fix Action | Example | |-----------|----------------|---------| -| **Vague Acceptance** | Replace with quantified criteria | "works correctly" → "All unit tests pass with 100% success rate" | +| **Vague Convergence** | Replace with quantified criteria | "works correctly" → "All unit tests pass with 100% success rate" | | **Insufficient Steps** | Expand to 4-step template | Add: Analyze → Implement → Error handling → Verify | | **CLI-Provided Patches** | Apply JSON patches from CLI output | Update task fields per patch specification | @@ -876,7 +888,7 @@ After fixes, update `_metadata.quality_check` with fix log. After Phase 4 planObject generation: -1. **Write Initial Plan** → `${sessionFolder}/plan.json` +1. **Write Task Files** → `${sessionFolder}/.task/TASK-*.json` + **Write Plan** → `${sessionFolder}/plan.json` 2. **Execute CLI Check** → Gemini (Qwen fallback) 3. **Parse Results** → Extract recommendation and issues 4. **Handle Recommendation**: diff --git a/.claude/agents/context-search-agent.md b/.claude/agents/context-search-agent.md index adff37dd..a7db2745 100644 --- a/.claude/agents/context-search-agent.md +++ b/.claude/agents/context-search-agent.md @@ -165,12 +165,16 @@ if (file_exists(manifestPath)) { } // Synthesis helper functions (conceptual) +// NOTE: relevant_files items are now structured objects: +// {path, relevance, rationale, role, discovery_source?, key_symbols?} function synthesizeCriticalFiles(allRelevantFiles) { - // 1. Group by path + // 1. Group by path (files are objects with .path property) // 2. Count mentions across angles // 3. Average relevance scores - // 4. Rank by: (mention_count * 0.6) + (avg_relevance * 0.4) - // 5. Return top 10-15 with mentioned_by_angles attribution + // 4. Merge rationales from different angles (join with "; ") + // 5. Collect unique roles and key_symbols across angles + // 6. Rank by: (mention_count * 0.6) + (avg_relevance * 0.4) + // 7. Return top 10-15 with: path, relevance, rationale, role, mentioned_by_angles, key_symbols } function synthesizeConflictIndicators(explorationData) { @@ -544,7 +548,7 @@ Calculate risk level based on: } ], "aggregated_insights": { - "critical_files": [{"path": "src/auth/AuthService.ts", "relevance": 0.95, "mentioned_by_angles": ["architecture"]}], + "critical_files": [{"path": "src/auth/AuthService.ts", "relevance": 0.95, "rationale": "Contains login/register/verifyToken - core auth entry points", "role": "modify_target", "mentioned_by_angles": ["architecture"], "key_symbols": ["AuthService", "login", "verifyToken"]}], "conflict_indicators": [{"type": "pattern_mismatch", "description": "...", "source_angle": "architecture", "severity": "medium"}], "clarification_needs": [{"question": "...", "context": "...", "options": [], "source_angle": "architecture"}], "constraints": [{"constraint": "Must follow existing DI pattern", "source_angle": "architecture"}], diff --git a/.claude/commands/team/plan.md b/.claude/commands/team/plan.md index 1758361b..e8b231d3 100644 --- a/.claude/commands/team/plan.md +++ b/.claude/commands/team/plan.md @@ -52,7 +52,7 @@ mcp__ccw-tools__team_msg({ operation: "log", team: teamName, from: "planner", to mcp__ccw-tools__team_msg({ operation: "log", team: teamName, from: "planner", to: "coordinator", type: "plan_revision", summary: "已按反馈拆分task-2为两个子任务" }) // 错误上报 -mcp__ccw-tools__team_msg({ operation: "log", team: teamName, from: "planner", to: "coordinator", type: "error", summary: "plan-json-schema.json 未找到, 使用默认结构" }) +mcp__ccw-tools__team_msg({ operation: "log", team: teamName, from: "planner", to: "coordinator", type: "error", summary: "plan-overview-base-schema.json 未找到, 使用默认结构" }) ``` ## Execution Process @@ -73,10 +73,10 @@ Phase 2: Multi-Angle Exploration └─ Write exploration results to session folder Phase 3: Plan Generation - ├─ Read plan-json-schema.json for structure reference + ├─ Read plan-overview-base-schema.json + task-schema.json for structure reference ├─ Low complexity → Direct Claude planning ├─ Medium/High → cli-lite-planning-agent - └─ Output: plan.json + └─ Output: plan.json (overview with task_ids[]) + .task/TASK-*.json (independent task files) Phase 4: Submit for Approval ├─ SendMessage plan summary to coordinator @@ -208,6 +208,12 @@ Execute **${angle}** exploration for task planning context. ## Expected Output Write JSON to: ${sessionFolder}/exploration-${angle}.json Follow explore-json-schema.json structure with ${angle}-focused findings. + +**MANDATORY**: Every file in relevant_files MUST have: +- **rationale** (required): Specific selection basis tied to ${angle} topic (>10 chars, not generic) +- **role** (required): modify_target|dependency|pattern_reference|test_target|type_definition|integration_point|config|context_only +- **discovery_source** (recommended): bash-scan|cli-analysis|ace-search|dependency-trace|manual +- **key_symbols** (recommended): Key functions/classes/types relevant to task ` }) }) @@ -232,7 +238,7 @@ Write(`${sessionFolder}/explorations-manifest.json`, JSON.stringify(explorationM ```javascript // Read schema reference -const schema = Bash(`cat ~/.ccw/workflows/cli-templates/schemas/plan-json-schema.json`) +const schema = Bash(`cat ~/.ccw/workflows/cli-templates/schemas/plan-overview-base-schema.json`) if (complexity === 'Low') { // Direct Claude planning @@ -242,18 +248,31 @@ if (complexity === 'Low') { // Incorporate findings into plan }) - // Generate plan following schema + // Generate task files in .task/ directory + Bash(`mkdir -p ${sessionFolder}/.task`) + + const tasks = [/* structured tasks with dependencies, files[].change, convergence.criteria */] + const taskIds = tasks.map(t => t.id) + + // Write individual task files following task-schema.json + tasks.forEach(task => { + Write(`${sessionFolder}/.task/${task.id}.json`, JSON.stringify(task, null, 2)) + }) + + // Generate plan overview following plan-overview-base-schema.json const plan = { summary: "...", approach: "...", - tasks: [/* structured tasks with dependencies, modification points, acceptance criteria */], + task_ids: taskIds, + task_count: taskIds.length, estimated_time: "...", recommended_execution: "Agent", complexity: "Low", _metadata: { timestamp: new Date().toISOString(), source: "team-planner", - planning_mode: "direct" + planning_mode: "direct", + plan_type: "feature" } } Write(`${sessionFolder}/plan.json`, JSON.stringify(plan, null, 2)) @@ -264,16 +283,26 @@ if (complexity === 'Low') { run_in_background: false, description: "Generate detailed implementation plan", prompt: ` -Generate implementation plan and write plan.json. +Generate implementation plan with two-layer output. ## Output Location **Session Folder**: ${sessionFolder} **Output Files**: - ${sessionFolder}/planning-context.md -- ${sessionFolder}/plan.json +- ${sessionFolder}/plan.json (overview with task_ids[]) +- ${sessionFolder}/.task/TASK-*.json (independent task files) ## Output Schema Reference -Execute: cat ~/.ccw/workflows/cli-templates/schemas/plan-json-schema.json +Execute: cat ~/.ccw/workflows/cli-templates/schemas/plan-overview-base-schema.json +Execute: cat ~/.ccw/workflows/cli-templates/schemas/task-schema.json + +## Output Format: Two-Layer Structure +- plan.json: Overview with task_ids[] referencing .task/ files (NO tasks[] array) +- .task/TASK-*.json: Independent task files following task-schema.json + +plan.json required: summary, approach, task_ids, task_count, _metadata (with plan_type) +Task files required: id, title, description, depends_on, convergence (with criteria[]) +Task fields: files[].change (not modification_points), convergence.criteria (not acceptance), test (not verification) ## Task Description ${task.description} @@ -286,9 +315,9 @@ Path: ${exp.path}`).join('\n\n')} ${complexity} ## Requirements -Generate plan.json following schema. Key constraints: -- tasks: 2-7 structured tasks (group by feature/module, NOT by file) -- Each task: id, title, scope, modification_points, implementation, acceptance, depends_on +Generate plan.json + .task/*.json following schemas. Key constraints: +- 2-7 structured tasks (group by feature/module, NOT by file) +- Each task file: id, title, description, files[].change, convergence.criteria, depends_on - Prefer parallel tasks (minimize depends_on) ` }) @@ -301,6 +330,10 @@ Generate plan.json following schema. Key constraints: // Read generated plan const plan = JSON.parse(Read(`${sessionFolder}/plan.json`)) +// Load tasks from .task/ directory (two-layer format) +const tasks = plan.task_ids.map(id => JSON.parse(Read(`${sessionFolder}/.task/${id}.json`))) +const taskCount = plan.task_count || plan.task_ids.length + // Send plan summary to coordinator SendMessage({ type: "message", @@ -309,19 +342,20 @@ SendMessage({ **Task**: ${task.subject} **Complexity**: ${complexity} -**Tasks**: ${plan.tasks.length} +**Tasks**: ${taskCount} ### Task Summary -${plan.tasks.map((t, i) => `${i+1}. ${t.title} (${t.scope || 'N/A'})`).join('\n')} +${tasks.map((t, i) => `${i+1}. ${t.title} (${t.scope || 'N/A'})`).join('\n')} ### Approach ${plan.approach} ### Plan Location ${sessionFolder}/plan.json +${plan.task_ids ? `Task Files: ${sessionFolder}/.task/` : ''} Please review and approve or request revisions.`, - summary: `Plan ready: ${plan.tasks.length} tasks` + summary: `Plan ready: ${taskCount} tasks` }) // Wait for coordinator response @@ -359,7 +393,11 @@ if (nextTasks.length > 0) { ├── exploration-{angle2}.json ├── explorations-manifest.json # Exploration index ├── planning-context.md # Evidence + understanding (Medium/High) -└── plan.json # Implementation plan +├── plan.json # Plan overview with task_ids[] (NO embedded tasks[]) +└── .task/ # Independent task files + ├── TASK-001.json # Task file following task-schema.json + ├── TASK-002.json + └── ... ``` ## Error Handling diff --git a/.claude/commands/workflow/lite-fix.md b/.claude/commands/workflow/lite-fix.md index d0188034..985020f1 100644 --- a/.claude/commands/workflow/lite-fix.md +++ b/.claude/commands/workflow/lite-fix.md @@ -44,7 +44,8 @@ Intelligent lightweight bug fixing command with dynamic workflow adaptation base | `diagnosis-{angle}.json` | Per-angle diagnosis results (1-4 files based on severity) | | `diagnoses-manifest.json` | Index of all diagnosis files | | `planning-context.md` | Evidence paths + synthesized understanding | -| `fix-plan.json` | Structured fix plan (fix-plan-json-schema.json) | +| `fix-plan.json` | Fix plan overview with `task_ids[]` (plan-overview-fix-schema.json) | +| `.task/FIX-*.json` | Independent fix task files (one per task) | **Output Directory**: `.workflow/.lite-fix/{bug-slug}-{YYYY-MM-DD}/` @@ -52,7 +53,7 @@ Intelligent lightweight bug fixing command with dynamic workflow adaptation base - Low/Medium severity → Direct Claude planning (no agent) - High/Critical severity → `cli-lite-planning-agent` generates `fix-plan.json` -**Schema Reference**: `~/.ccw/workflows/cli-templates/schemas/fix-plan-json-schema.json` +**Schema Reference**: `~/.ccw/workflows/cli-templates/schemas/plan-overview-fix-schema.json` ## Auto Mode Defaults @@ -91,7 +92,7 @@ Phase 2: Clarification (optional, multi-round) Phase 3: Fix Planning (NO CODE EXECUTION - planning only) +- Decision (based on Phase 1 severity): - |- Low/Medium -> Load schema: cat ~/.ccw/workflows/cli-templates/schemas/fix-plan-json-schema.json -> Direct Claude planning (following schema) -> fix-plan.json -> MUST proceed to Phase 4 + |- Low/Medium -> Load schema: cat ~/.ccw/workflows/cli-templates/schemas/plan-overview-fix-schema.json -> Direct Claude planning (following schema) -> fix-plan.json -> MUST proceed to Phase 4 +- High/Critical -> cli-lite-planning-agent -> fix-plan.json -> MUST proceed to Phase 4 Phase 4: Confirmation & Selection @@ -254,8 +255,12 @@ Execute **${angle}** diagnosis for bug root cause analysis. Analyze codebase fro **IMPORTANT**: Use structured format: \`{file: "src/module/file.ts", line_range: "45-60", issue: "Description", confidence: 0.85}\` - affected_files: Files involved from ${angle} perspective - **IMPORTANT**: Use object format with relevance scores: - \`[{path: "src/file.ts", relevance: 0.85, rationale: "Contains ${angle} logic"}]\` + **MANDATORY**: Every file MUST use structured object format with ALL required fields: + \`[{path: "src/file.ts", relevance: 0.85, rationale: "Contains handleLogin() at line 45 where null check is missing", change_type: "fix_target", discovery_source: "bash-scan", key_symbols: ["handleLogin"]}]\` + - **rationale** (required): Specific reason why this file is affected (>10 chars, not generic) + - **change_type** (required): fix_target|needs_update|test_coverage|reference_only + - **discovery_source** (recommended): bash-scan|cli-analysis|ace-search|dependency-trace|stack-trace|manual + - **key_symbols** (recommended): Key functions/classes related to the bug - reproduction_steps: Steps to reproduce the bug - fix_hints: Suggested fix approaches from ${angle} viewpoint - dependencies: Dependencies relevant to ${angle} diagnosis @@ -268,7 +273,9 @@ Execute **${angle}** diagnosis for bug root cause analysis. Analyze codebase fro - [ ] Schema obtained via cat diagnosis-json-schema.json - [ ] get_modules_by_depth.sh executed - [ ] Root cause identified with confidence score -- [ ] At least 3 affected files identified with ${angle} rationale +- [ ] At least 3 affected files identified with specific rationale + change_type +- [ ] Every file has rationale >10 chars (not generic like "Contains ${angle} logic") +- [ ] Every file has change_type classification (fix_target/needs_update/etc.) - [ ] Fix hints are actionable (specific code changes, not generic advice) - [ ] Reproduction steps are verifiable - [ ] JSON output follows schema exactly @@ -426,21 +433,74 @@ if (autoYes) { **Low/Medium Severity** - Direct planning by Claude: ```javascript // Step 1: Read schema -const schema = Bash(`cat ~/.ccw/workflows/cli-templates/schemas/fix-plan-json-schema.json`) +const schema = Bash(`cat ~/.ccw/workflows/cli-templates/schemas/plan-overview-fix-schema.json`) -// Step 2: Generate fix-plan following schema (Claude directly, no agent) -// For Medium complexity: include rationale + verification (optional, but recommended) +// Step 2: Generate fix tasks with NEW field names (Claude directly, no agent) +// Field mapping: modification_points -> files, acceptance -> convergence, verification -> test +const fixTasks = [ + { + id: "FIX-001", + title: "...", + description: "...", + scope: "...", + action: "Fix|Update|Refactor|Add|Delete", + depends_on: [], + convergence: { + criteria: ["..."] // Quantified acceptance criteria + }, + files: [ + { path: "src/module/file.ts", action: "modify", target: "functionName", change: "Description of change" } + ], + implementation: ["Step 1: ...", "Step 2: ..."], + test: { + manual_checks: ["Reproduce issue", "Verify fix"], + success_metrics: ["Issue resolved", "No regressions"] + }, + complexity: "Low|Medium", + + // Medium severity fields (optional for Low, recommended for Medium) + ...(severity === "Medium" ? { + rationale: { + chosen_approach: "Direct fix approach", + alternatives_considered: ["Workaround", "Refactor"], + decision_factors: ["Minimal impact", "Quick turnaround"], + tradeoffs: "Doesn't address underlying issue" + }, + test: { + unit: ["test_bug_fix_basic"], + integration: [], + manual_checks: ["Reproduce issue", "Verify fix"], + success_metrics: ["Issue resolved", "No regressions"] + } + } : {}) + } + // ... additional tasks as needed +] + +// Step 3: Write individual task files to .task/ directory +const taskDir = `${sessionFolder}/.task` +Bash(`mkdir -p "${taskDir}"`) + +fixTasks.forEach(task => { + Write(`${taskDir}/${task.id}.json`, JSON.stringify(task, null, 2)) +}) + +// Step 4: Generate fix-plan overview (NO embedded tasks[]) const fixPlan = { summary: "...", - root_cause: "...", - strategy: "immediate_patch|comprehensive_fix|refactor", - tasks: [...], // Each task: { id, title, scope, ..., depends_on, complexity } + approach: "...", + task_ids: fixTasks.map(t => t.id), + task_count: fixTasks.length, + fix_context: { + root_cause: "...", + strategy: "immediate_patch|comprehensive_fix|refactor", + severity: severity, + risk_level: "..." + }, estimated_time: "...", recommended_execution: "Agent", - severity: severity, - risk_level: "...", - // Medium complexity fields (optional for direct planning, auto-filled for Low) + // Medium complexity fields (optional for Low) ...(severity === "Medium" ? { design_decisions: [ { @@ -448,58 +508,22 @@ const fixPlan = { rationale: "Keeps changes localized and quick to review", tradeoff: "Defers comprehensive refactoring" } - ], - tasks_with_rationale: { - // Each task gets rationale if Medium - task_rationale_example: { - rationale: { - chosen_approach: "Direct fix approach", - alternatives_considered: ["Workaround", "Refactor"], - decision_factors: ["Minimal impact", "Quick turnaround"], - tradeoffs: "Doesn't address underlying issue" - }, - verification: { - unit_tests: ["test_bug_fix_basic"], - integration_tests: [], - manual_checks: ["Reproduce issue", "Verify fix"], - success_metrics: ["Issue resolved", "No regressions"] - } - } - } + ] } : {}), _metadata: { timestamp: getUtc8ISOString(), source: "direct-planning", planning_mode: "direct", + plan_type: "fix", complexity: severity === "Medium" ? "Medium" : "Low" } } -// Step 3: Merge task rationale into tasks array -if (severity === "Medium") { - fixPlan.tasks = fixPlan.tasks.map(task => ({ - ...task, - rationale: fixPlan.tasks_with_rationale[task.id]?.rationale || { - chosen_approach: "Standard fix", - alternatives_considered: [], - decision_factors: ["Correctness", "Simplicity"], - tradeoffs: "None" - }, - verification: fixPlan.tasks_with_rationale[task.id]?.verification || { - unit_tests: [`test_${task.id}_basic`], - integration_tests: [], - manual_checks: ["Verify fix works"], - success_metrics: ["Test pass"] - } - })) - delete fixPlan.tasks_with_rationale // Clean up temp field -} - -// Step 4: Write fix-plan to session folder +// Step 5: Write fix-plan overview to session folder Write(`${sessionFolder}/fix-plan.json`, JSON.stringify(fixPlan, null, 2)) -// Step 5: MUST continue to Phase 4 (Confirmation) - DO NOT execute code here +// Step 6: MUST continue to Phase 4 (Confirmation) - DO NOT execute code here ``` **High/Critical Severity** - Invoke cli-lite-planning-agent: @@ -510,17 +534,18 @@ Task( run_in_background=false, description="Generate detailed fix plan", prompt=` -Generate fix plan and write fix-plan.json. +Generate fix plan using two-layer output format. ## Output Location **Session Folder**: ${sessionFolder} **Output Files**: - ${sessionFolder}/planning-context.md (evidence + understanding) -- ${sessionFolder}/fix-plan.json (fix plan) +- ${sessionFolder}/fix-plan.json (fix plan overview -- NO embedded tasks[]) +- ${sessionFolder}/.task/FIX-*.json (independent fix task files, one per task) ## Output Schema Reference -Execute: cat ~/.ccw/workflows/cli-templates/schemas/fix-plan-json-schema.json (get schema reference before generating plan) +Execute: cat ~/.ccw/workflows/cli-templates/schemas/plan-overview-fix-schema.json (get schema reference before generating plan) ## Project Context (MANDATORY - Read Both Files) 1. Read: .workflow/project-tech.json (technology stack, architecture, key components) @@ -550,20 +575,49 @@ ${JSON.stringify(clarificationContext) || "None"} ${severity} ## Requirements -Generate fix-plan.json with: + +**Output Format**: Two-layer structure: +- fix-plan.json: Overview with task_ids[] referencing .task/ files (NO tasks[] array) +- .task/FIX-*.json: Independent task files following task-schema.json + +**fix-plan.json required fields**: - summary: 2-3 sentence overview of the fix -- root_cause: Consolidated root cause from all diagnoses -- strategy: "immediate_patch" | "comprehensive_fix" | "refactor" -- tasks: 1-5 structured fix tasks (**IMPORTANT: group by fix area, NOT by file**) - - **Task Granularity Principle**: Each task = one complete fix unit - - title: action verb + target (e.g., "Fix token validation edge case") - - scope: module path (src/auth/) or feature name - - action: "Fix" | "Update" | "Refactor" | "Add" | "Delete" - - description - - modification_points: ALL files to modify for this fix (group related changes) - - implementation (2-5 steps covering all modification_points) - - acceptance: Quantified acceptance criteria - - depends_on: task IDs this task depends on (use sparingly) +- approach: Overall fix approach description +- task_ids: Array of task IDs (e.g., ["FIX-001", "FIX-002"]) +- task_count: Number of tasks +- fix_context: + - root_cause: Consolidated root cause from all diagnoses + - strategy: "immediate_patch" | "comprehensive_fix" | "refactor" + - severity: ${severity} + - risk_level: "Low" | "Medium" | "High" +- estimated_time, recommended_execution +- data_flow (High/Critical REQUIRED): How data flows through affected code + - diagram: "A -> B -> C" style flow + - stages: [{stage, input, output, component}] +- design_decisions (High/Critical REQUIRED): Global fix decisions + - [{decision, rationale, tradeoff}] +- _metadata: + - timestamp, source, planning_mode + - plan_type: "fix" + - complexity: "High" | "Critical" + - diagnosis_angles: ${JSON.stringify(manifest.diagnoses.map(d => d.angle))} + +**Each .task/FIX-*.json required fields**: +- id: "FIX-001" (prefix FIX-, NOT TASK-) +- title: action verb + target (e.g., "Fix token validation edge case") +- description +- scope: module path (src/auth/) or feature name +- action: "Fix" | "Update" | "Refactor" | "Add" | "Delete" +- depends_on: task IDs this task depends on (use sparingly) +- convergence: { criteria: ["Quantified acceptance criterion 1", "..."] } +- files: ALL files to modify for this fix (group related changes) + - [{ path: "src/file.ts", action: "modify|create|delete", target: "component/function", change: "Description of what changes" }] +- implementation: ["Step 1: ...", "Step 2: ..."] (2-5 steps covering all files) +- test: + - unit: ["test names to add/verify"] + - integration: ["integration test names"] + - manual_checks: ["manual verification steps"] + - success_metrics: ["quantified success criteria"] **High/Critical complexity fields per task** (REQUIRED): - rationale: @@ -571,11 +625,6 @@ Generate fix-plan.json with: - alternatives_considered: Other approaches evaluated - decision_factors: Key factors influencing choice - tradeoffs: Known tradeoffs of this approach - - verification: - - unit_tests: Test names to add/verify - - integration_tests: Integration test names - - manual_checks: Manual verification steps - - success_metrics: Quantified success criteria - risks: - description: Risk description - probability: Low|Medium|High @@ -586,18 +635,10 @@ Generate fix-plan.json with: - interfaces: [{name, definition, purpose}] - key_functions: [{signature, purpose, returns}] -**Top-level High/Critical fields** (REQUIRED): -- data_flow: How data flows through affected code - - diagram: "A → B → C" style flow - - stages: [{stage, input, output, component}] -- design_decisions: Global fix decisions - - [{decision, rationale, tradeoff}] - -- estimated_time, recommended_execution, severity, risk_level -- _metadata: - - timestamp, source, planning_mode - - complexity: "High" | "Critical" - - diagnosis_angles: ${JSON.stringify(manifest.diagnoses.map(d => d.angle))} +**Field name rules** (do NOT use old names): +- files[].change (NOT modification_points) +- convergence.criteria (NOT acceptance) +- test (NOT verification at task level) ## Task Grouping Rules 1. **Group by fix area**: All changes for one fix = one task (even if 2-3 files) @@ -605,25 +646,30 @@ Generate fix-plan.json with: 3. **Substantial tasks**: Each task should represent 10-45 minutes of work 4. **True dependencies only**: Only use depends_on when Task B cannot start without Task A's output 5. **Prefer parallel**: Most tasks should be independent (no depends_on) +6. **Task IDs**: Use FIX-001, FIX-002 prefix (NOT TASK-) ## Execution 1. Read ALL diagnosis files for comprehensive context 2. Execute CLI planning using Gemini (Qwen fallback) with --rule planning-fix-strategy template 3. Synthesize findings from multiple diagnosis angles -4. Generate fix-plan with: - - For High/Critical: REQUIRED new fields (rationale, verification, risks, code_skeleton, data_flow, design_decisions) - - Each task MUST have rationale (why this fix), verification (how to verify success), and risks (potential issues) -5. Parse output and structure fix-plan +4. Generate fix tasks (1-5 tasks): + - Each task file written to \`${sessionFolder}/.task/FIX-NNN.json\` + - For High/Critical: REQUIRED fields (rationale, test, risks, code_skeleton) +5. Generate fix-plan overview: + - Written to \`${sessionFolder}/fix-plan.json\` + - Contains task_ids[] referencing .task/ files (NO embedded tasks[]) + - For High/Critical: REQUIRED fields (data_flow, design_decisions) 6. **Write**: \`${sessionFolder}/planning-context.md\` (evidence paths + understanding) -7. **Write**: \`${sessionFolder}/fix-plan.json\` -8. Return brief completion summary +7. **Write**: \`${sessionFolder}/.task/FIX-*.json\` (individual task files) +8. **Write**: \`${sessionFolder}/fix-plan.json\` (plan overview with task_ids[]) +9. Return brief completion summary ## Output Format for CLI Include these sections in your fix-plan output: -- Summary, Root Cause, Strategy (existing) +- Summary, Root Cause (in fix_context), Strategy (existing) - Data Flow: Diagram showing affected code paths - Design Decisions: Key architectural choices in the fix -- Tasks: Each with rationale (Medium/High), verification (Medium/High), risks (High), code_skeleton (High) +- Task files: Each with convergence, files, test, rationale (High), risks (High), code_skeleton (High) ` ) ``` @@ -638,18 +684,26 @@ Include these sections in your fix-plan output: ```javascript const fixPlan = JSON.parse(Read(`${sessionFolder}/fix-plan.json`)) +// Load tasks from .task/ directory (two-layer format) +const tasks = (fixPlan.task_ids || []).map(id => { + return JSON.parse(Read(`${sessionFolder}/.task/${id}.json`)) +}) +const taskList = tasks + +const fixContext = fixPlan.fix_context || {} + console.log(` ## Fix Plan **Summary**: ${fixPlan.summary} -**Root Cause**: ${fixPlan.root_cause} -**Strategy**: ${fixPlan.strategy} +**Root Cause**: ${fixContext.root_cause || fixPlan.root_cause} +**Strategy**: ${fixContext.strategy || fixPlan.strategy} -**Tasks** (${fixPlan.tasks.length}): -${fixPlan.tasks.map((t, i) => `${i+1}. ${t.title} (${t.scope})`).join('\n')} +**Tasks** (${taskList.length}): +${taskList.map((t, i) => `${i+1}. ${t.title} (${t.scope})`).join('\n')} -**Severity**: ${fixPlan.severity} -**Risk Level**: ${fixPlan.risk_level} +**Severity**: ${fixContext.severity || fixPlan.severity} +**Risk Level**: ${fixContext.risk_level || fixPlan.risk_level} **Estimated Time**: ${fixPlan.estimated_time} **Recommended**: ${fixPlan.recommended_execution} `) @@ -679,7 +733,7 @@ if (autoYes) { userSelection = AskUserQuestion({ questions: [ { - question: `Confirm fix plan? (${fixPlan.tasks.length} tasks, ${fixPlan.severity} severity)`, + question: `Confirm fix plan? (${taskList.length} tasks, ${fixContext.severity || fixPlan.severity} severity)`, header: "Confirm", multiSelect: false, options: [ @@ -695,7 +749,7 @@ if (autoYes) { options: [ { label: "Agent", description: "@code-developer agent" }, { label: "Codex", description: "codex CLI tool" }, - { label: "Auto", description: `Auto: ${fixPlan.severity === 'Low' ? 'Agent' : 'Codex'}` } + { label: "Auto", description: `Auto: ${(fixContext.severity || fixPlan.severity) === 'Low' ? 'Agent' : 'Codex'}` } ] }, { @@ -734,14 +788,21 @@ manifest.diagnoses.forEach(diag => { const fixPlan = JSON.parse(Read(`${sessionFolder}/fix-plan.json`)) +const fixSeverity = fixPlan.fix_context?.severity || fixPlan.severity + executionContext = { mode: "bugfix", - severity: fixPlan.severity, + severity: fixSeverity, planObject: { ...fixPlan, // Ensure complexity is set based on severity for new field consumption - complexity: fixPlan.complexity || (fixPlan.severity === 'Critical' ? 'High' : (fixPlan.severity === 'High' ? 'High' : 'Medium')) + complexity: fixPlan.complexity || (fixSeverity === 'Critical' ? 'High' : (fixSeverity === 'High' ? 'High' : 'Medium')) }, + // Task files from .task/ directory (two-layer format) + taskFiles: (fixPlan.task_ids || []).map(id => ({ + id, + path: `${sessionFolder}/.task/${id}.json` + })), diagnosisContext: diagnoses, diagnosisAngles: manifest.diagnoses.map(d => d.angle), diagnosisManifest: manifest, @@ -758,7 +819,8 @@ executionContext = { path: diag.path })), diagnoses_manifest: `${sessionFolder}/diagnoses-manifest.json`, - fix_plan: `${sessionFolder}/fix-plan.json` + fix_plan: `${sessionFolder}/fix-plan.json`, + task_dir: `${sessionFolder}/.task` } } } @@ -780,7 +842,11 @@ Skill(skill="workflow:lite-execute", args="--in-memory --mode bugfix") ├── diagnosis-{angle4}.json # Diagnosis angle 4 (if applicable) ├── diagnoses-manifest.json # Diagnosis index ├── planning-context.md # Evidence + understanding -└── fix-plan.json # Fix plan +├── fix-plan.json # Fix plan overview (task_ids[], NO embedded tasks[]) +└── .task/ # Independent fix task files + ├── FIX-001.json # Fix task 1 + ├── FIX-002.json # Fix task 2 + └── ... # Additional fix tasks ``` **Example**: @@ -791,7 +857,10 @@ Skill(skill="workflow:lite-execute", args="--in-memory --mode bugfix") ├── diagnosis-validation.json ├── diagnoses-manifest.json ├── planning-context.md -└── fix-plan.json +├── fix-plan.json +└── .task/ + ├── FIX-001.json + └── FIX-002.json ``` ## Error Handling diff --git a/.claude/commands/workflow/lite-plan.md b/.claude/commands/workflow/lite-plan.md index 0fa6fdf3..5323ea92 100644 --- a/.claude/commands/workflow/lite-plan.md +++ b/.claude/commands/workflow/lite-plan.md @@ -44,7 +44,8 @@ Intelligent lightweight planning command with dynamic workflow adaptation based | `exploration-{angle}.json` | Per-angle exploration results (1-4 files based on complexity) | | `explorations-manifest.json` | Index of all exploration files | | `planning-context.md` | Evidence paths + synthesized understanding | -| `plan.json` | Structured implementation plan (plan-json-schema.json) | +| `plan.json` | Plan overview with task_ids[] (plan-overview-base-schema.json) | +| `.task/TASK-*.json` | Independent task files (one per task) | **Output Directory**: `.workflow/.lite-plan/{task-slug}-{YYYY-MM-DD}/` @@ -52,7 +53,7 @@ Intelligent lightweight planning command with dynamic workflow adaptation based - Low complexity → Direct Claude planning (no agent) - Medium/High complexity → `cli-lite-planning-agent` generates `plan.json` -**Schema Reference**: `~/.ccw/workflows/cli-templates/schemas/plan-json-schema.json` +**Schema Reference**: `~/.ccw/workflows/cli-templates/schemas/plan-overview-base-schema.json` ## Auto Mode Defaults @@ -89,7 +90,7 @@ Phase 2: Clarification (optional, multi-round) Phase 3: Planning (NO CODE EXECUTION - planning only) └─ Decision (based on Phase 1 complexity): - ├─ Low → Load schema: cat ~/.ccw/workflows/cli-templates/schemas/plan-json-schema.json → Direct Claude planning (following schema) → plan.json + ├─ Low → Load schema: cat ~/.ccw/workflows/cli-templates/schemas/plan-overview-base-schema.json → Direct Claude planning (following schema) → plan.json └─ Medium/High → cli-lite-planning-agent → plan.json (agent internally executes quality check) Phase 4: Confirmation & Selection @@ -251,9 +252,13 @@ Execute **${angle}** exploration for task planning context. Analyze codebase fro **Required Fields** (all ${angle} focused): - project_structure: Modules/architecture relevant to ${angle} - relevant_files: Files affected from ${angle} perspective - **IMPORTANT**: Use object format with relevance scores for synthesis: - \`[{path: "src/file.ts", relevance: 0.85, rationale: "Core ${angle} logic"}]\` - Scores: 0.7+ high priority, 0.5-0.7 medium, <0.5 low + **MANDATORY**: Every file MUST use structured object format with ALL required fields: + \`[{path: "src/file.ts", relevance: 0.85, rationale: "Contains AuthService.login() - entry point for JWT token generation", role: "modify_target", discovery_source: "bash-scan", key_symbols: ["AuthService", "login"]}]\` + - **rationale** (required): Specific selection basis tied to ${angle} topic (>10 chars, not generic) + - **role** (required): modify_target|dependency|pattern_reference|test_target|type_definition|integration_point|config|context_only + - **discovery_source** (recommended): bash-scan|cli-analysis|ace-search|dependency-trace|manual + - **key_symbols** (recommended): Key functions/classes/types in the file relevant to the task + - Scores: 0.7+ high priority, 0.5-0.7 medium, <0.5 low - patterns: ${angle}-related patterns to follow - dependencies: Dependencies relevant to ${angle} - integration_points: Where to integrate from ${angle} viewpoint (include file:line locations) @@ -264,7 +269,9 @@ Execute **${angle}** exploration for task planning context. Analyze codebase fro ## Success Criteria - [ ] Schema obtained via cat explore-json-schema.json - [ ] get_modules_by_depth.sh executed -- [ ] At least 3 relevant files identified with ${angle} rationale +- [ ] At least 3 relevant files identified with specific rationale + role +- [ ] Every file has rationale >10 chars (not generic like "Related to ${angle}") +- [ ] Every file has role classification (modify_target/dependency/etc.) - [ ] Patterns are actionable (code examples, not generic advice) - [ ] Integration points include file:line locations - [ ] Constraints are project-specific to ${angle} @@ -416,7 +423,11 @@ if (autoYes) { // 2. 默认 → agent const executorAssignments = {} // { taskId: { executor: 'gemini'|'codex'|'agent', reason: string } } -plan.tasks.forEach(task => { + +// Load tasks from .task/ directory for executor assignment +const taskFiles = Glob(`${sessionFolder}/.task/TASK-*.json`) +taskFiles.forEach(taskPath => { + const task = JSON.parse(Read(taskPath)) // Claude 根据上述规则语义分析,为每个 task 分配 executor executorAssignments[task.id] = { executor: '...', reason: '...' } }) @@ -425,7 +436,7 @@ plan.tasks.forEach(task => { **Low Complexity** - Direct planning by Claude: ```javascript // Step 1: Read schema -const schema = Bash(`cat ~/.ccw/workflows/cli-templates/schemas/plan-json-schema.json`) +const schema = Bash(`cat ~/.ccw/workflows/cli-templates/schemas/plan-overview-base-schema.json`) // Step 2: ⚠️ MANDATORY - Read and review ALL exploration files const manifest = JSON.parse(Read(`${sessionFolder}/explorations-manifest.json`)) @@ -434,22 +445,51 @@ manifest.explorations.forEach(exp => { console.log(`\n### Exploration: ${exp.angle}\n${explorationData}`) }) -// Step 3: Generate plan following schema (Claude directly, no agent) -// ⚠️ Plan MUST incorporate insights from exploration files read in Step 2 +// Step 3: Generate task objects (Claude directly, no agent) +// ⚠️ Tasks MUST incorporate insights from exploration files read in Step 2 +// Task fields use NEW names: convergence.criteria (not acceptance), files[].change (not modification_points), test (not verification) +const tasks = [ + { + id: "TASK-001", + title: "...", + description: "...", + depends_on: [], + convergence: { criteria: ["..."] }, + files: [{ path: "...", change: "..." }], + implementation: ["..."], + test: "..." + }, + // ... more tasks +] + +// Step 4: Write task files to .task/ directory +const taskDir = `${sessionFolder}/.task` +Bash(`mkdir -p "${taskDir}"`) +tasks.forEach(task => { + Write(`${taskDir}/${task.id}.json`, JSON.stringify(task, null, 2)) +}) + +// Step 5: Generate plan overview (NO embedded tasks[]) const plan = { summary: "...", approach: "...", - tasks: [...], // Each task: { id, title, scope, ..., depends_on, execution_group, complexity } + task_ids: tasks.map(t => t.id), + task_count: tasks.length, + complexity: "Low", estimated_time: "...", recommended_execution: "Agent", - complexity: "Low", - _metadata: { timestamp: getUtc8ISOString(), source: "direct-planning", planning_mode: "direct" } + _metadata: { + timestamp: getUtc8ISOString(), + source: "direct-planning", + planning_mode: "direct", + plan_type: "feature" + } } -// Step 4: Write plan to session folder +// Step 6: Write plan overview to session folder Write(`${sessionFolder}/plan.json`, JSON.stringify(plan, null, 2)) -// Step 5: MUST continue to Phase 4 (Confirmation) - DO NOT execute code here +// Step 7: MUST continue to Phase 4 (Confirmation) - DO NOT execute code here ``` **Medium/High Complexity** - Invoke cli-lite-planning-agent: @@ -467,10 +507,11 @@ Generate implementation plan and write plan.json. **Session Folder**: ${sessionFolder} **Output Files**: - ${sessionFolder}/planning-context.md (evidence + understanding) -- ${sessionFolder}/plan.json (implementation plan) +- ${sessionFolder}/plan.json (plan overview -- NO embedded tasks[]) +- ${sessionFolder}/.task/TASK-*.json (independent task files, one per task) ## Output Schema Reference -Execute: cat ~/.ccw/workflows/cli-templates/schemas/plan-json-schema.json (get schema reference before generating plan) +Execute: cat ~/.ccw/workflows/cli-templates/schemas/plan-overview-base-schema.json (get schema reference before generating plan) ## Project Context (MANDATORY - Read Both Files) 1. Read: .workflow/project-tech.json (technology stack, architecture, key components) @@ -500,10 +541,17 @@ ${JSON.stringify(clarificationContext) || "None"} ${complexity} ## Requirements -Generate plan.json following the schema obtained above. Key constraints: -- tasks: 2-7 structured tasks (**group by feature/module, NOT by file**) +Generate plan.json and .task/*.json following the schema obtained above. Key constraints: - _metadata.exploration_angles: ${JSON.stringify(manifest.explorations.map(e => e.angle))} +**Output Format**: Two-layer structure: +- plan.json: Overview with task_ids[] referencing .task/ files (NO tasks[] array) +- .task/TASK-*.json: Independent task files following task-schema.json + +plan.json required fields: summary, approach, task_ids, task_count, _metadata (with plan_type: "feature") +Each task file required fields: id, title, description, depends_on, convergence (with criteria[]) +Task fields use: files[].change (not modification_points), convergence.criteria (not acceptance), test (not verification) + ## Task Grouping Rules 1. **Group by feature**: All changes for one feature = one task (even if 3-5 files) 2. **Group by context**: Tasks with similar context or related functional changes can be grouped together @@ -517,10 +565,12 @@ Generate plan.json following the schema obtained above. Key constraints: 1. Read schema file (cat command above) 2. Execute CLI planning using Gemini (Qwen fallback) 3. Read ALL exploration files for comprehensive context -4. Synthesize findings and generate plan following schema +4. Synthesize findings and generate tasks + plan overview 5. **Write**: \`${sessionFolder}/planning-context.md\` (evidence paths + understanding) -6. **Write**: \`${sessionFolder}/plan.json\` -7. Return brief completion summary +6. **Create**: \`${sessionFolder}/.task/\` directory (mkdir -p) +7. **Write**: \`${sessionFolder}/.task/TASK-001.json\`, \`TASK-002.json\`, etc. (one per task) +8. **Write**: \`${sessionFolder}/plan.json\` (overview with task_ids[], NO tasks[]) +9. Return brief completion summary ` ) ``` @@ -535,14 +585,21 @@ Generate plan.json following the schema obtained above. Key constraints: ```javascript const plan = JSON.parse(Read(`${sessionFolder}/plan.json`)) +// Load tasks from .task/ directory +const tasks = (plan.task_ids || []).map(id => { + const taskPath = `${sessionFolder}/.task/${id}.json` + return JSON.parse(Read(taskPath)) +}) +const taskList = tasks + console.log(` ## Implementation Plan **Summary**: ${plan.summary} **Approach**: ${plan.approach} -**Tasks** (${plan.tasks.length}): -${plan.tasks.map((t, i) => `${i+1}. ${t.title} (${t.file})`).join('\n')} +**Tasks** (${taskList.length}): +${taskList.map((t, i) => `${i+1}. ${t.title} (${t.scope || t.files?.[0]?.path || ''})`).join('\n')} **Complexity**: ${plan.complexity} **Estimated Time**: ${plan.estimated_time} @@ -575,7 +632,7 @@ if (autoYes) { userSelection = AskUserQuestion({ questions: [ { - question: `Confirm plan? (${plan.tasks.length} tasks, ${plan.complexity})`, + question: `Confirm plan? (${taskList.length} tasks, ${plan.complexity})`, header: "Confirm", multiSelect: false, options: [ @@ -632,7 +689,11 @@ manifest.explorations.forEach(exp => { const plan = JSON.parse(Read(`${sessionFolder}/plan.json`)) executionContext = { - planObject: plan, + planObject: plan, // plan overview (no tasks[]) + taskFiles: (plan.task_ids || []).map(id => ({ + id, + path: `${sessionFolder}/.task/${id}.json` + })), explorationsContext: explorations, explorationAngles: manifest.explorations.map(e => e.angle), explorationManifest: manifest, @@ -653,7 +714,8 @@ executionContext = { path: exp.path })), explorations_manifest: `${sessionFolder}/explorations-manifest.json`, - plan: `${sessionFolder}/plan.json` + plan: `${sessionFolder}/plan.json`, + task_dir: `${sessionFolder}/.task` } } } @@ -674,7 +736,12 @@ Skill(skill="workflow:lite-execute", args="--in-memory") ├── exploration-{angle3}.json # Exploration angle 3 (if applicable) ├── exploration-{angle4}.json # Exploration angle 4 (if applicable) ├── explorations-manifest.json # Exploration index -└── plan.json # Implementation plan +├── planning-context.md # Evidence paths + understanding +├── plan.json # Plan overview (task_ids[]) +└── .task/ # Task files directory + ├── TASK-001.json + ├── TASK-002.json + └── ... ``` **Example**: @@ -684,7 +751,12 @@ Skill(skill="workflow:lite-execute", args="--in-memory") ├── exploration-auth-patterns.json ├── exploration-security.json ├── explorations-manifest.json -└── plan.json +├── planning-context.md +├── plan.json +└── .task/ + ├── TASK-001.json + ├── TASK-002.json + └── TASK-003.json ``` ## Error Handling diff --git a/.claude/commands/workflow/tools/context-gather.md b/.claude/commands/workflow/tools/context-gather.md index 05e653f1..c93ab012 100644 --- a/.claude/commands/workflow/tools/context-gather.md +++ b/.claude/commands/workflow/tools/context-gather.md @@ -164,9 +164,13 @@ Execute **${angle}** exploration for task planning context. Analyze codebase fro **Required Fields** (all ${angle} focused): - project_structure: Modules/architecture relevant to ${angle} - relevant_files: Files affected from ${angle} perspective - **IMPORTANT**: Use object format with relevance scores for synthesis: - \`[{path: "src/file.ts", relevance: 0.85, rationale: "Core ${angle} logic"}]\` - Scores: 0.7+ high priority, 0.5-0.7 medium, <0.5 low + **MANDATORY**: Every file MUST use structured object format with ALL required fields: + \`[{path: "src/file.ts", relevance: 0.85, rationale: "Contains AuthService.login() - entry point for JWT token generation", role: "modify_target", discovery_source: "bash-scan", key_symbols: ["AuthService", "login"]}]\` + - **rationale** (required): Specific selection basis tied to ${angle} topic (>10 chars, not generic) + - **role** (required): modify_target|dependency|pattern_reference|test_target|type_definition|integration_point|config|context_only + - **discovery_source** (recommended): bash-scan|cli-analysis|ace-search|dependency-trace|manual + - **key_symbols** (recommended): Key functions/classes/types in the file relevant to the task + - Scores: 0.7+ high priority, 0.5-0.7 medium, <0.5 low - patterns: ${angle}-related patterns to follow - dependencies: Dependencies relevant to ${angle} - integration_points: Where to integrate from ${angle} viewpoint (include file:line locations) diff --git a/.codex/agents/cli-explore-agent.md b/.codex/agents/cli-explore-agent.md index 6ec5c3ce..cb414f5e 100644 --- a/.codex/agents/cli-explore-agent.md +++ b/.codex/agents/cli-explore-agent.md @@ -92,9 +92,11 @@ RULES: {from prompt, if template specified} | analysis=READ-ONLY ### Dual-Source Synthesis -1. Bash results: Precise file:line locations -2. Gemini results: Semantic understanding, design intent -3. Merge with source attribution (bash-discovered | gemini-discovered) +1. Bash results: Precise file:line locations → `discovery_source: "bash-scan"` +2. Gemini results: Semantic understanding, design intent → `discovery_source: "cli-analysis"` +3. ACE search: Semantic code search → `discovery_source: "ace-search"` +4. Dependency tracing: Import/export graph → `discovery_source: "dependency-trace"` +5. Merge with source attribution and generate rationale for each file --- @@ -118,7 +120,16 @@ Parse and memorize: 4. **Enum values** - Copy exact strings (e.g., `"critical"` not `"Critical"`) 5. **Nested structures** - Note flat vs nested requirements -**Step 3: Pre-Output Validation Checklist** +**Step 3: File Rationale Validation** (MANDATORY for relevant_files / affected_files) + +Every file entry MUST have: +- `rationale` (required, minLength 10): Specific reason tied to the exploration topic, NOT generic + - GOOD: "Contains AuthService.login() which is the entry point for JWT token generation" + - BAD: "Related to auth" or "Relevant file" +- `role` (required, enum): Structural classification of why it was selected +- `discovery_source` (optional but recommended): How the file was found + +**Step 4: Pre-Output Validation Checklist** Before writing ANY JSON output, verify: @@ -128,6 +139,8 @@ Before writing ANY JSON output, verify: - [ ] Enum values EXACTLY match schema (case-sensitive) - [ ] Nested structures follow schema pattern (flat vs nested) - [ ] Data types correct (string, integer, array, object) +- [ ] Every file in relevant_files has: path + relevance + rationale + role +- [ ] Every rationale is specific (>10 chars, not generic) --- @@ -167,13 +180,15 @@ Brief summary: **ALWAYS**: 1. **Search Tool Priority**: ACE (`mcp__ace-tool__search_context`) → CCW (`mcp__ccw-tools__smart_search`) / Built-in (`Grep`, `Glob`, `Read`) 2. Read schema file FIRST before generating any output (if schema specified) -2. Copy field names EXACTLY from schema (case-sensitive) -3. Verify root structure matches schema (array vs object) -4. Match nested/flat structures as schema requires -5. Use exact enum values from schema (case-sensitive) -6. Include ALL required fields at every level -7. Include file:line references in findings -8. Attribute discovery source (bash/gemini) +3. Copy field names EXACTLY from schema (case-sensitive) +4. Verify root structure matches schema (array vs object) +5. Match nested/flat structures as schema requires +6. Use exact enum values from schema (case-sensitive) +7. Include ALL required fields at every level +8. Include file:line references in findings +9. **Every file MUST have rationale**: Specific selection basis tied to the topic (not generic) +10. **Every file MUST have role**: Classify as modify_target/dependency/pattern_reference/test_target/type_definition/integration_point/config/context_only +11. **Track discovery source**: Record how each file was found (bash-scan/cli-analysis/ace-search/dependency-trace/manual) **Bash Tool**: - Use `run_in_background=false` for all Bash/CLI calls to ensure foreground execution diff --git a/.codex/agents/cli-lite-planning-agent.md b/.codex/agents/cli-lite-planning-agent.md index b843cc19..e6c3827b 100644 --- a/.codex/agents/cli-lite-planning-agent.md +++ b/.codex/agents/cli-lite-planning-agent.md @@ -505,6 +505,8 @@ function parseCLIOutput(cliOutput) { ### Context Enrichment ```javascript +// NOTE: relevant_files items are structured objects: +// {path, relevance, rationale, role, discovery_source?, key_symbols?} function buildEnrichedContext(explorationsContext, explorationAngles) { const enriched = { relevant_files: [], patterns: [], dependencies: [], integration_points: [], constraints: [] } @@ -519,7 +521,16 @@ function buildEnrichedContext(explorationsContext, explorationAngles) { } }) - enriched.relevant_files = [...new Set(enriched.relevant_files)] + // Deduplicate by path, keep highest relevance entry for each path + const fileMap = new Map() + enriched.relevant_files.forEach(f => { + const path = typeof f === 'string' ? f : f.path + const existing = fileMap.get(path) + if (!existing || (f.relevance || 0) > (existing.relevance || 0)) { + fileMap.set(path, typeof f === 'string' ? { path: f, relevance: 0.5, rationale: 'discovered', role: 'context_only' } : f) + } + }) + enriched.relevant_files = [...fileMap.values()] return enriched } ``` @@ -540,7 +551,7 @@ function validateAndEnhanceTasks(rawTasks, enrichedContext) { implementation: task.implementation?.length >= 2 ? task.implementation : [`Analyze ${task.file}`, `Implement ${task.title}`, `Add error handling`], - reference: task.reference || { pattern: "existing patterns", files: enrichedContext.relevant_files.slice(0, 2), examples: "Follow existing structure" }, + reference: task.reference || { pattern: "existing patterns", files: enrichedContext.relevant_files.slice(0, 2).map(f => typeof f === 'string' ? f : f.path), examples: "Follow existing structure" }, acceptance: task.acceptance?.length >= 1 ? task.acceptance : [`${task.title} completed`, `Follows conventions`], @@ -554,9 +565,11 @@ function inferAction(title) { return match ? match[1] : "Implement" } +// NOTE: relevant_files items are structured objects with .path property function inferFile(task, ctx) { const files = ctx?.relevant_files || [] - return files.find(f => task.title.toLowerCase().includes(f.split('/').pop().split('.')[0].toLowerCase())) || "file-to-be-determined.ts" + const getPath = f => typeof f === 'string' ? f : f.path + return getPath(files.find(f => task.title.toLowerCase().includes(getPath(f).split('/').pop().split('.')[0].toLowerCase())) || {}) || "file-to-be-determined.ts" } ``` @@ -695,7 +708,7 @@ function validateAndEnhanceTasks(rawTasks, enrichedContext, complexity) { implementation: task.implementation?.length >= 2 ? task.implementation : [`Analyze ${task.scope || task.file}`, `Implement ${task.title}`, `Add error handling`], - reference: task.reference || { pattern: "existing patterns", files: enrichedContext.relevant_files.slice(0, 2), examples: "Follow existing structure" }, + reference: task.reference || { pattern: "existing patterns", files: enrichedContext.relevant_files.slice(0, 2).map(f => typeof f === 'string' ? f : f.path), examples: "Follow existing structure" }, acceptance: task.acceptance?.length >= 1 ? task.acceptance : [`${task.title} completed`, `Follows conventions`], @@ -747,8 +760,9 @@ try { } else throw error } +// NOTE: relevant_files items are structured objects with .path property function generateBasicPlan(taskDesc, ctx) { - const files = ctx?.relevant_files || [] + const files = (ctx?.relevant_files || []).map(f => typeof f === 'string' ? f : f.path) const tasks = [taskDesc].map((t, i) => ({ id: `T${i + 1}`, title: t, file: files[i] || "tbd", action: "Implement", description: t, modification_points: [{ file: files[i] || "tbd", target: "main", change: t }], diff --git a/.codex/agents/context-search-agent.md b/.codex/agents/context-search-agent.md index d101c2f5..d3e9865c 100644 --- a/.codex/agents/context-search-agent.md +++ b/.codex/agents/context-search-agent.md @@ -165,12 +165,16 @@ if (file_exists(manifestPath)) { } // Synthesis helper functions (conceptual) +// NOTE: relevant_files items are now structured objects: +// {path, relevance, rationale, role, discovery_source?, key_symbols?} function synthesizeCriticalFiles(allRelevantFiles) { - // 1. Group by path + // 1. Group by path (files are objects with .path property) // 2. Count mentions across angles // 3. Average relevance scores - // 4. Rank by: (mention_count * 0.6) + (avg_relevance * 0.4) - // 5. Return top 10-15 with mentioned_by_angles attribution + // 4. Merge rationales from different angles (join with "; ") + // 5. Collect unique roles and key_symbols across angles + // 6. Rank by: (mention_count * 0.6) + (avg_relevance * 0.4) + // 7. Return top 10-15 with: path, relevance, rationale, role, mentioned_by_angles, key_symbols } function synthesizeConflictIndicators(explorationData) { @@ -495,7 +499,7 @@ Calculate risk level based on: } ], "aggregated_insights": { - "critical_files": [{"path": "src/auth/AuthService.ts", "relevance": 0.95, "mentioned_by_angles": ["architecture"]}], + "critical_files": [{"path": "src/auth/AuthService.ts", "relevance": 0.95, "rationale": "Contains login/register/verifyToken - core auth entry points", "role": "modify_target", "mentioned_by_angles": ["architecture"], "key_symbols": ["AuthService", "login", "verifyToken"]}], "conflict_indicators": [{"type": "pattern_mismatch", "description": "...", "source_angle": "architecture", "severity": "medium"}], "clarification_needs": [{"question": "...", "context": "...", "options": [], "source_angle": "architecture"}], "constraints": [{"constraint": "Must follow existing DI pattern", "source_angle": "architecture"}], diff --git a/.codex/skills/workflow-plan-execute/phases/02-context-gathering.md b/.codex/skills/workflow-plan-execute/phases/02-context-gathering.md index cfa5b600..9a27342d 100644 --- a/.codex/skills/workflow-plan-execute/phases/02-context-gathering.md +++ b/.codex/skills/workflow-plan-execute/phases/02-context-gathering.md @@ -195,9 +195,13 @@ ${sourceRefsDirective} **Required Fields** (all ${angle} focused): - project_structure: Modules/architecture relevant to ${angle} - relevant_files: Files affected from ${angle} perspective - **IMPORTANT**: Use object format with relevance scores for synthesis: - \`[{path: "src/file.ts", relevance: 0.85, rationale: "Core ${angle} logic"}]\` - Scores: 0.7+ high priority, 0.5-0.7 medium, <0.5 low + **MANDATORY**: Every file MUST use structured object format with ALL required fields: + \`[{path: "src/file.ts", relevance: 0.85, rationale: "Contains AuthService.login() - entry point for JWT token generation", role: "modify_target", discovery_source: "bash-scan", key_symbols: ["AuthService", "login"]}]\` + - **rationale** (required): Specific selection basis tied to ${angle} topic (>10 chars, not generic) + - **role** (required): modify_target|dependency|pattern_reference|test_target|type_definition|integration_point|config|context_only + - **discovery_source** (recommended): bash-scan|cli-analysis|ace-search|dependency-trace|manual + - **key_symbols** (recommended): Key functions/classes/types in the file relevant to the task + - Scores: 0.7+ high priority, 0.5-0.7 medium, <0.5 low - patterns: ${angle}-related patterns to follow - dependencies: Dependencies relevant to ${angle} - integration_points: Where to integrate from ${angle} viewpoint (include file:line locations) @@ -210,7 +214,9 @@ ${sourceRefsDirective} ## Success Criteria - [ ] Schema obtained via cat explore-json-schema.json - [ ] get_modules_by_depth.sh executed -- [ ] At least 3 relevant files identified with ${angle} rationale +- [ ] At least 3 relevant files identified with specific rationale + role +- [ ] Every file has rationale >10 chars (not generic like "Related to ${angle}") +- [ ] Every file has role classification (modify_target/dependency/etc.) - [ ] Patterns are actionable (code examples, not generic advice) - [ ] Integration points include file:line locations - [ ] Constraints are project-specific to ${angle} diff --git a/ccw/frontend/src/components/lite-tasks/LiteContextContent.tsx b/ccw/frontend/src/components/lite-tasks/LiteContextContent.tsx index 44535420..e047cc31 100644 --- a/ccw/frontend/src/components/lite-tasks/LiteContextContent.tsx +++ b/ccw/frontend/src/components/lite-tasks/LiteContextContent.tsx @@ -248,7 +248,7 @@ function ContextContent({
{ctx.relevant_files.map((f, i) => { const filePath = typeof f === 'string' ? f : f.path; - const reason = typeof f === 'string' ? undefined : f.reason; + const reason = typeof f === 'string' ? undefined : (f.rationale || f.reason); return (
{i + 1}. diff --git a/ccw/frontend/src/components/session-detail/context/ExplorationsSection.tsx b/ccw/frontend/src/components/session-detail/context/ExplorationsSection.tsx index 0d362dcd..11e93f0c 100644 --- a/ccw/frontend/src/components/session-detail/context/ExplorationsSection.tsx +++ b/ccw/frontend/src/components/session-detail/context/ExplorationsSection.tsx @@ -105,10 +105,11 @@ function extractString(val: unknown): string { return String(val); } -/** Extract a secondary description from an object (reason, description, etc.) */ +/** Extract a secondary description from an object (rationale, reason, description, etc.) */ function extractReason(val: unknown): string | undefined { if (!val || typeof val !== 'object') return undefined; const obj = val as Record; + if (typeof obj.rationale === 'string') return obj.rationale; if (typeof obj.reason === 'string') return obj.reason; if (typeof obj.description === 'string') return obj.description; return undefined; diff --git a/ccw/src/templates/dashboard-js/views/lite-tasks.js b/ccw/src/templates/dashboard-js/views/lite-tasks.js index 17b0de00..b41ecfde 100644 --- a/ccw/src/templates/dashboard-js/views/lite-tasks.js +++ b/ccw/src/templates/dashboard-js/views/lite-tasks.js @@ -954,7 +954,7 @@ function renderMultiCliContextContent(context, session) {
  • 📄 ${escapeHtml(typeof f === 'string' ? f : f.path || f.file || '')} - ${f.reason ? `${escapeHtml(f.reason)}` : ''} + ${(f.rationale || f.reason) ? `${escapeHtml(f.rationale || f.reason)}` : ''}
  • `).join('')}