Agent skill
pm-organization-task-selection
Priority algorithm for selecting next PRD task based on category, dependencies, and risk
Install this agent skill to your Project
npx add-skill https://github.com/majiayu000/claude-skill-registry/tree/main/skills/data/pm-organization-task-selection
SKILL.md
Task Selection Skill
"Fail fast on risky work – tackle hard problems before easy wins."
Parallel Task Opportunity Check (MANDATORY FIRST STEP)
⚠️ CRITICAL: BEFORE selecting any single task, ALWAYS check if parallel assignment is possible.
When both Developer AND Tech Artist are idle, the PM MUST check for parallel work opportunities. This happens BEFORE the standard single-task selection flow.
Check Steps
// 1. Read both PRD files for complete picture
const prd = readJson('prd.json');
const backlog = readJson(prd.backlogFile || 'prd_backlog.json');
const allItems = [...prd.items, ...backlog.backlogItems];
// 2. Check if BOTH agents are idle
const developerIdle =
prd.agents.developer?.status === 'idle' || prd.agents.developer?.status === 'awaiting_pm';
const techartistIdle =
prd.agents.techartist?.status === 'idle' || prd.agents.techartist?.status === 'awaiting_pm';
// 3. If both idle, find tasks for each agent
if (developerIdle && techartistIdle) {
// Find Developer task (architectural, functional, integration categories)
const devCategories = ['architectural', 'functional', 'integration'];
const devTasks = allItems
.filter(
(item) =>
!item.passes &&
item.status === 'pending' &&
devCategories.includes(item.category) &&
item.dependencies.every((depId) => allItems.find((i) => i.id === depId)?.passes === true)
)
.sort(priorityComparator);
// Find Tech Artist task (visual, shader, polish categories)
const artistCategories = ['visual', 'shader', 'polish'];
const artistTasks = allItems
.filter(
(item) =>
!item.passes &&
item.status === 'pending' &&
artistCategories.includes(item.category) &&
item.dependencies.every((depId) => allItems.find((i) => i.id === depId)?.passes === true)
)
.sort(priorityComparator);
// 4. Check if we have non-conflicting tasks
if (devTasks.length > 0 && artistTasks.length > 0) {
const devTask = devTasks[0];
const artistTask = artistTasks[0];
// Check for file path conflicts
if (!areTasksConflicting(devTask, artistTask)) {
// ⭐ PARALLEL ASSIGNMENT PATH ⭐
// Assign BOTH tasks in parallel using 5-step atomic process for each
assignTask(devTask, 'developer');
assignTask(artistTask, 'techartist');
return 'parallel_assigned'; // Exit after parallel assignment
}
}
}
// 5. Fall through to single-task selection if no parallel opportunity
Conflict Detection Function
function areTasksConflicting(task1, task2) {
// Direct file overlap check
const paths1 = task1.files || [];
const paths2 = task2.files || [];
// Check if any file paths overlap
for (const p1 of paths1) {
for (const p2 of paths2) {
// Extract directory paths
const dir1 = p1.substring(0, p1.lastIndexOf('/'));
const dir2 = p2.substring(0, p2.lastIndexOf('/'));
if (dir1 === dir2) return true; // Same directory = conflict
}
}
// Check shared dependencies
const deps1 = new Set(task1.dependencies || []);
const deps2 = new Set(task2.dependencies || []);
for (const dep of deps1) {
if (deps2.has(dep)) return true; // Shared dependency = conflict
}
return false; // No conflicts
}
When to Use This Skill
Use when:
- Assigning a new task from the PRD
prd.json.session.currentTaskis null or task status is "passed"- After retrospective completes
⚠️ FIRST check for parallel assignment, then proceed to single-task selection if parallel is not possible.
Quick Start
// 1. Read both PRD files
const prd = readJson('prd.json');
const backlog = readJson('prd.backlogFile' || 'prd_backlog.json');
// 2. Combine items from both files
const allItems = [...prd.items, ...backlog.backlogItems];
// 3. Filter incomplete items
const incomplete = allItems.filter((item) => !item.passes);
// 4. Filter unblocked items (dependencies met)
const unblocked = incomplete.filter((item) =>
item.dependencies.every((depId) => allItems.find((i) => i.id === depId)?.passes === true)
);
// 5. Sort by priority and select first
const next = unblocked.sort(priorityComparator)[0];
PRD Backlog Architecture
Since v3.1.0, tasks are split between two files:
| File | Contains | Size |
|---|---|---|
prd.json |
Top 5 active queue | ~5 tasks |
prd_backlog.json |
Remaining backlog | ~70 tasks |
Only PM and Game Designer need full backlog access. Workers only read their assigned task.
Reading Tasks from Both Files
When selecting tasks, PM must:
- Read
prd.jsonfor active queue - Read
prd.backlogFile(defaults toprd_backlog.json) - Combine arrays:
allItems = [...prd.items, ...backlog.backlogItems] - Apply filtering/sorting on combined array
- If selected task is in backlog, move it to
prd.json.items
Automatic Backlog Refill
After a task completes (status → "completed") and is removed from active queue:
// Check if refill needed
if (prd.items.length < 5) {
// Read backlog
const backlog = readJson(prd.backlogFile);
// Find highest-priority UNBLOCKED task from backlog
const allTasks = [...prd.items, ...backlog.backlogItems];
const candidates = backlog.backlogItems.filter((item) => {
if (item.passes) return false;
// Check dependencies (need to check across both files)
const depsMet = item.dependencies.every(
(depId) => allTasks.find((t) => t.id === depId)?.passes === true
);
return depsMet;
});
// Sort by priority tier, then by priority value
const tierOrder = {
TIER_0_BLOCKER: 1,
TIER_1_FOUNDATION: 2,
TIER_2_ECONOMY: 3,
TIER_3_SUPPORT: 4,
};
candidates.sort((a, b) => {
const tierDiff = (tierOrder[a.tier] || 99) - (tierOrder[b.tier] || 99);
if (tierDiff !== 0) return tierDiff;
return (
(a.priority === 'critical' ? 1 : a.priority === 'high' ? 2 : 3) -
(b.priority === 'critical' ? 1 : b.priority === 'high' ? 2 : 3)
);
});
// Move highest priority to prd.json
if (candidates.length > 0) {
const toMove = candidates[0];
// Remove from backlog
backlog.backlogItems = backlog.backlogItems.filter((i) => i.id !== toMove.id);
// Add to prd.json
prd.items.push(toMove);
// Update stats
prd.session.stats.backlogSize = backlog.backlogItems.length;
prd.session.stats.activeQueueSize = prd.items.length;
// Write both files
writeJson('prd_backlog.json', backlog);
writeJson('prd.json', prd);
}
}
Decision Framework
| Category | Priority | When to Use |
|---|---|---|
architectural |
1 (Highest) | Affects entire codebase structure |
integration |
2 | Reveals incompatibilities early |
spike / unknown |
3 | Exploratory work, reduces uncertainty |
functional |
4 | Standard feature implementation |
polish |
5 (Lowest) | UI, optimization, documentation |
Progressive Guide
Level 1: Basic Selection
Select first incomplete, unblocked task (reads from both files):
// Read both files
const prd = readJson('prd.json');
const backlog = readJson(prd.backlogFile || 'prd_backlog.json');
const allItems = [...prd.items, ...backlog.backlogItems];
const next = allItems.find(
(item) => !item.passes && item.dependencies.every((d) => allItems.find((i) => i.id === d)?.passes)
);
Level 2: Priority-Weighted Selection
Apply category priority ordering (reads from both files):
// Read both files
const prd = readJson('prd.json');
const backlog = readJson(prd.backlogFile || 'prd_backlog.json');
const allItems = [...prd.items, ...backlog.backlogItems];
const priorityOrder = {
architectural: 1,
integration: 2,
unknown: 3,
spike: 3,
functional: 4,
polish: 5,
};
const priorityValue = { high: 1, medium: 2, low: 3 };
const incomplete = allItems.filter((item) => !item.passes);
const unblocked = incomplete.filter((item) =>
item.dependencies.every((d) => allItems.find((i) => i.id === d)?.passes)
);
unblocked.sort((a, b) => {
const catDiff = priorityOrder[a.category] - priorityOrder[b.category];
if (catDiff !== 0) return catDiff;
return priorityValue[a.priority] - priorityValue[b.priority];
});
Level 3: Risk-Adjusted Selection
Consider retry count and complexity (reads from both files):
// Read both files
const prd = readJson('prd.json');
const backlog = readJson(prd.backlogFile || 'prd_backlog.json');
const allItems = [...prd.items, ...backlog.backlogItems];
// Deprioritize repeatedly failing tasks
if (task.retryCount >= 3) {
// Consider skipping or escalating
logWarning(`Task ${task.id} failed ${task.retryCount} times`);
}
// Boost tasks that unblock many others (check across both files)
const unblockScore = allItems.filter((i) => i.dependencies.includes(task.id)).length;
Anti-Patterns
❌ DON'T:
- Select tasks with unmet dependencies
- Assign new task while
prd.json.session.currentTaskis not null - Skip retrospective to assign faster
- Assign multiple tasks to the SAME agent simultaneously
- Assign parallel tasks that modify the same files/directories
- Skip backlog refill when
prd.json.items.length < 5 - Only read prd.json without checking prd_backlog.json
- Leave completed tasks in prd.json instead of moving to completed file
✅ DO:
- Verify all dependencies have
passes: true(check across both files) - Wait for QA validation before assigning next
- Complete retrospective before clearing
prd.json.session.currentTask - Log selection rationale in progress file
- Assign parallel tasks to Developer AND Tech Artist when safe (see Parallel Assignment below)
- Read both prd.json and prd_backlog.json when selecting tasks
- Automatically refill when active queue drops below 5 tasks
- Move completed tasks to prd_completed.json and refill queue
Parallel Task Assignment (Git Worktree Support)
With git worktrees, Developer and Tech Artist can work simultaneously on non-conflicting tasks.
When to Assign Parallel Tasks
Check these conditions before assigning parallel tasks:
-
Both agents are idle:
prd.json.agents.developer.status === "idle"(or "awaiting_pm")prd.json.agents.techartist.status === "idle"(or "awaiting_pm")
-
Tasks are non-conflicting:
- Different file paths (e.g.,
src/hooks/vssrc/assets/) - No shared dependencies
- Can be tested independently
- Different file paths (e.g.,
-
Each agent has their own worktree:
- Developer works in
../developer-worktreeondeveloper-worktreebranch - Tech Artist works in
../techartist-worktreeontechartist-worktreebranch
- Developer works in
Conflict Detection Matrix
| Developer Category | Tech Artist Category | File Path Overlap? | Safe for Parallel? |
|---|---|---|---|
architectural (src/hooks, src/stores, src/server) |
visual (src/assets) |
❌ No | ✅ Yes |
functional (src/components/* logic) |
shader (src/vfx) |
❌ No | ✅ Yes |
integration (src/utils) |
polish (src/styles) |
❌ No | ✅ Yes |
architectural (src/components) |
visual (src/components) |
⚠️ Yes | ❌ No - sequential only |
functional (public/) |
visual (public/) |
⚠️ Yes | ❌ No - sequential only |
Parallel Assignment Algorithm
// Check if parallel assignment is possible
const developerIdle = prd.agents.developer?.status === 'idle';
const techartistIdle = prd.agents.techartist?.status === 'idle';
if (developerIdle && techartistIdle) {
// Find highest priority Developer task
const devTask = findNextTaskForAgent('developer', allItems);
// Find highest priority Tech Artist task
const artistTask = findNextTaskForAgent('techartist', allItems);
// Check if tasks are non-conflicting
if (areTasksNonConflicting(devTask, artistTask)) {
// Assign to both agents in parallel
assignTask(devTask, 'developer');
assignTask(artistTask, 'techartist');
return; // Exit after parallel assignment
}
}
// Otherwise, assign single task to available agent
File Path-Based Conflict Detection
function areTasksNonConflicting(task1, task2) {
// Define file path patterns for each agent
const devPaths = [
'src/hooks/',
'src/stores/',
'src/server/',
'src/utils/',
'src/types/',
'test/',
];
const artistPaths = [
'src/assets/',
'src/vfx/',
'src/styles/',
'public/textures/',
'public/models/',
];
// Check for overlap (simplified - in production, parse task descriptions)
const task1Path = guessFilePathFromTask(task1);
const task2Path = guessFilePathFromTask(task2);
// Tasks in different directories = non-conflicting
const task1InDev = devPaths.some((p) => task1Path?.startsWith(p));
const task2InArtist = artistPaths.some((p) => task2Path?.startsWith(p));
return task1InDev && task2InArtist;
}
Parallel Assignment Steps
- Check both Developer and Tech Artist are idle
- Find highest-priority incomplete task for each agent
- Verify tasks are non-conflicting (different file paths, no shared deps)
- Assign to Developer (5-step atomic process)
- Assign to Tech Artist (5-step atomic process)
- Exit - both agents work in parallel in their worktrees
Checklist
Before assigning a task:
- Current task is
null(MUST be null, not "passed")- Check:
prd.json.session.currentTask === null
- Check:
- NOT in any retrospective or playtest phase (forbidden to assign during these phases)
- Check:
prd.json.session.status !== "in_retrospective" - Check:
prd.json.session.status !== "retrospective_synthesized" - Check:
prd.json.session.status !== "playtest_phase" - Check:
prd.json.session.status !== "playtest_complete" - Check:
prd.json.session.status !== "prd_refinement" - Check:
prd.json.session.status !== "skill_research"
- Check:
- Previous task is "completed" status (all phases complete)
- Check:
prd.json.items[{previousTaskId}].status === "completed"
- Check:
- Acceptance criteria received from Game Designer (MANDATORY before assignment)
- Check:
prd.json.items[{taskId}].acceptanceCriteriaexists - Check:
prd.json.items[{taskId}].testPlanexists (provided by Game Designer) - If not exist: Send
acceptance_criteria_requestto Game Designer FIRST
- Check:
- Task has all required fields (id, title, description, acceptanceCriteria, testPlan)
- All dependencies have
passes: true - Worker heartbeats are fresh (< 60 seconds)
- Check:
prd.json.agents.{agent}.lastHeartbeatwithin last 60s
- Check:
- Selection rationale logged to progress.txt
⚠️ CRITICAL: New Phased Workflow Must Complete Before Assignment
When a task status is "passed", the following phases MUST complete in order:
- Retrospective (worker contributions only) →
retrospective_synthesized - Playtest Session (Game Designer validates) →
playtest_complete - PRD Refinement (optional, if needed) →
prd_refinementor skip - Acceptance Criteria (MANDATORY - Game Designer provides) →
task_ready - Skill Research (PM improves ALL skills) →
completed
ONLY when status is "completed" may you set prd.json.session.currentTask = null and select the next task.
⚠️ NEVER assign a task without acceptance criteria from Game Designer.
After Selecting a Task
Once a task is selected, you MUST update it in the PRD before assigning:
- Read
prd.json - Find the selected task by ID
- Update the task with:
json
{ "status": "assigned", "assignedAt": "{{ISO_TIMESTAMP}}" } - Write back to
prd.json - Then update session state in
prd.json.session:json{ "session": { "currentTask": "{{taskId}}", "currentTaskStatus": "assigned", "lastAssignmentTime": "{{ISO_TIMESTAMP}}" } }
⚠️ This is Step 1 of the 5-step atomic assignment process. See PM AGENT.md for complete flow.
Completed Task Cleanup (MANDATORY After Retrospective)
⚠️ CRITICAL: After a task completes retrospective (status: "completed"), you MUST:
- Move completed task to
prd_completed.json - Remove it from
prd.json.itemsarray - Refill from backlog if
prd.json.items.length < 5
Step-by-Step Cleanup Procedure
// 1. Identify completed tasks
const prd = readJson('prd.json');
const completedTasks = prd.items.filter(
(item) => item.status === 'completed' && item.passes === true
);
// 2. For each completed task, append to prd_completed.json
for (const task of completedTasks) {
const entry = {
id: task.id,
title: task.title,
category: task.category,
tier: task.tier,
completedAt: task.completedAt || task.qaValidatedAt,
notes: task.notes || '',
validationSummary: task.validationResults || 'PASSED',
};
// Append to completed file
appendFileSync('prd_completed.json', JSON.stringify(entry, null, 2) + '\n\n');
}
// 3. Remove completed tasks from prd.json
prd.items = prd.items.filter((item) => !(item.status === 'completed' && item.passes === true));
// 4. Update completedTasks count
prd.completedTasks =
parseInt(readFileSync('prd_completed.json', 'utf8').match(/"id":/g)?.length || 0) +
completedTasks.length;
// 5. Refill from backlog if needed
if (prd.items.length < 5) {
const backlog = readJson(prd.backlogFile || 'prd_backlog.json');
// Find highest-priority UNBLOCKED task from backlog
const allTasks = [...prd.items, ...backlog.backlogItems];
const candidates = backlog.backlogItems.filter((item) => {
if (item.passes || item.status === 'deferred') return false;
// Check dependencies (need to check across both files)
const depsMet =
item.dependencies?.every((depId) => allTasks.find((t) => t.id === depId)?.passes === true) ??
true;
return depsMet;
});
// Sort by priority tier, then by priority value
const tierOrder = {
TIER_0_BLOCKER: 1,
TIER_1_FOUNDATION: 2,
TIER_2_ECONOMY: 3,
TIER_3_SUPPORT: 4,
};
candidates.sort((a, b) => {
const tierDiff = (tierOrder[a.tier] || 99) - (tierOrder[b.tier] || 99);
if (tierDiff !== 0) return tierDiff;
return (
(a.priority === 'critical' ? 1 : a.priority === 'high' ? 2 : 3) -
(b.priority === 'critical' ? 1 : b.priority === 'high' ? 2 : 3)
);
});
// Move highest priority to prd.json until we have 5 items
while (prd.items.length < 5 && candidates.length > 0) {
const toMove = candidates.shift();
// Remove from backlog
backlog.backlogItems = backlog.backlogItems.filter((i) => i.id !== toMove.id);
// Add to prd.json
prd.items.push(toMove);
}
// Update backlog
writeJson(prd.backlogFile || 'prd_backlog.json', backlog);
}
// 6. Update stats
prd.session.stats.activeQueueSize = prd.items.length;
prd.session.stats.completed = prd.completedTasks;
prd.session.stats.backlogSize = backlog.backlogItems.length;
// 7. Write updated prd.json
writeJson('prd.json', prd);
Cleanup Timing
When to run cleanup:
- ✅ AFTER retrospective completes (task status = "completed", passes = true)
- ✅ BEFORE selecting next task (keeps prd.json clean)
- ❌ NOT during task assignment (too many file operations)
Cleanup Checklist
- Task has
status: "completed"ANDpasses: true - Retrospective completed (
retrospectiveCompletedAtis set) - Task appended to
prd_completed.json - Task removed from
prd.json.items -
prd.completedTaskscount updated - If
prd.json.items.length < 5, refilled from backlog -
prd.session.statsupdated
Reference
- pm-organization-scale-adaptive — Scale-adaptive planning
- pm-organization-task-research — Codebase research before assignment
- shared-worktree — Git worktree coordination for parallel agents
Didn't find tool you were looking for?