Skip to content

fix: merge analyze-complexity results across --from/--to runs#1668

Open
withsivram wants to merge 2 commits intoeyaltoledano:mainfrom
withsivram:fix/complexity-merge-issue-1644
Open

fix: merge analyze-complexity results across --from/--to runs#1668
withsivram wants to merge 2 commits intoeyaltoledano:mainfrom
withsivram:fix/complexity-merge-issue-1644

Conversation

@withsivram
Copy link
Copy Markdown

@withsivram withsivram commented Mar 22, 2026

Summary

  • Fix the merge logic in analyze-complexity that incorrectly dropped existing report entries when running with --from/--to ranges
  • The bug was caused by filtering existing entries against currentTagTaskIds (derived from tasksData.tasks, the filtered subset) instead of preserving all existing entries in the tag-specific report file
  • Removed the currentTagTaskIds filter since the report file is already tag-specific (resolved by resolveComplexityReportOutputPath), so all entries in it belong to the correct tag

Fixes #1644

Test plan

  • Run analyze-complexity --from=1 --to=5, verify report contains entries for tasks 1-5
  • Run analyze-complexity --from=6 --to=10, verify report now contains entries for tasks 1-10 (merged)
  • Run analyze-complexity --from=3 --to=7, verify entries for 1-2 and 8-10 are preserved, entries for 3-7 are updated
  • Run analyze-complexity without --from/--to to verify full analysis still works
  • Verify tag isolation still works (different tags write to different report files)

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Automatic task ID renumbering when moving tasks between tags with existing ID collisions.
    • Enhanced response messages now include original-to-new ID mappings for renumbered tasks.
  • Bug Fixes

    • Fixed complexity analysis to preserve historical report entries across multiple analysis runs.
    • Improved dependency reference updates when tasks are renumbered during moves.
  • Tests

    • Updated integration tests to validate automatic renumbering behavior and dependency integrity.

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#1644)

The merge logic incorrectly filtered existing report entries against
tasksData.tasks (the --from/--to filtered subset) via currentTagTaskIds,
which dropped entries from earlier runs that analyzed different ranges.

Since the report file is already tag-specific (resolved by
resolveComplexityReportOutputPath), all entries in it belong to the
current tag. The fix removes the currentTagTaskIds filter so that all
existing entries not re-analyzed in the current run are preserved.

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

changeset-bot bot commented Mar 22, 2026

🦋 Changeset detected

Latest commit: 29e3daf

The changes in this PR will be included in the next version bump.

Not sure what this means? Click here to learn what changesets are.

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

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Mar 22, 2026

📝 Walkthrough

Walkthrough

This PR implements automatic task ID renumbering when moving tasks across tags that encounter ID collisions, fixes complexity report merging to preserve existing entries across multiple --from/--to runs, and updates related tests to expect the new renumbering behavior.

Changes

Cohort / File(s) Summary
Release metadata
.changeset/fix-complexity-merge.md
Adds changeset entry documenting patch release for task-master-ai with complexity report merge fix.
Complexity report merging
scripts/modules/task-manager/analyze-task-complexity.js
Modifies merge logic to preserve existing report entries not in the current AI analysis run, removing prior filtering against the current tasks subset to fix results being lost across multiple --from/--to invocations.
Task ID renumbering on move
scripts/modules/task-manager/move-task.js
Implements automatic renumbering of tasks when moving across tags with ID collisions, updates dependency references (both direct and in subtasks) using idRemapping, and includes originalId/newId in move results. Adds validation tip advising dependency integrity checks when renumbering occurs.
Cross-tag move response
mcp-server/src/core/direct-functions/move-task-cross-tag.js
Dynamically constructs success message to include renumbering details (originalId → newId) when tasks are renumbered during cross-tag move operations.
Move operation integration tests
tests/integration/move-task-cross-tag.integration.test.js
Updates cross-tag ID conflict test to expect automatic renumbering instead of error, validates returned result fields including renumbering mapping, verifies target tag contains both pre-existing and moved tasks, and confirms source tag becomes empty.

Sequence Diagram(s)

