core

package
v0.2.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: 32 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 (
	DashboardModeAll          = "all"
	DashboardModeConductor    = "conductor"
	DashboardModeOrchestrator = "orchestrator"
	DashboardModeCost         = "cost"
	DashboardModeEval         = "eval"
)

dashboardModes are the panel filters accepted by `--mode`. "all" renders every panel; the others scope the view to one concern.

View Source
const (
	PrototypePending  = "pending"
	PrototypePromoted = "promoted"
)

Prototype lifecycle statuses. A prototype spec is created `pending`, skips the design/tasks planning gates, and can never reach `complete`; `promote` transitions it to `promoted`, after which the normal ratchet applies.

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"
	ModeConductor    = "conductor"
)

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 (
	TrajectoryFileName     = "trajectory.jsonl"
	MaxTrajectoryLineBytes = 64 * 1024
)
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 DefaultMaxObserveBytes = 256 * 1024

DefaultMaxObserveBytes caps a single inbound error payload. Production observability payloads are small; a large body is hostile input and rejected before parsing (V9 §5).

View Source
const DeployFileName = "deploy.jsonl"

DeployFileName is the append-only per-spec deploy ledger. Every step and rollback result is recorded here so the rollback chain is computed from *recorded* successful steps only — no partial-execution ambiguity (V9/P5.1).

View Source
const EvalSchemaVersion = 1
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 InventoryFileName = "inventory.json"

InventoryFileName is the CLI-owned, deterministic inventory of a legacy codebase brought under the harness (V10/P5.3). The binary only *inventories* (countable facts); the agent *understands* (semantics via the skill); the gate *enforces coverage* (countable). This is the boot/enrich lesson codified — zero perception in the binary (invariant 1).

View Source
const MaxDeployPlanBytes = 64 * 1024

MaxDeployPlanBytes caps a deploy config file. Deploy plans are small operator documents; a huge file is hostile input and rejected before parsing.

View Source
const MaxManifestBytes = 256 * 1024

