Documentation
¶
Overview ¶
Package status implements the `aiwf status` verb (per-verb subpackage of M-0116).
Index ¶
- Constants
- func MdEscape(s string) string
- func NewCmd() *cobra.Command
- func ReadRecentActivity(ctx context.Context, root string, limit int) ([]history.HistoryEvent, error)
- func RenderACProgress(p *StatusACProgress) string
- func RenderStatusMarkdown(w io.Writer, r *StatusReport) error
- func RenderStatusText(w io.Writer, r *StatusReport, termWidth int, colorEnabled bool) error
- func RenderWorktreeViews(w io.Writer, views []WorktreeView, colorEnabled bool) error
- func Run(root, format string, pretty, noTrunc, worktrees bool) int
- func TruncStatusTitle(title string, termWidth int, prefix, tail string) string
- func WriteStatusEpicMarkdown(b *strings.Builder, e StatusEpic)
- func WriteStatusEpicText(b *strings.Builder, e StatusEpic, termWidth int)
- type ACRow
- type EpicChildRow
- type OtherInFlightRow
- type StatusACProgress
- type StatusEntity
- type StatusEpic
- type StatusFinding
- type StatusGap
- type StatusHealthCounts
- type StatusMilestone
- type StatusReport
- type StatusSweepPending
- type WorktreeView
Constants ¶
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 ¶
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 ¶
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 ¶
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 ¶
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 ¶
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 ¶
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 ¶
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.