plan

package
v0.11.1 Latest Latest
Warning

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

Go to latest
Published: Jul 2, 2026 License: MIT Imports: 41 Imported by: 0

Documentation

Overview

Package plan enriches agent-generated implementation plans with SageOx team context. ox computes DETERMINISTIC badges locally (zero LLM tokens) and assembles a context bundle; the client agent does any inference. ox NEVER makes an LLM or network call in this path.

Architecture:

  • Detectors produce deterministic Annotations from local data (collision, prior-art, expert-routing). They are fail-open: missing/unreadable data returns (nil, nil), never an aborting error.
  • Retrievers assemble a context bundle ([]ContextItem) the client agent reasons over to author judgment badges (aligns/conflicts, expert perspective). Also fail-open.
  • Enrich() orchestrates registered detectors and retrievers, aggregating results into a Result with a deterministic SignalSummary.

Round 2 agents implement detectors/retrievers in collision.go, expert.go, priorart.go (and a context-bundle assembler) and register them via init().

Index

Constants

View Source
const (
	// NonTrivialMinFiles: a multi-file plan (>= 2 distinct files) is non-trivial.
	// Exported as the single source of truth: the plan-exit hook mirrors these
	// for wording and a drift test asserts the copies stay equal.
	NonTrivialMinFiles = 2
	// NonTrivialMinSteps: a ~5+ step plan is non-trivial, matching the prime
	// "~5+ steps" criterion. H2 sections are the step proxy.
	NonTrivialMinSteps = 5
)
View Source
const (
	SessionOutcomeActive  = "active"
	SessionOutcomeStopped = "stopped"
	SessionOutcomeAborted = "aborted"
)

Session-outcome values for Provenance.SessionOutcome. "" == unknown.

View Source
const SchemaVersion = "v1"

SchemaVersion is the on-disk schema version stamped into both annotations.json (Result.SchemaVersion) and meta.json (Meta.SchemaVersion) on write. These are long-lived ledger artifacts a future ox may need to migrate; an explicit version lets a reader detect and adapt to an older layout instead of guessing. Bump this when the serialized shape of Result or Meta changes incompatibly.

Variables

This section is empty.

Functions

func AnchorFor added in v0.11.1

func AnchorFor(heading, text string) string

AnchorFor computes the review anchor for an element, given its enclosing section heading and its full text content — matching review.js anchorFor().

func AppendResolution

func AppendResolution(planDir string, r Resolution, now time.Time) error

AppendResolution adds one agent disposition to the append log. The whole read-modify-write runs under an advisory flock: the reviewer's Accept (via the review server) and the agent's `ox plan feedback resolve` are separate processes writing the same file, and an unlocked RMW loses whichever resolution lands first — a sacred-data loss, not a cosmetic race.

func ContestedAnchors added in v0.11.0

func ContestedAnchors(items []MergedItem) map[string]bool

ContestedAnchors returns the set of anchors where OPEN reviewer verdicts conflict — e.g. one reviewer approves while another requests a change. These are the marks a human authorizer must reconcile before the plan is safe to execute (ADR-026). Comment-only marks never contest; an approve alongside any change/flag does.

func CountOpenFeedback added in v0.11.0

func CountOpenFeedback(planDir string) int

CountOpenFeedback returns the number of OPEN, actionable review items for a plan dir — approvals don't count (they close, not open, the loop). Fail-open: 0 on any read error, so a discovery surface never breaks on one bad plan.

func FeedbackDigest

func FeedbackDigest(items []MergedItem) string

FeedbackDigest renders a compact, agent-readable summary from the merged view: open/addressed/verified/wontfix counts, then every OPEN actionable item with its anchor (the id to resolve), section, label, and note. Returns "" when there is no feedback.

func Load

func Load(gitRoot, slug string) (string, Result, PlanInfo, error)

Load reads a captured plan by slug. The slug matches either the meta.json slug or the directory's trailing slug segment (the YYYY-MM-DD- prefix is optional in the lookup). Returns the raw plan markdown, the stored Result, and the listing info.

func MutatePlanMeta

func MutatePlanMeta(ctx context.Context, planDir string, mutate func(*Meta) (*Meta, error)) error

MutatePlanMeta runs an exclusive read-modify-write under an advisory flock on a plan's meta.json — the direct mirror of lfs.MutateSessionMeta. Every write to meta.json AFTER the initial Save (status changes, session-outcome reconciliation) MUST go through this so a re-save and a concurrent session-stop/doctor write serialize at the filesystem level instead of clobbering each other.

The mutator receives the on-disk Meta (nil if the file is missing) and returns the Meta to write, or nil to leave the file untouched (an "only if exists" guard). Returning an error aborts the write.

func PlanHTMLPath

func PlanHTMLPath(dir string) (path string, ref lfs.FileRef, isPointer, exists bool)

PlanHTMLPath returns the absolute path to a captured plan's plan.html, the referenced FileRef when it is an LFS pointer, and whether the file exists. The view path uses this to decide between opening a plain HTML file and hydrating a pointer first.

func ReconcileSessionOutcome

func ReconcileSessionOutcome(gitRoot, slug, sessionID, outcome string) error