MaxManifestBytes caps how much of a manifest file the stdlib parsers read. Manifests are small; a large one is skipped for module-name extraction (its size still appears in the file inventory).

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 = 6

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] [--guardrails] [--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, registry name, or http(s) URL"}, {Name: "sha256", Type: "string", Description: "Pinned SHA256 digest required for a remote --pack URL"}, {Name: "registry", Type: "string", Description: "Git URL of a pack registry index; resolves a named --pack and pins it in .specd/pack.lock"}, {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] [--prototype]", Synopsis: "specd new <slug> [--title \"...\"] [--orchestrated] [--prototype]",
		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. --prototype creates a prototype spec that skips the design/tasks planning gates but can never reach complete — run `specd promote` to convert it to a full spec.",
		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"}, {Name: "prototype", Type: "boolean", Description: "Create a prototype spec (planning gates relaxed; cannot complete until promoted)"}},
		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", "specd new spike-idea --prototype"},
	},

	{
		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 [<link|unlink|schedule|tick>] ...] [--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. --program is also the home for the program frontier's mutating sub-verbs (the survivors of the removed top-level `program` command): `--program link <spec> --on <dep>` and `--program unlink <spec> --on <dep>` author cross-spec dependencies (cycles are refused); `--program schedule <name> --interval <seconds> --command \"<cmd>\" [--sandbox <backend>]` registers a host-triggered maintenance schedule (bare `--program schedule` lists them, `--program schedule <name> --remove` deletes one); and `--program tick [--now <unix>]` runs every schedule due now exactly once through the sandboxed exec path (specd never daemonizes — a host scheduler drives ticks).",
		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, or run a program sub-verb (link|unlink|schedule|tick)"}, {Name: "on", Type: "string", Description: "With --program link|unlink: the dependency spec slug"}, {Name: "interval", Type: "string", Description: "With --program schedule: rerun interval in seconds"}, {Name: "command", Type: "string", Description: "With --program schedule: the sandboxed shell command to run"}, {Name: "sandbox", Type: "string", Description: "With --program schedule: isolation backend for the scheduled command (none|bwrap|container)"}, {Name: "remove", Type: "boolean", Description: "With --program schedule <name>: delete the named schedule"}, {Name: "now", Type: "string", Description: "With --program tick: deterministic unix clock for host scheduling / tests"}, {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, session-active refusal, dependency cycle, or scheduled command failure"}, {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", "specd status --program", "specd status --program link checkout --on auth", "specd status --program schedule dep-audit --interval 86400 --command \"specd check --security\"", "specd status --program tick --now 1720000000"},
	},

	{
		Command: "check", Category: "inspection",
		Description: "Run all validation gates",
		Usage:       "specd check <slug> [--schema-only] [--security] [--json] | specd check --schema", Synopsis: "specd check <slug> [--schema-only] [--security] [--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. --security runs the deterministic security suite (secrets, injection, slopsquatting) over the working-tree changed files and records a summary in state; advisory scanners never fail the command, only blocking (error-severity) findings do.",
		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: "security", Type: "boolean", Description: "Run the deterministic security suite over changed files"}, {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> [--hud] [--json]", Synopsis: "specd context <slug> [--hud] [--json]",
		LongDescription: "Provides a minimal phase-scoped briefing for the current spec phase. --hud renders the deterministic context heads-up display instead: the steering/skill load files with their on-disk byte and approximate token cost, plus the active mode and routing tier (all measured from disk and recorded state — no interpretation).",
		Flags:           []FlagMeta{{Name: "hud", Type: "boolean", Description: "Render the context HUD (load files, byte/token cost, mode/tier) instead of the phase briefing"}, {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 --hud", "specd context my-feature --json"},
	},

	{
		Command: "eval", Category: "inspection",
		Description: "Score a spec against its eval rubric",
		Usage:       "specd eval <slug> [init|trend] [--suite <name>] [--force] [--json]", Synopsis: "specd eval <slug> [init|trend] [--suite <name>] [--json]",
		LongDescription: "Runs a spec's eval rubric and records the score to state.json and a result file. `eval init` compiles approved requirements into a rubric skeleton (one stub per acceptance criterion); `eval trend` reports score deltas and failure clustering over the result history. Scoring is deterministic; command checks run through the shared sandboxed exec path.",
		Flags:           []FlagMeta{{Name: "suite", Type: "string", Description: "Rubric suite name (default reads eval-rubric.json)"}, {Name: "force", Type: "boolean", Description: "Overwrite an existing rubric on eval init"}, {Name: "json", Type: "boolean"}},
		ExitCodes:       []ExitCodeMeta{{0, "Success"}, {1, "Score below minScore"}, {2, "Usage error"}, {3, "Spec or rubric not found"}},
		Examples:        []string{"specd eval my-feature", "specd eval my-feature init", "specd eval my-feature trend --json"},
	},

	{
		Command: "promote", Category: "lifecycle", Hidden: true,
		Description: "Promote a prototype spec after a passing eval",
		Usage:       "specd promote <slug> --evidence \"...\" [--suite <name>] [--json]", Synopsis: "specd promote <slug> --evidence \"...\"",
		LongDescription: "Converts a prototype spec into a full spec once its eval rubric passes. The evidence string is mandatory — promotion never bypasses the evidence discipline. The normal approve ratchet applies to the promoted spec.",
		Flags:           []FlagMeta{{Name: "evidence", Type: "string", Description: "Required promotion evidence", Required: true}, {Name: "suite", Type: "string", Description: "Rubric suite name (default reads eval-rubric.json)"}, {Name: "json", Type: "boolean"}},
		ExitCodes:       []ExitCodeMeta{{0, "Success"}, {1, "Not a prototype, eval failed, or missing evidence"}, {2, "Usage error"}, {3, "Spec not found"}},
		Examples:        []string{"specd promote my-feature --evidence \"eval green, owner sign-off\""},
	},

	{
		Command: "conductor", Category: "execution",
		Description: "Drive the interactive micro-task conductor session",
		Usage:       "specd conductor <slug> <start|step|accept|reject|stop|replay|switch|status> [micro] [--reason \"...\"] [--json]", Synopsis: "specd conductor <slug> <start|step|accept|reject|stop|status>",
		LongDescription: "Runs the hands-on conductor mode over a task's micro-tasks with an append-only ledger (conductor.jsonl). start opens a session under a spec lock; step briefs the next micro-task; accept/reject record the outcome (reject requires --reason — it is the training signal); stop closes the session. Acceptance never substitutes for verify evidence.",
		Flags:           []FlagMeta{{Name: "reason", Type: "string", Description: "Mandatory rejection reason (reject) or transition note (switch/stop)"}, {Name: "json", Type: "boolean"}},
		ExitCodes:       []ExitCodeMeta{{0, "Success"}, {1, "Gate failure (no session, missing reason, lock contention)"}, {2, "Usage error"}, {3, "Spec not found"}},
		Examples:        []string{"specd conductor my-feature start", "specd conductor my-feature reject --reason \"wrong file touched\"", "specd conductor my-feature status --json"},
	},

	{
		Command: "review", Category: "inspection",
		Description: "Scaffold a review report or extract a review checklist",
		Usage:       "specd review <slug> [checklist] [--force] [--json]", Synopsis: "specd review <slug> [checklist]",
		LongDescription: "Scaffolds review_report.md with the mandatory sections (Summary, Bugs, Security, Hallucinated Dependencies, Style, Verdict) and prints the read-only adversarial reviewer brief. `review checklist` deterministically extracts a human checklist from design.md sections and tasks.md contracts (extraction only). When config.review.required is on, `approve` blocks verifying→complete until a fresh, valid report with an `approve` verdict exists — human approval stays final.",
		Flags:           []FlagMeta{{Name: "force", Type: "boolean", Description: "Overwrite an existing review_report.md scaffold"}, {Name: "json", Type: "boolean"}},
		ExitCodes:       []ExitCodeMeta{{0, "Success"}, {1, "Gate failure"}, {2, "Usage error"}, {3, "Spec not found"}},
		Examples:        []string{"specd review my-feature", "specd review my-feature checklist --json"},
	},

	{
		Command: "orchestrate", Category: "execution",
		Description: "Inspect and resolve auto-escalations",
		Usage:       "specd orchestrate <slug> <status|resume> [--override] [--json]", Synopsis: "specd orchestrate <slug> <status|resume --override>",
		LongDescription: "Surfaces and resolves deterministic auto-escalations (V7). `status` prints the active escalation record (task, rule, facts) and the advisory conductor-handoff recommendation; `resume --override` is the human override that clears the escalation so orchestration may proceed. The binary never auto-clears an escalation and never auto-switches mode — resolution is always an explicit human action.",
		Flags:           []FlagMeta{{Name: "override", Type: "boolean", Description: "Clear the active escalation (required by resume)"}, {Name: "json", Type: "boolean"}},
		ExitCodes:       []ExitCodeMeta{{0, "Success"}, {1, "Gate failure (no escalation, missing --override)"}, {2, "Usage error"}, {3, "Spec not found"}},
		Examples:        []string{"specd orchestrate my-feature status", "specd orchestrate my-feature resume --override"},
	},

	{
		Command: "submit", Category: "execution",
		Description: "Validate all gates and run the configured PR submit command",
		Usage:       "specd submit <slug> [--waves w1,w2] [--dry-run] [--json]", Synopsis: "specd submit <slug> [--dry-run]",
		LongDescription: "Batch PR submission (V7). Validates that every configured gate is green for the spec, generates the deterministic, network-free PR summary, and streams it on stdin to the operator-configured config.submit.command (e.g. `gh pr create --body-file -`) run through the shared sandboxed exec path with a scrubbed env. No git/GitHub logic is embedded. A gate violation or a non-zero command exit is a failure with no partial state; --dry-run prints the summary without executing.",
		Flags:           []FlagMeta{{Name: "waves", Type: "string", Description: "Restrict the summary to a comma-separated wave bundle"}, {Name: "dry-run", Type: "boolean", Description: "Print the PR summary without running the submit command"}, {Name: "json", Type: "boolean"}},
		ExitCodes:       []ExitCodeMeta{{0, "Success"}, {1, "Gate violation or submit command failure"}, {2, "Usage error"}, {3, "Spec not found"}},
		Examples:        []string{"specd submit my-feature --dry-run", "specd submit my-feature"},
	},

	{
		Command: "deploy", Category: "execution",
		Description: "Run the evidence-gated deploy driver or its rollback",
		Usage:       "specd deploy <slug> --env <env> [--dry-run] [--json]  |  specd deploy rollback <slug> --env <env> [--json]", Synopsis: "specd deploy <slug> --env <env> [--dry-run]",
		LongDescription: "Evidence-gated deploy driver runner (V9). Refuses unless the spec is complete, every gate named in the env's `.specd/deploy/<env>.json` requiresGates is recorded green, and — for a production env or an approval-required plan — a human deploy approval exists (`specd approve <slug> --deploy --env <env>`). Runs the plan's steps sequenced through the shared sandboxed exec path with a scrubbed env, appending every result to deploy.jsonl. `deploy rollback` replays the recorded inverse chain (successful steps, reverse order); a failing rollback step halts and exits 3. No CD logic is embedded — steps are operator-authored commands.",
		Flags:           []FlagMeta{{Name: "env", Type: "string", Description: "Target environment (matches .specd/deploy/<env>.json)"}, {Name: "dry-run", Type: "boolean", Description: "Show the plan and precondition status without executing"}, {Name: "json", Type: "boolean"}},
		ExitCodes:       []ExitCodeMeta{{0, "Success"}, {1, "Precondition/gate failure or step failure"}, {2, "Usage error"}, {3, "Spec/config not found or rollback halted"}},
		Examples:        []string{"specd deploy my-feature --env staging --dry-run", "specd approve my-feature --deploy --env production", "specd deploy my-feature --env production", "specd deploy rollback my-feature --env staging"},
	},

	{
		Command: "observe", Category: "inspection",
		Description: "Correlate a production error into a mid-requirement",
		Usage:       "specd observe correlate <payload.json> [--spec <slug>] [--json]  |  specd observe --listen [--spec <slug>]", Synopsis: "specd observe correlate <payload.json> [--spec <slug>]",
		LongDescription: "Inbound production-error correlation (V9). `correlate` reads a schema-validated, size-capped error payload (a CI-piped Sentry export), deterministically attributes it to a spec by matching stack-frame files against task `files:` contracts (falling back to the recent deploy ledger), and appends an evidenced entry to that spec's mid-requirements.md — gating high/critical impact for human approval, exactly like `specd midreq`. `--listen` starts an optional loopback-only, token-authed HTTP receiver that applies the same transform per payload. The transform is the feature; the listener is optional.",
		Flags:           []FlagMeta{{Name: "listen", Type: "boolean", Description: "Start the loopback token-authed HTTP receiver"}, {Name: "spec", Type: "string", Description: "Force attribution to this spec"}, {Name: "json", Type: "boolean"}},
		ExitCodes:       []ExitCodeMeta{{0, "Success"}, {1, "Invalid payload or no correlation"}, {2, "Usage error"}, {3, "Payload/root not found"}},
		Examples:        []string{"specd observe correlate error.json", "specd observe correlate error.json --spec my-feature", "specd observe --listen"},
	},

	{
		Command: "ingest", Category: "lifecycle",
		Description: "Inventory a legacy codebase into an ingestion spec",
		Usage:       "specd ingest new <slug> --path <dir> [--include-ignored] [--json]", Synopsis: "specd ingest new <slug> --path <dir>",
		LongDescription: "Legacy ingestion (V10). `ingest new` validates the path (no traversal outside the repo), writes a deterministic inventory.json (sorted file list, sizes, and manifest-derived module names via stdlib — countable facts only, the binary never reads legacy semantics), and scaffolds an ingestion-flavored spec. File scoping respects `.gitignore` via `git ls-files` when in a git repo (`--include-ignored` forces a bounded walk with default excludes). The `specd-ingest` skill teaches the agent to reverse-engineer requirements/design/tasks; the opt-in `ingest` gate then enforces that every inventoried file is referenced by ≥1 requirement or waived with a reason.",
		Flags:           []FlagMeta{{Name: "path", Type: "string", Description: "Directory to inventory (inside the repo)"}, {Name: "include-ignored", Type: "boolean", Description: "Walk all files instead of git-tracked only"}, {Name: "title", Type: "string", Description: "Spec title (default derived from slug)"}, {Name: "json", Type: "boolean"}},
		ExitCodes:       []ExitCodeMeta{{0, "Success"}, {1, "Invalid path or existing spec"}, {2, "Usage error"}, {3, "Path/root not found"}},
		Examples:        []string{"specd ingest new legacy-billing --path ./billing", "specd ingest new legacy-billing --path ./billing --include-ignored"},
	},

	{
		Command: "report", Category: "inspection",
		Description: "Generate markdown, HTML, or metrics report",
		Usage:       "specd report <slug> [--format md|html|prometheus] [--out <path>] [--pr-summary] [--conductor] [--serve|--watch|--history|--diff]", Synopsis: "specd report <slug> [--format md|html|prometheus] [--out <path>] [--pr-summary] [--conductor]",
		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. With --conductor, clusters the conductor ledger's rejection reasons (exact string + count) — the deterministic rejection-analytics view.",
		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: "conductor", Type: "boolean", Description: "Cluster conductor rejection reasons (exact string + count)"}, {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", "specd report my-feature --conductor"},
	},

	{
		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:         "harness",
		Category:        "platform",
		Hidden:          true,
		Description:     "Share the configured harness (guardrails, deploy, roles, routing) as a versioned team asset.",
		Usage:           "specd harness <push|pull|list|enable> ... [--name <n>] [--force] [--json]",
		Synopsis:        "specd harness <push|pull|list|enable> ... [--force]",
		LongDescription: "Bundles the project's declarative policy — guardrails, deploy templates, roles, routing — under .specd/harness/ with a SHA256-pinned harness.json manifest, and shares it over stdlib-exec git (scrubbed env, transport allowlist, remote-URL validation).\n\npush builds the current bundle (version advances monotonically) and pushes it to a git URL. pull clones a remote bundle, verifies every pinned checksum, refuses a version downgrade or a locally-modified overwrite without --force, and quarantines every imported executable `command` artifact — copied to .specd/harness/quarantine/, listed, never installed — until an operator runs enable, which is recorded in the harness decision log. list shows the bundle and the quarantine; enable installs one quarantined artifact.",
		Flags: []FlagMeta{
			{Name: "name", Type: "string", Description: "Bundle name on push (defaults to the prior name or project dir)"},
			{Name: "force", Type: "boolean", Description: "Override a version downgrade or a locally-modified overwrite"},
			{Name: "json", Type: "boolean", Description: "Emit JSON"},
		},
		ExitCodes: []ExitCodeMeta{{0, "Success"}, {1, "Gate failure (refused overwrite, checksum mismatch, downgrade)"}, {2, "Usage error"}, {3, "No bundle or quarantined item not found"}},
		Examples:  []string{"specd harness push git@example.com:team/harness.git --name platform", "specd harness pull https://example.com/team/harness.git", "specd harness list --json", "specd harness enable .specd/deploy/prod.json"},
	},

	{
		Command:         "migrate",
		Category:        "lifecycle",
		Hidden:          true,
		Description:     "Migrate a v0.1.x project onto v0.2.0 state schema and report available config blocks.",
		Usage:           "specd migrate [--json]",
		Synopsis:        "specd migrate [--json]",
		LongDescription: "Idempotent one-shot upgrade. Rewrites every spec's state.json at the current schema version (the v5→v6 migration is otherwise silent on first load) and reports which additive v0.2.0 policy blocks — guardrails, routing, eval/review gates — are available to adopt. It never writes policy content, so a migrated repo keeps the new gates default-off (backward-compat invariant). Running it a second time is a no-op.",
		Flags: []FlagMeta{
			{Name: "json", Type: "boolean", Description: "Emit the migration report as JSON"},
		},
		ExitCodes: []ExitCodeMeta{{0, "Success"}, {1, "Migration failed (concurrent write or corrupt state)"}, {2, "Usage error"}, {3, ".specd/ not found"}},
		Examples:  []string{"specd migrate", "specd migrate --json"},
	},

	{
		Command:         "dashboard",
		Category:        "inspection",
		Hidden:          true,
		Description:     "Serve the unified, read-only project dashboard (waves, cost, escalations, evals, harness).",
		Usage:           "specd dashboard [<slug>] [--addr 127.0.0.1:8765] [--mode <all|conductor|orchestrator|cost|eval>]",
		Synopsis:        "specd dashboard [<slug>] [--addr 127.0.0.1:8765] [--mode all]",
		LongDescription: "Starts the read-only, browser-native unified dashboard bound to loopback. Renders project-wide state from local state and ledgers only — conductor sessions, orchestrator waves, eval trends, cost attribution, escalations, and the shared harness bundle — with zero outbound network. Reuses the existing SSE stream for live updates. --mode filters the rendered panels; the default lists every panel. A read-only alias over `specd report --serve` with a project-wide home page.",
		Flags: []FlagMeta{
			{Name: "addr", Type: "string", Description: "Loopback bind address (default 127.0.0.1:8765)"},
			{Name: "mode", Type: "string", Description: "Panel filter: all, conductor, orchestrator, cost, or eval (default all)"},
		},
		ExitCodes: []ExitCodeMeta{{0, "Success"}, {1, "Server error"}, {2, "Usage error"}},
		Examples:  []string{"specd dashboard", "specd dashboard --mode cost", "specd dashboard my-feature --addr 127.0.0.1:9000"},
	},

	{
		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 AppendConductorEvent added in v0.2.0

func AppendConductorEvent(root, slug string, ev ConductorEvent) error

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 AppendDeployEntry added in v0.2.0

func AppendDeployEntry(root, slug string, entry DeployLedgerEntry) error

AppendDeployEntry appends one result to the spec's deploy ledger, assigning the next sequence number. The ledger is append-only (dual-write discipline).

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 AppendTrajectoryEvent added in v0.2.0

func AppendTrajectoryEvent(root, slug string, event TrajectoryEvent) error

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 ConductorLedgerPath added in v0.2.0

func ConductorLedgerPath(root, slug string) string

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. Config is YAML-only as of v0.2.0; the legacy `config.json` path is still listed last so a leftover JSON config is discovered and reported as a clear unsupported-extension error rather than being silently ignored.

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 DefaultEvalRubricPath added in v0.2.0

func DefaultEvalRubricPath(root, slug string) string

func DefaultGuardrailsJSON added in v0.2.0

func DefaultGuardrailsJSON() string

func DeployLedgerPath added in v0.2.0

func DeployLedgerPath(root, slug string) string

DeployLedgerPath is the append-only deploy ledger for one spec.

func DeployPlanPath added in v0.2.0

func DeployPlanPath(root, env string) string

DeployPlanPath is the operator-authored config for one environment. The env segment is validated (slug shape) before it reaches the filesystem so it can never traverse out of .specd/deploy/.

func DeployPreconditions added in v0.2.0

func DeployPreconditions(state *State, plan DeployPlan, productionEnv bool) []string

DeployPreconditions returns the human-readable reasons a spec may not deploy under plan: it must be complete, the human deploy gate must be current when the plan (or a production env) requires it, and every required gate must have recorded green evidence. Empty slice = clear to deploy. Pure and deterministic.

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 EnableHarnessItem added in v0.2.0

func EnableHarnessItem(root, relPath string, force bool) error

EnableHarnessItem installs one quarantined artifact to its active path and records the decision in the harness decision log. It refuses a path that is not currently quarantined, and refuses to clobber a locally-modified target without force.

func EnsureGuardrailsScaffold added in v0.2.0

func EnsureGuardrailsScaffold(root string) (bool, error)

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 EscalationRationale added in v0.2.0

func EscalationRationale(verdict EscalationVerdict, task string) string

EscalationRationale renders the conductor-handoff rationale for a triggered verdict on a named task: the deterministic sentence a host shows when mode_recommend flips to conductor. It is phrasing over facts, never a decision.

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 EvaluateGuardrails added in v0.2.0

func EvaluateGuardrails(root string, doc *ParsedTasks, all bool) (GuardrailsResult, []Violation, []Violation)

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 GuardrailsDigest added in v0.2.0

func GuardrailsDigest(root string) (string, bool, error)

func GuardrailsPath added in v0.2.0

func GuardrailsPath(root string) string

func HarnessDir added in v0.2.0

func HarnessDir(root string) string

HarnessDir returns the path to root's `.specd/harness` bundle directory.

func HarnessQuarantined added in v0.2.0

func HarnessQuarantined(root string) []string

HarnessQuarantined lists the paths currently held in quarantine (imported executable artifacts awaiting explicit enable), in deterministic order.

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 InventoryPath added in v0.2.0

func InventoryPath(root, slug string) string

InventoryPath is the inventory file for a spec.

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 LatestTaskCompletion added in v0.2.0

func LatestTaskCompletion(state *State) time.Time

LatestTaskCompletion returns the most recent task FinishedAt timestamp in state, or the zero time when no task has completed. Used by the freshness check: the review must post-date the last thing it is supposed to have reviewed.

func LegacyConfigPath

func LegacyConfigPath(root string) string

LegacyConfigPath returns the path to root's legacy .specd/config.json file. JSON configs are no longer parsed (v0.2.0, YAML-only); this path exists only so a leftover config.json surfaces a clear unsupported-extension error.

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 MarkPrototypePromoted added in v0.2.0

func MarkPrototypePromoted(state *State, report *EvalReport, evidence string) error

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 MarshalEvalRubric added in v0.2.0

func MarshalEvalRubric(r *EvalRubric) (string, error)

MarshalEvalRubric renders a rubric as canonical indented JSON with a trailing newline, matching the byte discipline of the rest of specd's artifacts.

func MarshalInventory added in v0.2.0

func MarshalInventory(inv Inventory) ([]byte, error)

MarshalInventory renders the inventory as deterministic, indented JSON with a trailing newline — byte-identical for the same inventory (V10 §5).

func MarshalTrajectoryEvent added in v0.2.0

func MarshalTrajectoryEvent(event TrajectoryEvent) (string, error)

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 NormalizeDashboardMode added in v0.2.0

func NormalizeDashboardMode(mode string) (string, bool)

NormalizeDashboardMode maps a raw --mode value to a known panel filter, defaulting to "all". An unknown value is reported so the caller can reject it.

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 ReadReviewReport added in v0.2.0

func ReadReviewReport(root, slug string) (*string, time.Time)

ReadReviewReport returns the review report body and its mod time, or (nil, zero) when absent.

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 RemoveSchedule added in v0.2.0

func RemoveSchedule(root, name string) (bool, error)

RemoveSchedule deletes a schedule by name, returning whether one was removed.

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 RenderDashboardHTML added in v0.2.0

func RenderDashboardHTML(d DashboardData, autoRefreshSeconds int) string

RenderDashboardHTML renders the unified dashboard as a self-contained, dependency-free HTML page. autoRefreshSeconds, when > 0, mounts the shared SSE live-update hook; 0 renders a static page. The markup is deterministic given DashboardData so it can be byte-compared in tests.

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 RenderObserveMidreq added in v0.2.0

func RenderObserveMidreq(p ErrorPayload, c Correlation) string

RenderObserveMidreq renders the deterministic mid-requirements.md entry body for a correlated production error. The turn header is prepended by the caller (which owns the state lock and turn counter), mirroring `specd midreq`.

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 ReviewChecklist added in v0.2.0

func ReviewChecklist(designMd string, doc *ParsedTasks) []string

ReviewChecklist deterministically extracts a human review checklist from a spec's design.md section headings and tasks.md task contracts (id + files + verify). It is extraction only — zero interpretation (V8/P4.3).

func RolesDir

func RolesDir(root string) string

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

func RunGuardrails added in v0.2.0

func RunGuardrails(root string, doc *ParsedTasks, all bool) ([]Violation, []Violation)

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 SaveEvalReport added in v0.2.0

func SaveEvalReport(root, slug string, report *EvalReport) (string, error)

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 ScaffoldReviewReport added in v0.2.0

func ScaffoldReviewReport(slug string) string

ScaffoldReviewReport returns the mandatory-section skeleton the reviewer fills in. Deterministic — the same spec always yields the same skeleton.

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 SecureGitClone added in v0.2.0

func SecureGitClone(url, dst string, depth1 bool) error

SecureGitClone clones url into dst through the hardened git-exec discipline: the URL is validated (no arbitrary ext/fd transports, no option injection), the env is scrubbed, terminal prompts are disabled, and the protocol allowlist is enforced. depth1 requests a shallow clone. Shared by harness sharing and the pack registry so both ride one reviewed git path.

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 SeverityImpact added in v0.2.0

func SeverityImpact(sev string) string

SeverityImpact is the exported mapping used by callers to gate high/critical entries (V9/P5.2).

func SkillsDir

func SkillsDir(root string) string

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

func SortedDeployEnvs added in v0.2.0

func SortedDeployEnvs(root string) []string

SortedDeployEnvs lists the configured deploy environments (files under .specd/deploy/) in deterministic order, for help/diagnostics.

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 TrajectoryDigestBytes added in v0.2.0

func TrajectoryDigestBytes(b []byte) string

func TrajectoryDigestJSON added in v0.2.0

func TrajectoryDigestJSON(v any) (string, error)

func TrajectoryPath added in v0.2.0

func TrajectoryPath(root, slug string) string

func TryAppendTrajectoryEvent added in v0.2.0

func TryAppendTrajectoryEvent(root, slug string, event TrajectoryEvent)

TryAppendTrajectoryEvent records an event when possible and intentionally ignores failures so optional producers can remain inert on unwritable ledgers.

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 UpsertSchedule added in v0.2.0

func UpsertSchedule(root string, s MaintenanceSchedule) error

UpsertSchedule registers or replaces a schedule by name, persisting it to program.json under the program lock. A replaced schedule keeps its existing LastRunUnix so re-registering does not reset its cadence.

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 ValidateEnv added in v0.2.0

func ValidateEnv(env string) error

ValidateEnv rejects env names that are not safe filename segments, closing off path traversal through the `--env` flag (V9 §5).

func ValidateErrorPayload added in v0.2.0

func ValidateErrorPayload(p ErrorPayload) error

ValidateErrorPayload enforces the payload invariants: a message is required, severity must be recognized, and every frame path must be repo-relative with no traversal (correlation hints are matched against task contracts, so a path escaping the repo is rejected outright).

func ValidateEvalRubric added in v0.2.0

func ValidateEvalRubric(r *EvalRubric) error

func ValidateHandoff added in v0.2.0

func ValidateHandoff(h *ACPHandoff) error

ValidateHandoff checks an inter-role handoff is well-formed: a known origin role and a non-empty reason. Nil is valid (a fresh dispatch has no handoff).

func ValidateHarnessURL added in v0.2.0

func ValidateHarnessURL(url string) error

HarnessURLError is returned when a remote reference fails validation.

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 ValidateRoutingConfig added in v0.2.0

func ValidateRoutingConfig(cfg *RoutingCfg) error

func ValidateScaffoldManifest

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

ValidateScaffoldManifest verifies all templates before init writes anything.

func ValidateSchedule added in v0.2.0

func ValidateSchedule(s MaintenanceSchedule) error

ValidateSchedule checks a schedule is well-formed before it is persisted.

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 WithProgramLock added in v0.2.0

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

WithProgramLock runs fn while holding the repo-wide program lock, using the same cross-process O_EXCL primitive (with stale reclamation) as WithSpecLock. It serializes program.json mutations — notably the maintenance-schedule claim/tick path (P3.5) — so a double-invoked `specd program tick` cannot run a due schedule twice. Unlike WithSpecLock it is not reentrant and is keyed on a single fixed path, since program-level operations never nest under a spec lock.

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.

func WriteHarnessBundle added in v0.2.0

func WriteHarnessBundle(root string, m HarnessManifest) error

WriteHarnessBundle writes the manifest plus a self-contained copy of every artifact under `.specd/harness/files/`, so the bundle can be committed and pushed to a separate repository without depending on the live policy files.

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 ACPHandoff added in v0.2.0

type ACPHandoff struct {
	// From is the role that produced the work being handed off (e.g. "scout").
	From string `json:"from"`
	// Reason states why the handoff occurred (e.g. "scan complete, ready to build").
	Reason string `json:"reason"`
	// Artifacts are the paths or IDs the From role produced for the receiver.
	Artifacts []string `json:"artifacts,omitempty"`
}

ACPHandoff records an inter-role handoff on an ACP mission (P3.3): a prior worker passing its work to the mission's role. It is versioned by the enclosing ACP envelope and validated by ValidateHandoff. Maps to the A2A "handoff" concept (from-agent, cause, produced artifacts).

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"`
	Tier            string                            `json:"tier,omitempty"`
	Handoff         *ACPHandoff                       `json:"handoff,omitempty"`
}

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"`
}

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
	GuardrailsAll bool
}

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 ConductorEvent added in v0.2.0

