ShrubberyDocs
Sign in

Commitment Flow Score Rules

Overview

The Commitment Flow Score (CFS) is the team-health composite that rolls up three axes — Flow Score (Say-Do Ratio), Ghost Rate, and Reciprocity. It is the Say-Do receipt of the team. This guide states exactly which rows count toward each axis, when they enter the calculation, and what causes them to leave.

Actors

  • Lead — the CFS subject of the Garden widget. The Lead's CFS represents the team they manage.
  • Knight — contributes events into the calculation but does not "have a CFS" of their own; the Knight's reliability surfaces on their Dossier as historical roll-ups.
  • Squire — does not participate in scoring. Squire-extracted Shrubberies enter the calculation only after the Knight has Handshaked them.

Flow

  1. A Shrubbery is proposed — Draft or Pending_Handshake. Not counted yet in any axis. CFS is consent-first; nothing scores until the Knight has accepted.
  2. Knight Handshakes — Pending_HandshakeActive. Row enters the Flow Score denominator (it is now an "Active commitment with a deadline").
  3. Knight completes on or before the deadline — ActiveCompleted, completed_at <= deadline. Row enters the Flow Score numerator (a delivered commitment). A commitment with no deadline counts as on time when completed.
  4. Knight completes late — ActiveCompleted, completed_at > deadline. Row stays in the denominator but not the numerator. Flow Score drops.
  5. Knight refuses during Pending_HandshakePending_HandshakeRefused. Row enters the Ghost Rate numerator. It does not enter the Flow Score denominator (the Knight never agreed to deliver).
  6. A Pending_Handshake ages out past the stale threshold without acceptance — counts toward Ghost Rate the same as an explicit Refused.
  7. Lead is the Assignee on a Shrubbery (manager doing work on their team's behalf) — that row contributes to the Lead's own Reciprocity axis using the same Flow-Score arithmetic.

Edge cases

  • Renegotiation — a substantive edit on an Active row reverts it to Pending_Handshake and emits a Renegotiated audit event. The row leaves the Flow Score calculation until the Knight re-Handshakes. The original deadline does not carry over implicitly; the new deadline is whatever the Lead set on the edit.
  • Edited but not Renegotiated — non-substantive edits emit an Edited event and keep the row in Active. No CFS impact.
  • Deleted rows — soft-deletes preserve the audit trail; CFS calculation joins on the live shrubberies rows, so deletions remove the row from both numerator and denominator immediately.
  • Externally-completed via Sync webhookactor_kind = api_token on the audit row, but the CFS arithmetic treats it identically to a user-mark-complete (the Knight is still the responsible party). The Dossier surfaces the actor distinction; the score does not.
  • Open Pending_Handshake past the stale threshold but not yet aged out — sits in neither numerator. Visible on the dashboard as a Nudge candidate.

Thresholds

The numbers below are the live constants in src/lib/metrics/cfs.ts. Each surface that shows a score reads from the same engine, so these apply everywhere.

  • Rolling window — 90 days. Only events within the last 90 days count. Windowing keys on the state-change time (completion time for a delivered row, refusal/aged-out time for a Ghost), not on when the row was created — so a long-lived commitment completed recently still counts.
  • Minimum denominator — 3. An axis with fewer than 3 decided commitments in the window shows a "building — N of 3" state instead of a percentage. This stops a single early row from reading as 0% or 100%.
  • Stale threshold — 14 days. A Pending_Handshake left unanswered for 14 days ages out and counts toward Ghost Rate exactly like an explicit Refused. Accepting or refusing before then changes the outcome; re-deriving status from the audit trail means accepting after an age-out moves the row back into the Active "yes" set (it is no longer a Ghost).
  • No deadline — on time. A completed commitment with a null deadline counts in the Flow Score numerator.

Sentiment

Pillar III. How the Knight felt about the commitment at the moment they Handshake it. Optional, skippable, and never weighted into the numeric Flow Score, Ghost Rate, or Reciprocity axes — Sentiment is a display-only distribution, not a fourth scored axis.

  1. Knight Handshakes — the accept action offers a 4-chip prompt: Confident / Comfortable / Stretched / Overwhelmed, plus a Skip. Selecting a chip or Skip both complete the Handshake immediately; Skip is zero-friction, not a second step behind the chips.
  2. The choice is written once, at acceptance, to shrubberies.acceptance_sentiment (NULL on Skip). It is immutable afterward — there is no path to edit Sentiment after the fact.
  3. computeCfs() aggregates Sentiment behind an includeSentiment flag into breakdown.sentimentBreakdown — a { confident, comfortable, stretched, overwhelmed, skipped } count over rows where the subject is the assignee (the same row set as Reciprocity), keyed on the row's activated_at (Sentiment is an acceptance-time attribute, not a status-replay one) within the same 90-day rolling window as every other axis.
  4. Renegotiation clears activated_at back to null on revert to Pending_Handshake — the same field the Flow Score calc drops the row on. A previously-recorded Sentiment therefore drops out of the aggregate until the row is re-Handshaked; the stored column value itself is untouched, only window membership.
  5. Surfaces: a stacked distribution bar on /home beneath the Flow Score hero, and a compact proportional chip row on the Knight Dossier. Both hide entirely when the aggregate is all-zero — no empty chart.

Last updated: 7 July 2026