build

package
v0.17.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jun 5, 2026 License: MIT Imports: 16 Imported by: 0

Documentation

Overview

Package build orchestrates the coding agent integration.

Index

Constants

View Source
const (
	NodePending    = "pending"
	NodeInProgress = "in-progress"
	NodeComplete   = "complete"
	NodeFailed     = "failed"
)

Node status values for the per-node ledger.

View Source
const DAGSchemaVersion = "build-port/v1"

DAGSchemaVersion identifies the wire schema of the spec://current/dag resource. It is the versioned interface third-party build systems integrate against; changes within a major version are additive only.

Variables

This section is empty.

Functions

func AdvanceStep

func AdvanceStep(db *store.DB, session *SessionState) error

AdvanceStep marks the current step as complete and moves to the next. It also mirrors the change into the node ledger so the DAG source of truth stays in sync during the sequential-to-DAG transition.

func AllLeavesHaveDraftPR added in v0.16.0

func AllLeavesHaveDraftPR(prSectionContent string) bool

AllLeavesHaveDraftPR reports whether every leaf node in a §7.3 PR stack has a recorded draft-PR URL (a `<!-- pr: <url> -->` annotation). It is the verifier behind the pr_stack_exists gate: review may not begin until each stack tip has an open draft PR. prSectionContent is the §7.3 section body.

Returns false when the plan is empty, malformed (not a DAG), has no leaves, or any leaf is missing its PR — so an unbuilt or partially-finished spec never passes the gate.

func LogActivity

func LogActivity(specID, entry string) error

LogActivity appends an entry to both the SQLite activity log and the session file.

func SaveSession

func SaveSession(db *store.DB, session *SessionState) error

SaveSession persists a session to the database.

func SessionDir

func SessionDir(specID string) string

SessionDir returns the path to the session directory.

func SetActivityDB

func SetActivityDB(db *store.DB)

SetActivityDB sets the database used for activity logging.

func WriteContextFile

func WriteContextFile(ctx *BuildContext, outputPath string) error

WriteContextFile writes a consolidated context markdown file for non-MCP agents.

Types

type BuildContext

type BuildContext struct {
	SpecPath     string
	SpecContent  string
	PriorDiffs   []string
	FailingTests string
	Conventions  string
	CurrentStep  PRStep
	SystemPrompt string
	// Skills holds the resolved skill bodies (Agent Skills markdown). Empty
	// when no skills are present under .spec/agent/skills/ or in config.
	Skills []string
	// SkillPaths is the deduplicated union of skill paths across all DAG nodes,
	// passed to skill-capable agents as the set the orchestrator may dispatch.
	SkillPaths []string
}

BuildContext is the assembled context payload passed to an agent.

func AssembleContext

func AssembleContext(specPath string, session *SessionState, conventions string) (*BuildContext, error)

AssembleContext builds the full context payload for an agent.

type BuildStrategy added in v0.17.0

type BuildStrategy interface {
	// Name identifies the strategy (advertised via spec://current/capabilities).
	Name() string
	// FinishingTools lists the finishing tool names this strategy exposes, a
	// subset of {spec_push, spec_open_pr, spec_link_prs}. Empty means local-only:
	// the kernel commits to per-node branches and no remote/PR tools are offered.
	FinishingTools() []string
	// Complete reports terminal status given the node ledger and the DAG. It is
	// consulted once every node is complete to decide whether the build is fully
	// done (e.g. draft PRs present on stack leaves) or still needs a finishing
	// pass.
	Complete(session *SessionState, graph *Graph) Completion
}

BuildStrategy is the Tier-2 port: it owns the VCS/review workflow layered on top of the kernel's DAG traversal — which finishing tools the harness may call, and what "done" means for a build. The kernel always provisions a branch + worktree per node (a mechanism it owns); a strategy decides whether those branches become a stack of draft PRs, stay local, or something else. Strategy is pluggable policy: bringing a different one needs zero kernel change, and bringing none (local-only) is a first-class choice.

type Completion added in v0.17.0

type Completion struct {
	// Done reports whether the build is fully finished under the strategy.
	Done bool
	// Summary is a human-readable one-line status.
	Summary string
	// Hint is an optional next action shown when the build is not yet done.
	Hint string
}

Completion is a strategy's verdict on a build whose nodes are all complete.

type Engine

type Engine struct {
	// contains filtered or unexported fields
}

