Skip to content

feat: auto-slim completed tasks to reduce tasks.json size#1666

Open
withsivram wants to merge 4 commits intoeyaltoledano:mainfrom
withsivram:feat/auto-slim-completed-tasks-issue-1642
Open

feat: auto-slim completed tasks to reduce tasks.json size#1666
withsivram wants to merge 4 commits intoeyaltoledano:mainfrom
withsivram:feat/auto-slim-completed-tasks-issue-1642

Conversation

@withsivram
Copy link
Copy Markdown

@withsivram withsivram commented Mar 20, 2026

Summary

  • When a task transitions to done status, automatically slim it by clearing details and testStrategy fields and truncating description to 200 characters
  • Subtasks are also slimmed when a parent task is marked done
  • Controlled by a new slimDoneTasks config option in global settings (default: true, can be disabled by setting to false)
  • One-way operation — git history preserves original content if ever needed

Test plan

  • Existing set-task-status tests pass (12/12) with updated mock for new config option
  • Existing update-single-task-status tests pass (5/5)
  • Existing config-manager tests pass (35/35) with slimDoneTasks added to DEFAULT_CONFIG fixture
  • Manual test: mark a task as done and verify details/testStrategy are cleared in tasks.json
  • Manual test: verify slimming does NOT occur when slimDoneTasks: false in config
  • Manual test: verify already-done tasks are not re-slimmed on subsequent status changes

Closes #1642

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Tasks moved between tags auto-renumber to avoid ID collisions; success messages now indicate renumbering and list original→new IDs; dependency references are updated accordingly.
    • Completed-task slimming trims descriptions and clears heavy fields to reduce storage; can be enabled/disabled via config.
  • Tests

    • Added/updated unit and integration tests covering renumbering, dependency updates, slimming, and move behavior.

withsivram and others added 2 commits March 20, 2026 12:47
…ixes eyaltoledano#1647)

When moving tasks between tags, if a task's ID already exists in the
target tag, the task is now automatically renumbered to the next
available ID instead of throwing an error. Dependencies within the
moved batch are updated to reflect the new IDs.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…ltoledano#1642)

When a task transitions to "done" status, automatically slim it by:
- Clearing `details` and `testStrategy` fields (set to empty string)
- Truncating `description` to 200 characters with "..." suffix
- Slimming all subtasks when a parent task is marked done

This is controlled by the `slimDoneTasks` config option (default: true).
Git history preserves pre-slim content. Only triggers on transition TO
done, not on already-done tasks.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings March 20, 2026 16:51
@changeset-bot
Copy link
Copy Markdown

changeset-bot bot commented Mar 20, 2026

⚠️ No Changeset found

Latest commit: 8d09bbe

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Mar 20, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 1c1d184e-7de5-491a-a802-1007f8dbd54d

📥 Commits

Reviewing files that changed from the base of the PR and between 796e6f9 and 8d09bbe.

📒 Files selected for processing (1)
  • scripts/modules/task-manager/update-single-task-status.js

📝 Walkthrough

Walkthrough

Adds configurable automatic slimming of tasks when transitioned to done and implements automatic ID renumbering during cross‑tag moves (updates dependencies, records original→new ID mappings, and surfaces renumbering in MCP success messages). Tests and config defaults updated accordingly.

Changes

