Help us improve
Share bugs, ideas, or general feedback.
From spacedock
Reconstructs project context from AI agent session history, reporting workflow, open decisions, and interruptions to quickly orient on returning to a brownfield repo with multiple in-flight tracks.
npx claudepluginhub spacedock-dev/spacedock --plugin spacedockHow this skill is triggered — by the user, by Claude, or both
Slash command
/spacedock:surveyThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
Survey is the first thing you run on unfamiliar ground: it reconstructs what the AI agents in this project have implicitly been doing, from their session history. It reports the inferred workflow, the workstreams, the recent decisions, and — load-bearing — the OPEN decisions (the abandoned or unanswered forks) plus how often the human had to step in. Then it offers to commission a real spacedoc...
Runs context-aware retrospectives auto-gathering git metrics, learnings, away-logs, and handoffs into pre-populated tables for interactive or agent-summary review.
Distills patterns from Claude Code work history, git logs, lessons, and memory to suggest new agents/skills, review roster quality, prune redundancies, or consolidate feedback into rules.
Displays a work status dashboard for AgentOps workflow, showing current tasks, recent activity, git state, and inbox. Use /status to get a single-screen overview.
Share bugs, ideas, or general feedback.
Survey is the first thing you run on unfamiliar ground: it reconstructs what the AI agents in this project have implicitly been doing, from their session history. It reports the inferred workflow, the workstreams, the recent decisions, and — load-bearing — the OPEN decisions (the abandoned or unanswered forks) plus how often the human had to step in. Then it offers to commission a real spacedock workflow with explicit gates from what it found.
It reads agentsview's session DB and is strictly read-only — the recommended queries live in references/queries.sql (one labeled query per concern) so nothing is a black box. The decision and interruption signals below are Claude Code's; Codex is surfaced too, as its own body section (a workdir-attributed count + workstream clusters + activity), since Codex sessions land with no recorded cwd and need the exec_command.$.workdir signal to be scoped to this repo. Gemini and per-file Codex work-by-area remain deferred follow-ups. The closing move is the discovery → commission bridge: the OPEN decisions become candidate gates, the workstreams become candidate entities, the inferred loop becomes the stage list — and the offer is keyed to each track's MODE (automation for mechanical tracks, book-keeping for exploration tracks).
Run the four steps in order: check agentsview → scan → recognize scaffold → report and offer.
This skill may run in a sandboxed agent that cannot read ~/.agentsview/ directly (macOS TCC denies raw FS access to a limited-permission process, even though the agentsview binary itself reads it). So do NOT sqlite3 ~/.agentsview/sessions.db blindly. Instead, drive the read through the agentsview binary into a process-readable data directory under AGENTSVIEW_DATA_DIR, then query that copy.
Probe for the binary by invoking it, not by walking PATH. A command -v agentsview (or which/test -x/stat) is an FS-access lookup the sandbox's Seatbelt can deny while still allowing execve — so the lookup false-negatives even though agentsview runs fine. agentsview --version is the execve that survives whatever the survey's real through-the-binary reads survive; >/dev/null 2>&1 suppresses both the present banner and the absent "not found" so only the AGENTSVIEW MISSING sentinel reaches the agent (silent ⇒ present; sentinel ⇒ absent):
SURVEY_DB_DIR="${SPACEDOCK_SURVEY_DB_DIR:-${TMPDIR:-/tmp}/spacedock-survey}"
DB="$SURVEY_DB_DIR/sessions.db"
if ! agentsview --version >/dev/null 2>&1; then echo "AGENTSVIEW MISSING"; fi
If it prints AGENTSVIEW MISSING: tell the user agentsview is needed (it ingests the agent logs this skill reads), ask consent, and only on a yes run the install (brew install --cask agentsview; fallback curl -fsSL https://agentsview.io/install.sh | bash). NEVER install without an explicit yes — stop at the consent prompt otherwise.
With the binary present, sync — but scope the CLAUDE source to this project. A bare agentsview sync enumerates the ENTIRE ~/.claude/projects history (16k+ sessions, growing): on a real machine that walk exhausts any sane timeout, so the survey data dir ends up empty or partial. The fix is to narrow Claude's source root to just this repo's session directories before syncing, so the walk is bounded and this project's Claude sessions land in seconds. The narrowing applies ONLY to Claude (CLAUDE_PROJECTS_DIR); the same agentsview sync ALSO walks the other agents' default source dirs unscoped — notably Codex (~/.codex/sessions), which is what populates the codex-presence count in step 2 (Codex is a small backlog, so leaving it unscoped is cheap). You always run this sync as part of the survey — see below.
mkdir -p "$SURVEY_DB_DIR"
# Claude Code stores each project's sessions in ~/.claude/projects/<cwd-with-/-as->,
# so this repo's sessions live under dirs that begin with the dash-encoded cwd. Point
# CLAUDE_PROJECTS_DIR at a symlink farm of just those dirs — the sync then walks only
# this project's Claude sessions (this cwd plus its worktrees), not the whole backlog.
CLAUDE_ROOT="${CLAUDE_PROJECTS_DIR:-$HOME/.claude/projects}"
DASH_CWD=$(pwd | sed 's#/#-#g') # ~/.claude/projects dir-name convention
NARROW="$SURVEY_DB_DIR/claude-narrow"
rm -rf "$NARROW"; mkdir -p "$NARROW"
# Enumerate this project's Claude session dirs with find, not a shell glob: an unmatched
# `…-*` glob is a HARD error under zsh's default nomatch (and the skill runs under
# whatever shell the user has). find matches the exact dash-encoded cwd dir plus every
# `<cwd>-*` worktree sibling, and an empty match links nothing without erroring — so the
# "no Claude sessions for this project" path still flows to step 2's "no agent history".
find "$CLAUDE_ROOT" -maxdepth 1 -type d \( -name "$DASH_CWD" -o -name "$DASH_CWD-*" \) -print0 2>/dev/null |
while IFS= read -r -d '' d; do
ln -s "$d" "$NARROW/$(basename "$d")"
done
AGENTSVIEW_DATA_DIR="$SURVEY_DB_DIR" CLAUDE_PROJECTS_DIR="$NARROW" timeout 300 agentsview sync
Always run this sync as part of the survey — never query a pre-existing SURVEY_DB_DIR without re-syncing first. The data dir persists between runs so the re-sync is incremental (seconds), but it is the sync that refreshes BOTH this project's Claude sessions AND the Codex backlog into the DB. A persisted dir left over from an earlier survey can be missing newer sessions — or, if it predates Codex on disk, missing Codex entirely — so the step-2 codex-presence count would read a stale 0. The incremental sync backfills them (no --full needed); skipping the sync is what produces a wrong 0. Do not pass --full — a full resync re-ingests everything and can fill the disk. If the symlink farm is empty (this project has no Claude sessions under ~/.claude/projects), the synced DB has no Claude history for it; step 2 reports "no agent history" and stops.
If agentsview sync fails (network, disk, permissions), report the exact failure and stop — do not fall back to raw ~/.agentsview/ reads (they fail under TCC).
Scope by repo IDENTITY, not by project name. agentsview keys each session's project by the git-root basename (normalized: non-alphanumerics → _), so EVERY checkout of one repo — the root, a subdir, a worktree, the split-root state dir — already shares the SAME project key. The risk is the INVERSE: that basename key COLLIDES with a same-basename sibling repo elsewhere on disk, so a project = <basename> filter would fold an unrelated repo's sessions into this one. Resolve the repo root once, then scope every query by the absolute cwd-prefix so a same-basename sibling stays out:
# Repo-root identity: the parent of the common .git dir resolves to the SAME absolute
# path from the repo root, a subdir, the state checkout, or a linked worktree — so the
# cwd-prefix admits every checkout of THIS repo while excluding a same-basename sibling
# elsewhere on disk. --path-format=absolute needs git >= 2.31; if this is not a git repo
# or git is older, fall back to the cwd itself (today's behavior).
if REPO_GIT=$(git rev-parse --path-format=absolute --git-common-dir 2>/dev/null); then
REPO_ROOT=$(dirname "$REPO_GIT")
else
REPO_ROOT=$(pwd)
fi
Bind REPO_ROOT as the :repo_root parameter of the queries in references/queries.sql. The session-scoping subquery there matches cwd = :repo_root OR cwd LIKE :repo_root || '/%' — the cwd AT the root and everything strictly under it. Because project collides across same-basename repos, this absolute cwd-prefix is the ONLY column that keeps a same-basename sibling like …/other/proj out of scope. Run a labeled query by name (the SQL stays in the reference file; this only extracts and runs it):
SURVEY_SKILL_DIR="${SPACEDOCK_SURVEY_SKILL_DIR:-skills/survey}"
QUERIES="$SURVEY_SKILL_DIR/references/queries.sql"
# :repo_project is the git-root basename agentsview keys `project` by, with the SAME
# normalization agentsview applies (non-alphanumerics → `_`, e.g. spacedock-v1 →
# spacedock_v1). It is read ONLY by codex-presence (Codex sessions land cwd='', so they
# can't be cwd-prefix-scoped — they match by project name alone).
REPO_PROJECT=$(printf '%s' "$(basename "$REPO_ROOT")" | tr -c '[:alnum:]' '_')
run_query() { # run_query <name> — :repo_root → REPO_ROOT, :repo_project → REPO_PROJECT
local q
q=$(awk -v n="$1" '$0=="-- name: "n{f=1;next} /^-- name: /{f=0} f && $0 !~ /^--/{print}' "$QUERIES")
printf ".param set :repo_root '%s'\n.param set :repo_project '%s'\n%s\n" "$REPO_ROOT" "$REPO_PROJECT" "$q" | sqlite3 "$DB"
}
run_query scoping # #318 — sessions|blank_cwd|span over the cwd-prefix-scoped repo
run_query codex-presence # #69 — flagged Codex count|blank_cwd by project NAME (cwd unrecorded)
run_query codex-scoped # #321 — Codex attributed by exec_command.$.workdir prefix (sibling-free)
run_query codex-workstreams # #322 — cluster codex-scoped sessions into ensign-task workstreams
run_query codex-activity # #323 — exec_command/update_plan/spawn_agent tally over the codex-scoped set
run_query scaffold-usage # #319 — behavioral skill_name family tally (spacedock self EXCLUDED)
run_query work-by-area # #317.2 — Edit/Write file_path → LOGICAL area (worktree prefix stripped) + kind
run_query decision-open # #320 — AskUserQuestion/ExitPlanMode frontier; OPEN sorts first
run_query mode-classification # #324 — classify each git_branch track mechanical/exploration/unlabeled
scoping returns sessions=0 → there is no Claude agent history for this repo; say so and stop. Nothing to discover. (Survey reads Claude history only for now; a repo whose only agent history is Codex/Gemini will report "no agent history" here — surfacing those agents is a deferred follow-up.) Note the blank_cwd count in the report if non-zero (sessions agentsview never captured a cwd for, which the repo-root scope cannot place).
codex-presence returns the count of agent='codex' sessions matching this repo's project name, plus the blank-cwd sum among them. Because agentsview does not persist Codex cwd, matches are by git-root-basename project only — a same-basename sibling repo will collide, so the count may include unrelated Codex sessions. Treat it as a presence flag only; these sessions are never counted in the Claude scoping, scaffold-usage, or work-by-area sets. The body Codex section is built from the codex-scoped set below instead — when codex-presence > codex-scoped, the gap is the name-only superset (possible sibling), and the report says so.
Codex body signals (codex-scoped, codex-workstreams, codex-activity). agentsview persists no Codex session cwd, but a Codex session's exec_command tool calls carry $.workdir (the absolute working directory of each shell command). codex-scoped attributes a Codex session to THIS repo when it has an exec_command whose $.workdir is under the repo-root prefix — the workdir analogue of the Claude cwd-prefix scope, so it admits this repo's Codex and EXCLUDES a same-basename sibling (whose workdirs fall under a different prefix). Over that sibling-free set, codex-workstreams clusters the sessions into ensign-task workstreams from each first_message (the runnable 3-case rule: dispatch-file read → {TASK} with the stage suffix stripped; Spacedock task/entity backtick → the backtick-quoted {TASK}; else (unlabeled)), and codex-activity tallies the per-tool activity (exec_command/update_plan/spawn_agent). These are the Codex body section (step 4) — the workstreams surface real Codex tracks the Claude-only body misses. All from the agentsview DB; no raw-rollout parsing. (Per-file Codex work-by-area and a source-health signal are deferred — they need an upstream agentsview ingestion change.)
Track modes (mode-classification). mode-classification labels each git_branch track mechanical (low veto + gate-pass-dominant + issue→worktree→PR loop markers + code edits), exploration (high veto + a rejected/cancelled path + prose/.md edits), or unlabeled (neither signature clearly dominant). The report reads the per-track mode to label WORKSTREAMS and to pick the right commission offer per track (step 4) — automation for mechanical, book-keeping for exploration, generic book-keeping for unlabeled (never a guessed automation pitch).
Honest signal accounting. The decision-open rows are the human-decision points; OPEN = still needs the human, and you lead the report with those. For the interruption total, count the AskUserQuestion / ExitPlanMode decisions plus the hard-veto markers Claude sessions retain ([Request interrupted / Request interrupted by user / doesn't want to proceed in the message stream), over the same repo-scoped session set; pct = total*100/user_turns. Never dress an empty section up as "no decisions" — if a section is empty, say the run found none of that signal.
Recognize the scaffold from TWO signals and reconcile them — a file probe (what is installed on disk) and the behavioral tally (what actually ran), which is the scaffold-usage query you already ran in step 2. A file-only probe misses a scaffold that was invoked but isn't checked in; a tally-only read misses one installed but never used. Join them.
File probe — multi-label, not a single winner. Probe each scaffold INDEPENDENTLY and name EVERY match; report none only when no probe matched (the old single-winner if-ladder hid co-installed scaffolds):
.claude/skills/superpowers exists, superpowers appears in .claude-plugin/, or a superpowers discipline skill dir is present (.claude/skills/{brainstorming,writing-plans,executing-plans,subagent-driven-development,…});.claude/skills/gsd or .claude/skills/get-shit-done dir, a .claude/commands/gsd dir, or a GSD.md / gsd.md / .gsd file;.claude/skills or .claude/commands tree (name the dirs you found);Behavioral tally. The scaffold-usage rows are a family → invocations tally normalized from tool_calls.skill_name (superpowers:brainstorming and the bare running-research-spikes both fold to family superpowers); the spacedock family is excluded because survey/ensign self-invocation otherwise dominates and would make every repo read as "uses spacedock".
Join and state the fact. For each family appearing in either signal, state two observed facts plainly: its invocation count (the behavioral tally) and whether it is checked in on disk (the file probe). Do not narrate HOW the family was discovered (behavior vs files) — state only the usage + on-disk fact. For a family invoked but absent from disk, state the count and that it is not checked in. For example:
superpowers: 186 invocations (not checked in). Other one-offs: plan-writing, using-git-worktrees, systematic-debug, simplify, debugging.
A family on disk and invoked is stated plainly (family + count + present on disk); a family on disk but never invoked is stated as installed-but-not-yet-invoked. The state-the-fact statement drives the comparative benefit in the report (step 4). The probe reads files; the numbers come from the scan (step 2).
Every {slot} below is a FILL slot: substitute the real value from the step-2 scan before you show the user. A literal {slot} (or a <…> angle token) left in what you present is a bug — never show the user an unfilled slot. If a slot has no data (e.g. zero OPEN decisions), drop that line rather than printing an empty slot.
Cross-check the OPEN frontier against the repo (before you present it). The decision-open query is a TRANSCRIPT-only scan — a fork that read OPEN there may already be shipped (a merged PR / a commit) and over-reports. For each transcript-OPEN fork, cross-reference the repo (git log, merged PRs via gh pr list --state merged if available, the working tree) and split it:
NEEDS YOU frontier.Conservative-match rule. DROP only on a CONFIDENT repo match; anything less than confident → KEEP on the frontier. A false "still open" is a cheap nudge; a false "shipped" silently hides a real open fork — so the asymmetry favors keeping.
Mandatory degrade. When NO repo signal is available (not a git repo, or git log / PR lookup fails or is empty), the frontier degrades to transcript-only and EVERY OPEN fork is flagged unverified in the report — never silently presented as authoritative. The degrade is the default behavior, not an error.
Lead with the one-line headline, then render the body DIRECTLY in the same turn — do NOT stop and ask first. The survey is read-only orientation: the body IS the value, and a pre-body confirm/menu is a round-trip with no decision behind it (and risks ending with no survey at all). The ONLY stop in this flow is the end-of-report commission OFFER (the real decision). So emit the headline and flow straight into the synthesis fence:
Found {N} sessions in
{project}({date range}), with {D} decision points and {V} interruptions — here's the lay of the land:
PROJECT: {basename} {sessions} Claude sessions · {date range}
{if blank_cwd>0: {blank_cwd} uncaptured-cwd sessions}
CODEX (only if codex-scoped>0; workdir-attributed, distinct from the name-only presence flag)
{codex_scoped_sessions} Codex sessions attributed to this repo by exec_command working dir
{if codex-presence>codex-scoped: (codex-presence matches {codex_sessions} by project NAME only — may include a same-named sibling; the workdir-attributed count above is sibling-free)}
workstreams: {the codex-workstreams clusters — workstream → session count; (unlabeled) last}
activity: {the codex-activity tally — exec_command {n}, update_plan {n}, spawn_agent {n}}
SCAFFOLD
{state-the-fact per family: family + invocation count + on-disk presence — e.g. "superpowers: 186 invocations (not checked in). Other one-offs: …"; or "none"}
INFERRED WORKFLOW
{the implicit loop across the decisions + prompts, as an arrow chain} — {one honest line}
WORKSTREAMS mode
{cluster the decisions + prompts into tracks; one line each, status glyph + the mode-classification label (mechanical / exploration / unlabeled) per track}
WORK BY AREA (logical areas; worktree edits attributed to their area — F)
{the product work-by-area buckets (kind=product), by edit count: area — {edits}}
{if any kind=config: (+ {sum} edits in .claude/.beads/.git config + <external> sibling refs, footnoted)}
NEEDS YOU (only if any decision is still OPEN after the repo cross-check)
{if any OPEN exploration track: exploration (tracked, prioritized — work you're holding, not bottlenecks):}
◐ {the open EXPLORATION forks — deliberately-held threads}
{if any OPEN mechanical track: mechanical (automatable backlog — gate-and-drive candidates):}
⚠ {the open MECHANICAL forks — never-decided questions}{if degraded: each flagged unverified (no repo signal)}
BACKLOG (only if any fork was decided-not-shipped)
{decided forks with no shipped artifact yet}
RECENT DECISIONS (answered or shipped)
{the rest: header — short question}
INTERRUPTIONS
{if any exploration track: exploration tracks: {n} steers across {m} sessions — this IS the work; book-keeping tracks the threads}
{if any mechanical track: mechanical tracks: {n} steps across {m} sessions — gates + autonomy would carry these between your calls}
After the synthesis, offer spacedock — but key the offer to the MODE of each track (from the mode-classification query). Two modes call for two DIFFERENT things; do NOT make one undifferentiated automate-everything pitch. As in the synthesis above, every {slot} is a FILL slot: substitute the real step-2 numbers/forks/track-names before you show the user; a literal {slot} in your output is a bug.
For the MECHANICAL tracks (mode=mechanical) — offer AUTOMATION. These are disciplined routine execution (the issue→worktree→PR loop, routine implementation): gate the crucial decisions and let the agent drive the loop between gates. Keep the gate-and-autonomy pitch — it is CORRECT for these. State it tied to the scan (the mechanical tracks' names + their gate-pass count or the interruption count). The per-scaffold flavor sharpens the automation offer:
For your MECHANICAL tracks ({the mechanical track names}): a spacedock workflow that gates the crucial decisions and lets the agent drive the loop between gates — these passed {the gate-pass count}, so the agent can carry them and stop only where you marked a gate.
For the EXPLORATION tracks (mode=exploration) — offer BOOK-KEEPING, never automation. These are human-driven creative/exploratory work (writing/content, design exploration, steering an agent that drifts): the involvement IS the point. Offer spacedock as structure for the parallel threads — track each draft/path and its state (in-flight / paused-by-choice / abandoned) so several run in parallel without losing which is which. An open thread is tracked-prioritized work, NOT a bottleneck; a cancelled path is a valid tracked outcome, NOT a failure. The exploration offer MUST NOT contain "advances on its own", "without you re-driving each", "minimize involvement", or any automate-the-human-out framing.
For your EXPLORATION tracks ({the exploration track names}): spacedock as book-keeping — track each draft/design path and its state (in-flight / paused-by-choice / abandoned) so you run several in parallel without losing which is which. The {the cancelled-path count} cancelled paths are tracked outcomes, not failures; the involvement is the point, so there's no automation here — just structure for the threads.
For UNLABELED tracks (mode=unlabeled) — generic book-keeping, never a guessed automation pitch (the asymmetry favors not mis-offering: a missed automation offer is cheap; a wrong automation pitch at creative work is the misread to avoid).
If a project carries BOTH modes, make BOTH offers (they MUST differ — the mechanical one keeps the gate-and-drive pitch; the exploration one carries none of the automate-the-human-out framing). If it carries only one mode, make only that offer. none scaffold → the generic spacedock benefit, mode-keyed the same way. Each offer must cite a real scan number (filled track names, gate-pass count, OPEN forks, or cancelled-path count), not a placeholder.
Then make the offer:
Want me to commission a spacedock workflow from this{if both modes: — gated automation for the mechanical tracks, thread book-keeping for the exploration tracks}?
On a yes, invoke commission in batch mode, supplying inputs derived from the scan (commission already accepts batch design inputs in its first message — see its Batch Mode). Assemble:
Hand off by invoking commission with those assembled inputs. Survey does NOT generate workflow files itself — file generation stays commission's job; survey only assembles the invocation and hands off.
On a no, stop — the survey stands on its own as an orientation.
PROMPTS are sparse/noisy — secondary). Be honest when a track is one-off or stalled.OPEN = still needs the human; lead the report with the true-open forks. The transcript scan can't tell shipped from open — that's what the step-4 repo cross-check is for; drop a fork to "shipped" only on a confident match, and flag the whole frontier unverified when there's no repo signal.work-by-area buckets say WHAT this project is (where edits land), by LOGICAL area regardless of physical location — a worktree edit is attributed to its area (a worktree src/ edit is src), so worktree-based product work is not hidden. The lead lists product areas (kind=product) by edit count; genuine config (.claude/.beads/.git) and an <external> sibling-repo path demote to a footnote (kind=config) — still counted, just not the project's identity. Report it separately from the decision frontier (where you stop).mode-classification labels each track mechanical / exploration / unlabeled. Mechanical tracks (the issue→worktree→PR loop) get the automation offer (gate-and-drive); exploration tracks (creative/content/design steering) get the book-keeping offer (track the parallel threads + their states) — the involvement IS the point, so NO automation pitch for them; an unlabeled track gets generic book-keeping, never a guessed automation pitch.{slot} in the report and the comparison comes from the step-2 numbers; a literal {slot} shown to the user is a bug. If a section's signal is empty (no OPEN decisions, no interruptions, no edits), say the run found none — never dress an empty section up as "no decisions."codex-scoped set: the workdir-attributed count + the workstream clusters + the activity tally (Gemini and per-file Codex work-by-area remain deferred follow-ups). A repo whose ONLY history is Codex still reports "no agent history" at the scoping=0 stop (the Claude body has nothing); the Codex section renders alongside a non-empty Claude body, not in place of it.