type ConductorEvent struct {
	SessionID string `json:"sessionID"`
	Time      string `json:"time"`
	Action    string `json:"action"`
	Task      string `json:"task,omitempty"`
	Micro     string `json:"micro,omitempty"`
	Status    string `json:"status,omitempty"`
	Reason    string `json:"reason,omitempty"`
}

func ReadConductorEvents added in v0.2.0

func ReadConductorEvents(root, slug string) ([]ConductorEvent, error)

type ConductorHandoffRecommendation added in v0.2.0

type ConductorHandoffRecommendation struct {
	Recommended string `json:"recommended"` // "conductor" when escalated, else ""
	Task        string `json:"task,omitempty"`
	Rule        string `json:"rule,omitempty"`
	Rationale   string `json:"rationale"`
	UserDecides bool   `json:"userDecides"`
}

ConductorHandoffRecommendation is the advisory mode flip a host reads when a task has been escalated: mode_recommend → conductor with the escalation facts as rationale. UserDecides is always true (never an automatic switch).

func RecommendConductorForEscalation added in v0.2.0

func RecommendConductorForEscalation(state *State) ConductorHandoffRecommendation

RecommendConductorForEscalation returns the conductor-handoff recommendation for the spec's current escalation record, or a zero recommendation when the spec is not escalated. Deterministic from the record.

