---
phase: 32-list-overhaul-skill-hint-audit
verified: 2026-05-16T22:00:00Z
status: passed
score: 13/13
overrides_applied: 0
re_verification: null
---

# Phase 32: List Overhaul + Skill Hint Audit — Verification Report

**Phase Goal:** `$OWL list` and `$LIVE list` default to online-only with a unified `--all`/`--offline`/`--here` flag surface, AND every `/spt:*` skill ships an accurate `argument-hint` frontmatter field.
**Verified:** 2026-05-16T22:00:00Z
**Status:** passed
**Re-verification:** No — initial verification

## Goal Achievement

### Observable Truths

| #  | Truth | Status | Evidence |
|----|-------|--------|----------|
| 1  | `$OWL list` and `$LIVE list` with no flags show only online perches by default (LIST-01) | VERIFIED | `src/owl/list.rs:14-31` and `src/live/list.rs:14-26` call `list_filter::collect(..., mode, ...)` where default mode is `ListMode::Online`; `list_flags.rs` test `list_default_emits_no_online_hint_on_empty` confirms behaviour |
| 2  | `--all` flag includes offline perches alongside online (LIST-02) | VERIFIED | `ListMode::All` populates both `online` and `offline` buckets in `list_filter.rs:38-44`; `list_flags.rs` test `list_all_includes_offline` passes |
| 3  | `--offline` flag shows exclusively offline perches, mutex with `--all` (LIST-03) | VERIFIED | `src/cli.rs:75-76` declares `#[arg(long, conflicts_with = "all")] offline: bool`; `list_flags.rs` tests `list_offline_conflicts_with_all` and `list_offline_excludes_online` both pass |
| 4  | `--here` filters by `info.json.project_history` repo-name match (LIST-04) | VERIFIED | `list_filter.rs:168-174,239-243` applies `owlery::perch_has_any_repo_history` when `here_names` is Some; `list_flags.rs::list_here_smart_hint_when_filter_zeroes_out` verifies the filtering |
| 5  | Empty-online list emits stderr discovery hint pointing at `--all` (LIST-05) | VERIFIED (wording deviation — see note) | `src/owl/list.rs:50-53` emits `"No online listeners. Pass --all to include offline."` — satisfies ROADMAP SC#3 ("emits a stderr discovery hint pointing the user at `--all`"); note REQUIREMENTS.md LIST-05 literal uses "include offline perches." (with "perches") but PLAN must_haves, tests, and ROADMAP SC all use the shorter form without "perches" |
| 6  | `src/live/list.rs` refactored to share `ListMode` + filter with `src/owl/list.rs` (LIST-06) | VERIFIED | `src/common/list_filter.rs` (292 LOC) exports `ListMode`, `collect()`, `PerchEntry`, `CollectedPerches`; `live/list.rs` shrank from 138 to 73 LOC; both files call `list_filter::collect(...)` |
| 7  | Repo-history helper `perch_has_repo_history` extracted to `src/common/owlery.rs` and reused (LIST-07) | VERIFIED | `owlery.rs:480` declares `pub fn perch_has_repo_history`; `owlery.rs:499` declares `pub fn perch_has_any_repo_history`; both called from `list_filter.rs:170,209,241` |
| 8  | Golden test fixtures updated for new default (LIST-08) | VERIFIED | `list_one.{stderr,stdout}` orphan fixtures deleted; `golden_owl.rs::golden_list_empty` and `golden_live.rs::golden_list_empty` updated to assert new hint strings; Phase 32 commits confirm git rm |
| 9  | Every `/spt:*` skill has `argument-hint:` frontmatter key (HINT-01) | VERIFIED | 17/17 skills found by `tests/skill_hints.rs::every_skill_has_argument_hint` test; test passes `cargo test --test skill_hints` |
| 10 | `list-ready`, `list-live`, `list-psyche` gain `argument-hint: "[--all] [--offline] [--here]"` (HINT-02) | VERIFIED | Confirmed by grep: `plugin/spt/skills/list-ready/SKILL.md:6`, `list-live/SKILL.md:6`, `list-psyche/SKILL.md:6` all show `argument-hint: "[--all] [--offline] [--here]"` |
| 11 | Inaccurate hints corrected: commune → `""`, psyche-download → `"[<id>]"`, whoami → `""` (HINT-03) | VERIFIED | Grep shows `commune/SKILL.md:6:argument-hint: ""`, `psyche-download/SKILL.md:7:argument-hint: "[<id>]"`, `whoami/SKILL.md:7:argument-hint: ""`; `argument_hint_keys_known_set` test pins these values |
| 12 | YAML-significant char values are double-quoted (HINT-04) | VERIFIED | `listen`, `live`, `revive`, `send` updated to double-quoted form; `listen-stop` and `live-stop` were already quoted; `argument_hint_values_quote_yaml_special_chars` test passes |
| 13 | Regression guard test verifies every SKILL.md has `argument-hint` with quoting rules (HINT-05) | VERIFIED | `tests/skill_hints.rs` ships 3 test functions (`every_skill_has_argument_hint`, `argument_hint_values_quote_yaml_special_chars`, `argument_hint_keys_known_set`); all 3 pass; zero `serde_yaml` dep added |