Cohort / File(s) Summary
Config
scripts/modules/config-manager.js
Add global.slimDoneTasks default (true) and export isSlimDoneTasksEnabled(explicitRoot) getter.
Slim utilities & tests
scripts/modules/task-manager/slim-task.js, tests/unit/scripts/modules/task-manager/slim-task.test.js
New slimming module with DESCRIPTION_TRUNCATE_LENGTH and helpers to clear details/testStrategy and truncate description; unit tests cover null handling and transition semantics.
Status update flow
scripts/modules/task-manager/update-single-task-status.js, scripts/modules/task-manager/set-task-status.js, tests/unit/.../set-task-status.test.js
Pass slimOnDone option (from config) into status updates; call slimming helpers on completion transitions; tests/mocks adjusted to expect new option.
Move auto‑renumbering & tests
scripts/modules/task-manager/move-task.js, tests/integration/move-task-cross-tag.integration.test.js
Introduce getNextAvailableId and idRemapping; auto‑renumber target collisions, update moved-batch dependency references, include originalId→newId in results; integration test updated to expect renumbering success.
MCP messaging
mcp-server/src/core/direct-functions/move-task-cross-tag.js
Compose success data.message dynamically to include renumbered mappings when present.
Test fixtures
tests/unit/config-manager.test.js
Update DEFAULT_CONFIG fixture to include slimDoneTasks: true.

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant SetTaskStatus
    participant ConfigManager
    participant UpdateSingleTask
    participant SlimTask
    participant Storage

    User->>SetTaskStatus: setTaskStatus(id, "done")
    SetTaskStatus->>ConfigManager: isSlimDoneTasksEnabled(explicitRoot)
    ConfigManager-->>SetTaskStatus: true/false
    SetTaskStatus->>UpdateSingleTask: updateSingleTaskStatus(..., { slimOnDone })
    UpdateSingleTask->>UpdateSingleTask: Check status transition
    alt Transitioning to done/completed
        UpdateSingleTask->>SlimTask: slimTaskOnComplete(task, oldStatus, newStatus)
        SlimTask->>SlimTask: Clear details, testStrategy
        SlimTask->>SlimTask: Truncate description to 200 chars
        SlimTask->>SlimTask: Slim subtasks when parent completes
    end
    UpdateSingleTask->>Storage: Write updated task(s)
    Storage-->>User: Updated task saved
Loading
sequenceDiagram
    participant User
    participant MoveTasksAPI
    participant MoveLogic
    participant IdResolver
    participant DependencyUpdater
    participant Storage

    User->>MoveTasksAPI: moveTasksBetweenTags(source, target, ids)
    MoveTasksAPI->>MoveLogic: executeMoveOperation()
    MoveLogic->>IdResolver: Check for ID collisions in target
    alt ID collision detected
        IdResolver->>IdResolver: getNextAvailableId(existingIds, idRemapping)
        IdResolver-->>MoveLogic: newId
        MoveLogic->>MoveLogic: Update task.id = newId
        MoveLogic->>MoveLogic: Record originalId → newId in idRemapping
    end
    MoveLogic->>DependencyUpdater: Remap dependencies within moved batch using idRemapping
    DependencyUpdater->>DependencyUpdater: Update parent.subId and subtask dependency strings
    MoveLogic->>Storage: Write moved tasks to target tag
    MoveLogic-->>User: Success response with renumbering details (originalId → newId)
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

  • PR #1088: Overlaps cross‑tag move direct‑function edits and messaging; likely touches same move logic and MCP surface.
  • PR #1135: Related to collision handling and move-task suggestions; intersects renumbering and dependency logic.

Suggested reviewers

  • Crunchyman-ralph
🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Out of Scope Changes check ⚠️ Warning The PR also includes changes to move-task-cross-tag.js with ID renumbering functionality that is unrelated to the slimming objective in issue #1642. Remove or defer the ID renumbering changes in move-task-cross-tag.js, move-task.js, and related tests to a separate PR focused on task ID collision handling.
Docstring Coverage ⚠️ Warning Docstring coverage is 78.57% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely summarizes the primary change: auto-slimming completed tasks to reduce file size, which matches the main changeset.
Linked Issues check ✅ Passed The PR implements all core requirements from issue #1642: auto-slimming on transition to done, clearing details/testStrategy, truncating description to 200 chars, subtask handling, and a configurable slimDoneTasks flag.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds an “auto-slim on completion” behavior to reduce tasks.json bloat by clearing verbose task fields when tasks transition to done, controlled via a new global config flag. The PR also includes a separate behavioral change to cross-tag moves that auto-renumbers tasks on ID collisions.

Changes:

  • Add global.slimDoneTasks (default true) and plumb it into set-task-statusupdate-single-task-status to slim tasks/subtasks on transition to done.
  • Introduce slim-task.js utilities to clear details/testStrategy and truncate description.
  • Change cross-tag move behavior to auto-renumber tasks on ID collisions (and update related integration/MCP messaging).