type ConductorPlan added in v0.2.0

type ConductorPlan struct {
	Slug   string      `json:"slug"`
	Micros []MicroTask `json:"micros"`
}

func LoadConductorPlan added in v0.2.0

func LoadConductorPlan(root, slug string) (ConductorPlan, error)

type ConductorSession added in v0.2.0

type ConductorSession struct {
	SessionID string `json:"sessionID"`
	Task      string `json:"task"`
	Micro     string `json:"micro"`
	StartedAt string `json:"startedAt"`
}

type ConductorStatus added in v0.2.0

type ConductorStatus struct {
	Active    *ConductorSession `json:"active,omitempty"`
	Frontier  []MicroTask       `json:"frontier"`
	Completed []string          `json:"completed,omitempty"`
	Rejected  int               `json:"rejected"`
	Events    int               `json:"events"`
}

func BuildConductorStatus added in v0.2.0

func BuildConductorStatus(state *State, plan ConductorPlan, events []ConductorEvent) ConductorStatus

type Config

type Config struct {
	Version            int              `json:"version"`
	DefaultVerify      string           `json:"defaultVerify"`
	Report             ReportCfg        `json:"report"`
	Routing            RoutingCfg       `json:"routing,omitempty"`
	Roles              RolesCfg         `json:"roles"`
	PromotionThreshold int              `json:"promotionThreshold"`
	Gates              GatesCfg         `json:"gates"`
	Verify             VerifyCfg        `json:"verify"`
	Orchestration      OrchestrationCfg `json:"orchestration"`
	MCP                MCPConfig        `json:"mcp"`
	// Escalation configures the deterministic auto-escalation engine (V7/P3.2).
	// Off by default (Enabled=false); omitempty keeps pre-escalation config files
	// byte-identical.
	Escalation EscalationConfig `json:"escalation,omitempty"`
	// Review configures the AI-first review workflow gate (V8/P4.1). Required=off
	// by default for migrated repos; omitempty keeps configs byte-identical.
	Review ReviewCfg `json:"review,omitempty"`
	// Security configures the deterministic security gate suite (V8/P4.2). All
	// gates advisory/off by default; omitempty keeps configs byte-identical.
	Security SecurityCfg `json:"security,omitempty"`
	// Submit configures the batch PR submission command (V7/P3.4). Empty command
	// disables `specd submit` exec; omitempty keeps configs byte-identical.
	Submit SubmitCfg `json:"submit,omitempty"`
	// Deploy configures the deploy driver runner (V9/P5.1). Sandbox selects the
	// isolation backend for step commands; omitempty keeps configs byte-identical.
	Deploy DeployCfg `json:"deploy,omitempty"`
	// Observe configures the production-error correlation listener (V9/P5.2).
	// Empty token disables `observe --listen`; omitempty keeps configs identical.
	Observe ObserveCfg `json:"observe,omitempty"`
}

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 ConfigHint added in v0.2.0

type ConfigHint struct {
	Name    string `json:"name"`
	Present bool   `json:"present"`
	Adopt   string `json:"adopt"`
}

ConfigHint names an additive v0.2.0 policy block that is available but not yet adopted, with the command that would adopt it. Reporting only — never applied.

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 Correlation added in v0.2.0

type Correlation struct {
	Spec         string   `json:"spec"`
	Tasks        []string `json:"tasks,omitempty"`
	MatchedFiles []string `json:"matchedFiles,omitempty"`
	Impact       string   `json:"impact"`
	Confidence   string   `json:"confidence"`
	Facts        []string `json:"facts"`
}

Correlation is the deterministic result of matching a payload to a spec: the attributed spec, the tasks whose file contracts matched, the matched frame files, the mapped impact, and the confidence with its supporting facts.

func CorrelatePayload added in v0.2.0

func CorrelatePayload(root string, p ErrorPayload, forceSpec string) (Correlation, error)

CorrelatePayload attributes an error payload to a spec, deterministically. Attribution order: an explicit forceSpec; else the spec whose task `files:` contracts match the most frame paths (ties broken by slug); else the spec with the most recent recorded deploy to the payload's environment. When nothing correlates it returns an error asking for --spec, so every accepted error can still become an evidenced midreq entry (success metric).

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 DashboardData added in v0.2.0

type DashboardData struct {
	Mode         string           `json:"mode"`
	Harness      *HarnessManifest `json:"harness,omitempty"`
	Quarantined  []string         `json:"quarantined,omitempty"`
	Specs        []DashboardSpec  `json:"specs"`
	TotalCostUSD string           `json:"totalCostUSD,omitempty"`
	TotalTokens  int64            `json:"totalTokens,omitempty"`
}

DashboardData is the deterministic, project-wide projection the unified dashboard renders.

func BuildDashboard added in v0.2.0

func BuildDashboard(root, mode string) (DashboardData, error)

BuildDashboard assembles the project-wide dashboard projection from disk. It reads every spec's state, the harness bundle if present, and sums cost/tokens across specs. It never touches the network and never mutates state.

type DashboardSpec added in v0.2.0

