Hangar is a faithful — often cleaner — re-architecture of Multica’s task FSM, but it is not yet at feature parity: of 113 mapped Multica features, 19 are at parity, 36 partial, 45 gaps, and 13 out of scope by design — and three design-level holes (an unauthenticated unix socket, a producer-absent event push channel, a per-issue concurrency regression) sit underneath the feature deltas.
This review was produced by a 96-agent workflow. Five parallel extractors catalogued 102 code-level features plus 60 doc/site-level features on the Multica side (162 total) and 44 features on the Hangar side. An independent mapping pass produced the 113-row parity matrix below; every gap and partial claim was then adversarially re-checked against the Hangar codebase by separate verifier agents.
The verifier reproduced the claim against Hangar source with file:line evidence.
The claim was directionally right but the status or rationale changed on re-check (e.g. gap → partial).
The original claim was wrong — the feature exists (agent concurrency limit; appearance/theme). Statuses corrected.
Parity / out-of-scope rows that were not sent through the adversarial pass.
rg over ainb-hangar-daemon/src finds zero HangarEvent/EVENT_METHOD emission sites and zero auth-token use on the RPC path; bind() at rpc/mod.rs:104 creates the socket with default permissions.Every Multica feature mapped to its Hangar equivalent, grouped by area. Status: parity partial gap oos (out of scope by design). Verification badge per row; priority chip on gap/partial rows. Expand evidence & judgment for the verifier’s file:line evidence and the web-only-feature judgment.
| Multica feature | Hangar equivalent | Status | Verified | Rationale |
|---|---|---|---|---|
| Issues & projects · 26 rows | ||||
| Create issue (title, desc, status, priority, assignee, dates, labels) | issue create CLI/screen (F01/F03) | partial P1 | confirmed | Create supports title/description/state/assignee only; no priority, due dates, or labels in the issue model.evidence & judgmentWeb-only judgment — Issue creation is fully TUI/CLI-native and present, but the data model lacks priority/dates/labels fields, so partial with real gaps in those attributes. Verification evidence — store NewIssue/Issue ainb-hangar-store/src/repo/issue.rs:26-66 + wire IssueRow ainb-hangar-proto/src/events.rs:203-227 + CLI IssueCreateArgs ainb-core/src/cli/hangar/mod.rs:425-443 all = title/desc/state/assignee only; no priority/due/labels. Labels/priority live only in beads adapter (beads_adapter/mod.rs:99 discards priority). |
| Quick-create issue | issue create (F01) with c on issue list (F03) | partial P3 | unverified | 'c' on the issue-list screen creates an issue inline; a distinct origin-tagged quick-create lane is not modeled.evidence & judgmentWeb-only judgment — Inline quick-create is TUI-native and present; the origin-tracking nuance is the only missing piece. |
| Board & list views | Issue list (F03) + Kanban board (F04) | parity | unchallenged | Issue-list screen plus 4-column kanban with card-move RPC mirror Multica's list+board. |
| Grouped issue views | Kanban columns by task status (F04) | partial P3 | confirmed | Kanban groups by task status (queued/running/done/failed); grouping by arbitrary fields (status/priority/assignee) is not offered.evidence & judgmentWeb-only judgment — Status-grouped columns exist; richer group-by is TUI-expressible, hence partial. Verification evidence — Status grouping: issue_list.rs:37-76 (IssueColumn Todo/InProgress/Done), kanban.rs:56-112; tested in issue_list_reducer_test.rs:109-151. Assignee is filter not group: issue_list.rs:84-126. No priority on IssueRow (events.rs:203-215) or store; no group-by axis toggle in plugin. |
| Issue detail page (timeline, comments, subscribers, runs) | Task detail + live transcript (F08); task runs | partial P1 | confirmed | Hangar's detail shows the live agent transcript and PR badge but has no comments, subscribers, or full activity timeline.evidence & judgmentWeb-only judgment — Detail/transcript is TUI-native and present; missing comments/timeline/subscribers are TUI-expressible gaps, so partial. Verification evidence — task_detail.rs:508/519 (PR badge+transcript), :424-433 interleave CommentAdded, sidebar.rs:32-46 (status/assignee/project, no subscribers). CommentRow/table exist (events.rs:357, migration 0003) but NO daemon producer, no comment repo, no compose key (keymap :338-348 only j/k/R/X). Arch doc:155 scopes detail to transcript+badge+retry/cancel. No subscribers/separate timeline anywhere. |
| Edit / update issue (status, priority, assignee, project, dates) | task transition (F04 card move); assign (F11) | partial P1 | adjusted | State changes via kanban move and assignee via picker work; priority/project/dates are not in the model.evidence & judgmentWeb-only judgment — Field-edit is TUI-native; the missing fields (priority/project/dates) are real model gaps, so partial. Verification evidence — Rationale refuted: assignee picker intent dropped (app_screens.rs:646-655, route ignores out.intent); kanban moves tasks not issues (kanban.rs:1-10, methods.rs:119); no issue-write RPC (methods.rs:155-174); issue state only via beads (inbound.rs:184). Create ignored (app_screens.rs:557). Model lacks priority/project/dates (events.rs:203-227). |
| Batch update / delete issues | none | gap P2 | unverified | No multi-select batch update/delete in issue-list screen or CLI.evidence & judgmentWeb-only judgment — Multi-select batch ops are common in TUIs; absent, so gap not oos. |
| Parent / child sub-issues | none (issues); task retry parent/child (F06) | gap P2 | unverified | Tasks have parent/child for retries, but issues have no parent_id/sub-issue hierarchy.evidence & judgmentWeb-only judgment — Sub-issue nesting is a TUI-expressible data relationship; not present on issues, so gap. |
| Label issues (create/attach/detach labels) | none | gap P2 | confirmed | No label table or issue-label join; 'label' hits are bd-sync flags, not issue labels.evidence & judgmentWeb-only judgment — Issue labels are simple CRUD+chips, very TUI-native; entirely missing, so gap. Verification evidence — migrations/0003_issue_comment.sql:19-43 (no label table/join); store/src/repo/issue.rs:84-99 (insert has no label col); proto/events.rs:203-227 (IssueRow no labels); proto/methods.rs ALL_METHODS + daemon rpc/mod.rs:260-261 (only skill_attach/detach, no issue-label verb); beads_sync/inbound.rs:43 SYNC_LABEL="hangar-v1" is bd-filter flag only. |
| Custom issue metadata (KV get/set/delete) | none | gap P2 | confirmed | No per-issue key/value metadata store or CLI; pipeline-state tracking unavailable.evidence & judgmentWeb-only judgment — KV metadata is a CLI-native feature (Multica's own is CLI form-factor); absent in Hangar, so gap. Verification evidence — migrations/0003_issue_comment.sql:23-33 issue table has fixed cols, no metadata/KV col; proto/src/methods.rs:155-174 ALL_METHODS has no meta/kv method; store/src/repo/issue.rs:81,112,135,156 only insert/get/update_state/list — no set/get/delete_metadata; no issue_meta/pipeline_state anywhere. |
| Subscribe / unsubscribe to issues | none | gap P3 | confirmed | workspace/subscribe is an event-stream subscribe, not per-issue notification subscription; no issue-subscriber model.evidence & judgmentWeb-only judgment — Per-issue subscription driving notifications is TUI-expressible; missing, so gap. Verification evidence — ainb-hangar-proto/src/methods.rs:155 ALL_METHODS has only workspace/subscribe (event stream, line 19) + hangar/issues_list; no issue/subscribe. issue.rs:49 Issue struct lacks subscribers/watchers field; no subscription migration (0001-0010); no daemon handler; notification hits = notifyd/tracing only. |
| React to issues (emoji reactions) | none | gap P3 | confirmed | No reaction table or surface for issues.evidence & judgmentWeb-only judgment — Emoji reactions render fine in a TUI; absent, so gap (marginal). Verification evidence — No reaction table: migrations/0003_issue_comment.sql:19-43 has only issue+comment tables, no reaction col. IssueRow has no reaction field (proto/events.rs:203). issue_list.rs reducer keys are j/k///c(create)/a(assign)/enter only (issue_list.rs:338-352); no reaction RPC/event anywhere. |
| Search issues (full-text) | none | partial P2 | adjusted | No full-text search index or search RPC/CLI over issues; issue list has filter chips only.evidence & judgmentWeb-only judgment — Issue full-text search is CLI/TUI-expressible (Multica ships a CLI search); absent in Hangar, so gap. Verification evidence — issue_list.rs:152,171-173,231-234 has `/` free-text query: `r.title.to_lowercase().contains(&q)` (title-only, client-side). Claim's "filter chips only" is wrong. But no server search: rpc/mod.rs:203 + snapshots.rs:46 only *_LIST; no FTS/LIKE in migrations/0003 (only idx_issue_workspace_state) or repo/issue.rs:164. Narrower than Multica's ranked title+desc+comment LIKE search → partial. |
| My Issues view | issue list 'Mine' filter chip (F03) | partial P3 | unverified | Issue-list has a 'Mine' chip approximating My Issues, but no dedicated assigned/created/agent-scoped view.evidence & judgmentWeb-only judgment — A Mine filter exists; richer scoped views are TUI-expressible, hence partial. |
| Comment on issues | comment table (schema only); CommentRow event | gap P1 | confirmed | comment table and CommentRow event exist but there is no write path (no RPC/CLI/screen to post a comment).evidence & judgmentWeb-only judgment — Commenting is fully TUI-native (text input + list); schema present but no create surface, so gap and core-workflow important. Verification evidence — No write path: comment table only created (migrations/0003_issue_comment.sql:36), never inserted; no comment method in proto methods.rs ALL_METHODS (drift-guarded); CommentRow is display-only via fold at task_detail.rs:424; intents are RetryTask/CancelTask only (task_detail.rs:300-305). |
| Threaded comment replies | none | gap P3 | confirmed | Comment table has no parent_id; no threading and no comment write path at all.evidence & judgmentWeb-only judgment — Threaded replies are TUI-expressible but depend on the absent comment feature; gap. Verification evidence — migrations/0003_issue_comment.sql comment table has no parent_id (only id,issue_id,author_*,body,created_at). Zero "comment" mentions in ainb-hangar-daemon/src; no INSERT INTO comment / add_comment / comment RPC. CommentRow/CommentAdded (proto events.rs:356) only built in proto event_roundtrip_test.rs sample_comment — dead wire type, no write path. |
| Edit / delete comments | none | gap P3 | unverified | No comment mutation surface; comments cannot even be created.evidence & judgmentWeb-only judgment — Edit/delete are TUI-native list actions; gap because comments are absent end-to-end. |
| Resolve / unresolve comments | none | gap P3 | unverified | No resolved_at field or resolve surface on comments.evidence & judgmentWeb-only judgment — Resolve toggle is TUI-expressible; gap, contingent on comments existing. |
| React to comments | none | gap P3 | unverified | No comment reaction surface.evidence & judgmentWeb-only judgment — Reactions render in a TUI; gap, marginal. |
| Projects (status, priority, lead, grouped issues) | none | gap P2 | confirmed | No project model, project_id on issues, or project screen/CLI.evidence & judgmentWeb-only judgment — Projects are data+list views, fully TUI/CLI-expressible (Multica has a project CLI); absent in Hangar, so gap. Verification evidence — No project table in migrations 0001-0010; issue.rs:49-66 Issue has only workspace_id (no project_id/priority/lead); events.rs:203 IssueRow same; methods.rs lists 15 RPCs, none project; cli/hangar/mod.rs zero project tokens; sidebar.rs:45 "Project" label aliases issue.workspace_id. |
| Project resources | none | gap P3 | unverified | No projects, so no resource links/docs attachment.evidence & judgmentWeb-only judgment — Resource pointers are CLI-expressible; gap, contingent on projects. |
| Search projects | none | gap P3 | unverified | No projects and no project search.evidence & judgmentWeb-only judgment — Project search is CLI/TUI-expressible; gap, depends on projects. |
| Upload / view attachments | none | gap P3 | unverified | No file upload, issue-attachment listing, or preview surface.evidence & judgmentWeb-only judgment — Attaching files by path and listing them is CLI/TUI-expressible; image preview is oos but listing/attach is a gap, so overall gap. |
| Link PRs to issues with CI / conflict status | PR-URL capture (F36) + PR badge / open-in-browser (F37) | partial P2 | confirmed | Hangar parses a gh PR URL from the transcript and shows a badge with open-in-browser, but does not surface CI check results or merge-conflict status, nor auto-move-to-Done.evidence & judgmentWeb-only judgment — PR linking is TUI-expressible and partly present (URL+badge); CI/conflict status and auto-done are the gaps, so partial. Verification evidence — pr_url.rs:47 scrapes one PR URL via regex; result.rs:42 + events.rs:220 carry only pr_url:Option<String>; task_detail.rs:541 renders "▶ PR <url> [o] open". No ci_status/check_run/mergeable/conflict anywhere (grep all crates). FSM done stamps on task exit not PR merge (architecture.md:83). P9.md:40 scopes out webhooks. |
| Global search / Command palette (Cmd+K) | per-screen filter chips | gap P2 | confirmed | No global cross-entity search or command palette; only per-screen filter chips exist.evidence & judgmentWeb-only judgment — A fuzzy command palette is a classic TUI pattern (very TUI-native); absent, so gap. Verification evidence — router.rs:34-62 global keys are tab/help/quit only, no Cmd+K/palette; proto methods.rs:19-155 ALL_METHODS closed catalogue has no search/find RPC; issue_list.rs:152-234 `/` filters only current-screen issue titles; agent_picker.rs:7,92-103 `/` narrows assign-modal actors only. No cross-entity search anywhere. |
| Assignee frequency suggestions | agent picker recents pinned (F11) | partial P3 | confirmed | Agent picker pins recents, approximating frequency suggestions, but not a true frequency-ranked assignee model.evidence & judgmentWeb-only judgment — Recency/frequency ordering is TUI-expressible and partly present (recents), so partial. Verification evidence — No frequency model exists. Picker pins by recency only (agent_picker.rs:117-131 sort_recent_then_alpha; events.rs:248-250 recent_rank). Daemon hardcodes recent_rank: None for every actor (snapshots.rs:113-116,138,149), so even recency pinning is inert; only alpha+filter is live. |
| Agents, tasks & runtimes · 19 rows | ||||
| Assign issue to agent or member | Agent picker (F11) + --assign enqueue (F01) | parity | unchallenged | Agent picker assigns human or agent; agent assignment enqueues a task — the core assign-and-queue flow. |
| Rerun issue task | task retry (F06/F09); task detail r | parity | unchallenged | 'r' in task detail and CLI task retry spawn a fresh execution, matching rerun. |
| Cancel running task | task cancel (F08 x / F09 CLI) | parity | unchallenged | Task detail 'x' (confirm modal) and CLI task cancel match cancel-in-progress. |
| View task runs & run messages | Task detail live transcript (F08); execution history | partial P2 | unverified | Single live transcript per task is shown; a list of historical runs per issue with per-run message logs is not fully surfaced.evidence & judgmentWeb-only judgment — Transcript view is present and TUI-native; the per-issue run-history list is the gap, so partial. |
| Mention agent in comment to trigger it | none | gap P1 | confirmed | No comment mention parser or comment-triggered task path; comments themselves are unreachable.evidence & judgmentWeb-only judgment — @-mention-to-trigger is a core agent-collaboration loop and TUI-expressible; absent, so an important gap. Verification evidence — No mention parser/regex over comment bodies anywhere; no comment-write RPC (methods.rs ALL_METHODS lacks any comment method); zero comment refs in ainb-hangar-store. CommentAdded (events.rs:97) only rendered read-only at task_detail.rs:424, produced solely by test fixtures. No comment-triggered task-spawn path. (Rationale's "comments unreachable" is wrong-they render-but feature absent.) |
| Agent posts progress / blocker comments | live task transcript stream (F08/F40) | partial P2 | confirmed | Running agents stream progress to the task transcript, but they do not write durable system-authored comments on the issue.evidence & judgmentWeb-only judgment — Progress reporting exists via transcript (TUI-native); persisting it as issue comments is the gap, so partial. Verification evidence — Progress streams: runner.rs:294 stream_stdout; docs/hangar/architecture.md:82. But comment table (migrations/0003_issue_comment.sql:36) is unwired: no SQL touches it, no repo/comment.rs, no hangar/comment RPC in proto/src/methods.rs; CommentRow/CommentAdded (events.rs:96,355) constructed only in tests, never by daemon/runner. |
| Create / edit / archive agents | templates use materialises agent (F18); agent repo | partial P1 | confirmed | Agents can be created by materialising a template (CLI), but there is no general create/edit/archive/restore agent CRUD surface or archived flag.evidence & judgmentWeb-only judgment — Agent CRUD is fully CLI/TUI-expressible; only template-instantiation exists, no edit/archive, so partial. Verification evidence — agent.rs:55/79/94 AgentRepo=insert/get/list only (no update/delete); migration 0002 agent table has no archived col; methods.rs ALL_METHODS has only hangar/agents_list; zero `archiv` matches in all hangar crates+CLI; create only via `templates use` (cli/hangar/mod.rs) + seed.rs. |
| Create agent from template | templates list/show/use (F17/F18) | parity | unchallenged | 10 curated embedded templates with list/show/use that materialises a live agent + skills. |
| Configure agent runtime, model, args, env, MCP, thinking | agent table (instructions/runtime/visibility only); env allowlist (F27) | partial P1 | confirmed | Runtime binding and env allowlist exist, but model, thinking level, custom CLI args, MCP config, and per-agent env vars are not in the agent model.evidence & judgmentWeb-only judgment — Agent config is CLI/TUI-expressible; most knobs (model/args/MCP/thinking/env) are absent from the schema, so partial with substantial gaps. Verification evidence — agent model has only id/workspace_id/name/runtime_id/instructions/visibility/owner_id (migrations/0002_agent_runtime_skill.sql:31-39, repo/agent.rs:26-42). Runtime binding present (runtime_id FK + provider). Env is global EnvPolicy allowlist, not per-agent (env_policy.rs:84). Zero matches for mcp/model_id/thinking_level across all hangar crates. Thinking refs are transcript display only (task_detail.rs:51). |
| Agent concurrency limit | none (per-agent); daemon claim-slot cache (F34) | parity | refuted | Daemon has global claim-slot bounds but no configurable per-agent max-concurrency field.evidence & judgmentWeb-only judgment — Per-agent concurrency is a config field, TUI-expressible; absent, so gap. Verification evidence — migrations/0006_claim_dispatch_concurrency.sql:24 adds `agent.max_concurrent_tasks INTEGER NOT NULL DEFAULT 1`; store/src/service/claim.rs:108-111 enforces it atomically in CLAIM_SQL (cites Multica task.go:761 CountRunningTasks); tests/claim_task_integration.rs:245,275 cover caps of 1 and 2. |
| Cancel all agent tasks | per-task cancel (F08/F09) | partial P3 | confirmed | Individual tasks can be cancelled, but there is no single 'cancel all tasks for this agent' operation.evidence & judgmentWeb-only judgment — Bulk-cancel-by-agent is a CLI verb, TUI-expressible; only per-task exists, so partial. Verification evidence — Per-task cancel only: cancel.rs:41 cancel(task_id); methods.rs:121 task_transition params {task_id}; cli/hangar/mod.rs:466,482 Cancel(TaskIdArgs{id}); kanban.rs:301 single-card transition; task_detail.rs:304 CancelTask(TaskId). No by_agent/bulk cancel query in store or daemon. |
| Set agent avatar | none | oos | unchallenged | Avatar is an uploaded image with no meaningful terminal rendering.evidence & judgmentWeb-only judgment — Image avatars have no TUI rendering surface; oos. (Presence dots in the picker are the TUI-native identity cue.) |
| Live task progress streaming | task detail live transcript (F08); task message stream (F40) | parity | unchallenged | 5-colour live transcript streams task messages as events to the UI, matching task:message/updated streaming. |
| Manage agent runtimes / Unified runtimes dashboard | Daemon health pane (F34) lists registered runtimes; daemon status CLI | partial P2 | confirmed | Daemon health lists registered runtimes with status, but there is no full manage surface (update, set visibility, delete, archive-and-delete).evidence & judgmentWeb-only judgment — Runtime listing+status is present and TUI-native; the management verbs (delete/visibility/update) are the gap, so partial. Verification evidence — methods.rs:155-173 ALL_METHODS has only hangar/agents_list + hangar/daemon_health (read); no update/delete/visibility/archive verbs. agent_runtime.rs:37-94 + agent.rs:55-94 repos are insert/get/list only despite agent carrying visibility/owner (architecture.md:112). daemon_health.rs screen has zero key handlers. |
| Inspect runtime models / skills / update CLI | none | gap P3 | confirmed | No query of a runtime's available models or local skills, and no runtime CLI-update trigger.evidence & judgmentWeb-only judgment — Querying runtime models/skills is CLI-expressible; absent, so gap. Verification evidence — agent_runtime.rs:17-32 + migration 0002_agent_runtime_skill.sql:16-24 store only provider/mode/status (no models/version); settings.rs:113-120 RuntimeHealthRow exposes only provider/connected/pid; methods.rs lists 15 RPCs, none query runtime models or trigger CLI update; runner.rs:1 spawns claude only to execute tasks. |
| Cloud runtime node control | none (runtime_mode cloud enum only) | oos | adjusted | Schema models a 'cloud' runtime_mode but there is no create/start/stop/reboot/exec on cloud nodes.evidence & judgmentWeb-only judgment — Node lifecycle commands are CLI-expressible; the enum exists but no control surface, so gap (and Multica itself marks cloud as waitlist). Verification evidence — No cloud-node verbs exist (methods.rs:155-174 ALL_METHODS test-guarded; agent_runtime.rs:45-94 only insert/get/list; seed.rs:171 local-only). But it's a deliberate scope-out: build-plan.md:56 "Cloud runtime: skip entirely"; Multica's cloud control is a proxy to a closed SaaS fleet (research/01:136, research/04:43). |
| Multiple coding-agent backends (12 providers) | claude runner only (F43); Provider trait stub | partial P1 | confirmed | Only the claude provider ships; a Provider trait exists for future codex/copilot/gemini but no other backend is implemented.evidence & judgmentWeb-only judgment — Multi-provider execution is daemon-native and core to the product; only 1 of 12 implemented, so partial with a major gap. Verification evidence — runner.rs:137-152 only Provider impl=Runner("claude"); run_loop.rs:284 unconditionally run_claude (no codex/copilot/gemini exec path); cfg only claude_path/HANGAR_CLAUDE_PATH. Scaffolding real+tested: materialise.rs:57-122 ProviderSkillLayout (claude/codex/cursor/copilot/gemini) + materialise_skills_tests.rs. Core registry (4 providers) unused by hangar crates. |
| Task lifecycle & state machine (queued/running/completed/failed/cancelled, timeouts, retries, orphan reclaim) | Task FSM (F05) + retry chain (F06) + TTL sweepers (F07) + runner timeout (F43) | parity | unchallenged | Strict task FSM, parent/child retry with max-attempts, TTL orphan/stale sweeps, and runner timeout cover the full lifecycle. |
| Session resumption (reuse session_id + work_dir) | runner pins first session_id (F43); ExecEnv workdir | partial P2 | confirmed | Runner pins the claude session_id and uses an isolated ExecEnv workdir per task, but reuse across the same (agent,issue) pair to preserve context is not confirmed.evidence & judgmentWeb-only judgment — Session reuse is daemon-internal and TUI-invisible; session_id pinning present, cross-task reuse is the unverified gap, so partial. Verification evidence — runner.rs:192-205 spawns claude with ZERO args (no .arg/.args); grep for --resume/-r/--session/--continue across daemon+core = 0 hits. session_id only captured outbound (runner.rs:302-319), persisted (complete.rs:63-68, run_loop.rs:329) but never fed back. claim.rs:37-40 exposes prior_session_id but execute_claimed (run_loop.rs:228-284) never reads it. Workdir keyed per-task-id (execenv.rs:144; worktree.rs:46 branch hangar/task/{shortID}); resume = same-task recovery only (worktree.rs:81-86). retry.rs:64,108-116 drops session_id (NULL), only work_dir inherited. No (agent,issue) context reuse exists. |
| Autopilots · 8 rows | ||||
| Create / edit / delete autopilots | Autopilot cron CRUD (F19); Autopilots screen (F22) | parity | unchallenged | CLI create/list/enable/disable and Autopilots screen a/e/d cover autopilot CRUD with prompt+assignee. |
| Schedule autopilot via cron | Autopilot cron + scheduler (F19/F20) | parity | unchallenged | Cron validation, next_tick scheduling, and a firing scheduler match cron scheduling. |
| Manually trigger autopilot | autopilot run / fire_now (F20/F23); screen r | parity | unchallenged | CLI 'autopilot run' and screen 'r' run-now both fire immediately via fire_now RPC. |
| Webhook-triggered autopilots | none | gap P2 | confirmed | Only cron + manual triggers; no webhook URL, signing secret, or event filters.evidence & judgmentWeb-only judgment — Webhook ingestion needs an HTTP listener; a local daemon can expose one and it is configurable via CLI, so this is a gap not oos (capability is server-side, not browser-bound). Verification evidence — autopilot/mod.rs:1 "cron-scheduled"; service.rs:38-99 Autopilot/CreateAutopilot carry only cron_expr/next_tick_at, no webhook/secret/filter fields; proto methods.rs:83-106 only autopilots_list/runs/fire_now/set_enabled (cron + manual); pr_url.rs:3 "no webhook"; daemon lib.rs:74 only UnixListener, no HTTP ingress. |
| Rotate webhook token / set signing secret | none | gap P3 | unverified | No webhook trigger exists, so no token rotation or HMAC secret management.evidence & judgmentWeb-only judgment — Token/secret management is CLI-expressible; gap, contingent on webhook triggers. |
| View autopilot runs & deliveries | Autopilots screen run list (F22); autopilot_runs RPC | partial P3 | confirmed | Autopilot run history is listed, but there are no webhook deliveries to inspect/replay (no webhooks).evidence & judgmentWeb-only judgment — Run history is present and TUI-native; the webhook-delivery inspection half is gap, so partial. Verification evidence — Runs at parity: methods.rs:91 HANGAR_AUTOPILOT_RUNS; autopilots.rs:44,82,350 run-history pane; snapshots.rs:380 autopilot_runs; tested autopilots_reducer_test.rs:37. Deliveries absent: zero webhook_deliveries/replay in code; P7.md:6 "Out of scope: webhook autopilots"; build-plan.md:55 "no webhook ingress at v1". |
| Autopilot modes & concurrency (create_issue/run_only; skip/queue/replace) | scheduler skip when in-flight (F21) | partial P2 | confirmed | Concurrency skip-when-running is implemented, but explicit create_issue vs run_only modes and queue/replace policies are not exposed.evidence & judgmentWeb-only judgment — Modes/policies are config enums, TUI-expressible; only skip-concurrency present, so partial. Verification evidence — Skip-only: scheduler.rs:38-42 + architecture.md:143; queue/replace enum collapsed to int (P7.md:17). No create_issue/run_only mode — fire path hardcodes issue_id=NULL (autopilot_run.rs:133); grep finds no ExecutionMode/create_issue/run_only/queue/replace in hangar src. Multica had all three (01-codebase-archaeology.md:269,493). |
| Autopilot templates (one-click presets) | none | gap P3 | confirmed | No built-in autopilot preset templates (news digest, PR review, bug triage, etc.).evidence & judgmentWeb-only judgment — Preset autopilots are embedded data + a picker, TUI-expressible; absent, so gap. Verification evidence — No autopilot preset surface: cli/hangar/mod.rs:142-176 AutopilotCommand=create/list/disable/enable/run (freeform name+cron+agent), autopilots.rs:127-131 Add/Edit open blank create flow, rpc/mod.rs:262-265 no create RPC. template/mod.rs:59-94 embeds 10 AGENT templates (no cron); all templates/*.json lack autopilot/schedule keys. |
| Squads · 5 rows | ||||
| Assign issue to squad | none | gap P2 | unverified | No squad model, leader-routing, or squad assignment anywhere in Hangar.evidence & judgmentWeb-only judgment — Squad assignment is data+orchestration, fully TUI/CLI-expressible; entirely absent, so gap. |
| Create / edit / archive squads | none | gap P2 | confirmed | No squad model, CLI, or screen.evidence & judgmentWeb-only judgment — Squad CRUD is data+TUI-expressible; entirely absent, so gap. Verification evidence — ainb-hangar-core/src/actor.rs:24-40 ActorKind only Member|Agent (no squad). Zero `squad` hits in any hangar crate/plugin/sql. methods.rs lists workspace/agents/autopilots/issues/skills/tasks only. 16 migration tables, none a squad/group. hangar-tui has no squad screen. |
| Manage squad members & roles | none | gap P3 | unverified | No squad membership surface at all.evidence & judgmentWeb-only judgment — Squad-member management is TUI-expressible; gap, depends on absent squads. |
| Squad leader routing | none | gap P3 | confirmed | No leader-agent briefing/routing logic in the daemon.evidence & judgmentWeb-only judgment — Leader routing is server orchestration with a TUI status view; absent, so gap. Verification evidence — actor.rs:24-29 ActorKind={Member,Agent} only; migrations/0003_issue_comment.sql:25 assignee_type CHECK IN('member','agent'); no squad/leader_id table in any migration; daemon "leader" refs (runner.rs:207,352) are POSIX process-group, not agent routing. Plan admits "need squad DB entity + routing". |
| Squad member status | presence dots in agent picker (F11) | gap P3 | confirmed | Presence dots exist for agents generally, but there is no squad construct to show per-member squad status.evidence & judgmentWeb-only judgment — Live status rendering exists (presence dots), but squad scoping is absent; gap. Verification evidence — No squad construct in hangar: migrations/0003_issue_comment.sql:25 enforces assignee_type IN ('member','agent') (no 'squad'); no squad/squad_member table in any migration; rg 'squad' over crates/ exits 1 (zero hits). actor.rs:20-26 only Member/Agent; PresenceState (events.rs:187) is per-agent, not per-squad-member. |
| Skills · 5 rows | ||||
| Assign skills to agents | Skill manager attach/detach (F15); agent_skill M:N (F13) | parity | unchallenged | Skill manager i/d attach/detach to selected agent over agent_skill join matches skill assignment. |
| Create / edit / delete skills | Skill repo CRUD (F13); skills sync (F14); skill manager (F15) | partial P2 | unverified | Skills are imported/synced and attached/detached and viewed, but authoring/editing/deleting individual skills from the TUI is not the primary surface (sync-driven).evidence & judgmentWeb-only judgment — Skill repo CRUD exists at the store layer and is TUI-expressible; in-TUI authoring/edit/delete is limited, so partial. |
| Import skills | skills sync importer (F14) | partial P2 | confirmed | Imports the local toolkit skills tree idempotently, but importing from arbitrary URLs (clawhub/skills.sh) is not supported.evidence & judgmentWeb-only judgment — Local-tree import is present and TUI/CLI-native; URL import is a gap, so partial. Verification evidence — Local toolkit-dir import only: skills_sync.rs:148-219 ToolkitDirImporter+skills_sync_from (idempotent upsert_by_name); CLI source is Option<PathBuf> (cli/hangar/mod.rs:256), no URL arg. UrlImporter (skills.sh/GitHub) is explicitly "future" (skills_sync.rs:24-29); no reqwest/http-fetch in import path. Plugin exposes only `s sync` (skill_manager.rs:486). Tested in tripwire_skill_import_and_dispatch.rs. |
| Search skills | skill manager filter chips (F15) | parity | adjusted | Skill manager has All/Used/Unused/Mine filter chips but no free-text skill search query.evidence & judgmentWeb-only judgment — Free-text skill search is TUI-expressible; only chip filters present, so partial. Verification evidence — skill_manager.rs:36-45 All/Used/Unused/Mine chips; reduce_key:260-271 j/k/s/i/d/Enter/r (no '/' text mode); snapshots.rs:165 skills_list(workspace) no query param. Upstream multica skill.go:217 only ListSkills/GetSkill (no search); UX research 05:106 skills UI = chips only. Hangar fully replicates multica's chip-based skill filtering. |
| Manage skill files (list/upsert/delete) | skill_file model + skill manager file tree (F13/F15) | partial P3 | confirmed | skill_file CRUD exists in the store and a file tree is rendered/viewable, but in-TUI upsert/delete of individual files is not surfaced.evidence & judgmentWeb-only judgment — File-list view present (TUI-native); per-file edit/delete is the gap, so partial. Verification evidence — skill.rs:151,212,292,470 have full skill_file CRUD; skill_manager.rs:260-271 binds only s/i/d/Enter/r (no file create/edit/delete); editor_pane.rs:1 read-only; SkillManagerIntent (209-225) lacks any file-mutation variant; methods.rs:155-174 exposes no upsert/delete RPC. |
| Chat · 4 rows | ||||
| Chat with an agent | none | gap P2 | confirmed | No 1:1 agent chat sessions, messages, or chat screen; transcript is task-scoped only.evidence & judgmentWeb-only judgment — Streaming agent chat is fully TUI-native (a message pane); entirely absent, so gap. Verification evidence — No chat_session/chat_message tables (migrations 0001-0010, grep NONE). No Screen::Chat (screen/mod.rs:44 enum). Messages task-scoped: HangarEvent::TaskMessage{task_id,...} (events.rs:79-86). "Chat task" is only worktree-skip (worktree.rs:49-73; 0004:6), no enqueue/chat path. Multica uses chat_session+chat_message (research/01:265). |
| Attach files in chat | none | gap P3 | unverified | No chat and no file-attachment binding to a chat session.evidence & judgmentWeb-only judgment — File attach by path is CLI/TUI-expressible; gap, depends on absent chat. |
| Chat unread tracking | none | gap P3 | confirmed | No chat sessions, so no unread tracking.evidence & judgmentWeb-only judgment — Unread badges are TUI-expressible; gap, contingent on chat. Verification evidence — No chat conversation surface in hangar: proto RPC catalogue has zero chat/messages/inbox/unread methods (ainb-hangar-proto/src/events.rs:23, lib.rs); no chat/inbox screen in plugins/hangar-tui/src/screen/app_screens.rs. "chat" is only a no-issue_id task origin (worktree.rs:12, task.rs:16), not sessions+messages. notifyd's unread_by_session (ainb-plugin-notifyd/src/store.rs:248) tracks agent-hook notifications, unrelated to hangar, no chat ref. |
| Pending chat task indicator | task-started banner (F10) for task flow | gap P3 | unverified | Task banners exist for the issue/task flow but there is no chat surface to show pending chat tasks.evidence & judgmentWeb-only judgment — Pending-task indicators are TUI-native; absent in a chat context that does not exist, so gap. |
| Notifications · 3 rows | ||||
| Notification inbox | none | gap P2 | confirmed | No activity inbox aggregating issue/comment/task notifications with unread counts.evidence & judgmentWeb-only judgment — A notification inbox is fully TUI-native (a list screen with unread counts); absent, so gap. Verification evidence — No Inbox screen in Screen enum (plugins/hangar-tui/src/screen/mod.rs:44-69). HangarEvent (ainb-hangar-proto/src/events.rs:39) is live stream, not persisted/aggregated. "notification" = JSON-RPC only (events.rs:4-35). No unread/read_at/mark_read in core/daemon/proto/store. TUI-viable (Logs/Kanban prove it), so not oos. |
| Bulk inbox actions | none | partial P3 | adjusted | No inbox, so no mark-all-read / archive-all actions.evidence & judgmentWeb-only judgment — Bulk list actions are TUI-native; gap, depends on absent inbox. Verification evidence — Rationale "no inbox" is false: host TUI has a notification Inbox with bulk archive. ainb-plugin-notifyd/src/store.rs:431 dismiss_visible() archives every visible row; inbox.rs:244 + events.rs:207 bind it to Shift+C; store.rs:411 mark_read. Hangar's own RPC (proto/methods.rs:155) lacks it, but the Inbox is a host builtin screen (events.rs:894) in the same binary. No mark-all-read sweep -> partial, not gap. |
| Notification preferences | none | gap P3 | confirmed | No notification preference configuration in Settings or CLI.evidence & judgmentWeb-only judgment — Preference toggles are TUI-native settings; absent, so gap. Verification evidence — os_notifications hardcoded true (listener.rs:49); only test sets false (listener.rs:252). No config/env/CLI toggle (bin/ainb-notifyd.rs:30 = Run/Stop/Install/Uninstall/Status). Settings categories lack Notifications (state.rs:1189-1197). Daemon Settings = Daemon/Providers only (tripwire_p4_settings_renders.rs:30-37). |
| Settings, workspaces & onboarding · 21 rows | ||||
| Onboarding questionnaire / 5-step onboarding flow | danger-full-access first-run warning (F28); firstrun.rs wizard | partial P2 | unverified | Hangar has a first-run flow (provider danger warning) but no source/role/use-case questionnaire or runtime-provisioning onboarding wizard.evidence & judgmentWeb-only judgment — A guided first-run is fully TUI-expressible (Hangar already has firstrun.rs), so the missing questionnaire/runtime-bootstrap steps are a real gap, not oos. |
| Bootstrap runtime during onboarding | daemon boot + runtime auto-register (F12/F38) | partial P3 | confirmed | Daemon registers runtimes on boot but there is no guided onboarding step to provision/opt-out/join-waitlist a starter runtime.evidence & judgmentWeb-only judgment — Runtime provisioning is CLI/daemon-shaped and partly present; the onboarding-wizard wrapper is the gap, TUI-expressible. Verification evidence — No runtime onboarding step: setup_menu.rs:24-30 (wizard = deps/git/auth/editor/reset only); onboarding.rs has no runtime field. Multica's CLI-detect Runtime step is TUI-able (05-ux-design-analysis.md:37-48). Rationale's "registers on boot" is wrong — boot() (daemon lib.rs:173-232) never inserts; all agent_runtime inserts are test-only (seed.rs + tests/). |
| Create workspace | Settings new workspace (F29 n key); workspace repo | parity | unchallenged | Settings screen 'n' creates a workspace via workspace:write capability; reserved-slug blocking not verified but core create exists. |
| Switch / list workspaces | Settings workspace switch (F29); workspace/list RPC | parity | unchallenged | Settings 's' sets active, 'd' default; workspace/list RPC enumerates; mirrors switch/list. |
| Edit workspace name & avatar | Settings rename workspace (F29 r key) | partial P3 | unverified | Rename exists; avatar is an image upload with no TUI rendering surface so only name-edit reaches parity.evidence & judgmentWeb-only judgment — Name-rename is TUI-native (present). Avatar image upload/display has no terminal rendering equivalent, so the avatar half is oos and the feature overall is partial. |
| Manage workspace members & roles | member table with roles (schema only) | gap P2 | confirmed | member table carries owner/admin/member roles but there is no RPC/CLI/screen to list members, change roles, or remove members.evidence & judgmentWeb-only judgment — Member/role management is plain CRUD fully expressible as CLI verbs or a TUI screen; schema exists but no surface, so gap. Verification evidence — member(role owner/admin/member) is data-only: migrations/0001_init_workspace_user_member.sql:23-28; seeded once as owner seed.rs:75-78, cli/hangar/mod.rs:1511. Test-enforced method catalogue methods.rs:155-174 has zero member-mutation RPCs. members_of folds members read-only into agents_list snapshots.rs:142-217. No member screen (settings = keys+workspace switch, architecture.md:159); no set_role/remove_member fn or test anywhere. TUI equivalent is feasible, not web-only. |
| Invite members / revoke invitations | none | gap P2 | unverified | No invitation table, RPC, or CLI; single-user local daemon, but multi-member workspaces are modeled so invite is a missing CRUD surface.evidence & judgmentWeb-only judgment — Invitation issuance is data-CRUD expressible in CLI/TUI; absence is a gap, though low urgency for a local single-operator tool. |
| Accept / decline invitations | none | gap P3 | unverified | No invitation surface at all; depends on invite feature which is absent.evidence & judgmentWeb-only judgment — Accept/decline is a TUI-expressible list action; gap because no invite pipeline exists. |
| Leave workspace | none | gap P3 | unverified | No leave-membership operation in RPC/CLI/Settings; member rows cannot be self-removed.evidence & judgmentWeb-only judgment — Self-removal from a workspace is a simple CLI/TUI action; gap not oos. |
| Pin items to sidebar | none | gap P3 | unverified | No pin model or pin/reorder surface for issues/projects/agents.evidence & judgmentWeb-only judgment — Pinning favourites is TUI-expressible (a pinned list); absent, so gap (marginal). |
| Usage dashboard | Daemon health pane + sparkline (F34); daemon status | partial P2 | confirmed | Daemon-health shows runtimes, concurrent tasks, and a throughput/failure sparkline, but there is no token-usage or per-agent usage rollup dashboard.evidence & judgmentWeb-only judgment — Usage charts are TUI-expressible (sparkline already present); token/per-agent usage rollup is the gap, so partial. Verification evidence — daemon_health.rs:1-9,37-48 renders only runtimes+claim_cache+concurrent_tasks+throughput sparkline; proto settings.rs:131-141 DaemonHealthSnapshot has no token/cost/per-agent fields ("never persists it"); methods.rs has no usage method. Real token-usage burndown plugin (provides analytics, /usage) is ainb-tui host, zero refs in hangar-tui/daemon/proto/core. Multica usage/daily+by-agent rollup absent. |
| Connect GitHub (App install/manage) | none | oos | unchallenged | GitHub App installation is an OAuth/web App-install handshake; no terminal install flow.evidence & judgmentWeb-only judgment — App installation requires the GitHub web consent flow; oos. (PR-URL capture below is the TUI-native slice Hangar does provide.) |
| Lark/Feishu integration | none | oos | unchallenged | Lark bot install/bind and chat-to-issue is a hosted third-party chat webhook integration with no local-TUI equivalent.evidence & judgmentWeb-only judgment — Lark binding is a SaaS OAuth/webhook flow tied to Lark's cloud; no terminal-equivalent capability, so oos. |
| Personal access tokens (create/list/renew/revoke) | PAT / daemon tokens (F26) | partial P3 | confirmed | Create/list/revoke PATs (hash-only, shown once) plus hidden daemon-token; renew/rotate is not listed.evidence & judgmentWeb-only judgment — PAT management is CLI-native and largely present; the renew verb is the only apparent gap, so partial. Verification evidence — cli/hangar/mod.rs:342-349 TokenCommand=Create/List/Revoke only (no Renew); :1143 mint_pat, :1170 list_by_user, :1179 revoke; :336 daemon-token hide=true. store/repo/token.rs:94 stores sha256 only. Zero renew/rotate hits anywhere. |
| Edit user profile & preferences (name/lang/timezone) | none | gap P3 | confirmed | No user profile/description/language/timezone editing in Settings or CLI.evidence & judgmentWeb-only judgment — Profile/preference editing is TUI-native settings; absent, so gap. Verification evidence — Settings has only Daemon/Providers/Keys/Workspaces sections (plugins/hangar-tui/src/screen/settings.rs:61-70); RPC catalogue lacks any user/profile/prefs method (ainb-hangar-proto/src/methods.rs); user table = id/email/created_at only, no name/lang/tz (ainb-hangar-store/migrations/0001_init_workspace_user_member.sql:17-21). No timezone/language/locale hits anywhere. |
| Submit feedback | none | gap P3 | unverified | No in-product feedback submission path.evidence & judgmentWeb-only judgment — A feedback submit (text + POST) is CLI/TUI-expressible; absent, so gap (marginal). |
| Contact sales / enterprise inquiry | none | oos | unchallenged | Landing-site sales inquiry form is a marketing web surface, irrelevant to a local TUI tool.evidence & judgmentWeb-only judgment — Contact-sales is a public marketing-site lead form; no TUI-equivalent purpose, so oos. |
| Multi-language UI / localized docs & CLI | none | gap P3 | confirmed | No i18n/localization of the TUI or CLI messages (English only).evidence & judgmentWeb-only judgment — Localization (string tables, locale switch) is TUI-expressible; absent, so gap (marginal for a dev tool). Verification evidence — No i18n crate in any ainb-tui/hangar Cargo.toml. settings.rs:95-100 sections = Daemon/Providers/Keys/Workspaces only, no locale. runner.rs:47-48 LANG/LC_ALL are subprocess env pass-through, not UI localization. No translation/message-catalog anywhere. |
| Workspace context prompt (per-workspace system prompt) | none | gap P2 | confirmed | No workspace-level context/system prompt injected into every agent.evidence & judgmentWeb-only judgment — A per-workspace prompt is a config field injected at dispatch, fully TUI/CLI-expressible; absent, so gap. Verification evidence — workspace table has no prompt col (migrations/0001_init_workspace_user_member.sql:11-16); WorkspaceRow lacks one (ainb-hangar-proto/src/settings.rs:60-72); execenv writes no CLAUDE.md (execenv.rs:8-13); run_claude passes zero prompt args (runner.rs:192-205); synonym sweep + daemon tests empty. |
| Multi-workspace isolation (context prompt, repo whitelist, issue prefix) | workspace-scoped store + Settings switch (F29) | partial P2 | confirmed | All store queries are workspace-scoped and workspaces can be created/switched, but per-workspace context prompt, repo whitelist, and issue prefix are not modeled.evidence & judgmentWeb-only judgment — Workspace isolation is present at the data layer; the per-workspace config extras (prompt/whitelist/prefix) are TUI-expressible gaps, so partial. Verification evidence — workspace table only id/slug/name/created_at (0001_init_workspace_user_member.sql:10-15); WorkspaceRow wire = id/slug/name/current/default (proto/settings.rs:55-68); scoping+switch present (repo/issue.rs:164, tripwire_workspace_switch_e2e.rs:56-150); zero context_prompt/repo-whitelist/issue_prefix hits anywhere; no workspace.rs repo. |
| Appearance / theme (light/dark/system) | none (TUI style guide / fixed palette) | parity | refuted | No light/dark/system theme selection in Settings.evidence & judgmentWeb-only judgment — Terminal colour themes are TUI-expressible (palette switch); no toggle present, so gap (marginal). Verification evidence — Host Settings has live Appearance category with Dark/Light/System Choice: state.rs:1462-1475 (seed), 1741-1755 (load), 1875-1882 (apply); persisted UiPreferences.theme config/mod.rs:505 (default "dark"); rendered config_screen.rs:152; roundtrip tests config/mod.rs:1057,1107. Note: lives in ainb host shell, not hangar-tui plugin's own settings pane. |
| Auth · 7 rows | ||||
| Email verification-code login (auth) | PAT / daemon tokens (F26); CLI auth token | oos | unchallenged | Email magic-code is a browser/email round-trip; Hangar is a local single-user daemon authed via tokens/keychain, no email server or session cookies.evidence & judgmentWeb-only judgment — Email-code login presupposes a multi-tenant web server emailing codes. A local TUI has no email infra; the TUI-equivalent capability (proving identity to the daemon) is served by PAT/daemon tokens, so no email-login gap. |
| Google OAuth login | none | oos | unchallenged | OAuth requires a browser redirect/callback and hosted client. Local daemon has no OAuth flow and no need for federated identity.evidence & judgmentWeb-only judgment — OAuth callback is intrinsically browser-bound; token-based local auth (F26) is the TUI-equivalent, so OAuth itself is oos not gap. |
| Logout | token revoke (F26 auth token revoke) | parity | adjusted | No session concept to log out of, but tokens can be revoked via CLI, which is the equivalent credential-invalidation capability.evidence & judgmentWeb-only judgment — Logout = invalidate credential. TUI-equivalent (revoke token) exists, so this is partial rather than oos; a one-shot 'auth logout' convenience verb is the only gap. Verification evidence — PatRepo::revoke hard-DELETEs token (token.rs:255-261), CLI `hangar auth token revoke` (mod.rs:347,1177-1188), tested revoke_pat_removes_row_and_verify_returns_none (repo_token.rs:209) + daemon-token (:328). No session/login table exists (migrations 0001-0010); auth is stateless PAT bearer (architecture.md:115,243), so revoke IS the complete credential-invalidation equivalent. |
| Auth route protection / redirect guard | capability gating + token auth on RPC | partial P3 | confirmed | No web routes to guard, but daemon RPC enforces workspace/secret capabilities and token auth, the equivalent access-control surface.evidence & judgmentWeb-only judgment — Route-redirect is a web-router concept; the underlying capability (deny unauthenticated access) is met by RPC capability gating, so partial not oos. Verification evidence — Real enforced access control: capability gate ainb-plugin-runtime/src/secret_store.rs:174-180 (-32001) and workspace-scope IDOR guard docs/hangar/architecture.md:35,133. But token auth is unwired: PatRepo::verify (repo/token.rs:215) called only in tests/repo_token.rs; daemon listener serve() rpc/mod.rs:119-131 + handle() rpc/mod.rs:187-293 authenticate no requests. No web routes to guard. |
| Mobile email login + verify | none | oos | unchallenged | Mobile app login screens have no TUI form factor.evidence & judgmentWeb-only judgment — Mobile-native screen, no terminal equivalent. |
| PAT & daemon tokens (browser JWT, mul_ PAT, mdt_ daemon) | PAT + daemon tokens (F26) | oos | adjusted | User PATs and hidden daemon tokens exist (hash-only), but there is no browser JWT tier (no web sessions).evidence & judgmentWeb-only judgment — The CLI-relevant token tiers (PAT + daemon) are present; the browser-JWT tier is oos (no web), so the feature is partial. Verification evidence — token.rs:46-63 TokenKind has only Pat(ainb_)/Daemon(mdt_); repo/token.rs pat+daemon_token tables sha256-only; cli/hangar/mod.rs:329-407 exposes auth token + daemon-token CLI. Zero jwt/cookie/web-session matches. Missing tier = browser cookie session, pure web form-factor with no TUI equivalent. |
| OS keychain secret store | keychain secret store (F24) + capability gating (F25) | parity | unchallenged | macOS/Linux keychain-backed secret store with zeroized bytes and secrets:read capability gating — a TUI-native superset of secret handling. |
| CLI & daemon · 9 rows | ||||
| CLI issue management | ainb hangar issue / task CLI (F01/F09) | partial P1 | confirmed | CLI covers create/list/show/assign and task list/cancel/retry, but not comment, label, subscribe, or search verbs (those features are absent).evidence & judgmentWeb-only judgment — Core issue CLI is present and native; missing verbs map to missing features (comment/label/search), so partial. Verification evidence — cli/hangar/mod.rs:413-421 IssueCommand={Create,List,Show} + assign flag :442; no comment/label/subscribe/search verb. But comment IS a tested layer: migration 0003_issue_comment.sql, tripwire_migrations_apply.rs:247, CommentRow/CommentAdded event_roundtrip_test.rs:46 (no CLI verb/RPC). label/search genuinely absent. |
| CLI agent / squad / skill / project / label / autopilot / runtime management | templates, skills, autopilot CLI (F18/F14/F23) | partial P1 | confirmed | Templates, skills, and autopilot CLIs exist; squad, project, label, and full agent/runtime management CLIs are absent.evidence & judgmentWeb-only judgment — CLI management is the native surface and partly present; the missing entity CLIs track missing features, so partial. Verification evidence — cli/hangar/mod.rs:67-99 HangarCommand enum has skills/templates/autopilot (dispatch 686/826/897) but no squad/project/label/agent CRUD; agent only via templates_use (869-894); AgentRuntimeRepo is read-helper for task list (1331-1351), not mgmt; squad marked unbuilt in hangar-plan.html:606-609 |
| CLI login (browser/token) | auth token CLI (F26) | partial P2 | confirmed | Token-based auth exists via CLI; a browser-login flow and status/logout convenience verbs are not present.evidence & judgmentWeb-only judgment — Token CLI auth is native and present; browser-login is oos but a 'login --token/status/logout' UX is a TUI-expressible gap, so partial. Verification evidence — cli/hangar/mod.rs:331-349 AuthCommand=Token{Create,List,Revoke}+DaemonToken; dispatch_auth:1118-1126 routes only those. No login/logout/status verb; rg for Login/Logout/browser-open/OAuth across hangar CLI+daemon+proto+core = none. Multica login=browser-OAuth+status (research 05:226, 06:192). |
| One-command setup | none (daemon start exists) | gap P2 | confirmed | No single 'setup' command that configures, authenticates, and starts the daemon; daemon must be started separately.evidence & judgmentWeb-only judgment — A setup wrapper command is pure CLI orchestration; absent, so gap. Verification evidence — cli/hangar/mod.rs:24-29 lists init+daemon-start as NOT-wired; mod.rs:516-519 DaemonCommand has only Status. plugins/hangar-tui/src/plugin.rs:20-21 says spawn_managed_subprocess (auto-start daemon) lands later; on_init:843-851 only dials pre-existing socket. tripwire_p4_common.rs:186-195 spawns daemon separately. No init/setup/onboard verb exists. |
| Self-update CLI | none in hangar (ainb has self-update elsewhere) | gap P3 | confirmed | No 'hangar update' self-update verb in the hangar CLI tree.evidence & judgmentWeb-only judgment — Binary self-update is CLI-native; not in the hangar surface, so gap. Verification evidence — hangar/mod.rs:68-99 verb tree (issue/task/beads/daemon/auth/config/skills/templates/autopilot/logs) has no update/upgrade; DaemonCommand only Status (mod.rs:516-519). registry.rs:928 update is `ainb plugin update`. 03-architecture-review.md:50 deliberately defers self-update to OS package manager. |
| Agent daemon (start/stop/status/restart/logs/disk) | daemon boot (F38) + daemon CLI + logs (F35) | partial P2 | confirmed | Daemon boots with migrations, has status and logs tail; start/stop/restart/disk-usage subcommands are not all confirmed present.evidence & judgmentWeb-only judgment — Daemon lifecycle is CLI-native; status+logs present, full start/stop/restart/disk set is partial. Verification evidence — hangar/mod.rs:514-519 DaemonCommand has only Status; mod.rs:26 defers daemon start|stop; logs is top-level `hangar logs tail` mod.rs:108-111; P9.md:281 `daemon start` unchecked [ ]; disk/du = 0 code hits (docs-only 05-ux-design-analysis.md:214). status proven live in proofs/cli-hangar-174.11.cast. 2/6 verbs present. |
| Workspace garbage collection | TTL sweepers for tasks (F07) | partial P3 | confirmed | TTL sweepers fail stale tasks, but there is no disk-reclaiming GC of done/orphaned workspace dirs and build artifacts.evidence & judgmentWeb-only judgment — Disk GC is a daemon loop (TUI/CLI-invisible but configurable); task-sweep present, dir/artifact GC is the gap, so partial. Verification evidence — execenv.rs:213 cleanup() = Full/ArtifactOnly(output build-artifacts)/OrphanScan(no .gc_meta.json, 72h grace execenv.rs:39); worktree.rs:114 cleanup_worktree rm -rf; tested execenv_layout.rs:134,147,165,188. But NO runtime wiring: run_loop.rs:137,181 spawn_sweepers runs only TTL row-sweepers; no scheduled OrphanScan/Full call, no RPC GC. So claim's status right, rationale ("no disk-reclaiming GC exists") wrong: it exists+tested, just unscheduled. |
| CLI repo checkout / Repository whitelist | none | gap P2 | confirmed | No repo checkout into managed workdir and no workspace repo-whitelist management.evidence & judgmentWeb-only judgment — Repo checkout + whitelist are CLI-native (Multica's are CLI); absent in Hangar, so gap. Verification evidence — No repos table (migrations 0001-0010); no repo/* or checkout RPC (proto/src/methods.rs full catalogue); hangar's own inventory 06-ainb-capability-inventory.md:307 "No workspace-level repo policy". worktree.rs prepare_worktree exists but has zero call sites in daemon run_loop/dispatch (unwired, test-only). |
| Daemon profiles (multiple isolated daemons) | none confirmed | gap P3 | confirmed | No profile mechanism for running multiple isolated daemons (separate config/state/port/workspace root) on one machine.evidence & judgmentWeb-only judgment — Daemon profiles are pure CLI/config, TUI-expressible; not present, so gap. Verification evidence — No --profile/registry/launcher anywhere. Daemon is single-instance-per-home: rpc/mod.rs:107 "only one daemon owns a given hangar home"; lib.rs:20 O_EXCL pidfile; snapshots.rs:520 "single-daemon". $AINB_HANGAR_HOME (store.rs:104, rpc/mod.rs:90) is a test seam, not a profile. Unix socket, no port; daemon start|stop deferred (mod.rs:26). |
| Infra & platform surfaces · 5 rows | ||||
| Live realtime updates (WebSocket feed) | workspace/subscribe event stream (F40); live streaming | parity | unchallenged | Plugin subscribes to a workspace and async events (TaskStarted/Finished, skill/autopilot updates) stream over the socket — equivalent realtime fabric. |
| Desktop app with embedded daemon | none | oos | unchallenged | Electron desktop shell with embedded web UI and auto-update has no TUI form factor.evidence & judgmentWeb-only judgment — A native Electron app is intrinsically a GUI shell; Hangar's whole premise is the TUI itself, so oos. |
| Mobile app | none | oos | unchallenged | Expo iOS app is a mobile-native client with no terminal equivalent.evidence & judgmentWeb-only judgment — Mobile-native client; no TUI form factor, oos. |
| Self-host via Docker Compose / Kubernetes | local daemon binary (F38) | oos | unchallenged | Hangar is a single local daemon binary by design; there is no multi-service web stack to containerize/orchestrate.evidence & judgmentWeb-only judgment — Compose/Helm exist to deploy a hosted web+Postgres stack; Hangar's local-daemon model needs no such deployment, so oos rather than a missing capability. |
| Product analytics (PostHog) / Prometheus metrics | Tracing JSONL sink (F31) + OTLP exporter (F32) + service spans (F33) | partial P3 | confirmed | Structured JSONL tracing, optional OTLP span export, and instrumented spans provide operational telemetry, but there is no PostHog product-analytics funnel or Prometheus counter set.evidence & judgmentWeb-only judgment — Operational telemetry is present via tracing/OTLP; product-analytics and Prometheus counters are TUI-adjacent gaps but low value for a local tool, so partial. Verification evidence — Tracing present: observability.rs:54-123 (OTLP seam), claim.rs:65 #[tracing::instrument name="task.claim"], architecture.md:67-68,253-255 (JSONL+OTLP+8 spans tested by it_otlp_export_when_endpoint_set.rs). ABSENT: zero posthog/prometheus/metrics-exporter deps in any hangar Cargo.toml/Cargo.lock; no .capture()/track_event/counter/gauge code — all grep hits are test/seed noise. OTLP is traces-only, no meter API. |
| Billing · 1 rows | ||||
| Cloud billing & top-ups | none | oos | unchallenged | Stripe checkout and billing portal are hosted web payment flows with no terminal equivalent.evidence & judgmentWeb-only judgment — Payment checkout/portal is intrinsically a browser PCI flow; no TUI-equivalent capability, so oos. |
Hangar is a faithful, often cleaner re-architecture of Multica's task FSM: idempotent single-UPDATE finalize, atomic per-agent-capped claim, parent_task_id retry chains gated to infra-only failures, cadence-preserving scheduler with catch-up-not-replay, and a TTL sweeper with a 90s reclaim band — all verified in code and matching Multica's load-bearing SQL invariants. The IO-free core/proto split and capability-gated host calls are genuinely better-factored than Multica's app+SQL blend. But three design-level gaps are real, not feature gaps: (1) the unix-socket RPC server has NO authentication and NO socket-permission hardening — the token mint/verify in core/token.rs is unwired, so any local process reaching the socket gets full multi-workspace control-plane access; (2) the "dual-channel event stream" is half-built — the plugin fully decodes hangar/event frames the daemon never emits (zero emission sites), so live UI is snapshot-pull-only despite the architecture claim; (3) hangar's idx_one_pending_task_per_issue serializes ONE pending task per issue globally, silently dropping Multica's deliberate per-(issue,agent) model that lets different agents work one issue in parallel. Hangar also has no OS-level agent sandbox (env-allowlist only) vs Multica's Codex Seatbelt config.
These are not missing features — they are holes in the design as built. The three headline items first, with code evidence; the remaining five below.
Unix-socket RPC server is unauthenticated and unhardened: bind() removes the stale file and binds with default perms; serve_conn dispatches every request with no token/peer-cred check. The token mint/verify in ainb-hangar-core/src/token.rs (sha256 + ConstantTimeEq) is never invoked on the request path. Any local process that can open the socket gets full control-plane access to every workspace. A feature-parity matrix scores 'tokens: implemented' and misses that they gate nothing.
ainb-hangar-daemon/src: zero auth-token use on the RPC path; bind() at rpc/mod.rs:104 removes the stale file and binds with default socket perms; serve()/handle() (rpc/mod.rs:119–293) dispatch every framed request with no token/peer-cred check. core/token.rs mint/verify (sha256 + ConstantTimeEq) is invoked only in tests. Independently re-verified by hand.The 'dual-channel event stream' is producer-absent. ainb-hangar-proto defines HangarEvent + the hangar/event notification method, and plugins/hangar-tui/src/stream.rs fully decodes those frames, but rg for EVENT_METHOD / HangarEvent:: / any notification write in ainb-hangar-daemon/src returns nothing. workspace/subscribe acks with an empty snapshot and comments that push is 'the stream client's concern'. Live updates therefore depend on snapshot re-pulls; the instant-feedback half of the design does not run.
ainb-hangar-daemon/src: zero HangarEvent / EVENT_METHOD emission sites. ainb-hangar-proto defines HangarEvent + the hangar/event notification and plugins/hangar-tui/src/stream.rs fully decodes those frames — but no daemon code ever sends one. Live UI is snapshot-pull-only. Independently re-verified by hand.Per-issue concurrency is a silent semantic regression. idx_one_pending_task_per_issue enforces one pending task per issue across ALL agents; Multica's ClaimAgentTask deliberately serializes per-(issue,agent) to let different agents work one issue in parallel. The parity matrix sees a dedupe index in both and marks parity, hiding that hangar forbids a concurrency pattern Multica designed for.
idx_one_pending_task_per_issue (partial unique index) enforces one pending task per issue across ALL agents. Multica’s ClaimAgentTask (pkg/db/queries/agent.sql) deliberately serializes per-(issue, agent) via a NOT EXISTS active-set guard, allowing different agents to work one issue in parallel.Coverage is strong but not yet "trust without manual testing." The tripwire harness is genuinely high-grade: real daemon binary + real SQLite WAL + real tmux + real ainb CLI, positive AND negative assertions (no substring-OR), skip-not-fail gates, exact-name kills, byte-level materialise asserts, poll-with-deadline (no bare sleep), run in CI on mac+ubuntu via run_all_tripwires.sh, and a meta-guard against silent deletion. Store-layer concurrency is real (tokio::join! races for both claim and finalize). What's missing is resilience and isolation: no daemon restart/recovery, no plugin/host crash recovery, no migration upgrade-from-populated-DB, no proof of multi-workspace DATA isolation (only the active-marker move is asserted), no daemon-level concurrent dispatch, no long-session soak, and every "happy path" uses fake-claude so no real provider is ever exercised. 44 features each have a path, but the unhappy/recovery quadrant is thin.
Real daemon binary + real SQLite WAL + real tmux + real ainb CLI; run in CI on mac+ubuntu via run_all_tripwires.sh, with a meta-guard against silently deleted legs.
Positive AND negative assertions (no substring-OR), skip-not-fail gates, exact-name kills, byte-level materialise asserts, poll-with-deadline (no bare sleep).
tokio::join! races for both claim and finalize prove the atomic-claim and idempotent-finalize invariants in-process.
All 44 catalogued Hangar features carry at least an acceptance test; most carry an e2e tripwire (see the full feature × coverage list below).
| ID | Feature | Area | Acceptance | e2e tripwire |
|---|---|---|---|---|
| F01 | Create / list / show issue (CLI) Create, list, and show Hangar issues; --assign enqueues a task for an agent runtime. | Issues | hangar_cli_integration.rs + cli::hangar parse | tripwire_hangar_issue_roundtrip (referenced in doc; issue roundtrip) |
| F02 | Persist issue + assignee Store layer persists issue rows with assignee, workspace-scoped, partial-unique one-pending-task-per-issue index. | Issues | repo_issue.rs | none (covered via issue roundtrip) |
| F03 | Issue list screen (nav / filter / create) Browse and filter issues (All/Members/Agents/Mine chips), c create, a assign agent, Enter open task detail. | Issues | issue_list_reducer_test.rs (7) | tripwire_p4_issue_list_renders.rs (tmux) |
| F04 | Kanban board (4 columns, card move) Four columns queued/running/done/failed; Shift+Left/Right drags focused card, firing a task transition RPC. | Issues | kanban_reducer_test.rs (9) + kanban_rpc_over_socket + snapshot_kanban_layout | tripwire_kanban_columns_render.rs (tmux) |
| F05 | Task FSM (claim / start / complete / fail / cancel) Strict task finite-state machine in core, enforced by store finalize services; idempotent terminal transitions. | Tasks | finalize_idempotency (22) + claim_task_integration + task_state_transitions + finalize_concurrent_complete_vs_cancel | tripwire_task_happy_path_claude_provider.rs (tmux) |
| F06 | Retry chain (parent/child, max-attempts) Retryable failure spawns child task linked by parent_task_id, capped by max_attempts; agent_error does not retry. | Tasks | retry_chain.rs (8) | none |
| F07 | TTL sweepers (stale queued/dispatched/running to failed) Stale queued (2h)/dispatched (5min)/running (2.5h) rows swept to failed in idempotent batches capped at 500. | Tasks | sweeper_ttls.rs (10) | tripwire_ttl_sweeper_fails_stale_dispatched.rs (real daemon socket) |
| F08 | Task detail + live transcript screen Live 5-colour transcript stream, PR badge, o open-in-browser, r retry / x cancel (confirm modal). | Tasks | transcript_reducer_test.rs (10) + transcript_render_snapshot (2) | tripwire_p4_task_detail_streams.rs (tmux) |
| F09 | Task CLI (list / cancel / retry) List pending tasks, cancel a task, spawn retry child for a retryable failed task. | Tasks | hangar_cli_integration + cli::hangar parse | none |
| F10 | Task-started banner Transient banner notifies when a task starts running, folded from TaskStarted event. | Tasks | banner_reducer_test.rs (6) | none |
| F11 | Agent picker (assign agent) Modal to pick a human or agent to assign; presence dots, / filter, recents pinned, Enter assign. | Agents | agent_picker_reducer_test.rs (8) | tripwire_p4_agent_picker_opens.rs (tmux) |
| F12 | agents_list snapshot Daemon answers hangar/agents_list from store, workspace-scoped, feeding the agent picker. | Agents | repo_agent.rs + rpc_server.rs | none (exercised in picker tripwire) |
| F13 | Skill repo CRUD (scoping, cascade) Workspace-scoped skill + skill_file + agent_skill M:N CRUD with cascade delete and cross-tenant guard. | Skills | skill_repo_tests.rs (9) + skill_service inline | none |
| F14 | Skills sync importer (idempotent) Imports toolkit/packages/skills tree into a workspace, idempotent on (workspace, name); dry-run supported. | Skills | tripwire_skills_sync_idempotent.rs (5, acceptance-style) + cli parse | screens_render_from_daemon (sync RPC); tripwire_skills_sync_idempotent (daemon-level, not tmux) |
| F15 | Skill manager screen (attach / detach / sync / view) Three-pane skill list/file-tree/detail; s sync, i/d attach/detach to selected agent, Enter view SKILL.md body, filter chips. | Skills | skill_manager_reducer_test.rs (9) + skill_screen_snapshot (2) | tripwire_p4_skill_manager_lists.rs (tmux) |
| F16 | Dispatch-time skill materialisation On claim, copies agent's attached skills to provider-native path (.claude/skills etc), chmod 0755, outside git root. | Skills | materialise_skills_tests.rs (8) | tripwire_skill_import_and_dispatch.rs (real daemon socket) |
| F17 | Curated agent templates (embedded, resolve) 10 curated agent templates baked into the binary; resolve instructions + bundled skill list from no database. | Templates | template_registry_tests.rs (5) | none |
| F18 | Templates list / show / use CLI List embedded templates, show one in full, use materialises a template into a live agent + skill attachments. | Templates | template_use_tests.rs (6) + cli parse | none |
| F19 | Autopilot cron CRUD (reject invalid cron) Create/list/enable/disable cron autopilots; invalid cron rejected before any row written (6-field, 5-field normalised). | Autopilots | repo_autopilot.rs (14) + cron.rs inline (12) | none |
| F20 | Autopilot scheduler fires on schedule Single daemon task sleeps until earliest next_tick_at; at fire time inserts run + enqueues task in one transaction. | Autopilots | scheduler_loop (5) + repo_autopilot_enqueue | tripwire_autopilot_fires_on_schedule.rs (real daemon socket) |
| F21 | Autopilot scheduler skips when in-flight At fire time, if concurrent runs >= max_concurrent_runs, tick is skipped and autopilot.tick_skipped emitted. | Autopilots | scheduler_loop::skip_when_prior_run_in_flight | tripwire_autopilot_skips_when_running.rs (real daemon socket) |
| F22 | Autopilots manager screen Lists autopilots + recent runs; a/e create/edit, r run-now, d enable/disable, selection loads runs. | Autopilots | autopilots_reducer_test.rs (6) + autopilots_screen_snapshot (4) + autopilot_rpc_over_socket (2) | tripwire_autopilots_screen_renders.rs (tmux; NEW, postdates architecture.md which lists this as a gap) |
| F23 | Autopilot CLI (create/list/disable/enable/run) Full CLI surface for cron autopilots including immediate manual tick via run. | Autopilots | hangar_autopilot_cli.rs (2) + parse | none |
| F24 | OS keychain secret store (get/set/delete) Secret store backed by macOS Security framework / Linux backend; zeroized SecretBytes; in-memory backend for tests. | Auth / Secrets | backend.rs (7) | tripwire_keychain_roundtrip.rs (#[ignore], dev-mac only) |
| F25 | secret_store_get capability gating Plugin host capability secrets:read gates host/secret_store_get; key allow-list; read-only, ungranted returns -32001. | Auth / Secrets | secret_store_cap.rs (5) | none |
| F26 | PAT / daemon tokens (hash-only) Create/list/revoke personal access tokens (sha256 stored, plaintext shown once); hidden daemon-token create. | Auth / Secrets | repo_token (11) + token.rs inline (3) + cli | none |
| F27 | Env allowlist (block LD_PRELOAD family) Allowlist governs ambient env vars passed to provider subprocess; hardcoded code-injection deny family always overrides. | Auth / Secrets | env_policy (5) + env_allow_config (3) + runner | tripwire_env_allowlist_blocks_ld_preload.rs + tripwire_env_allowlist_passes_home.rs (daemon-level) |
| F28 | danger-full-access first-run warning Warns about danger-full-access first time each provider invoked per session; y dismisses + records ack in state.toml. | Auth / Secrets | warnings.rs inline (4) | tripwire_warning_shown_on_first_provider_use.rs (tmux) |
| F29 | Workspace switching in Settings Set active / toggle default / create / rename workspaces via Settings; workspace:write capability gates host calls. | Auth / Secrets | settings_reducer_test + workspace_cap.rs (7) | tripwire_workspace_switch_e2e.rs (tmux) |
| F30 | Settings screen (sections, provider key entry) Four-section settings: provider keys (keychain write via n key entry) + workspace switching; section/row nav. | Auth / Secrets | settings_reducer_test.rs (9) + settings_workspace_render_snapshot | tripwire_p4_settings_renders.rs (tmux) |
| F31 | Tracing JSONL sink Daemon tracing wired to structured JSONL sink with daily rotation (daemon.<date>). | Observability | it_subscriber_writes_jsonl.rs (1) | none |
| F32 | OTLP exporter (otlp feature) Optional OTLP span export when endpoint set; zero crates linked in default build. | Observability | it_otlp_export_when_endpoint_set.rs (--features otlp) | tripwire_otel_export_when_endpoint_set.rs (daemon-level, feature-gated) |
| F33 | Instrumented service spans Eight task/service methods plus beads sync emit tracing spans for observability. | Observability | service_spans_emit + beads_sync_spans_emit | none |
| F34 | Daemon health pane + dual-dim sparkline Runtimes, claim-cache, concurrent tasks, throughput sparkline encoding height=total and red proportion=failure rate. | Observability | snapshot_daemon_health.rs (5) | tripwire_daemon_health_sparkline.rs (tmux) |
| F35 | Logs tail CLI + logs screen Tail daemon structured JSONL: CLI tail -f/--lines/--level; TUI screen with level-filter chips, colour-by-level. | Observability | logs.rs inline (8) + cli + snapshot_logs_screen (3) | tripwire_logs_screen_renders.rs (tmux; NEW, postdates architecture.md which lists this as a gap) |
| F36 | PR-URL capture into task result FSM finalize parses any gh pr create URL from the transcript into result.pr_url, surfaced in issues_list. | gh integration | pr_url_parse (10) + result inline + issues_list_pr_url (3) | tripwire_pr_capture.rs (real daemon socket) |
| F37 | PR badge + o open-in-browser Task detail shows a gold PR badge when result.pr_url is set; o raises OpenPrUrl only when a PR exists. | gh integration | pr_badge_snapshot (5) + pr_open_keybinding (3) | tripwire_pr_badge.rs (tmux) |
| F38 | Daemon boot + migrations apply Daemon binary boots, applies migrations 0001-0010 (16 tables) against the SQLite file. | Daemon / Transport | tripwire_migrations_apply.rs (16 tables) | tripwire_daemon_boots.rs (real daemon binary) |
| F39 | Unix-socket JSON-RPC + snapshots Unix-socket JSON-RPC 2.0 server answers 17 methods; snapshot RPCs scoped by resolved workspace id. | Daemon / Transport | wire_types (6) + rpc inline + rpc_server.rs | tripwire_hangar_plugin_connects.rs (plugin connects to real socket) |
| F40 | workspace/subscribe + event stream Plugin subscribes to a workspace; async events (TaskStarted/Finished, tick_skipped, skill updates) stream back. | Daemon / Transport | event_roundtrip (6) + stream_decode (8) + daemon_dial | tripwire_detects_daemon_drop (referenced; detects socket drop) |
| F41 | Cross-screen navigation Tab routing across screens (1 issues, 4 skills, , settings, etc) handled before any screen reducer. | Daemon / Transport | screen_router_test.rs (5) | tripwire_p4_cross_screen_navigation.rs (tmux) |
| F42 | Beads bidirectional sync Reconcile hangar issues with bd tracker: inbound + outbound sync, mapping table, drift repair, dry-run/json. | Daemon / Transport | beads_adapter/reconcile/inbound/outbound/cli (50+) | tripwire_beads_roundtrip.rs (real daemon socket) |
| F43 | Claude runner exec (env / exit / stream / timeout) Spawns claude binary in isolated ExecEnv, deny-by-default env, streams JSONL stdout, pins session_id, enforces timeout. | Daemon / Transport | runner_claude.rs (6) + execenv_layout | none (exercised in tripwire_task_happy_path_claude_provider) |
| F44 | Full-suite e2e meta-guard Meta-tripwire guards the Hangar tripwire suite against silent deletion / rename (count baseline). | Daemon / Transport | none | tripwire_full_e2e.rs (meta-count baseline) |
| Area | What’s missing | Suggested check |
|---|---|---|
| Daemon restart / crash recovery | No tripwire kills the running daemon mid-task and restarts it. A task in 'dispatched'/'running' when the daemon dies should be reclaimed or swept on restart; the WAL DB should survive an unclean kill. Today only TTL sweeps (F07) cover stale rows, and the daemon process is only ever torn down at end-of-test by exact-pid kill, never restarted against the same DB. | Spawn daemon, enqueue+claim a task, kill -9 the daemon pid mid-run, respawn a fresh daemon against the same $HOME/hangar.db, assert (a) DB opens cleanly post-unclean-WAL-kill and (b) the orphaned running/dispatched row reaches a terminal state (reclaimed or swept-to-failed) within budget. |
| Plugin crash / host reconnect | F40 references tripwire_detects_daemon_drop (socket drop seen by plugin) but there is no test for the inverse: plugin subprocess crashing and the host re-spawning/re-dialing, nor the macOS parent-death watcher added in commit 8494ad0f (orphaned-plugin prevention). A crashed plugin that leaves the host hung or a zombie plugin after host exit is untested end-to-end. | In a real tmux ainb-tui session on the Hangar screen, kill the hangar-tui plugin child pid, assert the host either re-spawns it and re-renders seeded rows OR shows a clean disconnected state (not a frozen pane). Separately: kill the ainb host and assert no orphaned hangar-tui process survives (validates the parent-death watcher). |
| Multi-workspace DATA isolation | tripwire_workspace_switch_e2e only proves the active ▶ marker moves to the second workspace slug; it seeds the second workspace EMPTY and never asserts that switching changes the visible issues/tasks/agents. Cross-tenant leakage (workspace A's issues showing under B) would pass today. reference_hangar_workspace_slug_vs_id explicitly warns that fixtures where id==slug mask the resolution bug. | Seed workspace A with issue 'Refactor API' and workspace B (acme) with a DISTINCT issue 'Acme Login Bug'. After switching to B, assert the issue list shows 'Acme Login Bug' and does NOT contain 'Refactor API' (and vice-versa). Add a store-layer test asserting agents_list/issues_list for ws A never returns ws B rows. |
| Schema migration upgrade-from-populated | tripwire_migrations_apply only proves migrations 0001-0010 apply to a FRESH SQLite DB (16 tables). There is no test that applies an older migration set, seeds real rows, then applies a newer migration on top — the actual upgrade path every existing user hits. A destructive or non-idempotent later migration against populated data is uncaught. | Build a DB at migration N, INSERT representative rows (workspace/issue/task/agent/skill/autopilot/token), then run apply_migrations to head and assert (a) no error, (b) seeded rows survive/transform correctly, (c) re-running apply_migrations a second time is a no-op (idempotent). |
| Daemon-level concurrent dispatch | Concurrency is proven only at the store layer (tokio::join! on claim/finalize in-process). No test runs a real daemon with N queued tasks and a high agent concurrency cap and asserts the claim loop dispatches them without double-claiming, exceeding max_concurrent_tasks, or deadlocking the WAL. The daemon-health tripwire drains a queue but doesn't assert the concurrency CAP is respected under contention. | Spawn one claim-enabled daemon, set agent max_concurrent_tasks=3, enqueue 10 tasks with a fake-claude that sleeps; poll and assert running count never exceeds 3 at any sample and all 10 reach terminal. Optionally spawn two daemons bound to the same runtime to prove only one claims each row. |
| Real provider + failure/timeout/retry chain end-to-end | Every dispatch tripwire uses fake-claude.sh; the real claude binary is never exercised (acceptable for CI determinism, but disclose it). More importantly, the retry chain (F06) and timeout enforcement (F43 timeout) have store-layer tests but NO end-to-end tripwire: a real daemon failing a task, spawning the parent_task_id child, capping at max_attempts, and an agent_error NOT retrying. | Spawn claim-enabled daemon with fake-claude that exits non-zero (retryable) then succeeds; assert a child task with parent_task_id is created, runs, and succeeds; assert agent_error variant does NOT spawn a child; assert a fake-claude that sleeps past HANGAR timeout is killed and failed with reason=timeout. |
| Create-flow through keystrokes (vs SQL-seeded state) | Almost all TUI tripwires seed state via direct SQL then assert the render. The actual user CREATE paths (issue create via 'c', autopilot create via 'a/e', key entry via 'n', card move via Shift+arrows firing the transition RPC) are covered by reducer unit tests but the keystroke->RPC->DB->re-render round trip is largely unproven in tmux. Multica drives every create through real UI clicks/forms and asserts the resulting toast + persisted row. | In tmux, press 'c' on the issue list, type a title, submit, and assert (a) a success indicator renders and (b) the row appears in the daemon DB. Same for Kanban Shift+Right: assert the focused card's task status actually transitioned in the DB, not just that the card moved on screen. |
| Long-session soak / leak / stream backpressure | No test runs the daemon+plugin for a sustained period under a steady event stream (TaskStarted/Finished/transcript chunks). Transcript streaming (F08), the event stream (F40), and the throughput ring (F34) could leak memory, drop events under backpressure, or unbounded-grow the transcript buffer over a long session — none is sampled beyond a few seconds. | Drive 500+ transcript/event messages over ~2 min through a live plugin session; assert no dropped sequence ids, bounded RSS growth, and the screen still responds to a nav key at the end. |
docs/hangar/verify-hangar-goal.md. Dropped into a fresh agent session at the repo root, it builds and stages the binaries + plugin, seeds two non-empty workspaces, walks all 44 features (F01–F44) through real CLI / tmux / daemon legs, runs the eight resilience legs above (R01–R08), and emits a per-feature PASS/SKIP/FAIL table with exit code = failed legs. Provider execution uses fake-claude (mocked), not the real binary — disclosed per the mocked-vs-live rule.serve()/handle() (rpc/mod.rs:119-293) dispatch every framed request with zero auth; core::token::verify (sha256+ConstantTimeEq) is invoked only in tests. Enforce SO_PEERCRED same-uid, chmod socket 0600 in bind(), require minted daemon_token on first frame via existing verify path. Any local process currently gets full control-plane access.
Plugin stream.rs fully decodes hangar/event frames but daemon emits zero — no HangarEvent::/EVENT_METHOD write anywhere in daemon/src; workspace/subscribe acks an empty snapshot. Live updates silently depend on snapshot re-polls. Add per-connection subscriber fanout in serve_conn at every finalize/claim/scheduler transition (SchedulerEvent tx.send seam exists), or document as snapshot-poll.
idx_one_pending_task_per_issue (migration 0006) forbids one pending task per issue across ALL agents; Multica serializes per-(issue,agent) to allow parallel agents on one issue. Either document single-agent-per-issue as a deliberate v1 divergence, or change the partial index to (issue_id,agent_id) and port the NOT EXISTS active-set guard into the claim SELECT.
CLAIM_SQL orders by created_at,id only — strict FIFO with no expedite path. Issue/task model has no priority field (events.rs:203-227, issue.rs:26-66). Add priority to schema + ORDER BY priority DESC, created_at. Cheap schema add now vs a migration on the hottest table later. Unblocks issue-create priority and group-by-priority work.
comment table (migration 0003:36) is never inserted; no repo/comment.rs; no comment method in ALL_METHODS (drift-guarded, verified); CommentRow/CommentAdded built only by test fixtures. task_detail.rs:424 renders them read-only with no compose key. Add a hangar/comment_add RPC, store insert, and a compose keybind so the core comment workflow is reachable end-to-end.
No mention parser over comment bodies and no comment-triggered task-spawn path; the core agent-collaboration loop is absent. After the comment write path lands, parse @agent mentions in new comments and enqueue a task for the mentioned agent. Depends on comment write-path bead.
No issue-mutation RPC in ALL_METHODS (verified); assignee picker intent is dropped (app_screens.rs:646-655 ignores out.intent), create is ignored (app_screens.rs:557), issue state only changes via beads sync. Add hangar/issue_update RPC + store update, route the picker intent, and persist create. Kanban moves tasks not issues, so issue field-edit has no surface today.
NewIssue/Issue (issue.rs:26-66), IssueRow (events.rs:203-227), and IssueCreateArgs (cli/hangar/mod.rs:425-443) carry title/desc/state/assignee only. Add priority/due-date/label fields to the store schema, wire type, CLI create, and 'c' inline create so created issues persist these attributes. Pairs with the priority/claim bead.
AgentRepo is insert/get/list only (agent.rs:55-94), no archived col, agents created only via `templates use`. agent model lacks model/thinking/CLI-args/MCP/per-agent-env (migration 0002:31-39). Add update/archive RPCs, an archived flag, and schema+config for model/args/MCP/thinking/per-agent env. Currently no general agent edit surface exists.
runner.rs:137-152 has one Provider impl (claude); run_loop.rs:284 unconditionally run_claude; cfg only claude_path. ProviderSkillLayout scaffolding exists+tested (materialise.rs:57-122) for codex/cursor/copilot/gemini. Wire real exec paths for at least one additional provider behind the Provider trait so the multi-backend product promise is met (1 of 12 today).
dispatch/runner give a 12-var env allowlist + deny family + process-group kill (secret-leak boundary) but no Seatbelt/seccomp/Landlock and no FS confinement — the agent subprocess has the daemon user's ambient FS/network access. Port a Seatbelt profile on macOS and Landlock/seccomp on Linux before codex/copilot land. Gates the multi-provider bead.
No test kills the running daemon mid-task and restarts against the same hangar.db. Spawn daemon, enqueue+claim a task, kill -9 mid-run, respawn against the same $HOME db, assert (a) WAL DB opens cleanly post-unclean-kill and (b) the orphaned running/dispatched row reaches a terminal state (reclaimed or swept-to-failed) within budget. Today only TTL sweeps cover stale rows; restart is never exercised.
workspace_switch_e2e only proves the ▶ marker moves and seeds workspace B empty. Seed A with 'Refactor API' and B with 'Acme Login Bug'; after switching to B assert the list shows 'Acme Login Bug' and NOT 'Refactor API' (and vice-versa). Add a store-layer test that issues_list/agents_list for ws A never returns ws B rows. Cross-tenant leakage passes today; id==slug fixtures mask the resolution bug.
Running agents stream to the live transcript (runner.rs:294) but write no durable issue comments — comment table untouched by daemon/runner. Once the comment write path exists, emit system-authored progress/blocker comments at runner checkpoints so the activity survives beyond the transcript buffer. Depends on the comment write-path bead.
No label table or issue-label join (migration 0003:19-43); IssueRow has no labels; only skill_attach/detach in RPC catalogue; 'label' hits are bd-sync flags. Add a label table + join, hangar/issue_label_attach|detach RPCs, and render label chips on the issue list/detail. Simple CRUD, very TUI-native.
member table carries owner/admin/member (migration 0001:23-28) but is data-only — seeded once as owner, zero member-mutation RPCs in catalogue, no member screen (settings = keys+workspace switch). Add hangar/members_list, set_role, remove_member RPCs and a Settings members pane. Covers manage-members, invite/accept/leave as the same CRUD surface where feasible.
issue_list.rs:152-234 has client-side `/` title-only contains filter; no server search RPC, no FTS/LIKE in migrations (only idx_issue_workspace_state). Add a hangar/issues_search RPC doing ranked title+desc+comment LIKE matching so search works beyond the loaded page and across description/comment bodies. Narrower than Multica today.
router.rs:34-62 global keys are tab/help/quit only; per-screen `/` filters one screen's loaded rows; no search RPC in the closed catalogue. Add a fuzzy palette overlay that searches issues/agents/skills/autopilots cross-entity via a search RPC and jumps to the selected entity. Classic TUI pattern, currently absent.
No Inbox in Screen enum (screen/mod.rs:44-69); HangarEvent is a live stream, not persisted/aggregated; no unread/read_at/mark_read in store. Add an aggregated inbox of issue/comment/task events with unread counts and a mark-read sweep (the host notifyd Inbox proves the pattern). TUI-viable like Logs/Kanban.
Zero squad construct: ActorKind={Member,Agent} (actor.rs:24-29), assignee_type CHECK IN('member','agent'), no squad table in any migration, no squad screen, daemon 'leader' refs are POSIX process-groups. Add a squad DB entity + members + leader-routing in the dispatcher and a squad status view. Plan already admits this is unbuilt. Covers assign-to-squad/CRUD/leader-routing/member-status family.
Autopilots are cron+manual only; daemon binds UnixListener only (lib.rs:74), no HTTP ingress; CreateAutopilot has no webhook/secret/filter fields (service.rs:38-99). Add a local HTTP ingress, per-autopilot webhook URL + HMAC signing secret + event filters, and a deliveries pane. Capability is server-side, not browser-bound. Covers token-rotation + delivery-inspection sub-gaps.
Only skip-when-running is implemented (scheduler.rs:38-42); queue/replace collapsed to an int; fire path hardcodes issue_id=NULL so create_issue vs run_only mode is absent (autopilot_run.rs:133). Add ExecutionMode {create_issue,run_only} and concurrency policy {skip,queue,replace} as config enums surfaced in the autopilot create/edit flow. Multica shipped all three.
DaemonCommand has only Status (mod.rs:514-519); start/stop deferred; no one-command setup; boot() never inserts runtimes (all agent_runtime inserts are test-only). Add daemon start/stop/restart verbs, a setup wrapper that configures+auths+starts, and runtime auto-register on boot. Covers one-command-setup + agent-daemon + bootstrap-runtime + onboarding-runtime-step family.
workspace table is id/slug/name/created_at only (migration 0001:10-15); no context_prompt/repo-whitelist/issue_prefix; execenv writes no CLAUDE.md and run_claude passes zero prompt args (runner.rs:192-205). Add per-workspace config fields injected at dispatch. Repo whitelist also gates the absent repo-checkout flow. TUI/CLI-expressible config.
RetryService gates on infra-failure reasons but has no exclusion set for conversation-poisoning terminals (iteration_limit, api_invalid_request, semantic_inactivity); an auto-retry that resumes the parent session can inherit a wedged conversation. Add an exclusion taxonomy mirroring Multica's GetLastTaskSession so poisoned sessions start fresh instead of resuming.
Concurrency is proven only at the store layer (in-process tokio::join!). Spawn one claim-enabled daemon, set agent max_concurrent_tasks=3, enqueue 10 sleep-tasks via fake-claude; poll and assert running count never exceeds 3 at any sample and all 10 reach terminal. Optionally spawn two daemons on the same runtime to prove only one claims each row. The daemon-health tripwire drains but never asserts the cap under contention.
Retry chain (F06) and timeout (F43) have store-layer tests but no e2e tripwire. With a claim-enabled daemon: fake-claude exits non-zero (retryable) then succeeds — assert a child task with parent_task_id is created and succeeds; assert agent_error does NOT spawn a child; assert a fake-claude sleeping past HANGAR timeout is killed and failed with reason=timeout.
Almost all TUI tripwires SQL-seed state then assert render; real create paths are only reducer-tested. In tmux: press 'c' on the issue list, type a title, submit, assert (a) a success indicator renders and (b) the row lands in the daemon DB. Same for Kanban Shift+Right: assert the focused card's task status actually transitioned in the DB, not just on screen.
migrations_apply only proves 0001-0010 against a FRESH db. Build a db at migration N, INSERT representative rows (workspace/issue/task/agent/skill/autopilot/token), apply_migrations to head and assert (a) no error, (b) seeded rows survive/transform, (c) re-running apply_migrations is a no-op. This is the real upgrade path every existing user hits; a destructive later migration is uncaught today.
F40 covers daemon-drop seen by plugin but not the inverse. In a real tmux ainb-tui Hangar session, kill the hangar-tui plugin child pid; assert the host re-spawns+re-renders seeded rows OR shows a clean disconnected state (not a frozen pane). Separately kill the ainb host and assert no orphaned hangar-tui process survives (validates the macOS parent-death watcher from commit 8494ad0f).
firstrun.rs has a danger-full-access warning and setup_menu.rs:24-30 covers deps/git/auth/editor/reset, but no source/role/use-case questionnaire or runtime-provisioning step. Add the guided questionnaire steps (TUI-expressible, wizard already exists) so first-run matches Multica's 5-step flow. The runtime auto-register half is covered by the daemon-lifecycle bead.
pr_url.rs:47 scrapes one PR URL; result carries only pr_url; task_detail renders a badge with open-in-browser. No ci_status/check_run/mergeable/conflict anywhere; done stamps on task exit not PR merge. Query gh for CI checks + mergeable state, render status on the badge, and optionally auto-transition to Done on merge. Webhook path is out of scope here.
daemon_health renders runtimes+claim_cache+concurrent_tasks+throughput sparkline only; DaemonHealthSnapshot has no token/cost/per-agent fields and never persists them; no usage method in the catalogue. Persist per-task token/cost, add a usage RPC, and render a daily + per-agent rollup pane (sparkline infra already exists). The host burndown plugin is unrelated to hangar.
execenv.rs:213 cleanup() (Full/ArtifactOnly/OrphanScan, 72h grace) and worktree cleanup are implemented+tested but never scheduled — spawn_sweepers (run_loop.rs:137,181) runs only TTL row-sweepers, no OrphanScan/Full call and no GC RPC. Wire a periodic GC tick so done/orphaned workspace dirs and build artifacts actually get reclaimed. Code exists; just unscheduled.
No test runs daemon+plugin under a sustained event stream. Drive 500+ transcript/event messages over ~2 min through a live plugin session; assert no dropped sequence ids, bounded RSS growth, and the screen still responds to a nav key at the end. Transcript buffer, event stream, and throughput ring could leak or drop under backpressure — none is sampled beyond seconds.