--- 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]()