**Score:** 13/13 truths verified

**Note on LIST-05 wording:** REQUIREMENTS.md LIST-05 specifies the exact string `No online listeners. Pass --all to include offline perches.` (with "perches"). The implementation uses `No online listeners. Pass --all to include offline.` (without "perches"). This deviation is consistent across: the PLAN frontmatter must_haves truth (which uses the shorter form), all tests, and is satisfied by the ROADMAP SC#3 which is semantically broad ("emits a stderr discovery hint pointing the user at `--all`"). The deviation is intentional (the PLAN adopted it from RESEARCH.md which used the shorter form) and the ROADMAP contract is met. This is recorded as informational only.

### Required Artifacts

| Artifact | Expected | Status | Details |
|----------|----------|--------|---------|
| `.planning/REQUIREMENTS.md` | LIST-04 reword + LIST-07 reword | VERIFIED | Contains `project_history`; LIST-04 no longer references `info.json.cwd`; LIST-07 contains `perch_has_repo_history` |
| `.planning/ROADMAP.md` | Phase 32 SC#2 reworded to `project_history` | VERIFIED | SC#2 references `info.json.project_history` and `current repo names` |
| `src/common/types.rs` | `InfoJson::project_history: Vec<String>` with serde default | VERIFIED | Line 61: `pub project_history: Vec<String>` with `#[serde(default, skip_serializing_if = "Vec::is_empty")]` at line 60 |
| `src/common/owlery.rs` | `derive_current_repo_names`, `perch_has_repo_history`, `append_project_history` exports | VERIFIED | Lines 396, 480, 515 — all three `pub fn` exports confirmed |
| `src/owl/poll.rs` | `append_project_history` call after info.json write | VERIFIED | Line 135: `owlery::append_project_history(id, &repo_names);` — inserted AFTER `fs::write` per Pitfall #1 |
| `src/live/start.rs` | `append_project_history` call after info.json write | VERIFIED | Line 245: `owlery::append_project_history(id, &repo_names);` — in reconnect branch AFTER `fs::write` |
| `src/common/list_filter.rs` | Shared `ListMode` + `collect()` function | VERIFIED | 292 LOC; exports `pub enum ListMode`, `pub fn collect(...)`, `pub struct PerchEntry`, `pub struct CollectedPerches` |
| `src/cli.rs` | `--all`/`--offline`/`--here` flags on owl and live list; `conflicts_with` | VERIFIED | `Commands::List` and `LiveCommands::List` both have the triplet; `conflicts_with = "all"` appears 2x (grep count confirmed) |
| `src/owl/list.rs` | Thin wrapper using `list_filter::collect` | VERIFIED | Calls `list_filter::collect(...)` at line 21; shrank from 271 to 187 LOC |
| `src/live/list.rs` | Thin wrapper using `list_filter::collect` | VERIFIED | Calls `list_filter::collect(...)` at line 21; shrank from 138 to 73 LOC |
| `tests/list_flags.rs` | 7 integration tests for flag mutex + hints | VERIFIED | 7 `#[test]` functions: D-09 mutex (2), LIST-05/D-10 hints (2), D-11 smart hint (1), LIST-02 (1), LIST-03 (1) — all pass |
| `plugin/spt/skills/list-ready/SKILL.md` | `argument-hint: "[--all] [--offline] [--here]"` | VERIFIED | Line 6 confirms exact value |
| `plugin/spt/skills/list-live/SKILL.md` | `argument-hint: "[--all] [--offline] [--here]"` | VERIFIED | Line 6 confirms exact value |
| `plugin/spt/skills/list-psyche/SKILL.md` | `argument-hint: "[--all] [--offline] [--here]"` | VERIFIED | Line 6 confirms exact value |
| `plugin/spt/skills/commune/SKILL.md` | `argument-hint: ""` (corrected from `<msg>`) | VERIFIED | Line 6 shows `argument-hint: ""` |
| `plugin/spt/skills/psyche-download/SKILL.md` | `argument-hint: "[<id>]"` | VERIFIED | Line 7 shows `argument-hint: "[<id>]"` |
| `plugin/spt/skills/whoami/SKILL.md` | `argument-hint: ""` | VERIFIED | Line 7 shows `argument-hint: ""` |
| `tests/skill_hints.rs` | 3 test functions; zero serde_yaml dep | VERIFIED | 3 `#[test]` fns confirmed; `grep -c serde_yaml tests/skill_hints.rs` = 0; all 3 tests pass |

