Documentation
¶
Overview ¶
Package plancontract owns the schema for structured plans: the machine-readable plan document stored at .sprout/plan.json (plus a rendered .sprout/plan.md view that is regenerated from the JSON on every write) and the validator that every reader and writer of that file must run.
A plan is the single source of truth that links planned work to machine- checkable acceptance criteria, so that later tooling (verified done, progress events, benchmark) can tell whether the work a plan describes is actually finished. The package is a pure data + validation contract: it holds no I/O. Callers read and write the JSON file; this package defines its shape and enforces its invariants.
JSON field names are part of the on-disk contract and must stay in sync with the spec (roadmap/SP-148-structured-plans.md). Bumping the schema version (SchemaVersion / SupportedVersions) is the only supported way to change the contract in a breaking way.
Index ¶
Constants ¶
const SchemaVersion = 1
SchemaVersion is the current plan schema version. A plan document carries it in its "version" field; readers that do not support it must refuse the plan rather than guess.
Variables ¶
var SupportedVersions = []int{SchemaVersion}
SupportedVersions are the schema versions Validate accepts. A plan whose version is not in this set fails validation with a clear message instead of being silently misread.
Functions ¶
func KindDescription ¶
KindDescription returns a short human description of k, or "" for an unknown kind.
func Validate ¶
Validate reports every structural problem with plan and returns a *ValidationError when the plan is invalid, or nil when it is valid.
It is a pure function over the in-memory struct, so it can be used both when building a new plan and on every write (every write validates and bumps revision). The invariants enforced are:
- version is a supported schema version;
- revision >= 1 (a persisted plan has been written at least once);
- created and updated are set;
- goal is non-empty;
- every scope item has a non-empty id and title, and scope ids are unique;
- every step references a known scope id and has a description;
- every acceptance item has a non-empty id, a known kind, references a known scope id, and acceptance ids are unique;
- an interaction acceptance item carries a non-empty steps list whose steps each have a non-empty action, and no other kind carries steps;
- every scope item is covered by at least one acceptance item;
- every out_of_scope item names an item and records why it was excluded ("with the reason").
Types ¶
type Acceptance ¶
type Acceptance struct {
ID string `json:"id"`
Scope string `json:"scope"`
// Check is the concrete check (command, route, or steps reference). It is
// optional for KindManual, which is reported rather than run.
Check string `json:"check,omitempty"`
Kind Kind `json:"kind"`
// Steps are the scripted browser steps for KindInteraction items
// in execution order. They are empty for every other
// kind; the validator enforces both rules (required non-empty for
// interaction, forbidden otherwise).
Steps []BrowseStep `json:"steps,omitempty"`
}
Acceptance is one check that proves a scope item is done. Kind selects which verifier applies; an empty or unknown kind is a validation error.
type BrowseStep ¶
type BrowseStep struct {
// Action is the step verb (e.g. "click", "fill", "assert_text"). It is
// required and non-empty (mirroring parseBrowseSteps); the action set
// itself is validated by the executor at runtime, not by the plan
// validator.
Action string `json:"action"`
// Selector is the CSS selector the step targets, where applicable.
Selector string `json:"selector,omitempty"`
// Value is the value the step types or fills, where applicable.
Value string `json:"value,omitempty"`
// Key is the keyboard key for press-style steps, where applicable.
Key string `json:"key,omitempty"`
// Millis is a wait time in milliseconds for sleep-style steps, where
// applicable.
Millis int `json:"millis,omitempty"`
// Script is a JavaScript snippet for eval-style steps, where applicable.
Script string `json:"script,omitempty"`
// Expect is the expected value or text for assert-style steps, where
// applicable.
Expect string `json:"expect,omitempty"`
// ScreenshotPath is the file path for a screenshot_selector step's
// cropped element screenshot, where applicable.
ScreenshotPath string `json:"screenshot_path,omitempty"`
}
BrowseStep is one scripted browser step of an interaction acceptance item. It mirrors the JSON wire format of webcontent.BrowseStep — the browse tool's step language — field for field (same fields, same JSON tags, same omitempty rules), so a plan's steps can be handed to the browse step parser (parseBrowseSteps in pkg/agent/tool_handlers_browse.go) without conversion: there is one step language for browser automation.
plancontract must stay a pure package (it is used by every reader and writer of the plan, including WASM builds) and therefore cannot import pkg/webcontent (which drags in a headless browser). Compatibility is a wire-format contract, not a Go type dependency: plancontract fits webcontent's wire format, never the reverse. A cross-package pin test in pkg/agent (plan_browse_compat_test.go) keeps the two formats together: marshalling a plan's steps and running them through parseBrowseSteps must round-trip field for field. When webcontent.BrowseStep gains a field or a tag changes, this struct and that pin test must be updated in the same change.
type Kind ¶
type Kind string
Kind classifies an acceptance item. It is stored as a string in the JSON document so unknown values round-trip and can be reported as "unknown kind" by the validator instead of failing the JSON decode.
const ( // KindBuild: the project builds. KindBuild Kind = "build" // KindTest: the project's tests pass. KindTest Kind = "test" // KindPage: a route renders (a page check). KindPage Kind = "page" // KindInteraction: scripted browser steps (reuse of the // browse step format). KindInteraction Kind = "interaction" // KindManual: reported by a human, never machine-checked. KindManual Kind = "manual" )
The acceptance kinds defined by the plan schema.
type OutOfScope ¶
OutOfScope is an item that was discussed and deliberately left out of the plan. Item is the excluded feature/change; Reason records why it was excluded, so the exclusion is deliberate and auditable "with the reason"). Both are required for a valid plan — an exclusion without a reason is exactly the silent scope creep the list exists to prevent.
type Plan ¶
type Plan struct {
// Version is the schema version of this document.
Version int `json:"version"`
// Revision increments on every edit to the plan; the store
// bumps it on each write. A persisted plan is always >= 1.
Revision int `json:"revision"`
// Created and Updated are the plan's lifecycle timestamps (RFC3339 in
// JSON).
Created time.Time `json:"created"`
Updated time.Time `json:"updated"`
// Goal is what the work is for, in one or two sentences.
Goal string `json:"goal"`
// Scope is the set of features or changes the plan covers.
Scope []ScopeItem `json:"scope"`
// Steps are the ordered implementation steps; each references a scope item.
Steps []Step `json:"steps"`
// Design is an optional reference to screens or files under design/
// (SP-140 directory contract).
Design string `json:"design,omitempty"`
// Starter is an optional starter ID when the project uses one.
Starter string `json:"starter,omitempty"`
// Acceptance are the machine- or human-checkable criteria. Every scope
// item must be covered by at least one acceptance item.
Acceptance []Acceptance `json:"acceptance"`
// OutOfScope are items discussed and deliberately excluded, with the
// reason, so scope does not creep silently between sessions.
OutOfScope []OutOfScope `json:"out_of_scope"`
}
Plan is a versioned, machine-readable implementation plan. It is the in-memory form of .sprout/plan.json. JSON field names are the on-disk contract; see the package doc.
func New ¶
New returns a plan ready to be filled in: the current schema version, revision 1, both timestamps set to now, and every collection initialized to an empty (non-nil) slice so the marshalled JSON is complete and stable. Callers then populate Goal, Scope, Steps, Acceptance, and OutOfScope and run Validate before writing.
func ValidateJSON ¶
ValidateJSON decodes a plan JSON document and validates the result. It returns the decoded plan (non-nil when err is nil) and the first error encountered: either a JSON decode error or a *ValidationError. This is the convenience entry point for readers/writers that start from the bytes of .sprout/plan.json.
func (*Plan) AcceptanceForScope ¶
func (p *Plan) AcceptanceForScope(scopeID string) []Acceptance
AcceptanceForScope returns the acceptance items that cover the given scope id, in document order.
func (*Plan) BumpRevision ¶
BumpRevision records an edit: it advances the revision by one and sets Updated to now. The store calls this before writing so that every write is observable as a new revision.
type ScopeItem ¶
type ScopeItem struct {
ID string `json:"id"`
Title string `json:"title"`
Description string `json:"description,omitempty"`
}
ScopeItem is one feature or change the plan covers.
type Step ¶
type Step struct {
// Scope is the id of the scope item this step implements.
Scope string `json:"scope"`
// Description says what the step does.
Description string `json:"description"`
}
Step is one ordered implementation step that references a scope item. The array order is the step order; a step carries no ID of its own.
type ValidationError ¶
type ValidationError struct {
Problems []string
}
ValidationError describes the structural problems found in a plan. It carries one entry per problem so callers can surface them all at once (instead of fixing-and-revalidating one at a time). Use errors.As to recover the structured list from the error returned by Validate.
func (*ValidationError) Error ¶
func (e *ValidationError) Error() string
Error implements error. It always lists every problem so the message is actionable on its own.