sequenceDiagram
    actor User
    participant moveTask as move-task.js<br/>(executeMoveOperation)
    participant deps as Dependency<br/>Processor
    participant storage as Task Storage
    participant response as move-task-cross-tag.js<br/>(response builder)

    User->>moveTask: Initiate move with collision risk
    moveTask->>moveTask: Check ID collision in target
    alt ID Collision Detected
        moveTask->>moveTask: Compute next available ID
        moveTask->>moveTask: Update task.id (renumber)
        moveTask->>moveTask: Record idRemapping
    end
    moveTask->>deps: Update dependencies<br/>using idRemapping
    deps->>deps: Rewrite dependency strings<br/>(old parent ID → new ID)
    deps->>moveTask: Return updated task
    moveTask->>storage: Write tasks to target tag
    storage->>moveTask: Confirm write
    moveTask->>response: Pass result with<br/>originalId/newId
    response->>response: Build dynamic message<br/>including renumbering info
    response->>User: Return success + renumbering details
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

  • PR #1645 — Directly related parallel PR that also modifies analyze-task-complexity.js to fix the same merge logic issue, preserving existing report entries across separate runs.
  • PR #1135 — Related to cross-tag move flow changes, including how ID collisions are handled and move results are reported in move-task-cross-tag.js and move-task.js.
  • PR #1088 — Related to the move-task-cross-tag.js enhancement for building dynamic success messages that include renumbering information during cross-tag moves.

Suggested reviewers

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

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Linked Issues check ⚠️ Warning The PR implements the core fix for #1644 by removing the currentTagTaskIds filter to preserve existing report entries. However, it includes unrelated changes for task renumbering on cross-tag moves (#1647), mixing two separate feature objectives. Separate the task renumbering logic (move-task.js, move-task-cross-tag.js changes) into a distinct PR, or update the PR title and objectives to reflect both #1644 and #1647.
Out of Scope Changes check ⚠️ Warning Changes to move-task.js and move-task-cross-tag.js for automatic task renumbering are out of scope for the stated PR objective of fixing the analyze-complexity merge issue. Move task renumbering changes to a separate PR focused on #1647, keeping this PR focused solely on the analyze-complexity merge fix.
✅ 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 accurately describes the main fix for merging analyze-complexity results across separate --from/--to runs, directly addressing the primary objective.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

✏️ 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.

Tip

You can enable review details to help with troubleshooting, context usage and more.

Enable the reviews.review_details setting to include review details such as the model used, the time taken for each step and more in the review comments.

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

This PR fixes incremental analyze-complexity runs so previously computed analyses are preserved when running multiple --from/--to ranges, and also introduces new cross-tag move behavior that auto-renumbers tasks when IDs collide.

Changes:

  • Fix analyze-complexity report merging to preserve existing entries not re-analyzed in the current run.
  • Update cross-tag task moves to auto-renumber on ID collisions and return renumbering details in responses.
  • Adjust integration tests and MCP direct-function messaging to match the new move behavior.

Reviewed changes

Copilot reviewed 5 out of 5 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
scripts/modules/task-manager/analyze-task-complexity.js Removes range-derived filtering during merge so reports accumulate across multiple --from/--to runs.
scripts/modules/task-manager/move-task.js Auto-renumbers moved tasks on ID collisions and attempts to remap dependencies accordingly.
mcp-server/src/core/direct-functions/move-task-cross-tag.js Surfaces renumbering details in the returned message for MCP callers.
tests/integration/move-task-cross-tag.integration.test.js Updates ID-collision test to expect successful move with renumbering rather than an error.
.changeset/fix-complexity-merge.md Adds a patch changeset for the analyze-complexity merge fix.

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

Comment on lines +956 to +965
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 22, 2026

Choose a reason for hiding this comment

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

The dependency remapping here uses normalizeDependency(dep), which intentionally strips the subtask portion from dotted IDs (e.g. "12.3" -> 12). If a task dependency is a subtask reference string, this code will replace it with a bare number (the new parent ID), losing the ".3" suffix and breaking the dependency reference. Consider handling dotted-string dependencies explicitly by remapping only the parent portion while preserving the subtask suffix, and only using numeric replacement for pure task-ID dependencies.