Engine orchestrates the build handoff. spec-cli owns the DAG, the durable node ledger, and all git/worktree/GitHub mechanics; the agent (pi) conducts the traversal via MCP. One StartOrResume call = one whole-DAG invocation.

func NewEngine

func NewEngine(db *store.DB, agent adapter.AgentAdapter, opts Options) *Engine

NewEngine creates a new build engine.

func (*Engine) Check added in v0.17.0

func (e *Engine) Check(ctx context.Context, specID, specPath, startDir string) error

Check runs a read-only preflight for a build without launching the agent or touching any session: it validates the DAG, resolves each node's workspace and routed skills, surfaces skill name collisions, and reports the agent's capabilities and the completion definition. It returns an error when the build is not launchable so callers (and CI) can gate on it.

func (*Engine) StartOrResume

func (e *Engine) StartOrResume(ctx context.Context, specID, specPath, startDir string) error

StartOrResume begins or continues a build session for a spec. It ensures the session and DAG exist, validates the workspaces the DAG needs, assembles the context, and invokes the agent once with the DAG exposed via MCP. The agent provisions and checkpoints each node back through the spec MCP tools; on return the engine reconciles the node ledger and, if work remains, prints resume guidance. A resume re-dispatches only the surviving ready nodes — completed nodes are never re-run.

type Graph added in v0.16.0

type Graph struct {
	// Nodes are stored in their original plan order.
	Nodes []PRStep
	// contains filtered or unexported fields
}

Graph is a validated DAG of PR-stack nodes. It is the deterministic substrate the orchestrator walks: spec-cli owns the graph, pi conducts traversal.

func BuildGraph added in v0.16.0

func BuildGraph(steps []PRStep) (*Graph, error)

BuildGraph validates a PR stack and returns its DAG. It rejects edges that reference unknown steps and dependency cycles, naming the offending nodes so the author can fix the §7.3 plan.

func (*Graph) Dependencies added in v0.16.0

func (g *Graph) Dependencies(id string) []string

Dependencies returns the node IDs the given node depends on.

func (*Graph) Leaves added in v0.16.0

func (g *Graph) Leaves() []PRStep

Leaves returns the nodes no other node depends on — the tips of the stack, which are the PRs that must exist before review.

func (*Graph) Node added in v0.16.0

func (g *Graph) Node(id string) (PRStep, bool)

Node returns the step for a node ID and whether it exists.

func (*Graph) ReadySet added in v0.16.0

func (g *Graph) ReadySet(done map[string]bool) []PRStep

ReadySet returns the nodes whose dependencies are all complete and which are not themselves complete. This is the resume primitive: given the set of completed node IDs, it yields exactly the nodes safe to dispatch next.

func (*Graph) Waves added in v0.16.0

func (g *Graph) Waves() [][]PRStep

Waves returns a Kahn topological sort grouped into waves. Every node in a wave has all of its dependencies satisfied by earlier waves, so a wave can be fanned out in parallel. Plan order is preserved within each wave.

type MCPResource

type MCPResource struct {
	URI     string `json:"uri"`
	Name    string `json:"name"`
	Content string `json:"content"`
}

MCPResource represents a resource served by the MCP server.

type MCPServer

type MCPServer struct {
	// contains filtered or unexported fields
}

MCPServer serves spec context to MCP-compatible agents. It owns the DAG, the node ledger, and every git/worktree operation: the orchestrator reads the DAG once and checkpoints each node back through the node tools, while spec-cli keeps all branch/worktree mechanics on this side of the contract.

func NewMCPServer

func NewMCPServer(session *SessionState, buildCtx *BuildContext, db *store.DB, specPath string, opts Options) *MCPServer

NewMCPServer creates a new MCP server for a build session. opts carries the workspace map (source repos for worktrees) and skill routing inputs. The DAG is built from the session's steps; a malformed plan yields a nil graph and a descriptive DAG resource rather than a panic.

func (*MCPServer) CallTool

func (s *MCPServer) CallTool(name string, args json.RawMessage) (*MCPToolResult, error)

CallTool executes an MCP tool.

func (*MCPServer) GetResource

func (s *MCPServer) GetResource(uri string) (*MCPResource, error)

GetResource returns a specific resource by URI.

func (*MCPServer) ListResources

func (s *MCPServer) ListResources() []MCPResource

ListResources returns all available resources.

func (*MCPServer) ToolSpecs added in v0.17.0