type DashboardSpec struct {
	Slug       string            `json:"slug"`
	Title      string            `json:"title"`
	Status     string            `json:"status"`
	Phase      string            `json:"phase"`
	Mode       string            `json:"mode"`
	TasksDone  int               `json:"tasksDone"`
	TasksTotal int               `json:"tasksTotal"`
	Waves      []DashboardWave   `json:"waves,omitempty"`
	CostUSD    string            `json:"costUSD,omitempty"`
	Tokens     int64             `json:"tokens,omitempty"`
	Conductor  *ConductorSession `json:"conductor,omitempty"`
	Escalation *EscalationRecord `json:"escalation,omitempty"`
	Evals      []EvalSummary     `json:"evals,omitempty"`
}

DashboardSpec is one spec's row in the unified dashboard.

type DashboardWave added in v0.2.0

type DashboardWave struct {
	Wave  int `json:"wave"`
	Done  int `json:"done"`
	Total int `json:"total"`
}

DashboardWave summarises one orchestrator wave: how many of its tasks are complete. Waves are derived from each task's recorded Wave index.

type DeployApproval added in v0.2.0

type DeployApproval struct {
	Env  string `json:"env"`
	Time string `json:"time"`
}

DeployApproval is the recorded human deploy gate (V9/P5.1): `specd approve --deploy` writes it, and `specd deploy --env production` refuses without a current one. Pointer + omitempty keeps state.json byte-identical otherwise.

type DeployCfg added in v0.2.0

type DeployCfg struct {
	Sandbox string `json:"sandbox,omitempty"`
}

DeployCfg configures the deploy driver runner. Sandbox is the isolation backend for every step/rollback command ("none" (default), "bwrap", "container"); an unavailable backend fails the step closed. Step commands are operator-authored config (`.specd/deploy/<env>.json`), run with a scrubbed env through the shared sandboxed exec path.

type DeployLedgerEntry added in v0.2.0

type DeployLedgerEntry struct {
	Seq             int64  `json:"seq"`
	At              string `json:"at"`
	Env             string `json:"env"`
	Kind            string `json:"kind"` // "step" | "rollback"
	Step            string `json:"step"`
	RollbackCommand string `json:"rollbackCommand,omitempty"`
	ExitCode        int    `json:"exitCode"`
	Success         bool   `json:"success"`
}

DeployLedgerEntry is one recorded step or rollback result appended to deploy.jsonl. RollbackCommand is copied onto step entries so the rollback chain is reconstructable from the ledger alone.

func ReadDeployLedger added in v0.2.0

func ReadDeployLedger(root, slug string) ([]DeployLedgerEntry, error)

ReadDeployLedger reads all recorded entries in order. A missing ledger is an empty slice, not an error.

func RollbackChain added in v0.2.0

func RollbackChain(entries []DeployLedgerEntry) []DeployLedgerEntry

RollbackChain computes the inverse chain for the most recent deploy run: the successful step entries since the last rollback marker, in reverse order, each carrying a non-empty rollback command. It reads recorded successful steps only (V9 §5) so a mid-chain failure yields exactly the steps that actually ran.

type DeployPlan added in v0.2.0

type DeployPlan struct {
	Env              string       `json:"env"`
	RequiresGates    []string     `json:"requiresGates,omitempty"`
	Steps            []DeployStep `json:"steps"`
	ApprovalRequired bool         `json:"approvalRequired,omitempty"`
}

DeployPlan is the parsed .specd/deploy/<env>.json. It is hostile input: unknown fields, missing timeouts, and empty step lists are rejected.

func LoadDeployPlan added in v0.2.0

func LoadDeployPlan(root, env string) (DeployPlan, error)

LoadDeployPlan reads and validates the deploy config for env. A missing file is a not-found error so the feature is inert until an operator opts in.

func ParseDeployPlan added in v0.2.0

func ParseDeployPlan(env string, data []byte) (DeployPlan, error)

ParseDeployPlan strictly decodes and validates a deploy plan. Every step must name a command and a positive, bounded timeout; requiresGates entries must be recognized gate keywords; the declared env must match the filename's env.

type DeployRecord added in v0.2.0

type DeployRecord struct {
	Env      string `json:"env"`
	Outcome  string `json:"outcome"`
	Steps    int    `json:"steps"`
	Approved bool   `json:"approved"`
	Time     string `json:"time"`
}

DeployRecord is the recorded summary of the last `specd deploy` run (V9/P5.1): the target env, terminal outcome ("succeeded"|"failed"|"rolled-back"), how many steps ran, whether a human deploy approval was recorded, and the time. It is a deterministic projection of deploy.jsonl so reports render it without replaying the ledger. Pointer + omitempty keeps state.json byte-identical for specs that never deploy.

type DeployStep added in v0.2.0

type DeployStep struct {
	Name            string `json:"name"`
	Command         string `json:"command"`
	RollbackCommand string `json:"rollbackCommand,omitempty"`
	TimeoutSeconds  int    `json:"timeoutSeconds"`
}

DeployStep is one operator-declared deploy action. Command runs through the shared sandboxed exec path; RollbackCommand is its recorded inverse (optional, but a step with no rollback contributes nothing to the rollback chain).

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 ErrorPayload added in v0.2.0

type ErrorPayload struct {
	Service     string       `json:"service,omitempty"`
	Environment string       `json:"environment,omitempty"`
	Severity    string       `json:"severity"`
	Message     string       `json:"message"`
	Fingerprint string       `json:"fingerprint,omitempty"`
	Timestamp   string       `json:"timestamp,omitempty"`
	Frames      []StackFrame `json:"frames,omitempty"`
}

ErrorPayload is the schema-validated inbound production error (V9/P5.2). It is hostile input: unknown fields, absolute/traversing frame paths, and missing required fields are rejected. Fields mirror the common Sentry-export shape.

func ParseErrorPayload added in v0.2.0

func ParseErrorPayload(data []byte) (ErrorPayload, error)

ParseErrorPayload strictly decodes and validates an error payload.

type EscalationConfig added in v0.2.0

type EscalationConfig struct {
	// Enabled is the master switch. Off by default: escalation is opt-in per repo
	// (invariant 9 — migrated repos default-off for new gates).
	Enabled bool `json:"enabled,omitempty"`
	// VerifyFailThreshold fires the "verify-fail" rule when a task has this many
	// or more failed verify records. 0 falls back to defaultVerifyFailThreshold.
	VerifyFailThreshold int `json:"verifyFailThreshold,omitempty"`
	// BlockerThreshold fires the "blocker" rule at this many open blockers. 0
	// falls back to defaultBlockerThreshold.
	BlockerThreshold int `json:"blockerThreshold,omitempty"`
	// ComplexityThreshold fires the "complexity" rule at this score. 0 disables
	// the rule (there is no meaningful default complexity ceiling).
	ComplexityThreshold int `json:"complexityThreshold,omitempty"`
}

EscalationConfig holds the tunable thresholds. All are additive with backward-compatible zero values: the whole engine is opt-in via Enabled, so a migrated repo (Enabled=false) never escalates — the documented rollback path (spec §5) is "disable escalation via config thresholds".

type EscalationFacts added in v0.2.0

type EscalationFacts struct {
	Task            string  `json:"task"`
	VerifyFailCount int     `json:"verifyFailCount"`
	RetryCount      int     `json:"retryCount"`
	MaxRetries      int     `json:"maxRetries"`
	BlockerCount    int     `json:"blockerCount"`
	CostUSD         float64 `json:"costUSD"`
	TierBudgetUSD   float64 `json:"tierBudgetUSD"`
	ComplexityScore int     `json:"complexityScore"`
}

EscalationFacts are the countable inputs the rules evaluate. Every field is a plain count/measure taken from state, ledgers, or the router policy — nothing derived from prose. Two callers assembling the same facts always get the same verdict.

func EscalationFactsForTask added in v0.2.0

func EscalationFactsForTask(state *State, traj []TrajectoryEvent, taskID string, maxRetries int, tierBudgetUSD, costUSD float64) EscalationFacts

EscalationFactsForTask assembles the fact tuple for one task from recorded, countable evidence: verify failures and retries from the trajectory ledger (V3), open blockers from state, the retry budget from orchestration config, and the routed tier budget/cost from the caller (V4). It reads only recorded facts, so the verdict is reproducible from disk (invariant 6). tierBudgetUSD/costUSD are 0 when routing is not configured.

Derivation from the trajectory: a "verify fail" is a verify tool/result event for the task carrying a non-zero exit code; RetryCount is the number of verify attempts beyond the first (attempts-1), since each re-attempt after an initial failure is a retry.

type EscalationRecord added in v0.2.0

type EscalationRecord struct {
	Task  string `json:"task"`
	Rule  string `json:"rule"`
	Facts string `json:"facts"`
	Time  string `json:"time"`
}

func EscalateOnVerify added in v0.2.0

func EscalateOnVerify(state *State, traj []TrajectoryEvent, taskID string, cfg EscalationConfig, maxRetries int, tierBudgetUSD, costUSD float64) *EscalationRecord

EscalateOnVerify is the wiring point invoked after a verify record is written (the "verify record" trigger, spec §3). It assembles the task's facts, applies the rules, and on a trigger writes state.Escalation and returns the record so the caller can surface it. It mutates only state.Escalation and is a no-op when escalation is disabled or nothing fires — so the default (Enabled=false) path is byte-identical to pre-escalation behaviour. The task is *not* auto-switched to conductor; that stays a human decision (invariant: never auto-switch).

func NewEscalationRecord added in v0.2.0

func NewEscalationRecord(verdict EscalationVerdict, task string) *EscalationRecord

NewEscalationRecord builds the EscalationRecord written to state when a verdict triggers. The clock is the injected FakeClock-compatible Clock so records are deterministic under test (invariant 7).

type EscalationRuleName added in v0.2.0

type EscalationRuleName = string

EscalationRuleName identifies a single escalation trigger. The set is closed; callers switch on it and it appears verbatim in the EscalationRecord.

const (
	RuleVerifyFail     EscalationRuleName = "verify-fail"
	RuleRetryExhausted EscalationRuleName = "retry-exhausted"
	RuleBlocker        EscalationRuleName = "blocker"
	RuleCostOverBudget EscalationRuleName = "cost-over-budget"
	RuleComplexity     EscalationRuleName = "complexity"
)

