一键导入
pm-organization-task-selection
Priority algorithm for selecting next PRD task based on category, dependencies, and risk
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
菜单
Priority algorithm for selecting next PRD task based on category, dependencies, and risk
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
基于 SOC 职业分类
Complete Developer workflow orchestration - task research sequence, implementation flow, validation gates, PRD synchronization, exit conditions.
Complete Game Designer workflow - skill invocation protocol, GDD creation, playtest flow with GDD review, design sessions. MUST load before starting assignments.
Complete PM Coordinator workflow - task assignment, project orchestration, PRD management, worker coordination. Use proactively when starting PM agent work.
Complete PM Coordinator workflow - task assignment, project orchestration, PRD management, worker coordination. Use proactively when starting PM agent work.
Complete QA Validator workflow orchestration. References specialized skills for each validation step. Load at session startup for full protocol.
Base instructions and guidelines for all agents in the system. This skill provides foundational behaviors and communication protocols that all agents should follow.
| name | pm-organization-task-selection |
| description | Priority algorithm for selecting next PRD task based on category, dependencies, and risk |
| category | organization |
"Fail fast on risky work – tackle hard problems before easy wins."
⚠️ 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.
// 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
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
}
Use when:
prd.json.session.currentTask is null or task status is "passed"⚠️ FIRST check for parallel assignment, then proceed to single-task selection if parallel is not possible.
// 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];
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.
When selecting tasks, PM must:
prd.json for active queueprd.backlogFile (defaults to prd_backlog.json)allItems = [...prd.items, ...backlog.backlogItems]prd.json.itemsAfter 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);
}
}
| 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 |
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)
);
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];
});
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;
❌ DON'T:
prd.json.session.currentTask is not nullprd.json.items.length < 5✅ DO:
passes: true (check across both files)prd.json.session.currentTaskWith git worktrees, Developer and Tech Artist can work simultaneously on non-conflicting 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:
src/hooks/ vs src/assets/)Each agent has their own worktree:
../developer-worktree on developer-worktree branch../techartist-worktree on techartist-worktree branch| 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 |
// 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
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;
}
Before assigning a task:
null (MUST be null, not "passed")
prd.json.session.currentTask === nullprd.json.session.status !== "in_retrospective"prd.json.session.status !== "retrospective_synthesized"prd.json.session.status !== "playtest_phase"prd.json.session.status !== "playtest_complete"prd.json.session.status !== "prd_refinement"prd.json.session.status !== "skill_research"prd.json.items[{previousTaskId}].status === "completed"prd.json.items[{taskId}].acceptanceCriteria existsprd.json.items[{taskId}].testPlan exists (provided by Game Designer)acceptance_criteria_request to Game Designer FIRSTpasses: trueprd.json.agents.{agent}.lastHeartbeat within last 60s⚠️ CRITICAL: New Phased Workflow Must Complete Before Assignment
When a task status is "passed", the following phases MUST complete in order:
retrospective_synthesizedplaytest_completeprd_refinement or skiptask_readycompletedONLY 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.
Once a task is selected, you MUST update it in the PRD before assigning:
prd.json{
"status": "assigned",
"assignedAt": "{{ISO_TIMESTAMP}}"
}
prd.jsonprd.json.session:
{
"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.
⚠️ CRITICAL: After a task completes retrospective (status: "completed"), you MUST:
prd_completed.jsonprd.json.items arrayprd.json.items.length < 5// 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);
When to run cleanup:
status: "completed" AND passes: trueretrospectiveCompletedAt is set)prd_completed.jsonprd.json.itemsprd.completedTasks count updatedprd.json.items.length < 5, refilled from backlogprd.session.stats updatedWhen assigning tasks related to JSON level data:
Verification Requirements:
Test Plan Considerations:
| Task Type | Base Priority | Adjustment |
|---|---|---|
| Level JSON creation | functional | +1 if blocks progression |
| Schema validation | architectural | +2 (data integrity) |
| Asset loading | architectural | +1 (blocks gameplay) |