ReconcileSessionOutcome backfills the producing session's canonical id and final outcome onto a plan (by slug) at session-stop, where both are known. sessionID is the ses_<UUIDv7> minted at stop (skipped when ""); outcome is a SessionOutcome* constant. Single flocked write so it can't race a re-save.

func RecordPlanCraft added in v0.11.0

func RecordPlanCraft(rep CraftReport)

RecordPlanCraft emits the design-craft realization metric for a rendered plan: how many visual craft expectations ox produced (a diagram suggested, a user-facing surface detected) and how many the render actually realized. The hints_emitted vs hints_realized ratio, aggregated across the ledger, is the visual-enrichment "did the agent act on the hint" rate — the tachometer for whether enrichment changes what gets drawn, not just what gets suggested. No plan content is recorded, only counts and the unrealized rule names. A render with no craft expectation is silent (nothing to measure).

func RecordPlanGenerated

func RecordPlanGenerated(res Result, saved bool)

RecordPlanGenerated emits a single-line, key=value structured metric for a completed `ox plan` enrichment. It is purely local observability — there is no server LLM in this path, so there is nothing to meter server-side. The counts let us see, in aggregate, how often plans fire collision / prior-art / expert signals and how much context the bundle carried, without recording any plan content.

saved reports whether the enriched plan was captured to the ledger. It is a separate boolean (not derived from res) because auto-save is gated on config and on a configured ledger, independent of the signal summary.

func RegisterDetector

func RegisterDetector(d Detector)

RegisterDetector adds a deterministic detector to the global registry. Call from an init() in the detector's file. Nil detectors are ignored.

func RegisterRetriever

func RegisterRetriever(r Retriever)

RegisterRetriever adds a context-bundle retriever to the global registry. Call from an init() in the retriever's file. Nil retrievers are ignored.

func RenderHTML

func RenderHTML(in Input, res Result) ([]byte, error)

RenderHTML renders a resolved plan + its enrichment Result into a single self-contained HTML document. Deterministic and network-free at render time (Mermaid loads from CDN only when the page is viewed).

func RenderHTMLOpts

func RenderHTMLOpts(in Input, res Result, opts RenderOptions) ([]byte, error)

RenderHTMLOpts is RenderHTML with optional render-time context (e.g. the slug for the review layer). RenderHTML delegates here with zero options.

func RenderViz

func RenderViz(pattern string, data []byte) (string, error)

RenderViz renders one parameterized pattern from its JSON data into an HTML fragment. Returns an error for an unknown pattern or malformed data so the command layer can show an actionable message.

On a render failure (usually a JSON-shape mismatch), the error echoes the pattern's expected `param:` shape from the catalog so the caller — typically an AI coworker driving ox from inside another agent — can self-correct in one shot instead of guessing the schema. (Goose's Auto Visualiser, which has the agent supply the full chart spec, documented exactly this failure mode with no shape hint to recover from; ox supplies data only, and names the shape on a miss.)

func ReviewServiceWorkerJS added in v0.11.1

func ReviewServiceWorkerJS() ([]byte, error)

ReviewServiceWorkerJS returns the embedded service worker the live review server exposes at /sw.js — the offline shell that keeps a rendered plan readable in the browser after the server exits (review.js registers it).

func Save

func Save(gitRoot string, in Input, res Result, html []byte, meta Meta) (string, error)

Save writes a captured plan into the ledger under data/plans/<dated-slug>/. It writes plan.md (from in.Raw), annotations.json (res), and meta.json as plain git-tracked text. plan.html is written ONLY when html != nil: plain when small, as an LFS pointer when it exceeds htmlLFSThreshold. Save never renders HTML and never commits — it only materializes files in the working tree. Returns the absolute plan directory.

gitRoot is the producing project's git root; the ledger path is resolved from it via ProjectContext. Returns an error if no ledger is configured (the caller decides whether that is fatal — the porcelain path treats it as "nothing to save").

func SaveFeedback

func SaveFeedback(planDir string, set FeedbackSet, now time.Time) (string, error)

SaveFeedback writes a review round under <planDir>/feedback/. now controls the timestamp (tests stay deterministic). Returns the written path.

func SaveReviewServerState added in v0.11.1

func SaveReviewServerState(gitRoot, planDirName string, st ReviewServerState) error

SaveReviewServerState persists a plan's server identity (0600 — it carries the review token). Best-effort callers may ignore the error: without state the next run simply mints a fresh identity, which is the pre-existing behavior, not a data loss.

func SetSessionOutcome

func SetSessionOutcome(gitRoot, slug, outcome string) error

SetSessionOutcome reconciles the producing session's lifecycle onto the plan (by slug) under the meta flock — written only by session-stop / `ox doctor`, never by Save. Use the SessionOutcome* constants. No-op if no meta.json.

func SetStatus

func SetStatus(gitRoot, slug string, status PlanStatus) error

SetStatus updates a saved plan's lifecycle status (by slug) under the meta flock. No-op if the plan dir has no meta.json. Use the PlanStatus* constants.

func Slugify

func Slugify(topic string) string

Slugify derives a 2-4 word kebab-case slug from a topic/title. Lowercases, strips punctuation, and keeps the first 2-4 meaningful words. An empty or punctuation-only input yields "untitled-plan" so a directory name is always well-formed.