type EscalationVerdict added in v0.2.0

type EscalationVerdict struct {
	Triggered bool                 `json:"triggered"`
	Rule      EscalationRuleName   `json:"rule,omitempty"`
	Fired     []EscalationRuleName `json:"fired,omitempty"`
	Facts     string               `json:"facts,omitempty"`
}

EscalationVerdict is the engine's output: which rule (if any) fired first, the full set of rules that fired, and a deterministic, human-readable rendering of the facts that justified it.

func EvaluateEscalation added in v0.2.0

func EvaluateEscalation(facts EscalationFacts, cfg EscalationConfig) EscalationVerdict

EvaluateEscalation applies the rule set to a fact tuple under cfg. It returns a verdict naming the first firing rule (in escalationRuleOrder) plus every rule that fired. When cfg.Enabled is false it always returns an untriggered verdict — the single opt-in gate for the whole engine. Pure: no IO, no clock, no state mutation.

type EvalCheck added in v0.2.0

type EvalCheck struct {
	ID         string  `json:"id"`
	Kind       string  `json:"kind"`
	Weight     float64 `json:"weight,omitempty"`
	Path       string  `json:"path,omitempty"`
	Pattern    string  `json:"pattern,omitempty"`
	Command    string  `json:"command,omitempty"`
	TimeoutMs  int     `json:"timeoutMs,omitempty"`
	Predicate  string  `json:"predicate,omitempty"`
	Max        int     `json:"max,omitempty"`
	Min        int     `json:"min,omitempty"`
	Required   bool    `json:"required,omitempty"`
	WorkingDir string  `json:"workingDir,omitempty"`
	// Sandbox selects the isolation backend for command checks: "none"
	// (default, host shell), "bwrap", or "container". Reuses the shared verify
	// runner so eval command checks inherit the same env-scrub and fail-closed
	// isolation as verify (invariant 8: single shared sandboxed exec path).
	Sandbox string `json:"sandbox,omitempty"`
}

type EvalCheckResult added in v0.2.0

type EvalCheckResult struct {
	ID         string  `json:"id"`
	Kind       string  `json:"kind"`
	Weight     float64 `json:"weight"`
	Score      float64 `json:"score"`
	Passed     bool    `json:"passed"`
	Message    string  `json:"message,omitempty"`
	Digest     string  `json:"digest,omitempty"`
	DurationMs int64   `json:"durationMs,omitempty"`
}

type EvalFailureCluster added in v0.2.0

type EvalFailureCluster struct {
	CheckID string `json:"checkID"`
	Count   int    `json:"count"`
}

EvalFailureCluster counts how often a given check ID failed across the run history. Clustering is by exact check ID — a deterministic key, no prose interpretation.

type EvalReport added in v0.2.0

type EvalReport struct {
	SchemaVersion int               `json:"schemaVersion"`
	Suite         string            `json:"suite"`
	Score         float64           `json:"score"`
	MinScore      float64           `json:"minScore"`
	Passed        bool              `json:"passed"`
	RubricDigest  string            `json:"rubricDigest"`
	GeneratedAt   string            `json:"generatedAt"`
	Checks        []EvalCheckResult `json:"checks"`
	Failures      []string          `json:"failures,omitempty"`
	ReportPath    string            `json:"reportPath,omitempty"`
}

func RunEval added in v0.2.0

func RunEval(root, slug string, rubric *EvalRubric, rubricDigest string) (*EvalReport, error)

type EvalRubric added in v0.2.0

type EvalRubric struct {
	SchemaVersion int         `json:"schemaVersion,omitempty"`
	Suite         string      `json:"suite,omitempty"`
	MinScore      float64     `json:"minScore,omitempty"`
	Checks        []EvalCheck `json:"checks"`
}

func EvalRubricSkeleton added in v0.2.0

func EvalRubricSkeleton(requirements string) *EvalRubric

EvalRubricSkeleton compiles requirements prose into a rubric skeleton with one stub check per acceptance criterion. The transform is deterministic and interpretation-free: criterion IDs fully determine the check IDs and count. Every stub is a regex check with a placeholder pattern the author replaces — minScore stays 0 so a freshly-compiled skeleton never blocks by accident.

func LoadEvalRubric added in v0.2.0

func LoadEvalRubric(path string) (*EvalRubric, string, error)

type EvalSummary added in v0.2.0

type EvalSummary struct {
	Suite    string  `json:"suite"`
	Score    float64 `json:"score"`
	MinScore float64 `json:"minScore"`
	Pass     bool    `json:"pass"`
	Seq      int     `json:"seq"`
	Time     string  `json:"time"`
}

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

type EvalTrendReport added in v0.2.0

type EvalTrendReport struct {
	Slug     string               `json:"slug"`
	Runs     []EvalTrendRun       `json:"runs"`
	Clusters []EvalFailureCluster `json:"clusters,omitempty"`
}

EvalTrendReport is the deterministic rollup of a spec's eval result history.

func EvalTrend added in v0.2.0

func EvalTrend(root, slug, suite string) (*EvalTrendReport, error)

EvalTrend reads the result-file history for a spec and returns score deltas plus failure clustering. When suite is non-empty only that suite's runs are considered. Output is a pure function of the on-disk result files.

type EvalTrendRun added in v0.2.0

type EvalTrendRun struct {
	Suite  string  `json:"suite"`
	Seq    int     `json:"seq"`
	Score  float64 `json:"score"`
	Delta  float64 `json:"delta"`
	Passed bool    `json:"passed"`
}

EvalTrendRun is one recorded eval run in chronological (seq) order, with the score delta from the previous run of the same suite.

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"`
	// Eval is the opt-in eval-completion gate: "" / "off" = no-op (default,
	// including migrated repos), "required" blocks `approve` from marking a spec
	// complete until at least one recorded rubric run passed its minScore. New
	// inits may default this on (V5 quality flywheel).
	Eval string `json:"eval"`
	// Ingest is the opt-in ingestion-coverage gate (V10/P5.3): "" / "off" = no-op
	// (default, including migrated repos), else "warn"/"error". When set it flags
	// any inventory.json file that no requirement references and no waiver excuses
	// — coverage as a countable fact. Off by default keeps non-ingestion specs
	// clean.
	Ingest string `json:"ingest,omitempty"`
	// 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 GuardrailRule added in v0.2.0

type GuardrailRule struct {
	ID      string `json:"id,omitempty"`
	Pattern string `json:"pattern"`
	Path    string `json:"path,omitempty"`
	Message string `json:"message,omitempty"`
}

type GuardrailsConfig added in v0.2.0

type GuardrailsConfig struct {
	BaseRef           string          `json:"baseRef,omitempty"`
	ForbiddenImports  []GuardrailRule `json:"forbiddenImports,omitempty"`
	ForbiddenPatterns []GuardrailRule `json:"forbiddenPatterns,omitempty"`
	ForbiddenPaths    []GuardrailRule `json:"forbiddenPaths,omitempty"`
	ForbiddenCommands []GuardrailRule `json:"forbiddenCommands,omitempty"`
}

func LoadGuardrailsConfig added in v0.2.0

func LoadGuardrailsConfig(root string) (GuardrailsConfig, bool, error)

type GuardrailsResult added in v0.2.0

type GuardrailsResult struct {
	Present  bool
	Warnings []Violation
}

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 HarnessFile added in v0.2.0

type HarnessFile struct {
	Path       string `json:"path"`
	SHA256     string `json:"sha256"`
	Executable bool   `json:"executable"`
}

HarnessFile is one policy artifact carried by a bundle. Path is relative to the project root (canonical, forward-slash, never escaping the tree). SHA256 pins the content; Executable marks an artifact that carries a `command` check and so must be quarantined on import.

type HarnessManifest added in v0.2.0

type HarnessManifest struct {
	Name       string        `json:"name"`
	Version    int           `json:"version"`
	Provenance string        `json:"provenance"`
	Files      []HarnessFile `json:"files"`
}

HarnessManifest is the parsed `.specd/harness/harness.json`. Version is a plain monotonically-increasing int compared across pulls to refuse downgrades; Provenance records where the bundle came from ("local" or a remote URL).

func BuildHarnessManifest added in v0.2.0

func BuildHarnessManifest(root, name string) (HarnessManifest, error)

BuildHarnessManifest assembles a manifest for the project's current policy artifacts. Version is one past the existing local manifest (1 for a first build) so each push advances the shared version monotonically.

func LoadHarnessManifest added in v0.2.0

func LoadHarnessManifest(root string) (HarnessManifest, error)

LoadHarnessManifest reads and validates the local bundle manifest.

func ParseHarnessManifest added in v0.2.0

func ParseHarnessManifest(raw []byte) (HarnessManifest, error)

ParseHarnessManifest decodes and validates a manifest. It fails closed on unknown fields, missing required fields, a non-positive version, and any file path that could escape the project root (same discipline as pack manifests).

func PushHarness added in v0.2.0

func PushHarness(root, url, name string) (HarnessManifest, error)

PushHarness builds the current bundle and pushes it to a remote git URL. It clones the remote into a temp working tree, copies the manifest and files in, commits, and pushes — so an existing shared history is preserved.

type HarnessPullResult added in v0.2.0

type HarnessPullResult struct {
	Manifest    HarnessManifest `json:"manifest"`
	Installed   []string        `json:"installed"`
	Quarantined []string        `json:"quarantined"`
	Refused     []string        `json:"refused"`
}

HarnessPullResult reports what a pull did: files installed, files quarantined (executable artifacts awaiting explicit enable), and files refused for local modification (only when not forced).

func PullHarness added in v0.2.0

func PullHarness(root, url string, force bool) (HarnessPullResult, error)

PullHarness clones a remote bundle, verifies every file's pinned SHA256, and installs it. Non-executable policy installs directly unless it would clobber a locally-modified file (refused without force). Executable artifacts are copied to quarantine and listed, never installed. A remote whose version is older than the local bundle is refused without force (no silent downgrade). Any checksum mismatch is a hard failure that writes nothing.