Reviewed changes

Copilot reviewed 9 out of 9 changed files in this pull request and generated 6 comments.

Show a summary per file
File Description
tests/unit/scripts/modules/task-manager/set-task-status.test.js Updates mocks/expectations for the new slimming option passed into status updates.
tests/unit/config-manager.test.js Extends default-config fixture with slimDoneTasks.
tests/integration/move-task-cross-tag.integration.test.js Updates expectations to reflect auto-renumbering on cross-tag ID collisions.
scripts/modules/task-manager/update-single-task-status.js Adds optional statusOptions.slimOnDone and calls slim helpers during status transitions.
scripts/modules/task-manager/slim-task.js New module implementing task/subtask slimming behavior on completion.
scripts/modules/task-manager/set-task-status.js Reads slimDoneTasks config and passes slimOnDone into updateSingleTaskStatus.
scripts/modules/task-manager/move-task.js Implements auto-renumbering on ID collisions and attempts to remap dependencies post-move.
scripts/modules/config-manager.js Adds default global.slimDoneTasks and isSlimDoneTasksEnabled() getter export.
mcp-server/src/core/direct-functions/move-task-cross-tag.js Updates returned message to include renumbering details when applicable.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines 885 to 893
if (existingTaskIndex !== -1) {
throw new MoveTaskError(
MOVE_ERROR_CODES.TASK_ALREADY_EXISTS,
`Task ${taskId} already exists in target tag "${targetTag}"`,
{
conflictingId: normalizedTaskId,
targetTag,
suggestions: [
'Choose a different target tag without conflicting IDs',
'Move a different set of IDs (avoid existing ones)',
'If needed, move within-tag to a new ID first, then cross-tag move'
]
}
// ID collision detected — auto-renumber to the next available ID
assignedId = getNextAvailableId(rawData, targetTag, idRemapping);
idRemapping.set(normalizedTaskId, assignedId);
log(
'info',
`Task ${normalizedTaskId} conflicts with existing ID in "${targetTag}", renumbered to ${assignedId}`
);
}
Copy link

Copilot AI Mar 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This auto-renumbering behavior on ID conflicts is a significant semantic change (previously a collision threw TASK_ALREADY_EXISTS). It’s also unrelated to the PR’s stated purpose (auto-slimming done tasks). Either split this change into a separate PR or update the PR description/issues to reflect the cross-tag move behavior change and its implications for users.

Copilot uses AI. Check for mistakes.
Comment on lines +955 to +965
if (movedTask && Array.isArray(movedTask.dependencies)) {
movedTask.dependencies = movedTask.dependencies.map((dep) => {
const normalizedDep = normalizeDependency(dep);
if (
Number.isFinite(normalizedDep) &&
idRemapping.has(normalizedDep)
) {
return idRemapping.get(normalizedDep);
}
return dep;
});
Copy link

Copilot AI Mar 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The dependency remapping uses normalizeDependency(dep), which collapses dotted subtask dependencies like "2.1" to the parent task ID (2). If an ID is remapped, this code will replace a subtask dependency string with a numeric task ID, losing the .subtask portion and breaking dependency semantics. Preserve dotted dependency strings by only remapping the parent portion (and keep the original type/format) when applying idRemapping.

Copilot uses AI. Check for mistakes.
Comment on lines +968 to +981
if (movedTask && Array.isArray(movedTask.subtasks)) {
movedTask.subtasks.forEach((subtask) => {
if (Array.isArray(subtask.dependencies)) {
subtask.dependencies = subtask.dependencies.map((dep) => {
const normalizedDep = normalizeDependency(dep);
if (
Number.isFinite(normalizedDep) &&
idRemapping.has(normalizedDep)
) {
return idRemapping.get(normalizedDep);
}
return dep;
});
}
Copy link

Copilot AI Mar 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same issue for subtask dependencies: normalizeDependency(dep) will turn strings like "5.1" into 5, so when an ID is remapped this can overwrite a subtask dependency with a parent task ID and drop the subtask suffix. When remapping, detect dotted dependency strings and rewrite only the parent part (e.g., ${newParent}.${subId}) rather than replacing the whole value with a number.

Copilot uses AI. Check for mistakes.

/**
* Slim a completed task by removing verbose fields that are no longer actionable.
* Removes `details` and `testStrategy`, truncates `description` to 200 chars.
Copy link

Copilot AI Mar 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The docstring says "truncates description to 200 chars", but the implementation keeps the first 200 characters and then appends "..." (resulting in a string longer than 200). Update the comment (or the truncation logic) so the documented behavior matches the actual output length.

Suggested change
* Removes `details` and `testStrategy`, truncates `description` to 200 chars.
* Removes `details` and `testStrategy`, and if `description` exceeds 200 characters,
* keeps the first 200 characters and appends an ellipsis ("...").

Copilot uses AI. Check for mistakes.
Comment on lines +17 to +36
function slimTask(task) {
if (!task) return task;

// Clear verbose fields
if (task.details) {
task.details = '';
}

if (task.testStrategy) {
task.testStrategy = '';
}

// Truncate description to max length
if (
task.description &&
task.description.length > DESCRIPTION_TRUNCATE_LENGTH
) {
task.description =
task.description.substring(0, DESCRIPTION_TRUNCATE_LENGTH) + '...';
}
Copy link

Copilot AI Mar 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This PR introduces new behavior (slimming tasks/subtasks on transition to done) but there are no unit tests asserting the actual slimming results (e.g., details/testStrategy cleared, description truncation, and that slimming only happens on the transition-to-done). Adding focused unit tests around slimTaskOnComplete/slimSubtaskOnComplete (or via updateSingleTaskStatus) would help prevent regressions.

Copilot uses AI. Check for mistakes.
@@ -2,6 +2,7 @@ import chalk from 'chalk';

import { isValidTaskStatus } from '../../../src/constants/task-status.js';
Copy link

Copilot AI Mar 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TASK_STATUS_OPTIONS is referenced in the invalid-status error message, but it is not imported in this module. As-is, passing an invalid status will throw a ReferenceError instead of the intended error. Import TASK_STATUS_OPTIONS from src/constants/task-status.js (alongside isValidTaskStatus) or change the message to avoid referencing an undefined symbol.

Suggested change
import { isValidTaskStatus } from '../../../src/constants/task-status.js';
import { isValidTaskStatus, TASK_STATUS_OPTIONS } from '../../../src/constants/task-status.js';

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (3)
scripts/modules/task-manager/move-task.js (2)

828-834: Potential undefined access if target tag was just created.

The function assumes rawData[targetTag].tasks exists. While validateMove creates the target tag structure (line 647-648), consider adding a defensive check.

🛡️ Optional defensive fix
 function getNextAvailableId(rawData, targetTag, idRemapping) {
-	const existingIds = rawData[targetTag].tasks.map((t) => t.id);
+	const existingIds = (rawData[targetTag]?.tasks || []).map((t) => t.id);
 	const remappedIds = Array.from(idRemapping.values());
 	const allIds = [...existingIds, ...remappedIds];
 	if (allIds.length === 0) return 1;
 	return Math.max(...allIds) + 1;
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@scripts/modules/task-manager/move-task.js` around lines 828 - 834,
getNextAvailableId assumes rawData[targetTag].tasks exists and can throw if the
tag was just created without a tasks array; update getNextAvailableId to
defensively read existingIds from rawData[targetTag]?.tasks or an empty array
(e.g., use optional chaining or an explicit existence/Array.isArray check) so
mapping an empty array is safe, then continue computing remappedIds and the max;
refer to getNextAvailableId and validateMove to ensure the target tag creation
won't cause undefined access.

943-985: Dependency remapping only updates the moved batch, leaving potential dangling references in the source tag.

The remapping logic correctly updates dependencies within the moved tasks when another moved task gets renumbered. However, tasks remaining in the source tag that depended on moved tasks will have dangling references. This is expected behavior (addressed by the tip at line 1043-1045), but documenting this limitation in a code comment would help future maintainers.

📝 Suggested documentation comment
 	// After all tasks are moved, update cross-references within the moved batch.
 	// If task A depended on task B and both were moved but B got renumbered,
 	// update A's dependency to point to B's new ID.
+	// NOTE: Tasks left behind in the source tag are NOT updated here. If they
+	// depended on moved tasks, they will have dangling references. Users are
+	// advised to run validate-dependencies (see tip added in finalizeMove).
 	if (idRemapping.size > 0) {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@scripts/modules/task-manager/move-task.js` around lines 943 - 985, Add a
brief code comment above the idRemapping handling block that documents the
limitation: explain that the current remapping loop (referencing idRemapping,
tasksToMove, rawData[targetTag], movedTask and normalizeDependency) only updates
dependencies inside the moved batch and its subtasks, and that tasks remaining
in the source tag which depended on moved tasks may keep dangling references;
suggest callers should handle cleaning up source-tag references if needed.
mcp-server/src/core/direct-functions/move-task-cross-tag.js (1)

204-215: Dead code: TASK_ALREADY_EXISTS error handling is unreachable.

Since moveTasksBetweenTags now auto-renumbers on ID collisions instead of throwing TASK_ALREADY_EXISTS, this error handling block will never be executed. Consider removing it or adding a comment explaining it's kept for potential future changes.

♻️ Option 1: Remove dead code
-		} else if (
-			error.code === 'TASK_ALREADY_EXISTS' ||
-			error.message?.includes('already exists in target tag')
-		) {
-			// Target tag has an ID collision
-			errorCode = 'TASK_ALREADY_EXISTS';
-			suggestions = [
-				'Choose a different target tag without conflicting IDs',
-				'Move a different set of IDs (avoid existing ones)',
-				'If needed, move within-tag to a new ID first, then cross-tag move'
-			];
-		}
📝 Option 2: Add explanatory comment
 		} else if (
 			error.code === 'TASK_ALREADY_EXISTS' ||
 			error.message?.includes('already exists in target tag')
 		) {
-			// Target tag has an ID collision
+			// NOTE: As of auto-renumbering feature, cross-tag moves no longer throw
+			// TASK_ALREADY_EXISTS. Kept for backward compatibility with potential
+			// future changes or edge cases.
 			errorCode = 'TASK_ALREADY_EXISTS';
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@mcp-server/src/core/direct-functions/move-task-cross-tag.js` around lines 204
- 215, The else-if branch handling the 'TASK_ALREADY_EXISTS' case in
move-task-cross-tag.js is now dead because moveTasksBetweenTags auto-renumbers
on ID collisions; remove the entire else-if block that checks error.code ===
'TASK_ALREADY_EXISTS' || error.message?.includes('already exists in target tag')
along with its assignments to errorCode and suggestions (and any related
comment), or if you prefer to keep it, replace it with a short explanatory
comment referencing moveTasksBetweenTags to indicate why the block is
intentionally unreachable; update any tests or callers that assumed that
errorCode/suggestions branch if present.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@scripts/modules/task-manager/slim-task.js`:
- Around line 115-130: slimSubtaskOnComplete can throw if oldStatus or newStatus
is null/undefined because the code calls toLowerCase() directly; update the
function (referencing slimSubtaskOnComplete and slimSubtask) to defensively
normalize statuses before comparing (e.g., coerce oldStatus/newStatus to empty
string or check for falsy first) and then perform the lowercase comparisons,
returning early as before and calling slimSubtask(subtask) only when the safe
comparisons indicate a transition to done/completed.
- Around line 80-91: slimTaskOnComplete can throw when oldStatus (or newStatus)
is null/undefined because code calls
oldStatus.toLowerCase()/newStatus.toLowerCase(); fix by normalizing both
statuses before calling toLowerCase (e.g., coalesce to '' or 'pending') and use
those normalized variables in the isDoneStatus and wasDone checks so
slimTaskOnComplete, and any callers of oldStatus/newStatus, safely handle
missing values.

---

Nitpick comments:
In `@mcp-server/src/core/direct-functions/move-task-cross-tag.js`:
- Around line 204-215: The else-if branch handling the 'TASK_ALREADY_EXISTS'
case in move-task-cross-tag.js is now dead because moveTasksBetweenTags
auto-renumbers on ID collisions; remove the entire else-if block that checks
error.code === 'TASK_ALREADY_EXISTS' || error.message?.includes('already exists
in target tag') along with its assignments to errorCode and suggestions (and any
related comment), or if you prefer to keep it, replace it with a short
explanatory comment referencing moveTasksBetweenTags to indicate why the block
is intentionally unreachable; update any tests or callers that assumed that
errorCode/suggestions branch if present.

In `@scripts/modules/task-manager/move-task.js`:
- Around line 828-834: getNextAvailableId assumes rawData[targetTag].tasks
exists and can throw if the tag was just created without a tasks array; update
getNextAvailableId to defensively read existingIds from
rawData[targetTag]?.tasks or an empty array (e.g., use optional chaining or an
explicit existence/Array.isArray check) so mapping an empty array is safe, then
continue computing remappedIds and the max; refer to getNextAvailableId and
validateMove to ensure the target tag creation won't cause undefined access.
- Around line 943-985: Add a brief code comment above the idRemapping handling
block that documents the limitation: explain that the current remapping loop
(referencing idRemapping, tasksToMove, rawData[targetTag], movedTask and
normalizeDependency) only updates dependencies inside the moved batch and its
subtasks, and that tasks remaining in the source tag which depended on moved
tasks may keep dangling references; suggest callers should handle cleaning up
source-tag references if needed.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: b8ebc69c-a6bf-4b85-823e-d96b47837757

📥 Commits

Reviewing files that changed from the base of the PR and between 2d1211b and 63ee384.

📒 Files selected for processing (9)
  • mcp-server/src/core/direct-functions/move-task-cross-tag.js
  • scripts/modules/config-manager.js
  • scripts/modules/task-manager/move-task.js
  • scripts/modules/task-manager/set-task-status.js
  • scripts/modules/task-manager/slim-task.js
  • scripts/modules/task-manager/update-single-task-status.js
  • tests/integration/move-task-cross-tag.integration.test.js
  • tests/unit/config-manager.test.js
  • tests/unit/scripts/modules/task-manager/set-task-status.test.js

Comment on lines +80 to +91
function slimTaskOnComplete(task, oldStatus, newStatus) {
const isDoneStatus =
newStatus.toLowerCase() === 'done' ||
newStatus.toLowerCase() === 'completed';
const wasDone =
oldStatus.toLowerCase() === 'done' ||
oldStatus.toLowerCase() === 'completed';

// Only slim on transition TO done, not if already done
if (!isDoneStatus || wasDone) {
return task;
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Potential TypeError if oldStatus is null/undefined.

oldStatus.toLowerCase() at lines 85-86 will throw if oldStatus is null or undefined. While callers currently pass oldStatus from subtask.status || 'pending', defensive handling here would be safer.

🛡️ Proposed defensive fix
 function slimTaskOnComplete(task, oldStatus, newStatus) {
+	if (!oldStatus || !newStatus) return task;
+
 	const isDoneStatus =
 		newStatus.toLowerCase() === 'done' ||
 		newStatus.toLowerCase() === 'completed';
 	const wasDone =
-		oldStatus.toLowerCase() === 'done' ||
-		oldStatus.toLowerCase() === 'completed';
+		(oldStatus || '').toLowerCase() === 'done' ||
+		(oldStatus || '').toLowerCase() === 'completed';
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@scripts/modules/task-manager/slim-task.js` around lines 80 - 91,
slimTaskOnComplete can throw when oldStatus (or newStatus) is null/undefined
because code calls oldStatus.toLowerCase()/newStatus.toLowerCase(); fix by
normalizing both statuses before calling toLowerCase (e.g., coalesce to '' or
'pending') and use those normalized variables in the isDoneStatus and wasDone
checks so slimTaskOnComplete, and any callers of oldStatus/newStatus, safely
handle missing values.

Comment on lines +115 to +130
function slimSubtaskOnComplete(subtask, oldStatus, newStatus) {
const isDoneStatus =
newStatus.toLowerCase() === 'done' ||
newStatus.toLowerCase() === 'completed';
const wasDone =
oldStatus.toLowerCase() === 'done' ||
oldStatus.toLowerCase() === 'completed';

if (!isDoneStatus || wasDone) {
return subtask;
}

log('info', `Slimming completed subtask ${subtask.id}: "${subtask.title}"`);

return slimSubtask(subtask);
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Same null safety concern for oldStatus parameter.

Apply the same defensive handling as suggested for slimTaskOnComplete.

🛡️ Proposed defensive fix
 function slimSubtaskOnComplete(subtask, oldStatus, newStatus) {
+	if (!oldStatus || !newStatus) return subtask;
+
 	const isDoneStatus =
 		newStatus.toLowerCase() === 'done' ||
 		newStatus.toLowerCase() === 'completed';
 	const wasDone =
-		oldStatus.toLowerCase() === 'done' ||
-		oldStatus.toLowerCase() === 'completed';
+		(oldStatus || '').toLowerCase() === 'done' ||
+		(oldStatus || '').toLowerCase() === 'completed';
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@scripts/modules/task-manager/slim-task.js` around lines 115 - 130,
slimSubtaskOnComplete can throw if oldStatus or newStatus is null/undefined
because the code calls toLowerCase() directly; update the function (referencing
slimSubtaskOnComplete and slimSubtask) to defensively normalize statuses before
comparing (e.g., coerce oldStatus/newStatus to empty string or check for falsy
first) and then perform the lowercase comparisons, returning early as before and
calling slimSubtask(subtask) only when the safe comparisons indicate a
transition to done/completed.

… unit tests

- Import TASK_STATUS_OPTIONS in update-single-task-status.js to fix
  ReferenceError when validating invalid status values
- Update slimTask docstring to say "200 chars + ellipsis" (actual
  output can be 203 chars due to appended "...")
- Add 24 unit tests for slim-task module covering: description
  truncation, details/testStrategy clearing, transition-only slimming,
  subtask slimming, and edge cases (null/undefined inputs)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
scripts/modules/task-manager/update-single-task-status.js (1)

25-28: Consider defensive handling for null statusOptions.

If a caller explicitly passes null for statusOptions, line 28's destructuring will throw a TypeError. While the current caller in set-task-status.js always passes a valid object, adding a guard would make this more robust for future callers.

🛡️ Defensive fix
-	statusOptions = {}
+	statusOptions = null
 ) {
-	const { slimOnDone = false } = statusOptions;
+	const { slimOnDone = false } = statusOptions || {};
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@scripts/modules/task-manager/update-single-task-status.js` around lines 25 -
28, The destructuring "const { slimOnDone = false } = statusOptions;" will throw
if a caller passes null for statusOptions; make it defensive by falling back to
an empty object before destructuring (e.g., use statusOptions || {} in the
assignment) so slimOnDone is safely defaulted. Update the code in
update-single-task-status.js where statusOptions and slimOnDone are used to use
this guarded destructuring to avoid a TypeError when null is passed.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@scripts/modules/task-manager/update-single-task-status.js`:
- Around line 25-28: The destructuring "const { slimOnDone = false } =
statusOptions;" will throw if a caller passes null for statusOptions; make it
defensive by falling back to an empty object before destructuring (e.g., use
statusOptions || {} in the assignment) so slimOnDone is safely defaulted. Update
the code in update-single-task-status.js where statusOptions and slimOnDone are
used to use this guarded destructuring to avoid a TypeError when null is passed.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 5b0968dc-fa51-40e2-a7b1-82c984f115f3

📥 Commits

Reviewing files that changed from the base of the PR and between 63ee384 and 796e6f9.

📒 Files selected for processing (3)
  • scripts/modules/task-manager/slim-task.js
  • scripts/modules/task-manager/update-single-task-status.js
  • tests/unit/scripts/modules/task-manager/slim-task.test.js
✅ Files skipped from review due to trivial changes (1)
  • scripts/modules/task-manager/slim-task.js

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat: Auto-slim completed tasks to reduce tasks.json size

2 participants