### Key Link Verification

| From | To | Via | Status | Details |
|------|----|-----|--------|---------|
| `src/owl/poll.rs:134` | `src/common/owlery.rs::append_project_history` | call AFTER `fs::write` info.json | VERIFIED | Lines 134-135 confirmed; ordering correct per RESEARCH Pitfall #1 |
| `src/live/start.rs:244` | `src/common/owlery.rs::append_project_history` | call AFTER `fs::write` info.json (reconnect branch) | VERIFIED | Lines 244-245 confirmed; inside `if reconnecting` block, post-write |
| `src/common/owlery.rs::try_remote_origin_basename` | `Command::new("git")` shellout | `process::hide_window` + `remote get-url origin` | VERIFIED | `owlery.rs:439-443` uses `Command::new("git")` + `process::hide_window(&mut cmd)` + `.args(["remote", "get-url", "origin"])` |
| `src/cli.rs` | `list_filter::ListMode` | `ListMode::from_flags(all, offline)` | VERIFIED | `src/owl/mod.rs` dispatch arm uses `list_filter::ListMode::from_flags(all, offline)` |
| `src/owl/list.rs` | `list_filter::collect` | call with PerchState predicate + ListMode + here_names | VERIFIED | `owl/list.rs:21-31` calls `list_filter::collect(|state| !matches!(...), mode, here_names.as_deref(), true)` |
| `src/live/list.rs` | `list_filter::collect` | call with Live-only predicate | VERIFIED | `live/list.rs:21-26` calls `list_filter::collect(|state| *state == PerchState::Live, mode, ...)` |
| `list_filter::collect` | `owlery::perch_has_any_repo_history` | `--here` filter predicate | VERIFIED | `list_filter.rs:170,209,241` — three call sites inside collect() |
| `tests/skill_hints.rs` | `plugin/spt/skills/*/SKILL.md` | readdir walk via `CARGO_MANIFEST_DIR/plugin/spt/skills` | VERIFIED | `skill_hints.rs:18-23` builds skills dir from `env!("CARGO_MANIFEST_DIR")`; 17/17 skills found |
| `tests/skill_hints.rs` | `argument-hint` frontmatter key | hand-rolled YAML line-scan parser | VERIFIED | `parse_frontmatter_keys` (lines 29-56) extracts top-level YAML key-value pairs; tested against all 17 skills |

### Data-Flow Trace (Level 4)

| Artifact | Data Variable | Source | Produces Real Data | Status |
|----------|--------------|--------|--------------------|--------|
| `src/owl/list.rs::run` | `collected: CollectedPerches` | `list_filter::collect(...)` which reads owlery dir via `fs::read_dir` + `is_process_alive` checks | Yes — real filesystem + process state | FLOWING |
| `src/common/list_filter.rs::collect` | `here_names: Option<&[String]>` | `owlery::derive_current_repo_names()` which walks `current_dir()` parents for `.git` + shellout `git remote get-url origin` | Yes — real filesystem + git process | FLOWING |
| `src/owl/poll.rs` | `repo_names: Vec<String>` | `owlery::derive_current_repo_names()` then `append_project_history` writes to info.json Value round-trip | Yes — real filesystem write, deduped | FLOWING |

### Behavioral Spot-Checks

| Behavior | Command | Result | Status |
|----------|---------|--------|--------|
| D-09 mutex: `--all --offline` exits code 2 | `cargo test --test list_flags list_offline_conflicts_with_all` | PASS — exit code 2, stderr contains "cannot be used with" | PASS |
| LIST-05 hint fires on empty SPT_HOME | `cargo test --test list_flags list_default_emits_no_online_hint_on_empty` | PASS — stderr contains "No online listeners. Pass --all to include offline." | PASS |
| D-10 hint fires for `live list` | `cargo test --test list_flags live_list_default_emits_no_online_hint_on_empty` | PASS | PASS |
| D-11 smart hint when --here zeros online | `cargo test --test list_flags list_here_smart_hint_when_filter_zeroes_out` | PASS — D-11 hint fires | PASS |
| All 17 skills have argument-hint | `cargo test --test skill_hints every_skill_has_argument_hint` | PASS — 17/17 | PASS |
| Pinned values for 6 mandated skills | `cargo test --test skill_hints argument_hint_keys_known_set` | PASS | PASS |
| YAML quoting for special-char values | `cargo test --test skill_hints argument_hint_values_quote_yaml_special_chars` | PASS | PASS |
| Full integration suite | `cargo test --test list_flags` | 7/7 PASS | PASS |