type IngestCoverage added in v0.2.0

type IngestCoverage struct {
	Mapped   []string `json:"mapped"`
	Waived   []string `json:"waived"`
	Unmapped []string `json:"unmapped"`
}

IngestCoverage is the countable coverage result over an inventory: which files a requirement references, which are waived (with a reason), and which are unmapped. Mapped = the file path appears verbatim in requirements.md.

func ComputeIngestCoverage added in v0.2.0

func ComputeIngestCoverage(inv Inventory, requirementsMd string) IngestCoverage

ComputeIngestCoverage is the pure coverage math (V10/P5.3): every inventory file is mapped (referenced by ≥1 requirement) or waived (has a reason) or unmapped. A waiver with an empty reason does not count — reason strings are mandatory (same discipline as the security allowlist).

type IngestRecord added in v0.2.0

type IngestRecord struct {
	Files int    `json:"files"`
	Time  string `json:"time"`
}

IngestRecord is the recorded summary of the last ingestion inventory (V10/P5.3): how many files the deterministic inventory captured and when. Pointer + omitempty keeps state.json byte-identical for non-ingestion specs.

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 Inventory added in v0.2.0

type Inventory struct {
	Base    string            `json:"base"`
	Files   []InventoryFile   `json:"files"`
	Modules []string          `json:"modules,omitempty"`
	Waivers map[string]string `json:"waivers,omitempty"`
}

Inventory is the deterministic projection of an ingested tree. Files and Modules are sorted so the same tree yields byte-identical inventory.json. Waivers maps an inventory path to the reason it is intentionally excluded from requirement coverage (same discipline as the security allowlist).

func BuildInventory added in v0.2.0

func BuildInventory(baseDir, base string, relFiles []string) (Inventory, error)

BuildInventory produces the deterministic inventory for baseDir given the already-resolved repo-relative file list (the caller scopes via `git ls-files` or a bounded walk). It stats each file, skips symlinks and unreadable entries, and extracts module names from recognized manifests with stdlib only. It never reads source semantics — only countable facts.

func LoadInventory added in v0.2.0

func LoadInventory(root, slug string) (*Inventory, error)

LoadInventory reads and decodes a spec's inventory.json. A missing file is a nil inventory (feature inert), not an error.

type InventoryFile added in v0.2.0

type InventoryFile struct {
	Path string `json:"path"`
	Size int64  `json:"size"`
}

InventoryFile is one countable file fact: its repo-relative path and byte size.

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 MaintenanceSchedule added in v0.2.0

type MaintenanceSchedule struct {
	Name string `json:"name"`
	// Command is the operator-authored shell command, run through the shared
	// sandboxed exec path with a scrubbed env — never git/GitHub logic embedded.
	Command string `json:"command"`
	// Sandbox selects the runner backend ("" = default); mirrors submit.sandbox.
	Sandbox string `json:"sandbox,omitempty"`
	// IntervalSeconds is the minimum elapsed wall-clock time between runs.
	IntervalSeconds int64 `json:"intervalSeconds"`
	// LastRunUnix is the wall-clock second at which tick last claimed this
	// schedule. Zero means never run (so the first tick always claims it).
	LastRunUnix int64 `json:"lastRunUnix,omitempty"`
}

MaintenanceSchedule is a registered recurring maintenance program (P3.5). It is a declaration only: specd never daemonizes and never runs it on a timer. A host scheduler (cron, CI, systemd timer) invokes `specd program tick`, which runs each schedule whose interval has elapsed exactly once, guarded by the program lock so a double-invoked tick is idempotent.

func ClaimSchedule added in v0.2.0

func ClaimSchedule(root, name string, nowUnix int64) (MaintenanceSchedule, bool, error)

ClaimSchedule atomically claims a schedule for execution at nowUnix. Under the program lock it re-reads the manifest, and if the named schedule is still due it advances LastRunUnix to nowUnix and persists before returning ok=true. A concurrent or repeated tick that finds the schedule no longer due returns ok=false without executing — this is the CAS that makes tick idempotent. The claim is recorded before the command runs, so a crashed command is retried on the next elapsed interval rather than re-run within the same window.

func DueSchedules added in v0.2.0

func DueSchedules(m ProgramManifest, nowUnix int64) []MaintenanceSchedule

DueSchedules returns the schedules in manifest that are due at nowUnix, in the manifest's stored order. It is a pure read — it never claims or mutates state.

type MicroTask added in v0.2.0

type MicroTask struct {
	TaskID  string   `json:"taskID"`
	ID      string   `json:"id"`
	Key     string   `json:"key"`
	Title   string   `json:"title"`
	Checked bool     `json:"checked"`
	Deps    []string `json:"deps,omitempty"`
	Verify  string   `json:"verify,omitempty"`
	Line    int      `json:"line"`
}

func ConductorFrontier added in v0.2.0

func ConductorFrontier(plan ConductorPlan, events []ConductorEvent) []MicroTask

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 MigrateReport added in v0.2.0

type MigrateReport struct {
	SchemaVersion int             `json:"schemaVersion"`
	Specs         []SpecMigration `json:"specs"`
	Hints         []ConfigHint    `json:"hints"`
}

MigrateReport is the deterministic result of `specd migrate`.

func MigrateProject added in v0.2.0

func MigrateProject(root string) (MigrateReport, error)

MigrateProject migrates every spec's state to the current SchemaVersion and reports available config blocks. It is idempotent: a second run finds every spec already current and rewrites nothing. Each state rewrite takes the spec lock and goes through SaveState, so a concurrent writer is detected rather than clobbered.

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 ObserveCfg added in v0.2.0

type ObserveCfg struct {
	Token           string `json:"token,omitempty"`
	Addr            string `json:"addr,omitempty"`
	MaxPayloadBytes int    `json:"maxPayloadBytes,omitempty"`
}

ObserveCfg configures the inbound observability listener. Token is the shared bearer secret required on every request (empty disables the listener); Addr is the localhost bind address (default 127.0.0.1:0); MaxPayloadBytes caps a single error payload (0 = built-in default). No listener is ever started implicitly.

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"`
	// Wave-4 trust/scale sections. Each is a pointer so an absent feature renders a
	// deterministic "not configured" line rather than an empty block (V7/V8).
	Evals      []EvalSummary     `json:"evals,omitempty"`
	Security   *SecurityScan     `json:"security,omitempty"`
	Escalation *EscalationRecord `json:"escalation,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"`
	// Tier is the routing tier the Brain dispatched this mission at (V4), carried
	// through the ACP handoff so a downstream host can attribute cost. Omitempty
	// and excluded from the dispatch digest — a mission without it is byte-identical.
	Tier string `json:"tier,omitempty"`
	// Handoff records an inter-role handoff (P3.3): when a prior worker (e.g. a
	// scout) passes its work to this mission's role (e.g. a craftsman), it names
	// the origin role, the reason, and the artifacts produced. Nil for a fresh
	// dispatch. Omitempty and excluded from the dispatch digest.
	Handoff *ACPHandoff `json:"handoff,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"`
	Schedules []MaintenanceSchedule `json:"schedules,omitempty"`
}

ProgramManifest is the persisted program.json document: the schema version, the declared cross-spec dependency edges (keyed by spec slug), and any registered maintenance schedules (P3.5).

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 PrototypeState added in v0.2.0

type PrototypeState struct {
	Status        string `json:"status"`
	CreatedAt     string `json:"createdAt,omitempty"`
	PromotedAt    string `json:"promotedAt,omitempty"`
	EvalReport    string `json:"evalReport,omitempty"`
	EvalDigest    string `json:"evalDigest,omitempty"`
	Evidence      string `json:"evidence,omitempty"`
	PromotedScore string `json:"promotedScore,omitempty"`
}

type RejectionCluster added in v0.2.0

type RejectionCluster struct {
	Reason string `json:"reason"`
	Count  int    `json:"count"`
}

RejectionCluster is one exact rejection reason with its occurrence count.

func ConductorRejectionReport added in v0.2.0

func ConductorRejectionReport(events []ConductorEvent) []RejectionCluster

ConductorRejectionReport clusters conductor reject events by their exact reason string and counts each. Rejections are the training signal; this is a pure count with no interpretation of the prose (invariant 6). Clusters sort by descending count, then reason ascending, so the report is deterministic.

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 ReviewCfg added in v0.2.0

type ReviewCfg struct {
	Required bool `json:"required,omitempty"`
}

ReviewCfg configures the review workflow gate. Required gates the verifying→complete approve transition on a fresh, structurally-valid review_report.md. Off for migrated repos (invariant 9); new inits may default it on.

type ReviewGateResult added in v0.2.0

type ReviewGateResult struct {
	OK      bool
	Verdict ReviewVerdict
	Fresh   bool
	Problem string
}

ReviewGateResult is the outcome of evaluating the review gate.

func EvaluateReviewGate added in v0.2.0

func EvaluateReviewGate(state *State, reportBody *string, reportModTime time.Time) ReviewGateResult

EvaluateReviewGate checks the review report for a spec: existence, structural validity, verdict presence, and freshness relative to the latest task completion. reportModTime is the report file's modification time (the staleness signal); pass the zero time when the report is absent.

type ReviewRecord added in v0.2.0

type ReviewRecord struct {
	Verdict string `json:"verdict"`
	Fresh   bool   `json:"fresh"`
	Time    string `json:"time"`
}

ReviewRecord is the recorded outcome of the review gate: the verdict parsed from review_report.md, whether the report was fresh relative to the latest task completion, and when the gate evaluated.

type ReviewReport added in v0.2.0

type ReviewReport struct {
	Verdict  ReviewVerdict `json:"verdict"`
	Sections []string      `json:"sections"`
}

ReviewReport is the parsed, validated review artifact.

func ParseReviewReport added in v0.2.0

func ParseReviewReport(body string) (ReviewReport, error)