func StableReviewPort added in v0.11.1

func StableReviewPort(planDirName string) int

StableReviewPort maps a plan's dated dir name to its deterministic default port. Same plan → same port on every run, on every machine.

Types

type Annotation

type Annotation struct {
	Section string    `json:"section,omitempty"`
	Kind    BadgeKind `json:"kind"`
	Type    BadgeType `json:"type"`
	Why     string    `json:"why"`
	// HumanWhy is the curated, decision-first phrasing used in the HTML render. It
	// drops agent-only provenance noise that Why carries for the --json/agent path
	// (commit SHAs, relative timestamps like "12h ago", workspace counts). The
	// renderer prefers HumanWhy when non-empty; --json keeps the raw Why intact.
	// Empty means "use Why" — most annotations need no separate human phrasing.
	HumanWhy  string   `json:"human_why,omitempty"`
	SourceURL string   `json:"source_url,omitempty"`
	Expert    string   `json:"expert,omitempty"`
	Files     []string `json:"files,omitempty"`
	// Date, Summary, RefKind carry the structured prior-art fields the renderer
	// composes into a crisp "person · date · summary" link. Environment-
	// independent (no URL), so they're safe to emit in `ox plan enrich --json`;
	// the web URL is built only at render time from the local project config.
	Date    string `json:"date,omitempty"`
	Summary string `json:"summary,omitempty"`
	RefKind string `json:"ref_kind,omitempty"` // session | plan | murmur
}

Annotation is a single badge attached to a plan section.

type BadgeKind

type BadgeKind string

BadgeKind distinguishes who produces an annotation: ox locally (deterministic, zero tokens) versus the client agent (judgment, reasoned from the bundle).

const (
	// BadgeDeterministic: ox computes the badge locally with zero LLM tokens.
	BadgeDeterministic BadgeKind = "deterministic"
	// BadgeJudgment: the client agent authors the badge from the context bundle.
	BadgeJudgment BadgeKind = "judgment"
)

type BadgeType

type BadgeType string

BadgeType is the specific signal an annotation carries.

const (
	// BadgeCollision: plan touches files in an open PR, hotspot, or recent murmur.
	BadgeCollision BadgeType = "collision"
	// BadgePriorArt: a teammate already did or planned this.
	BadgePriorArt BadgeType = "prior-art"
	// BadgeExpertRoute: who owns this area + their relevant work (deterministic).
	BadgeExpertRoute BadgeType = "expert-routing"
	// BadgeAligns: plan aligns with ADRs, decisions, conventions (judgment).
	BadgeAligns BadgeType = "aligns"
	// BadgeConflicts: plan conflicts with ADRs, decisions, conventions (judgment).
	BadgeConflicts BadgeType = "conflicts"
	// BadgeExpertPersp: synthesized expert stance, cited (judgment).
	BadgeExpertPersp BadgeType = "expert-perspective"
	// BadgeRigor: collaboration-rigor stance synthesized from CollabSignals —
	// how thoughtful the human↔agent path to this plan was (judgment). ox emits
	// the raw counts (CollabSignals); the agent/cloud authors this badge.
	BadgeRigor BadgeType = "rigor"
)

type BrandingFinding

type BrandingFinding = Finding

BrandingFinding is retained as an alias so existing callers/tests keep compiling; new code should use Finding.

func LintBranding

func LintBranding(html []byte, res Result) []BrandingFinding

LintBranding verifies a rendered plan HTML carries the conditional SageOx attribution the html-plan skill is spec'd to produce. The contract (extensions/claude/skills/ox-plan/SKILL.md, "SageOx attribution — subtle, earned, conditional"):

  • EARNED: when the plan carried enrichment — any deterministic badges OR context-bundle items were present — the render MUST credit it: a footer line ("…enriched by SageOx") and, when there are deterministic badges, at least one anchored OX marker.
  • NO OVERCLAIM: an un-enriched plan (no badges, empty context) must NOT carry SageOx credit — there is nothing to credit.
  • SELF-CONTAINED: the OX marker's avatar must never be a live remote <img src>; it is data:-inlined or an inline-SVG monogram. Always checked.

Returns nil when the page satisfies the contract. Fail-open: callers warn, never block.

type CollabSignals

type CollabSignals struct {
	UserPrompts     int `json:"user_prompts"`     // distinct human turns before the plan
	AgentQuestions  int `json:"agent_questions"`  // AskUserQuestion / clarifying tool calls
	ToolCalls       int `json:"tool_calls"`       // exploration-depth proxy
	DurationSeconds int `json:"duration_seconds"` // first user prompt → plan finalized
}

CollabSignals are deterministic, locally-counted facts about the human↔agent collaboration that produced the plan — effort proxies, NOT a score. Scoring (a rigor judgment) is authored by the agent now / a cloud judge later, per ADR-021. Signal COUNTS (collisions/prior-art/expert-routes) deliberately live in annotations.json (Result.Signals), not here, to avoid duplication.

type ContextItem