### Requirements Coverage

| Requirement | Source Plan | Description | Status | Evidence |
|-------------|-------------|-------------|--------|---------|
| LIST-01 | 32-02 | No-flags shows online only | SATISFIED | `ListMode::Online` default; list_flags test confirms |
| LIST-02 | 32-02 | `--all` includes offline | SATISFIED | `ListMode::All` populates both buckets; test confirms |
| LIST-03 | 32-02 | `--offline` exclusive; mutex with `--all` | SATISFIED | `conflicts_with = "all"` in cli.rs; test confirms exit 2 |
| LIST-04 | 32-01 | `--here` filters by `project_history` repo-name | SATISFIED | `perch_has_any_repo_history` wired in list_filter.rs; D-11 test seeds project_history directly |
| LIST-05 | 32-02 | Empty-online stderr hint | SATISFIED (wording minor deviation) | Hint fires: "No online listeners. Pass --all to include offline." — ROADMAP SC#3 met; exact REQUIREMENTS.md string differs by "perches" at end |
| LIST-06 | 32-02 | `live/list.rs` shares `ListMode` + filter | SATISFIED | `list_filter.rs` shared module; both list files import and call `list_filter::collect` |
| LIST-07 | 32-01 | `perch_has_repo_history` in `owlery.rs`, reused by both list flows | SATISFIED | `pub fn perch_has_repo_history` at owlery.rs:480; `perch_has_any_repo_history` at owlery.rs:499; both used via list_filter.rs |
| LIST-08 | 32-02 | Golden fixtures updated for new default | SATISFIED | `list_one.*` deleted; `golden_list_empty` tests updated to assert new hint strings |
| HINT-01 | 32-03 | Every skill has `argument-hint` key | SATISFIED | 17/17; `every_skill_has_argument_hint` test PASS |
| HINT-02 | 32-03 | list-ready/list-live/list-psyche gain `[--all] [--offline] [--here]` | SATISFIED | All three files show the exact key+value |
| HINT-03 | 32-03 | commune/psyche-download/whoami hints corrected | SATISFIED | commune: `""`, psyche-download: `"[<id>]"`, whoami: `""` — pinned by test |
| HINT-04 | 32-03 | YAML-significant-char values double-quoted | SATISFIED | listen/live/revive/send updated; listen-stop/live-stop already quoted; test enforces |
| HINT-05 | 32-03 | Regression guard test | SATISFIED | `tests/skill_hints.rs` with 3 `#[test]` fns; zero-dep; all 3 PASS |

### Anti-Patterns Found

| File | Line | Pattern | Severity | Impact |
|------|------|---------|----------|--------|
| `src/common/list_filter.rs` | 90-91 | Duplicate variable name `let owlery = ...; let owlery_path = ...;` (both call `owlery_dir()` — style-level redundancy, both values used) | Info | Cosmetic only — both are used (`owlery` for read_dir, `owlery_path` for spool); no behavioral impact |

No `TBD`, `FIXME`, `XXX`, or unreferenced debt markers found in phase-modified files. No stub implementations found.

### Human Verification Required

None — all critical behaviors are covered by integration tests that run against a real compiled binary with isolated tempdir SPT_HOME.

### Gaps Summary

No blocking gaps. One informational wording note:

**LIST-05 hint string (informational only):** REQUIREMENTS.md LIST-05 specifies `No online listeners. Pass --all to include offline perches.` The implementation omits the trailing "perches", shipping `No online listeners. Pass --all to include offline.`. The PLAN's own must_haves truth, the RESEARCH.md reference table, and all tests all use the shorter form. The ROADMAP SC#3 is semantically satisfied ("emits a stderr discovery hint pointing the user at `--all`"). This is a REQUIREMENTS.md literal inconsistency introduced during planning, not an execution failure. No follow-up action required unless a future phase re-reads REQUIREMENTS.md and expects exact string matching.

---

_Verified: 2026-05-16T22:00:00Z_
_Verifier: Claude (gsd-verifier)_