func (s *MCPServer) ToolSpecs() []ToolSpec

ToolSpecs returns the build (DAG) tools to advertise, given the active strategy. Core traversal tools are always present; finishing tools appear only when the strategy exposes them.

func (*MCPServer) WithRepo added in v0.16.0

func (s *MCPServer) WithRepo(r adapter.RepoAdapter) *MCPServer

WithRepo injects the repo adapter used by the draft-PR tools and returns the server for chaining. Kept separate from the constructor so call sites that never open PRs need not thread an adapter through.

type MCPToolResult

type MCPToolResult struct {
	Success bool   `json:"success"`
	Message string `json:"message"`
}

MCPToolResult represents the result of an MCP tool call.

type NodeState added in v0.16.0

type NodeState struct {
	ID       string `json:"id"`
	Status   string `json:"status"`
	Branch   string `json:"branch,omitempty"`
	BaseRef  string `json:"base_ref,omitempty"`
	Worktree string `json:"worktree,omitempty"`
	Reason   string `json:"reason,omitempty"`
	PRNumber int    `json:"pr_number,omitempty"`
	PRURL    string `json:"pr_url,omitempty"`
}

NodeState is the durable per-node record in the DAG ledger. It carries everything needed to resume, diff, and stack a node: its status, the branch and worktree git placed it on, the base ref it was cut from, an optional failure reason, and any draft-PR coordinates recorded during finishing.

type Options added in v0.15.0

type Options struct {
	// Headless runs the agent autonomously (e.g. `spec fix --auto`).
	Headless bool
	// SkillRefs are explicit skill paths from config
	// (integrations.agent.settings.skill). They take precedence over
	// profile.yaml refs and the .spec/agent/skills/ directory. They are the
	// per-node worker fallback used when registry routing does not match.
	SkillRefs []string
	// ConductorSkills are the orchestrator-level skills handed to an MCP-capable
	// agent (integrations.agent.settings.conductor_skill). They are resolved
	// from the start dir only and are kept distinct from per-node worker skills,
	// which reach workers solely via spec_provision_node.
	ConductorSkills []string
	// TestCommand, when set, is run to populate FailingTests (best-effort).
	TestCommand string
	// Workspaces maps a PR-step repo name to a local directory. It is the
	// source-of-truth repo that worktrees are added to for each node.
	Workspaces map[string]string
	// MaxParallel bounds orchestrator fan-out across ready nodes. Surfaced to
	// the agent via the DAG resource; 0 means "use the default".
	MaxParallel int
	// Router selects the Tier-1 SkillRouter: "registry" (default), or
	// "none"/"discovery" to route nothing and let the harness discover skills.
	// Empty means the default.
	Router string
	// Strategy selects the Tier-2 BuildStrategy: "stacked-draft-pr" (default), or
	// "none"/"local" to keep work on local branches with no finishing tools.
	// Empty means the default.
	Strategy string
}

Options configures a build engine beyond its adapters.

type PRStep

type PRStep struct {
	Number      int    `yaml:"number" json:"number"`
	ID          string `yaml:"id" json:"id"`
	Repo        string `yaml:"repo" json:"repo"`
	Layer       string `yaml:"layer" json:"layer,omitempty"`
	Description string `yaml:"description" json:"description"`
	Branch      string `yaml:"branch" json:"branch"`
	Status      string `yaml:"status" json:"status"` // "pending", "in-progress", "complete", "failed"
	// DependsOn holds the step numbers this node depends on (parsed from the
	// `(after: 1,2)` edge annotation). Empty for a root node.
	DependsOn []int `yaml:"depends_on" json:"depends_on,omitempty"`
	// ACs holds the 1-based acceptance-criteria indices this node satisfies,
	// parsed from a trailing `(ac: 3,4)` annotation in §7.3. Empty when the
	// author did not map the node to specific criteria.
	ACs []int `yaml:"acs" json:"acs,omitempty"`
	// PRURL is the draft PR recorded for this node in §7.3 via a trailing
	// `<!-- pr: <url> -->` annotation. Empty until the finisher opens a PR.
	PRURL string `yaml:"pr_url" json:"pr_url,omitempty"`
	// BaseRef is the commit the step branch was created from. Used to capture
	// the step's diff for cumulative cross-step context.
	BaseRef string `yaml:"base_ref" json:"base_ref,omitempty"`
}

