Documentation
¶
Overview ¶
Package gardenbundle is the garden bundle -- one default-on fold over the repo's read-only gardening passes.
fak already measures itself with several orchestrators, each folding a different slice of repo health, and each run by hand or in CI but never as ONE thing:
- tools/scorecard_control_pane.py folds the scorecard family into one portfolio total_debt with a pinned-baseline ratchet (--check).
- tools/fresh_status.py folds the four top-level domains (git, benchmarks, work, industry) into one cross-domain snapshot.
- tools/fleet_control_pane.py loop-audit runs the gardening loop catalog and buckets each loop healthy / action / broken.
This is the fold over those folds -- the single read-only "is the garden tended?" verdict, so "run the gardening" is one command instead of three. It is deliberately READ-ONLY: it runs each member (which stay grandfathered Python tools), reads each member's control-pane JSON payload, and folds one schema/ok/verdict/finding/reason/next_action envelope. It mutates nothing and fixes nothing -- auto-fix is a later, witness-gated rung.
--check is the bundle's CI contract: it exits non-zero when any gating member reports a hard regression (today only the scorecard ratchet gates; fresh-status and loop-audit are advisory panes that surface conditions without failing the bundle). A member that fails to RUN (errored) always trips the gate, so a silently-broken pass can't masquerade as a clean garden.
The bundle is skipped entirely when FAK_GARDEN is set to an off value (0/off/false/no/disable/disabled) -- the env-side half of the governor brake.
The pure, tested surface is Interpret (fold one member payload into a uniform row), Fold (fold member rows into one envelope) and CheckGate (the CI gate decision over a folded payload). The live runner shells out to python3 tools/<x>.py exactly as the Python version did.
Beyond the read-only fold, two ACT-side passes share this package:
- tick.go (PlanTick) acts at the MEMBER level: it turns a member's surfaced stale-work condition into a documented, idempotent remediation (reap an expired lease, surface an orphan worklist) on a recurring cadence.
- walk.go (PlanWalk) acts at the ITEM level: it walks the HUNDREDS of individual items a member surfaces (today the open-issue backlog) and folds them into a resource-aware, worst-first, budget-bounded worklist — skipping the in-progress/fresh/healthy items so a 300-item set yields a bounded proposal, not 300 reports. Both are pure and tested; the cmd verb performs the side effects and ledgers the run.
Full design: docs/notes/GARDENING-BUNDLE-DEFAULT-ON-2026-06-25.md.
Index ¶
- Constants
- Variables
- func AttentionTriageLine(p WalkPlan) string
- func CheckGate(p Payload) (int, string)
- func CollectBounded(root string, opt CollectOptions) ([]MemberResult, CollectProgress)
- func DecisionNeedsHuman(d WalkDecision) bool
- func DefaultPython() string
- func GardenOff() bool
- func GardenWalkTriageEnforced(mode string) bool
- func HeadCommit(root string) string
- func Render(p Payload) string
- func RunMember(root string, member Member, python string, timeout time.Duration) (map[string]any, int, string)
- func TriageDecision(d WalkDecision) choicetriage.Verdict
- func TriageSelfcheck() error
- type ActDecision
- type ActKind
- type CollectOptions
- type CollectProgress
- type GardenIssue
- type GardenScore
- type Member
- type MemberResult
- type MemberRunner
- type Payload
- type TickPlan
- type WalkDecision
- type WalkDisposition
- type WalkItem
- type WalkPlan
- type WalkPolicy
Constants ¶
const Schema = "fak-garden-bundle/1"
Schema is the stable control-pane schema identifier for the bundle envelope.
const WalkSchema = "fak.garden-walk.v1"
Schema identifiers for the walk envelope.
Variables ¶
var DeepMember = Member{ Key: "loop_audit", Label: "fleet loop-audit", Argv: []string{"tools/fleet_control_pane.py", "loop-audit", "--names", strings.Join(loopAuditNames, ","), "--json"}, Gates: false, Kind: "loop_audit", }
DeepMember is the opt-in deep member (added by --deep). Non-gating advisory. It goes through fleet_control_pane's per-loop orchestration, which carries enough overhead that it doesn't belong on the default tick.
var Members = []Member{ { Key: "scorecard", Label: "scorecard control pane", Argv: []string{"tools/scorecard_control_pane.py", "--check", "--json"}, Gates: true, Kind: "envelope", }, { Key: "propagation_scorecard", Label: "propagation scorecard", Argv: []string{"fak", "propagation-scorecard", "--json"}, Gates: false, Kind: "envelope", Exec: "command", }, { Key: "fresh_status", Label: "fresh status", Argv: []string{"fak", "fresh-status", "--json"}, Gates: false, Kind: "envelope", Exec: "command", }, { Key: "guard_route", Label: "guard-session route", Argv: []string{"fak", "guard-verdict-rsi", "route", "--no-issues", "--json"}, Gates: false, Kind: "envelope", Exec: "command", }, { Key: "sessions_learn", Label: "sessions learn", Argv: []string{"fak", "sessions", "learn", "--corpus", "experiments/sessionobs/corpus.jsonl", "--json"}, Gates: false, Kind: "envelope", Exec: "command", }, { Key: "orphaned_runs", Label: "orphaned runs", Argv: []string{"fak", "loop", "recover", "--control-pane"}, Gates: false, Kind: "envelope", Exec: "command", }, { Key: "release_staleness", Label: "release staleness", Argv: []string{"fak", "release-staleness", "--json"}, Gates: true, Kind: "envelope", Exec: "command", }, { Key: "stale_leases", Label: "stale leases", Argv: []string{"fak", "leaseref", "audit"}, Gates: false, Kind: "envelope", Exec: "command", ActOnVerdict: true, }, { Key: "windowgate", Label: "no desktop popups", Argv: []string{"fak", "windowgate", "--json"}, Gates: true, Kind: "envelope", Exec: "command", }, { Key: "growthgate", Label: "unbounded ledger growth", Argv: []string{"fak", "growthgate", "--json"}, Gates: false, Kind: "envelope", Exec: "command", }, }
Members is the DEFAULT bundle: the two fast, canonical folds that already speak the same control-pane envelope -- the scorecard control pane and fresh-status. It is deliberately small so a scheduled tick is fast and host-safe.
Functions ¶
func AttentionTriageLine ¶ added in v0.38.0
AttentionTriageLine renders the decenter split for a walk plan as one readout line: of the emitted worklist, how many genuinely wait on a person vs how many the fleet drives. Returns "" when nothing was emitted (an empty or all-skipped walk has no attention to split). Rendered only under FAK_GARDENWALK_TRIAGE_GATE=enforce.
func CheckGate ¶
CheckGate is the CI gate decision over a folded payload (pure: exit code + message).
0 garden tended (clear or advisory-only) 1 a gating member regressed, or a member failed to run
func CollectBounded ¶ added in v0.44.0
func CollectBounded(root string, opt CollectOptions) ([]MemberResult, CollectProgress)
CollectBounded runs a checkpointed suffix of the member list. Collection used to run every member sequentially with a full per-member timeout, before the tick's phase budget even began. With ten members, that made a 900-second timeout an hours-scale aggregate allowance. This function instead:
- never starts a member after the whole-tick budget is spent;
- caps each member to min(per-member timeout, global time remaining);
- checkpoints after every returned member; and
- resumes at the first member not yet checkpointed.
A member killed by RunMember's timeout returns an errored row and collection advances best-effort. If the OUTER watchdog kills RunMember itself, the checkpoint remains on that member and the next tick retries it.
func DecisionNeedsHuman ¶ added in v0.38.0
func DecisionNeedsHuman(d WalkDecision) bool
DecisionNeedsHuman reports whether an emitted decision genuinely waits on a person — the enforce-mode replacement for "DispReview == needs you". True only for a real priority/policy/release authority; a ready-command act and a knowable classification review both return false even though PlanWalk emits the latter under DispReview.
func DefaultPython ¶
func DefaultPython() string
DefaultPython is the exported form of defaultPython, so sibling packages (e.g. cadencereport) can share this one resolver instead of copying it.
func GardenOff ¶
func GardenOff() bool
GardenOff reports whether FAK_GARDEN names an off value (the env-side governor brake).
func GardenWalkTriageEnforced ¶ added in v0.38.0
GardenWalkTriageEnforced reports whether the decenter split is active for the given mode string. Only "enforce" (case-insensitive) turns it on; "", "warn" and anything else leave the walk's worklist byte-for-byte unchanged so the fold can soak. Mirrors every other decenter seam's enforce/warn switch.
func HeadCommit ¶
HeadCommit returns the short HEAD commit of root, or "unknown".
func RunMember ¶
func RunMember(root string, member Member, python string, timeout time.Duration) (map[string]any, int, string)
RunMember runs one member and parses its JSON stdout. A default ("" / "python") member runs Argv[0] as a repo python script under the interpreter; a "command" member runs Argv[0] as a direct executable. A "command" member whose Argv[0] is the bare token "fak" is rewritten to the CURRENTLY-RUNNING executable (os.Executable), so a Go-native member re-invokes the same built binary -- never `go run`, which would recompile the whole tree and so error whenever a peer's uncommitted edit leaves cmd/fak uncompilable on the shared trunk. It returns the parsed payload (nil on any failure), the process exit code, and an error string (empty on success).
func TriageDecision ¶ added in v0.38.0
func TriageDecision(d WalkDecision) choicetriage.Verdict
TriageDecision folds one emitted walk decision into its choicetriage disposition. The Signal is built from the decision's own fields — the ready Command is the strongest TAKE_OBVIOUS signal, and the Reason (the condition tags, e.g. "needs-area, likely-dup") carries the authority test: a reason naming priority/policy/release routes to HUMAN_RESIDUAL, everything else to FRESH_CONTEXT. Source is the neutral "garden" — never a token choicetriage reads as authority.
func TriageSelfcheck ¶ added in v0.38.0
func TriageSelfcheck() error
TriageSelfcheck is the deterministic, no-I/O proof of the walk fold: a ready-command act is the fleet's to run, a needs-area / needs-kind / likely-dup review is the fleet's to drive in a fresh context, and only an unset-priority review genuinely waits on a person. It is the witness the CLI surfaces as the walk's triage selfcheck.
Types ¶
type ActDecision ¶
type ActDecision struct {
Key string `json:"key"`
Label string `json:"label"`
State string `json:"state"`
Act ActKind `json:"act"`
Perform bool `json:"perform"`
Mode string `json:"mode"` // "act" or "dry-run"
Reason string `json:"reason"`
Detail string `json:"detail"`
}
ActDecision is one member's tick decision: which remediation applies, whether the tick will Perform it (false under --dry-run, or when the member is ok), and a human Reason. The cmd verb reads Perform+Act to do the side effect; Detail carries the member's own surfaced reason for the tick's witness record.
type ActKind ¶
type ActKind string
ActKind is the closed set of remediations the garden tick can take. It is the per-member action policy from the ticket made into a typed value.
const ( // ActNone: the member is ok, or has no registered remediation — nothing to do. ActNone ActKind = "none" // ActReap: an expired cross-machine lease lingers — delete the reapable // records (the `fak leaseref reap` remediation). Idempotent: a second reap of // an already-gone lease is a no-op. ActReap ActKind = "reap" // ActSurface: orphaned/unwitnessed runs exist — surface the recovery worklist // as a witnessed tick event so the operator can re-dispatch/re-verify // (re-dispatch stays gated, never automatic). Idempotent: one bounded event. ActSurface ActKind = "surface" // ActAdvisory: the member surfaced a condition whose remediation is owned // elsewhere (release_staleness -> #1367). Reported, never auto-acted by the tick. ActAdvisory ActKind = "advisory" // ActGrowthReap: an oversized, COLD, disposable log crossed its growthgate // budget — collect (reap) the reapable set. The growthgate detector already // runs on the tick, but its ACTION verdict was reported and never acted; this // binds it to the collector. Idempotent: an os.Remove of an already-gone file // is a no-op. Deletes stay default-off (ledger-only) until the soak opt-in // flips them on, so a surfaced verdict inherits surfacing + dry-run untouched. ActGrowthReap ActKind = "growth-reap" )
type CollectOptions ¶ added in v0.44.0
type CollectOptions struct {
Python string
PerMemberTimeout time.Duration
Budget time.Duration
Start time.Time
Deep bool
// Next names the next member to run. Empty starts a fresh collection.
Next string
// Prior is the already-checkpointed prefix from previous ticks.
Prior []MemberResult
Now func() time.Time
Run MemberRunner
// Checkpoint is called after each returned member with the accumulated prefix
// and the key of the next member (empty when collection completed).
Checkpoint func(next string, results []MemberResult) error
}
CollectOptions makes member collection part of the tick's GLOBAL budget.
type CollectProgress ¶ added in v0.44.0
type CollectProgress struct {
Total int `json:"total"`
Completed int `json:"completed"`
Ran []string `json:"ran"`
Deferred []string `json:"deferred"`
Next string `json:"next,omitempty"`
Complete bool `json:"complete"`
Exhausted bool `json:"exhausted"`
Millis int64 `json:"millis"`
CheckpointError string `json:"checkpoint_error,omitempty"`
}
CollectProgress is the typed resume account for one bounded collection pass.
type GardenIssue ¶ added in v0.37.0
type GardenIssue struct {
Key string `json:"key"`
RecurrenceKey string `json:"recurrence_key"`
MemberKey string `json:"member_key"`
Label string `json:"label"`
State string `json:"state"`
Verdict string `json:"verdict"`
Gates bool `json:"gates"`
Severity int `json:"severity"`
Debt int `json:"debt"`
Detail string `json:"detail,omitempty"`
NextAction string `json:"next_action"`
}
GardenIssue is the stable, deduplicable work item projected from one garden member. RecurrenceKey is intentionally deterministic: if the same member keeps surfacing the same class of condition across runs, downstream issue filers and ratchets can update one item instead of opening a fresh "garden red" note.
type GardenScore ¶ added in v0.37.0
type GardenScore struct {
Score int
Debt int
TopIssues []GardenIssue
}
GardenScore is the scoreboard view of a garden payload. Score is 100 when the garden is clear and falls as debt rises; Debt is the additive unit used for trend/ratchet comparisons; TopIssues is bounded so the envelope stays small.
func ScoreResults ¶ added in v0.37.0
func ScoreResults(results []MemberResult) GardenScore
ScoreResults folds member rows into a deterministic score and a bounded worst-first worklist. It is deliberately pure: same member rows in, same score out, so scheduled runs can compare score/debt over time.
type Member ¶
type Member struct {
Key string
Label string
Argv []string
Gates bool
Kind string // "envelope" or "loop_audit"
// Exec selects how Argv is run. "" / "python" (the default) runs Argv[0] as a
// repo python script under the resolved interpreter -- the shape every original
// member uses. "command" runs Argv[0] as a direct executable with Argv[1:] as
// its args (e.g. `go run ./cmd/fak ...`), so a Go-native member can join the
// bundle without a python shim.
Exec string
// ActOnVerdict opts this member into VERDICT-DRIVEN state (#5003). A READ-ONLY
// detector reports `ok: true` unconditionally -- reporting IS the pass working --
// and carries its finding in `verdict: ACTION`. Interpret derives State from `ok`
// alone, so such a member folds to "ok" no matter what it found and PlanTick, which
// acts only on "action"/"red", can never reach its registered remediation. With
// this set, an ok=true payload whose verdict is ACTION folds to State "action"
// while OK stays true -- the condition becomes actionable without gating the
// garden red. It is OPT-IN PER MEMBER on purpose: a blanket promotion would newly
// arm file-deleting remediations (growthgate) off an advisory verdict.
ActOnVerdict bool
}
Member binds a label to the argv that produces its control-pane payload and whether a RED verdict from that member GATES the bundle (--check exits non-zero). The scorecard ratchet is the only hard gate today; the others are advisory panes whose ACTION verdict is a surfaced condition, not a broken garden.
func CollectionMembers ¶ added in v0.44.0
CollectionMembers returns a copy of the ordered member list one collection cycle must visit. Callers may safely append or slice the result.
type MemberResult ¶
type MemberResult struct {
Key string `json:"key"`
Label string `json:"label"`
Gates bool `json:"gates"`
ExitCode int `json:"exit_code"`
State string `json:"state"`
OK bool `json:"ok"`
Verdict string `json:"verdict"`
Detail string `json:"detail"`
// Counts carries the loop-audit bucket counts; nil for envelope members.
Counts map[string]int `json:"counts,omitempty"`
}
MemberResult is the bundle's normalization of one member into a uniform row. State is one of:
- "ok" -- the member ran and reports nothing to do
- "action" -- the member ran and surfaces a real condition (advisory)
- "red" -- the member's gate tripped (a hard regression)
- "errored" -- the member could not run / produced no usable payload
func Collect ¶
func Collect(root, python string, timeout time.Duration, deep bool) []MemberResult
Collect runs every member (plus the deep member when deep is set) and folds each into a MemberResult.
func Interpret ¶
Interpret folds one member's raw payload into a uniform member-result row. payload is nil when the member produced no usable payload.
func (MemberResult) MarshalJSON ¶
func (r MemberResult) MarshalJSON() ([]byte, error)
MarshalJSON emits the same field set and order as the Python member row.
type MemberRunner ¶ added in v0.44.0
type MemberRunner func(root string, member Member, python string, timeout time.Duration) (map[string]any, int, string)
MemberRunner is the injectable RunMember seam used by CollectBounded tests.
type Payload ¶
type Payload struct {
OK bool
Verdict string
Finding string
Reason string
NextAction string
Workspace string
Commit string
Members []MemberResult
MemberCount int
Gating []string
Skipped bool
// contains filtered or unexported fields
}
Payload is one folded garden-bundle control-pane envelope.
func Fold ¶
func Fold(results []MemberResult, workspace, commit string) Payload
Fold folds member results into one garden-bundle control-pane payload.
func SkippedPayload ¶
SkippedPayload is the well-formed payload for an FAK_GARDEN=off skip (still ok=True).
func (Payload) MarshalJSON ¶
MarshalJSON emits the same field set and order as the Python payload.
type TickPlan ¶
type TickPlan struct {
DryRun bool `json:"dry_run"`
Decisions []ActDecision `json:"decisions"`
// ToReap / ToSurface count the members the tick WILL act on (0 under dry-run).
ToReap int `json:"to_reap"`
ToSurface int `json:"to_surface"`
// Advisory counts members surfacing a condition acted on elsewhere.
Advisory int `json:"advisory"`
}
TickPlan is the folded act-pass plan: the per-member decisions plus the summary counts the verb and its witness event report.
func PlanTick ¶
func PlanTick(results []MemberResult, dryRun bool) TickPlan
PlanTick folds member results into the act-pass plan. It is pure: same results + dryRun in, same plan out, no I/O. dryRun=true forces every decision to Perform=false / Mode="dry-run" (acts on nothing).
type WalkDecision ¶ added in v0.37.0
type WalkDecision struct {
ID int `json:"id"`
Title string `json:"title"`
Score int `json:"score"`
IdleDays int `json:"idle_days"`
Disposition WalkDisposition `json:"disposition"`
Action string `json:"action,omitempty"`
Command string `json:"cmd,omitempty"`
Perform bool `json:"perform"`
Reason string `json:"reason,omitempty"`
}
WalkDecision is one budgeted item's outcome: which handling applies, whether the walk would Perform it (act + not dry-run), and the ready command.
func WalkAttentionSplit ¶ added in v0.38.0
func WalkAttentionSplit(p WalkPlan) (needHuman, fleetDrives []WalkDecision)
WalkAttentionSplit partitions the plan's emitted worklist into the decisions that genuinely wait on a person and the ones the fleet drives itself. Deterministic: it walks plan.Decisions in their already-sorted worst-first order.
type WalkDisposition ¶ added in v0.37.0
type WalkDisposition string
WalkDisposition is the closed set of per-item outcomes the walk assigns.
const ( // DispAct: a concrete, low-judgment gardening action with a ready command // (close a dormant question, mark a stale issue). Eligible to Perform. DispAct WalkDisposition = "act" // DispReview: the item surfaces a condition that needs human judgment // (needs-area / needs-kind / likely-dup). Surfaced, never auto-performed. DispReview WalkDisposition = "review" // DispSkip: nothing to do — the item is healthy (no condition) or fresh // (touched within the freshness window). Counted, never emitted in detail. DispSkip WalkDisposition = "skip" )
type WalkItem ¶ added in v0.37.0
type WalkItem struct {
ID int
Title string
Score int
IdleDays int
// InProgress marks an item already being actively worked (the source's own
// "someone is on it" signal, e.g. the in-progress label). The cheapest, most
// reliable resource pre-filter: re-surfacing an item under active work is waste,
// and unlike IdleDays it is immune to bot churn bumping the timestamp.
InProgress bool
// Disposition is the source's classification BEFORE the resource policy: act,
// review, or skip (healthy). PlanWalk may downgrade act/review to skip when the
// item is fresh or in-progress, but never upgrades a skip.
Disposition WalkDisposition
// Action is the action kind (e.g. "mark-stale"); Command is the ready-to-run
// shell command for an act (empty for review/skip). Reason explains the tags.
Action string
Command string
Reason string
}
WalkItem is one classified garden item handed to the pure planner. The caller (the cmd verb) does the source-specific classification (the issue gardener's tags/score/action) and fills these fields; PlanWalk decides per-item handling and applies the resource policy. Source-agnostic on purpose: an issue, a trajectory turn, or an orphaned run all reduce to (id, score, idle, disposition).
type WalkPlan ¶ added in v0.37.0
type WalkPlan struct {
Schema string `json:"schema"`
Source string `json:"source"`
DryRun bool `json:"dry_run"`
Budget int `json:"budget"`
Total int `json:"total"` // items walked
Active int `json:"active"` // cheap-skipped: actively in-progress
Fresh int `json:"fresh"` // cheap-skipped: idle < SkipFreshDays
Healthy int `json:"healthy"` // skipped: no condition
Attention int `json:"attention"` // act+review needing follow-up (pre-budget)
Acted int `json:"acted"` // act decisions emitted (Perform under !dry-run)
Review int `json:"review"` // review decisions emitted
Deferred int `json:"deferred"` // attention items beyond budget -> next pass
Decisions []WalkDecision `json:"decisions"` // budgeted, worst-first
OK bool `json:"ok"`
Verdict string `json:"verdict"`
Finding string `json:"finding"`
Reason string `json:"reason"`
NextAction string `json:"next_action"`
}
WalkPlan is the folded walk: the resource counts plus the bounded, worst-first worklist. It carries the control-pane envelope fields so it speaks the same schema/ok/verdict/reason/next_action language as the rest of the garden and can join the bundle as a member later.
func PlanWalk ¶ added in v0.37.0
func PlanWalk(source string, items []WalkItem, policy WalkPolicy) WalkPlan
PlanWalk folds a classified item set into the resource-aware walk plan. It is pure: same items + policy in, same plan out, no I/O. The order of operations is the resource policy:
- skip-fresh — drop items idle fewer than SkipFreshDays (cheap pre-filter), counting them Fresh, before anything else looks at them.
- partition the rest into attention (act/review) vs healthy (skip).
- sort attention worst-first by score (tie: lower id first, stable).
- take the first Budget as the emitted worklist; the rest are Deferred.
type WalkPolicy ¶ added in v0.37.0
type WalkPolicy struct {
// Budget caps how many attention items (act + review) earn a follow-up
// decision, worst-first. <= 0 means unbounded.
Budget int
// SkipFreshDays skips any item idle FEWER than this many days, regardless of its
// tags (a recently-touched item is left alone). <= 0 disables the freshness skip.
// NOTE: keyed on the item's update timestamp, so it is only meaningful where that
// timestamp tracks real work — on a bot-churned tracker it bumps constantly and
// the filter over-skips, which is why the cmd default is off.
SkipFreshDays int
// SkipInProgress skips any item the source marks actively-in-progress — the cheap
// pre-filter that genuinely fires even when timestamps are unreliable.
SkipInProgress bool
// DryRun forces every decision's Perform=false: the plan proposes, never acts.
DryRun bool
}
WalkPolicy is the resource-awareness knob set. Zero values mean: no freshness skip, unbounded budget, act for real (not dry-run) — but the cmd defaults are resource-safe (a freshness window, a finite budget, dry-run on).