core

package
v0.1.0 Latest Latest
Warning

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

Go to latest
Published: Jul 3, 2026 License: MIT Imports: 27 Imported by: 0

Documentation

Overview

Package core holds specd's domain logic: the layer between the CLI commands in internal/cmd and the on-disk spec/state representation. It owns state.json load/save with compare-and-swap, the per-spec advisory lock, the wave DAG and frontier/critical-path computation, the EARS requirements linter, spec-artifact accessors and traceability gates, and the embedded scaffold templates used by `specd init`/`new`.

core has no dependency on internal/cmd or internal/cli — commands call into core, never the reverse — and it performs no flag parsing or process-level argument handling of its own.

Index

Constants

View Source
const (
	ACPVersion             = "1"
	ACPMaxEnvelopeBytes    = 256 * 1024
	ACPMaxTextBytes        = 16 * 1024
	ACPMaxListItems        = 128
	ACPMaxPathBytes        = 1024
	ACPVerificationRefSize = 256
)

ACP envelope protocol version and the size/length limits enforced when validating an envelope (see ValidateACPEnvelope).

View Source
const (
	ExitOK       = 0
	ExitGate     = 1
	ExitUsage    = 2
	ExitNotFound = 3
)

ExitOK, ExitGate, ExitUsage, and ExitNotFound are the process exit codes returned by specd commands: success, a gate/validation failure, invalid usage, and a missing resource, respectively.

View Source
const (
	ConfidenceNeutral = "neutral"
	ConfidenceSuggest = "suggest"
	ConfidenceStrong  = "strong"
)

Confidence levels for a mode recommendation.

View Source
const (
	CompactionNone   = "none"
	CompactionPhase  = "phase"
	CompactionBudget = "budget"
	CompactionBoth   = "both"
)

Compaction policy modes. Empty is treated as CompactionNone.

View Source
const (
	ModeSimple       = "simple"
	ModeOrchestrated = "orchestrated"
)

Execution mode for a spec. Simple is the plain spec-driven lifecycle the host agent drives itself; Orchestrated lets the Brain/Pinky multi-agent layer drive. Mode is per-spec and recorded in state.json (the single source of truth), never inferred from project-wide config — capability permits, mode selects.

View Source
const (
	OriginDefault     = "default"              // never opted in; default Simple
	OriginUser        = "user"                 // explicit user opt-in (flag or --set)
	OriginRecommended = "recommended-accepted" // user accepted a harness recommendation
)

Origin records how a spec's execution mode was chosen, for audit via replay.

View Source
const (
	StatusRequirements = spec.StatusRequirements
	StatusDesign       = spec.StatusDesign
	StatusTasks        = spec.StatusTasks
	StatusExecuting    = spec.StatusExecuting
	StatusVerifying    = spec.StatusVerifying
	StatusComplete     = spec.StatusComplete
	StatusBlocked      = spec.StatusBlocked
)

StatusRequirements through StatusBlocked re-export the spec lifecycle statuses from internal/spec under their core.Status* names, so existing call sites keep compiling unchanged (see the SpecStatus alias comment).

View Source
const (
	PhasePerceive = spec.PhasePerceive
	PhaseAnalyze  = spec.PhaseAnalyze
	PhasePlan     = spec.PhasePlan
	PhaseExecute  = spec.PhaseExecute
	PhaseVerify   = spec.PhaseVerify
	PhaseReflect  = spec.PhaseReflect
)

PhasePerceive through PhaseReflect re-export the perceive-analyze-plan- execute-verify-reflect loop phases from internal/spec under their core.Phase* names.

View Source
const (
	ACPRuntimeIDMaxBytes = 128
)

ACPRuntimeIDMaxBytes is the maximum byte length allowed for an untrusted runtime path segment (e.g. a worker or artifact ID) before validation rejects it.

View Source
const HandshakeBootstrapVersion = 1

HandshakeBootstrapVersion is the schema version reported in the Version field of HandshakeBootstrap and HandshakePolicy payloads.

View Source
const InitResultSchemaVersion = 1

InitResultSchemaVersion is the schema version stamped onto every InitResult returned by ExecuteInitPlan, for consumers parsing the JSON output.

View Source
const OrchestrationModelVersion = 1

OrchestrationModelVersion is the schema version stamped on orchestration snapshots, decisions, sessions, and checkpoint records; validation rejects any value other than this constant.

View Source
const ProgramVersion = 1

ProgramVersion is the current schema version for program.json.

View Source
const SchemaVersion = 5

SchemaVersion is the current state.json schema version this build of specd writes and understands. LoadState refuses to read a state.json whose schemaVersion is newer than this value.

Variables

View Source
var (
	MandatoryKeys = []string{"why", "role", "files", "contract", "acceptance", "verify", "depends"}
	KeyOrder      = []string{"why", "role", "files", "contract", "acceptance", "verify", "depends", "requirements"}
	ValidRoles    = spec.RoleNames()
)

MandatoryKeys, KeyOrder, and ValidRoles define the canonical task metadata schema: which metadata keys every task must define, the order those keys render in on disk, and which role names are recognized.

View Source
var Artifacts = []string{
	"requirements.md", "design.md", "tasks.md",
	"decisions.md", "memory.md", "mid-requirements.md",
}

Artifacts lists the standard per-spec markdown filenames managed under a spec's directory.

CheckGates is the ordered gate pipeline run by `specd check <slug>`. Order is a user/agent-visible contract (it determines violation listing order) and must not change without intent.

View Source
var Clock = time.Now

Clock is the time source for all spec-state timestamps and human-readable date stamps. Production uses the real wall clock; tests override it (see internal/testharness.FakeClock) for deterministic, golden-comparable output.

Note: the advisory-lock staleness logic in lock.go deliberately does NOT use Clock — lock reclamation needs real elapsed wall-clock time and must stay immune to a frozen test clock.

View Source
var Commands = []CommandMeta{
	{
		Command: "init", Category: "lifecycle",
		Description: "Scaffold project assets and configure coding agents",
		Usage:       "specd init [--agent <auto|all|none|codex|claude-code|cursor|antigravity|vscode>] [--scope project|global] [--yes] [--non-interactive] [--verbose] [--dry-run] [--repair|--refresh|--force] [--orchestration [<policy>]] [--orchestration-workers <n>] [--orchestration-retries <n>] [--orchestration-timeout <minutes>] [--orchestration-cost-limit <usd>] [--orchestration-mode <inline|delegate>] [--orchestration-sandbox <none|bwrap|container>]", Synopsis: "specd init [--agent <name>] [--yes] [--dry-run]",
		LongDescription: "Scaffolds .specd/ and AGENTS.md, passively detects supported coding-agent hosts, optionally installs project-scoped MCP registration, verifies the in-process MCP server, and returns one next action. Non-interactive auto-detection never mutates host configuration unless --yes is supplied. Global scope requires explicit consent.",
		Flags:           []FlagMeta{{Name: "agent", Type: "string", Description: "Coding-agent selection: auto, all, none, codex, claude-code, cursor, antigravity, or vscode"}, {Name: "scope", Type: "string", Description: "Integration scope (default project)"}, {Name: "yes", Type: "boolean", Description: "Accept non-destructive project-scoped integration changes"}, {Name: "non-interactive", Type: "boolean", Description: "Disable prompts"}, {Name: "verbose", Type: "boolean", Description: "Include detailed path results"}, {Name: "json", Type: "boolean", Description: "Output one versioned InitResult document"}, {Name: "dry-run", Type: "boolean", Description: "Preview exact actions without writing"}, {Name: "repair", Type: "boolean", Description: "Restore missing managed assets only"}, {Name: "refresh", Type: "boolean", Description: "Refresh frozen managed assets and AGENTS.md markers"}, {Name: "force", Type: "boolean", Description: "Destructively overwrite all scaffold files and AGENTS.md"}, {Name: "list-packs", Type: "boolean", Description: "List the embedded spec packs and exit"}, {Name: "pack", Type: "string", Description: "Apply a spec pack by built-in name or http(s) URL"}, {Name: "sha256", Type: "string", Description: "Pinned SHA256 digest required for a remote --pack URL"}, {Name: "orchestration", Type: "string", Description: "Enable Brain/Pinky and set approval policy (manual, planning, session)"}, {Name: "orchestration-workers", Type: "string", Description: "Max concurrent Pinky workers (1..64, default 4)"}, {Name: "orchestration-retries", Type: "string", Description: "Retry budget for failed/reclaimed work (0..10, default 2)"}, {Name: "orchestration-timeout", Type: "string", Description: "Session wall-clock timeout in minutes (1..1440, default 120)"}, {Name: "orchestration-cost-limit", Type: "string", Description: "Host-reported cost brake in USD (default 0)"}, {Name: "orchestration-mode", Type: "string", Description: "Subagent coordination mode: inline or delegate (default delegate)"}, {Name: "orchestration-sandbox", Type: "string", Description: "Default verify sandbox: none, bwrap, or container (default none)"}},
		ExitCodes:       []ExitCodeMeta{{0, "Success"}, {1, "Initialization or pack operation failed"}, {2, "Usage error"}},
		Examples:        []string{"specd init --agent auto --yes", "specd init --agent none --non-interactive", "specd init --agent all --dry-run --json", "specd init --repair"},
	},

	{
		Command: "handshake", Category: "inspection", Hidden: true,
		Description: "Emit startup bootstrap and binding policy oracles",
		Usage:       "specd handshake bootstrap [--include-schema] [--json] | specd handshake policy [<slug>] [--expect-config-digest <sha256>] [--json]", Synopsis: "specd handshake <bootstrap|policy> [slug] [--json]",
		LongDescription: "Read-only agent integration surface. bootstrap returns the first-turn load list, command schema digest, config digest, health, and active modes. policy summarizes binding config, optional spec execution mode, allowed loop family, diagnostics, and config digest drift.",
		Flags:           []FlagMeta{{Name: "include-schema", Type: "boolean", Description: "Inline full command schema in bootstrap output"}, {Name: "expect-config-digest", Type: "string", Description: "Fail if current config digest differs from this sha256"}, {Name: "json", Type: "boolean", Description: "Output JSON"}},
		Positionals:     []PositionalMeta{{Name: "subcommand", Required: true, Description: "bootstrap or policy"}, {Name: "slug", Required: false, Description: "Spec slug for policy mode"}},
		ExitCodes:       []ExitCodeMeta{{0, "Success"}, {1, "Policy violation or digest mismatch"}, {2, "Usage error"}, {3, ".specd/ or spec not found"}},
		Examples:        []string{"specd handshake bootstrap --json", "specd handshake policy my-feature --json", "specd handshake policy --expect-config-digest <sha256> --json"},
	},

	{
		Command: "new", Category: "lifecycle",
		Description: "Create a spec with six artifacts",
		Usage:       "specd new <slug> [--title \"...\"] [--orchestrated]", Synopsis: "specd new <slug> [--title \"...\"] [--orchestrated]",
		LongDescription: "Creates a new spec directory under .specd/specs/<slug>/ with six artifact stubs. Specs default to Base execution mode; --orchestrated records executionMode=orchestrated (origin user) and requires project orchestration capability.",
		Flags:           []FlagMeta{{Name: "title", Type: "string", Description: "The title of the spec"}, {Name: "orchestrated", Type: "boolean", Description: "Create the spec in orchestrated (Brain/Pinky) mode; requires project orchestration capability"}},
		ExitCodes:       []ExitCodeMeta{{0, "Success"}, {1, "Orchestration requested without project capability"}, {2, "Usage error"}, {3, ".specd/ not found or spec already exists"}},
		Examples:        []string{"specd new my-feature", "specd new my-feature --title \"My Feature\"", "specd new payments --title \"Billing\" --orchestrated"},
	},

	{
		Command: "approve", Category: "lifecycle",
		Description: "Clear approval gate / advance phase",
		Usage:       "specd approve <slug> [--json]", Synopsis: "specd approve <slug> [--json]",
		LongDescription: "Clears an awaiting-approval gate or advances the planning phase of the specified spec.",
		Flags:           []FlagMeta{{Name: "json", Type: "boolean", Description: "Output in JSON format"}},
		ExitCodes:       []ExitCodeMeta{{0, "Success"}, {1, "Gate validation failed"}, {2, "Usage error"}, {3, "Spec not found"}},
		Examples:        []string{"specd approve my-feature", "specd approve my-feature --json"},
	},

	{
		Command: "decision", Category: "lifecycle",
		Description: "Record an architectural decision (ADR)",
		Usage:       "specd decision <slug> \"<text>\" [--supersedes <id>]", Synopsis: "specd decision <slug> \"<text>\" [--supersedes <id>]",
		LongDescription: "Appends an architectural decision record (ADR) to decisions.md.",
		Flags:           []FlagMeta{{Name: "supersedes", Type: "string", Description: "ID of the decision being superseded"}},
		ExitCodes:       []ExitCodeMeta{{0, "Success"}, {2, "Usage error"}, {3, "Spec not found"}},
		Examples:        []string{"specd decision my-feature \"Use SQLite instead of PostgreSQL\""},
	},

	{
		Command: "midreq", Category: "lifecycle",
		Description: "Log feedback mid-requirement",
		Usage:       "specd midreq <slug> \"<input>\" --impact <low|medium|high|critical>", Synopsis: "specd midreq <slug> \"<input>\" --impact <low|medium|high|critical>",
		LongDescription: "Logs user feedback or mid-requirement changes and triggers appropriate gates.",
		Flags:           []FlagMeta{{Name: "impact", Type: "string", Description: "Impact level (low/medium/high/critical)"}, {Name: "interpretation", Type: "string"}, {Name: "changes", Type: "string"}},
		ExitCodes:       []ExitCodeMeta{{0, "Success"}, {2, "Usage error"}, {3, "Spec not found"}},
		Examples:        []string{"specd midreq my-feature \"Add search bar\" --impact medium"},
	},

	{
		Command: "memory", Category: "lifecycle",
		Description: "Add or promote learning items",
		Usage:       "specd memory <slug> add|promote [flags]", Synopsis: "specd memory <slug> <add|promote> [flags]",
		LongDescription: "Manages persistent project learnings.",
		Flags:           []FlagMeta{{Name: "key", Type: "string"}, {Name: "pattern", Type: "string"}, {Name: "body", Type: "string"}, {Name: "source", Type: "string"}, {Name: "criticality", Type: "string"}, {Name: "related", Type: "string"}, {Name: "force", Type: "boolean"}},
		ExitCodes:       []ExitCodeMeta{{0, "Success"}, {2, "Usage error"}, {3, "Spec not found"}},
		Examples:        []string{"specd memory my-feature add --key db-lock --pattern \"parallel writes\" --body \"SQLite locks on concurrent write\" --source log --criticality important"},
	},

	{
		Command: "next", Category: "execution",
		Description: "Next runnable task",
		Usage:       "specd next <slug> [--all] [--dispatch] [--json]", Synopsis: "specd next <slug> [--all] [--dispatch] [--json]",
		LongDescription: "Finds and prints the next runnable task in the spec's task DAG.",
		Flags:           []FlagMeta{{Name: "all", Type: "boolean", Description: "Print all runnable tasks"}, {Name: "dispatch", Type: "boolean", Description: "Emit ready-to-run dispatch packets for the runnable frontier"}, {Name: "json", Type: "boolean"}},
		ExitCodes:       []ExitCodeMeta{{0, "Success"}, {2, "Usage error"}, {3, "Spec not found"}},
		Examples:        []string{"specd next my-feature", "specd next my-feature --all --json"},
	},

	{
		Command: "verify", Category: "execution",
		Description:     "Run task verify command / record proof",
		Usage:           "specd verify <slug> <id>  |  specd verify <slug> --criterion <r>.<n> --status pass|fail --evidence \"...\"",
		Synopsis:        "specd verify <slug> [id] [flags]",
		LongDescription: "Runs a task's verification command and records the result, or records a per-criterion acceptance proof.",
		Flags:           []FlagMeta{{Name: "criterion", Type: "string"}, {Name: "status", Type: "string"}, {Name: "evidence", Type: "string"}, {Name: "revert-on-fail", Type: "boolean", Description: "On a failed verify, stash the working tree (recoverable) instead of leaving it dirty"}, {Name: "sandbox", Type: "string", Description: "Isolation backend for this run (none|bwrap|container); overrides verify.sandbox config"}},
		ExitCodes:       []ExitCodeMeta{{0, "Success"}, {1, "Verification failed"}, {2, "Usage error"}, {3, "Spec or task not found"}},
		Examples:        []string{"specd verify my-feature T1", "specd verify my-feature --criterion 1.1 --status pass --evidence \"Tested manually\""},
	},

	{
		Command: "task", Category: "execution",
		Description:     "Evidence-gated status flip",
		Usage:           "specd task <slug> <id> --status <s> [--evidence \"...\"] [--reason \"...\"] [--force]",
		Synopsis:        "specd task <slug> <id> --status <status> [flags]",
		LongDescription: "Updates the status of a specific task. Completing requires a passing verify record.",
		Flags:           []FlagMeta{{Name: "status", Type: "string"}, {Name: "evidence", Type: "string"}, {Name: "reason", Type: "string"}, {Name: "force", Type: "boolean"}, {Name: "unverified", Type: "boolean"}, {Name: "tokens", Type: "string", Description: "Annotate task telemetry with a token count (stored, not computed)"}, {Name: "cost", Type: "string", Description: "Annotate task telemetry with a cost (stored, not computed)"}},
		ExitCodes:       []ExitCodeMeta{{0, "Success"}, {1, "Gate verification failed"}, {2, "Usage error"}, {3, "Spec or task not found"}},
		Examples:        []string{"specd task my-feature T1 --status running", "specd task my-feature T1 --status complete --evidence \"Ran tests\"", "specd task my-feature T1 --status complete --evidence \"…\" --tokens 12000 --cost 0.42"},
	},

	{
		Command: "status", Category: "inspection",
		Description: "Render durable ledger / board",
		Usage:       "specd status [<slug>] [--all] [--program] [--set-mode simple|orchestrated] [--recommend] [--json]", Synopsis: "specd status [<slug>] [--all] [--program] [--json]",
		LongDescription: "Renders the durable status board of a specific spec, lists all specs, or displays the cross-spec program frontier. With a slug, --set-mode records a new per-spec execution mode (orchestrated requires project capability; switching to simple is refused while a Brain session is active) and --recommend emits a deterministic, advisory mode recommendation — the survivor home for the merged `mode` command's set/recommend paths.",
		Flags:           []FlagMeta{{Name: "all", Type: "boolean", Description: "List all specs (default when no slug is supplied)"}, {Name: "program", Type: "boolean", Description: "Show the cross-spec program frontier"}, {Name: "set-mode", Type: "string", Description: "Set the spec's execution mode: simple or orchestrated"}, {Name: "recommend", Type: "boolean", Description: "Emit an advisory mode recommendation from countable spec facts"}, {Name: "json", Type: "boolean"}},
		ExitCodes:       []ExitCodeMeta{{0, "Success"}, {1, "Capability missing or session-active refusal"}, {2, "Usage error"}, {3, "Spec not found"}},
		Examples:        []string{"specd status", "specd status my-feature --json", "specd status my-feature --set-mode orchestrated", "specd status my-feature --recommend --json"},
	},

	{
		Command: "check", Category: "inspection",
		Description: "Run all validation gates",
		Usage:       "specd check <slug> [--schema-only] [--json] | specd check --schema", Synopsis: "specd check <slug> [--schema-only] [--json] | specd check --schema",
		LongDescription: "Runs all seven validation gates on the specified spec. --schema-only validates state.json against the embedded open spec schema; --schema emits that schema.",
		Flags:           []FlagMeta{{Name: "schema-only", Type: "boolean", Description: "Validate state.json against the embedded open spec schema only"}, {Name: "schema", Type: "boolean", Description: "Emit the embedded open spec format JSON Schema"}, {Name: "json", Type: "boolean"}},
		ExitCodes:       []ExitCodeMeta{{0, "Success"}, {1, "Validation failed"}, {2, "Usage error"}, {3, "Spec not found"}},
		Examples:        []string{"specd check my-feature", "specd check my-feature --json"},
	},

	{
		Command: "context", Category: "inspection",
		Description: "Phase-scoped briefing",
		Usage:       "specd context <slug> [--json]", Synopsis: "specd context <slug> [--json]",
		LongDescription: "Provides a minimal phase-scoped briefing for the current spec phase.",
		Flags:           []FlagMeta{{Name: "json", Type: "boolean"}},
		ExitCodes:       []ExitCodeMeta{{0, "Success"}, {2, "Usage error"}, {3, "Spec not found"}},
		Examples:        []string{"specd context my-feature", "specd context my-feature --json"},
	},

	{
		Command: "report", Category: "inspection",
		Description: "Generate markdown, HTML, or metrics report",
		Usage:       "specd report <slug> [--format md|html|prometheus] [--out <path>] [--pr-summary] [--serve|--watch|--history|--diff]", Synopsis: "specd report <slug> [--format md|html|prometheus] [--out <path>] [--pr-summary]",
		LongDescription: "Compiles a comprehensive HTML or Markdown progress report, or an opt-in Prometheus textfile metrics view. With --pr-summary, emits a deterministic, network-free pull-request summary (Markdown, or JSON under SPECD_JSON): wave/task progress, gate status, and the commit↔task link map.",
		Flags:           []FlagMeta{{Name: "format", Type: "string", Description: "Output format: md, html, or prometheus"}, {Name: "out", Type: "string"}, {Name: "pr-summary", Type: "boolean", Description: "Emit a deterministic PR summary instead of the full report"}, {Name: "serve", Type: "boolean", Description: "Serve the live dashboard"}, {Name: "watch", Type: "boolean", Description: "Stream runnable-frontier changes"}, {Name: "history", Type: "boolean", Description: "Replay the spec audit timeline"}, {Name: "diff", Type: "boolean", Description: "Diff spec artifacts between git refs"}},
		ExitCodes:       []ExitCodeMeta{{0, "Success"}, {2, "Usage error"}, {3, "Spec not found"}},
		Examples:        []string{"specd report my-feature --format html --out ./report.html", "specd report my-feature --format prometheus"},
	},

	{
		Command: "waves", Category: "inspection",
		Description: "Show task wave DAG",
		Usage:       "specd waves <slug> [--json]", Synopsis: "specd waves <slug> [--json]",
		LongDescription: "Renders the task wave dependency graph in ASCII or JSON.",
		Flags:           []FlagMeta{{Name: "json", Type: "boolean"}},
		ExitCodes:       []ExitCodeMeta{{0, "Success"}, {2, "Usage error"}, {3, "Spec not found"}},
		Examples:        []string{"specd waves my-feature"},
	},

	{
		Command:     "brain",
		Category:    "orchestration",
		Description: "Drive deterministic Brain orchestration sessions.",
		Usage:       "specd brain <start|status|step|pause|resume|cancel|checkpoint> ... [--program] [--auto-step|--verbose|--ledger|--directive|--compact]",
		Synopsis:    "specd brain <start|status|step|pause|resume|cancel|checkpoint> ... [--program]",
		Flags: []FlagMeta{
			{Name: "program", Type: "boolean", Description: "operate on the cross-spec program session instead of one spec"},
			{Name: "auto-step", Type: "boolean", Description: "Start then drive the Brain loop (merged from brain run)"},
			{Name: "verbose", Type: "boolean", Description: "Explain scheduling state (merged from brain why)"},
			{Name: "ledger", Type: "boolean", Description: "Show the session context ledger"},
			{Name: "compact", Type: "boolean", Description: "Compact/checkpoint context before host clear"},
			{Name: "directive", Type: "boolean", Description: "Record a host directive on brain step"},
			{Name: "session", Type: "string", Description: "explicit orchestration session id"},
			{Name: "approval-policy", Type: "string", Description: "required approval policy for start/step"},
			{Name: "max-workers", Type: "string", Description: "required worker concurrency limit for start/step"},
			{Name: "max-retries", Type: "string", Description: "required retry limit for start/step"},
			{Name: "timeout-seconds", Type: "string", Description: "required session timeout for start/step"},
			{Name: "cost-limit", Type: "string", Description: "optional host-reported cost limit"},
			{Name: "worker-cmd", Type: "string", Description: "host shell command to spawn Pinky workers"},
			{Name: "bootstrap", Type: "boolean", Description: "automatically bootstrap missing specs during run"},
			{Name: "max-steps", Type: "string", Description: "max driver loop steps (default 100 for single, 200 for program)"},
			{Name: "title", Type: "string", Description: "title to use when bootstrapping a missing spec"},
			{Name: "worker", Type: "string", Description: "worker id for directive"},
			{Name: "spec", Type: "string", Description: "spec slug for directive"},
			{Name: "task", Type: "string", Description: "task id for directive"},
			{Name: "attempt", Type: "string", Description: "positive attempt number for directive"},
			{Name: "action", Type: "string", Description: "directive action: continue, retry, cancel, reassign, or escalate"},
			{Name: "reason", Type: "string", Description: "directive reason"},
			{Name: "in-reply-to", Type: "string", Description: "query message id this directive answers"},
			{Name: "json", Type: "boolean", Description: "emit JSON"},
		},
		ExitCodes: []ExitCodeMeta{{0, "Success"}, {1, "Gate or validation failure"}, {2, "Usage error"}, {3, "Workspace or session not found"}},
		Examples:  []string{"specd brain run my-spec --worker-cmd './worker.sh'", "specd brain resume --session 11111111111111111111111111111111", "specd brain start my-spec --approval-policy manual --max-workers 4 --max-retries 2 --timeout-seconds 7200 --json", "specd brain directive --session 11111111111111111111111111111111 --worker t1-a1 --spec my-spec --task T1 --attempt 1 --action continue --reason 'docs not required'", "specd brain step my-spec --session 11111111111111111111111111111111 --approval-policy manual --max-workers 4 --max-retries 2 --timeout-seconds 7200"},
	},

	{
		Command:     "pinky",
		Category:    "orchestration",
		Description: "Record deterministic Pinky worker claims, reports, and queries.",
		Usage:       "specd pinky <claim|status|update|report|block|release> ...",
		Synopsis:    "specd pinky <claim|status|update|report|block|release> ...",
		Flags: []FlagMeta{
			{Name: "mission", Type: "string", Description: "mission JSON path or - for claim"},
			{Name: "session", Type: "string", Description: "session id"},
			{Name: "worker", Type: "string", Description: "worker id"},
			{Name: "spec", Type: "string", Description: "spec slug"},
			{Name: "task", Type: "string", Description: "task id"},
			{Name: "attempt", Type: "string", Description: "positive attempt number"},
			{Name: "artifact", Type: "string", Description: "the artifact template name for brief (e.g. requirements.md)"},
			{Name: "percent", Type: "string", Description: "progress percent"},
			{Name: "message", Type: "string", Description: "progress message"},
			{Name: "reason", Type: "string", Description: "blocker reason"},
			{Name: "text", Type: "string", Description: "bounded Pinky query text"},
			{Name: "verification-ref", Type: "string", Description: "terminal verification reference"},
			{Name: "summary", Type: "string", Description: "terminal summary"},
			{Name: "changed-files", Type: "string", Description: "comma-separated changed files"},
			{Name: "git-head", Type: "string", Description: "git commit observed by the host worker"},
			{Name: "duration-ms", Type: "string", Description: "host-reported task duration in milliseconds"},
			{Name: "host-tokens", Type: "string", Description: "host-reported token count (stored, not computed)"},
			{Name: "host-cost", Type: "string", Description: "host-reported cost (stored, not computed)"},
			{Name: "json", Type: "boolean", Description: "emit JSON"},
		},
		ExitCodes: []ExitCodeMeta{{0, "Success"}, {1, "Gate or validation failure"}, {2, "Usage error"}, {3, "Workspace or session not found"}},
		Examples:  []string{"specd pinky claim --mission mission.json --json", "specd pinky update --session s --worker w --spec my-spec --task T1 --attempt 1 --message 'working' --percent 50", "specd pinky status --session s --worker w --json"},
	},

	{
		Command: "version", Category: "meta", Hidden: true,
		Description: "Show version information",
		Usage:       "specd version [--json]", Synopsis: "specd version [--json]",
		LongDescription: "Prints the version of the installed specd binary. With --json, emits a machine-readable object for CI and release automation.",
		Flags:           []FlagMeta{{Name: "json", Type: "boolean", Description: "Emit machine-readable version JSON"}}, ExitCodes: []ExitCodeMeta{{0, "Success"}},
		Examples: []string{"specd version", "specd version --json"},
	},

	{
		Command: "mcp", Category: "meta", Hidden: true,
		Description:     "Run the MCP stdio server (or print a host config snippet)",
		Usage:           "specd mcp [--root <path>] [--spec <slug>] [--config <host>]",
		Synopsis:        "specd mcp [--root <path>] [--spec <slug>] [--config <host>]",
		LongDescription: "Starts a Model Context Protocol (MCP) JSON-RPC 2.0 server over stdio, exposing every read-safe and state-mutating specd command as an MCP tool. A thin transport over the existing handlers — stdlib-only, no network, no LLM calls.\n\n--spec <slug> pins active-spec resolution to one spec for the lifetime of the MCP process. --config <host> prints a ready-to-paste config snippet for the named MCP host and exits without starting the server. Supported hosts: antigravity, claude-code, claude-desktop, codex, cursor, vscode. Combine with --root to substitute your project path into the snippet.",
		Flags: []FlagMeta{
			{Name: "root", Type: "string", Description: "Resolve specs against this project root (also substituted into --config output)"},
			{Name: "spec", Type: "string", Description: "Pin MCP phase/status resolution to one spec slug"},
			{Name: "config", Type: "string", Description: "Print ready-to-paste MCP config for a host (antigravity | claude-code | claude-desktop | codex | cursor | vscode) and exit"},
		},
		ExitCodes: []ExitCodeMeta{{0, "Success (stream closed or config printed)"}, {1, "Server error"}, {2, "Usage error"}},
		Examples:  []string{"specd mcp", "specd mcp --root /path/to/project", "specd mcp --spec auth", "specd mcp --config cursor", "specd mcp --config codex --root /path/to/project"},
	},

	{
		Command: "help", Category: "meta", Hidden: true,
		Description: "Show detailed help for a command",
		Usage:       "specd help [command]", Synopsis: "specd help [command]",
		LongDescription: "Prints summary documentation for all commands, or detailed reference for a single command.",
		Flags:           []FlagMeta{{Name: "all", Type: "boolean", Description: "Include meta-hidden commands"}, {Name: "json", Type: "boolean", Description: "Output the command registry as JSON"}},
		ExitCodes:       []ExitCodeMeta{{0, "Success"}, {2, "Usage error (unknown command)"}},
		Examples:        []string{"specd help", "specd help init", "specd help --json"},
	},
}

