Quantum-Loop: Plan
You are converting a Product Requirements Document (PRD) into a machine-readable quantum.json file that will drive autonomous execution. Every decision you make here determines whether the execution loop succeeds or fails.
Phase 0: Phase-skip check (Phase 18 / P2.4)
Before reading the PRD, check whether a prior /ql-plan run already converted the same PRD + spec handoff into quantum.json:
PRD=$(ls -t tasks/prd-*.md 2>/dev/null | head -1)
SPEC_HANDOFF=".handoffs/spec.md"
ARGS=()
[[ -n "$PRD" ]] && ARGS+=("$PRD")
[[ -f "$SPEC_HANDOFF" ]] && ARGS+=("$SPEC_HANDOFF")
if bash lib/phase-skip.sh skip plan . "${ARGS[@]}"; then
echo "[SKIP] plan is up-to-date — PRD + spec handoff unchanged."
bash lib/handoff.sh read plan | jq '.'
exit 0
fi
After writing quantum.json and .handoffs/plan.md, record the fingerprint:
PRD_H=$(bash lib/phase-skip.sh hash "$PRD")
SPEC_H=$(bash lib/phase-skip.sh hash "$SPEC_HANDOFF")
FP=$(jq -cn --arg pp "$PRD" --arg ph "$PRD_H" --arg sp "$SPEC_HANDOFF" --arg sh "$SPEC_H" \
'{artifacts: [{path: $pp, sha256: $ph}, {path: $sp, sha256: $sh}]}')
bash lib/phase-skip.sh record plan "$FP" . >/dev/null
Prerequisite: read prior-stage handoffs (Phase 15 / P2.3)
Before reading the PRD, ingest every prior-stage handoff so decisions, rejected alternatives, and risks carry forward across context compaction:
bash lib/handoff.sh all | jq '.'
bash lib/handoff.sh read brainstorm | jq '.'
bash lib/handoff.sh read spec | jq '.'
Treat spec.decided as binding (these are the ACs you MUST plan for), spec.rejected as closed (don't re-introduce), spec.remaining as explicit gaps you should surface to the user before finalizing the DAG, and the union of brainstorm.risks ∪ spec.risks as mandatory inputs to every story's risk consideration.
At the end of /ql-plan, write .handoffs/plan.md:
bash lib/handoff.sh write plan "$(cat <<'JSON'
{
"decided": ["<each DAG + wave decision>", "<contract materialization picks>"],
"rejected": ["<each alternative story split / ordering considered>"],
"risks": ["<carried from upstream + any new planning risks>"],
"files": ["quantum.json"],
"remaining": ["<any AC you could not resolve into a concrete story>"],
"notes": "<notes on parallelism, file-conflict sets, contract choices>"
}
JSON
)"
Step 1: Read the PRD
- Look for the most recent PRD in
tasks/prd-*.md - If multiple PRDs exist, ask the user which one to convert
- Read the entire PRD, extracting:
- User stories (US-NNN) with acceptance criteria
- Functional requirements (FR-N)
- Technical considerations and constraints
- Non-goals (to prevent scope creep during execution)
Also read:
- Project files (package.json, pyproject.toml, etc.) for project name and tech stack
- Existing code structure to determine correct file paths for tasks
Step 2: Analyze Dependencies
Build a dependency graph between stories. Dependencies follow natural layering:
1. Schema / Database changes (foundation)
2. Type definitions / Models (depends on schema)
3. Backend logic / API endpoints (depends on types)
4. UI components (depends on API)
5. Integration / Aggregate views (depends on components)
Dependency Rules
- A story that reads from a table DEPENDS ON the story that creates that table
- A story that renders data DEPENDS ON the story that provides the API
- A story that tests integration DEPENDS ON all component stories
- If two stories touch unrelated parts of the codebase, they are INDEPENDENT (no dependency)
Cycle Detection
After building the dependency graph, verify there are no cycles. If you detect a cycle:
- STOP and inform the user
- Explain which stories form the cycle
- Ask how to break the cycle (usually by splitting a story)
Contracts Generation (after dependency DAG)
After building the dependency graph, scan for values that appear in 2+ stories' acceptance criteria or task descriptions. These are contract candidates — shared constants that parallel agents must agree on.
- Identify candidates: Look for repeated references to the same entity across stories — secret key names, environment variable names, type/class names, API route paths, event names, CSS class names
- Group by category: Organize candidates into logical categories:
secret_keys— shared secret/config key namesenv_vars— environment variable namesshared_types— type names, class names, enum valuesapi_routes— API endpoint pathsevent_names— event/signal namescss_classes— shared CSS class names or design tokens
- Rule: When in doubt, add it — an unused contract entry costs nothing; a missing contract causes cross-story mismatches that require manual fixes
- Optional
patternfield: For values with a naming convention, add apatternregex so the implementer can validate at runtime (e.g.,"pattern": "^[a-z][a-z0-9-]*$")
Example contracts block:
"contracts": {
"secret_keys": {
"openai": { "value": "openai-api-key", "pattern": "^[a-z][a-z0-9-]*$" },
"db_password": { "value": "DATABASE_PASSWORD" }
},
"shared_types": {
"priority_enum": { "value": "Priority" }
}
}
Add the contracts object to quantum.json at the top level, after codebasePatterns.
For language-specific shape and definition examples, read references/contract-shapes.md when generating structural contracts for shared types.
Structural Contract Generation (Enhanced)
After building the basic contracts block above, enhance shared_types entries with structural information so that downstream layers (materialization, type audit) can generate real code files.
Step 1: Detect Shared Types
Scan all stories' descriptions, acceptance criteria, and task descriptions for type names (classes, interfaces, structs, enums) that appear in 2 or more stories. These are structural contract candidates.
For each shared type candidate:
shape— A structured representation of the type's interface:properties: Array of{name, type, readonly?}entriesmethods: Array of{name, params: [{name, type}], returns}entries
definition— A verbatim code string in the project's language (see Step 2 for language detection)owner— The story ID that primarily implements/defines the type (usually the story that creates it as an output)consumers— Array of story IDs that reference or depend on the type (all stories except the owner)definitionFile— The file path where the type definition should live (see "InferringdefinitionFilePaths" below)
Anti-rationalization: If 2+ stories reference a type by name, you MUST generate shape and definition fields. "It's only used lightly" or "the shape is obvious" are not valid reasons to skip structural contracts. The downstream materializer cannot generate a file without a definition or shape.
Step 2: Detect Project Language
Determine the project's primary language by checking for config files in the project root:
| Config File | Language | definition Style |
|---|---|---|
tsconfig.json | TypeScript | export interface X { ... } or export type X = { ... } |
pyproject.toml or setup.py | Python | class X(Protocol): ... or @dataclass class X: ... |
go.mod | Go | type X interface { ... } or type X struct { ... } |
Detection priority: check in the order listed above. If multiple config files exist, use the definitionFile extension as a tiebreaker.
Step 3: Generate Language-Specific Definitions
Based on the detected language, generate the definition string:
TypeScript:
export interface TaskResult {
id: string;
status: "pending" | "passed" | "failed";
output: string;
errorMessage?: string;
}
Python:
from dataclasses import dataclass
from typing import Optional
@dataclass
class TaskRe