Documentation
¶
Overview ¶
Package plan parses single-file Plan artifacts at spec/plans/<slug>.md per the SpecStudio plan-Feature contract (https://github.com/synchestra-io/specstudio-skills/blob/main/spec/features/skills/plan/README.md).
The directory-form plans at spec/plans/<slug>/README.md historically used by specscore-cli are out of scope for this package — they are parsed by the existing plan-hierarchy / plan-roi-metadata lint checkers.
Index ¶
Constants ¶
const FormatURL = "https://specscore.md/plan-specification"
FormatURL is the canonical spec URL for the Plan document type. It is carried verbatim in both the frontmatter `format:` field and the adherence-footer line, per the artifact-frontmatter-convention.
const PlaceholderBodyToken = "<!-- implement: pending -->"
PlaceholderBodyToken is the byte-exact marker the parser recognizes as a placeholder task body in `**Mode:** stub` Plans. The MVP working decision (see Open Questions in the plan-rules Feature) is an HTML comment so the marker is invisible in rendered markdown.
Variables ¶
This section is empty.
Functions ¶
func IsSingleFilePlanPath ¶
IsSingleFilePlanPath reports whether path looks like a single-file Plan candidate location — i.e., directly under spec/plans/, has a `.md` extension, and is not named README.md (which is the index file).
It does NOT read the file; callers still must validate the title prefix via Parse() before treating it as a Plan.
func Scaffold ¶ added in v0.7.0
func Scaffold(opts ScaffoldOptions) ([]byte, error)
Scaffold returns a lint-clean flat Plan file body: the artifact-frontmatter-convention frontmatter (`format:` + `status:` mirroring the body `**Status:** Draft`), the `# Plan:` title, the body-metadata header, the four required sections with HTML-comment prompts, and the adherence footer whose URL agrees with `format:`.
func ValidateSlug ¶ added in v0.7.0
ValidateSlug returns nil when slug is a lowercase, hyphen-separated, URL-safe identifier with no `/` (cli/plan/new#req:slug-format).
Types ¶
type DeferredAC ¶
type DeferredAC struct {
ACID string // `<feature-slug>#ac:<ac-slug>`
Line int // 1-based line of the entry
Reason string // text after the em-dash; opaque to lint
}
DeferredAC is a single `- <feature-slug>#ac:<ac-slug> — <reason>` line.
type Plan ¶
type Plan struct {
Path string // absolute path on disk
Slug string // filename without `.md`
HasPlanTitle bool // first H1 line was `# Plan: <title>`
TitleLine int // 1-based line number of the title (0 when absent)
Title string // the `<title>` portion after `# Plan: `
SourceFeature string // value of `**Source Feature:**` (empty when missing)
SourceFeatureLine int // 1-based line of the field; 0 when absent
Status string // value of `**Status:**` (empty when missing)
StatusLine int // 1-based line of the field; 0 when absent
Date string // value of `**Date:**` (empty when missing)
DateLine int // 1-based line of the field; 0 when absent
Owner string // value of `**Owner:**` (empty when missing)
OwnerLine int // 1-based line of the field; 0 when absent
Mode Mode // `full` (default) or `stub`
ModeLine int // 1-based line of `**Mode:**`; 0 when absent
ModeRaw string // raw value as written (used by P-004 to report invalid tokens)
ModeRawPresent bool // true when the field was present at all
ModeValueValid bool // true when ModeRaw parsed cleanly into Mode
Tasks []Task // task blocks in source order
DeferredACs []DeferredAC // entries under `## Deferred AC Coverage`
DeferredACsLine int // 1-based line of the H2 heading; 0 when absent
}
Plan is a parsed single-file Plan artifact.
func Discover ¶ added in v0.7.0
Discover walks the direct children of plansDir and returns the parsed single-file Plans found there, sorted alphabetically by Slug.
It selects candidates via IsSingleFilePlanPath (which excludes README.md and anything not directly under plansDir), Parses each, and keeps only files whose first H1 was `# Plan: <title>` (HasPlanTitle == true). Directory-form plans at spec/plans/<slug>/README.md are out of scope and skipped.
An absent plansDir is not an error: Discover returns an empty slice and nil.
func Parse ¶
Parse reads a candidate Plan file. It returns a populated Plan even when the file is not actually a Plan (HasPlanTitle == false in that case) so callers can distinguish "not a Plan" from "malformed Plan".
func (*Plan) TaskRollup ¶ added in v0.7.0
TaskRollup tallies p.Tasks by status. Total is len(p.Tasks); each per-status count is 0 when no task holds that status.
type Rollup ¶ added in v0.7.0
type Rollup struct {
Total int `yaml:"total" json:"total"`
Done int `yaml:"done" json:"done"`
InProgress int `yaml:"in-progress" json:"in-progress"`
Pending int `yaml:"pending" json:"pending"`
Blocked int `yaml:"blocked" json:"blocked"`
}
Rollup counts a plan's tasks by their parsed task **Status:** value.
type ScaffoldOptions ¶ added in v0.7.0
type ScaffoldOptions struct {
Slug string
Title string // defaults to a title-cased slug
Owner string // defaults to "unknown"
Date string // ISO-8601 (YYYY-MM-DD); defaults to today's UTC date
// Exactly one of SourceFeature / SourceIdea must be set: a plan decomposes
// exactly one Feature or one Idea (cli/plan/new#req:source-required).
SourceFeature string
SourceIdea string
}
ScaffoldOptions controls the flat Plan file Scaffold emits.
type Task ¶
type Task struct {
Number int // parsed N from `### Task N:`
Name string // text after `Task N: `
HeadingLine int // 1-based line of the `### Task N:` heading
BodyLines []string // lines after the heading, up to the next task / H2 / EOF (verbatim)
BodyStart int // 1-based line where the body begins (one past the heading)
Verifies []string // AC IDs from `**Verifies:**`, in source order
VerifiesLine int // 1-based line of `**Verifies:**`; 0 when absent
VerifiesPresent bool // true when the field was present
Status TaskStatus
StatusLine int // 1-based line of `**Status:**`; 0 when absent
StatusRaw string // raw value as written
StatusPresent bool // true when the field was present
StatusValueValid bool // true when StatusRaw parsed cleanly into TaskStatus
DependsOn []int // predecessor task numbers, empty when none
DependsOnLine int // 1-based line of `**Depends-On:**`; 0 when absent
DependsOnRaw string // raw value as written
DependsOnPresent bool
DependsOnValid bool // true when raw value parsed cleanly (em-dash or list of ints)
HasPlaceholder bool // true when the body contains the placeholder token on its own line
PlaceholderLine int // 1-based line of the placeholder; 0 when absent
}
Task captures a `### Task N: <name>` block.
type TaskStatus ¶
type TaskStatus string
TaskStatus enumerates valid `**Status:**` task body-field values.
const ( StatusPending TaskStatus = "pending" StatusInProgress TaskStatus = "in-progress" StatusDone TaskStatus = "done" StatusBlocked TaskStatus = "blocked" )