Commands is the complete registry of specd CLI commands, used to drive help text, JSON schema generation, and `specd help`. Positionals, phase/mode compatibility, and flag enums are filled in by the init function below.

View Source
var DefaultConfig = Config{
	Version:            1,
	DefaultVerify:      "echo 'specd: defaultVerify is unset — set it to your repo test command (see the specd-steering skill)' >&2; exit 1",
	Report:             ReportCfg{Format: "md", AutoRefreshSeconds: 0},
	Roles:              RolesCfg{SubagentMode: "inline"},
	PromotionThreshold: 3,
	Gates:              GatesCfg{Traceability: "warn", Acceptance: "off", Scope: "off", ContextBudget: "off", Custom: []CustomGateCfg{}},
	Verify:             VerifyCfg{Sandbox: "none"},
	Orchestration: OrchestrationCfg{
		Enabled:                  false,
		ApprovalPolicy:           "manual",
		WorkerMode:               "host",
		MaxWorkers:               4,
		MaxRetries:               2,
		SessionTimeoutMinutes:    120,
		HostReportedCostLimitUSD: 0,
		Transport: TransportCfg{
			Kind:               "file",
			PollIntervalMillis: 500,
			MessageTTLSeconds:  3600,
			LeaseSeconds:       120,
			HeartbeatSeconds:   30,
		},
		Program: ProgramCfg{MaxConcurrentSpecs: 2},
	},
}

DefaultConfig is the Config used when .specd/config.yml is absent or omits a field, providing safe out-of-the-box defaults for verify, gates, and orchestration.

View Source
var DesignSections = []string{
	"Overview", "Architecture", "Components and interfaces", "Data models",
	"Error handling", "Verification strategy", "Risks and open questions",
}

DesignSections lists the required level-2 headings a design.md must contain, in the order DesignGate checks for them.

View Source
var Glyph = map[TaskStatus]string{
	TaskComplete: "✓",
	TaskRunning:  "◐",
	TaskPending:  "○",
	TaskBlocked:  "⚠",
}

Glyph maps each TaskStatus to the single-character symbol used when rendering task lists and wave graphs.

View Source
var PhaseForStatus = spec.PhaseForStatus

PhaseForStatus is re-exported from internal/spec so existing core call sites (and PlanningAdvance below) keep working without importing spec directly.

View Source
var PlanningAdvance = map[SpecStatus]AdvanceTarget{
	StatusRequirements: {StatusDesign, PhaseForStatus(StatusDesign)},
	StatusDesign:       {StatusTasks, PhaseForStatus(StatusTasks)},
	StatusTasks:        {StatusExecuting, PhaseForStatus(StatusExecuting)},
}

PlanningAdvance maps each planning status to the status and phase a spec advances to once that status's readiness gate is satisfied.

View Source
var SlugRE = regexp.MustCompile(`^[a-z0-9][a-z0-9-]*$`)

SlugRE is the canonical spec-slug grammar. A slug is a path segment under .specd/specs/, so it must never contain path separators, "..", or other shell/filesystem metacharacters. Lowercase alnum plus internal hyphens only.

View Source
var TemplatesFS embed.FS

TemplatesFS embeds the contents of embed_templates, the built-in scaffold and config templates shipped with the binary.

View Source
var Version = "dev"

Version is set at build time via -ldflags.

Functions

func ACPEventFilename

func ACPEventFilename(sequence uint64, messageID string) (string, error)

ACPEventFilename builds the deterministic, lexically sortable filename for one event record from its sequence number and messageID: the sequence is zero-padded to acpEventSequenceWidth digits and joined with the (validated) message ID, e.g. "00000000000000000001-<messageID>.json".

func AddBlocker

func AddBlocker(s *State, id, reason string, turn int)

AddBlocker records (or replaces) the blocker for task id. Any prior entry for the same task is removed first so a task never has two blocker records.

func AgentsPath

func AgentsPath(root string) string

AgentsPath returns the path to root's AGENTS.md file.

func AppendContextLedger

func AppendContextLedger(session *OrchestrationSession, entry ContextLedgerEntry)

AppendContextLedger appends an entry to the session's context ledger and recomputes the peak-token high-water mark. It is pure (no IO): callers persist the mutated session through the normal save path so the write is atomic and serialized by the session lock.

func AppendFile

func AppendFile(path, data string) error

AppendFile appends data to the file at path (creating it and any missing parent dirs), fsyncs, and propagates any write or close failure so a partial append is never reported as success.

func ApplyTaskAnnotation

func ApplyTaskAnnotation(text, id string, checked bool, ann *Annotation) (string, error)

ApplyTaskAnnotation updates the checklist line for task id within text, setting its checked state and annotation, and returns the updated document text, or a GateError if the task line cannot be found.

func ApplyVars

func ApplyVars(text string, vars map[string]string) string

ApplyVars replaces every "{{key}}" placeholder in text with its corresponding value from vars.

func ArtifactPath

func ArtifactPath(root, slug, name string) string

ArtifactPath returns the on-disk path of a named artifact file within the given spec's directory.

func AtomicWrite

func AtomicWrite(path, data string) error

AtomicWrite writes data to path atomically: it creates any missing parent dirs, writes to a temp file in the same directory, fsyncs, sets 0644 (honoring umask), and renames over path. A partial write never replaces the target, and any failure is propagated to the caller.

func BlockerLines

func BlockerLines(state *State) []string

BlockerLines renders state's recorded blockers as "<task>: <reason>" lines, one per blocker.

func BuildMissionContextManifest

func BuildMissionContextManifest(mission PinkyMission, read func(name string) (string, bool)) contextpkg.MissionContextManifest