Copilot uses AI. Check for mistakes.
Comment on lines +969 to +980
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 22, 2026

Choose a reason for hiding this comment

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

Subtask dependencies can be full task/subtask IDs like "5.2" (see dependency-manager validation). Because normalizeDependency() returns only the parent task ID for dotted strings, this remapping will convert "5.2" into a number when task 5 is renumbered, dropping the subtask ID and corrupting the dependency graph. Update this logic to preserve dotted-string format by rewriting just the parent ID (e.g. "5.2" -> ".2").

Copilot uses AI. Check for mistakes.
Comment on lines +479 to +483
// Keep all existing entries that weren't re-analyzed in this run.
// The report file is already tag-specific (resolved by resolveComplexityReportOutputPath),
// so all entries in the existing report belong to the current tag.
// Previous logic incorrectly filtered against tasksData.tasks (the --from/--to filtered
// subset), which dropped entries from earlier runs that analyzed different ranges.
Copy link

Copilot AI Mar 22, 2026

Choose a reason for hiding this comment

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

The merge logic assumes the report file is always tag-specific, but resolveComplexityReportOutputPath() returns the explicit --output path unchanged. If a user reuses the same explicit output path across tags, this change will now preserve and merge entries from other tags instead of filtering them out. Consider either recording the tag in report meta and validating it on read, or scoping preservation to task IDs that exist in the current tag (but based on the full tag task list, not the --from/--to subset).

Copilot uses AI. Check for mistakes.
Comment on lines +1 to +5
---
'task-master-ai': patch
---

Fix analyze-complexity to merge results with existing report across --from/--to runs instead of overwriting
Copy link

Copilot AI Mar 22, 2026

Choose a reason for hiding this comment

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

This changeset only mentions the analyze-complexity merge fix, but this PR also changes cross-tag move behavior to auto-renumber on ID collisions (and updates MCP + integration tests accordingly). To avoid release-note/versioning surprises, consider updating the changeset (or adding a second one) to include the move-task behavior change, and/or align the PR description/title with the full scope.

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 (4)
scripts/modules/task-manager/analyze-task-complexity.js (1)

470-498: Fix looks correct—merge logic now properly preserves entries from prior runs.

The removal of the currentTagTaskIds filter is the right fix. Since the report path is already tag-specific (via resolveComplexityReportOutputPath), all entries in the file belong to the correct tag and should be preserved.

One optional enhancement: consider sorting finalComplexityAnalysis by taskId before writing to make the report easier to navigate manually.

,

