harness/plugins/work-manager/common/skills/explore/SKILL.md
Research phase before implementation. Given a list of entry points, spawn one subagent per entry point in parallel and write refactor-oriented research artifacts into the work-manager notes directory (`<notes-dir>/research/`). Use when the user says "explore", "research these entry points", or provides a list of files/symbols to investigate before starting a task. To render the resulting flows as an interactive HTML, use the `explore-flow-map` skill.
npx skillsauth add popoffvg/dotfiles exploreInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Build a shared understanding of unknown code before the task is implemented. The user provides N entry points; you spawn N subagents in parallel, each producing artifacts so a downstream agent (or human) can navigate the territory without re-reading the codebase.
Output lives inside the work-manager notes directory for the current task so it persists with the rest of the planning context (plan.md, worklog.md, todos/) and ships with the work, instead of vanishing from $TMPDIR.
A list of entry points. Each may be:
src/server/index.ts)HandleRequest, userController.create)If user provides a free-form description, use cocoindex to find relevant entry points.
<notes-dir> is the work-manager notes directory for the active task (commonly .notes/). Resolve it from the phase context:
.notes/ at repo root)../.notes/ in the current working directory and tell the user.--notes-dir <path>.Create the research subdirectory:
NOTES_DIR="${NOTES_DIR:-_notes}"
RESEARCH_DIR="$NOTES_DIR/research"
mkdir -p "$RESEARCH_DIR"
Per entry point <ep-slug>, three files in $RESEARCH_DIR/:
| File | Purpose |
|---|---|
| <ep-slug>.questions.md | Grill-phase questions the explorer must answer |
| <ep-slug>.md | Scannable refactor-oriented write-up. Follow the mandatory structure in "MD artifact structure" below. Every claim links to path:line. |
| <ep-slug>.workflow.ts | Typed TS pseudocode — clean, no inline paths (see "Workflow TS schema" below) |
| <ep-slug>.bindings.json | ULID → real source for the notable if branches in the workflow (see schema) |
Plus, shared across the research dir (write once, append as entry points are explored):
| File | Purpose |
|---|---|
| components/<name>.d.ts | Typed declaration of each app component referenced in pseudocode, with a @source tag to its real (possibly non-TS) source. Powers autocomplete + reveal. |
| _flow.entities.d.ts | Shared ambient types used by the workflow files (Request, Response, domain types, the binding shapes) |
| tsconfig.json | Includes the workflow files + components/*.d.ts + _flow.entities.d.ts so the editor's TS server offers autocomplete/diagnostics |
Plus, after all subagents finish:
| File | Purpose |
|---|---|
| flows.json | Aggregated flows document — input for explore-flow-map |
| INDEX.md | One-line summary + links per entry point |
The previous $TMPDIR/claude-explore/ location is deprecated.
The .md is read by humans planning refactors. Prose paragraphs are forbidden as the primary form — use the headings below in this order. Each section is short, scannable, and citation-dense.
Use markdown links for code references: [packageName|typeName.functionName](path:line)
# <Title> — <scope one-liner>
**Scope.** What this doc covers. **Out of scope.** What it doesn't.
## Terms
The table contains terms used in the workflow and the related area.
## TL;DR
ASCII flow diagram — the whole pipeline at a glance, using `→`, `│`, `▼`, branches with `├─`/`└─`. No prose.
## 1. Workflow steps
Numbered table: | # | Step | File:line |. One row per atomic operation in happy-path order.
If there are parallel paths (e.g. prerun + main), use a second sub-table per path.
## 2. Decision points
Each decision is `DP-N` with:
- **Condition** (the exact predicate / field check)
- **Branches** as a small table or bullets, each with effect + file:line
- Cross-link to relevant EC-N if a branch has a known edge case
## 3. Identity / data carriers (when relevant)
Table: what value carries identity at each layer, how equality is defined, which fields are mutated vs immutable.
## 4. Per-variant shapes (when relevant)
Table of shape + conflict key + conflict resolution for each polymorphic case (dataset type, resource kind, message variant, …).
## 5. Edge cases
Numbered `EC-N` table: | # | Case | Effect | Source (file:line) |.
Be adversarial: empty inputs, races, partial failure, encoder ambiguity, duplicate keys, deleted resources, iteration-order non-determinism, cache staleness.
## 6. Refactor risks (hotspots)
Table: | Hotspot | Why it bites |. One row per surface that future changes will trip on — coupling, hidden invariants, non-deterministic ordering, missing rollback, silent overwrites, undocumented contracts.
## 7. File map
Table: | File | Role |. Every file referenced anywhere above.
## Grill answers
Numbered answers to every question from `<ep-slug>.questions.md`, in the same order. This is the verification trail.
Rules.
path:line must be verified — open the file before you cite it.DP-1, EC-1, …) so other docs and TODOs can reference them.Each <ep-slug>.workflow.ts is typed TS pseudocode written as an imperative function that reads top-to-bottom like the real code path. It is clean TypeScript — no inline // path:line clutter. Real source is bound out-of-band, in two layers, so the file stays readable, type-checks, and autocompletes in the editor, while every step can still be revealed to its real (possibly non-TS) source.
The file exports:
meta object with name + description.import type { Request, Response, Body } from "./_flow.entities";
import { RunnerController } from "./components/runner-controller";
export const meta = {
name: "handle-request", // matches <ep-slug>
description: "HTTP request lifecycle from router to response.",
};
// Imperative pseudocode. Reads top-to-bottom like the actual code path.
// Components come from typed ./components/*.d.ts (autocomplete + reveal).
// Notable branches carry a trailing ULID that resolves via <ep-slug>.bindings.json.
export function flow(req: Request): Response {
const body = RunnerController.parseBody(req);
if (!validate(body)) { // 01J9F2K8QFABCDEFGHJKMNPQRS
return reject(400, "invalid body");
}
const handler = RunnerController.dispatch(body.action);
if (!handler) { // 01KSZ1B5TZK8YD8PEC7X2CDV5N
return reject(404, "no route");
}
try {
const result = handler(body);
return write(200, result);
} catch (e) { // 01J9F2K8QH1234567890ABCDEF
log.error("handler crashed", { action: body.action, e });
return reject(500, "internal");
}
}
Rules:
components/*.d.ts + _flow.entities.d.ts. No raw paths inside the code, no /* ... */ blobs hiding logic.RunnerController) is referenced via an import from ./components/<name>.d.ts. This is what gives autocomplete. Declare the component in its .d.ts (below) the first time you use it.steps[] graph, no id/calls indirection.if, switch, early return, throw, async fan-out is shown.// <ULID> comment (generate with ~/.claude/scripts/flow-ulid.mjs). Map each ULID to its real source in <ep-slug>.bindings.json. Plain control-flow scaffolding needs no ULID — only the branches worth revealing.db.x, redis.x, emit, log, fs, http calls — don't hide them inside helpers.components/<name>.d.tsOne declaration file per app component referenced in pseudocode. Declares the API (for autocomplete) and binds each symbol to its real source with a @source <path:line> JSDoc tag. The real source may be any language (Go, Rust, …) and any repo — the path lives in the tag, not in a TS declaration map.
Prefer absolute paths in @source (unambiguous and repo-independent — the component may live in a different repo than the workflow notes). A relative path is resolved against the open Zed worktree root.
/** Runner controller — orchestrates job execution.
* @source /Users/me/git/pl/pkg/runner/controller.go:120 */
export declare class RunnerController {
/** @source /Users/me/git/pl/pkg/runner/parse.go:44 */
static parseBody(req: Request): Body;
/** @source /Users/me/git/pl/pkg/runner/dispatch.go:88 */
static dispatch(action: string): Handler;
}
<ep-slug>.bindings.jsonMaps each notable-branch ULID to its real source.
{
"01J9F2K8QFABCDEFGHJKMNPQRS": { "kind": "if", "label": "invalid body", "source": "/Users/me/git/pl/pkg/server/validate.go:10" },
"01KSZ1B5TZK8YD8PEC7X2CDV5N": { "kind": "if", "label": "no route", "source": "pkg/server/router.go:104" }
}
Prefer an absolute source (used verbatim). A relative source resolves against the open Zed worktree root ($ZED_WORKTREE_ROOT); set "repo": "<abs-path>" on an entry to override the base for that one branch.
@source and each bindings.json source before writing it. Then run the lint:
~/.claude/scripts/flow-reveal.mjs check <research-dir> — fails if any ULID/@source points at a missing path or past-EOF line.tsconfig.json in the research dir makes the editor type-check + autocomplete the workflow files:
{ "compilerOptions": { "noEmit": true, "checkJs": false, "module": "esnext", "moduleResolution": "bundler" },
"include": ["*.workflow.ts", "components/*.d.ts", "_flow.entities.d.ts"] }
if line → reveal key opens its real source via the ULID; for a component, cmd-click jumps into its .d.ts, then the reveal key on that line opens the @source. Both run flow-reveal.mjs reveal. See .config/zed/tasks.json + keymap.json.Cross-reference: the .md artifact's "Workflow steps" table is the human-readable index (markdown path:line links); this .workflow.ts + its bindings.json/components are the machine-readable, navigable spec. They must agree on the cited locations.
flows.jsonAfter all subagents finish, combine their workflows into a single document at $RESEARCH_DIR/flows.json that the explore-flow-map skill renders as interactive HTML. Schema:
{
"packages": [
{ "id": "web", "label": "Web Frontend", "kind": "app", "path": "apps/web" },
{ "id": "api", "label": "API Server", "kind": "service", "path": "services/api" },
{ "id": "db", "label": "Postgres", "kind": "store" }
],
"flows": [
{
"id": "invite-user",
"label": "Invite new user",
"description": "Admin invites a teammate; an email is queued and the user row is pre-created.",
"edges": [
{ "from": "web", "to": "api", "via": "POST /invites", "payload": "{ email, role }", "source": "apps/web/src/Invite.tsx:84" },
{ "from": "api", "to": "db", "via": "INSERT users", "payload": "{ id, email, status: 'invited' }", "source": "services/api/users.ts:201" },
{ "from": "api", "to": "mail","via": "queue invite-email","payload": "{ token, url }", "source": "services/api/users.ts:230" }
]
}
]
}
Rules: every from/to references a package id. Every source is a verified path:line. Derive packages[].id from the // path:line anchors inside each <ep-slug>.workflow.ts function (top-level dir or repo package of the cited file).
Write $RESEARCH_DIR/INDEX.md so the planner can find each artifact at a glance:
# Research index — <task slug>
Generated: <ISO date>
| Entry point | Slug | Artifacts | Summary |
|---|---|---|---|
| `src/server/index.ts` | server-index | [md](server-index.md) · [workflow](server-index.workflow.ts) · [questions](server-index.questions.md) | HTTP request lifecycle from router to response |
| `HandleRequest` | handle-request | [md](handle-request.md) · [workflow](handle-request.workflow.ts) · [questions](handle-request.questions.md) | Dispatch + middleware chain |
**Aggregated flows:** [flows.json](flows.json) — render with `/flow-map`.
TASK_SLUG.<notes-dir>. Prefer the active work-manager notes dir; fall back to ./_notes/. Create $RESEARCH_DIR = $NOTES_DIR/research if missing.$RESEARCH_DIR/INDEX.md exists, ask the user whether to append, overwrite, or bail. Don't silently overwrite previous research.claude CLI per entry point to produce $RESEARCH_DIR/<ep-slug>.questions.md. These are questions the explorer must answer — not questions for the user.Agent tool calls). Brief each with:
$RESEARCH_DIR, absolute path)<ep-slug>.questions.md — explorer must answer each question in a ## Grill answers section at the bottom of the .md artifactpath:line by reading the file — do not guess"$RESEARCH_DIR/flows.json (schema above). Deduplicate packages by id.$RESEARCH_DIR/INDEX.md (template above).<notes-dir>/worklog.md if it exists./flow-map against $RESEARCH_DIR/flows.json for an interactive HTML view.The explorer subagent works better when it has a sharp question list. We use grill-me not to interview the user but to interrogate the entry point itself: a separate Claude Opus CLI process is asked to act as a paranoid reviewer and produce the questions the explorer must answer.
For each entry point, spawn one Haiku CLI call (these can run in parallel via shell &):
EP="<entry-point>"
EP_SLUG="<ep-slug>"
QFILE="$RESEARCH_DIR/$EP_SLUG.questions.md"
cat <<PROMPT | claude --model haiku --print --output-format text > "$QFILE"
/grill-me
You are NOT interviewing a human. You are grilling the codebase entry point below to generate a research agenda for another agent (the "explorer") that will read the code and answer your questions.
Entry point: $EP
Task context: <one-line task description>
The explorer will use your questions to populate a refactor-oriented artifact organised as: workflow steps, decision points (DP-N), edge cases (EC-N), refactor hotspots. Bias your questions so the explorer is forced to surface those.
Produce a markdown file with sections:
## Workflow-step questions
- What are the ordered atomic steps from entry to exit on the happy path?
- For each step, what is the exact file:line and what state does it touch?
## Decision-point questions
- What every \`if\`/\`switch\`/dispatch table branches on; what fields it checks; what each branch does differently.
- Where the code forks into parallel paths (prerun/main, sync/async, fast-path/slow-path) and what carries identity across the fork.
## Edge-case questions (adversarial)
- Empty inputs, single-element inputs, duplicate keys, key collisions.
- Race conditions, iteration-order non-determinism, concurrent mutation.
- Partial failure mid-loop: what state has already been mutated and is there a rollback?
- Deleted/missing referenced resources, stale caches, encoder ambiguity (same input → different serialisation?).
- Silent overwrites, silent drops (\`continue\` on missing field), asymmetric branches across similar handlers.
## Identity & invariant questions
- What is "identity" at each layer (handle string, resource ID, hash, axis key, …) and how is equality defined?
- Which fields are mutable vs locked-after-creation? Which are part of dedup keys vs annotations?
## Refactor-hotspot questions
- Which surfaces couple multiple files (schema + dispatcher + handler)?
- Which contracts are implicit (cache key formula, iteration order, name canonicalisation)?
- Where would a future change most likely cause silent data loss or stale cache?
## Surprises / gotchas
- What would a new contributor most likely get wrong?
Each question must be answerable by reading the code. Be specific and adversarial — assume the code has hidden complexity. No questions for humans.
PROMPT
Outputs: one <ep-slug>.questions.md per entry point in $RESEARCH_DIR. These files become required input to the matching explorer subagent.
Notes:
--print keeps it non-interactive: Opus generates the question list and exits.claude CLI is unavailable, the main agent invokes grill-me in-session (sequentially per entry point) to produce the same .questions.md files.subagent_type: "Explore" for read-only investigation. If the entry point needs deeper reasoning (cross-file design questions), use general-purpose.$RESEARCH_DIR path.explore-research saves coarse findings as <notes-dir>/research-*.md. explore complements that with per-entry-point deep dives under <notes-dir>/research/.plan / plan-todo-prepare may reference research/<ep-slug>.md#DP-N or #EC-N from a TODO's Pre-reads so the implementer doesn't re-derive the analysis.research/ is committed alongside plan.md and todos/ — it travels with the task.testing
Use when the user asks to create test sets, enumerate scenarios, generate edge cases, or draft a coverage matrix before implementation.
testing
Use when the user asks to review, audit, score, or validate test sets for missed cases before execution or merge.
tools
Test harness plugins in isolation using tmux panes. Runs MCP servers, unit tests, typecheck, and Claude plugin loading. Use when user says "test plugin", "check plugin", "run plugin tests", "validate plugin", or names a specific plugin to test.
development
Guide for designing integration and e2e tests using BDD (Behavior-Driven Development) methodology with Cucumber-style Given/When/Then scenarios. Use when writing or reviewing tests for any service, API, or component. Language-agnostic — covers scenario structure, step notation, assertion principles, async patterns, and common anti-patterns.