BuildMissionContextManifest is the mission-mode adapter over the shared context engine (see contextpkg.BuildContextManifest). It gives every host the same minimal sufficient context for a mission: role contract, Pinky operating skill, one phase-scoped skill, the specd context briefing, scoped files, then the source-of-truth artifacts (measured, and sliced to the task's row/covered requirements where a selector matches). The injected read closure is the only IO; pass nil to fall back to default hints and whole-file modes.

The adapter (and the env/disk readers below) stay in core because PinkyMission embeds the ACP cluster; moving them into the pure engine would create a core → context → core import cycle.

func CanonicalOrchestrationJSON

func CanonicalOrchestrationJSON(value any) ([]byte, error)

CanonicalOrchestrationJSON validates and normalizes value (one of the orchestration model types) into a deterministic byte-stable form, then marshals it as indented JSON with a trailing newline.

func CleanupCheckpoint

func CleanupCheckpoint(root, sessionID, taskID string) error

CleanupCheckpoint removes every checkpoint record for a completed task so a verified-done task can never be resurrected by a stale checkpoint (Req 6). It removes all attempts of the task, not just one, and is best-effort: a missing directory or already-deleted record is not an error.

func ConfigPath

func ConfigPath(root string) string

ConfigPath is the deprecated legacy JSON compatibility helper. New config discovery code should use ConfigPaths.

func ConfigPaths

func ConfigPaths(root string) []string

ConfigPaths returns project config candidates in priority order. YAML is the human-authored default; JSON is retained as the legacy compatibility path.

func CriticalPath

func CriticalPath(tasks []DagTask) []string

CriticalPath returns the longest dependency chain (by task count) in the DAG.

Precondition: tasks must be acyclic. A cycle makes "longest path" ill-defined, and the memo would otherwise be populated with partial paths computed under a specific cycle-guard context and reused incorrectly across roots. To stay safe when called directly (it is exported), CriticalPath returns nil if DetectCycle reports a cycle.

func DeriveSpecStatus

func DeriveSpecStatus(state *State)

DeriveSpecStatus recomputes the spec lifecycle status from task states once any work has started: all complete → verifying (unless already complete), an all-blocked frontier → blocked, otherwise executing. Phase follows status.

func DetectCycle

func DetectCycle(tasks []DagTask) []string

DetectCycle runs a depth-first search over the task dependency graph and returns the ids forming a cycle if one exists, or nil if the graph is acyclic.

func Divider

func Divider()

Divider prints a horizontal rule to stdout. It is a no-op in JSON mode.

func DurationMsBetween

func DurationMsBetween(startISO, endISO string) int64

DurationMsBetween returns the milliseconds between two RFC3339 timestamps, or 0 if either is unparseable or the interval is negative. It is the shared, clock-agnostic basis for telemetry durations (the timestamps themselves come from the injectable Clock, so the result is deterministic under the test clock).

func EarsForms

func EarsForms() []string

EarsForms returns the canonical EARS templates in match-precedence order, derived from the same table MatchEars enforces.

func EnvInt

func EnvInt(name string, def, min, max int) int

EnvInt reads name as an int, clamps to [min,max], and warns once on malformed input, returning def. max<=0 means "no upper bound".

func Error

func Error(msg string)

Error prints msg to stderr prefixed with a colorized "error" label.

func EstimateTokens

func EstimateTokens(b []byte) int

EstimateTokens forwards to contextpkg.EstimateTokens.

func EstimateTokensString

func EstimateTokensString(s string) int

EstimateTokensString forwards to contextpkg.EstimateTokensString — used by the compaction path to size a phase summary without an external tokenizer.

func ExtractSection

func ExtractSection(md *string, heading string) *string

ExtractSection returns the body text of the first "## heading" section in md (case-insensitive), stopping at the next "## " heading or end of document. It returns nil if md is nil, the heading is not found, or the extracted body is empty after trimming.

func FileExists

func FileExists(path string) bool

FileExists reports whether a file or directory exists at path.

func FindSpecdRoot

func FindSpecdRoot(start string) (string, bool)

FindSpecdRoot walks upward from start (or the current working directory when start is empty) looking for a .specd directory, returning the containing root and true if found, or false if it reaches the filesystem root without finding one.

func FormatSessionTimelineEvent

func FormatSessionTimelineEvent(event SessionTimelineEvent) string

FormatSessionTimelineEvent renders a SessionTimelineEvent as a single human-readable line: its zero-padded sequence, type, and any non-empty action/spec/task/escalation/reason/detail fields.

func FrontierOf

func FrontierOf(state *State) []string

FrontierOf returns the ordered runnable-task IDs for a spec's state.

func GlobalConfigPaths

func GlobalConfigPaths() []string

GlobalConfigPaths returns the candidate paths for a user-global specd config, checked in priority order across the OS config directory and the user's home directory, in both YAML and JSON forms.

func HasErrorDiagnostics

func HasErrorDiagnostics(diags []ConfigDiagnostic) bool

HasErrorDiagnostics reports whether diags contains at least one diagnostic with "error" severity.

func Header(title string)

Header prints title to stdout, upper-cased and bolded, preceded by a blank line. It is a no-op in JSON mode.

func HostContextBudgetFromEnv

func HostContextBudgetFromEnv() int

HostContextBudgetFromEnv reads the per-session host context budget that the MCP server exports under SPECD_MAX_CONTEXT_TOKENS before dispatching a tool call (capabilities.specd.maxContextTokens). It is the boundary-layer reader that feeds ContextRequest.HostBudget so the pure engine never touches the environment. Absent or non-numeric => 0 (no host cap), keeping non-MCP invocations byte-identical to the pre-feature path.

func Info

func Info(msg string)

Info prints msg to stdout prefixed with a colorized "info" label.

func InitMode

func InitMode(options InitOptions) string

InitMode derives the mode label ("force", "repair", "refresh", or "init") from InitOptions, used for InitPlan.Mode and InitResult.Mode.

func InjectPrompt

func InjectPrompt(reqMd, prompt string) string

InjectPrompt inserts an "Originating prompt" subsection carrying the verbatim `--from` text into a rendered requirements.md, placed just before the first `## Requirement` section (or appended if none is found). Returning the input unchanged when prompt is empty keeps the no-`--from` path byte-identical.

func IntegrationsPath

func IntegrationsPath(root string) string

IntegrationsPath returns the path to root's .specd/integrations.json file.

func IsJSONMode

func IsJSONMode() bool

IsJSONMode reports whether output should be emitted as JSON instead of the human-readable text format, based on the SPECD_JSON environment variable ("1" or "true").

func IsOrchestrationSessionNotFound

func IsOrchestrationSessionNotFound(err error) bool

IsOrchestrationSessionNotFound reports whether an orchestration lookup missed a persisted session. CLI callers map it to the standard not-found exit code.

func IsReadonlyRole

func IsReadonlyRole(r string) bool

IsReadonlyRole reports whether r is a read-only role (no runnable verify command required to complete).

func IsValidRole

func IsValidRole(r string) bool

IsValidRole reports whether r is a recognized task role.

func LegacyConfigPath

func LegacyConfigPath(root string) string

LegacyConfigPath returns the path to root's legacy .specd/config.json file.

func ListSpecs

func ListSpecs(root string) []string

ListSpecs returns the slugs of every spec under .specd/specs that has a state.json, sorted alphabetically. It returns nil if the specs directory cannot be read.

func LoadConfigStrict

func LoadConfigStrict(root string) (Config, []ConfigDiagnostic)

LoadConfigStrict loads the effective config the same way LoadConfig does, then additionally re-parses the raw project config file to validate every enum and integer-range field strictly, returning the full diagnostic list alongside the loaded config.

func LoadConfigWithDiagnostics

func LoadConfigWithDiagnostics(root string) (Config, ConfigLoadResult)

LoadConfigWithDiagnostics builds the effective Config by layering the global config file, the project config file, and environment variable overrides (in that order) onto DefaultConfig, validating any orchestration section before applying it, and collecting a diagnostic for every issue encountered along the way.

func MarshalEffectiveOrchestrationPolicy

func MarshalEffectiveOrchestrationPolicy(cfg Config) ([]byte, error)

MarshalEffectiveOrchestrationPolicy returns the authority-bearing policy in canonical JSON field order. The concrete type makes credentials, environment data, and other unrelated config structurally impossible to serialize.

func MaxSoftContextTokens

func MaxSoftContextTokens() int

MaxSoftContextTokens returns the upper bound allowed for gates.maxContextTokens.

func MergeAgentsMD

func MergeAgentsMD(path, template string, force bool) error

MergeAgentsMD merges template content with existing AGENTS.md. - If force=true: reset to template with markers (loses customizations) - If markers present: replaces content between them, preserves rest - If no markers: appends template with markers - Preserves content outside markers (unless force=true)

func MergeSection

func MergeSection(path, begin, end, body string) error

MergeSection replaces the content between begin/end markers in the file at path, preserving everything outside the markers. If the markers are absent the section is appended; if the file does not exist it is created holding only the section. This is idempotent: re-running with the same body is a no-op, and content the user added outside the markers is always preserved.

func NewACPID

func NewACPID() (string, error)

NewACPID generates a new random 32-character hex identifier suitable for use as an ACP message ID.

func NextSummary

func NextSummary(state *State) string

NextSummary returns a short, human-readable description of what to do next: the next runnable task, an all-complete message, an all-blocked message, or a waiting-on message.

func NowISO

func NowISO() string

NowISO returns the current time, via Clock, formatted as RFC3339Nano UTC — the timestamp format used throughout state.json.

func OrphanDeps

func OrphanDeps(tasks []DagTask) []struct{ Task, Dep string }

OrphanDeps returns every (task, dependency) pair where the dependency id does not match any task in the list.

func ParseAcceptanceMap

func ParseAcceptanceMap(value string) map[string]string

ParseAcceptanceMap reads criterion-id → test-name mappings from a task's `acceptance:` metadata value. It is intentionally lenient: free-form prose with no "id=test" tokens yields an empty (non-nil) map, so existing specs whose acceptance lines are descriptive remain valid and the acceptance gate stays a no-op for them. Later tokens win on duplicate ids (last write).

func ParseDepends

func ParseDepends(value string) []string

ParseDepends splits a task's `depends:` metadata value into a slice of task ids, treating an empty value, "-", "—", and "none" (case-insensitive) as no dependencies.

func ParseRequirements

func ParseRequirements(value string) []int

ParseRequirements parses a task's `requirements:` metadata value into the requirement numbers it references, silently skipping any comma-separated token that isn't a valid integer.

func ParseTaskRefs

func ParseTaskRefs(s string) []string

ParseTaskRefs returns the unique task IDs referenced in s, sorted by ordinal (T2 before T10) for deterministic output.

func PhaseReadiness

func PhaseReadiness(status SpecStatus, reqMd *string, designMd *string, doc ParsedTasks) []string

PhaseReadiness checks whether the spec's current status is ready to advance, returning a list of human-readable issues (empty when ready). It validates requirements.md via LintEars, design.md via DesignGate, or tasks.md via the dependency-graph checks, depending on status.

func PrintJSON

func PrintJSON(v any) error

PrintJSON marshals v with two-space indentation and writes it to stdout followed by a newline. It is the single JSON-emission path for every command: machine-readable results always go to stdout (diagnostics go to stderr via Error/Warn). Callers are responsible for ensuring list fields are non-nil so the agent never has to parse both `null` and `[]` — see the package convention in docs/command-reference.md.

func ProgramPath

func ProgramPath(root string) string

ProgramPath returns the path to program.json under the spec root's .specd directory.

func ProjectOrchestrationEnabled

func ProjectOrchestrationEnabled(root string) bool

ProjectOrchestrationEnabled reports whether the project has orchestration capability — i.e. `.specd/config.json` has `orchestration.enabled: true`. This is the capability gate consulted before a spec may be set to orchestrated; it never selects a mode on its own.

func ReadArtifact

func ReadArtifact(root, slug, name string) *string

ReadArtifact reads a named artifact file for a spec, returning nil if it does not exist.

func ReadOrDefault

func ReadOrDefault(path, fallback string) string

ReadOrDefault returns the file contents at path, or fallback if it cannot be read (missing or unreadable).

func ReadOrNull

func ReadOrNull(path string) *string

ReadOrNull returns a pointer to the file contents at path, or nil if it cannot be read. The nil result lets callers distinguish a missing file from an empty one.

func ReadRole

func ReadRole(root, role string) *string

ReadRole reads a role definition file (e.g. .specd/roles/<role>.md), returning nil if it does not exist.

func ReadTemplate

func ReadTemplate(rel string) (string, error)

ReadTemplate returns the contents of the embedded template at the embed_templates-relative path rel.

func ReclaimExpiredLeases

func ReclaimExpiredLeases(root, sessionID string) (int, error)

ReclaimExpiredLeases releases active leases whose deadline has passed so the underlying tasks become re-dispatchable at the next attempt. It is privileged Brain reclamation, distinct from a worker's cooperative ReleaseLease, and returns the number of leases reclaimed. Reclaiming twice reclaims nothing.

func RecomputePeakTokens

func RecomputePeakTokens(session *OrchestrationSession)

RecomputePeakTokens sets session.PeakTokens to the maximum estimated- or host-reported token count seen across the whole ledger. It is monotonic by construction (it scans the full trail) so recomputing twice is a no-op.

func Reconcile

func Reconcile(state *State, doc ParsedTasks)

Reconcile rebuilds state.Tasks from doc's parsed tasks.md, carrying over each existing task's runtime fields (status, timestamps, evidence, verification, blocker, telemetry) and role when still present, and drops blockers whose task no longer exists.

func RecordContextLedger

func RecordContextLedger(root, slug, sessionID string, manifest contextpkg.MissionContextManifest) error

RecordContextLedger appends a pre-dispatch ledger entry from a mission's context manifest (R6): the estimated tokens, derived budget, and soft ceiling the manifest computed at dispatch time. It is a no-op when no session exists. The step sequence is the spec revision at dispatch; the phase is the spec's current phase. The heuristic estimate is whatever the manifest carried — no external tokenizer is consulted.

func ReleasePinkyClaim

func ReleasePinkyClaim(root, sessionID, workerID string, attempt int) error

ReleasePinkyClaim releases a worker's lease for the given attempt, freeing the task for re-dispatch.

func RemoveBlocker

func RemoveBlocker(s *State, id string)

RemoveBlocker drops any blocker entry for task id from s.Blockers. It always writes a fresh slice so the result never aliases the previous backing array (which may still be referenced by an already-marshaled snapshot of state).

func RenderCommandHelp

func RenderCommandHelp(cmdName string) (string, error)

RenderCommandHelp renders detailed, man-page-style help (synopsis, description, flags, exit codes, examples) for the named command, returning an error if cmdName is not a known command.

func RenderConfigYAML

func RenderConfigYAML(cfg Config) string

RenderConfigYAML renders Config in the canonical v2 human-authored YAML shape. Field order is intentionally stable so migration output is reviewable.

func RenderHTML

func RenderHTML(d ReportData, autoRefreshSeconds int) string

RenderHTML renders d as a complete, self-contained HTML page: a styled header with status badge, every section returned by buildSections, and an inline live-update script that re-renders the page on matching /events SSE deltas. autoRefreshSeconds, when positive, adds a meta refresh tag as a fallback for clients without EventSource support.

func RenderHelp

func RenderHelp() string

RenderHelp renders the top-level human-readable help text listing visible commands grouped by category.

func RenderHelpAll

func RenderHelpAll() string

RenderHelpAll renders the top-level help text like RenderHelp but also includes hidden commands.

func RenderHelpJSON

func RenderHelpJSON() (string, error)

RenderHelpJSON renders the full, unfiltered command schema (including hidden and deprecated commands) as indented JSON.

func RenderHelpJSONAll

func RenderHelpJSONAll(includeHidden bool) (string, error)

RenderHelpJSONAll renders the command schema as indented JSON, including hidden commands only when includeHidden is true.

func RenderMarkdown

func RenderMarkdown(d ReportData) string

RenderMarkdown renders d as a complete Markdown report: a title with status badge, the spec/status/phase/turn summary line, the effective mode, and every section returned by buildSections.

func RenderMissionBrief

func RenderMissionBrief(mission PinkyMission) string

RenderMissionBrief assembles a fully context-engineered worker brief from a mission (GAP-3). It is the single place the harness packages a mission for a worker agent, so two different hosts hand a worker the same context for the same mission. The brief is paste-ready into a sub-agent system prompt: it names the role asset to load, the context command to run, the bounded contract, the files in scope, the verify command that is the only proof of done, and the exact `specd pinky` calls the worker must make.

It performs no IO and no model call — it is a pure rendering of an already validated mission.

func RenderPrometheusMetrics

func RenderPrometheusMetrics(data ReportData) string

RenderPrometheusMetrics emits a deterministic Prometheus textfile view of the spec report telemetry. It is format-only: no network endpoint, no exporter, no runtime dependency.

func RenderTaskLine

func RenderTaskLine(id, bareTitle string, checked bool, ann *Annotation) string

RenderTaskLine renders a single task's checklist line, including its checkbox state and any trailing completion/blocked annotation, in the on-disk tasks.md format.

func RequireSpec

func RequireSpec(root, slug string) error

RequireSpec validates slug and returns a NotFoundError if no spec with that slug exists under .specd/specs/.

func RequireSpecdRoot

func RequireSpecdRoot() (string, error)

RequireSpecdRoot resolves the specd root for the current working directory, returning a NotFoundError instructing the user to run `specd init` if none exists.

func RequirementNumbers

func RequirementNumbers(reqMd string) map[int]bool

RequirementNumbers extracts the set of requirement numbers declared by `## Requirement N` headers in reqMd, after stripping HTML comments.

func ResolveMode

func ResolveMode(flag string, s *State) (mode, origin string)

ResolveMode returns the effective execution mode for an invocation and the origin that should be recorded for it, enforcing the precedence:

explicit command flag  >  spec.ExecutionMode  >  simple

flag is the one-shot override ("" when none was given). When a flag is present it wins and the choice is attributed to the user (the caller is expected to persist it so state stays the single source of truth — no hidden drift). Otherwise the spec's recorded mode/origin carries through, defaulting to Simple/default for specs that never opted in.

func RolesDir

func RolesDir(root string) string

RolesDir returns the path to root's .specd/roles directory.

func RuntimeDir

func RuntimeDir(root string) (string, error)

RuntimeDir is a convenience wrapper that builds an ACPRuntimePaths for root and returns its validated runtime directory in one call.

func SaveProgram

func SaveProgram(root string, manifest ProgramManifest) error

SaveProgram writes manifest to program.json, sorting spec slugs and deduplicating/sorting each spec's dependency list so the file is deterministic and diff-stable across saves.

func SaveProgramState

func SaveProgramState(root string, state ProgramState) error

SaveProgramState writes the frontier atomically (temp + rename) so a crash always leaves a coherent latest file rather than a torn write. The program driver is the single writer (one step at a time), so the atomic rename is the CAS discipline here — exactly as saveProgramSession persists the parent session beside it.

func SaveState

func SaveState(root, slug string, state *State) error

SaveState performs a compare-and-swap commit of state to disk: it verifies the on-disk revision still matches state.Revision, then bumps the revision and atomically writes.

INVARIANT: SaveState MUST be called inside WithSpecLock(root, slug, …) for the same (root, slug). The read-then-write CAS is not atomic on its own; the advisory lock is what serializes it against concurrent writers. Calling SaveState without the lock silently reintroduces the lost-update race. Test builds set assertLocked to panic on violations.

func ScrubbedEnv

func ScrubbedEnv() []string

ScrubbedEnv builds a minimal allowlisted environment for child processes (verify commands and custom gates), dropping inherited secrets from the parent shell while keeping the SPECD_* namespace the harness relies on. It is the single source of the env-scrub policy shared by verify and the custom-gate runner.

func SerializeTasks

func SerializeTasks(doc ParsedTasks) string

SerializeTasks renders a ParsedTasks value back into tasks.md markdown text, grouping tasks under their `## Wave N` headers in ascending order.

func SkillsDir

func SkillsDir(root string) string

SkillsDir returns the path to root's .specd/skills directory.

func SortedScaffoldTargets

func SortedScaffoldTargets(assets []ScaffoldAsset) []string

SortedScaffoldTargets returns a stable target set for parity checks.

func SpecArtifactReader

func SpecArtifactReader(root, slug string) func(name string) (string, bool)

SpecArtifactReader returns the injected artifact reader the context engine uses for measurement and slicing: it yields the raw markdown for a spec artifact and ok=false when absent. Exported so command surfaces feed the same reader the mission adapter and gates use.

func SpecDir

func SpecDir(root, slug string) string

SpecDir returns the path to the spec directory for slug under root's .specd/specs directory.

func SpecExists

func SpecExists(root, slug string) bool

SpecExists reports whether a spec with the given slug has a state.json under .specd/specs/.

func SpecdDir

func SpecdDir(root string) string

SpecdDir returns the path to root's .specd directory.

func SpecsDir

func SpecsDir(root string) string

SpecsDir returns the path to root's .specd/specs directory.

func SplitCSV

func SplitCSV(value string) []string

SplitCSV splits a comma-separated meta field (e.g. a task's `files:` list) into trimmed, non-empty tokens, discarding the "—"/"-" placeholders. It is the exported entry point command surfaces use to feed ContextRequest.Files.

func SteeringDir

func SteeringDir(root string) string

SteeringDir returns the path to root's .specd/steering directory.

func StripHTMLComments

func StripHTMLComments(text string) string

StripHTMLComments blanks out the contents of every HTML comment (<!-- ... -->) in text, replacing each non-newline byte with a space so line numbers and column offsets in the surrounding text are preserved.

func Success

func Success(msg string)

Success prints msg to stdout prefixed with a colorized checkmark.

func UncoveredRequirements

func UncoveredRequirements(state *State, reqMd *string) []int

UncoveredRequirements returns the requirement numbers declared in reqMd that no task in state references, sorted ascending, or nil if reqMd is nil.

func ValidateACPEnvelope

func ValidateACPEnvelope(envelope ACPEnvelope) error

ValidateACPEnvelope checks an envelope's version, IDs, sizes, and type-specific payload constraints, returning an error describing the first violation found.

func ValidateAgentsMD

func ValidateAgentsMD(path string) (bool, error)

ValidateAgentsMD reports whether the AGENTS.md file at path has a well-formed managed marker section (both begin and end markers present, in order). It returns false, nil if the file does not exist.

func ValidateCheckpointRecord

func ValidateCheckpointRecord(rec CheckpointRecord) error

ValidateCheckpointRecord rejects malformed checkpoints before they are persisted or surfaced into a snapshot. It mirrors the validation discipline of ValidateOrchestrationSession: opaque session ID, slug spec, task-ID grammar, attempt >= 1, progress within [0,100], runtime-segment worker ID, parseable timestamp.

func ValidateConfigDoc

func ValidateConfigDoc(doc map[string]any) error

ValidateConfigDoc checks a parsed config document's enum-valued fields (accepting both camelCase and snake_case keys) against their allowed values and returns a single combined error listing every violation, or nil if the document is valid.

func ValidateInitOptions

func ValidateInitOptions(options InitOptions) error

ValidateInitOptions rejects InitOptions where more than one of Force, Repair, and Refresh is set, since those modes are mutually exclusive.

func ValidateOrchestrationConfig

func ValidateOrchestrationConfig(cfg *OrchestrationCfg) error

ValidateOrchestrationConfig validates authority-bearing values and normalizes resource limits. Callers receive an error for ambiguous or unsafe policy; bounded integers are clamped through clampOrchestrationInt.

func ValidateOrchestrationDecision

func ValidateOrchestrationDecision(decision OrchestrationDecision) error

ValidateOrchestrationDecision rejects a malformed decision: bad version, unsupported action, invalid spec/task ID/attempt, an artifact set on a non-authoring action (or missing on one that requires it), an empty reason/idempotency key, or escalation fields inconsistent with the action.

func ValidateOrchestrationPolicy

func ValidateOrchestrationPolicy(policy OrchestrationPolicy) error

ValidateOrchestrationPolicy rejects a policy whose approval mode, worker and retry limits, session timeout, cost limit, or compaction settings fall outside their allowed ranges.

func ValidateOrchestrationSession

func ValidateOrchestrationSession(session OrchestrationSession) error

ValidateOrchestrationSession rejects a malformed session: bad version, session ID, spec slug, owner, status, or policy, plus inconsistent created/updated/expires timestamps.

func ValidateOrchestrationSnapshot

func ValidateOrchestrationSnapshot(snapshot OrchestrationSnapshot) error

ValidateOrchestrationSnapshot rejects a malformed snapshot: bad version, session ID, spec slug, lifecycle state, or timestamps, plus duplicate or invalid runnable tasks, active leases, failures, or checkpoints.

func ValidateProgramState

func ValidateProgramState(state ProgramState) error

ValidateProgramState fails closed on any structurally invalid frontier so a corrupt file never drives a partial resume.

func ValidateScaffoldManifest

func ValidateScaffoldManifest(assets []ScaffoldAsset, readTemplate func(string) (string, error)) error

ValidateScaffoldManifest verifies all templates before init writes anything.

func ValidateSlug

func ValidateSlug(slug string) error

ValidateSlug rejects any slug that could escape the specs directory (path traversal) or otherwise resolve to an unintended path. Every command that accepts a user-supplied slug must call this before touching the filesystem — defense in depth, independent of whether the spec already exists.

func VerificationRef

func VerificationRef(rec *VerificationRecord) string

VerificationRef is the canonical reference a worker must echo back to claim a specd verification record. It binds the command, git head, and run timestamp, so any change to the verified work changes the ref.

func Warn

func Warn(msg string)

Warn prints msg to stdout prefixed with a colorized "warn" label.

func WaveGraph

func WaveGraph(state *State) string

WaveGraph renders a human-readable, wave-by-wave listing of state's tasks, including status glyphs, blocked reasons, and the critical path.

func WaveViolations

func WaveViolations(tasks []DagTask) []struct{ Task, Dep string }

WaveViolations returns every (task, dependency) pair where the dependency is assigned to a later wave than the task that depends on it.

func WithSpecLock

func WithSpecLock[T any](root, slug string, fn func() (T, error)) (T, error)

WithSpecLock runs fn while holding the spec's advisory lock. It provides three guarantees:

  • Cross-process exclusion via an O_EXCL lock file (with stale reclamation).
  • In-process exclusion via a per-path mutex, so concurrent goroutines in the same process serialize rather than racing the lock file.
  • Reentrancy for the owning goroutine, so a locked section may call another locking helper (e.g. LoadSpec inside an already-locked command) without deadlocking.

Types

type ACPAcceptedPayload

type ACPAcceptedPayload struct {
	WorkerID string `json:"workerId"`
}

ACPAcceptedPayload is the body of an accepted-message reply, identifying the worker that accepted the mission.

type ACPArchiveManifest

type ACPArchiveManifest struct {
	Version    int    `json:"version"`
	SessionID  string `json:"sessionId"`
	SealedAt   string `json:"sealedAt"`
	EventCount int    `json:"eventCount"`
	LastSeq    uint64 `json:"lastSequence"`
}

ACPArchiveManifest describes a sealed ACP session archive: its schema version, the archived session ID, when it was sealed, and the event count and last sequence number it captured.

type ACPAuthority

type ACPAuthority struct {
	ReadOnly       bool     `json:"readOnly"`
	AllowedActions []string `json:"allowedActions"`
}

ACPAuthority declares the access scope granted to a worker for a mission: whether it is read-only and which actions it is permitted to take.

type ACPBlockerPayload

type ACPBlockerPayload struct {
	Reason string `json:"reason"`
}

ACPBlockerPayload is the body of a blocker message a worker sends when it cannot proceed, giving the reason it is stuck.

type ACPCancelledPayload

type ACPCancelledPayload struct {
	Reason string `json:"reason"`
}

ACPCancelledPayload is the body of a cancelled message confirming a mission was cancelled, with the reason for the cancellation.

type ACPCheckpointPayload

type ACPCheckpointPayload struct {
	Percent      int      `json:"percent"`
	Reason       string   `json:"reason,omitempty"`
	ChangedFiles []string `json:"changedFiles,omitempty"`
	GitHead      string   `json:"gitHead,omitempty"`
}

ACPCheckpointPayload is the event-side summary of a checkpoint. The full resume payload (context manifest, working notes) lives in the on-disk CheckpointRecord; the event carries only the lean, bounded fields the Brain and observers need: how far the work got, why it stopped, and the file/git frontier it reached.

type ACPCursor

type ACPCursor struct {
	Version   int    `json:"version"`
	SessionID string `json:"sessionId"`
	WorkerID  string `json:"workerId"`
	Sequence  uint64 `json:"sequence"`
	MessageID string `json:"messageId,omitempty"`
	UpdatedAt string `json:"updatedAt"`
}

ACPCursor tracks the last ACP event a worker has reconciled for a session: the sequence number and message ID it last acknowledged via SaveCursor.

type ACPDirectivePayload

type ACPDirectivePayload struct {
	Action string `json:"action"`
	Reason string `json:"reason"`
}

ACPDirectivePayload is the body of a directive message the Brain sends to a worker, naming the action to take and the reason for it.

type ACPEnvelope

type ACPEnvelope struct {
	Version   string                 `json:"version"`
	MessageID string                 `json:"messageId"`
	SessionID string                 `json:"sessionId"`
	Sequence  uint64                 `json:"sequence"`
	CreatedAt string                 `json:"createdAt"`
	ExpiresAt string                 `json:"expiresAt"`
	Type      ACPMessageType         `json:"type"`
	From      string                 `json:"from"`
	To        string                 `json:"to"`
	Spec      string                 `json:"spec"`
	Task      string                 `json:"task,omitempty"`
	Attempt   int                    `json:"attempt"`
	InReplyTo string                 `json:"inReplyTo,omitempty"`
	Payload   json.RawMessage        `json:"payload"`
	Decision  *OrchestrationDecision `json:"decision,omitempty"`
}

ACPEnvelope is the transport-level wrapper for every ACP message exchanged between a Brain and its Pinky workers. Payload carries the type-specific body (see ACPMessageType) as raw JSON, decoded separately by the caller.

func AcknowledgePinkyCancellation

func AcknowledgePinkyCancellation(root, sessionID, workerID, spec, taskID string, attempt int, reason string, cfg OrchestrationCfg) (ACPEnvelope, error)

AcknowledgePinkyCancellation appends a cancelled event for the given task attempt to the session's ACP event log, the worker's acknowledgment that it has stopped after a cancel directive.

func NewACPEnvelope

func NewACPEnvelope(messageType ACPMessageType, payload any) (ACPEnvelope, error)

NewACPEnvelope builds an ACPEnvelope of the given message type by marshaling payload to JSON and setting Version to ACPVersion. Callers fill in any remaining envelope fields (ids, session, routing) before sending it.

func ParseACPEnvelope

func ParseACPEnvelope(raw []byte) (ACPEnvelope, error)

ParseACPEnvelope decodes raw JSON into an ACPEnvelope, rejecting empty input, input over ACPMaxEnvelopeBytes, and any envelope that fails ValidateACPEnvelope.

func RecordBrainDirective

func RecordBrainDirective(root string, directive BrainDirective, cfg OrchestrationCfg) (ACPEnvelope, error)

RecordBrainDirective validates that the targeted worker holds an active lease for the directive's task/attempt, then builds and appends a directive event addressed to that worker, stamping its ID, timestamps, and expiry from the host clock and configured message TTL.

func RecordPinkyBlocker

func RecordPinkyBlocker(root string, report PinkyBlockerReport, cfg OrchestrationCfg) (ACPEnvelope, error)

RecordPinkyBlocker appends a blocked event for report to the session's ACP event log.

func RecordPinkyProgress

func RecordPinkyProgress(root string, report PinkyProgressReport, cfg OrchestrationCfg) (ACPEnvelope, error)

RecordPinkyProgress appends a progress event for report to the session's ACP event log, stamping the report time from the host clock (rather than trusting the worker) so the driver can weight stall waits (R6).

func RecordPinkyQuery

func RecordPinkyQuery(root string, report PinkyQueryReport, cfg OrchestrationCfg) (ACPEnvelope, error)

RecordPinkyQuery appends a query event for report to the session's ACP event log.

func RecordPinkyTerminalReport

func RecordPinkyTerminalReport(root string, report PinkyTerminalReport, cfg OrchestrationCfg) (ACPEnvelope, error)

RecordPinkyTerminalReport appends an evidence event for report to the session's ACP event log, the worker's final word on a task attempt before the Brain reconciles it via ReconcilePinkyEvidence.

type ACPEvidencePayload

type ACPEvidencePayload struct {
	VerificationRef string   `json:"verificationRef"`
	Summary         string   `json:"summary"`
	ChangedFiles    []string `json:"changedFiles"`
	GitHead         string   `json:"gitHead,omitempty"`
	DurationMs      int64    `json:"durationMs,omitempty"`
	HostTokens      int      `json:"hostTokens,omitempty"`
	HostCost        string   `json:"hostCost,omitempty"`
}

ACPEvidencePayload is the body of an evidence message a worker sends on completion, summarizing the verification ref, changed files, git head, elapsed time, and any host token/cost usage reported for the mission.

type ACPHeartbeatPayload

type ACPHeartbeatPayload struct {
	WorkerID string `json:"workerId"`
	Status   string `json:"status"`
}

ACPHeartbeatPayload is the body of a heartbeat message a worker sends to signal it is still alive, along with its current status.

type ACPLease

type ACPLease struct {
	Version          int            `json:"version"`
	SessionID        string         `json:"sessionId"`
	WorkerID         string         `json:"workerId"`
	Spec             string         `json:"spec"`
	Task             string         `json:"task"`
	Attempt          int            `json:"attempt"`
	Status           ACPLeaseStatus `json:"status"`
	AcquiredAt       string         `json:"acquiredAt"`
	HeartbeatAt      string         `json:"heartbeatAt"`
	LeaseUntil       string         `json:"leaseUntil"`
	MessageExpiresAt string         `json:"messageExpiresAt"`
	ReleasedAt       string         `json:"releasedAt,omitempty"`
	// Suspend metadata (R3). All omitempty so a lease that never suspends
	// serializes byte-identically to the pre-resilience shape. SuspendedAt and
	// ResumeDeadline bound the suspension window; the reclaim predicate uses
	// ResumeDeadline (not LeaseUntil) to decide if a suspended lease is dead.
	// SuspendSecondsTotal accumulates requested suspension across repeated
	// suspend calls so the cumulative cap can be enforced.
	SuspendedAt         string `json:"suspendedAt,omitempty"`
	SuspendReason       string `json:"suspendReason,omitempty"`
	ResumeDeadline      string `json:"resumeDeadline,omitempty"`
	SuspendSecondsTotal int    `json:"suspendSecondsTotal,omitempty"`
}

ACPLease is the durable record of a worker's claim on one task attempt: its identity, timing window, status, and (when suspended) the suspend metadata governing how long it stays honored as in-flight.

func HeartbeatPinkyClaim

func HeartbeatPinkyClaim(root, sessionID, workerID string, attempt int, cfg OrchestrationCfg) (ACPLease, error)

HeartbeatPinkyClaim renews a worker's lease for the given attempt, extending it by the configured lease duration.

func ResumePinkyClaim

func ResumePinkyClaim(root, sessionID, workerID string, attempt int, cfg OrchestrationCfg) (ACPLease, time.Duration, error)

ResumePinkyClaim returns a suspended lease to active and emits a `resume` ACP event recording how long the worker was suspended (R3, Req 3). The lease keeps the same attempt and task, so the worker continues rather than re-claiming.

func SuspendPinkyClaim

func SuspendPinkyClaim(root, sessionID, workerID string, attempt int, reason string, resumeAfterSeconds int, cfg OrchestrationCfg) (ACPLease, error)

SuspendPinkyClaim suspends a worker's lease for `resumeAfterSeconds`, holding the task instead of failing it (R3). The heartbeat interval is added as a buffer so the worker has slack to call resume, and the cumulative-suspension cap is resolved from config (default 600s).

type ACPLeaseStatus

type ACPLeaseStatus string

ACPLeaseStatus is the lifecycle state of a worker's claim on a task attempt.

const (
	// ACPLeaseActive marks a lease currently held and heartbeating by a worker.
	ACPLeaseActive ACPLeaseStatus = "active"
	// ACPLeaseReleased marks a lease the worker has explicitly given up
	// (ReleaseLease) or that was reclaimed after expiry.
	ACPLeaseReleased ACPLeaseStatus = "released"
	// ACPLeaseSuspended marks a lease whose worker is temporarily away but will
	// return — typically rate-limited or compacting context (R3). A suspended
	// lease is NOT dead: it is honored as in-flight until its ResumeDeadline
	// passes, so the Brain does not false-fail it into a retry storm.
	ACPLeaseSuspended ACPLeaseStatus = "suspended"
)

type ACPMessageType

type ACPMessageType string

ACPMessageType identifies the kind of message carried by an ACPEnvelope.

const (
	ACPMessageMission   ACPMessageType = "mission"
	ACPMessageAccepted  ACPMessageType = "accepted"
	ACPMessageHeartbeat ACPMessageType = "heartbeat"
	ACPMessageProgress  ACPMessageType = "progress"
	ACPMessageEvidence  ACPMessageType = "evidence"
	ACPMessageBlocker   ACPMessageType = "blocker"
	ACPMessageQuery     ACPMessageType = "query"
	ACPMessageDirective ACPMessageType = "directive"
	ACPMessageCancelled ACPMessageType = "cancelled"
	// ACPMessageCheckpoint is a worker's durable mid-task progress marker (R1).
	// Like progress it flows pinky -> brain, but it also pairs with an on-disk
	// CheckpointRecord and releases the worker's lease so the Brain can resume.
	ACPMessageCheckpoint ACPMessageType = "checkpoint"
	// ACPMessageResume is a returning worker's signal that a suspended lease is
	// active again (R3). It flows pinky -> brain and records how long the worker
	// was suspended so the Brain (and observers) can attribute the pause.
	ACPMessageResume ACPMessageType = "resume"
)

ACP message type values, naming the lifecycle events that flow between a Brain and its Pinky workers.

type ACPMissionPayload

type ACPMissionPayload struct {
	DispatchDigest  string                            `json:"dispatchDigest"`
	Role            string                            `json:"role"`
	ContextCommand  string                            `json:"contextCommand"`
	ContextManifest contextpkg.MissionContextManifest `json:"contextManifest,omitempty"`
	Contract        string                            `json:"contract"`
	Files           []string                          `json:"files"`
	Acceptance      string                            `json:"acceptance"`
	VerifyCommand   string                            `json:"verifyCommand"`
	Dependencies    []string                          `json:"dependencies"`
	Authority       ACPAuthority                      `json:"authority"`
}

ACPMissionPayload is the body of an ACPMessageMission message: the full dispatch a Brain sends a worker, including its contract, file scope, acceptance and verify commands, dependencies, and granted ACPAuthority.

type ACPProgressPayload

type ACPProgressPayload struct {
	Percent int    `json:"percent"`
	Message string `json:"message"`
	// LastReport is the server-side write time of this progress record (RFC3339).
	// It is stamped by RecordPinkyProgress from the host clock, never from a
	// worker-supplied value, so it cannot be spoofed into the future. It lets the
	// driver distinguish slow-but-progressing work from a true stall (R6).
	// omitempty keeps pre-resilience progress events byte-stable.
	LastReport string `json:"lastReport,omitempty"`
}

ACPProgressPayload is the body of a progress message reporting how far a worker has gotten on its mission.

type ACPQueryPayload

type ACPQueryPayload struct {
	Text string `json:"text"`
}

ACPQueryPayload is the body of a query message a worker sends to ask the Brain a free-text question.

type ACPResumePayload

type ACPResumePayload struct {
	SuspendedSeconds int    `json:"suspendedSeconds"`
	Reason           string `json:"reason,omitempty"`
}

ACPResumePayload is the event-side record of a worker returning from a suspended lease (R3): how long it was away and why it had suspended.

type ACPRuntimePaths

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

ACPRuntimePaths derives paths beneath .specd/runtime after validating every untrusted segment and rejecting symlinks in existing runtime components.

func NewACPRuntimePaths

func NewACPRuntimePaths(root string) (ACPRuntimePaths, error)

NewACPRuntimePaths resolves root to an absolute, symlink-free path and returns an ACPRuntimePaths rooted at <root>/.specd/runtime, validating that the runtime directory itself is safe before any path is derived from it.

func (ACPRuntimePaths) ArchivePath

func (p ACPRuntimePaths) ArchivePath(sessionID string) (string, error)

ArchivePath returns the validated path to one archived session after checking sessionID is a well-formed opaque ID: archives/<id>.

func (ACPRuntimePaths) ArchivesDir

func (p ACPRuntimePaths) ArchivesDir() (string, error)

ArchivesDir returns the validated directory holding archived sessions: archives/.

func (ACPRuntimePaths) ArtifactPath

func (p ACPRuntimePaths) ArtifactPath(sessionID, artifactID string) (string, error)

ArtifactPath returns the validated path to one artifact after checking artifactID is a well-formed runtime segment: sessions/<id>/artifacts/<artifactID>.

func (ACPRuntimePaths) ArtifactsDir

func (p ACPRuntimePaths) ArtifactsDir(sessionID string) (string, error)

ArtifactsDir returns the validated directory holding a session's artifacts: sessions/<id>/artifacts.

func (ACPRuntimePaths) CheckpointDir

func (p ACPRuntimePaths) CheckpointDir(sessionID string) (string, error)

CheckpointDir returns the validated directory holding a session's checkpoint records: sessions/<id>/checkpoints. It sits beside events/, workers/, and artifacts/ in the runtime layout.

func (ACPRuntimePaths) CheckpointPath

func (p ACPRuntimePaths) CheckpointPath(sessionID, taskID string, attempt int) (string, error)

CheckpointPath returns the deterministic record path for one task attempt: checkpoints/<task>-<attempt>.json. The filename is keyed on (taskID, attempt) so a re-issued attempt overwrites its own checkpoint rather than accumulating duplicates, and the attempt-guard can compare attempts by filename. Task IDs are uppercase (e.g. T1) so they use the ACP task-ID grammar, not the lowercase runtime-segment validator.

func (ACPRuntimePaths) ContextSnapshotDir

func (p ACPRuntimePaths) ContextSnapshotDir(sessionID string) (string, error)

ContextSnapshotDir returns the validated directory holding a session's per-turn context snapshots: sessions/<id>/context-snapshots. It sits beside events/, workers/, artifacts/, and checkpoints/ in the runtime layout (R2).

func (ACPRuntimePaths) ContextSnapshotPath

func (p ACPRuntimePaths) ContextSnapshotPath(sessionID string, turn int) (string, error)

ContextSnapshotPath returns the deterministic snapshot path for one turn: context-snapshots/<turn>.json. Keying on the turn means re-emitting a turn's snapshot overwrites it rather than accumulating duplicates.

func (ACPRuntimePaths) CursorPath

func (p ACPRuntimePaths) CursorPath(sessionID, workerID string) (string, error)

CursorPath returns the validated path to a worker's cursor record: sessions/<id>/workers/<workerID>/cursor.json.

func (ACPRuntimePaths) EventPath

func (p ACPRuntimePaths) EventPath(sessionID string, sequence uint64, messageID string) (string, error)

EventPath returns the validated path to one event record, deriving the deterministic filename from sequence and messageID via ACPEventFilename: sessions/<id>/events/<sequence>-<messageID>.json.

func (ACPRuntimePaths) EventsDir

func (p ACPRuntimePaths) EventsDir(sessionID string) (string, error)

EventsDir returns the validated directory holding a session's events: sessions/<id>/events.

func (ACPRuntimePaths) LeasePath

func (p ACPRuntimePaths) LeasePath(sessionID, workerID string) (string, error)

LeasePath returns the validated path to a worker's lease record: sessions/<id>/workers/<workerID>/lease.json.

func (ACPRuntimePaths) MissionPath

func (p ACPRuntimePaths) MissionPath(slug, taskID string, attempt int) (string, error)

MissionPath returns the canonical, validated runtime path for one mission record: missions/<slug>-<taskID>-<attempt>.json. The filename is deterministic given (slug, taskID, attempt) so a re-issued attempt overwrites its own record rather than creating a duplicate, and two specs (or two attempts of one task) never share a filename. Task IDs are uppercase (e.g. T1), so they use the ACP task-ID grammar rather than the lowercase runtime-segment validator.

func (ACPRuntimePaths) MissionsDir

func (p ACPRuntimePaths) MissionsDir() (string, error)

MissionsDir returns the validated directory holding mission records: missions/.

func (ACPRuntimePaths) ProgramChildDir

func (p ACPRuntimePaths) ProgramChildDir(slug string) (string, error)

ProgramChildDir returns the validated directory for one program child after checking slug is a well-formed spec slug: program/children/<slug>.

func (ACPRuntimePaths) ProgramChildLeasePath

func (p ACPRuntimePaths) ProgramChildLeasePath(slug string) (string, error)

ProgramChildLeasePath returns the validated path to a program child's lease record: program/children/<slug>/lease.json.

func (ACPRuntimePaths) ProgramChildrenDir

func (p ACPRuntimePaths) ProgramChildrenDir() (string, error)

ProgramChildrenDir returns the validated directory holding program child records: program/children.

func (ACPRuntimePaths) ProgramSessionPath

func (p ACPRuntimePaths) ProgramSessionPath(sessionID string) (string, error)

ProgramSessionPath returns the validated path to one program session record after checking sessionID is a well-formed opaque ID: program/sessions/<id>.json.

func (ACPRuntimePaths) ProgramSessionsDir

func (p ACPRuntimePaths) ProgramSessionsDir() (string, error)

ProgramSessionsDir returns the validated directory holding program session records: program/sessions.

func (ACPRuntimePaths) ProgramStatePath

func (p ACPRuntimePaths) ProgramStatePath(parentSessionID string) (string, error)

ProgramStatePath returns the parent session's program-state.json: sessions/<parent>/program-state.json. It sits in the parent session dir beside events/, workers/, and checkpoints/, so resume discovery (which scans sessions/) can detect a program parent by the file's presence (cross-spec recovery).

func (ACPRuntimePaths) RuntimeDir

func (p ACPRuntimePaths) RuntimeDir() (string, error)

RuntimeDir returns the validated runtime root directory: .specd/runtime.

func (ACPRuntimePaths) SessionDir

func (p ACPRuntimePaths) SessionDir(sessionID string) (string, error)

SessionDir returns the validated directory for one session after checking sessionID is a well-formed opaque ID: sessions/<id>.

func (ACPRuntimePaths) SessionPath

func (p ACPRuntimePaths) SessionPath(sessionID string) (string, error)

SessionPath returns the validated path to a session's record: sessions/<id>/session.json.

func (ACPRuntimePaths) SessionsDir

func (p ACPRuntimePaths) SessionsDir() (string, error)

SessionsDir returns the validated directory holding all sessions: sessions/.

func (ACPRuntimePaths) WorkerDir

func (p ACPRuntimePaths) WorkerDir(sessionID, workerID string) (string, error)

WorkerDir returns the validated directory for one worker after checking workerID is a well-formed runtime segment: sessions/<id>/workers/<workerID>.

func (ACPRuntimePaths) WorkersDir

func (p ACPRuntimePaths) WorkersDir(sessionID string) (string, error)

WorkersDir returns the validated directory holding a session's workers: sessions/<id>/workers.

type ACPStore

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

ACPStore is the on-disk store for ACP session events: append-only, sequence-ordered, immutable event files plus the runtime paths used to locate them.

func NewACPStore

func NewACPStore(root string) (*ACPStore, error)

NewACPStore creates an ACPStore rooted at the project's ACP runtime directory under root.

func (*ACPStore) ArchiveSession

func (s *ACPStore) ArchiveSession(sessionID string, retention time.Duration) (ACPArchiveManifest, error)

ArchiveSession seals a terminal session's events into an immutable archive directory (writing it via a temp-dir-then-rename so the operation is atomic and idempotent — a pre-existing archive is returned as-is) and, when retention is non-positive, removes the live session directory afterward. It returns an error if the session has no events or its last event is not terminal.

func (*ACPStore) ClaimLease

func (s *ACPStore) ClaimLease(
	sessionID, workerID, spec, task string,
	attempt int,
	leaseDuration time.Duration,
	messageExpiresAt time.Time,
) (ACPLease, error)

ClaimLease acquires a new lease for the given worker on (spec, task, attempt), failing if another worker already holds in-flight work for that identity or for that (spec, task), or if attempt does not equal the next expected attempt number. The lease window is capped at messageExpiresAt. Runs under the session lock so the claim check and write are atomic.

func (*ACPStore) CleanupArchives

func (s *ACPStore) CleanupArchives(olderThan time.Time) ([]string, error)

CleanupArchives removes every session archive sealed before olderThan and returns the sorted list of session IDs that were removed. Archives with an unreadable or invalid manifest are skipped rather than treated as errors.

func (*ACPStore) ClearLease

func (s *ACPStore) ClearLease(sessionID, workerID string, attempt int) error

ClearLease removes a worker's lease record entirely after validating the caller still owns the active lease at the given attempt. Unlike ReleaseLease — which marks the lease released but keeps it in the ledger, so the next claim must use attempt+1 — clearing relinquishes the attempt slot itself, letting a fresh worker re-claim the SAME attempt. It is the cooperative-checkpoint hand-back: a checkpoint is a continuation, not a failed attempt, so it must not consume an attempt the way a crash/reclaim does (R1, R4).

func (*ACPStore) LoadCursor

func (s *ACPStore) LoadCursor(sessionID, workerID string) (ACPCursor, error)

LoadCursor reads and validates a worker's saved cursor for the session, returning a fresh zero-sequence ACPCursor (not an error) if none has been saved yet.

func (*ACPStore) LoadLease

func (s *ACPStore) LoadLease(sessionID, workerID string) (ACPLease, error)

LoadLease reads and validates the given worker's lease record for the session, returning errACPLeaseNotFound (wrapped) if no lease exists.

func (*ACPStore) ReadEvents

func (s *ACPStore) ReadEvents(sessionID, workerID string) ([]ACPEnvelope, error)

ReadEvents returns the session's events that are newer than workerID's saved cursor, deduplicated by message ID.

func (*ACPStore) ReleaseLease

func (s *ACPStore) ReleaseLease(sessionID, workerID string, attempt int) error

ReleaseLease marks a worker's lease as released after validating ownership at the given attempt; it is idempotent when the lease is already released at that same attempt. Unlike ClearLease, the released record stays in the ledger so a subsequent claim must use attempt+1.

func (*ACPStore) RenewLease

func (s *ACPStore) RenewLease(sessionID, workerID string, attempt int, leaseDuration time.Duration) (ACPLease, error)

RenewLease extends an actively-held lease's heartbeat and LeaseUntil window, after validating the caller still owns it at the given attempt. The new window is capped at the lease's MessageExpiresAt.

func (*ACPStore) ReplaySessionEvents

func (s *ACPStore) ReplaySessionEvents(sessionID string) ([]ACPEnvelope, error)

ReplaySessionEvents returns a session's events, preferring the live session store and falling back to its archive (if any) when the live store has none.

func (*ACPStore) ResumeLease

func (s *ACPStore) ResumeLease(sessionID, workerID string, attempt int, leaseDuration time.Duration) (ACPLease, time.Duration, error)

ResumeLease returns a suspended lease to `active`, refreshing the heartbeat and resetting LeaseUntil to a fresh normal window, and reports how long the lease was suspended (for the resume event). If the resume deadline already passed, or the lease was reclaimed, or the underlying message TTL expired, it returns errACPLeaseGone so the worker knows it must re-claim rather than silently continuing on a dead lease (Req 3.2).

func (*ACPStore) SaveCursor

func (s *ACPStore) SaveCursor(sessionID, workerID string, event ACPEnvelope) error

SaveCursor acknowledges one successfully reconciled event. Cursors may only move forward and must point at an existing event with a matching message ID.

func (*ACPStore) SuspendLease

func (s *ACPStore) SuspendLease(
	sessionID, workerID string,
	attempt int,
	reason string,
	resumeAfter, heartbeatBuffer, maxSuspend time.Duration,
) (ACPLease, error)

SuspendLease transitions a held lease to `suspended`, extending its life to a ResumeDeadline of now + resumeAfter + heartbeatBuffer without recording a failure (R3). The caller must still own the lease for `attempt` — active (the first suspend) or already suspended within window (a re-extend). Repeated suspends accumulate toward maxSuspend; a request that would exceed the cap is rejected and the lease is left to expire normally so a worker cannot hold a task forever by spamming suspend. Like the other ops it runs under the session lock so the CAS check and write are atomic.

func (*ACPStore) ValidateActiveLease

func (s *ACPStore) ValidateActiveLease(sessionID, workerID, spec, task string, attempt int) error

ValidateActiveLease is the terminal-report ownership gate. It rejects a released, expired, wrong-worker, or stale-attempt lease.

func (*ACPStore) WriteEvent

func (s *ACPStore) WriteEvent(envelope ACPEnvelope) (ACPEnvelope, error)

WriteEvent allocates the next session sequence and publishes an immutable event. Callers leave Sequence zero; the store is the sole sequence authority.

type AcceptanceGaps

type AcceptanceGaps struct {
	Unmet  []int    `json:"unmet"`
	Failed []string `json:"failed"`
}

AcceptanceGaps reports the acceptance-criteria gaps for a spec: requirement numbers with no passing acceptance record, and the keys of acceptance records that failed.

func GetAcceptanceGaps

func GetAcceptanceGaps(state *State, reqMd *string) AcceptanceGaps

GetAcceptanceGaps computes the AcceptanceGaps for state against reqMd: requirement numbers with no passing acceptance record, and the keys of any failed acceptance records.

type AdvanceTarget

type AdvanceTarget struct {
	Status SpecStatus
	Phase  Phase
}

AdvanceTarget is the status/phase pair a spec moves to when it advances past its current planning status.

type Annotation

type Annotation struct {
	Kind     AnnotationKind
	Evidence string // for complete
	Ts       string // for complete
	Reason   string // for blocked
}

Annotation is the parsed trailing annotation on a task line: a completion record (Evidence/Ts) or a blocked record (Reason), selected by Kind.

type AnnotationKind

type AnnotationKind string

AnnotationKind identifies the kind of trailing annotation appended to a task line, such as complete or blocked.

const (
	AnnotComplete AnnotationKind = "complete"
	AnnotBlocked  AnnotationKind = "blocked"
)

AnnotComplete and AnnotBlocked are the two recognized AnnotationKind values, marking a task as finished with evidence or stalled with a reason.

type ArtifactBrief

type ArtifactBrief struct {
	Artifact    string   `json:"artifact"`
	Gate        string   `json:"gate"`
	Constraints []string `json:"constraints"`
}

ArtifactBrief is the per-artifact slice of the brief: the gate constraints an author must satisfy for one file.

type AuthoringBrief

type AuthoringBrief struct {
	EarsForms      []string        `json:"earsForms"`
	DesignSections []string        `json:"designSections"`
	TaskKeys       []string        `json:"taskKeys"`
	Roles          []string        `json:"roles"`
	ReadonlyRoles  []string        `json:"readonlyRoles"`
	Artifacts      []ArtifactBrief `json:"artifacts"`
	Prompt         string          `json:"prompt,omitempty"`
}

AuthoringBrief is a gate-shaped description of what a faithful spec draft must contain. Every field is sourced from the same package vars the gates enforce (EarsForms, DesignSections, MandatoryKeys, ValidRoles) — never re-listed literals — so a faithful draft built from the brief passes `specd check`, and the brief cannot silently drift from the gates. It is a pure value: no network, no LLM, no IO.

func NewAuthoringBrief

func NewAuthoringBrief(prompt string) AuthoringBrief

NewAuthoringBrief builds the brief from live gate constraints. prompt is the optional originating `--from` text; pass "" to omit it.

func (AuthoringBrief) Text

func (b AuthoringBrief) Text() string

Text renders the brief as a human-readable authoring guide.

type AutoResumeCfg

type AutoResumeCfg struct {
	Enabled       bool `json:"enabled,omitempty"`
	OnHostStart   bool `json:"onHostStart,omitempty"`
	MaxAgeMinutes int  `json:"maxAgeMinutes,omitempty"`
}

AutoResumeCfg declares how a host should rediscover and continue sessions on startup (R5). Enabled is the master switch; OnHostStart asks the host adapter to auto-invoke resume when it boots; MaxAgeMinutes bounds how stale a session may be and still be auto-resumed (0 = no age filter).

type Badge

type Badge struct {
	Label string
	Color string
}

Badge is a display label and hex color used to render a spec's status in reports.

func GetBadge

func GetBadge(status SpecStatus) Badge

GetBadge maps a spec status to its display Badge, falling back to an "Unknown" badge for any status it does not recognize.

type Blocker

type Blocker struct {
	Task   string `json:"task"`
	Reason string `json:"reason"`
	Since  string `json:"since"`
}

Blocker records why a task is blocked: which task, the reason, and when it became blocked.

type BrainDirective

type BrainDirective struct {
	SessionID string
	WorkerID  string
	Spec      string
	TaskID    string
	Attempt   int
	Action    string
	Reason    string
	InReplyTo string
}

BrainDirective is the Brain's instruction back to a worker, recorded as an ACP directive event. InReplyTo links it to the message ID of the query or report it answers.

type CheckCtx

type CheckCtx struct {
	Root, Slug string
	ReqMd      *string
	Doc        *ParsedTasks
	State      *State
	Cfg        Config
}

CheckCtx is the shared, read-only context every check gate operates over. It is built once by the caller (RunCheck) and passed to each gate in turn.

type CheckGate

type CheckGate func(CheckCtx) (violations, warnings []Violation)

CheckGate is a pure check over CheckCtx returning the violations and warnings it found. Gates never mutate the context and never perform IO beyond reading the artifacts referenced by the context (design.md is read by GateDesign).

type CheckpointRecord

type CheckpointRecord struct {
	Version         int      `json:"version"`
	SessionID       string   `json:"sessionId"`
	Spec            string   `json:"spec"`
	TaskID          string   `json:"taskId"`
	Attempt         int      `json:"attempt"`
	WorkerID        string   `json:"workerId"`
	ProgressPercent int      `json:"progressPercent"`
	ContextManifest string   `json:"contextManifest,omitempty"`
	WorkingNotes    string   `json:"workingNotes,omitempty"`
	ChangedFiles    []string `json:"changedFiles,omitempty"`
	GitHead         string   `json:"gitHead,omitempty"`
	Reason          string   `json:"reason,omitempty"`
	CreatedAt       string   `json:"createdAt"`
}

CheckpointRecord is a worker's durable mid-task progress snapshot (R1). A worker writes one before shedding context (host /clear) or on a token warning, then exits gracefully; the Brain later prefers resuming from it over a fresh dispatch (see resume-from-checkpoint). The record carries everything a fresh worker needs to continue from ProgressPercent rather than rebuild from zero: the context manifest it was given, free-form working notes, the files it has already touched, and the git head it observed. All resume-payload fields are omitempty so a minimal checkpoint round-trips byte-stable.

func ForceCheckpointAll

func ForceCheckpointAll(root, sessionID, reason string, cfg OrchestrationCfg) ([]CheckpointRecord, error)

ForceCheckpointAll checkpoints every worker that currently holds an active lease in a session, so the host can shed all in-flight context (a /clear) without losing work (Req 3). Each recorded checkpoint carries the supplied reason, releases that worker's lease, and emits a checkpoint event — the same RecordCheckpoint path a worker uses voluntarily. Workers without progress detail are checkpointed at 0%, which still hands the task back for resume. Returns the records written; an empty slice (no error) when nothing is active.

func RecordCheckpoint

func RecordCheckpoint(root string, rec CheckpointRecord, cfg OrchestrationCfg) (CheckpointRecord, error)

RecordCheckpoint persists a worker's mid-task progress checkpoint, then hands the task back to the Brain by releasing the worker's lease and appending a `checkpoint` ACP event. It mirrors RecordPinkyProgress's discipline: the caller must still own an active lease for (session, worker, spec, task, attempt) — a forged or expired worker is rejected before anything is written (Req 2.3). The on-disk CheckpointRecord is written canonically so a later SenseOrchestration can resume from it byte-stably.

The sequence is write record -> append event -> clear lease. It is "atomic-ish": if the process dies between steps the worst case is a checkpoint file with a still-held lease, which the next lease expiry reconciles; no work is double-counted because the record is keyed on (task, attempt).

type CommandMeta

type CommandMeta struct {
	Command            string                  `json:"command"`
	Category           string                  `json:"category"`
	Description        string                  `json:"description"`
	Usage              string                  `json:"usage"`
	Synopsis           string                  `json:"synopsis"`
	LongDescription    string                  `json:"longDescription"`
	Flags              []FlagMeta              `json:"flags"`
	Positionals        []PositionalMeta        `json:"positionals,omitempty"`
	PhaseCompatibility *PhaseCompatibilityMeta `json:"phaseCompatibility,omitempty"`
	ModeCompatibility  *ModeCompatibilityMeta  `json:"modeCompatibility,omitempty"`
	ExitCodes          []ExitCodeMeta          `json:"exitCodes"`
	Examples           []string                `json:"examples"`
	Hidden             bool                    `json:"hidden,omitempty"`
	RemovedIn          string                  `json:"removedIn,omitempty"`
}

CommandMeta is the full metadata record for one CLI command: its usage, description, flags, positionals, compatibility constraints, exit codes, and examples. Commands is the registry of every CommandMeta.

type Commit

type Commit struct {
	SHA     string `json:"sha"`
	Subject string `json:"subject"`
}

Commit is the minimal view of a git commit the PR summary links against.

type CommitLink struct {
	SHA     string   `json:"sha"`
	Subject string   `json:"subject"`
	Tasks   []string `json:"tasks"`
}

CommitLink is one commit with the task IDs it references. A commit that references no task is still a CommitLink (Tasks empty) — unreferenced commits are listed, never dropped, so reviewers can see work that escaped the task graph.

func LinkCommits

func LinkCommits(commits []Commit) []CommitLink

LinkCommits maps each commit to the task IDs in its subject, preserving input order. The result is fully deterministic for a given input. Tasks is non-nil (empty slice) so JSON consumers never see null.

func UnreferencedCommits

func UnreferencedCommits(links []CommitLink) []CommitLink

UnreferencedCommits returns the links whose commit references no task.

type CompactionOutcome

type CompactionOutcome struct {
	Entry              ContextLedgerEntry
	SummaryFile        string
	PreEstimatedTokens int
}

CompactionOutcome reports a performed compaction: the appended ledger entry, the workspace-relative summary path the host can reference, and the pre-compaction estimate that motivated it (for observability).

func CompactOrchestrationSession

func CompactOrchestrationSession(root, slug, sessionID, reason string) (CompactionOutcome, error)

CompactOrchestrationSession performs a manual compaction (R3/R4): it validates the session is running, then renders + persists a phase summary and ledger checkpoint under the spec lock. It is the locked entrypoint the CLI calls; the decision engine calls performCompaction directly under its own lock.

type CompleteTaskRequest

type CompleteTaskRequest struct {
	Evidence   string
	Unverified bool
	Force      bool
	Idempotent bool
	Tokens     *int
	Cost       *string
}

CompleteTaskRequest carries the inputs for the single task-completion integrity path. Evidence/Unverified mirror the `specd task --status complete` flags; Force bypasses the awaiting-approval gate; Idempotent makes a re-completion of an already-complete task a no-op (used by the Pinky evidence reconciler so a duplicate worker report can never repeat a transition). Tokens/Cost are operator/host annotations recorded verbatim — specd never computes them.

type CompleteTaskResult

type CompleteTaskResult struct {
	Status          TaskStatus
	Evidence        string
	AlreadyComplete bool
}

CompleteTaskResult reports the outcome of CompleteTask. AlreadyComplete is true when an idempotent request found the task already complete and made no change.

func CompleteTask

func CompleteTask(root, slug, id string, req CompleteTaskRequest) (CompleteTaskResult, error)

CompleteTask is the one authoritative path that transitions a task to complete. Both `specd task --status complete` and the Pinky evidence reconciler call it, so there is exactly one place that enforces the dependency, verification, and approval-gate rules and one place that mutates state.json and tasks.md. It takes the spec lock itself; callers must not already hold it.

type Config

type Config struct {
	Version            int              `json:"version"`
	DefaultVerify      string           `json:"defaultVerify"`
	Report             ReportCfg        `json:"report"`
	Roles              RolesCfg         `json:"roles"`
	PromotionThreshold int              `json:"promotionThreshold"`
	Gates              GatesCfg         `json:"gates"`
	Verify             VerifyCfg        `json:"verify"`
	Orchestration      OrchestrationCfg `json:"orchestration"`
	MCP                MCPConfig        `json:"mcp"`
}

Config is the root shape of .specd/config.yml: project-wide defaults for verification, reporting, role hints, promotion gating, and the orchestration/MCP subsystems.

func LoadConfig

func LoadConfig(root string) Config

LoadConfig loads the effective config for root, discarding diagnostics.

type ConfigDiagnostic

type ConfigDiagnostic struct {
	Path     string `json:"path"`
	Source   string `json:"source,omitempty"`
	Layer    string `json:"layer,omitempty"`
	Field    string `json:"field,omitempty"`
	Severity string `json:"severity"`
	Message  string `json:"message"`
}

ConfigDiagnostic is a single config-loading or config-validation finding: where it came from (path/source/layer/field), its severity, and a human-readable message.

func LoadConfigFromPath

func LoadConfigFromPath(path string) (loadedConfigFile, []ConfigDiagnostic)

LoadConfigFromPath parses the config file at path (JSON or YAML, by extension) as a project-layer config and returns the parsed document along with any diagnostics produced while reading or parsing it.

func ValidateEffectiveConfig

func ValidateEffectiveConfig(cfg Config) []ConfigDiagnostic

ValidateEffectiveConfig checks the fully-merged Config for unsupported enum values and out-of-range fields (report format, subagent mode, gate modes, verify sandbox, max context tokens, orchestration config) and returns one error diagnostic per violation.

type ConfigLoadResult

type ConfigLoadResult struct {
	Diagnostics []ConfigDiagnostic `json:"diagnostics"`
	ProjectPath string             `json:"projectPath,omitempty"`
	GlobalPath  string             `json:"globalPath,omitempty"`
	Digest      string             `json:"digest"`
	Present     bool               `json:"present"`
}

ConfigLoadResult reports how config was loaded: the diagnostics collected along the way, the global/project file paths that were used (if any), a digest of the effective config contents, and whether any config file was present at all.

type ContextLedgerEntry

type ContextLedgerEntry struct {
	StepSequence       uint64 `json:"stepSequence"`
	Phase              Phase  `json:"phase"`
	Action             string `json:"action"` // dispatch | compact | approve | step
	EstimatedTokens    int    `json:"estimatedTokens"`
	HostReportedTokens int    `json:"hostReportedTokens,omitempty"`
	Budget             int    `json:"budget"`
	SoftCeiling        int    `json:"softCeiling"`
	Compacted          bool   `json:"compacted,omitempty"`
	CompactedAt        string `json:"compactedAt,omitempty"`
	Reason             string `json:"reason"` // phase-complete | budget-threshold | manual-clear
}

ContextLedgerEntry is one persistent record in a session's context ledger. It is the per-session counterpart of the ephemeral manifest token estimate: a pre-dispatch entry records the budget the manifest computed, a post-task entry records the host-reported actuals, and a compact entry marks a checkpoint where the host shed context. All entries together form the token high-water trail the Brain reasons over.

type ContextManifestTools

type ContextManifestTools struct {
	RequiredTools  []string `json:"requiredTools"`
	OptionalTools  []string `json:"optionalTools"`
	ForbiddenTools []string `json:"forbiddenTools"`
}

ContextManifestTools is the per-spec MCP tool policy declared in a spec's manifest.json (context-manifest spec C1). It is the most precise exposure layer, composed after the config/phase filter: required/optional define an allowlist, forbidden a hard exclude. Tool names use the MCP specd_*/brain_* namespace (post-composite), matching the names emitted on tools/list.

func LoadContextManifest

func LoadContextManifest(root, slug string) ContextManifestTools

LoadContextManifest reads a spec's manifest.json tool policy. It is read-only and deterministic (R6): a missing or malformed file yields an empty policy so the MCP server degrades to the config/phase plan rather than erroring. The policy lives under the optional top-level "contextManifest" key (spec §5.1), keeping manifest.json open to other future sections.

func (ContextManifestTools) Present

func (m ContextManifestTools) Present() bool

Present reports whether the manifest declared any tool policy. An absent or empty manifest leaves MCP exposure to the config/phase plan unchanged (R5).

type CostBrakeLevel

type CostBrakeLevel string

CostBrakeLevel is the deterministic enforcement state for host-reported session spend. Host costs are untrusted telemetry, but an operator-supplied limit still acts as a dispatch brake: warn at 80%, halt at 100%.

const (
	CostBrakeNone CostBrakeLevel = "none"
	CostBrakeWarn CostBrakeLevel = "warn"
	CostBrakeHalt CostBrakeLevel = "halt"
)

The CostBrakeLevel values are the three enforcement states EvaluateCostBrake can return, in increasing severity.

func EvaluateCostBrake

func EvaluateCostBrake(accumulatedUSD, limitUSD float64) CostBrakeLevel

EvaluateCostBrake compares accumulated host-reported cost to the configured limit. A limit <= 0 disables the brake. Callers validate finite/non-negative policy values before invoking this helper.

type Counts

type Counts struct {
	Pending  int `json:"pending"`
	Running  int `json:"running"`
	Complete int `json:"complete"`
	Blocked  int `json:"blocked"`
	Total    int `json:"total"`
}

Counts tallies tasks by status (pending, running, complete, blocked) plus the overall total, as reported by CountTasks.

func CountTasks

func CountTasks(state *State) Counts

CountTasks tallies the tasks in state by their Status into a Counts summary.

type Criterion

type Criterion struct {
	ID      string      // "<req>.<idx>", e.g. "1.2"
	Req     int         // requirement number
	Index   int         // 1-based position within the requirement's acceptance list
	Text    string      // criterion prose
	Line    int         // 1-based line in requirements.md
	Pattern EarsPattern // matched EARS pattern ("" when none matched)
	EarsOK  bool        // whether Text matched an EARS pattern
}

Criterion is a single acceptance criterion with a deterministic, mapping-stable ID. IDs are "<requirement>.<index>" where index is the 1-based position of the criterion within its requirement's acceptance list (matching the markdown ordinal in well-formed requirements.md).

func ExtractCriteria

func ExtractCriteria(text string) []Criterion

ExtractCriteria walks requirements.md and returns every acceptance criterion with a stable ID. It is read-only and side-effect free; the acceptance gate being off simply means callers never invoke it, so EARS linting and existing numbering are unaffected (T2: no change when the gate is off).

type CriterionRecord

type CriterionRecord struct {
	Requirement int    `json:"requirement"`
	Criterion   int    `json:"criterion"`
	Status      string `json:"status"` // "pass" | "fail"
	Evidence    string `json:"evidence"`
	RanAt       string `json:"ranAt"`
}

CriterionRecord is the recorded pass/fail evidence for one acceptance criterion (requirement.criterion) of a spec.

type CustomGateCfg

type CustomGateCfg struct {
	Name     string `json:"name"`
	Command  string `json:"command"`
	Severity string `json:"severity"`
	Sandbox  string `json:"sandbox,omitempty"`
}

CustomGateCfg declares one external custom gate. Command is run via the verify shell with a scrubbed env and bounded timeout; Severity ("warn"|"error", default "error") decides how its findings map into the check result.

Sandbox is the opt-in isolation backend for this gate's command, reusing the verify sandbox runner ("none" (default), "bwrap", "container"). The gate command is trusted operator input (not agent-authored), so it runs on the host with a scrubbed env by default; an operator who wants parity with verify's fail-closed sandbox sets this. An unavailable backend fails the gate closed.

type CustomGateFinding

type CustomGateFinding struct {
	Location string `json:"location"`
	Message  string `json:"message"`
}

CustomGateFinding is one problem reported by a custom gate.

type CustomGateInput

type CustomGateInput struct {
	Spec   string              `json:"spec"`
	Root   string              `json:"root"`
	Status string              `json:"status"`
	Tasks  []CustomGateTaskRef `json:"tasks"`
}

CustomGateInput is the JSON document written to a custom gate's stdin. It is a read-only snapshot of the spec; custom gates inspect it and emit findings. They never receive write access — the contract is data-in / findings-out.

func BuildCustomGateInput

func BuildCustomGateInput(root string, state *State) CustomGateInput

BuildCustomGateInput projects a spec's state into the read-only gate input.

type CustomGateOutput

type CustomGateOutput struct {
	Violations []CustomGateFinding `json:"violations"`
	Warnings   []CustomGateFinding `json:"warnings"`
}

CustomGateOutput is the JSON document a custom gate writes to stdout. Findings in Violations fail the gate; findings in Warnings are advisory. The runner applies the configured warn/error severity on top of this split.

func RunCustomGate

func RunCustomGate(parent context.Context, root, shell, command string, input CustomGateInput, timeout time.Duration, sandbox string) (CustomGateOutput, error)

RunCustomGate executes an external custom-gate command, passing input as JSON on stdin and parsing the gate's stdout as a CustomGateOutput. It mirrors the verify execution guarantees: a scrubbed environment, a bounded timeout, and a NUL-byte rejection. It loads no Go plugins and makes no network calls — the gate is an ordinary subprocess. Invalid JSON on stdout, a non-zero exit, or a timeout are all errors (the caller decides how a gate error maps to severity).

sandbox selects the isolation backend (A5 R2): the empty string or "none" runs the command directly on the host (historical, byte-identical behaviour); any other value routes the command through the shared verify sandbox runner for parity. The env scrub holds in BOTH modes — sandboxed runs receive the same ScrubbedEnv() — and an unavailable backend fails the gate closed, consistent with verify.

type CustomGateTaskRef

type CustomGateTaskRef struct {
	ID     string `json:"id"`
	Status string `json:"status"`
	Role   string `json:"role"`
	Wave   int    `json:"wave"`
}

CustomGateTaskRef is the read-only view of a task handed to a custom gate.

type DagTask

type DagTask struct {
	ID      string
	Wave    int
	Depends []string
	Status  TaskStatus
}

DagTask is a single task node in the task dependency graph, carrying its wave assignment, dependency ids, and current execution status.

func DagTasksFromState

func DagTasksFromState(state *State) []DagTask

DagTasksFromState converts state's tasks into the []DagTask form used by the DAG/critical-path algorithms (WaveGraph, NextRunnable, CriticalPath).

func RunnableFrontier

func RunnableFrontier(tasks []DagTask) []DagTask

RunnableFrontier returns every pending task whose dependencies are all complete, ordered by wave then ordinal.

type DriverDispatch

type DriverDispatch struct {
	Decision OrchestrationDecision
	Mission  PinkyMission
}

DriverDispatch is the unit handed to a worker: the dispatching decision and the fully built, claimable mission.

type DriverEvent

type DriverEvent struct {
	Event   string
	Session string
	Worker  string
	Task    string
	// Compaction fields are populated only on the "context.compact" event so the
	// host observer can log the token ledger checkpoint and run the real /clear.
	Phase              string
	Reason             string
	CompactionFile     string
	EstimatedTokens    int
	HostReportedTokens int
	Budget             int
}

DriverEvent is a stable operational event emitted at the driver boundary.

type DriverObserver

type DriverObserver func(DriverEvent)

DriverObserver receives best-effort operational events; it must not mutate orchestration state or stdout.

type DriverOptions

type DriverOptions struct {
	MaxSteps int
	MaxWaits int
	Worker   func(DriverDispatch) error
	Observer DriverObserver
}

DriverOptions configures a drive. Worker is the host callback that runs one mission to a reported terminal state; a nil Worker stops the loop at the first dispatch (host-wires-its-own-worker mode). MaxSteps bounds total iterations; MaxWaits bounds consecutive non-progress waits before the loop reports a stall (fail-closed rather than spin forever).

type DriverOutcome

type DriverOutcome string

DriverOutcome is the terminal reason a drive stopped.

const (
	DriverComplete   DriverOutcome = "complete"          // spec/session reached a terminal done state
	DriverEscalated  DriverOutcome = "escalated"         // a decision escalated to a human
	DriverAwaiting   DriverOutcome = "awaiting-approval" // a gate needs human approval
	DriverWorkerStop DriverOutcome = "worker-stop"       // dispatch with no Worker callback wired
	DriverMaxSteps   DriverOutcome = "max-steps"         // step budget exhausted
	DriverStalled    DriverOutcome = "stalled"           // too many consecutive waits — no progress
)

The DriverOutcome values name every terminal reason a drive loop can stop.

type DriverResult

type DriverResult struct {
	Steps   int                   `json:"steps"`
	Outcome DriverOutcome         `json:"outcome"`
	Final   OrchestrationDecision `json:"final"`
}

DriverResult reports how a drive ended.

func DriveOrchestration

func DriveOrchestration(root, slug, sessionID string, policy OrchestrationPolicy, cfg OrchestrationCfg, opts DriverOptions) (DriverResult, error)

DriveOrchestration runs the reference loop against an already-started session.

type EarsIssue

type EarsIssue struct {
	Line    int
	Message string
}

EarsIssue is a single requirements.md lint finding: the 1-based source line and a human-readable message describing the problem.

func LintEars

func LintEars(text string) []EarsIssue

LintEars scans requirements.md text for "## Requirement N" blocks and flags each one missing a **User story:** line, missing acceptance criteria, or containing a criterion that doesn't match any EARS pattern. It also flags the document as a whole when no requirement sections are found.

type EarsPattern

type EarsPattern string

EarsPattern identifies which EARS (Easy Approach to Requirements Syntax) form an acceptance criterion matched.

const (
	EarsUnwanted        EarsPattern = "unwanted"
	EarsEventDriven     EarsPattern = "event-driven"
	EarsStateDriven     EarsPattern = "state-driven"
	EarsOptionalFeature EarsPattern = "optional-feature"
	EarsUbiquitous      EarsPattern = "ubiquitous"
)

The EarsPattern values name the EARS forms MatchEars recognizes, in match-precedence order.

func MatchEars

func MatchEars(line string) (EarsPattern, bool)

MatchEars tests line against each known EARS pattern in precedence order and returns the first one that matches, or false if none do.

type ExitCodeMeta

type ExitCodeMeta struct {
	Code    int    `json:"code"`
	Meaning string `json:"meaning"`
}

ExitCodeMeta documents one possible process exit code for a command and what it means.

type FlagMeta

type FlagMeta struct {
	Name        string   `json:"name"`
	Type        string   `json:"type"`
	Description string   `json:"description,omitempty"`
	Enum        []string `json:"enum,omitempty"`
	Required    bool     `json:"required,omitempty"`
	Default     string   `json:"default,omitempty"`
}

FlagMeta describes one flag of a CLI command: its name, type, allowed values, default, and whether it is required, for help text and schema generation.

type FrontierDetector

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

FrontierDetector tracks the last frontier observed per spec and reports only real changes. It is the read-only heart of the watch daemon: feed it freshly loaded states and it emits a FrontierEvent exactly when the runnable set changes (including the first observation of a spec). Revision is monotonic (SaveState only ever bumps it): an unchanged revision short-circuits to the cached result, and an advanced revision triggers an incremental update that re-evaluates runnability only for tasks whose status changed and their direct dependents (see frontierFor), not the full task list. The detector also guards against emitting when the revision advanced but the frontier did not.

func NewFrontierDetector

func NewFrontierDetector() *FrontierDetector

NewFrontierDetector returns a detector with no prior observations.

func (*FrontierDetector) Observe

func (d *FrontierDetector) Observe(state *State) (FrontierEvent, bool)

Observe computes the current frontier for state and returns an event plus whether it represents a change since the last Observe for the same spec. The first observation of a spec always reports changed=true.

type FrontierEvent

type FrontierEvent struct {
	Spec     string   `json:"spec"`
	Revision int      `json:"revision"`
	Status   string   `json:"status"`
	Frontier []string `json:"frontier"`          // runnable task IDs, wave/ordinal order
	Added    []string `json:"added,omitempty"`   // entered the frontier since last event
	Removed  []string `json:"removed,omitempty"` // left the frontier since last event
	At       string   `json:"at"`
}

FrontierEvent is a single change in a spec's runnable frontier — the set of tasks whose dependencies are all complete and which are themselves pending. It is the unit the watch daemon emits. The event is purely derived from spec state; producing it never mutates anything.

type Gate

type Gate string

Gate represents whether a spec is blocked awaiting an explicit approval (e.g. `specd approve`) before it can proceed.

const (
	GateNone             Gate = "none"
	GateAwaitingApproval Gate = "awaiting-approval"
)

GateNone and GateAwaitingApproval are the two valid Gate values: no pending approval, or blocked until the operator approves.

type GatesCfg

type GatesCfg struct {
	Traceability string `json:"traceability"`
	Acceptance   string `json:"acceptance"`
	// Scope is the diff-scope gate severity: "off"/""/"*" = no-op, else
	// "warn"/"error". It flags verify-time changed files outside a task's
	// declared `files:` contract.
	Scope string `json:"scope"`
	// ContextBudget is the opt-in context-budget gate severity:
	// "off"/""/"*" = no-op (default), else "warn"/"error". When enabled it builds
	// the active spec's context manifest and flags it when the required-item token
	// estimate exceeds the effective budget, naming the heaviest items.
	ContextBudget string `json:"contextBudget"`
	// MaxContextTokens optionally caps the gate's effective budget (mirrors the MCP
	// capabilities.specd.maxContextTokens host hint). 0 = use the derived budget.
	MaxContextTokens int `json:"maxContextTokens"`
	// ModeCapability is the opt-in mode-capability gate severity:
	// "off"/""/"*" = no-op (default), else "warn"/"error". When enabled it flags a
	// spec recorded as orchestrated while the project lacks orchestration
	// capability (orchestration.enabled absent/false). Off by default keeps Base
	// projects clean.
	ModeCapability string `json:"modeCapability"`
	// Custom lists external, declarative custom gates run after the core
	// pipeline. Each is an ordinary subprocess (no Go plugin, no network).
	Custom []CustomGateCfg `json:"custom"`
}

GatesCfg configures the severity of each verify-time quality gate (traceability, acceptance, diff scope, context budget, mode capability) plus any external custom gates to run after the core pipeline.

type GlobalConfigScaffoldResult

type GlobalConfigScaffoldResult struct {
	Path    string `json:"path"`
	Created bool   `json:"created"`
}

GlobalConfigScaffoldResult reports the outcome of EnsureGlobalConfigScaffold: the global config path it used and whether it had to create the file.

func EnsureGlobalConfigScaffold

func EnsureGlobalConfigScaffold(readTemplate func(string) (string, error)) (GlobalConfigScaffoldResult, error)

EnsureGlobalConfigScaffold ensures a global config.yml exists, returning the path of the first existing candidate unchanged, or — if none exist — rendering the config.yml template (via readTemplate) to the first candidate path and reporting it as created.

type HandshakeActiveSpecMode

type HandshakeActiveSpecMode struct {
	Slug   string `json:"slug"`
	Status string `json:"status"`
	Phase  string `json:"phase"`
	Mode   string `json:"mode"`
	Origin string `json:"origin"`
	Gate   string `json:"gate"`
}

HandshakeActiveSpecMode summarizes one spec's status, phase, mode, mode origin, and gate state for the active-modes listing in a handshake bootstrap.

type HandshakeBootstrap

type HandshakeBootstrap struct {
	Version     int                       `json:"version"`
	Root        string                    `json:"root"`
	Load        []HandshakeLoadItem       `json:"load"`
	Commands    HandshakeCommandSummary   `json:"commands"`
	Config      HandshakeConfigSummary    `json:"config"`
	Health      HandshakeHealthSummary    `json:"health"`
	Modes       []HandshakeActiveSpecMode `json:"modes"`
	NextActions []string                  `json:"nextActions"`
}

HandshakeBootstrap is the JSON payload returned by `specd handshake bootstrap`, bundling the project root, the files an agent should load, command/config summaries, scaffold health, active spec modes, and recommended next actions.

func BuildHandshakeBootstrap

func BuildHandshakeBootstrap(root string, includeSchema bool) (HandshakeBootstrap, error)

BuildHandshakeBootstrap assembles the full handshake bootstrap payload for root (auto-discovering it when root is empty), including the command schema digest, config summary, scaffold health checks, active spec modes, and recommended next actions. includeSchema controls whether the full command schema is embedded in the result.

type HandshakeCommandSummary

type HandshakeCommandSummary struct {
	SchemaCommand string        `json:"schemaCommand"`
	Digest        string        `json:"digest"`
	Count         int           `json:"count"`
	Schema        []CommandMeta `json:"schema,omitempty"`
}

HandshakeCommandSummary summarizes the specd command schema (a digest and count, with the full schema optionally attached) so an agent can detect drift without always loading the entire schema.

type HandshakeConfigSummary

type HandshakeConfigSummary struct {
	Path                  string               `json:"path"`
	Digest                string               `json:"digest"`
	Roles                 HandshakeRolesConfig `json:"roles"`
	Orchestration         HandshakeOrchConfig  `json:"orchestration"`
	VerifySandbox         string               `json:"verifySandbox"`
	GateSeverities        map[string]string    `json:"gateSeverities"`
	ConfigFilePresent     bool                 `json:"configFilePresent"`
	EffectiveConfigSource string               `json:"effectiveConfigSource"`
}

HandshakeConfigSummary reports the effective specd config: its path and digest, role/orchestration settings, gate severities, and where the effective values were sourced from.

type HandshakeHealthCheck

type HandshakeHealthCheck struct {
	Name    string   `json:"name"`
	OK      bool     `json:"ok"`
	Missing []string `json:"missing"`
	Message string   `json:"message,omitempty"`
}

HandshakeHealthCheck reports the result of one scaffold health check, including any required files found missing.

type HandshakeHealthSummary

type HandshakeHealthSummary struct {
	OK     bool                   `json:"ok"`
	Checks []HandshakeHealthCheck `json:"checks"`
}

HandshakeHealthSummary reports overall scaffold health plus the individual checks that were run.

type HandshakeLoadItem

type HandshakeLoadItem struct {
	Path      string `json:"path"`
	Mode      string `json:"mode"`
	Rationale string `json:"rationale"`
}

HandshakeLoadItem describes a single file a handshake-aware agent should load, along with how to load it and why.

type HandshakeMCPExposure

type HandshakeMCPExposure struct {
	Expose               string   `json:"expose"`
	EssentialTools       []string `json:"essentialTools,omitempty"`
	IncludeMeta          bool     `json:"includeMeta"`
	IncludeOrchestration *bool    `json:"includeOrchestration,omitempty"`
}

HandshakeMCPExposure describes which MCP tools are exposed and how, mirroring the project's MCP exposure configuration.

type HandshakeOrchConfig

type HandshakeOrchConfig struct {
	Enabled        bool   `json:"enabled"`
	ApprovalPolicy string `json:"approvalPolicy"`
	WorkerMode     string `json:"workerMode"`
}

HandshakeOrchConfig holds the orchestration settings (enablement, approval policy, worker mode) exposed in a handshake summary.

type HandshakePolicy

type HandshakePolicy struct {
	Version              int                  `json:"version"`
	Root                 string               `json:"root"`
	SubagentMode         string               `json:"subagentMode"`
	OrchestrationEnabled bool                 `json:"orchestrationEnabled"`
	ApprovalPolicy       string               `json:"approvalPolicy"`
	WorkerMode           string               `json:"workerMode"`
	MaxWorkers           int                  `json:"maxWorkers"`
	MaxRetries           int                  `json:"maxRetries"`
	TimeoutSeconds       int                  `json:"timeoutSeconds"`
	VerifySandbox        string               `json:"verifySandbox"`
	GateSeverities       map[string]string    `json:"gateSeverities"`
	MCPExposure          HandshakeMCPExposure `json:"mcpExposure"`
	ConfigDigest         string               `json:"configDigest"`
	ConfigFilePresent    bool                 `json:"configFilePresent"`
	DigestMatch          *bool                `json:"digestMatch,omitempty"`
	ExpectedConfigDigest string               `json:"expectedConfigDigest,omitempty"`
	Spec                 *HandshakeSpecPolicy `json:"spec,omitempty"`
	Diagnostics          []ConfigDiagnostic   `json:"diagnostics"`
	Violations           []string             `json:"violations"`
	Recommendations      []string             `json:"recommendations"`
}

HandshakePolicy is the JSON payload returned by `specd handshake policy`, describing the effective orchestration/config policy plus any violations and recommendations, optionally narrowed to a single spec.

func BuildHandshakePolicy

func BuildHandshakePolicy(root, slug, expectDigest string) (HandshakePolicy, error)

BuildHandshakePolicy resolves the effective orchestration policy for root (auto-discovering it when root is empty) and, when slug is non-empty, layers in spec-specific mode/workflow guidance and violation checks. When expectDigest is non-empty it also records whether the loaded config digest matches it.

type HandshakeRolesConfig

type HandshakeRolesConfig struct {
	SubagentMode string `json:"subagentMode"`
}

HandshakeRolesConfig holds the subagent role configuration exposed in a handshake summary.

type HandshakeSpecPolicy

type HandshakeSpecPolicy struct {
	Slug            string `json:"slug"`
	SpecMode        string `json:"specMode"`
	ModeOrigin      string `json:"modeOrigin"`
	BrainAllowed    bool   `json:"brainAllowed"`
	BaseLoopAllowed bool   `json:"baseLoopAllowed"`
	Recommended     string `json:"recommendedCommandFamily"`
	NextCommand     string `json:"nextCommand"`
	PolicyViolation string `json:"policyViolation,omitempty"`
}

HandshakeSpecPolicy captures the resolved mode and allowed workflows (brain orchestration vs. base loop) for one spec within a HandshakePolicy.

type InitAction

type InitAction struct {
	Kind        string `json:"kind"`
	Target      string `json:"target"`
	Description string `json:"description"`
	Destructive bool   `json:"destructive"`
	Required    bool   `json:"required"`
	Template    string `json:"template"`
	Content     string `json:"-"`
}

InitAction is one planned filesystem action (write, merge, or skip) for a single scaffold asset, including whether it is destructive or required.

type InitAgentResults

type InitAgentResults struct {
	Detected   []string `json:"detected"`
	Configured []string `json:"configured"`
	Manual     []string `json:"manual"`
}

InitAgentResults buckets coding-agent hosts by outcome: passively detected, automatically configured, or requiring manual setup.

type InitExecutor

type InitExecutor struct {
	WriteFile   func(path, content string) error
	MergeAgents func(path, content string, force bool) error
	MkdirTemp   func(dir, pattern string) (string, error)
	Rename      func(oldPath, newPath string) error
	RemoveAll   func(path string) error
}

InitExecutor abstracts the filesystem operations ExecuteInitPlan performs, so tests can substitute fakes without touching the real disk.

func DefaultInitExecutor

func DefaultInitExecutor() InitExecutor

DefaultInitExecutor returns the production InitExecutor, wired to the real filesystem (AtomicWrite, MergeAgentsMD, and the os package primitives).

type InitFileResults

type InitFileResults struct {
	Written []string `json:"written"`
	Updated []string `json:"updated"`
	Skipped []string `json:"skipped"`
	Failed  []string `json:"failed"`
}

InitFileResults buckets the relative paths touched by an init run by outcome: newly written, updated (merged), skipped (preserved), or failed.

type InitNextAction

type InitNextAction struct {
	Kind string `json:"kind"`
	Text string `json:"text"`
}

InitNextAction is the single suggested follow-up step returned to the caller (typically the host agent) after init completes.

type InitOptions

type InitOptions struct {
	Root           string   `json:"root"`
	Force          bool     `json:"force"`
	Repair         bool     `json:"repair"`
	Refresh        bool     `json:"refresh"`
	DryRun         bool     `json:"dryRun"`
	Interactive    bool     `json:"interactive"`
	AgentSelection []string `json:"agentSelection"`
	Scope          string   `json:"scope"`
}

InitOptions are the user-supplied flags that drive PlanInit: which root to scaffold, which mutually-exclusive mode (force/repair/refresh) to run in, dry-run/interactive behavior, and agent/scope selection.

type InitPlan

type InitPlan struct {
	Root     string        `json:"root"`
	Mode     string        `json:"mode"`
	DryRun   bool          `json:"dryRun"`
	Fresh    bool          `json:"fresh"`
	Actions  []InitAction  `json:"actions"`
	Warnings []InitWarning `json:"warnings"`
}

InitPlan is the validated, write-free result of PlanInit: the resolved mode, whether this is a fresh (.specd/-absent) install, and the ordered list of actions to apply.

func PlanInit

func PlanInit(options InitOptions, assets []ScaffoldAsset, readTemplate func(string) (string, error)) (InitPlan, error)

PlanInit validates and resolves every template before returning actions. It reads project state but performs no writes.

type InitResult

type InitResult struct {
	SchemaVersion int                    `json:"schemaVersion"`
	Status        string                 `json:"status"`
	Root          string                 `json:"root"`
	Mode          string                 `json:"mode"`
	Files         InitFileResults        `json:"files"`
	Agents        InitAgentResults       `json:"agents"`
	Verification  InitVerificationResult `json:"verification"`
	Warnings      []InitWarning          `json:"warnings"`
	NextAction    InitNextAction         `json:"nextAction"`
}

InitResult is the complete, versioned outcome of an init run: status, file and agent results, MCP verification, warnings, and the next suggested action. It is the JSON document emitted under --json.

func ExecuteInitPlan

func ExecuteInitPlan(plan InitPlan, force bool, executor InitExecutor) InitResult

ExecuteInitPlan applies a previously computed InitPlan via executor, returning the resulting InitResult. A dry-run plan only categorizes actions without writing; a fresh-install plan stages and atomically renames the new .specd/ tree via executeFreshInitPlan; otherwise actions are applied in place, with the first failure on a required action stopping the run.

func NewInitResult

func NewInitResult(root string) InitResult

NewInitResult builds the default InitResult for the given root: schema version stamped, status "ready", empty file/agent buckets, an unattempted MCP verification, and the standard "create a spec" next action.

func (*InitResult) Normalize

func (r *InitResult) Normalize()

Normalize replaces any nil result slices with empty slices and sorts every file/agent bucket and the warnings list, so JSON output is deterministic regardless of the order actions were appended in.

type InitVerificationResult

type InitVerificationResult struct {
	MCP             string `json:"mcp"`
	ProtocolVersion string `json:"protocolVersion"`
	ToolCount       int    `json:"toolCount"`
}

InitVerificationResult reports the outcome of the in-process MCP handshake performed during init: the MCP status, negotiated protocol version, and discovered tool count.

type InitWarning

type InitWarning struct {
	Code    string `json:"code"`
	Message string `json:"message"`
}

InitWarning is a non-fatal, machine-readable issue surfaced by an init run, identified by a stable code and a human-readable message.

type LoadedSpec

type LoadedSpec struct {
	State *State
	Doc   ParsedTasks
}

LoadedSpec bundles a spec's persisted state with its parsed tasks.md.

func LoadSpec

func LoadSpec(root, slug string) (LoadedSpec, error)

LoadSpec loads a spec's state and tasks.md under the spec lock, reconciling state against the parsed tasks and persisting the result if reconciliation changed anything.

type MCPConfig

type MCPConfig struct {
	Expose               string   `json:"expose"`               // "all" (default) | "essential"
	EssentialTools       []string `json:"essentialTools"`       // commands/intents kept under expose:"essential"
	IncludeMeta          bool     `json:"includeMeta"`          // include install-mutating tools (update/uninstall/schema)
	IncludeOrchestration *bool    `json:"includeOrchestration"` // nil => derive from orchestration.enabled
}

MCPConfig tunes which tools the native MCP server advertises on tools/list. Its zero value is the backward-compatible contract: an absent `mcp` block leaves every field zero, which the tool builder treats as "expose everything" — byte-identical to pre-config output. IncludeOrchestration is a *bool so an unset value (nil) is distinguishable from an explicit false: nil means "derive from orchestration.enabled".

func (MCPConfig) Configured

func (m MCPConfig) Configured() bool

Configured reports whether an `mcp` block was supplied (any field set). A fully zero-value MCPConfig means no block was present, so the tool builder falls back to full passthrough for strict backward compatibility.

type MidreqSummary

type MidreqSummary struct {
	Turn   int
	Impact string
	Input  string
}

MidreqSummary is a condensed view of the most recent mid-spec requirement-change turn: its turn number, impact level, and the verbatim user input that triggered it.

func LatestMidreq

func LatestMidreq(root, slug string) *MidreqSummary

LatestMidreq reads root/slug's mid-requirements.md artifact and returns a summary of the most recent turn, or nil if the artifact is missing or has no parseable turn.

type ModeCompatibilityMeta

type ModeCompatibilityMeta struct {
	Modes                           []string `json:"modes,omitempty"`
	RequiresOrchestrationCapability bool     `json:"requiresOrchestrationCapability,omitempty"`
}

ModeCompatibilityMeta lists the execution modes a command supports and whether it requires project orchestration capability.

type ModeRecommendation

type ModeRecommendation struct {
	Recommended string      `json:"recommended"` // "simple" | "orchestrated"
	Confidence  string      `json:"confidence"`  // "neutral" | "suggest" | "strong"
	Signals     ModeSignals `json:"signals"`
	Rationale   string      `json:"rationale"`
	UserDecides bool        `json:"userDecides"`
}

ModeRecommendation is the advisory verdict. UserDecides is always true: the recommendation is input to a human choice, never an automatic action.

func RecommendMode

func RecommendMode(root, slug string) (ModeRecommendation, error)

RecommendMode computes the advisory recommendation for a spec. It reads the task DAG from state, cross-spec edges from the program manifest (if any), and a token estimate from the spec's planning artifacts. Before tasks.md has been parsed into the DAG (TaskCount == 0) the verdict is neutral — there is nothing to measure, so the host should reason from the prose instead.

type ModeSignals

type ModeSignals struct {
	TaskCount       int `json:"taskCount"`
	MaxWaveWidth    int `json:"maxWaveWidth"`
	DistinctRoles   int `json:"distinctRoles"`
	CrossSpecEdges  int `json:"crossSpecEdges"`
	EstimatedTokens int `json:"estimatedTokens"`
}

ModeSignals are the raw countable facts the recommendation is derived from.

type NextResult

type NextResult struct {
	Kind     NextResultKind `json:"kind"`
	ID       string         `json:"id,omitempty"`
	Blocked  []string       `json:"blocked,omitempty"`
	Blocking []string       `json:"blocking,omitempty"`
}

NextResult is the outcome of NextRunnable: the Kind discriminates whether ID names the next runnable task, or Blocked/Blocking list the task ids involved in a blocked or waiting state.

func NextRunnable

func NextRunnable(tasks []DagTask) NextResult

NextRunnable selects the next task to run from tasks, preferring the lowest-wave, lowest-ordinal pending task whose dependencies are all complete. It reports NextAllComplete, NextAllBlocked, or NextWaiting when no task is currently runnable.

type NextResultKind

type NextResultKind string

NextResultKind identifies what NextRunnable found: a runnable task, that all tasks are complete, that all remaining tasks are blocked, or that remaining tasks are merely waiting on incomplete dependencies.

const (
	NextTask        NextResultKind = "task"
	NextAllComplete NextResultKind = "all-complete"
	NextAllBlocked  NextResultKind = "all-blocked"
	NextWaiting     NextResultKind = "waiting"
)

Possible NextResultKind values returned by NextRunnable.

type OrchestrationAction

type OrchestrationAction string

OrchestrationAction is the action a DecideOrchestration call selects for one orchestration step (dispatch a task, wait, escalate, etc.).

const (
	OrchestrationIdle            OrchestrationAction = "idle"
	OrchestrationRequestApproval OrchestrationAction = "request-approval"
	OrchestrationDispatch        OrchestrationAction = "dispatch"
	OrchestrationDispatchAuthor  OrchestrationAction = "dispatch-authoring"
	OrchestrationAdvancePhase    OrchestrationAction = "advance-phase"
	OrchestrationWait            OrchestrationAction = "wait"
	OrchestrationRetry           OrchestrationAction = "retry"
	OrchestrationCancel          OrchestrationAction = "cancel"
	OrchestrationReplan          OrchestrationAction = "replan"
	OrchestrationEscalate        OrchestrationAction = "escalate"
	OrchestrationCompleteSession OrchestrationAction = "complete-session"
	// OrchestrationCompact instructs the host to shed conversation context
	// (a `/clear`) between planning ratchets or under token pressure. It is an
	// effecting decision with no worker dispatch: the engine writes a phase
	// summary and a ledger checkpoint; the host performs the real clear.
	OrchestrationCompact OrchestrationAction = "compact"
	// OrchestrationResume tells the host to dispatch a runnable task by handing
	// the worker its prior mid-task checkpoint instead of a fresh brief, so the
	// worker continues from the recorded progress rather than rebuilding from
	// zero (R1, R4). It is a dispatch with a resume payload: same (taskId,
	// attempt) as a fresh dispatch, but the Brain has observed a matching
	// checkpoint and no active lease.
	OrchestrationResume OrchestrationAction = "resume-from-checkpoint"
)

Orchestration actions the engine can decide on for a session step.

type OrchestrationAuthoring

type OrchestrationAuthoring struct {
	WorkID   string   `json:"workId"`   // reserved authoring ID (A1/A2/A3)
	Artifact string   `json:"artifact"` // e.g. "requirements.md"
	Gate     string   `json:"gate"`     // gate the artifact must clear
	Role     string   `json:"role"`     // worker role for the mission
	Issues   []string `json:"issues"`   // current `specd check`-shaped reasons
}

OrchestrationAuthoring is a synthetic, single authoring work item describing the phase artifact a worker must produce to clear the current planning gate. Planning is sequential (one artifact at a time), so the frontier is at most one item.

type OrchestrationCfg

type OrchestrationCfg struct {
	Enabled                  bool    `json:"enabled"`
	ApprovalPolicy           string  `json:"approvalPolicy"`
	WorkerMode               string  `json:"workerMode"`
	MaxWorkers               int     `json:"maxWorkers"`
	MaxRetries               int     `json:"maxRetries"`
	SessionTimeoutMinutes    int     `json:"sessionTimeoutMinutes"`
	HostReportedCostLimitUSD float64 `json:"hostReportedCostLimitUSD"`
	// CompactionPolicy / CompactionBudgetThreshold drive stage-aware context
	// compaction (none|phase|budget|both; threshold in [0,1]). omitempty keeps
	// pre-compaction config files byte-identical.
	CompactionPolicy          string       `json:"compactionPolicy,omitempty"`
	CompactionBudgetThreshold float64      `json:"compactionBudgetThreshold,omitempty"`
	Transport                 TransportCfg `json:"transport"`
	Program                   ProgramCfg   `json:"program"`
	// Resilience holds opt-in checkpoint/auto-resume policy. Pointer + omitempty
	// keeps existing config.json byte-identical when the block is absent.
	Resilience *ResilienceCfg `json:"resilience,omitempty"`
}

OrchestrationCfg configures the worker orchestration subsystem: whether it is enabled, approval and worker-mode policy, worker/retry limits, timeouts, cost caps, context-compaction policy, transport, program, and resilience settings.

func (OrchestrationCfg) EffectiveMaxSuspendSeconds

func (cfg OrchestrationCfg) EffectiveMaxSuspendSeconds() int

EffectiveMaxSuspendSeconds resolves the cumulative-suspension cap, applying the built-in default when the config block is absent or leaves the field unset.

type OrchestrationCheckpointSnapshot

type OrchestrationCheckpointSnapshot struct {
	TaskID          string `json:"taskId"`
	Attempt         int    `json:"attempt"`
	ProgressPercent int    `json:"progressPercent"`
}

OrchestrationCheckpointSnapshot is the snapshot projection of one persisted CheckpointRecord: just the keys DecideOrchestration needs to match a runnable task (taskId, attempt) plus the recorded progress for the decision reason. It carries no secrets and no resume payload — the worker brief loads that from the record on dispatch.

type OrchestrationDecision

type OrchestrationDecision struct {
	Version int                 `json:"version"`
	Action  OrchestrationAction `json:"action"`
	Spec    string              `json:"spec"`
	TaskID  string              `json:"taskId,omitempty"`
	Attempt int                 `json:"attempt,omitempty"`
	// Artifact is set on dispatch-authoring / advance-phase decisions: the
	// planning artifact (e.g. "design.md") the decision concerns.
	Artifact       string                  `json:"artifact,omitempty"`
	Reason         string                  `json:"reason"`
	IdempotencyKey string                  `json:"idempotencyKey"`
	Escalation     OrchestrationEscalation `json:"escalation"`
}

OrchestrationDecision is the result of DecideOrchestration: the action to take, the task/artifact it concerns, the reason and idempotency key for the effecting host, and any escalation detail.

func DecideOrchestration

func DecideOrchestration(snapshot OrchestrationSnapshot, policy OrchestrationPolicy) (OrchestrationDecision, error)

DecideOrchestration is the pure per-spec decision function: given a validated OrchestrationSnapshot and OrchestrationPolicy it walks an ordered set of checks — terminal/blocked status, human approval gates, exhausted retries, cost brake, session expiry, compaction triggers, worker capacity, task dispatch/resume, authoring, and phase advancement — and returns the single resulting OrchestrationDecision with a deterministic idempotency key.

type OrchestrationEscalation

type OrchestrationEscalation struct {
	Code    OrchestrationEscalationCode `json:"code"`
	Message string                      `json:"message"`
}

OrchestrationEscalation carries the code and human-readable message for a decision that escalates rather than acts.

type OrchestrationEscalationCode

type OrchestrationEscalationCode string

OrchestrationEscalationCode classifies why a decision escalated to a human or terminal failure state.

const (
	EscalationNone              OrchestrationEscalationCode = "none"
	EscalationUnknownState      OrchestrationEscalationCode = "unknown-state"
	EscalationInvalidGraph      OrchestrationEscalationCode = "invalid-graph"
	EscalationConflictingLease  OrchestrationEscalationCode = "conflicting-lease"
	EscalationCASExhausted      OrchestrationEscalationCode = "cas-exhausted"
	EscalationPolicyViolation   OrchestrationEscalationCode = "policy-violation"
	EscalationRetriesExhausted  OrchestrationEscalationCode = "retries-exhausted"
	EscalationHumanIntervention OrchestrationEscalationCode = "human-intervention"
)

Orchestration escalation codes.

type OrchestrationFailure

type OrchestrationFailure struct {
	TaskID    string `json:"taskId"`
	Attempt   int    `json:"attempt"`
	Kind      string `json:"kind"`
	Message   string `json:"message"`
	Retryable bool   `json:"retryable"`
}

OrchestrationFailure is the snapshot projection of one recent task failure: which task/attempt failed, the failure kind and message, and whether it is retryable.

type OrchestrationLeaseSnapshot

type OrchestrationLeaseSnapshot struct {
	WorkerID   string `json:"workerId"`
	TaskID     string `json:"taskId"`
	Attempt    int    `json:"attempt"`
	LeaseUntil string `json:"leaseUntil"`
	// Suspended marks a lease that is rate-limited/away but within its resume
	// window (R3). It is still in-flight (counts toward MaxWorkers) but is not a
	// heartbeating active worker. omitempty keeps non-suspended snapshots
	// byte-identical to the pre-resilience shape.
	Suspended bool `json:"suspended,omitempty"`
}

OrchestrationLeaseSnapshot is the snapshot projection of one active worker lease: which worker holds which task/attempt and until when.

type OrchestrationPolicy

type OrchestrationPolicy struct {
	ApprovalPolicy           string  `json:"approvalPolicy"`
	MaxWorkers               int     `json:"maxWorkers"`
	MaxRetries               int     `json:"maxRetries"`
	SessionTimeoutSeconds    int     `json:"sessionTimeoutSeconds"`
	HostReportedCostLimitUSD float64 `json:"hostReportedCostLimitUSD"`
	// CompactionPolicy selects automatic context compaction: none|phase|budget|
	// both. Empty is treated as none. omitempty keeps existing session.json
	// byte-identical.
	CompactionPolicy string `json:"compactionPolicy,omitempty"`
	// CompactionBudgetThreshold is the fraction of the manifest budget at which
	// budget-driven compaction fires, in [0,1]. omitempty keeps the zero value
	// (no budget trigger) out of byte-stable session.json.
	CompactionBudgetThreshold float64 `json:"compactionBudgetThreshold,omitempty"`
	// CheckpointEnabled mirrors resilience.checkpointEnabled into the policy so
	// the pure (snapshot, policy) decision path can gate resume-from-checkpoint
	// without reading config. omitempty keeps existing session.json byte-stable
	// when the resilience block is absent.
	CheckpointEnabled bool `json:"checkpointEnabled,omitempty"`
}

OrchestrationPolicy holds the validated, effective policy knobs governing one orchestration session: approval gating, worker/retry limits, the session timeout, the host-reported cost ceiling, and compaction/checkpoint behavior.

func NewOrchestrationPolicy

func NewOrchestrationPolicy(cfg OrchestrationCfg) (OrchestrationPolicy, error)

NewOrchestrationPolicy validates cfg and builds the effective OrchestrationPolicy it describes, converting the configured session timeout from minutes to seconds and deriving CheckpointEnabled from the resilience block.

type OrchestrationSession

type OrchestrationSession struct {
	Version      int                        `json:"version"`
	SessionID    string                     `json:"sessionId"`
	Spec         string                     `json:"spec"`
	Owner        string                     `json:"owner"`
	Status       OrchestrationSessionStatus `json:"status"`
	Policy       OrchestrationPolicy        `json:"policy"`
	CreatedAt    string                     `json:"createdAt"`
	UpdatedAt    string                     `json:"updatedAt"`
	ExpiresAt    string                     `json:"expiresAt"`
	LastSequence uint64                     `json:"lastSequence"`
	// ContextLedger is the persistent token-accounting trail (R1). LastCompactionStep
	// is the snapshot revision at which the most recent compaction fired, guarding
	// the phase-boundary trigger from re-emitting. PeakTokens is the high-water mark
	// across estimated and host-reported entries. All omitempty so pre-ledger
	// session.json round-trips byte-identical.
	ContextLedger      []ContextLedgerEntry `json:"contextLedger,omitempty"`
	LastCompactionStep uint64               `json:"lastCompactionStep,omitempty"`
	PeakTokens         int                  `json:"peakTokens,omitempty"`
}

OrchestrationSession is the persisted, durable record of one orchestration session: identity, owner, status, effective policy, lifecycle timestamps, and the running sequence/ledger state used to drive future decisions.

func ActiveOrchestrationSessionForSpec

func ActiveOrchestrationSessionForSpec(root, slug string) (*OrchestrationSession, error)

ActiveOrchestrationSessionForSpec returns the running/paused/cancelling session for a spec, or nil if none. It lets a driver resume an in-flight session instead of failing closed against the one-session-per-spec rule.

func CancelOrchestration

func CancelOrchestration(root, sessionID string) (OrchestrationSession, error)

CancelOrchestration moves a session into cooperative cancellation. The actual directives are issued by subsequent steps; this only records intent and is idempotent (cancelling→cancelling). Terminal sessions cannot be cancelled.

func LoadOrchestrationSession

func LoadOrchestrationSession(root, sessionID string) (OrchestrationSession, error)

LoadOrchestrationSession reads and validates session.json, returning errOrchestrationSessionNotFound when none exists yet.

func PauseOrchestration

func PauseOrchestration(root, sessionID string) (OrchestrationSession, error)

PauseOrchestration suspends new dispatch. It is idempotent (paused→paused) and refuses to pause a cancelling or terminal session.

func RecoverOrchestration

func RecoverOrchestration(root, sessionID string) (OrchestrationSession, error)

RecoverOrchestration rebuilds session state purely from on-disk session.json and the committed event log. It reconciles LastSequence to the actual event count and re-persists only when it changed, so recovering at any event boundary converges to the same state and recovering twice is a no-op.

func ResumeOrchestration

func ResumeOrchestration(root, sessionID string) (OrchestrationSession, error)

ResumeOrchestration restores dispatch. It is idempotent (running→running) and refuses to resume a cancelling or terminal session.

func StartOrchestrationSession

func StartOrchestrationSession(root, slug, sessionID, owner string, policy OrchestrationPolicy) (OrchestrationSession, error)

StartOrchestrationSession persists a new running session under session.json. It fails closed if a session with the same ID already exists, so a session is created exactly once and recovery has a single source of truth.

type OrchestrationSessionStatus

type OrchestrationSessionStatus string

OrchestrationSessionStatus is the lifecycle status of an orchestration session.

const (
	OrchestrationSessionRunning    OrchestrationSessionStatus = "running"
	OrchestrationSessionPaused     OrchestrationSessionStatus = "paused"
	OrchestrationSessionCancelling OrchestrationSessionStatus = "cancelling"
	OrchestrationSessionComplete   OrchestrationSessionStatus = "complete"
	OrchestrationSessionFailed     OrchestrationSessionStatus = "failed"
)

Orchestration session lifecycle statuses.

type OrchestrationSnapshot

type OrchestrationSnapshot struct {
	Version          int                          `json:"version"`
	SessionID        string                       `json:"sessionId"`
	Spec             string                       `json:"spec"`
	Revision         int                          `json:"revision"`
	Status           SpecStatus                   `json:"status"`
	Phase            Phase                        `json:"phase"`
	Gate             Gate                         `json:"gate"`
	HumanOnlyGate    bool                         `json:"humanOnlyGate"`
	Runnable         []OrchestrationTaskSnapshot  `json:"runnable"`
	ActiveLeases     []OrchestrationLeaseSnapshot `json:"activeLeases"`
	RecentFailures   []OrchestrationFailure       `json:"recentFailures"`
	SessionExpiresAt string                       `json:"sessionExpiresAt"`
	// Authoring is the planning-phase frontier: present when the spec is in a
	// planning status (requirements/design/tasks) and the phase artifact is
	// absent or fails `specd check`. It is the authoring counterpart of
	// Runnable for the execution DAG. PlanningReady is true when the spec is in
	// a planning status and the current artifact already passes its gate (the
	// phase is ready to advance).
	Authoring     *OrchestrationAuthoring `json:"authoring,omitempty"`
	PlanningReady bool                    `json:"planningReady"`
	// AccumulatedCostUSD is the sum of host-reported cost across the session's
	// evidence events. It is hostReported and untrusted — it never gates
	// completion — but it drives the advisory cost-limit escalation (GAP-4).
	AccumulatedCostUSD float64 `json:"accumulatedCostUSD"`
	// SessionExpired is true when the session's fixed wall-clock deadline
	// (session.ExpiresAt, set at start from sessionTimeoutSeconds) has passed.
	// It forces a terminal escalation rather than relying on lease expiry alone.
	SessionExpired bool `json:"sessionExpired"`
	// LastCompactionStep, LedgerEstimatedTokens, and LedgerBudget carry the
	// compaction inputs DecideOrchestration needs without breaking its pure
	// (snapshot, policy) signature: they are read from the persisted session's
	// ledger tail in SenseOrchestration. omitempty keeps snapshots without a
	// session (plain-controller mode) byte-identical to the pre-compaction shape.
	LastCompactionStep    uint64 `json:"lastCompactionStep,omitempty"`
	LedgerEstimatedTokens int    `json:"ledgerEstimatedTokens,omitempty"`
	LedgerBudget          int    `json:"ledgerBudget,omitempty"`
	// Checkpoints carries the mid-task checkpoints that survive on disk for this
	// session, projected so DecideOrchestration can prefer resume-from-checkpoint
	// over a fresh dispatch while staying a pure (snapshot, policy) function.
	// SenseOrchestration populates it only when resilience.checkpointEnabled is
	// set; omitempty keeps snapshots byte-identical to today when the feature is
	// off or no checkpoint exists.
	Checkpoints []OrchestrationCheckpointSnapshot `json:"checkpoints,omitempty"`
	// MostRecentProgressAt is the newest server-side progress-report time among
	// the session's in-flight workers (those holding an active lease). The driver
	// reads it to weight stall waits: a worker progressing within
	// resilience.progressTimeoutSeconds does not advance the consecutive-wait
	// counter (R6). It enters via SenseOrchestration, never via a clock read in
	// the pure decision. omitempty keeps snapshots byte-identical when no in-flight
	// worker has reported.
	MostRecentProgressAt string `json:"mostRecentProgressAt,omitempty"`
}

OrchestrationSnapshot is the pure, point-in-time view of a session's state that DecideOrchestration reasons over: lifecycle status, runnable tasks, active leases, recent failures, authoring frontier, and the compaction/checkpoint/cost inputs needed to decide the next action.

func SenseOrchestration

func SenseOrchestration(root, slug, sessionID string, policy OrchestrationPolicy) (OrchestrationSnapshot, error)

SenseOrchestration builds the current OrchestrationSnapshot for a spec session: it loads spec state, computes the runnable frontier and recent failures, gathers active (including suspended-but-resumable) worker leases, surfaces host-reported cost and session-expiry signals, carries forward compaction-ledger state, and — when checkpointing is enabled — attaches any resumable checkpoints, before validating the result.

type OrchestrationStepResult

type OrchestrationStepResult struct {
	Snapshot   OrchestrationSnapshot `json:"snapshot"`
	Decision   OrchestrationDecision `json:"decision"`
	Event      *ACPEnvelope          `json:"event,omitempty"`
	Compaction *CompactionOutcome    `json:"compaction,omitempty"`
}

OrchestrationStepResult is the outcome of one StepOrchestration call: the sensed snapshot, the decision made, the ACP event emitted (if any), and a compaction outcome when the decision triggered context compaction.

func StepOrchestration

func StepOrchestration(root, slug, sessionID string, policy OrchestrationPolicy, cfg OrchestrationCfg) (OrchestrationStepResult, error)

StepOrchestration runs one sense-decide-act cycle of the Brain orchestration loop for a spec under the spec lock: it senses the current snapshot, branches on the session's persisted lifecycle (paused/cancelling/terminal/ running), and otherwise decides and records the next orchestration decision — dispatching work, advancing the planning phase, triggering compaction, or completing/escalating the session as appropriate.

type OrchestrationTaskSnapshot

type OrchestrationTaskSnapshot struct {
	ID       string     `json:"id"`
	Wave     int        `json:"wave"`
	Status   TaskStatus `json:"status"`
	Attempt  int        `json:"attempt"`
	Role     string     `json:"role"`
	Depends  []string   `json:"depends"`
	Verified bool       `json:"verified"`
}

OrchestrationTaskSnapshot is the snapshot projection of one runnable task in the execution DAG: its identity, wave, status, attempt count, role, and unmet dependencies.

type PRSummary

type PRSummary struct {
	Spec       string          `json:"spec"`
	Title      string          `json:"title"`
	Status     string          `json:"status"`
	GatesOK    bool            `json:"gatesOk"`
	TasksDone  int             `json:"tasksDone"`
	TasksTotal int             `json:"tasksTotal"`
	Waves      []PRSummaryWave `json:"waves"`
	Violations []Violation     `json:"violations"`
	Warnings   []Violation     `json:"warnings"`
	Commits    []CommitLink    `json:"commits,omitempty"`
}

PRSummary is a deterministic, network-free snapshot of a spec suitable for a pull-request comment: gate status, wave/task progress, and (optionally) the commit↔task link map. It is derived purely from in-process data — no GitHub API, no network.

func BuildPRSummary

func BuildPRSummary(state *State, violations, warnings []Violation, commits []CommitLink) PRSummary

BuildPRSummary assembles a PRSummary from spec state, the gate result, and an optional commit-link map. Passing nil commits omits the commit section.

func (PRSummary) Markdown

func (s PRSummary) Markdown() string

Markdown renders the summary as a GitHub-flavored Markdown comment. Output is a pure function of the PRSummary value — identical input yields identical bytes.

type PRSummaryTask

type PRSummaryTask struct {
	ID     string `json:"id"`
	Title  string `json:"title"`
	Status string `json:"status"`
	Role   string `json:"role"`
}

PRSummaryTask is one task row in a PR summary.

type PRSummaryWave

type PRSummaryWave struct {
	Wave  int             `json:"wave"`
	Tasks []PRSummaryTask `json:"tasks"`
}

PRSummaryWave groups tasks by wave for the summary's DAG view.

type ParsedTask

type ParsedTask struct {
	ID         string
	Title      string
	Wave       int
	Checked    bool
	Meta       map[string]string
	Annotation *Annotation
	Line       int
}

ParsedTask is a single task entry parsed from tasks.md, including its id, title, wave, checked state, metadata fields, optional annotation, and source line number.

func FindTask

func FindTask(doc ParsedTasks, id string) *ParsedTask

FindTask returns a pointer to the task with the given id within doc, or nil if no task with that id exists.

type ParsedTasks

type ParsedTasks struct {
	Title string
	Tasks []ParsedTask
}

ParsedTasks is the full parsed contents of a tasks.md file: its title and the ordered list of tasks it contains.

func ParseTasks

func ParseTasks(text string) (ParsedTasks, error)

ParseTasks parses the full contents of a tasks.md file into a ParsedTasks value, validating wave headers, task id uniqueness, and the presence of mandatory/known metadata keys, and returning a GateError describing the first violation found.

func ParseTasksMd

func ParseTasksMd(root, slug string) (ParsedTasks, error)

ParseTasksMd reads and parses a spec's tasks.md, returning an empty ParsedTasks (titled by slug) if the file is missing or blank.

type Phase

type Phase = spec.Phase

Phase is a re-export of spec.Phase; see the SpecStatus alias comment above for why core re-declares it.

type PhaseCompatibilityMeta

type PhaseCompatibilityMeta struct {
	Statuses []string `json:"statuses,omitempty"`
	Phases   []string `json:"phases,omitempty"`
}

PhaseCompatibilityMeta lists the spec statuses and phases under which a command is valid to run.

type PinkyBlockerReport

type PinkyBlockerReport struct {
	SessionID string
	WorkerID  string
	Spec      string
	TaskID    string
	Attempt   int
	Reason    string
}

PinkyBlockerReport is a worker's report that a task attempt is blocked, with a free-form reason, recorded as an ACP blocked event.

type PinkyClaim

type PinkyClaim struct {
	Mission PinkyMission `json:"mission"`
	Lease   ACPLease     `json:"lease"`
}

PinkyClaim pairs a mission with the lease a worker acquired for it.

func ClaimPinkyMission

func ClaimPinkyMission(root string, mission PinkyMission, cfg OrchestrationCfg) (PinkyClaim, error)

ClaimPinkyMission validates a mission and acquires a lease for it in the ACP store, returning both as a PinkyClaim.

type PinkyEvidenceResult

type PinkyEvidenceResult struct {
	Event      ACPEnvelope        `json:"event"`
	Completion CompleteTaskResult `json:"completion"`
}

PinkyEvidenceResult reports the outcome of reconciling a worker's terminal report: the immutable ACP evidence event that was recorded and the completion result from the existing task-integrity path.

func ReconcilePinkyEvidence

func ReconcilePinkyEvidence(root string, report PinkyTerminalReport, cfg OrchestrationCfg) (PinkyEvidenceResult, error)

ReconcilePinkyEvidence turns an untrusted worker terminal report into a real task completion — but only through specd's own integrity paths. It records the report as an immutable ACP event (lease-gated, idempotent), then accepts it only when it references the matching specd-generated verification record, the git head and declared file scope agree, and the role is permitted. Completion itself runs through core.CompleteTask, the same path `specd task --status complete` uses, so Pinky never becomes a second verification or completion mechanism (R4.6, R4.7, R4.8, R4.14). It is idempotent: a duplicate report re-records nothing and re-completes nothing.

type PinkyInbox

type PinkyInbox struct {
	SessionID  string        `json:"sessionId"`
	WorkerID   string        `json:"workerId"`
	Directives []ACPEnvelope `json:"directives"`
}

PinkyInbox is the set of directives and other ACP events addressed to one worker, read back from the session's event log.

func ReadPinkyInbox

func ReadPinkyInbox(root, sessionID, workerID string) (PinkyInbox, error)

ReadPinkyInbox validates sessionID and workerID, then reads the session's ACP event log and returns the directive events addressed to that worker.

type PinkyMission

type PinkyMission struct {
	Version         int                               `json:"version"`
	SessionID       string                            `json:"sessionId"`
	WorkerID        string                            `json:"workerId"`
	Spec            string                            `json:"spec"`
	TaskID          string                            `json:"taskId"`
	Attempt         int                               `json:"attempt"`
	Deadline        string                            `json:"deadline"`
	HeartbeatEvery  int                               `json:"heartbeatEverySeconds"`
	Role            string                            `json:"role"`
	Title           string                            `json:"title"`
	ContextCommand  string                            `json:"contextCommand"`
	ContextManifest contextpkg.MissionContextManifest `json:"contextManifest"`
	Contract        string                            `json:"contract"`
	Files           []string                          `json:"files"`
	Acceptance      string                            `json:"acceptance"`
	VerifyCommand   string                            `json:"verifyCommand"`
	Dependencies    []string                          `json:"dependencies"`
	Requirements    []int                             `json:"requirements"`
	Authority       ACPAuthority                      `json:"authority"`
	DispatchDigest  string                            `json:"dispatchDigest"`
	// Resume, when present, carries the prior mid-task checkpoint a worker is
	// being handed so it continues from recorded progress instead of restarting
	// (R1, R4). It is omitempty and excluded from the dispatch digest, so a
	// fresh-dispatch and a resume mission for the same (task, attempt) share a
	// digest and a non-resume mission stays byte-identical to today.
	Resume *PinkyResume `json:"resume,omitempty"`
}

PinkyMission is the dispatch payload sent to a worker for one task attempt: identity (session/worker/spec/task/attempt), the deadline and heartbeat cadence, the role and authoring contract/acceptance/verify text pulled from tasks.md, the context manifest, and an optional resume checkpoint.

func BuildAuthoringMission

func BuildAuthoringMission(root, slug, sessionID, workerID, artifact string, cfg OrchestrationCfg) (PinkyMission, error)

BuildAuthoringMission renders a PinkyMission for a planning-phase artifact. It is the artifact-mission counterpart of BuildPinkyMission: there is no DAG task, so the contract and acceptance are sourced from the live authoring brief (NewAuthoringBrief, itself derived from the gates) and the verify command is `specd check <spec>` — the same gate the brain re-senses to detect completion.

func BuildPinkyMission

func BuildPinkyMission(root, slug, sessionID, workerID, taskID string, attempt int, cfg OrchestrationCfg) (PinkyMission, error)

BuildPinkyMission assembles and validates a PinkyMission for the given task and attempt: it loads the spec and task metadata, fills in the contract/acceptance/verify fields from tasks.md, builds the context manifest, computes the dispatch deadline and digest, and — when the resilience checkpoint feature is enabled and a matching checkpoint exists — attaches a Resume payload so a fresh worker continues prior progress.

type PinkyProgressReport

type PinkyProgressReport struct {
	SessionID string
	WorkerID  string
	Spec      string
	TaskID    string
	Attempt   int
	Percent   int
	Message   string
}

PinkyProgressReport is a worker's progress update for one in-flight task attempt: a percent-complete figure and free-form status message, recorded as an ACP progress event.

type PinkyQueryReport

type PinkyQueryReport struct {
	SessionID string
	WorkerID  string
	Spec      string
	TaskID    string
	Attempt   int
	Text      string
}

PinkyQueryReport is a worker's question to the Brain about a task attempt, recorded as an ACP query event awaiting a directive reply.

type PinkyResume

type PinkyResume struct {
	ProgressPercent int      `json:"progressPercent"`
	WorkingNotes    string   `json:"workingNotes,omitempty"`
	ChangedFiles    []string `json:"changedFiles,omitempty"`
	GitHead         string   `json:"gitHead,omitempty"`
	PriorManifest   string   `json:"priorManifest,omitempty"`
	// ContextDelta, when present, is the per-file reference/reload verdict from
	// diffing the latest context snapshot against the working tree (R2). It lets
	// the resumed worker reload only what changed. nil when snapshots are off or
	// none exist, so a resume without snapshots renders identically to before.
	ContextDelta *contextpkg.SnapshotDiff `json:"contextDelta,omitempty"`
}

PinkyResume is the resume payload threaded into a mission when the Brain decides resume-from-checkpoint: the progress the prior worker reached, its free-form working notes, the files it already touched, the git head it observed, and the context manifest it was given. The brief turns this into a "do not restart" header so the fresh worker continues the same work.

type PinkyTerminalReport

type PinkyTerminalReport struct {
	SessionID       string
	WorkerID        string
	Spec            string
	TaskID          string
	Attempt         int
	VerificationRef string
	Summary         string
	ChangedFiles    []string
	GitHead         string
	DurationMs      int64
	HostTokens      int
	HostCost        string
}

PinkyTerminalReport is a worker's final report for a task attempt: the verification reference, summary, changed files, observed git head, and host-reported duration/token/cost figures used to reconcile evidence in ReconcilePinkyEvidence.

type PositionalMeta

type PositionalMeta struct {
	Name        string `json:"name"`
	Required    bool   `json:"required"`
	Repeatable  bool   `json:"repeatable,omitempty"`
	Description string `json:"description,omitempty"`
}

PositionalMeta describes one positional argument of a CLI command for help text and schema generation.

type PreflightItem

type PreflightItem struct {
	Kind    string `json:"kind"`    // "workspace" | "steering" | "spec"
	Message string `json:"message"` // human-readable reason
	Remedy  string `json:"remedy"`  // the `specd` command that fixes it
}

PreflightItem is one missing precondition and the deterministic command that satisfies it.

func OrchestrationPreflight

func OrchestrationPreflight(root, slug string) []PreflightItem

OrchestrationPreflight reports, in apply order, the preconditions a bare or partial repo is missing before slug can be driven. An empty result means the spec is ready to sense.

type ProgramCfg

type ProgramCfg struct {
	MaxConcurrentSpecs int `json:"maxConcurrentSpecs"`
}

ProgramCfg configures multi-spec program orchestration, currently just the cap on specs that may be running concurrently.

type ProgramChildLease

type ProgramChildLease struct {
	Version         int                     `json:"version"`
	ParentSessionID string                  `json:"parentSessionId"`
	ChildSessionID  string                  `json:"childSessionId"`
	Slug            string                  `json:"slug"`
	Status          ProgramChildLeaseStatus `json:"status"`
	AcquiredAt      string                  `json:"acquiredAt"`
	LeaseUntil      string                  `json:"leaseUntil"`
	ReleasedAt      string                  `json:"releasedAt,omitempty"`
	EscalatedAt     string                  `json:"escalatedAt,omitempty"`
}

ProgramChildLease records a parent program session's exclusive claim on a child spec: which child session owns it, its lease expiry, and timestamps for when it was acquired, released, or escalated.

func AcquireProgramChildLease

func AcquireProgramChildLease(root, parentSessionID, slug string, cfg OrchestrationCfg) (ProgramChildLease, error)

AcquireProgramChildLease claims a child spec for a parent program session, creating a new child session and lease if none exists, extending the lease in place if the same parent already holds an active one, or returning an error if another parent currently owns it.

func LoadProgramChildLeases

func LoadProgramChildLeases(root string) ([]ProgramChildLease, error)

LoadProgramChildLeases reads every child lease stored under the program children directory, returning an empty slice (not an error) if the directory does not yet exist.

func ReleaseProgramChildLease

func ReleaseProgramChildLease(root, parentSessionID, slug string) (ProgramChildLease, error)

ReleaseProgramChildLease marks a child spec's lease as released by its owning parent session, clearing any escalated state. It is a no-op returning the existing lease if already released, and errors if the lease is missing or owned by a different parent.

type ProgramChildLeaseStatus

type ProgramChildLeaseStatus string

ProgramChildLeaseStatus is the lifecycle state of a ProgramChildLease.

const (
	ProgramChildLeaseActive    ProgramChildLeaseStatus = "active"
	ProgramChildLeaseReleased  ProgramChildLeaseStatus = "released"
	ProgramChildLeaseEscalated ProgramChildLeaseStatus = "escalated"
)

Possible ProgramChildLeaseStatus values.

type ProgramChildRuntime

type ProgramChildRuntime struct {
	Active         bool
	Escalated      bool
	ChildSessionID string
}

ProgramChildRuntime is the live runtime state of one child spec — whether it is actively running, escalated, and its child session ID — as observed independently of the persisted program graph.

type ProgramChildSnapshot

type ProgramChildSnapshot struct {
	Slug           string     `json:"slug"`
	Status         SpecStatus `json:"status"`
	Wave           int        `json:"wave"`
	Depends        []string   `json:"depends"`
	Complete       bool       `json:"complete"`
	Blocked        bool       `json:"blocked"`
	Active         bool       `json:"active"`
	Escalated      bool       `json:"escalated"`
	ChildSessionID string     `json:"childSessionId,omitempty"`
}

ProgramChildSnapshot is one child spec's state as seen by the program decision: its status, wave, dependencies, and completion/blocked/active/ escalated flags, plus its child orchestration session ID if one is running.

type ProgramChildStep

type ProgramChildStep struct {
	Slug      string                  `json:"slug"`
	SessionID string                  `json:"sessionId"`
	Result    OrchestrationStepResult `json:"result"`
}

ProgramChildStep records the orchestration step result for one child spec stepped during a program orchestration step.

type ProgramCounts

type ProgramCounts struct {
	Total     int `json:"total"`
	Complete  int `json:"complete"`
	Active    int `json:"active"`
	Blocked   int `json:"blocked"`
	Escalated int `json:"escalated"`
}

ProgramCounts summarizes how many child specs in a program fall into each status bucket.

type ProgramDecision

type ProgramDecision struct {
	Version int                   `json:"version"`
	Action  ProgramDecisionAction `json:"action"`
	Specs   []string              `json:"specs,omitempty"`
	Reason  string                `json:"reason"`
}

ProgramDecision is the outcome of DecideProgram: the action to take, the specs it applies to (when starting children), and a human-readable reason.

func DecideProgram

func DecideProgram(snapshot ProgramSnapshot) (ProgramDecision, error)

DecideProgram is the pure program-level decision function: given a ProgramSnapshot it escalates on cycles, orphan dependencies, or any escalated/blocked child spec; reports complete once every child is complete; waits when capacity is exhausted or no child is runnable; and otherwise starts as many runnable children as available capacity allows.

type ProgramDecisionAction

type ProgramDecisionAction string

ProgramDecisionAction names the action a program-level decision can take.

const (
	ProgramDecisionStart    ProgramDecisionAction = "start"
	ProgramDecisionWait     ProgramDecisionAction = "wait"
	ProgramDecisionEscalate ProgramDecisionAction = "escalate"
	ProgramDecisionComplete ProgramDecisionAction = "complete"
)

The ProgramDecisionAction values are the possible outcomes DecideProgram can return.

type ProgramDriverDispatch

type ProgramDriverDispatch struct {
	Slug           string
	ChildSessionID string
	Dispatch       DriverDispatch
}

ProgramDriverDispatch is the unit handed to a worker during a program drive: the child spec/session that produced the dispatch plus the dispatching decision and its claimable mission.

type ProgramDriverOptions

type ProgramDriverOptions struct {
	MaxSteps int
	MaxWaits int
	Worker   func(ProgramDriverDispatch) error
	Observer DriverObserver
}

ProgramDriverOptions configures a program drive. Worker is the host callback that runs one child mission to a reported terminal state; a nil Worker stops the loop at the first child dispatch. MaxSteps bounds total program steps; MaxWaits bounds consecutive non-progress steps before reporting a stall.

type ProgramDriverResult

type ProgramDriverResult struct {
	Steps   int             `json:"steps"`
	Outcome DriverOutcome   `json:"outcome"`
	Final   ProgramDecision `json:"final"`
}

ProgramDriverResult reports how a program drive ended.

func DriveProgramOrchestration

func DriveProgramOrchestration(root, parentSessionID string, policy OrchestrationPolicy, cfg OrchestrationCfg, opts ProgramDriverOptions) (ProgramDriverResult, error)

DriveProgramOrchestration runs the reference loop against an already-resolvable program (parentSessionID is created on first step if absent). Like the single-spec drive it is asynchronous (GAP-11): each child dispatch's worker is spawned in a goroutine so multiple specs and multiple workers run at once, realizing `max_concurrent_specs`. Per-child worker concurrency is bounded by each child's own `MaxWorkers` lease cap, and how many specs step concurrently is bounded by `max_concurrent_specs` inside StepProgramOrchestration.

type ProgramGraph

type ProgramGraph struct {
	Specs   []SpecNode
	Dag     []DagTask
	Orphans []struct{ Spec, Dep string }
	Cycle   []string
}

ProgramGraph is the resolved cross-spec dependency graph for a program: its specs ordered by wave then slug, the equivalent DAG task list, any dependency edges pointing at unknown specs (orphans), and any dependency cycle that was detected.

func BuildProgram

func BuildProgram(root string, manifest *ProgramManifest) (ProgramGraph, error)

BuildProgram loads (or reuses the given) program manifest and combines it with each spec's on-disk state to produce a ProgramGraph: it computes dependency waves, filters out edges to unknown specs as orphans, detects cycles, and returns specs sorted by wave then slug.

type ProgramManifest

type ProgramManifest struct {
	Version   int                 `json:"version"`
	DependsOn map[string][]string `json:"dependsOn"`
}

ProgramManifest is the persisted program.json document: the schema version and the declared cross-spec dependency edges, keyed by spec slug.

func LoadProgram

func LoadProgram(root string) (ProgramManifest, error)

LoadProgram reads and parses program.json, returning a default manifest with an empty dependency map when the file does not exist. Each spec's dependency list is deduplicated before it is returned.

type ProgramSession

type ProgramSession struct {
	Version         int                        `json:"version"`
	ParentSessionID string                     `json:"parentSessionId"`
	Status          OrchestrationSessionStatus `json:"status"`
	CreatedAt       string                     `json:"createdAt"`
	UpdatedAt       string                     `json:"updatedAt"`
}

ProgramSession is the persisted top-level orchestration session for a program: its schema version, parent session ID, lifecycle status, and creation/update timestamps.

func CancelProgramOrchestration

func CancelProgramOrchestration(root, parentSessionID string) (ProgramSession, error)

CancelProgramOrchestration marks a program session cancelling and propagates a cooperative cancel to every active child orchestration session.

func LoadProgramSession

func LoadProgramSession(root, parentSessionID string) (ProgramSession, error)

LoadProgramSession reads and validates a program session by its parent session ID, returning errOrchestrationSessionNotFound (wrapped) if no session file exists.

func PauseProgramOrchestration

func PauseProgramOrchestration(root, parentSessionID string) (ProgramSession, error)

PauseProgramOrchestration marks a program session paused and propagates the pause to every active child orchestration session so new dispatch stops across the program.

func ResumeProgramOrchestration

func ResumeProgramOrchestration(root, parentSessionID string) (ProgramSession, error)

ResumeProgramOrchestration marks a program session running again and propagates the resume to every active child orchestration session.

type ProgramSnapshot

type ProgramSnapshot struct {
	Version      int                          `json:"version"`
	Children     []ProgramChildSnapshot       `json:"children"`
	Capacity     int                          `json:"capacity"`
	ActiveCount  int                          `json:"activeCount"`
	Cycle        []string                     `json:"cycle"`
	Orphans      []struct{ Spec, Dep string } `json:"orphans"`
	CriticalPath []string                     `json:"criticalPath"`
}

ProgramSnapshot is the point-in-time view of a program's dependency graph that DecideProgram consumes: its children, dispatch capacity and active count, any cycle or orphan dependencies, and the critical path.

func BuildProgramSnapshot

func BuildProgramSnapshot(graph ProgramGraph, active map[string]bool, capacity int) (ProgramSnapshot, error)

BuildProgramSnapshot builds a ProgramSnapshot from a ProgramGraph and a simple active/inactive map keyed by spec slug. It is a convenience wrapper over BuildProgramSnapshotWithRuntime for callers with no richer per-child runtime data (e.g. escalation or child session ID) to report.

func BuildProgramSnapshotWithRuntime

func BuildProgramSnapshotWithRuntime(graph ProgramGraph, runtime map[string]ProgramChildRuntime, capacity int) (ProgramSnapshot, error)

BuildProgramSnapshotWithRuntime assembles a ProgramSnapshot of the cross-spec program: one ProgramChildSnapshot per spec in graph (merging in per-child runtime state such as Active/Escalated/ChildSessionID), the count of currently active children, the worker capacity, any dependency cycle or orphaned dependencies, and the computed critical path. capacity must be positive.

type ProgramState

type ProgramState struct {
	Version         int                   `json:"version"`
	ParentSessionID string                `json:"parentSessionId"`
	ChildSessions   map[string]string     `json:"childSessions"`
	InflightKeys    []string              `json:"inflightKeys"`
	ChildStatus     map[string]SpecStatus `json:"childStatus"`
	UpdatedAt       string                `json:"updatedAt"`
}

ProgramState is the authoritative on-disk projection of a program run's frontier (cross-spec recovery). It captures, for one parent session, the child session each spec is using, the dispatch keys in flight at the last driver step, and each child's last-known status — enough to reconstruct the program DAG frontier after a host crash without re-deriving it from scattered child sessions. The child session remains authoritative on resume; this file is a crash-coherent hint, written atomically on every program driver step.

func LoadProgramState

func LoadProgramState(root, parentSessionID string) (ProgramState, error)

LoadProgramState reads and validates the frontier for a parent session. A missing file yields errProgramStateNotFound; a present-but-corrupt file fails closed with a decode/validation error rather than a partial resume.

func (ProgramState) CompleteChildCount

func (s ProgramState) CompleteChildCount() int

CompleteChildCount returns how many children are in a terminal-done status.

type ProgramStatusReport

type ProgramStatusReport struct {
	Session    ProgramSession       `json:"session"`
	Snapshot   ProgramSnapshot      `json:"snapshot"`
	Decision   ProgramDecision      `json:"decision"`
	Counts     ProgramCounts        `json:"counts"`
	Frontier   []string             `json:"frontier"`
	Waves      []ProgramWaveSummary `json:"waves"`
	Escalation []string             `json:"escalation"`
}

ProgramStatusReport is the full status view of a program orchestration session: its session and snapshot state, the next decision, aggregate counts, the runnable frontier, per-wave summaries, and any escalated specs.

func SenseProgramOrchestration

func SenseProgramOrchestration(root, parentSessionID string, cfg OrchestrationCfg) (ProgramStatusReport, error)

SenseProgramOrchestration builds the current ProgramStatusReport for a program session: it loads the session and program graph, builds the runtime snapshot, derives the next decision (honoring paused/cancelling/ complete/failed session states before falling back to DecideProgram), and assembles the counts, frontier, and wave summaries.

type ProgramStepResult

type ProgramStepResult struct {
	Snapshot ProgramSnapshot     `json:"snapshot"`
	Decision ProgramDecision     `json:"decision"`
	Started  []ProgramChildLease `json:"started"`
	Stepped  []ProgramChildStep  `json:"stepped"`
	Leases   []ProgramChildLease `json:"leases"`
}

ProgramStepResult is the outcome of StepProgramOrchestration: the resulting program snapshot and decision, the leases newly started this step, the children stepped this step, and the full current set of child leases.

func StepProgramOrchestration

func StepProgramOrchestration(root, parentSessionID string, policy OrchestrationPolicy, cfg OrchestrationCfg) (ProgramStepResult, error)

StepProgramOrchestration advances a program orchestration session by one step: it reconciles the program graph and child leases, handles paused, cancelling, complete, and failed session states, then either starts new child specs or steps already-running children according to DecideProgram, updating the parent session's status as needed.

type ProgramWaveSummary

type ProgramWaveSummary struct {
	Wave     int      `json:"wave"`
	Specs    []string `json:"specs"`
	Complete int      `json:"complete"`
	Active   int      `json:"active"`
}

ProgramWaveSummary summarizes one dependency wave's specs and how many of them are complete or active.

type ReportCfg

type ReportCfg struct {
	Format             string `json:"format"`
	AutoRefreshSeconds int    `json:"autoRefreshSeconds"`
}

ReportCfg configures the progress report's output format and optional auto-refresh interval.

type ReportData

type ReportData struct {
	State        *State
	Requirements *string
	Design       *string
	Tasks        *string
	Decisions    *string
	Memory       *string
	MidReqs      *string
}

ReportData bundles a spec's State with the raw markdown of its planning and supporting artifacts, ready for rendering by RenderMarkdown or RenderHTML.

type ResilienceCfg

type ResilienceCfg struct {
	// CheckpointEnabled gates proactive checkpoint/resume behavior (R1, R4).
	CheckpointEnabled bool          `json:"checkpointEnabled,omitempty"`
	AutoResume        AutoResumeCfg `json:"autoResume,omitempty"`
	// MaxSuspendSeconds caps the cumulative time a worker may keep a task
	// suspended (rate-limited) before it is treated as dead (R3). 0 = use the
	// built-in default (600s); omitempty keeps configs without the field
	// byte-identical.
	MaxSuspendSeconds int `json:"maxSuspendSeconds,omitempty"`
	// ContextSnapshotEnabled gates per-turn context-snapshot writing (R2).
	// Default false; omitempty keeps configs without the field byte-identical.
	ContextSnapshotEnabled bool `json:"contextSnapshotEnabled,omitempty"`
	// ProgressTimeoutSeconds is the window within which an in-flight worker's
	// last progress report keeps a driver wait from counting toward the stall
	// limit (R6). Recommended 300. 0/unset disables progress weighting, keeping
	// today's behavior; omitempty keeps configs without the field byte-identical.
	ProgressTimeoutSeconds int `json:"progressTimeoutSeconds,omitempty"`
}

ResilienceCfg groups the opt-in resilience knobs (checkpointing, auto-resume). It is a pointer on OrchestrationCfg with omitempty so a config without a `resilience` block marshals byte-identically to the pre-resilience shape; the whole feature set is therefore default-off and additive.

type ResumableSession

type ResumableSession struct {
	SessionID    string                     `json:"sessionID"`
	Spec         string                     `json:"spec"`
	Status       OrchestrationSessionStatus `json:"status"`
	UpdatedAt    string                     `json:"updatedAt"`
	PausedSince  string                     `json:"pausedSince,omitempty"`
	LastDecision string                     `json:"lastDecision,omitempty"`
	// Program marks a program-parent session: one that orchestrates a DAG of
	// child specs and must be resumed via `brain resume --program`, not the
	// single-spec path (cross-spec recovery). ChildrenComplete/ChildrenTotal give
	// the host the parent's frontier progress at a glance. omitempty keeps
	// single-spec entries byte-identical to the pre-program shape.
	Program          bool `json:"program,omitempty"`
	ChildrenComplete int  `json:"childrenComplete,omitempty"`
	ChildrenTotal    int  `json:"childrenTotal,omitempty"`
}

ResumableSession is one entry in the host-facing resume discovery list (R5). It is a pure projection of a persisted session plus its last recorded Brain decision; it carries no secrets and is safe to print as JSON on startup.

func ListResumableSessions

func ListResumableSessions(root string, maxAge time.Duration) ([]ResumableSession, error)

ListResumableSessions enumerates every session worth resuming after a host restart: those whose status is running or paused, optionally bounded by maxAge against UpdatedAt (zero disables the age filter). The result is sorted most-recently-updated first so a host can auto-resume the head entry. It is a pure read — no session, lease, or event state is mutated — so repeated startup calls are free of side effects.

type RolesCfg

type RolesCfg struct {
	SubagentMode string `json:"subagentMode"`
}

RolesCfg configures how role guidance is delivered to subagents.

type ScaffoldAsset

type ScaffoldAsset struct {
	Template string         `json:"template"`
	Target   string         `json:"target"`
	Policy   ScaffoldPolicy `json:"policy"`
	Required bool           `json:"required"`
	Refresh  bool           `json:"refresh"`
}

ScaffoldAsset is one embedded asset managed by the default init flow. Target is project-root relative and always uses slash separators.

func DefaultScaffoldManifest

func DefaultScaffoldManifest() []ScaffoldAsset

DefaultScaffoldManifest is the single source of truth for files installed by the default init flow. Its order is the deterministic execution order.

type ScaffoldPolicy

type ScaffoldPolicy string

ScaffoldPolicy controls how a scaffold asset is written to the project root.

const (
	// ScaffoldCreate writes the target file only if it does not already exist.
	ScaffoldCreate ScaffoldPolicy = "create"
	// ScaffoldMarkerMerge merges template content into an existing file between markers.
	ScaffoldMarkerMerge ScaffoldPolicy = "marker-merge"
)

type SessionTimelineEvent

type SessionTimelineEvent struct {
	At         string `json:"at,omitempty"`
	Sequence   uint64 `json:"sequence"`
	Type       string `json:"type"`
	Spec       string `json:"spec,omitempty"`
	Task       string `json:"task,omitempty"`
	Action     string `json:"action,omitempty"`
	Reason     string `json:"reason,omitempty"`
	Escalation string `json:"escalation,omitempty"`
	Detail     string `json:"detail,omitempty"`
}

SessionTimelineEvent is a human-facing, replayable view of ACP session events. It preserves deterministic ordering while surfacing Brain decision intent.

func ExplainCurrentSessionDecision

func ExplainCurrentSessionDecision(events []ACPEnvelope) (SessionTimelineEvent, bool)

ExplainCurrentSessionDecision returns a concise explanation for the latest Brain decision.

func ReplaySessionTimeline

func ReplaySessionTimeline(events []ACPEnvelope) []SessionTimelineEvent

ReplaySessionTimeline normalizes ACP envelopes into a stable session timeline.

type SpecNode

type SpecNode struct {
	Slug      string     `json:"slug"`
	Status    SpecStatus `json:"status"`
	DependsOn []string   `json:"dependsOn"`
	Wave      int        `json:"wave"`
	Complete  bool       `json:"complete"`
}

SpecNode is one spec's resolved position in the program graph: its current status, its filtered (known-only) dependencies, its computed wave, and whether it has reached completion.

type SpecStatus

type SpecStatus = spec.SpecStatus

SpecStatus and Phase (with their consts) live in internal/spec so both core and the context engine can share them without an import cycle. These aliases and const re-declarations keep every existing core.SpecStatus / core.Status* / core.Phase / core.Phase* call site compiling unchanged.

func AdvancePlanningPhase

func AdvancePlanningPhase(root, slug string) (from, to SpecStatus, err error)

AdvancePlanningPhase ratchets a planning-phase spec to the next status when its current artifact passes its gate. It is the orchestration-loop counterpart of `specd approve`'s Case 3 planning ratchet: same readiness check, same PlanningAdvance table, so autonomy can never advance a phase the CLI gate would reject. It fails closed if readiness does not pass.

type SpecTelemetry

type SpecTelemetry struct {
	Spec             string          `json:"spec"`
	DurationMs       int64           `json:"durationMs"`
	VerifyDurationMs int64           `json:"verifyDurationMs"`
	Retries          int             `json:"retries"`
	Tokens           int             `json:"tokens"`
	Cost             float64         `json:"cost"`
	CostAnnotated    bool            `json:"costAnnotated"`
	Waves            []WaveTelemetry `json:"waves"`
}

SpecTelemetry is the per-spec roll-up plus its per-wave breakdown. It is a pure function of the task Telemetry records — no clock, no IO — so it is deterministic and golden-comparable.

func RollupTelemetry

func RollupTelemetry(state *State) SpecTelemetry

RollupTelemetry aggregates a spec's per-task Telemetry into per-wave and per-spec totals. Cost is parsed from the annotated string field; an unparseable cost is ignored (contributes 0) rather than failing the roll-up, keeping it total over partially-annotated specs.

func (SpecTelemetry) HasData

func (s SpecTelemetry) HasData() bool

HasData reports whether any task carried telemetry worth rendering.

type SpecdError

type SpecdError struct {
	Code    int
	Message string
}

SpecdError is an error that carries the process exit Code it should produce alongside its Message.

func GateError

func GateError(msg string) *SpecdError

GateError constructs a SpecdError with ExitGate, used for spec/gate validation failures.

func IsSpecdError

func IsSpecdError(err error) (*SpecdError, bool)

IsSpecdError reports whether err is (or wraps) a *SpecdError, returning the unwrapped error alongside the boolean for callers that need its structured fields.

func NotFoundError

func NotFoundError(msg string) *SpecdError

NotFoundError constructs a SpecdError with ExitNotFound, used when a required resource (e.g. a .specd root) cannot be located.

func UsageError

func UsageError(msg string) *SpecdError

UsageError constructs a SpecdError with ExitUsage, used for invalid command-line usage.

func ValidateTaskCompletion

func ValidateTaskCompletion(state *State, ts TaskState, docTask *ParsedTask, slug, id, evidence string, unverified bool) (string, *SpecdError)

ValidateTaskCompletion enforces the complete-status gate and returns the evidence string to record. It mutates nothing; on any gate failure it returns a *SpecdError (GateError). When unverified is false it requires a passing verification record whose command matches the current `verify:` line, so a stale or forged record fails closed.

func (*SpecdError) Error

func (e *SpecdError) Error() string

type State

type State struct {
	SchemaVersion int                        `json:"schemaVersion"`
	Revision      int                        `json:"revision"`
	Spec          string                     `json:"spec"`
	Title         string                     `json:"title"`
	Status        SpecStatus                 `json:"status"`
	Phase         Phase                      `json:"phase"`
	Gate          Gate                       `json:"gate"`
	Turn          int                        `json:"turn"`
	CreatedAt     string                     `json:"createdAt"`
	UpdatedAt     string                     `json:"updatedAt"`
	Tasks         map[string]TaskState       `json:"tasks"`
	Blockers      []Blocker                  `json:"blockers"`
	Acceptance    map[string]CriterionRecord `json:"acceptance,omitempty"`
	// Prompt is the optional originating `specd new --from` text. omitempty keeps
	// state.json byte-identical for specs created without --from.
	Prompt string `json:"prompt,omitempty"`
	// ExecutionMode is the per-spec execution mode ("simple" | "orchestrated").
	// Empty means Simple (see EffectiveMode); omitempty so Simple specs keep
	// byte-identical state.json and pre-mode specs migrate without data change.
	ExecutionMode string `json:"executionMode,omitempty"`
	// ModeOrigin records how ExecutionMode was set ("default" | "user" |
	// "recommended-accepted"), for the replay audit trail. omitempty for the
	// same byte-stability reason as ExecutionMode.
	ModeOrigin string `json:"modeOrigin,omitempty"`
}

State is the full on-disk representation of a spec's state.json: schema and revision bookkeeping, lifecycle status/phase/gate, its tasks, blockers, acceptance evidence, and execution-mode metadata.

func InitialState

func InitialState(spec, title string) State

InitialState builds the freshly-created State for a new spec: schema version stamped, revision 0, status/phase set to the start of the requirements-analysis lifecycle, and empty task/blocker collections.

func LoadState

func LoadState(root, slug string) (*State, error)

LoadState reads and returns the spec's state.json from .specd/specs/<slug>/state.json under root, migrating it to SchemaVersion and back-filling nil Tasks/Blockers as needed. It returns (nil, nil) if no state.json exists yet (a not-yet-initialized spec), and a GateError for any corrupt, malformed, or invalid-status state — LoadState never silently coerces bad on-disk state into something runnable. Callers that intend to mutate the result must hold the spec lock and persist via SaveState, whose compare-and-swap (CAS) on Revision is what actually protects state.json from concurrent writers; LoadState itself performs a single, lock-free read.

func (State) EffectiveMode

func (s State) EffectiveMode() string

EffectiveMode returns the spec's resolved execution mode, treating an empty ExecutionMode (the omitempty Simple default) as ModeSimple. This is the single place that maps the stored-or-absent field to a concrete mode, so callers never branch on the empty string.

type StateBackend

type StateBackend interface {
	// Name identifies the backend for diagnostics and evidence ("file").
	Name() string
	// Load reads the current state, or (nil, nil) when none exists.
	Load(root, slug string) (*State, error)
	// Save commits state under a revision compare-and-swap. It MUST be called
	// inside WithLock for the same (root, slug).
	Save(root, slug string, state *State) error
	// WithLock runs fn while holding the spec's advisory lock, providing
	// cross-process and in-process exclusion plus owning-goroutine reentrancy.
	WithLock(root, slug string, fn func() error) error
}

StateBackend is the storage contract for spec state. It abstracts the three guarantees every backend MUST honor, documented against the file backend in lock.go/state.go: serialize writers (WithLock), reject stale-revision writes (Save's CAS), and commit atomically. Extracting the interface lets alternative backends (git-native, Redis/Postgres behind build tags) slot in without weakening the integrity spine — they are held to the same conformance suite.

The default backend is the on-disk file backend; behavior is identical to calling LoadState/SaveState/WithSpecLock directly.

func DefaultBackend

func DefaultBackend() StateBackend

DefaultBackend returns the on-disk file backend used by every command.

func GitBackend

func GitBackend() StateBackend

GitBackend returns the git-native state backend.

func SelectBackend

func SelectBackend(name string) (StateBackend, error)

SelectBackend resolves a backend name to a StateBackend. "file"/"" is the default; "git" is always available (CLI-only, no driver); any other name must have been compiled in via its build tag, otherwise this fails closed.

type TaskState

type TaskState struct {
	ID           string              `json:"id"`
	Title        string              `json:"title"`
	Role         string              `json:"role"`
	Wave         int                 `json:"wave"`
	Depends      []string            `json:"depends"`
	Requirements []int               `json:"requirements"`
	Status       TaskStatus          `json:"status"`
	StartedAt    *string             `json:"startedAt,omitempty"`
	FinishedAt   *string             `json:"finishedAt,omitempty"`
	Evidence     *string             `json:"evidence,omitempty"`
	Verification *VerificationRecord `json:"verification,omitempty"`
	Blocker      *string             `json:"blocker,omitempty"`
	Telemetry    *Telemetry          `json:"telemetry,omitempty"`
}

TaskState is the persisted state of a single task within a spec: its metadata, current status, timestamps, evidence, verification record, blocker (if any), and telemetry.

type TaskStatus

type TaskStatus string

TaskStatus is the lifecycle status of a single task within a spec's DAG.

const (
	TaskPending  TaskStatus = "pending"
	TaskRunning  TaskStatus = "running"
	TaskComplete TaskStatus = "complete"
	TaskBlocked  TaskStatus = "blocked"
)

TaskPending through TaskBlocked are the valid TaskStatus values a task can hold.

type TaskView

type TaskView struct {
	ID           string
	Title        string
	Role         string
	Wave         int
	Meta         map[string]string
	Depends      []string
	Requirements []int
	FromDoc      bool // true when the task is present in tasks.md (doc)
}

TaskView is the merged "doc overrides state" projection of a single task: the authoritative tasks.md view (doc) layered over the persisted task state, with fields absent from the doc falling back to state. It is the shared source of truth for the dispatch and next renderers, which previously each open-coded this merge.

func ResolveTaskView

func ResolveTaskView(doc ParsedTasks, state *State, id string) TaskView

ResolveTaskView merges the doc view of task id over its persisted state. Title/Wave/Meta come from the doc when present; Role is overridden by the doc's role meta only when non-empty; Depends/Requirements always come from state (the doc does not carry resolved dependency state).

type Telemetry

type Telemetry struct {
	DurationMs       int64  `json:"durationMs,omitempty"`       // running → complete elapsed
	VerifyDurationMs int64  `json:"verifyDurationMs,omitempty"` // most recent verify run
	Retries          int    `json:"retries,omitempty"`          // verify re-runs for this task
	Tokens           int    `json:"tokens,omitempty"`           // annotated, not computed
	Cost             string `json:"cost,omitempty"`             // annotated (e.g. "0.42"), not computed
}

Telemetry is per-task cost/timing evidence. Durations are measured via the injectable Clock (deterministic under the test clock); tokens/cost are operator-annotated values, never computed by specd (no pricing API). Every field is omitempty so tasks without telemetry stay byte-identical.

type TimelineEvent

type TimelineEvent struct {
	At     string `json:"at"`             // RFC3339 timestamp; may be "" when the source lacks one
	Kind   string `json:"kind"`           // started|finished|verified|verify-failed|blocked|criterion-pass|criterion-fail
	Task   string `json:"task,omitempty"` // task id, when the event is task-scoped
	Detail string `json:"detail,omitempty"`
}

TimelineEvent is one normalized, audit-derived moment in a spec's life. Events are collected from state.json's task history (start/finish/verify/block) and acceptance records, then stably ordered for a deterministic replay.

func ReplayTimeline

func ReplayTimeline(state *State) []TimelineEvent

ReplayTimeline normalizes a spec's on-disk audit records into a stably ordered event list. It is read-only and total: missing timestamps and partially populated records are tolerated (an absent timestamp simply sorts first), so a corrupt or half-written state never panics the replay.

type TransportCfg

type TransportCfg struct {
	Kind               string `json:"kind"`
	PollIntervalMillis int    `json:"pollIntervalMillis"`
	MessageTTLSeconds  int    `json:"messageTTLSeconds"`
	LeaseSeconds       int    `json:"leaseSeconds"`
	HeartbeatSeconds   int    `json:"heartbeatSeconds"`
}

TransportCfg configures the ACP message transport used between Brain and Pinky workers: its kind, poll interval, message TTL, lease duration, and heartbeat interval.

type VerificationRecord

type VerificationRecord struct {
	Command    string  `json:"command"`
	ExitCode   int     `json:"exitCode"`
	Verified   bool    `json:"verified"`
	TimedOut   bool    `json:"timedOut"`
	StdoutTail string  `json:"stdoutTail"`
	StderrTail string  `json:"stderrTail"`
	DurationMs int64   `json:"durationMs"`
	RanAt      string  `json:"ranAt"`
	GitHead    *string `json:"gitHead,omitempty"`
	// ChangedFiles is the set of working-tree paths changed at verify time
	// (git diff --name-only vs HEAD). Evidence for the scope gate; omitempty so
	// records written before this field still parse byte-for-byte.
	ChangedFiles []string `json:"changedFiles,omitempty"`
	// Coverage is the parsed total coverage at verify time (e.g. "84.2%") or
	// "unavailable" when no coverage signal was found. It is evidence only —
	// coverage capture never fails a verify. omitempty for back-compat.
	Coverage string `json:"coverage,omitempty"`
	// Sandbox names the isolation backend the command ran under ("bwrap",
	// "container"). Empty/omitted means the default unsandboxed shell runner, so
	// pre-sandbox records and `--sandbox none` runs stay byte-identical.
	Sandbox string `json:"sandbox,omitempty"`
	// Reverted is true when a failed verify stashed the working tree under
	// --revert-on-fail. StashRef carries the recoverable git stash reference so
	// the change can be restored with `git stash apply <ref>`. Both omitempty so
	// passing/default runs stay byte-identical.
	Reverted bool   `json:"reverted,omitempty"`
	StashRef string `json:"stashRef,omitempty"`
}

VerificationRecord is the durable evidence captured when a task's verify command runs: exit status, captured output tails, timing, and optional scope/coverage/sandbox/revert metadata.

type VerifyCfg

type VerifyCfg struct {
	Sandbox string `json:"sandbox"`
}

VerifyCfg holds verify-execution policy. Sandbox selects the isolation backend ("none"|"bwrap"|"container"); empty means the default unsandboxed shell runner.

type Violation

type Violation struct {
	Gate     string `json:"gate"`
	Location string `json:"location"`
	Message  string `json:"message"`
}

Violation describes a single gate-check failure, identifying which gate produced it, where in the document it occurred, and a human-readable reason.

func DesignGate

func DesignGate(md *string) []Violation

DesignGate checks design.md content against DesignSections, returning a Violation for every required section that is missing, empty, or still contains a TODO marker.

func GateAcceptance

func GateAcceptance(c CheckCtx) (violations, warnings []Violation)

GateAcceptance enforces, for tasks that declare an `acceptance:` mapping, that every mapped criterion is (a) defined in requirements.md and (b) recorded as a pass in state.json once the task is complete. It is enforcement-only — specd never judges whether a criterion is "met"; it records and gates on the operator-supplied pass/fail evidence (`specd verify --criterion`). Severity is driven by cfg.Gates.Acceptance: "off"/"" disables the gate (byte-identical to pre-gate behaviour), "warn" demotes the completion findings to warnings, "error" fails the check. A criterion id that is mapped but undefined in requirements.md is always an error (a broken reference, not a severity knob).

func GateContextBudget

func GateContextBudget(c CheckCtx) (violations, warnings []Violation)

GateContextBudget is the opt-in context-budget gate. It is a no-op unless cfg.Gates.ContextBudget names a severity ("warn"/"error"; "off"/""/"*" disable it — the default, so the core pipeline is unchanged). When enabled it builds the active spec's context manifest through the shared engine and reports when the required-item token estimate exceeds the derived budget, naming the heaviest required items so the offender is actionable.

func GateDAG

func GateDAG(c CheckCtx) (violations, warnings []Violation)

GateDAG checks the dependency graph for orphan deps, cycles, and wave-order violations.

OrphanDeps/DetectCycle/WaveViolations each rebuild their own id→task map. We deliberately do not hoist a shared byID here (Stage 06 F3): benchmarking a 200-task DAG put DetectCycle at ~26µs with only 7 allocs, so the extra map builds are noise against a once-per-`check` invocation. Sharing the map would add *With API surface for no measurable win, so the public funcs stay self-contained.

func GateDesign

func GateDesign(c CheckCtx) (violations, warnings []Violation)

GateDesign checks design.md for the mandatory sections.

func GateEars

func GateEars(c CheckCtx) (violations, warnings []Violation)

GateEars lints requirements.md for EARS-form violations.

func GateEvidence

func GateEvidence(c CheckCtx) (violations, warnings []Violation)

GateEvidence checks that every state-complete task carries evidence and (for non-read-only roles) a verified record.

func GateModeCapability

func GateModeCapability(c CheckCtx) (violations, warnings []Violation)

GateModeCapability is the opt-in mode-capability gate. It is a no-op unless cfg.Gates.ModeCapability names a severity ("warn"/"error"; "off"/""/"*" disable it — the default, so Base projects stay clean). When enabled it flags a spec recorded as orchestrated while the project lacks orchestration capability (orchestration.enabled absent/false), pointing at the one enabling command. This catches a spec that opted into orchestration in a project that was never (or no longer is) orchestration-capable.

func GateScope

func GateScope(c CheckCtx) (violations, warnings []Violation)

GateScope flags verify-time changed files that fall outside a task's declared `files:` contract. cfg.Gates.Scope drives it: "off"/""/"*" disables it (the default — no behavioural change), "warn"/"error" set the severity. A task whose `files` contract is "*" (or empty) opts out individually. Evidence comes from the VerificationRecord's ChangedFiles; tasks without a verify record are skipped (nothing to scope). Matching is glob-based (path.Match per declared pattern) so contracts like `internal/core/*.go` work.

func GateSync

func GateSync(c CheckCtx) (violations, warnings []Violation)

GateSync checks that tasks.md checkboxes/annotations agree with state.json.

func GateTaskSchema

func GateTaskSchema(c CheckCtx) (violations, warnings []Violation)

GateTaskSchema validates per-task role and verify-command schema.

func GateTraceability

func GateTraceability(c CheckCtx) (violations, warnings []Violation)

GateTraceability checks the two-way mapping between requirements and tasks. Unreferenced requirements are warnings unless cfg.Gates.Traceability=="error".

func RunGates

func RunGates(c CheckCtx) (violations, warnings []Violation)

RunGates runs the full check pipeline: the ordered pure-gate slice followed by any configured external custom gates. It is the single entry point shared by `specd check` and `specd report --pr-summary`, so both surfaces apply an identical gate set (including custom gates) and can never drift.

type WaveRow

type WaveRow struct {
	Wave  int
	Tasks []DagTask
}

WaveRow groups the tasks belonging to a single wave, as produced by GroupWaves.

func GroupWaves

func GroupWaves(tasks []DagTask) []WaveRow

GroupWaves partitions tasks into WaveRow groups ordered by ascending wave number, with each group's tasks sorted by id ordinal.

type WaveTelemetry

type WaveTelemetry struct {
	Wave             int     `json:"wave"`
	Tasks            int     `json:"tasks"`
	DurationMs       int64   `json:"durationMs"`
	VerifyDurationMs int64   `json:"verifyDurationMs"`
	Retries          int     `json:"retries"`
	Tokens           int     `json:"tokens"`
	Cost             float64 `json:"cost"`
	CostAnnotated    bool    `json:"costAnnotated"`
}

WaveTelemetry is the aggregated cost/timing evidence for one wave. All numeric fields are sums of the per-task Telemetry records (which are themselves measured via the injectable Clock or operator-annotated); specd never computes cost or pricing — Tokens/Cost are simple roll-ups of annotated values.

Jump to

Keyboard shortcuts

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