ParseReviewReport parses and structurally validates the report body. It returns an error naming the first missing mandatory section or the absent/invalid verdict — so the gate message is actionable.

type ReviewVerdict added in v0.2.0

type ReviewVerdict string

ReviewVerdict is the parsed decision. Only approve|revise are valid.

const (
	ReviewApprove ReviewVerdict = "approve"
	ReviewRevise  ReviewVerdict = "revise"
)

type RolesCfg

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

RolesCfg configures how role guidance is delivered to subagents.

type RoutingCfg added in v0.2.0

type RoutingCfg struct {
	Enabled        bool               `json:"enabled"`
	DefaultTier    string             `json:"defaultTier,omitempty"`
	TaskTiers      map[string]string  `json:"taskTiers,omitempty"`
	Tiers          map[string]TierCfg `json:"tiers,omitempty"`
	BudgetsUSD     map[string]string  `json:"budgetsUSD,omitempty"`
	MaxTokens      map[string]int64   `json:"maxTokens,omitempty"`
	CostPerMTokIn  map[string]string  `json:"costPerMTokIn,omitempty"`
	CostPerMTokOut map[string]string  `json:"costPerMTokOut,omitempty"`
}

RoutingCfg is the deterministic model-tier policy stored in config.json.

func (*RoutingCfg) UnmarshalJSON added in v0.2.0

func (cfg *RoutingCfg) UnmarshalJSON(data []byte) error

type RoutingEconomics added in v0.2.0

type RoutingEconomics struct {
	TotalCostUSD string                        `json:"totalCostUSD,omitempty"`
	TotalTokens  int64                         `json:"totalTokens,omitempty"`
	ByTier       map[string]RoutingTierEconomy `json:"byTier,omitempty"`
}

RoutingEconomics is a stable cost rollup for reports.

func ResolveRoutingStamps added in v0.2.0

func ResolveRoutingStamps(cfg RoutingCfg, tasks map[string]TaskState) (map[string]RoutingStamp, RoutingEconomics, error)

func RoutingEconomicsFromState added in v0.2.0

func RoutingEconomicsFromState(state *State) RoutingEconomics

type RoutingStamp added in v0.2.0

type RoutingStamp struct {
	Tier      string  `json:"tier"`
	BudgetUSD float64 `json:"budgetUSD"`
	RuleIndex int     `json:"ruleIndex"`
}

type RoutingState added in v0.2.0

type RoutingState struct {
	Tasks     map[string]TaskRoutingState `json:"tasks,omitempty"`
	Economics RoutingEconomics            `json:"economics,omitempty"`
}

RoutingState is additive state.json metadata. It is derived from local config/task state and never from a model or network call.

func ResolveRoutingState added in v0.2.0

func ResolveRoutingState(cfg RoutingCfg, tasks map[string]TaskState) (RoutingState, error)

ResolveRoutingState builds the routing state from config and current task telemetry. The policy is deterministic: explicit task tier wins, otherwise the configured default tier, otherwise the lexicographically first tier.

type RoutingTierEconomy added in v0.2.0

type RoutingTierEconomy struct {
	CostUSD string `json:"costUSD,omitempty"`
	Tokens  int64  `json:"tokens,omitempty"`
	Tasks   int    `json:"tasks,omitempty"`
}

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 SecurityCfg added in v0.2.0

type SecurityCfg struct {
	Secrets   string `json:"secrets,omitempty"`
	Injection string `json:"injection,omitempty"`
	Slopsquat string `json:"slopsquat,omitempty"`
	// Deps names an external CVE-scan command (osv-scanner/grype). Empty disables
	// the plugin gate — no CVE database is ever embedded (invariant 2/3).
	Deps string `json:"deps,omitempty"`
}

SecurityCfg configures the security gate suite. Each sub-gate carries a severity: "" / "off" disables it, "warn" is advisory (default for the noisy heuristics — plan risk 2), "error" blocks. Secrets defaults to "error" only when explicitly enabled; the zero value is fully off so migrated repos are unaffected.

type SecurityScan added in v0.2.0

type SecurityScan struct {
	Findings  int            `json:"findings"`
	Blocking  int            `json:"blocking"`
	ByScanner map[string]int `json:"byScanner,omitempty"`
	Time      string         `json:"time"`
}

SecurityScan is the recorded summary of a security-suite run: total findings, how many were blocking (error-severity), the per-scanner tally, and the scan time. It is a deterministic projection of the findings, so reports/PR summaries render it without re-scanning.

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 SpecMigration added in v0.2.0

type SpecMigration struct {
	Slug        string `json:"slug"`
	FromVersion int    `json:"fromVersion"`
	ToVersion   int    `json:"toVersion"`
	Migrated    bool   `json:"migrated"`
}

SpecMigration records one spec's state migration outcome.

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 StackFrame added in v0.2.0

type StackFrame struct {
	File     string `json:"file"`
	Line     int    `json:"line,omitempty"`
	Function string `json:"function,omitempty"`
}

StackFrame is one correlation hint from a production error: a source file and (optionally) line and symbol. File is matched against task `files:` contracts.

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"`
	Evals         map[string]EvalSummary     `json:"evals,omitempty"`
	Routing       map[string]RoutingStamp    `json:"routing,omitempty"`
	Conductor     *ConductorSession          `json:"conductor,omitempty"`
	Prototype     *PrototypeState            `json:"prototype,omitempty"`
	Escalation    *EscalationRecord          `json:"escalation,omitempty"`
	// Security holds the last `specd check --security` scan summary (V8/P4.2).
	// Pointer + omitempty keeps state.json byte-identical for repos that never run
	// the security suite.
	Security *SecurityScan `json:"security,omitempty"`
	// Review holds the last review-gate evaluation (V8/P4.1). Pointer + omitempty
	// keeps state.json byte-identical for repos without a review report.
	Review *ReviewRecord `json:"review,omitempty"`
	// Deploy holds the last `specd deploy` outcome (V9/P5.1); DeployApproval holds
	// the current human deploy gate (`specd approve --deploy`). Pointer + omitempty
	// keeps state.json byte-identical for specs that never deploy.
	Deploy         *DeployRecord   `json:"deploy,omitempty"`
	DeployApproval *DeployApproval `json:"deployApproval,omitempty"`
	// Ingest holds the last ingestion inventory summary (V10/P5.3). Pointer +
	// omitempty keeps state.json byte-identical for non-ingestion specs.
	Ingest *IngestRecord `json:"ingest,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 SubmitCfg added in v0.2.0

type SubmitCfg struct {
	Command string `json:"command,omitempty"`
	Sandbox string `json:"sandbox,omitempty"`
}

SubmitCfg configures the batch PR submission command. Command is trusted operator input (not agent-authored) run through the shared sandboxed exec path with a scrubbed env; the PR summary is streamed to it on stdin.

type TaskRoutingState added in v0.2.0

type TaskRoutingState struct {
	Tier       string `json:"tier"`
	Model      string `json:"model,omitempty"`
	Reason     string `json:"reason,omitempty"`
	BudgetUSD  string `json:"budgetUSD,omitempty"`
	MaxTokens  int64  `json:"maxTokens,omitempty"`
	CostUSD    string `json:"costUSD,omitempty"`
	Tokens     int64  `json:"tokens,omitempty"`
	BrakeLevel string `json:"brakeLevel,omitempty"`
}

TaskRoutingState records the deterministic tier decision for a task.

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
	VerifyFails      int    `json:"verifyFails,omitempty"`      // cumulative failed verify runs (V7 escalation fact)
	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 TierCfg added in v0.2.0

type TierCfg struct {
	Model          string `json:"model,omitempty"`
	MaxTokens      int64  `json:"maxTokens,omitempty"`
	BudgetUSD      string `json:"budgetUSD,omitempty"`
	CostPerMTokIn  string `json:"costPerMTokIn,omitempty"`
	CostPerMTokOut string `json:"costPerMTokOut,omitempty"`
}

TierCfg describes a named routing tier without binding specd to a host model.

func (*TierCfg) UnmarshalJSON added in v0.2.0

func (tier *TierCfg) UnmarshalJSON(data []byte) error

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 TrajectoryEvent added in v0.2.0

type TrajectoryEvent struct {
	Seq          int64    `json:"seq"`
	At           string   `json:"at"`
	Actor        string   `json:"actor,omitempty"`
	Kind         string   `json:"kind"`
	Tool         string   `json:"tool,omitempty"`
	TaskIDs      []string `json:"taskIds,omitempty"`
	ArgsDigest   string   `json:"argsDigest,omitempty"`
	ResultDigest string   `json:"resultDigest,omitempty"`
	CwdDigest    string   `json:"cwdDigest,omitempty"`
	ExitCode     *int     `json:"exitCode,omitempty"`
	WallMs       int64    `json:"wallMs,omitempty"`
}

TrajectoryEvent is the digest-only record written to trajectory.jsonl. Argument and result payloads must be recorded through their sha256 digests, never as raw values.

func DecodeTrajectory added in v0.2.0

func DecodeTrajectory(r io.Reader) ([]TrajectoryEvent, error)

func ReadTrajectory added in v0.2.0

func ReadTrajectory(root, slug string) ([]TrajectoryEvent, error)

func ReadTrajectoryFile added in v0.2.0

func ReadTrajectoryFile(path string) ([]TrajectoryEvent, error)

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 GateGuardrails added in v0.2.0

func GateGuardrails(c CheckCtx) (violations, warnings []Violation)

func GateIngest added in v0.2.0

func GateIngest(c CheckCtx) (violations, warnings []Violation)

GateIngest is the opt-in ingestion-coverage gate. It is a no-op unless cfg.Gates.Ingest names a severity and the spec has an inventory.json. When enabled it flags every inventory file that no requirement references and no waiver excuses — coverage as a countable fact (V10/P5.3).

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.

Directories

Path Synopsis
Package security is the deterministic security-gate suite (V8/P4.2).
Package security is the deterministic security-gate suite (V8/P4.2).

Jump to

Keyboard shortcuts

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