Classify every outcome cli.py records as a success or an error - #69
Conversation
`state.SUCCESS_OUTCOMES` listed only `tend`/`created`/`report`, but `cli.py` records four more values: `implement` and `file-issue` (cmd_align records `mode.value` verbatim on success), `created_incomplete`, and `error`. The first three fell into neither `SUCCESS_OUTCOMES` nor `repo_stats()`' `outcome = 'error'` arm, so they were counted as neither — leaving a repo whose runs all succeeded with `successes: 0` and a NULL `last_success`, which the dashboard's `healthOf` draws as a struggling plant with zero tends. Add all three to `SUCCESS_OUTCOMES`, introduce `ERROR_OUTCOME`/ `KNOWN_OUTCOMES` so the vocabulary is stated in one place, and guard the gap in tests: `test_state.py` asserts every `KNOWN_OUTCOMES` value lands in exactly one of the two buckets, and `test_cli.py` asserts every `Mode` value a run records verbatim is a known outcome. All five new assertions were confirmed to fail against the pre-fix set. Also bring `docs/ARCHITECTURE.md`'s module tree back in line with CLAUDE.md's "lists every file in gardener/" contract: add `__init__.py` and `__main__.py`, and list all eight of cli.py's subcommands instead of four. Closes #67 Closes #68 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Self-reviewNo reviewer is configured for this repo (no Overall: the fix is correct and the change is small, but I found two Checked and clean
Findings1.
2. Before this change Non-findings considered and dismissed
|
…rals Addresses both findings from this PR's self-review. The `created`/`created_incomplete` strings were spelled in three places — cli.py's record site, state.py's SUCCESS_OUTCOMES, and the new test — and unlike every other non-error outcome they aren't derived from a Mode, so the drift guard this PR adds couldn't see them. Name them in state.py and have `_run_tend_dispatch` record through those constants. test_state.py keeps the bare literal deliberately: that string is already in rows of every existing gardener.sqlite3 and must keep classifying correctly even if the constant is renamed. Also make docs/DASHBOARD.md's plant/data table say what `successes` now actually counts — successful dispatches, not just tends, so an align run or a dev-loop bootstrap grows the plant too. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The two bootstrap tests exercised the created/created_incomplete branches but only asserted on stderr and notification counts, so nothing checked which string actually reached the db. That left TestRecordedOutcomeVocabulary asserting a constant against itself for this one pair. Assert the recorded outcome directly, as both the bare literal (what old sqlite3 rows hold) and the state constant (what cli.py now records through). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Summary
state.SUCCESS_OUTCOMESwas missing three of the seven outcome valuescli.pyactually records. It listedtend/created/report;implementandfile-issue(cmd_alignrecordsmode.valueverbatimon success,
cli.py:473) andcreated_incomplete(cli.py:633) were inneither that set nor
repo_stats()'outcome = 'error'arm, so theycounted as neither a success nor an error.
align --implementruns came back fromrepo_stats()withsuccesses: 0andlast_success: NULL. The dashboard's garden viewreads both straight off that —
healthOf(row)hitsif (!row.last_success) return "struggling"— so it drew a brown,drooping plant with a
0in the Tends column for a repo that had neverfailed.
docs/DASHBOARD.md's mapping table documents that stem heightcomes from
successesand droop fromlast_success, so this was thedocumented behaviour quietly not holding.
SUCCESS_OUTCOMES.created_incompleteis asuccess on purpose — the
<slug>-dev-loopskill was created andusable (
_run_tend_dispatchproceeds straight to the real tend dispatchafter it,
cli.py:657-669); what's incomplete is create-dev-loop's ownStep 6 GitHub tracker repo, which says nothing about the target repo's
health.
ERROR_OUTCOMEandKNOWN_OUTCOMESin
state.py. Nothing readsKNOWN_OUTCOMESat runtime; it exists sothe tests below can assert the classification is total.
repo_stats()'SQL now binds
ERROR_OUTCOMErather than repeating the'error'literal.
SUCCESS_OUTCOMES' originalcomment said it was a set rather than
outcome != "error""so a futureoutcome has to be classified deliberately" — that mechanism only
surfaces a new outcome if something actually checks. Now two things do:
tests/test_state.pyrecords one run perKNOWN_OUTCOMESvalue andasserts
successes + errors == runs, plus explicit regression casesfor a successful
--implement/--file-issuerun and acreated_incompletebootstrap.tests/test_cli.pyasserts everyModevalue that gets recordedverbatim is in
KNOWN_OUTCOMES(skippingCREATE_DEV_LOOP, the oneMode that records
created/created_incompleteinstead of its ownvalue — both asserted separately).
docs/ARCHITECTURE.mdmodule tree brought back in line withCLAUDE.md's "lists every file ingardener/with an accurate one-linedescription" contract:
__init__.pyand__main__.pywere missingentirely, and
cli.py's one-liner listed four of its eight subcommands(
cli.py's own module docstring already had all eight).docs/TESTING.md's per-module coverage description updated to match thenew assertions.
Test plan
python3 -m compileall gardener— cleanPYTHONPATH=. python3 -m unittest discover -s tests -v— 496passed (491 before, 5 new)
tautologies: re-run with
SUCCESS_OUTCOMESmonkeypatched back tothe pre-fix
{"tend", "created", "report"}, all 5 new assertionsfail (
'file-issue' not found in frozenset(...),'created_incomplete' not found in ..., and therepo_statssuccess-count assertions). Restored afterward; the throwaway script
lived in
/tmp, nothing added to the repo.CLAUDE.md's "Testing changes" requires a realclaudedispatchonly for changes touching
dispatch.py,dev_loop.py, or a prompttemplate/preamble. This PR touches
state.py, two test files, andtwo docs — no dispatch-layer behaviour, no
claudeinvocationshape, and no change to what any mode is permitted to do. The
automated gate above is the full gate here.
Note on existing data
This changes how already-recorded history is aggregated, not just future
runs — which is the point: previously-uncounted
implement/file-issue/created_incompleterows will now show up in the dashboard's Tends columnand can move a repo's health from "struggling" to a real value. No schema
change, no migration;
repo_stats()recomputes from the same rows.Closes #67
Closes #68
This PR description was drafted during a Gardener session (Stephenson-Software/gardener).