plan#
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#
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#
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#
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.
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.
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#
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#
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#
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).
type PlanError struct {
Line int
Msg string
}
func Validate#
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#
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.
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#
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.
type StepRef struct {
GroupPath []int
Index int
}
func (StepRef) ID#
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