UNPKG

sqlew

Version:

MCP server for efficient context sharing between Claude Code sub-agents (60-70% token reduction), Kanban Task Watcher, Decision or Constraint Context, and streamlined documentation

590 lines (444 loc) 16.4 kB
# Task File Auto-Pruning (v3.5.0) **Feature**: Automatic removal of non-existent watched files with audit trail preservation **Problem Solved**: Tasks watching planned files that were never created during implementation block quality gates indefinitely. Auto-pruning maintains clean watch lists while preserving project archaeology. --- ## Table of Contents - [Overview](#overview) - [How It Works](#how-it-works) - [Safety Checks](#safety-checks) - [Audit Trail](#audit-trail) - [MCP Actions](#mcp-actions) - [Use Cases](#use-cases) - [Best Practices](#best-practices) - [Configuration](#configuration) - [Examples](#examples) --- ## Overview ### The Problem When implementing features, AI agents often watch files that represent planned work: - `src/api/new-endpoint.ts` - endpoint that was never needed - `src/utils/helper.ts` - functionality absorbed into existing code - `tests/integration/feature-x.test.ts` - test file that wasn't created When these files remain non-existent, tasks cannot transition to `waiting_review` because the quality gate requires ALL watched files to be modified. This blocks workflow progression. ### The Solution **v3.5.0 Auto-Pruning** automatically removes non-existent watched files when tasks transition to `waiting_review`, while preserving a complete audit trail for project archaeology. ### Key Benefits 1. **Clean Watch Lists**: Non-existent files removed automatically 2. **Audit Trail**: Every pruned file recorded with timestamp 3. **Decision Linking**: Optional WHY reasoning for project archaeology 4. **Safety Checks**: Cannot complete tasks with zero work done 5. **Zero Configuration**: Works out of the box --- ## How It Works ### Trigger Point Auto-pruning is triggered during the `in_progress waiting_review` transition in the `detectAndTransitionToReview()` function. This timing ensures: - Files are checked only when work is considered "complete" - Pruning happens before quality gate validation - No manual intervention required ### Execution Flow ``` 1. Task idle for configured time (default: 3 minutes) 2. detectAndTransitionToReview() runs periodic check 3. FOR EACH candidate task: a. Get watched files b. Check filesystem existence c. Identify non-existent files d. **SAFETY CHECK**: If ALL files non-existent BLOCK transition e. Prune non-existent files f. Record to t_task_pruned_files audit table g. Remove from t_task_file_links h. Continue with quality gate checks on remaining files 4. Transition to waiting_review (if quality gates pass) ``` ### Database Changes **Table Created**: `t_task_pruned_files` ```sql CREATE TABLE t_task_pruned_files ( id INTEGER PRIMARY KEY AUTOINCREMENT, task_id INTEGER NOT NULL REFERENCES t_tasks(id) ON DELETE CASCADE, file_path TEXT NOT NULL, pruned_ts INTEGER DEFAULT (unixepoch()), linked_decision_id INTEGER REFERENCES t_decisions(id) ON DELETE SET NULL ); ``` **Indexes**: - `idx_pruned_task`: Fast lookup by task - `idx_pruned_decision`: Fast lookup by linked decision --- ## Safety Checks ### Zero-Work Prevention **Rule**: If ALL watched files are non-existent, the transition is BLOCKED. **Rationale**: A task with no existing files indicates no actual work was performed. This prevents tasks from being marked complete when they should be closed or re-scoped. **Behavior**: ```typescript // Example: Task watches 3 files, all non-existent watchedFiles = [ "src/feature-a.ts", // doesn't exist "src/feature-b.ts", // doesn't exist "tests/feature.test.ts" // doesn't exist ] // Auto-prune attempts to run // Safety check triggers: ALL files non-existent // Error: "Cannot prune files for task #42: ALL 3 watched files are non-existent..." // Task stays in 'in_progress' ``` **Resolution Options**: 1. Verify at least one watched file exists (create placeholder if needed) 2. Mark task as invalid and close manually 3. Update watch list to include files that actually exist ### Partial Pruning **Rule**: If SOME files are non-existent, only those files are pruned. **Behavior**: ```typescript // Example: Task watches 3 files, 1 non-existent watchedFiles = [ "src/feature-a.ts", // exists "src/feature-b.ts", // doesn't exist "tests/feature.test.ts" // exists ] // Auto-prune runs // Prunes: src/feature-b.ts // Keeps: src/feature-a.ts, tests/feature.test.ts // Records pruned file to audit table // Task continues to waiting_review ``` --- ## Audit Trail ### Purpose The audit trail enables project archaeology: - **Post-Mortem Analysis**: Understand why planned files weren't created - **Architecture Reviews**: Track scope changes during implementation - **Team Handoffs**: Explain decisions to future developers - **Decision History**: Link to architectural decisions ### Data Preserved For each pruned file: - **file_path**: Raw path string (not normalized) - **pruned_ts**: Unix timestamp when pruned - **task_id**: Task that watched this file - **linked_decision_id**: Optional link to decision explaining WHY ### Persistence **Pruned files survive task archival** - audit records remain even after tasks are archived, enabling long-term project archaeology. --- ## MCP Actions ### get_pruned_files Retrieve audit trail for a task. **Action**: `get_pruned_files` **Parameters**: - `task_id` (required): Task ID - `limit` (optional): Max results (default: 100) **Example Request**: ```json { "action": "get_pruned_files", "task_id": 42, "limit": 50 } ``` **Example Response**: ```json { "success": true, "task_id": 42, "count": 2, "pruned_files": [ { "id": 15, "file_path": "src/api/v2/endpoint.ts", "pruned_at": "2025-10-22 08:15:23", "linked_decision": "api-v1-sufficient" }, { "id": 14, "file_path": "src/utils/deprecated.ts", "pruned_at": "2025-10-22 08:15:23", "linked_decision": null } ], "message": "Found 2 pruned file(s) for task 42" } ``` ### link_pruned_file Attach WHY reasoning to a pruned file. **Action**: `link_pruned_file` **Parameters**: - `pruned_file_id` (required): Pruned file record ID - `decision_key` (required): Decision key to link **Example Request**: ```json { "action": "link_pruned_file", "pruned_file_id": 15, "decision_key": "api-v1-sufficient" } ``` **Example Response**: ```json { "success": true, "pruned_file_id": 15, "decision_key": "api-v1-sufficient", "task_id": 42, "file_path": "src/api/v2/endpoint.ts", "message": "Linked pruned file \"src/api/v2/endpoint.ts\" to decision \"api-v1-sufficient\"" } ``` --- ## Use Cases ### Use Case 1: Endpoint Not Needed **Scenario**: Task planned to create `/api/v2/users` but during implementation, realized v1 endpoint was sufficient. **Workflow**: 1. Task created with watch files: `["src/api/v2/users.ts", "tests/api/v2/users.test.ts"]` 2. During implementation, agent updates existing v1 endpoint instead 3. Agent creates decision: `api-v1-sufficient` explaining why v2 wasn't needed 4. Task transitions to `waiting_review` 5. Auto-prune detects non-existent files, prunes them, records to audit 6. Agent links pruned files to decision via `link_pruned_file` 7. Future developers can see WHY v2 wasn't created **Commands**: ```bash # 1. Create decision mcp-tool decision set \ --key "api-v1-sufficient" \ --value "v1 endpoint handles all current requirements" \ --layer "presentation" # 2. Get pruned files (after auto-prune) mcp-tool task get_pruned_files --task_id 42 # 3. Link decision to pruned files mcp-tool task link_pruned_file \ --pruned_file_id 15 \ --decision_key "api-v1-sufficient" ``` ### Use Case 2: Feature Absorbed into Existing Code **Scenario**: Planned utility module absorbed into existing helper. **Workflow**: 1. Task watches `src/utils/new-helper.ts` 2. During implementation, functionality added to `src/utils/existing-helper.ts` 3. Auto-prune removes non-existent `new-helper.ts` 4. Audit trail shows the file was planned but not created ### Use Case 3: Test Strategy Changed **Scenario**: Planned integration tests replaced with unit tests. **Workflow**: 1. Task watches `tests/integration/feature-x.test.ts` 2. Team decides unit tests are sufficient 3. Auto-prune removes integration test file 4. Decision linked explaining test strategy change --- ## Best Practices ### 1. Document WHY with Decisions **DO**: Link pruned files to decisions explaining reasoning ```bash # Create decision first task.decision.set({ key: "feature-x-scope-reduced", value: "Feature X simplified during implementation" }) # Link after pruning task.link_pruned_file({ pruned_file_id: 15, decision_key: "feature-x-scope-reduced" }) ``` **DON'T**: Leave pruned files undocumented ```bash # Missing context - future developers won't understand why # (no decision link) ``` ### 2. Review Pruned Files Regularly **DO**: Periodically review audit trail for patterns ```bash # Check recent prunings across all tasks task.get_pruned_files({ limit: 50 }) ``` **DON'T**: Ignore pruned files - they indicate planning issues ### 3. Use Pruning as Feedback **DO**: Treat frequent pruning as a signal to improve planning - If many files are pruned, planning phase needs refinement - Consider more conservative watch lists - Focus on must-have files only ### 4. Leverage for Post-Mortems **DO**: Use audit trail during sprint retrospectives ```sql -- Query pruned files for sprint analysis SELECT t.title, tpf.file_path, datetime(tpf.pruned_ts, 'unixepoch') as pruned_at, k.key as decision FROM t_task_pruned_files tpf JOIN t_tasks t ON tpf.task_id = t.id LEFT JOIN t_decisions d ON tpf.linked_decision_id = d.id LEFT JOIN m_context_keys k ON d.key_id = k.id WHERE tpf.pruned_ts >= unixepoch('now', '-7 days') ORDER BY tpf.pruned_ts DESC; ``` --- ## Configuration ### Auto-Prune Timing Auto-pruning runs as part of `detectAndTransitionToReview()`, triggered by: - **Task idle time**: Default 3 minutes (configurable via `review_idle_minutes`) - **Periodic checks**: Runs on database initialization and periodically **Configuration Keys**: - `review_idle_minutes`: Time before considering task for review (default: 3) - `review_require_all_files_modified`: Quality gate setting (default: true) **Change Configuration**: ```bash # Via MCP tool mcp-tool config update --key review_idle_minutes --value 5 # Via SQL UPDATE m_config SET value = '5' WHERE key = 'review_idle_minutes'; ``` ### Quality Gates Auto-pruning works in conjunction with quality gates: 1. **File Existence Check**: Prunes non-existent files 2. **File Modification Check**: Validates remaining files were modified 3. **Compilation Check**: Ensures TypeScript compiles (if applicable) 4. **Test Check**: Validates tests pass (if applicable) --- ## Examples ### Example 1: Simple Pruning ```typescript // Task watches 2 files watchedFiles = ["src/feature.ts", "src/helper.ts"] // During implementation, helper absorbed into feature.ts // helper.ts never created // Auto-prune runs: // Checks: feature.ts exists // Checks: helper.ts doesn't exist // Prunes helper.ts // Records: { file_path: "src/helper.ts", pruned_ts: 1729584000 } // Task continues to waiting_review ``` ### Example 2: Safety Check Triggered ```typescript // Task watches 3 files, NONE exist watchedFiles = ["a.ts", "b.ts", "c.ts"] // Auto-prune runs: // ALL files non-existent // Safety check blocks transition // Error: "Cannot prune files for task #X: ALL 3 watched files are non-existent" // Task stays in_progress ``` ### Example 3: Full Workflow with Decision Linking ```typescript // 1. Create task with watch files const task = await task.create({ title: "Implement feature X", watch_files: ["src/feature-x.ts", "src/feature-x-helper.ts"] }); // 2. During work, agent creates only feature-x.ts // (feature-x-helper absorbed into main file) // 3. Agent documents decision await decision.set({ key: "feature-x-no-helper", value: "Helper functions absorbed into main module for simplicity", layer: "business" }); // 4. Task becomes idle > 3 minutes // Auto-prune runs automatically: // - Detects feature-x-helper.ts doesn't exist // - Prunes it // - Records to t_task_pruned_files // 5. Agent links decision to pruned file const pruned = await task.get_pruned_files({ task_id: task.id }); await task.link_pruned_file({ pruned_file_id: pruned.pruned_files[0].id, decision_key: "feature-x-no-helper" }); // 6. Future developers can query: // "Why was feature-x-helper.ts never created?" // Answer: Linked to decision "feature-x-no-helper" ``` --- ## Technical Details ### Database Schema ```sql -- Audit table CREATE TABLE t_task_pruned_files ( id INTEGER PRIMARY KEY AUTOINCREMENT, task_id INTEGER NOT NULL REFERENCES t_tasks(id) ON DELETE CASCADE, file_path TEXT NOT NULL, pruned_ts INTEGER DEFAULT (unixepoch()), linked_decision_id INTEGER REFERENCES t_decisions(id) ON DELETE SET NULL ); -- Indexes CREATE INDEX idx_pruned_task ON t_task_pruned_files(task_id); CREATE INDEX idx_pruned_decision ON t_task_pruned_files(linked_decision_id); ``` ### Implementation Files - **Migration**: `src/migrations/add-v3.5.0-pruned-files.ts` - **Core Logic**: `src/utils/file-pruning.ts` - **Integration**: `src/utils/task-stale-detection.ts` - **MCP Actions**: `src/tools/tasks.ts` (getPrunedFiles, linkPrunedFile) ### Token Efficiency Auto-pruning maintains token efficiency: - File paths stored as raw strings (not normalized to m_files) - Audit queries return only necessary fields - Pagination supported via `limit` parameter --- ## Troubleshooting ### Issue: Task stuck in in_progress **Symptom**: Task won't transition to waiting_review **Diagnosis**: ```bash # Check if ALL watched files are non-existent task.get({ task_id: X, include_dependencies: true }) # Look at linked_files array ``` **Solution**: 1. Create at least one watched file (even if placeholder) 2. Or manually update watch list to remove non-existent files 3. Or close task as invalid ### Issue: Can't find pruned files **Symptom**: `get_pruned_files` returns empty **Diagnosis**: - Auto-prune may not have run yet (task must be idle > 3 minutes) - Task may have no non-existent files **Solution**: ```bash # Force check by waiting for idle timeout # Or manually query database: SELECT * FROM t_task_pruned_files WHERE task_id = X; ``` ### Issue: Decision link not working **Symptom**: `link_pruned_file` fails **Diagnosis**: ```bash # Check if decision exists decision.get({ key: "your-decision-key" }) # Check if pruned_file_id is correct task.get_pruned_files({ task_id: X }) ``` --- ## Migration Notes ### Upgrading from v3.4.x Auto-pruning is **automatic** in v3.5.0. No configuration required. **Database Migration**: - Runs automatically on startup - Creates `t_task_pruned_files` table - Idempotent (safe to run multiple times) **Behavioral Changes**: - Tasks with non-existent files now auto-prune during review transition - No impact on existing tasks (pruning only affects future transitions) **Rollback**: If needed, downgrade to v3.4.x: ```bash # Audit table will remain but won't be used # No data loss git checkout v3.4.1 npm install ``` --- ## Related Documentation - **TASK_OVERVIEW.md**: Task lifecycle and status transitions - **TASK_ACTIONS.md**: All task action references - **TASK_LINKING.md**: Linking tasks to decisions/constraints/files - **DECISION_CONTEXT.md**: Rich decision documentation - **ARCHITECTURE.md**: Database schema details --- ## Changelog ### v3.5.0 (2025-10-22) - Initial release of Auto-Pruning feature - Added `t_task_pruned_files` audit table - Implemented `pruneNonExistentFiles()` with safety checks - Added MCP actions: `get_pruned_files`, `link_pruned_file` - Integrated into `detectAndTransitionToReview()` workflow