---
title: internal/plan
description: Go API reference for the plan package.
---
# plan
```go
import "github.com/jbcom/radioactive-ralph/internal/plan"
```
Package plan implements the heuristic markdown plan engine described in docs/superpowers/specs/2026\-07\-16\-supervisor\-architecture\-design.md §11.
Plans are markdown documents parsed with goldmark into an AST and decomposed heuristically \-\- no LLM, no structured output, no vectors. The grammar:
- A heading of level N is a nesting group. Its "section" runs from the heading to the next heading of level \<= N.
- Heading order encodes group dependency: "\# Do first" then "\# Do next" means the first group completes before the second \(sequential groups at the same level\).
- Under a leaf heading \(no child subheadings in its section\): an unordered list is parallelizable steps; an ordered list is sequential steps. A step may carry paragraphs of detail \(bullets \+ paragraphs together form one step with detail\).
- Do not descend past a heading that has child subheadings \-\- the subheadings carry the ordering.
## Index
- [func DecomposeRefs\(p \*Plan, done map\[string\]bool\) \(readyNow \[\]Step, refs \[\]StepRef, parallel bool\)](<#DecomposeRefs>)
- [func Slug\(title string\) string](<#Slug>)
- [func Title\(markdown, fallback string\) string](<#Title>)
- [type Group](<#Group>)
- [type Plan](<#Plan>)
- [func Parse\(md \[\]byte\) \(\*Plan, error\)](<#Parse>)
- [func \(p \*Plan\) StepAt\(ref StepRef\) \(Step, Group, error\)](<#Plan.StepAt>)
- [func \(p \*Plan\) StepIDs\(\) \[\]string](<#Plan.StepIDs>)
- [type PlanError](<#PlanError>)
- [func Validate\(md \[\]byte\) \[\]PlanError](<#Validate>)
- [func \(e PlanError\) String\(\) string](<#PlanError.String>)
- [type Step](<#Step>)
- [func Decompose\(p \*Plan, done map\[string\]bool\) \(readyNow \[\]Step, parallel bool\)](<#Decompose>)
- [type StepRef](<#StepRef>)
- [func \(r StepRef\) ID\(\) string](<#StepRef.ID>)
## func [DecomposeRefs]()
```go
func DecomposeRefs(p *Plan, done map[string]bool) (readyNow []Step, refs []StepRef, parallel bool)
```
DecomposeRefs is like Decompose but also returns the StepRef for each ready step, in the same order, so a caller can mark individual steps done via StepRef.ID\(\) without recomputing positions.
## func [Slug]()
```go
func Slug(title string) string
```
Slug lower\-cases a title and collapses every run of non\-alphanumeric characters into a single hyphen, trimming leading/trailing hyphens — a stable, filesystem\-and\-URL\-safe plan slug. Returns "plan" for an all\-punctuation/empty title.
## func [Title]()
```go
func Title(markdown, fallback string) string
```
Title returns the plan markdown's first level\-1 heading, or fallback \(e.g. a filename sans extension\) when there is none or it is blank. Shared by the \`plan import\` CLI and the supervisor's plan\-import IPC handler so both derive identical titles.
## type [Group]()
Group is a single heading's section. A Group either carries Steps \(it is a leaf: no child subheadings appear in its section\) or SubGroups \(it has child subheadings, which carry the ordering\) \-\- never both.
```go
type Group struct {
// Heading is the trimmed text of the heading line.
Heading string
// Level is the heading level (1-6).
Level int
// Parallel is true when this leaf group's steps come from an
// unordered list (dispatchable together). It is false for an
// ordered-list leaf (steps run one at a time) and is meaningless
// (left false) for a non-leaf group.
Parallel bool
// Steps holds this leaf group's steps in document order. Empty for
// a non-leaf group.
Steps []Step
// SubGroups holds this group's child subheadings in document order.
// Empty for a leaf group.
SubGroups []Group
}
```
## type [Plan]()
Plan is the parsed, nested representation of a plan document.
```go
type Plan struct {
// Groups holds the top-level (heading level 1) groups in document
// order. Document order is dependency order: Groups[0] completes
// before Groups[1] starts, and so on.
Groups []Group
}
```
### func [Parse]()
```go
func Parse(md []byte) (*Plan, error)
```
Parse parses plan markdown into a Plan. Parse uses goldmark's core parser only \(block \+ inline\); GFM extensions \(tables, strikethrough, autolinks, task\-list checkboxes, etc.\) are deliberately not enabled \-\- the plan grammar is intentionally small.
### func \(\*Plan\) [StepAt]()
```go
func (p *Plan) StepAt(ref StepRef) (Step, Group, error)
```
StepAt resolves a StepRef back to its Step and owning Group, primarily for callers that received a StepRef from DecomposeRefs and need to re\-fetch the current Step/Group \(e.g. after a re\-parse\).
### func \(\*Plan\) [StepIDs]()
```go
func (p *Plan) StepIDs() []string
```
StepIDs returns the stable ID \(see StepRef.ID\) for every step in the plan, in document order. This is the full universe of valid keys for a done\-set map, and is useful for validating/seeding one.
## type [PlanError]()
PlanError describes one advisory ambiguity found in a plan document. Line is 1\-based, matching editor conventions; it is 0 when the finding applies to the document as a whole rather than one location.
part of this package's specified public API \(see the Phase 6a plan engine grammar\); calling it plan.Error would collide with the "Error\(\) string" convention for the error interface, which this type deliberately does not implement \(findings are advisory, not errors\).
```go
type PlanError struct {
Line int
Msg string
}
```
### func [Validate]()
```go
func Validate(md []byte) []PlanError
```
Validate parses md and flags grammar ambiguities the heuristic decomposer \(Parse/Decompose\) has to guess through. Validate is advisory: it never blocks Parse from running, but a plan with findings is a plan whose dispatch order may not be what its author intended. Findings are:
- a section with both a list and a leading bare paragraph, which is ambiguous under the disambiguation rule \(list =\> step\-group, bare paragraph with no list =\> narrative\) when the paragraph precedes the list and could be misread as an intended first step;
- a section that mixes an ordered and an unordered list \-\- Parse picks the first list's orderedness for Group.Parallel and silently folds the rest in, which is very likely not what the author meant;
- an empty group: a heading whose section \(recursing into subheadings\) has no steps at all.
### func \(PlanError\) [String]()
```go
func (e PlanError) String() string
```
## type [Step]()
Step is a single unit of work: the list item text plus any trailing paragraph\(s\) of detail found alongside the list under the same heading.
```go
type Step struct {
// Text is the trimmed text of the list item itself, with any recognized
// trailing marker (see RequiresApproval) stripped off.
Text string
// Detail is the trimmed, newline-joined text of any paragraphs found
// in the same section as the list (narrative elaborating the step).
// Empty when there is no such detail.
Detail string
// RequiresApproval is true when the step carries the `[approval]` marker
// (case-insensitive, at the end of the list-item text). Such a step is
// materialized as a task in status 'ready_pending_approval': it is held
// out of dispatch until an operator approves it (GUI/IPC ApproveTask),
// which transitions it to 'ready' so it becomes claimable. This is the
// human-in-the-loop gate — the producer for the approval flow the
// observe/drive surface already exposes.
RequiresApproval bool
}
```
### func [Decompose]()
```go
func Decompose(p *Plan, done map[string]bool) (readyNow []Step, parallel bool)
```
Decompose computes the PRESENT: what is dispatchable right now, given the plan structure and a done\-set keyed by StepRef.ID\(\).
It walks groups in document order \(document order encodes dependency: an earlier group must complete before a later one starts\). Within the first not\-fully\-done group:
- if it has subgroups, Decompose recurses into the first incomplete subgroup \(subheadings carry the ordering, so earlier subgroups gate later ones exactly like top\-level groups do\);
- at a leaf, if the group is Parallel, every not\-done step is returned together \(they are dispatchable concurrently\); otherwise \(sequential\) only the first not\-done step is returned, since later steps depend on it completing.
Decompose returns \(nil, false\) when every step in the plan is done.
## type [StepRef]()
StepRef identifies one Step's position in a Plan: the path of zero\-based indices through Group/SubGroups from the plan root, followed by the zero\-based index into that leaf Group's Steps.
```go
type StepRef struct {
GroupPath []int
Index int
}
```
### func \(StepRef\) [ID]()
```go
func (r StepRef) ID() string
```
ID returns a stable, deterministic string key for this step, suitable for use in a done\-set. It is derived purely from position in the plan tree \(e.g. "0.1.2"\), not from step text, so it stays stable across re\-parses of the same document and is independent of wording edits that don't change structure.
Generated by [gomarkdoc]()