type ContextItem struct {
	Kind    string  `json:"kind"` // murmur|session|decision|adr|commit|discussion
	Title   string  `json:"title"`
	Ref     string  `json:"ref"`
	Snippet string  `json:"snippet,omitempty"`
	Score   float64 `json:"score"`
	Author  string  `json:"author,omitempty"`
	When    string  `json:"when,omitempty"`
}

ContextItem is one ranked, pre-retrieved slice of ledger / team context / code the client agent reasons over to author judgment badges.

type CraftReport added in v0.11.0

type CraftReport struct {
	Emitted  int
	Realized int
	Gaps     []Finding
}

CraftReport is the realization side of the design-craft check: how many craft expectations enrich produced (a diagram suggested, a user-facing surface detected) and how many the rendered page realized. The render path records it as the `plan_craft` metric — hints_emitted vs hints_realized, aggregated across the ledger, is the "did the agent act on the visual hint" rate — and surfaces the unrealized Gaps as advisory nudges.

func CraftRealization added in v0.11.0

func CraftRealization(res Result, htmlBytes []byte) CraftReport

CraftRealization compares what ox EXPECTED (computed at enrich, surfaced cross-agent in Result.Guidance) against what the page DREW. Detection lives at enrich (DiagramHints / MockupSection); this is the thin, belt-and-suspenders realization check for an agent already in the ox render flow — NOT the primary cross-agent lever. Fail-open: an empty page yields a zero report. Precision over recall: a diagram expectation is met by ANY visual (a chart counts — "show, don't tell" holds even when the form differs from the hint), so a well-visualized plan is never nagged for the wrong diagram shape.

type Detector

type Detector interface {
	Name() string
	Detect(ctx context.Context, in Input, gitRoot string) ([]Annotation, error)
}

Detector produces deterministic annotations from local data. MUST be fail-open: on missing/unreadable data return (nil, nil), never an error that aborts enrichment.

type DiagramHint

type DiagramHint struct {
	Section       string      `json:"section"`        // H2 heading the hint applies to
	SuggestedType DiagramKind `json:"suggested_type"` // the diagram form that fits
	Reason        string      `json:"reason"`         // what structure was detected, in one clause
}

DiagramHint is a deterministic, per-section suggestion of which diagram form best captures the structure ox detected in that section. Rendering an HTML plan is now deterministic and free, so the only remaining lever on diagram QUALITY is the Mermaid the agent authors into the plan markdown — these hints point any agent (Claude, Codex, Gemini, …) at the right diagram for THIS plan, per section, instead of defaulting every section to a flowchart. Computed locally with zero LLM/network calls, same lane as the badge detectors.

type DiagramKind

type DiagramKind string

DiagramKind is a suggested diagram form for a plan section. The values are the literal Mermaid diagram keyword (or "swimlane-timeline" for the hand-built CSS timeline) so the agent can paste the suggestion straight into a fenced block.

const (
	DiagramSequence  DiagramKind = "sequenceDiagram"   // ordered call/response path
	DiagramState     DiagramKind = "stateDiagram-v2"   // states + time-bounded transitions
	DiagramSwimlane  DiagramKind = "swimlane-timeline" // phased/parallel work (CSS, not Mermaid)
	DiagramTopology  DiagramKind = "flowchart-LR"      // dependency/topology graph
	DiagramFlowchart DiagramKind = "flowchart-TB"      // branching procedure (hero default)
)

type FeedbackItem

type FeedbackItem struct {
	Anchor   string         `json:"anchor"`             // stable content-hash id, e.g. "h3f9a1c2"
	Section  string         `json:"section,omitempty"`  // section heading the element sits under
	Label    string         `json:"label"`              // short text of the element
	Status   FeedbackStatus `json:"status"`             // approve | request-change | flag | comment
	Note     string         `json:"note,omitempty"`     // the reviewer's comment
	Reviewer string         `json:"reviewer,omitempty"` // who left this mark (multi-user); stamped from the round on save
}

FeedbackItem is one anchored review mark. Anchor is a CONTENT hash of the element (section heading + element text), computed page-side, so it survives a re-render and only disappears when the agent rewrites that text — which is itself the signal the item was addressed. Anchor doubles as the item id used by `ox plan feedback resolve`.

type FeedbackSet

type FeedbackSet struct {
	Slug      string         `json:"slug"`
	Reviewer  string         `json:"reviewer,omitempty"`
	CreatedAt time.Time      `json:"created_at"`
	Items     []FeedbackItem `json:"items"`
}

FeedbackSet is one review round (one submit from the page).

func LoadAllFeedback

func LoadAllFeedback(planDir string) ([]FeedbackSet, error)

LoadAllFeedback reads every review round under a plan dir, oldest first. A missing feedback/ dir is not an error. resolutions.json is skipped (it is not a round).

func ParseFeedback

func ParseFeedback(raw []byte) (FeedbackSet, error)

ParseFeedback decodes and validates a review-round JSON (the page submit/export). Fail-loud on malformed input, an unknown status, or an unsafe slug.

type FeedbackStatus

type FeedbackStatus string

FeedbackStatus is the reviewer's verdict on one anchored element.

const (
	FeedbackApprove       FeedbackStatus = "approve"
	FeedbackRequestChange FeedbackStatus = "request-change"
	FeedbackFlag          FeedbackStatus = "flag"
	FeedbackComment       FeedbackStatus = "comment"
)