🔧 Optional: Sort merged entries by taskId
 				// Combine: existing entries preserved from prior runs + new/updated analyses
 				finalComplexityAnalysis = [
 					...existingEntriesNotAnalyzed,
 					...complexityAnalysis
-				];
+				].sort((a, b) => a.taskId - b.taskId);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@scripts/modules/task-manager/analyze-task-complexity.js` around lines 470 -
498, The merge logic is correct but add a deterministic sort to
finalComplexityAnalysis before writing so the report is easier to inspect: after
building finalComplexityAnalysis (from existingEntriesNotAnalyzed and
complexityAnalysis) perform a sort by taskId (ensure numeric comparison if
taskId is numeric, otherwise lexicographic) so entries are ordered; this change
touches finalComplexityAnalysis and should be done just before the write that
uses the file path resolved by resolveComplexityReportOutputPath.
mcp-server/src/core/direct-functions/move-task-cross-tag.js (2)

204-215: Consider removing or documenting defensive error handling for TASK_ALREADY_EXISTS.

With the new auto-renumbering behavior, moveTasksBetweenTags no longer throws TASK_ALREADY_EXISTS for cross-tag ID collisions. This error handling block is now unreachable for this direct function.

You could either:

  1. Remove this block as dead code
  2. Add a comment explaining it's defensive code for potential future behavior changes
🤖 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 error branch that checks for TASK_ALREADY_EXISTS in
move-task-cross-tag.js (the else-if that tests error.code ===
'TASK_ALREADY_EXISTS' || error.message?.includes('already exists in target
tag')) is now unreachable because moveTasksBetweenTags no longer throws that
error; remove that entire branch and its suggestions array to eliminate dead
code, or if you prefer to keep defensive handling, replace it with a clear
comment on why the check remains (referencing moveTasksBetweenTags and
TASK_ALREADY_EXISTS) so future readers know it's intentionally retained for
potential behavior changes.

120-140: Redundant message construction duplicates core function logic.

The moveTasksBetweenTags core function already returns a result.message that includes renumbering details (see finalizeMove in move-task.js lines 1016-1022). Here, the same logic is duplicated to compute message, which then overwrites result.message in the response.

Consider using the message directly from the result to avoid duplication and keep the logic centralized:

♻️ Proposed simplification
-			// Check if any tasks were renumbered during the move
-			const renumberedTasks = (result.movedTasks || []).filter(
-				(t) => t.newId !== undefined
-			);
-			let message = `Successfully moved ${sourceIds.length} task(s) from "${args.sourceTag}" to "${args.targetTag}"`;
-			if (renumberedTasks.length > 0) {
-				const renumberDetails = renumberedTasks
-					.map((t) => `${t.originalId} → ${t.newId}`)
-					.join(', ');
-				message += `. Renumbered to avoid ID collisions: ${renumberDetails}`;
-			}
-
 			return {
 				success: true,
 				data: {
 					...result,
-					message,
 					moveOptions,
 					sourceTag: args.sourceTag,
 					targetTag: args.targetTag
 				}
 			};
🤖 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 120
- 140, The response builds a duplicate `message` from `result` (using
`renumberedTasks`) which overwrites the core
`moveTasksBetweenTags`/`result.message` produced by `finalizeMove` in
`move-task.js`; remove the local reconstruction and instead use `result.message`
(with a short fallback to the original success string if `result.message` is
missing) when building the returned `data` object. In practice, delete the
`renumberedTasks` computation and `message` assembly and set `data.message` to
`result.message || \`Successfully moved ${sourceIds.length} task(s) from
"${args.sourceTag}" to "${args.targetTag}"\`` while preserving `...result`,
`moveOptions`, `sourceTag` and `targetTag`.
tests/integration/move-task-cross-tag.integration.test.js (1)

553-610: Consider adding tests for dependency remapping during renumbering.

The current test validates basic ID collision handling. Consider adding tests for:

  1. Moving multiple tasks where some need renumbering
  2. Tasks with dependencies on other moved tasks that get renumbered (verifying dependency IDs are updated)

This would help catch edge cases like subtask dependency references being incorrectly handled.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/integration/move-task-cross-tag.integration.test.js` around lines 553 -
610, Add integration tests in
tests/integration/move-task-cross-tag.integration.test.js around the
moveTasksBetweenTags scenario to cover dependency remapping and
multiple-renumbering: create mockUtils.readJSON data with multiple tasks moved
from source to target where some target IDs collide and some moved tasks depend
on other moved tasks, call moveTasksBetweenTags, then assert result.movedTasks
contains correct originalId→newId mappings for each moved task and that each
moved task's dependencies were updated to the new IDs; also verify
mockUtils.writeJSON's writtenData for the target tag contains both original and
renumbered tasks with dependency arrays referencing the updated IDs and the
source tag is emptied as before.
🤖 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/move-task.js`:
- Around line 955-965: movedTask.dependencies mapping currently normalizes
dependencies with normalizeDependency and blindly replaces the whole dependency
when idRemapping has the normalized value, which breaks dotted subtask refs like
"5.2" (becoming "6" and losing ".2"). Update the mapping in
movedTask.dependencies to detect dotted/string deps, split into baseId and subId
(preserve suffix after the first dot), normalize only the baseId using
normalizeDependency, if idRemapping.has(normalizedBaseId) replace the base with
idRemapping.get(normalizedBaseId) and reattach the original subId (e.g., newBase
+ "." + subId), otherwise leave the original dependency unchanged; keep this
logic inside the same movedTask.dependencies map callback so it uses the
existing normalizeDependency and idRemapping symbols.
- Around line 967-983: The current post-move update in movedTask.subtasks is
using normalizeDependency(dep) which strips subtask suffixes, so when
idRemapping maps a parent ID (normalizedDep) you end up returning the mapped
parent as a bare number and lose the original subtask portion (e.g., "5.2" ->
6). Update the mapping in the movedTask.subtasks loop to detect and preserve any
subtask suffix from the original dep: extract the suffix (e.g., the ".N" part)
from the original dep string when present, look up the remapped parent via
idRemapping.get(normalizedDep), and return the remapped parent combined with the
preserved suffix (as a string) instead of returning the raw mapped number; still
fall back to returning the original dep when no remap applies. Ensure this
change touches the block using movedTask.subtasks, normalizeDependency,
idRemapping, and subtask.dependencies.

