Skip to content

Commit bd163b6

Browse files
📝 Add docstrings to fix-ai-sdk-jsonschema-fallback
Docstrings generation was requested by @TheLazyIndianTechie. * #1556 (comment) The following files were modified: * `scripts/modules/task-manager/parse-prd/parse-prd-helpers.js` * `scripts/modules/task-manager/scope-adjustment.js`
1 parent 1d8d723 commit bd163b6

File tree

2 files changed

+108
-17
lines changed

2 files changed

+108
-17
lines changed

scripts/modules/task-manager/parse-prd/parse-prd-helpers.js

Lines changed: 58 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -120,19 +120,21 @@ export function validateFileOperations({
120120
}
121121

122122
/**
123-
* Process and transform tasks with ID remapping
124-
* @param {Array} rawTasks - Raw tasks from AI
125-
* @param {number} startId - Starting ID for new tasks
126-
* @param {Array} existingTasks - Existing tasks for dependency validation
127-
* @param {string} defaultPriority - Default priority for tasks
128-
* @returns {Array} Processed tasks with remapped IDs
129-
*/
123+
* Transform raw PRD tasks into normalized tasks with reassigned sequential IDs.
124+
* @param {Array} rawTasks - Tasks parsed from a PRD; each task must include an integer `id`.
125+
* @param {number} startId - ID to assign to the first processed task.
126+
* @param {Array} existingTasks - Existing tasks used to validate dependency references.
127+
* @param {string} defaultPriority - Priority to apply when a task has no priority.
128+
* @returns {Array} An array of tasks with reassigned sequential IDs, normalized fields (status, priority, title, description, details, testStrategy, subtasks), and dependencies remapped to the new IDs.
130129
export function processTasks(
131130
rawTasks,
132131
startId,
133132
existingTasks,
134133
defaultPriority
135134
) {
135+
// Runtime guard: ensure PRD task IDs are unique and sequential (1..N).
136+
validateSequentialTaskIds(rawTasks, startId);
137+
136138
let currentId = startId;
137139
const taskMap = new Map();
138140
@@ -172,6 +174,54 @@ export function processTasks(
172174
return processedTasks;
173175
}
174176
177+
/**
178+
* Validate that an array of PRD tasks uses unique, positive integer IDs that form a contiguous sequence.
179+
*
180+
* Validates that each task has an integer `id` >= 1, that IDs are unique, and that the sorted IDs form
181+
* a contiguous sequence starting at either `1` or the provided `expectedStartId`.
182+
*
183+
* @param {Array<{id: number}>} rawTasks - Array of task objects containing an `id` property. If not an array or empty, no validation is performed.
184+
* @param {number} [expectedStartId=1] - Allowed alternative starting ID for the contiguous sequence.
185+
* @throws {Error} If any `id` is not an integer >= 1: "PRD tasks must use sequential positive integer IDs starting at 1."
186+
* @throws {Error} If IDs are not unique: "PRD task IDs must be unique and sequential starting at 1."
187+
* @throws {Error} If the sequence does not start at `1` or `expectedStartId`: "PRD task IDs must start at 1 or {expectedStartId} and be sequential."
188+
* @throws {Error} If IDs are not contiguous: "PRD task IDs must be a contiguous sequence starting at {startId}."
189+
*/
190+
function validateSequentialTaskIds(rawTasks, expectedStartId = 1) {
191+
if (!Array.isArray(rawTasks) || rawTasks.length === 0) {
192+
return;
193+
}
194+
195+
const ids = rawTasks.map((task) => task.id);
196+
197+
if (ids.some((id) => !Number.isInteger(id) || id < 1)) {
198+
throw new Error(
199+
'PRD tasks must use sequential positive integer IDs starting at 1.'
200+
);
201+
}
202+
203+
const uniqueIds = new Set(ids);
204+
if (uniqueIds.size !== ids.length) {
205+
throw new Error('PRD task IDs must be unique and sequential starting at 1.');
206+
}
207+
208+
const sortedIds = [...uniqueIds].sort((a, b) => a - b);
209+
const startId = sortedIds[0];
210+
if (startId !== 1 && startId !== expectedStartId) {
211+
throw new Error(
212+
`PRD task IDs must start at 1 or ${expectedStartId} and be sequential.`
213+
);
214+
}
215+
216+
for (let index = 0; index < sortedIds.length; index += 1) {
217+
if (sortedIds[index] !== startId + index) {
218+
throw new Error(
219+
`PRD task IDs must be a contiguous sequence starting at ${startId}.`
220+
);
221+
}
222+
}
223+
}
224+
175225
/**
176226
* Save tasks to file with tag support
177227
* @param {string} tasksPath - Path to save tasks
@@ -380,4 +430,4 @@ export function displayNonStreamingCliOutput({
380430
if (aiServiceResponse?.telemetryData) {
381431
displayAiUsageSummary(aiServiceResponse.telemetryData, 'cli');
382432
}
383-
}
433+
}

scripts/modules/task-manager/scope-adjustment.js

Lines changed: 50 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -157,14 +157,22 @@ function getCurrentComplexityScore(taskId, context) {
157157
}
158158

159159
/**
160-
* Regenerates subtasks for a task based on new complexity while preserving completed work
161-
* @param {Object} task - The updated task object
162-
* @param {string} tasksPath - Path to tasks.json
163-
* @param {Object} context - Context containing projectRoot, tag, session
164-
* @param {string} direction - Direction of scope change (up/down) for logging
165-
* @param {string} strength - Strength level ('light', 'regular', 'heavy')
166-
* @param {number|null} originalComplexity - Original complexity score for smarter adjustments
167-
* @returns {Promise<Object>} Object with updated task and regeneration info
160+
* Regenerates a task's pending subtasks to match a changed complexity while preserving subtasks with work already done.
161+
*
162+
* Generates new pending subtasks (via the configured AI service) to reach a target subtask count based on direction and strength, preserves subtasks whose status indicates work should be kept, and remaps IDs/dependencies so generated subtasks follow preserved ones.
163+
*
164+
* @param {Object} task - The task object to update; its subtasks array will be replaced with preserved and newly generated subtasks in the returned `updatedTask`.
165+
* @param {string} tasksPath - Filesystem path to tasks.json (used for context; not modified by this function).
166+
* @param {Object} context - Execution context containing { projectRoot, tag, session, ... } used for AI calls and logging.
167+
* @param {string} direction - Scope change direction: 'up' to increase complexity or 'down' to decrease complexity.
168+
* @param {string} [strength='regular'] - Adjustment strength: 'light', 'regular', or 'heavy'.
169+
* @param {number|null} [originalComplexity=null] - Original complexity score (1-10) used to bias how aggressive the adjustment should be.
170+
* @returns {Promise<Object>} An object with:
171+
* - updatedTask: the task object with preserved and regenerated subtasks,
172+
* - regenerated: `true` if new subtasks were generated, `false` otherwise,
173+
* - preserved: number of subtasks kept,
174+
* - generated: number of subtasks newly generated,
175+
* - error?: string when regeneration failed (present only on failure).
168176
*/
169177
async function regenerateSubtasksForComplexity(
170178
task,
@@ -378,6 +386,7 @@ Ensure the JSON is valid and properly formatted.`;
378386
});
379387

380388
const generatedSubtasks = aiResult.mainResult.subtasks || [];
389+
ensureSequentialSubtaskIds(generatedSubtasks);
381390

382391
// Post-process generated subtasks to ensure defaults
383392
const processedGeneratedSubtasks = generatedSubtasks.map((subtask) => ({
@@ -441,6 +450,38 @@ Ensure the JSON is valid and properly formatted.`;
441450
}
442451
}
443452

453+
/**
454+
* Validate that subtasks have unique, positive integer IDs forming a sequential series starting at 1.
455+
*
456+
* @param {Array<object>} subtasks - Array of subtask objects; each object is expected to have an `id` property.
457+
* @throws {Error} If any `id` is not a positive integer ("Generated subtask ids must be positive integers").
458+
* @throws {Error} If `id` values are not unique ("Generated subtasks must have unique ids").
459+
* @throws {Error} If `id` values are not a contiguous sequence starting at 1 ("Generated subtask ids must be sequential starting from 1").
460+
*/
461+
function ensureSequentialSubtaskIds(subtasks) {
462+
if (!Array.isArray(subtasks) || subtasks.length === 0) {
463+
return;
464+
}
465+
466+
const ids = subtasks.map((subtask) => subtask.id);
467+
if (ids.some((id) => !Number.isInteger(id) || id < 1)) {
468+
throw new Error('Generated subtask ids must be positive integers');
469+
}
470+
const uniqueIds = new Set(ids);
471+
if (uniqueIds.size !== ids.length) {
472+
throw new Error('Generated subtasks must have unique ids');
473+
}
474+
475+
const sortedIds = [...uniqueIds].sort((a, b) => a - b);
476+
for (let index = 0; index < sortedIds.length; index += 1) {
477+
if (sortedIds[index] !== index + 1) {
478+
throw new Error(
479+
'Generated subtask ids must be sequential starting from 1'
480+
);
481+
}
482+
}
483+
}
484+
444485
/**
445486
* Generates AI prompt for scope adjustment
446487
* @param {Object} task - The task to adjust
@@ -872,4 +913,4 @@ export async function scopeDownTask(
872913
updatedTasks,
873914
telemetryData: combinedTelemetryData
874915
};
875-
}
916+
}

0 commit comments

Comments
 (0)