type Finding

type Finding struct {
	Rule    string // stable id, e.g. "branding.footer-credit" / "mermaid.arrow-in-label"
	Message string // human-readable, actionable
}

Finding is one advisory lint result on a rendered plan HTML — attribution (branding.*) or diagram (mermaid.*). All findings are warn-level: linting NEVER blocks a render or a save (fail-open agent UX). A non-empty slice means the render did not honor the html-plan contract or carries a diagram that will not render.

func LintArtifact added in v0.11.0

func LintArtifact(htmlBytes []byte) []Finding

LintArtifact verifies a rendered plan HTML is safe to publish as a Claude Code Artifact: strictly self-contained, with no construct that the artifact CSP would block at view time. It checks resource loads (external script/style/ font/img, CSS url() to a remote host) and the SSE review layer (EventSource). It deliberately does NOT flag <a href="https://…"> — outbound navigation is allowed, and the SageOx enrichment links rely on it. Fail-open: an empty page returns nil; callers warn, never block.

func LintCraft added in v0.11.0

func LintCraft(res Result, htmlBytes []byte) []Finding

LintCraft is the advisory view of CraftRealization — the unrealized craft gaps, printed (never blocking) after a render.

func LintMermaid

func LintMermaid(htmlBytes []byte) []Finding

LintMermaid extracts every Mermaid diagram from a rendered/saved plan HTML and returns one Finding per high-confidence problem. Fail-open: no diagrams (or none broken) returns nil.

func LintMermaidMarkdown

func LintMermaidMarkdown(raw string) []Finding

LintMermaidMarkdown is the same check over raw plan markdown (```mermaid fences), for the render-time path where the source is in hand before the page is built.

func LintRender

func LintRender(htmlBytes []byte, res Result) []Finding

LintRender runs the full advisory contract over a rendered plan HTML: SageOx attribution (LintBranding) plus diagram validity (LintMermaid). It is the single entrypoint `ox plan lint` / `ox plan save` call. Fail-open: an empty page returns nil.

type Input

type Input struct {
	Path     string
	Raw      string
	Sections []Section
}

Input is a resolved plan: its source path (if any), raw markdown, and parsed sections.

func Parse

func Parse(raw string) Input

Parse splits markdown into Sections on "## " H2 headings and extracts the file references cited in each section. Content before the first H2 becomes a preamble Section with an empty Heading (only emitted when it has content).

func Resolve

func Resolve(file string, stdin io.Reader) (Input, error)

Resolve reads a plan from --file if set, otherwise from a piped stdin, and parses it into an Input. When neither is provided it best-effort auto-discovers the active plan-mode file: the newest *.md under ~/.claude/plans/ (the dir Claude Code plan-mode writes to). Precedence is --file > piped stdin > auto-discovery. An empty/unfound source yields an Input with empty Raw and no sections (the enrich path must stay fail-open on empty input); the caller is expected to surface a clear "no plan found" message rather than enrich nothing.

type MergedItem

type MergedItem struct {
	FeedbackItem
	RaisedAt   time.Time
	Resolution *Resolution
	Open       bool
	// RemappedFrom is the item's original anchor when a plan update moved its
	// content and the save-time remap rebound it (see remap.go). Empty when the
	// mark still lives at its original address. Anchor always holds the CURRENT
	// address — the one `ox plan feedback resolve` acts on.
	RemappedFrom string `json:"remapped_from,omitempty"`
}

MergedItem is a review item joined with its latest resolution and a computed open/closed state. Open = no resolution, or the item was re-raised after the last resolution (CreatedAt newer than the resolution's At).

func AssembleReview

func AssembleReview(planDir string) ([]MergedItem, error)

AssembleReview joins every review item (latest mark per anchor across rounds) with its latest resolution, computing open/closed. This is the single source the digest and the render read. An item is OPEN when it has no resolution, or when it was re-raised after the latest resolution (supporting the verify loop).

Anchors are CANONICALIZED through the remap chain first (see remap.go): a mark whose content moved when the plan was updated is merged, resolved, and rendered at its current address, with the original preserved in RemappedFrom. Rounds on disk are never rewritten — the chain is applied at read time.

type Meta

type Meta struct {
	// SchemaVersion stamps the meta.json shape (set to SchemaVersion on write)
	// so a future reader can detect and migrate an older layout.
	SchemaVersion  string    `json:"schema_version,omitempty"`
	Topic          string    `json:"topic"`
	Slug           string    `json:"slug"`
	Authors        []string  `json:"authors,omitempty"`
	CreatedAt      time.Time `json:"created_at"`
	SourcePlanPath string    `json:"source_plan_path,omitempty"`

	// Status is the plan's lifecycle. Missing == "draft" for legacy plans.
	Status PlanStatus `json:"status,omitempty"`
	// Provenance links the plan to its producing session/agent/repo (forward).
	Provenance *Provenance `json:"provenance,omitempty"`
	// Collaboration holds the deterministic collaboration-effort counts.
	Collaboration *CollabSignals `json:"collaboration,omitempty"`
}

Meta is the git-tracked descriptor written as meta.json alongside a captured plan. It carries the searchable, hydration-free facts about the plan: who authored it, when, where it came from, which session/agent produced it, and how thoughtful the collaboration was.

func LoadMeta added in v0.11.0

func LoadMeta(planDir string) (Meta, error)

LoadMeta reads and parses a captured plan's meta.json from its plan directory (PlanInfo.Dir). Exported so the cmd layer can read provenance — e.g. the authoring agent id, to notify that coworker when review feedback arrives — without re-deriving the on-disk path. A missing/unreadable meta is an error.

func ReadPlanMeta

func ReadPlanMeta(gitRoot, slug string) (Meta, error)

ReadPlanMeta returns the stored Meta for a saved plan (by slug), including provenance, collaboration signals, and status. Used by the view path to surface the link without re-deriving it.

type OpenFeedbackSummary added in v0.11.0

type OpenFeedbackSummary struct {
	Slug      string `json:"slug"`
	Topic     string `json:"topic,omitempty"`
	Open      int    `json:"open"`                 // open, actionable items (approvals excluded)
	AgentType string `json:"agent_type,omitempty"` // authoring coworker type, if recorded
	Dir       string `json:"-"`                    // absolute plan dir (local only)
}

OpenFeedbackSummary is one saved plan with unaddressed human review feedback waiting — the unit a discovery surface (ox plan list, ox agent prime) lists so the feedback is findable even when the push notification missed.

func OpenFeedbackPlans added in v0.11.0

func OpenFeedbackPlans(gitRoot string) ([]OpenFeedbackSummary, error)

OpenFeedbackPlans returns every saved plan in the project's ledger that has open review feedback, newest first. It is the PULL half of the feedback loop: the push (a plan-feedback agent-task) can miss — wrong agent type, a failed queue write, an unlinked plan, or simply a human choosing to look — so this reads the ledger directly and never depends on the queue. Fail-open: an unreadable plan dir is skipped, not fatal.

type PlanInfo

type PlanInfo struct {
	Slug      string
	Topic     string
	Dir       string
	CreatedAt time.Time
	Authors   []string
	HasHTML   bool
}

PlanInfo is the listing-level view of a captured plan, assembled from meta.json. Dir is the absolute path to the plan folder.

func List

func List(gitRoot string) ([]PlanInfo, error)

List enumerates captured plans under <ledger>/data/plans/, parsing each meta.json. Fail-open: an unconfigured or missing ledger yields an empty slice (not an error), matching the detectors' fail-open contract. Results are sorted newest-first by CreatedAt.

type PlanStatus

type PlanStatus string

PlanStatus is the plan's own lifecycle, independent of the producing session. A plan is worth keeping even if never built — it is a decision record and prior-art seed — so status lets UI/search weight rather than discard. v1: a plain writable field; no CLI auto-detection of "implemented" (that correlation is inference and belongs in the cloud judge per ADR-021).

const (
	PlanStatusDraft       PlanStatus = "draft"
	PlanStatusApproved    PlanStatus = "approved" // reviewer signed off via the review loop
	PlanStatusImplemented PlanStatus = "implemented"
	PlanStatusAbandoned   PlanStatus = "abandoned"
	PlanStatusSuperseded  PlanStatus = "superseded"
)

type Provenance

type Provenance struct {
	// Join keys (may dangle if the session was aborted / never uploaded).
	//
	// Two-phase population, because the canonical ses_ SessionID is minted
	// fresh at session-STOP and is NOT knowable mid-recording:
	//   - SessionName is the durable identifier available at plan-save time
	//     (the recording's folder name, what `ox session view <name>` resolves).
	//     It is the primary join key and is always set when a recording is live.
	//   - SessionID (ses_<UUIDv7>) is BACKFILLED at session-stop, in the same
	//     reconciliation that sets SessionOutcome=stopped — we have the real id
	//     and the produced-plan slugs in hand there. Empty for aborted sessions
	//     (no stop) and for plans saved outside a recording.
	SessionName string `json:"session_name,omitempty"`
	SessionID   string `json:"session_id,omitempty"` // ses_<UUIDv7>, backfilled at stop
	AgentID     string `json:"agent_id,omitempty"`   // Ox#### stable agent instance
	RepoID      string `json:"repo_id,omitempty"`

	// Denormalized snapshot — renders without the session present.
	AgentType  string `json:"agent_type,omitempty"` // claude-code, codex, ...
	Model      string `json:"model,omitempty"`
	AuthorName string `json:"author_name,omitempty"` // privacy-safe display name at save time

	// SessionOutcome is RECONCILED SYSTEM STATE, not authored provenance:
	// "" (unknown) | "active" | "stopped" | "aborted". Written only by
	// session-stop / `ox doctor` through MutatePlanMeta, never by Save.
	SessionOutcome string `json:"session_outcome,omitempty"`
}

Provenance ties a saved plan back to the session/agent/repo that produced it. It is DENORMALIZED on purpose: the join keys (SessionID/AgentID/RepoID) are the precise link, but a session can be aborted, never uploaded, or GC'd, so the snapshot fields (AgentType/Model/AuthorName) let a plan render fully without the session present. Duplication is the feature, not a smell.

type RemapEntry added in v0.11.1

type RemapEntry struct {
	From    string    `json:"from"`              // anchor that vanished from the render
	To      string    `json:"to"`                // anchor of the element it was rebound to
	Section string    `json:"section,omitempty"` // new element's section heading
	Label   string    `json:"label,omitempty"`   // new element's label
	Method  string    `json:"method"`            // label-exact | label-fuzzy
	Score   float64   `json:"score"`             // similarity that justified the rebind
	At      time.Time `json:"at"`
}

RemapEntry records one anchor rebind. Append-only, alongside rounds and resolutions, so the full history of where a mark lived stays in the ledger.

func LoadRemaps added in v0.11.1

func LoadRemaps(planDir string) ([]RemapEntry, error)

LoadRemaps reads the append log of anchor rebinds. Missing file is empty.

func RemapFeedback added in v0.11.1

func RemapFeedback(planDir string, htmlBytes []byte, now time.Time) ([]RemapEntry, error)

RemapFeedback re-anchors open review items onto a freshly rendered plan. html is the new render (the same bytes being saved as plan.html). Returns the rebinds it recorded; an empty slice means every open item still anchors (or nothing was confidently rebindable). Fail-soft by design: called from Save, where feedback durability must never block the plan write itself.

type RenderOptions

type RenderOptions struct {
	Slug string
	// Review is the merged review state (rounds + resolutions) for this plan, so
	// the render can show each item's open/addressed state inline and in a
	// summary. Empty for a plan with no review yet.
	Review []MergedItem
	// ReviewEndpoint + ReviewToken are set ONLY when the page is served by the
	// ephemeral `ox plan review` server: the page POSTs marks to the endpoint
	// with the token. Empty for a static file:// render (clipboard fallback).
	ReviewEndpoint string
	ReviewToken    string
	// PriorArtURL resolves a prior-art source (its kind + ref/slug) to a SageOx
	// web URL, opened in a new tab from the enrichment panel. Nil-safe: when nil
	// or when it returns "", the prior-art entry renders as crisp text with no
	// link. This is the seam that keeps internal/plan config-agnostic — the
	// command layer builds the closure from the local project config, and
	// `ox plan enrich --json` (no config) never embeds an environment URL.
	PriorArtURL func(refKind, ref string) string
	// Artifact renders a strictly self-contained page with NO external resource
	// requests, suitable for publishing as a Claude Code Artifact (served under a
	// strict CSP that blocks all cross-origin script/style/font/img and all
	// fetch/XHR/WebSocket). In this mode the Google-Fonts <link> is dropped (the
	// font stacks fall back to system fonts), the SSE review layer is omitted, and
	// the Mermaid CDN <script> is replaced by the vendored library inlined in
	// place (only when the plan carries a diagram), so diagrams render at full
	// parity with zero network. The SageOx enrichment reference links are
	// preserved — top-level <a href> navigation is not CSP-blocked, so a published
	// artifact stays a hub back into the Ledger.
	Artifact bool
}

RenderOptions carries optional render-time context that isn't part of the enrichment Result.

type Resolution

type Resolution struct {
	Anchor string          `json:"anchor"`           // the item it resolves
	State  ResolutionState `json:"state"`            // addressed | wontfix | verified
	Commit string          `json:"commit,omitempty"` // commit SHA that made the change
	Note   string          `json:"note,omitempty"`   // what the agent did / why wontfix
	At     time.Time       `json:"at"`
}

Resolution is one agent disposition of a review item, append-logged.

func LoadResolutions

func LoadResolutions(planDir string) ([]Resolution, error)

LoadResolutions reads the append log (latest entries last). Missing is empty.

type ResolutionState

type ResolutionState string

ResolutionState is the agent's disposition of a review item.

const (
	ResolutionAddressed ResolutionState = "addressed" // agent made the change
	ResolutionWontfix   ResolutionState = "wontfix"   // agent declined, with reason
	ResolutionVerified  ResolutionState = "verified"  // human confirmed the fix
)

type Result

type Result struct {
	// SchemaVersion stamps the serialized annotations.json shape (set to
	// SchemaVersion on write) so a future reader can detect an older layout.
	SchemaVersion string        `json:"schema_version,omitempty"`
	Annotations   []Annotation  `json:"annotations"`
	Context       []ContextItem `json:"context"`
	Signals       SignalSummary `json:"signals"`
	// DiagramHints are deterministic per-section diagram suggestions (which
	// Mermaid/timeline form fits the structure ox detected). Empty when no
	// section had strong enough structure to suggest one.
	DiagramHints []DiagramHint `json:"diagram_hints,omitempty"`
	// VizHints are deterministic per-section data-visualization suggestions
	// (which parameterized catalog pattern fits — risk-matrix, file-impact-map,
	// …), each renderable via `ox plan viz render <id> --data`. Empty when no
	// section matched a pattern's use: signal strongly enough.
	VizHints []VizHint `json:"viz_hints,omitempty"`
	// MockupSection is the heading of the section that changes a user-facing
	// surface (one the reader sees on screen), or "" when the plan changes nothing
	// visible. Detected deterministically at enrich (mockupCues, precision-gated
	// like the diagram hints) and surfaced in Guidance so every agent is told to
	// show a mockup pre-authoring; the render-time craft lint only checks it was
	// realized. Environment-independent, so it's safe in `ox plan enrich --json`.
	MockupSection string `json:"mockup_section,omitempty"`
	// Guidance is a concise, cross-agent authoring contract for rendering a
	// fantastic HTML plan (decision-first, ten-minute reader, diagrams over
	// prose). It folds in the DiagramHints so the agent gets plan-specific
	// direction, not a generic spec. Empty for a trivial/empty plan.
	Guidance string `json:"guidance,omitempty"`
}

Result is the full output of Enrich: deterministic annotations, the context bundle, and the signal summary.

func Enrich

func Enrich(ctx context.Context, in Input, gitRoot string) Result

Enrich runs every registered detector and retriever against the plan, FAIL-OPEN: a panic or error in any one detector/retriever is logged and skipped, never aborting the others. It aggregates the annotations and context items, computes a deterministic SignalSummary, and returns a sorted, deduped Result.

ox makes NO network or LLM call here — detectors and retrievers read only local data. Round 2 owns their implementations.

type Retriever

type Retriever interface {
	Name() string
	Retrieve(ctx context.Context, in Input, gitRoot string) ([]ContextItem, error)
}

Retriever produces context-bundle items. Also fail-open.

type ReviewServerState added in v0.11.1

type ReviewServerState struct {
	Port  int    `json:"port"`  // last port actually served on
	Token string `json:"token"` // review token the served pages carry
}

ReviewServerState is the persisted identity of a plan's review server.

func LoadReviewServerState added in v0.11.1

func LoadReviewServerState(gitRoot, planDirName string) (ReviewServerState, bool)

LoadReviewServerState reads a plan's persisted server identity. ok is false when there is none (first review, no ledger, or unreadable state).

type Section

type Section struct {
	Heading string
	Body    string
	Files   []string
}

Section is one H2-delimited block of a plan, with any file references it cites.

type SignalSummary

type SignalSummary struct {
	Collisions   int  `json:"collisions"`
	PriorArt     int  `json:"prior_art"`
	ExpertRoutes int  `json:"expert_routes"`
	Material     bool `json:"material"`
	Files        int  `json:"files"`
	Steps        int  `json:"steps"`
	NonTrivial   bool `json:"non_trivial"`
}

SignalSummary is the deterministic rollup of which signals fired.

Material is the TEAM-CONTEXT axis: true when the plan warrants surfacing a nudge because team context had something to say (any collision OR expert-route OR at least one strong prior-art hit).

NonTrivial is the STRUCTURAL axis, independent of team context: true when the plan is substantial enough to warrant an enriched HTML render for human review even on greenfield work where zero team-context signals fire — multi-file (Files >= 2) OR many-step (Steps >= 5). Files counts distinct file references cited across all sections; Steps counts H2 sections (excluding the preamble). These mirror the prime non-triviality criteria; hotspot/open-PR is already covered by Material, and "architectural" is left to agent judgment.

type VizHint added in v0.11.0

type VizHint struct {
	Section   string `json:"section"`         // H2 heading the hint applies to
	PatternID string `json:"pattern_id"`      // catalog id, e.g. "risk-matrix" (renderable via `ox plan viz render`)
	Reason    string `json:"reason"`          // the trigger terms matched, in one clause
	Param     string `json:"param,omitempty"` // the pattern's `param:` JSON skeleton to fill and render
}

VizHint is the data-visualization counterpart of DiagramHint: a per-section suggestion of which PARAMETERIZED catalog pattern (one with a deterministic `ox plan viz render` renderer — risk-matrix, file-impact-map, cost-waterfall, stat-cards, flag-rollout-matrix, …) fits a section. It closes the gap that DiagramHint only covers Mermaid/CSS diagram FORMS, leaving the data-viz catalog invisible to content-aware matching. The match signal is DERIVED from each pattern's `use:` line (no separate catalog field) — see computeVizHints.

Param carries the matched pattern's `param:` JSON skeleton so the agent goes straight from section→pattern→fill-in-the-blanks, instead of round-tripping `ox plan viz <id>` to recall the shape. This is the most "auto" an autovisualizer can be from INSIDE another coding agent (Claude, Codex, …): ox can't render into a host surface it doesn't own, but it can pre-stage the exact data shape to fill.

type VizPattern

type VizPattern struct {
	ID    string `json:"id"`              // stable slug, e.g. "sparkline"
	Use   string `json:"use"`             // when to reach for it
	Why   string `json:"why"`             // the cognitive payoff
	Param string `json:"param,omitempty"` // data-shape hint when `ox plan viz render <id> --data` is supported
	Body  string `json:"body"`            // copy-paste snippet(s) + any notes
}

VizPattern is one catalog entry.

func VizCatalog

func VizCatalog() []VizPattern

VizCatalog parses and returns every visualization pattern, in document order.

func VizPatternByID

func VizPatternByID(id string) (VizPattern, bool)

VizPatternByID returns the pattern with the given id (case-insensitive), or ok=false when none matches.

Jump to

Keyboard shortcuts

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