---

Nitpick comments:
In `@mcp-server/src/core/direct-functions/move-task-cross-tag.js`:
- Around line 204-215: The error branch that checks for TASK_ALREADY_EXISTS in
move-task-cross-tag.js (the else-if that tests error.code ===
'TASK_ALREADY_EXISTS' || error.message?.includes('already exists in target
tag')) is now unreachable because moveTasksBetweenTags no longer throws that
error; remove that entire branch and its suggestions array to eliminate dead
code, or if you prefer to keep defensive handling, replace it with a clear
comment on why the check remains (referencing moveTasksBetweenTags and
TASK_ALREADY_EXISTS) so future readers know it's intentionally retained for
potential behavior changes.
- Around line 120-140: The response builds a duplicate `message` from `result`
(using `renumberedTasks`) which overwrites the core
`moveTasksBetweenTags`/`result.message` produced by `finalizeMove` in
`move-task.js`; remove the local reconstruction and instead use `result.message`
(with a short fallback to the original success string if `result.message` is
missing) when building the returned `data` object. In practice, delete the
`renumberedTasks` computation and `message` assembly and set `data.message` to
`result.message || \`Successfully moved ${sourceIds.length} task(s) from
"${args.sourceTag}" to "${args.targetTag}"\`` while preserving `...result`,
`moveOptions`, `sourceTag` and `targetTag`.

In `@scripts/modules/task-manager/analyze-task-complexity.js`:
- Around line 470-498: The merge logic is correct but add a deterministic sort
to finalComplexityAnalysis before writing so the report is easier to inspect:
after building finalComplexityAnalysis (from existingEntriesNotAnalyzed and
complexityAnalysis) perform a sort by taskId (ensure numeric comparison if
taskId is numeric, otherwise lexicographic) so entries are ordered; this change
touches finalComplexityAnalysis and should be done just before the write that
uses the file path resolved by resolveComplexityReportOutputPath.

In `@tests/integration/move-task-cross-tag.integration.test.js`:
- Around line 553-610: Add integration tests in
tests/integration/move-task-cross-tag.integration.test.js around the
moveTasksBetweenTags scenario to cover dependency remapping and
multiple-renumbering: create mockUtils.readJSON data with multiple tasks moved
from source to target where some target IDs collide and some moved tasks depend
on other moved tasks, call moveTasksBetweenTags, then assert result.movedTasks
contains correct originalId→newId mappings for each moved task and that each
moved task's dependencies were updated to the new IDs; also verify
mockUtils.writeJSON's writtenData for the target tag contains both original and
renumbered tasks with dependency arrays referencing the updated IDs and the
source tag is emptied as before.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 2127e5c8-94f5-4a35-b5c5-70a95d652342

📥 Commits

Reviewing files that changed from the base of the PR and between 2d1211b and 29e3daf.

📒 Files selected for processing (5)
  • .changeset/fix-complexity-merge.md
  • mcp-server/src/core/direct-functions/move-task-cross-tag.js
  • scripts/modules/task-manager/analyze-task-complexity.js
  • scripts/modules/task-manager/move-task.js
  • tests/integration/move-task-cross-tag.integration.test.js

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
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 | 🟠 Major

Same subtask dependency issue affects top-level task dependencies.

The same issue exists here: if a top-level task has a dependency like "5.2" (referencing a subtask), and task 5 was renumbered to 6, this code would replace "5.2" with 6, losing the subtask reference.

🐛 Proposed fix to handle dotted dependencies
 			if (movedTask && Array.isArray(movedTask.dependencies)) {
 				movedTask.dependencies = movedTask.dependencies.map((dep) => {
+					// Handle dotted subtask references like "5.2"
+					if (typeof dep === 'string' && dep.includes('.')) {
+						const [depParent, depSub] = dep.split('.');
+						const parentId = parseInt(depParent, 10);
+						if (Number.isFinite(parentId) && idRemapping.has(parentId)) {
+							return `${idRemapping.get(parentId)}.${depSub}`;
+						}
+						return dep;
+					}
+					// Handle numeric dependencies
 					const normalizedDep = normalizeDependency(dep);
 					if (
 						Number.isFinite(normalizedDep) &&
 						idRemapping.has(normalizedDep)
 					) {
 						return idRemapping.get(normalizedDep);
 					}
 					return dep;
 				});
 			}
🤖 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 955 - 965,
movedTask.dependencies mapping currently normalizes dependencies with
normalizeDependency and blindly replaces the whole dependency when idRemapping
has the normalized value, which breaks dotted subtask refs like "5.2" (becoming
"6" and losing ".2"). Update the mapping in movedTask.dependencies to detect
dotted/string deps, split into baseId and subId (preserve suffix after the first
dot), normalize only the baseId using normalizeDependency, if
idRemapping.has(normalizedBaseId) replace the base with
idRemapping.get(normalizedBaseId) and reattach the original subId (e.g., newBase
+ "." + subId), otherwise leave the original dependency unchanged; keep this
logic inside the same movedTask.dependencies map callback so it uses the
existing normalizeDependency and idRemapping symbols.

Comment on lines +967 to +983
// Also update subtask dependencies that reference remapped IDs
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
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 | 🟠 Major

Subtask dependency references lose subtask portion when parent ID is remapped.

When updating subtask dependencies in the post-move pass, the code uses normalizeDependency which extracts only the parent ID. If a subtask dependency like "5.2" exists and task 5 was renumbered to 6, the code replaces "5.2" with 6 (a plain number), losing the .2 subtask portion.

This differs from the earlier logic at lines 899-913 which correctly preserves the subtask portion using string manipulation.

🐛 Proposed fix to preserve subtask portion during remapping
 			if (movedTask && Array.isArray(movedTask.subtasks)) {
 				movedTask.subtasks.forEach((subtask) => {
 					if (Array.isArray(subtask.dependencies)) {
 						subtask.dependencies = subtask.dependencies.map((dep) => {
+							// Handle dotted subtask references like "5.2"
+							if (typeof dep === 'string' && dep.includes('.')) {
+								const [depParent, depSub] = dep.split('.');
+								const parentId = parseInt(depParent, 10);
+								if (Number.isFinite(parentId) && idRemapping.has(parentId)) {
+									return `${idRemapping.get(parentId)}.${depSub}`;
+								}
+								return dep;
+							}
+							// Handle numeric dependencies
 							const normalizedDep = normalizeDependency(dep);
 							if (
 								Number.isFinite(normalizedDep) &&
 								idRemapping.has(normalizedDep)
 							) {
 								return idRemapping.get(normalizedDep);
 							}
 							return dep;
 						});
 					}
 				});
 			}
🤖 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 967 - 983, The
current post-move update in movedTask.subtasks is using normalizeDependency(dep)
which strips subtask suffixes, so when idRemapping maps a parent ID
(normalizedDep) you end up returning the mapped parent as a bare number and lose
the original subtask portion (e.g., "5.2" -> 6). Update the mapping in the
movedTask.subtasks loop to detect and preserve any subtask suffix from the
original dep: extract the suffix (e.g., the ".N" part) from the original dep
string when present, look up the remapped parent via
idRemapping.get(normalizedDep), and return the remapped parent combined with the
preserved suffix (as a string) instead of returning the raw mapped number; still
fall back to returning the original dep when no remap applies. Ensure this
change touches the block using movedTask.subtasks, normalizeDependency,
idRemapping, and subtask.dependencies.

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.

fix: analyze-complexity overwrites existing report instead of merging across --from/--to runs

2 participants