PRStep represents one node in the §7.3 PR stack plan. A plan is a DAG: each step has a stable ID, an optional repo and layer (which drive worktree placement and skill routing), and zero or more dependencies on earlier steps.

func ParsePRStack

func ParsePRStack(content string) ([]PRStep, error)

ParsePRStack extracts PR steps from the §7.3 PR Stack Plan section.

func ParsePRStackFromFile

func ParsePRStackFromFile(path string) ([]PRStep, error)

ParsePRStackFromFile reads a spec file and extracts PR steps.

func (PRStep) NodeID added in v0.16.0

func (s PRStep) NodeID() string

NodeID returns the stable node identifier, deriving it from the step number when an explicit ID was not parsed (e.g. "n3").

type SessionState

type SessionState struct {
	SpecID       string    `json:"spec_id"`
	CurrentStep  int       `json:"current_step"`
	Branch       string    `json:"branch"`
	Repo         string    `json:"repo"`
	WorkDir      string    `json:"work_dir"`
	LastActivity time.Time `json:"last_activity"`
	Steps        []PRStep  `json:"steps"`
	// Nodes is the per-node status ledger keyed by PRStep.ID. It is the DAG
	// source of truth for the orchestrated build; CurrentStep is retained only
	// for the legacy sequential walk and is removed once the DAG engine lands.
	Nodes map[string]*NodeState `json:"nodes,omitempty"`
}

SessionState persists the build session for `spec do` resume.

func CreateSession

func CreateSession(db *store.DB, specID string, steps []PRStep, workDir string) (*SessionState, error)

CreateSession creates a new build session.

func LoadSession

func LoadSession(db *store.DB, specID string) (*SessionState, error)

LoadSession loads a session from the database.

func (*SessionState) CurrentPRStep

func (s *SessionState) CurrentPRStep() *PRStep

CurrentPRStep returns the current step, or nil if complete.

func (*SessionState) DoneSet added in v0.16.0

func (s *SessionState) DoneSet() map[string]bool

DoneSet returns the set of completed node IDs, the input to Graph.ReadySet.

func (*SessionState) FailedNodes added in v0.16.0

func (s *SessionState) FailedNodes() []string

FailedNodes returns the IDs of nodes recorded as failed, sorted for stable reporting.

func (*SessionState) InitNodes added in v0.16.0

func (s *SessionState) InitNodes()

InitNodes populates the ledger from the session's steps when it is empty, giving every node a pending record. Existing records are preserved so a reload never clobbers progress.

func (*SessionState) IsComplete

func (s *SessionState) IsComplete() bool

IsComplete returns true if all steps are done.

func (*SessionState) MarkNodeComplete added in v0.16.0

func (s *SessionState) MarkNodeComplete(id string)

MarkNodeComplete records a node as complete and clears any failure reason. It is idempotent: completing an already-complete node is a no-op.

func (*SessionState) MarkNodeFailed added in v0.16.0

func (s *SessionState) MarkNodeFailed(id, reason string)

MarkNodeFailed records a node as failed with a reason for resume/reporting.

func (*SessionState) NodeStatus added in v0.16.0

func (s *SessionState) NodeStatus(id string) string

NodeStatus returns the status of a node, or "pending" if unknown.

func (*SessionState) NodesComplete added in v0.16.0

func (s *SessionState) NodesComplete() bool

NodesComplete reports whether every node in the ledger is complete. It returns false for an empty ledger so an uninitialised session is never mistaken for a finished one.

func (*SessionState) SetNodeStatus added in v0.16.0

func (s *SessionState) SetNodeStatus(id, status string)

SetNodeStatus updates a node's status in the ledger.

type SkillRouter added in v0.17.0

type SkillRouter interface {
	// Route returns absolute skill paths for a node, or nil when it does not
	// route (the harness/model then discovers skills itself).
	Route(node PRStep) []string
}

SkillRouter is the Tier-1 port: it maps a DAG node to the capability skill paths its worker should load. Routing is opaque to the kernel — a router is pluggable policy, and bringing none is a first-class choice. spec-cli ships a registry router (the default) and a passthrough "none" router; teams may provide their own by populating skill paths another way.

type ToolSpec added in v0.17.0

type ToolSpec struct {
	Name        string
	Description string
	InputSchema map[string]interface{}
}

ToolSpec is a harness-neutral description of a build MCP tool. cmd maps these onto the transport's tool type so the tool list and the dispatcher cannot drift, and so strategy-gated tools are advertised consistently.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL