status

package
v0.8.1 Latest Latest
Warning

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

Go to latest
Published: May 21, 2026 License: Apache-2.0 Imports: 21 Imported by: 0

Documentation

Overview

Package status implements the `aiwf status` verb (per-verb subpackage of M-0116).

Index

Constants

View Source
const RecentActivityLimit = 5

RecentActivityLimit is the number of recent commits surfaced by `aiwf status`'s "Recent activity" section. Five fits in a glance and answers "what changed lately?" without scrolling. For longer history, fall through to `aiwf history <id>`.

Variables

This section is empty.

Functions

func MdEscape

func MdEscape(s string) string

MdEscape escapes the four characters that break a markdown table row or a mermaid label: pipe, backtick, the bracket pair (mermaid uses them for node syntax), and the double-quote (mermaid uses it to delimit labels). Newlines are stripped so a single field stays on one row. Conservative; not a full markdown sanitizer.

func NewCmd

func NewCmd() *cobra.Command

NewCmd builds `aiwf status`: a project-wide snapshot of in-flight work, open decisions, open gaps, and recent activity. Read-only; produces no commit. Use it to answer "what's next?", "where are we?", "what are we working on?". With --worktrees, swaps the output to a worktree-organized layout (G-0122): per-worktree section, full epic expansion for epic-branch worktrees, stale/trunk catch-alls.

func ReadRecentActivity

func ReadRecentActivity(ctx context.Context, root string, limit int) ([]history.HistoryEvent, error)

ReadRecentActivity returns the last `limit` commits whose message carries any `aiwf-verb:` trailer. Cross-entity, no filter — used by `aiwf status` to answer "what changed lately?" across the whole project. Events come back newest-first.

The `--grep` is an I/O-narrowing pre-filter; correctness is gated on Git's structured trailer parser (`%(trailers:key=…,valueonly=…)`). Hand-authored prose that wraps such that a line happens to start with `aiwf-verb:` would match the grep but produce an empty parsed Verb column — those records are skipped (G30). The grep over a long history is also asked for more rows than `limit` so the post-filter doesn't silently shrink the result; we then truncate.

func RenderACProgress

func RenderACProgress(p *StatusACProgress) string

RenderACProgress formats the AC progress badge appended to a milestone row. Returns "" when there are no ACs (so the renderer can skip the separator). Format:

"ACs 2/3 met"           — typical case, in-scope total ≥ 1
"ACs 1/2 met (1 open)"  — when there are still open ACs
"ACs all cancelled"     — every AC was cancelled (in-scope = 0)

func RenderStatusMarkdown

func RenderStatusMarkdown(w io.Writer, r *StatusReport) error

RenderStatusMarkdown writes the status report as a self-contained markdown document, with mermaid `flowchart` blocks for in-flight and roadmap epics. The output renders unchanged in any markdown viewer that supports mermaid (GitHub web, VSCode, Obsidian, glow + mermaid extension, etc.). Plain markdown — no HTML, no JS.

func RenderStatusText

func RenderStatusText(w io.Writer, r *StatusReport, termWidth int, colorEnabled bool) error

RenderStatusText writes the human-readable status report to w. The in-progress milestone gets a `→` prefix; done a `✓`; draft a `○`; cancelled a `✗` — the four-glyph G-0080 palette, applied so every milestone row carries a visible state marker. Empty sections render with a parenthesised "(none)" so a glance can see "yes there are open decisions" without counting bullets. Builds the full output in a strings.Builder and writes once so the only error to surface is the final write.

termWidth caps title widths to keep rows on one visual line when stdout is a TTY narrower than the natural row; pass 0 to disable truncation (default in tests, in pipes, and under --no-trunc). colorEnabled toggles ANSI-bold section labels; row content stays escape-free so downstream tooling (grep, awk) sees plain text. The glyph palette is content, not style, and appears regardless.

func RenderWorktreeViews

func RenderWorktreeViews(w io.Writer, views []WorktreeView, colorEnabled bool) error

RenderWorktreeViews writes one section per worktree to w. Each section carries the worktree path as a header, the branch on its own line prefixed with the bold ⎇ glyph, the driver entity row, and any kind-specific expansion (milestones+gaps under an epic, parent-epic breadcrumb + ACs under a milestone). Stale and trunk worktrees use the same shape with a one-line marker line — no separate top-level grouping.

G-0122.

func Run

func Run(root, format string, pretty, noTrunc, worktrees bool) int

Run executes `aiwf status`. Returns one of the cliutil.Exit* codes. When worktrees is true, the output switches to the G-0122 worktree- organized layout (text format only renders the worktree sections; json format adds a `worktrees` key to the result envelope; md format ignores the flag for now).

func TruncStatusTitle

func TruncStatusTitle(title string, termWidth int, prefix, tail string) string

TruncStatusTitle caps title to fit termWidth given the non-title prefix/tail of the line. Returns title unchanged when termWidth is 0 (no TTY / --no-trunc) or when the available room would fall below the per-G-0080 minimum useful column width. The minimum (10 runes) is the same floor renderListRowsText uses — see list.MinTitleColumnRunes.

func WriteStatusEpicMarkdown

func WriteStatusEpicMarkdown(b *strings.Builder, e StatusEpic)

WriteStatusEpicMarkdown writes one epic — header, milestone list, and a mermaid `flowchart LR` keyed by milestone status — into b. Empty milestone lists render an explicit "(no milestones)" line so the section stays visually balanced and the diagram is omitted (mermaid barfs on a flowchart with one node and no edges).

func WriteStatusEpicText

func WriteStatusEpicText(b *strings.Builder, e StatusEpic, termWidth int)

WriteStatusEpicText writes one epic plus its milestones in the terminal-friendly shape shared by the In flight and Roadmap sections. termWidth caps title widths so long titles don't wrap into the next row's column-zero (the G-0080 visual-scan bug); 0 disables truncation.

Milestone rows lead with a glyph from the G-0080 palette so every row carries a visible state marker — the in-progress and done glyphs (→ ✓) have always been present; this function also emits ○ for draft and ✗ for cancelled so the four-glyph palette is uniformly applied across all milestone states.

Types

type ACRow

type ACRow struct {
	ID       string `json:"id"`
	Title    string `json:"title"`
	Status   string `json:"status"`
	TDDPhase string `json:"tdd_phase,omitempty"`
}

ACRow is one acceptance-criterion row under a milestone-driver worktree. Status is the AC's status (open / met / cancelled / deferred); TDDPhase is the optional phase (red / green / refactor / done) when the parent milestone is `tdd: required` / `advisory`.

type EpicChildRow

type EpicChildRow struct {
	ID           string `json:"id"`
	Title        string `json:"title"`
	Status       string `json:"status"`
	DrivenByPath string `json:"driven_by_path,omitempty"` // worktree path driving this child, if any (omit when self-driven)
}

EpicChildRow is one row under an epic-driver worktree's expanded listing: a milestone or a gap the epic owns / closes.

type OtherInFlightRow

type OtherInFlightRow struct {
	ID         string    `json:"id"`
	Title      string    `json:"title"`
	Status     string    `json:"status"`
	Branch     string    `json:"branch,omitempty"`      // empty = no dedicated branch (work on trunk)
	BranchTime time.Time `json:"branch_time,omitempty"` // HEAD commit time on the branch (zero when no branch)
}

OtherInFlightRow is one in-flight entity that no worktree is driving — either a dedicated branch exists but is not checked out anywhere (Branch != ""), or no dedicated branch exists at all and work happens directly on trunk (Branch == ""). Rendered under the main-checkout worktree's section per G-0122 user feedback "work might be on a branch but not on a worktree".

type StatusACProgress

type StatusACProgress struct {
	Total     int `json:"total"`
	InScope   int `json:"in_scope"`
	Open      int `json:"open"`
	Met       int `json:"met"`
	Deferred  int `json:"deferred"`
	Cancelled int `json:"cancelled"`
}

StatusACProgress is the per-status count of a milestone's ACs. `Total` includes cancelled entries (they remain in the list per the position-stability rule); `InScope` excludes them, so that's the denominator the renderers use for "M/T met" progress.

func SummarizeACs

func SummarizeACs(acs []entity.AcceptanceCriterion) *StatusACProgress

SummarizeACs returns the per-status counts for a milestone's acs[]. Returns nil when the slice is empty so the renderer can skip the "ACs: …" suffix entirely on milestones that don't carry any.

type StatusEntity

type StatusEntity struct {
	ID     string `json:"id"`
	Title  string `json:"title"`
	Status string `json:"status"`
	Kind   string `json:"kind"`
}

StatusEntity is the shared shape for ADRs and decisions in the "Open decisions" section.

type StatusEpic

type StatusEpic struct {
	ID         string            `json:"id"`
	Title      string            `json:"title"`
	Status     string            `json:"status"`
	Milestones []StatusMilestone `json:"milestones"`
}

StatusEpic is one in-flight epic plus every milestone under it.

type StatusFinding

type StatusFinding struct {
	Code     string `json:"code"`
	EntityID string `json:"entity_id,omitempty"`
	Path     string `json:"path,omitempty"`
	Message  string `json:"message"`
}

StatusFinding is one warning surfaced inline in the status report. Mirrors the load-bearing fields of check.Finding without coupling the JSON shape to the validator package's internal schema.

type StatusGap

type StatusGap struct {
	ID           string `json:"id"`
	Title        string `json:"title"`
	DiscoveredIn string `json:"discovered_in,omitempty"`
}

StatusGap is one open gap with the milestone or epic it was discovered in (if any).

type StatusHealthCounts

type StatusHealthCounts struct {
	Entities int `json:"entities"`
	Errors   int `json:"errors"`
	Warnings int `json:"warnings"`
}

StatusHealthCounts summarizes the tree's current validation state without re-running expensive checks; pulled from a single check.Run.

type StatusMilestone

type StatusMilestone struct {
	ID     string            `json:"id"`
	Title  string            `json:"title"`
	Status string            `json:"status"`
	TDD    string            `json:"tdd,omitempty"`
	ACs    *StatusACProgress `json:"acs,omitempty"`
}

StatusMilestone is one milestone under an in-flight epic, with the in-progress one identifiable by Status. The TDD and ACs fields carry the I2 acceptance-criteria surface; ACs is omitted from JSON when the milestone carries none (zero progress).

type StatusReport

type StatusReport struct {
	Date           string                 `json:"date"`
	InFlightEpics  []StatusEpic           `json:"in_flight_epics"`
	PlannedEpics   []StatusEpic           `json:"planned_epics"`
	OpenDecisions  []StatusEntity         `json:"open_decisions"`
	OpenGaps       []StatusGap            `json:"open_gaps"`
	Warnings       []StatusFinding        `json:"warnings"`
	RecentActivity []history.HistoryEvent `json:"recent_activity"`
	SweepPending   *StatusSweepPending    `json:"sweep_pending,omitempty"`
	Health         StatusHealthCounts     `json:"health"`
	// Worktrees is populated only when `--worktrees` is set (G-0122).
	// Always omitted from the JSON envelope when nil so the default
	// shape stays unchanged for existing JSON consumers.
	Worktrees []WorktreeView `json:"worktrees,omitempty"`
}

StatusReport is the pure-data payload for `aiwf status`. The text and JSON renderers consume the same struct; BuildStatus produces it from a loaded tree. Lives alongside the CLI dispatcher rather than under internal/ because it is purely a presentational read view — adding a package boundary would be over-engineering for one verb.

func BuildStatus

func BuildStatus(tr *tree.Tree, loadErrs []tree.LoadError) StatusReport

BuildStatus returns the project status payload for tree tr, with loadErrs surfaced as part of the health counts. The returned report has RecentActivity unset; the caller fills it via ReadRecentActivity (which needs git access; this function stays pure for testability).

type StatusSweepPending

type StatusSweepPending struct {
	Count   int    `json:"count"`
	Message string `json:"message"`
}

StatusSweepPending is the tree-health one-liner for terminal-status entities still living in active directories. Per ADR-0004 §"Display surfaces": "The tree-health section gains a one-liner when sweep is pending: 'Sweep pending: N terminal entities not yet archived (run `aiwf archive --dry-run` to preview).' Hidden when 0."

Populated from the `archive-sweep-pending` aggregate finding (M-0086); nil when the count is zero so the renderer can skip the section with a single nil-check. Lifted out of StatusReport.Warnings on purpose — the aggregate belongs in the tree-health section, not mixed in with body-empty / resolver-missing warnings.

func ParseSweepPending

func ParseSweepPending(message string) *StatusSweepPending

ParseSweepPending extracts the count from an `archive-sweep-pending` finding message and packages it into a StatusSweepPending. The rule's message format ("%d terminal entities awaiting `aiwf archive --apply`...") is the upstream contract; this function is the consumer-side parser, so a future format change must update both sites. The friendlier render-side message names the dry-run verb per ADR-0004's worded example ("run `aiwf archive --dry-run` to preview").

Returns nil if the message doesn't begin with a digit, which would only happen if the upstream finding-rule produced an empty count; the rule itself returns nil at zero so this branch shouldn't fire in practice.

type WorktreeView

type WorktreeView struct {
	Path           string    `json:"path"`
	Branch         string    `json:"branch"`
	HeadTime       time.Time `json:"head_time,omitempty"`        // author-date of the HEAD commit on this worktree's branch (G-0122 age display)
	CreatedTime    time.Time `json:"created_time,omitempty"`     // author-date of the first ahead-of-trunk commit on this branch (worktree creation proxy)
	LastEntityTime time.Time `json:"last_entity_time,omitempty"` // author-date of the most recent aiwf-verb-trailered commit on this branch
	Dirty          bool      `json:"dirty,omitempty"`            // true when `git status --porcelain` reports any uncommitted changes (G-0122 option A)
	DriverEntityID string    `json:"driver_entity_id,omitempty"`
	DriverKind     string    `json:"driver_kind,omitempty"` // "epic" / "milestone" / "gap"
	DriverStatus   string    `json:"driver_status,omitempty"`
	DriverTitle    string    `json:"driver_title,omitempty"`
	Stale          bool      `json:"stale,omitempty"`
	// Populated only when DriverKind == "epic": milestones under this
	// epic + gaps the epic (or its milestones) closes + gaps the epic
	// (or its milestones) surfaced.
	EpicMilestones   []EpicChildRow `json:"epic_milestones,omitempty"`
	EpicClosesGaps   []EpicChildRow `json:"epic_closes_gaps,omitempty"`
	EpicSurfacedGaps []EpicChildRow `json:"epic_surfaced_gaps,omitempty"`
	// Milestone-driver breadcrumb + AC enumeration + related rows.
	ParentEpicID     string         `json:"parent_epic_id,omitempty"`
	ParentEpicTitle  string         `json:"parent_epic_title,omitempty"`
	ParentEpicStatus string         `json:"parent_epic_status,omitempty"`
	ACs              []ACRow        `json:"acs,omitempty"`
	DependsOn        []EpicChildRow `json:"depends_on,omitempty"`
	SurfacedGaps     []EpicChildRow `json:"surfaced_gaps,omitempty"` // gaps with discovered_in == driver milestone
	// OtherInFlight is populated only on the main-checkout worktree —
	// in-flight entities that no worktree drives, either on a branch
	// that has no worktree or directly on trunk (no dedicated branch).
	OtherInFlight []OtherInFlightRow `json:"other_in_flight,omitempty"`
}

WorktreeView is the per-worktree row in `aiwf status --worktrees` output. One per `git worktree list --porcelain` entry.

G-0122.

func BuildWorktreeViews

func BuildWorktreeViews(ctx context.Context, rootDir string, tr *tree.Tree) ([]WorktreeView, error)

BuildWorktreeViews enumerates the repo's worktrees, correlates each to an entity via the hybrid cascade (scope-defining events → trailer-recency → branch-name parsing), and returns one WorktreeView per worktree.

rootDir is the consumer repo root (any worktree's path resolves the shared git dir). tr is the loaded entity tree. ctx scopes the git subprocess calls.

Jump to

Keyboard shortcuts

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